[ARVADOS] created: 888e08c4bd321745607ca35cd71fa6c53ece0405
Git user
git at public.curoverse.com
Fri Aug 11 15:47:25 EDT 2017
at 888e08c4bd321745607ca35cd71fa6c53ece0405 (commit)
commit 888e08c4bd321745607ca35cd71fa6c53ece0405
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 15:30:30 2017 -0400
12033: Add top nav link to multisite search.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/views/layouts/body.html.erb b/apps/workbench/app/views/layouts/body.html.erb
index 3315027..a2256a0 100644
--- a/apps/workbench/app/views/layouts/body.html.erb
+++ b/apps/workbench/app/views/layouts/body.html.erb
@@ -29,6 +29,11 @@ SPDX-License-Identifier: AGPL-3.0 %>
<% if current_user %>
<% if current_user.is_active %>
<li>
+ <%= link_to(controller: 'collections', action: 'multisite') do %>
+ Multisite search (beta)
+ <% end %>
+ </li>
+ <li>
<form class="navbar-form" role="search"
data-search-modal=
"<%= url_for(
commit 14edbf1a2327edf9e797928f6095153f22b486be
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 11:43:49 2017 -0400
12033: Fix double slash in href.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 6167b2c..9e1db75 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -46,7 +46,7 @@ window.components.collection_table = {
m('tbody', [
vnode.attrs.loader.displayable.map(function(item) {
return m('tr', [
- m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, 'Show')),
+ m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'collections/'+item.uuid}, 'Show')),
m('td.arvados-uuid', item.uuid),
m('td', item.name || '(unnamed)'),
m('td', m(window.components.datetime, {parse: item.modified_at})),
commit a20ea2d9b6b861829d9daa91990ce064b1000170
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 11:40:08 2017 -0400
12033: Log out and back in to a site without forgetting it.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index a06e2bb..6167b2c 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -119,7 +119,9 @@ window.components.collection_search = {
? m('span.label.label-xs.label-danger', 'none')
: Object.keys(sessions).sort().map(function(key) {
return [m('span.label.label-xs', {
- className: vnode.state.loader.pagers[key].items() ? 'label-info' : 'label-default',
+ className: !vnode.state.loader.pagers[key] ? 'label-default' :
+ vnode.state.loader.pagers[key].items() ? 'label-success' :
+ 'label-warning',
}, key), ' ']
}),
' ',
diff --git a/apps/workbench/app/assets/javascripts/components/sessions.js b/apps/workbench/app/assets/javascripts/components/sessions.js
index 3ff9f4d..19a481d 100644
--- a/apps/workbench/app/assets/javascripts/components/sessions.js
+++ b/apps/workbench/app/assets/javascripts/components/sessions.js
@@ -18,36 +18,46 @@ window.components.sessions = {
var db = vnode.state.db
var sessions = db.loadAll()
return m('container', [
- m('table.table.table-condensed.table-hover', m('tbody', [
- Object.keys(sessions).map(function(uuidPrefix) {
- var session = sessions[uuidPrefix]
- return m('tr', [
- session.token && session.user ? [
- m('td', session.isFromRails ? null : m('a.btn.btn-xs.btn-default', {
+ m('table.table.table-condensed.table-hover', [
+ m('thead', m('tr', [
+ m('th', 'status'),
+ m('th', 'cluster ID'),
+ m('th', 'username'),
+ m('th', 'email'),
+ m('th', 'actions'),
+ m('th'),
+ ])),
+ m('tbody', [
+ Object.keys(sessions).map(function(uuidPrefix) {
+ var session = sessions[uuidPrefix]
+ return m('tr', [
+ session.token && session.user ? [
+ m('td', m('span.label.label-success', 'logged in')),
+ m('td', {title: session.baseURL}, uuidPrefix),
+ m('td', session.user.username),
+ m('td', session.user.email),
+ m('td', session.isFromRails ? null : m('button.btn.btn-xs.btn-default', {
+ uuidPrefix: uuidPrefix,
+ onclick: m.withAttr('uuidPrefix', db.logout),
+ }, 'Log out ', m('span.glyphicon.glyphicon-log-out'))),
+ ] : [
+ m('td', m('span.label.label-default', 'logged out')),
+ m('td', {title: session.baseURL}, uuidPrefix),
+ m('td'),
+ m('td'),
+ m('td', m('a.btn.btn-xs.btn-primary', {
+ uuidPrefix: uuidPrefix,
+ onclick: db.login.bind(db, session.baseURL),
+ }, 'Log in ', m('span.glyphicon.glyphicon-log-in'))),
+ ],
+ m('td', session.isFromRails ? null : m('button.btn.btn-xs.btn-default', {
uuidPrefix: uuidPrefix,
- onclick: m.withAttr('uuidPrefix', db.logout),
- }, 'log out')),
- m('td', m('span.label.label-info', 'logged in')),
- m('td', {title: session.baseURL}, uuidPrefix),
- m('td', session.user.username),
- m('td', session.user.email),
- ] : [
- m('td', m('a.btn.btn-xs.btn-info', {
- uuidPrefix: uuidPrefix,
- onclick: m.withAttr('uuidPrefix', db.login),
- }, 'log in')),
- m('td', 'span.label.label-default', 'logged out'),
- m('td', {title: session.baseURL}, uuidPrefix),
- m('td'),
- m('td'),
- ],
- m('td', session.isFromRails ? null : m('a.glyphicon.glyphicon-trash', {
- uuidPrefix: uuidPrefix,
- onclick: m.withAttr('uuidPrefix', db.trash),
- })),
- ])
- }),
- ])),
+ onclick: m.withAttr('uuidPrefix', db.trash),
+ }, 'Remove ', m('span.glyphicon.glyphicon-trash'))),
+ ])
+ }),
+ ]),
+ ]),
m('.row', m('.col-md-6', [
m('form', {
onsubmit: function() {
diff --git a/apps/workbench/app/assets/javascripts/models/loader.js b/apps/workbench/app/assets/javascripts/models/loader.js
index 0a3181b..17b2ad5 100644
--- a/apps/workbench/app/assets/javascripts/models/loader.js
+++ b/apps/workbench/app/assets/javascripts/models/loader.js
@@ -65,7 +65,7 @@ window.models.MultisiteLoader = function(config) {
// displayed).
lowWaterMark: 23,
})
- var sessions = loader.sessionDB.loadAll()
+ var sessions = loader.sessionDB.loadActive()
m.stream.merge(Object.keys(sessions).map(function(key) {
var pager = new window.models.Pager(loader.loadFunc.bind(null, sessions[key]))
loader.pagers[key] = pager
diff --git a/apps/workbench/app/assets/javascripts/models/session_db.js b/apps/workbench/app/assets/javascripts/models/session_db.js
index 13d3eaf..b64481e 100644
--- a/apps/workbench/app/assets/javascripts/models/session_db.js
+++ b/apps/workbench/app/assets/javascripts/models/session_db.js
@@ -20,6 +20,14 @@ window.models.SessionDB = function() {
}
return all
},
+ loadActive: function() {
+ var sessions = db.loadAll()
+ Object.keys(sessions).forEach(function(key) {
+ if (!sessions[key].token)
+ delete sessions[key]
+ })
+ return sessions
+ },
save: function(k, v) {
var sessions = db.loadAll()
sessions[k] = v
@@ -50,6 +58,14 @@ window.models.SessionDB = function() {
document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href.replace(/\?.*/, '')+'?baseURL='+encodeURIComponent(baseURL))
return false
},
+ logout: function(k) {
+ // Forget the token, but leave the other info in the db so
+ // the user can log in again without providing the login
+ // host again.
+ var sessions = db.loadAll()
+ delete sessions[k].token
+ db.save(k, sessions[k])
+ },
checkForNewToken: function() {
// If there's a token and baseURL in the location bar (i.e.,
// we just landed here after a successful login), save it and
commit c97e090a3c26363f750903edb86e422c43b95233
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 10:05:19 2017 -0400
12033: Fix logging in from location with non-empty query string.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/models/session_db.js b/apps/workbench/app/assets/javascripts/models/session_db.js
index fde02f6..13d3eaf 100644
--- a/apps/workbench/app/assets/javascripts/models/session_db.js
+++ b/apps/workbench/app/assets/javascripts/models/session_db.js
@@ -47,7 +47,7 @@ window.models.SessionDB = function() {
baseURL = 'https://' + baseURL
if (!baseURL.endsWith('/'))
baseURL = baseURL + '/'
- document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href+'?baseURL='+encodeURIComponent(baseURL))
+ document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href.replace(/\?.*/, '')+'?baseURL='+encodeURIComponent(baseURL))
return false
},
checkForNewToken: function() {
commit 6e1eda9ff559bb61df8880c8f53aca008aa95c9c
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 10:03:30 2017 -0400
12033: Ensure current Rails login is always in sessions list.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/sessions.js b/apps/workbench/app/assets/javascripts/components/sessions.js
index be4b7e3..3ff9f4d 100644
--- a/apps/workbench/app/assets/javascripts/components/sessions.js
+++ b/apps/workbench/app/assets/javascripts/components/sessions.js
@@ -23,7 +23,7 @@ window.components.sessions = {
var session = sessions[uuidPrefix]
return m('tr', [
session.token && session.user ? [
- m('td', m('a.btn.btn-xs.btn-default', {
+ m('td', session.isFromRails ? null : m('a.btn.btn-xs.btn-default', {
uuidPrefix: uuidPrefix,
onclick: m.withAttr('uuidPrefix', db.logout),
}, 'log out')),
@@ -41,7 +41,7 @@ window.components.sessions = {
m('td'),
m('td'),
],
- m('td', m('a.glyphicon.glyphicon-trash', {
+ m('td', session.isFromRails ? null : m('a.glyphicon.glyphicon-trash', {
uuidPrefix: uuidPrefix,
onclick: m.withAttr('uuidPrefix', db.trash),
})),
diff --git a/apps/workbench/app/assets/javascripts/models/session_db.js b/apps/workbench/app/assets/javascripts/models/session_db.js
index 75fe6f9..fde02f6 100644
--- a/apps/workbench/app/assets/javascripts/models/session_db.js
+++ b/apps/workbench/app/assets/javascripts/models/session_db.js
@@ -6,15 +6,27 @@ window.models = window.models || {}
window.models.SessionDB = function() {
var db = this
Object.assign(db, {
- loadAll: function() {
+ loadFromLocalStorage: function() {
try {
return JSON.parse(window.localStorage.getItem('sessions')) || {}
} catch(e) {}
return {}
},
+ loadAll: function() {
+ var all = db.loadFromLocalStorage()
+ if (window.defaultSession) {
+ window.defaultSession.isFromRails = true
+ all[window.defaultSession.user.uuid.slice(0, 5)] = window.defaultSession
+ }
+ return all
+ },
save: function(k, v) {
var sessions = db.loadAll()
sessions[k] = v
+ Object.keys(sessions).forEach(function(key) {
+ if (sessions[key].isFromRails)
+ delete sessions[key]
+ })
window.localStorage.setItem('sessions', JSON.stringify(sessions))
},
trash: function(k) {
diff --git a/apps/workbench/app/controllers/sessions_controller.rb b/apps/workbench/app/controllers/sessions_controller.rb
index f72b451..48fbc6b 100644
--- a/apps/workbench/app/controllers/sessions_controller.rb
+++ b/apps/workbench/app/controllers/sessions_controller.rb
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: AGPL-3.0
class SessionsController < ApplicationController
- skip_around_filter :require_thread_api_token, :only => [:destroy, :index]
- skip_around_filter :set_thread_api_token, :only => [:destroy, :index]
- skip_before_filter :find_object_by_uuid, :only => [:destroy, :index]
+ skip_around_filter :require_thread_api_token, :only => [:destroy, :logged_out]
+ skip_around_filter :set_thread_api_token, :only => [:destroy, :logged_out]
+ skip_before_filter :find_object_by_uuid
skip_before_filter :find_objects_for_index
skip_before_filter :ensure_arvados_api_exists
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index 71b1cd1..b59bad4 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -18,6 +18,11 @@ SPDX-License-Identifier: AGPL-3.0 %>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<meta name="description" content="">
<meta name="author" content="">
+ <% if current_user %>
+ <% content_for :js do %>
+ window.defaultSession = <%=raw({baseURL: Rails.configuration.arvados_v1_base.sub(/\/arvados\/v1$/, '/'), token: Thread.current[:arvados_api_token], user: current_user}.to_json)%>
+ <% end %>
+ <% end %>
<% if current_user and $arvados_api_client.discovery[:websocketUrl] %>
<meta name="arv-websocket-url" content="<%=$arvados_api_client.discovery[:websocketUrl]%>?api_token=<%=Thread.current[:arvados_api_token]%>">
<% end %>
commit 16231ef0f56005b487cde9a7fb7dfb62952205a6
Author: Tom Clegg <tom at curoverse.com>
Date: Fri Aug 11 01:23:07 2017 -0400
12033: Fix auto scroll after search; acknowledge end of results.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 19f2fd2..a06e2bb 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -4,30 +4,36 @@
window.components = window.components || {}
window.components.collection_table = {
- oncreate: function(vnode) {
- vnode.state.autoload = function() {
- if (!vnode.attrs.loader.loadMore)
- // Can't load more content anyway: no point in
- // checking anything else.
- return
- var contentRect = vnode.dom.getBoundingClientRect()
- var scroller = window // TODO: use vnode.dom's nearest ancestor with scrollbars
- if (contentRect.bottom < 2 * scroller.innerHeight) {
- // We have less than 1 page worth of content available
- // below the visible area. Load more.
- vnode.attrs.loader.loadMore()
- // Indicate loading is in progress.
- window.requestAnimationFrame(m.redraw)
- }
+ maybeLoadMore: function(dom) {
+ var loader = this.loader
+ if (loader.done || !loader.loadMore)
+ // Can't load more content anyway: no point in
+ // checking anything else.
+ return
+ var contentRect = dom.getBoundingClientRect()
+ var scroller = window // TODO: use dom's nearest ancestor with scrollbars
+ if (contentRect.bottom < 2 * scroller.innerHeight) {
+ // We have less than 1 page worth of content available
+ // below the visible area. Load more.
+ loader.loadMore()
+ // Indicate loading is in progress.
+ window.requestAnimationFrame(m.redraw)
}
- window.addEventListener('scroll', vnode.state.autoload)
- window.addEventListener('resize', vnode.state.autoload)
- vnode.state.autoloadTimer = window.setInterval(vnode.state.autoload, 200)
+ },
+ oncreate: function(vnode) {
+ vnode.state.maybeLoadMore = vnode.state.maybeLoadMore.bind(vnode.state, vnode.dom)
+ window.addEventListener('scroll', vnode.state.maybeLoadMore)
+ window.addEventListener('resize', vnode.state.maybeLoadMore)
+ vnode.state.timer = window.setInterval(vnode.state.maybeLoadMore, 200)
+ vnode.state.onupdate(vnode)
+ },
+ onupdate: function(vnode) {
+ vnode.state.loader = vnode.attrs.loader
},
onremove: function(vnode) {
- window.clearInterval(vnode.state.autoloadTimer)
- window.removeEventListener('scroll', vnode.state.autoload)
- window.removeEventListener('resize', vnode.state.autoload)
+ window.clearInterval(vnode.state.timer)
+ window.removeEventListener('scroll', vnode.state.maybeLoadMore)
+ window.removeEventListener('resize', vnode.state.maybeLoadMore)
},
view: function(vnode) {
return m('table.table.table-condensed', [
@@ -48,7 +54,7 @@ window.components.collection_table = {
}),
]),
m('tfoot', m('tr', [
- m('th[colspan=4]', m('button.btn.btn-xs', {
+ vnode.attrs.loader.done ? null : m('th[colspan=4]', m('button.btn.btn-xs', {
className: vnode.attrs.loader.loadMore ? 'btn-primary' : 'btn-default',
style: {
display: 'block',
diff --git a/apps/workbench/app/assets/javascripts/models/loader.js b/apps/workbench/app/assets/javascripts/models/loader.js
index 1dc7079..0a3181b 100644
--- a/apps/workbench/app/assets/javascripts/models/loader.js
+++ b/apps/workbench/app/assets/javascripts/models/loader.js
@@ -10,7 +10,7 @@ window.models.Pager = function(loadFunc) {
done: false,
items: m.stream(),
thresholdItem: null,
- loadNextPage: function() {
+ loadMore: function() {
// Get the next page, if there are any more items to get.
if (pager.done)
return
@@ -53,6 +53,7 @@ window.models.MultisiteLoader = function(config) {
Object.assign(loader, config, {
// Sorted items ready to display, merged from all pagers.
displayable: [],
+ done: false,
pagers: {},
loadMore: false,
// Number of undisplayed items to keep on hand for each result
@@ -68,7 +69,7 @@ window.models.MultisiteLoader = function(config) {
m.stream.merge(Object.keys(sessions).map(function(key) {
var pager = new window.models.Pager(loader.loadFunc.bind(null, sessions[key]))
loader.pagers[key] = pager
- pager.loadNextPage()
+ pager.loadMore()
// Resolve the stream with the session key when the results
// arrive.
return pager.items.map(function() { return key })
@@ -112,14 +113,15 @@ window.models.MultisiteLoader = function(config) {
if (!loader.pagers[key].done)
loadable.push(loader.pagers[key])
})
- if (loadable.length == 0)
+ if (loadable.length == 0) {
+ loader.done = true
loader.loadMore = false
- else
+ } else
loader.loadMore = function() {
loader.loadMore = false
loadable.map(function(pager) {
if (pager.items().length - pager.itemsDisplayed < loader.lowWaterMark)
- pager.loadNextPage()
+ pager.loadMore()
})
}
})
commit ea9b417bc727f859878f17898571ec9f997e4f11
Author: Tom Clegg <tom at curoverse.com>
Date: Thu Aug 10 23:35:20 2017 -0400
12033: Load more results automatically on scroll.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 4f995af..19f2fd2 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -4,6 +4,31 @@
window.components = window.components || {}
window.components.collection_table = {
+ oncreate: function(vnode) {
+ vnode.state.autoload = function() {
+ if (!vnode.attrs.loader.loadMore)
+ // Can't load more content anyway: no point in
+ // checking anything else.
+ return
+ var contentRect = vnode.dom.getBoundingClientRect()
+ var scroller = window // TODO: use vnode.dom's nearest ancestor with scrollbars
+ if (contentRect.bottom < 2 * scroller.innerHeight) {
+ // We have less than 1 page worth of content available
+ // below the visible area. Load more.
+ vnode.attrs.loader.loadMore()
+ // Indicate loading is in progress.
+ window.requestAnimationFrame(m.redraw)
+ }
+ }
+ window.addEventListener('scroll', vnode.state.autoload)
+ window.addEventListener('resize', vnode.state.autoload)
+ vnode.state.autoloadTimer = window.setInterval(vnode.state.autoload, 200)
+ },
+ onremove: function(vnode) {
+ window.clearInterval(vnode.state.autoloadTimer)
+ window.removeEventListener('scroll', vnode.state.autoload)
+ window.removeEventListener('resize', vnode.state.autoload)
+ },
view: function(vnode) {
return m('table.table.table-condensed', [
m('thead', m('tr', [
commit 9ff99b73aa200fa21bfcad1ae8a7098b593864bb
Author: Tom Clegg <tom at curoverse.com>
Date: Thu Aug 10 15:01:13 2017 -0400
12033: Style uuid column.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 527ce6c..4f995af 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -16,7 +16,7 @@ window.components.collection_table = {
vnode.attrs.loader.displayable.map(function(item) {
return m('tr', [
m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, 'Show')),
- m('td', item.uuid),
+ m('td.arvados-uuid', item.uuid),
m('td', item.name || '(unnamed)'),
m('td', m(window.components.datetime, {parse: item.modified_at})),
])
commit 2220f7f54be3445457a82c71e1e60d6492ede3a3
Author: Tom Clegg <tom at curoverse.com>
Date: Thu Aug 10 14:12:33 2017 -0400
12033: Extract multisite loader to its own class.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 9334bcc..527ce6c 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
window.components = window.components || {}
-window.components.collection_table_narrow = {
+window.components.collection_table = {
view: function(vnode) {
return m('table.table.table-condensed', [
m('thead', m('tr', [
@@ -13,7 +13,7 @@ window.components.collection_table_narrow = {
m('th', 'last modified'),
])),
m('tbody', [
- vnode.attrs.results.displayable.map(function(item) {
+ vnode.attrs.loader.displayable.map(function(item) {
return m('tr', [
m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, 'Show')),
m('td', item.uuid),
@@ -24,149 +24,42 @@ window.components.collection_table_narrow = {
]),
m('tfoot', m('tr', [
m('th[colspan=4]', m('button.btn.btn-xs', {
- className: vnode.attrs.results.loadMore ? 'btn-primary' : 'btn-default',
+ className: vnode.attrs.loader.loadMore ? 'btn-primary' : 'btn-default',
style: {
display: 'block',
width: '12em',
marginLeft: 'auto',
marginRight: 'auto',
},
- disabled: !vnode.attrs.results.loadMore,
+ disabled: !vnode.attrs.loader.loadMore,
onclick: function() {
- vnode.attrs.results.loadMore()
+ vnode.attrs.loader.loadMore()
return false
},
- }, vnode.attrs.results.loadMore ? 'Load more' : '(loading)')),
+ }, vnode.attrs.loader.loadMore ? 'Load more' : '(loading)')),
])),
])
},
}
-function Pager(loadFunc) {
- // loadFunc(filters) returns a promise for a page of results.
- var pager = this
- Object.assign(pager, {
- done: false,
- items: m.stream(),
- thresholdItem: null,
- loadNextPage: function() {
- // Get the next page, if there are any more items to get.
- if (pager.done)
- return
- var filters = pager.thresholdItem ? [
- ["modified_at", "<=", pager.thresholdItem.modified_at],
- ["uuid", "!=", pager.thresholdItem.uuid],
- ] : []
- loadFunc(filters).then(function(resp) {
- var items = pager.items() || []
- Array.prototype.push.apply(items, resp.items)
- if (resp.items.length == 0)
- pager.done = true
- else
- pager.thresholdItem = resp.items[resp.items.length-1]
- pager.items(items)
- })
- },
- })
-}
-
window.components.collection_search = {
oninit: function(vnode) {
vnode.state.sessionDB = new window.models.SessionDB()
vnode.state.searchEntered = m.stream('')
vnode.state.searchStart = m.stream('')
vnode.state.searchStart.map(function(q) {
- var sessions = vnode.state.sessionDB.loadAll()
- var cookie = (new Date()).getTime()
- // Each time searchStart() is called we replace the
- // vnode.state.results stream with a new one, and use
- // the local variable to update results in callbacks. This
- // avoids crosstalk between AJAX calls from consecutive
- // searches.
- var results = {
- // Sorted items ready to display, merged from all
- // pagers.
- displayable: [],
- pagers: {},
- loadMore: false,
- // Number of undisplayed items to keep on hand for
- // each result set. When hitting "load more", if a
- // result set already has this many additional results
- // available, we don't bother fetching a new
- // page. This is the _minimum_ number of rows that
- // will be added to results.displayable in each "load
- // more" event (except for the case where all items
- // are displayed).
- lowWaterMark: 23,
- }
- vnode.state.results = results
- m.stream.merge(Object.keys(sessions).map(function(key) {
- var pager = new Pager(function(filters) {
+ vnode.state.loader = new window.models.MultisiteLoader({
+ loadFunc: function(session, filters) {
if (q)
filters.push(['any', '@@', q+':*'])
- return vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
+ return vnode.state.sessionDB.request(session, 'arvados/v1/collections', {
data: {
filters: JSON.stringify(filters),
count: 'none',
},
})
- })
- results.pagers[key] = pager
- pager.loadNextPage()
- // Resolve the stream with the session key when the
- // results arrive.
- return pager.items.map(function() { return key })
- })).map(function(keys) {
- // Top (most recent) of {bottom (oldest) entry of any
- // pager that still has more pages left to fetch}
- var cutoff
- keys.forEach(function(key) {
- var pager = results.pagers[key]
- var items = pager.items()
- if (items.length == 0 || pager.done)
- return
- var last = items[items.length-1].modified_at
- if (!cutoff || cutoff < last)
- cutoff = last
- })
- var combined = []
- keys.forEach(function(key) {
- var pager = results.pagers[key]
- pager.itemsDisplayed = 0
- pager.items().every(function(item) {
- if (cutoff && item.modified_at < cutoff)
- // Some other pagers haven't caught up to
- // this point, so don't display this item
- // or anything after it.
- return false
- item.session = sessions[key]
- combined.push(item)
- pager.itemsDisplayed++
- return true // continue
- })
- })
- results.displayable = combined.sort(function(a, b) {
- return a.modified_at < b.modified_at ? 1 : -1
- })
- // Make a new loadMore function that hits the pagers
- // (if necessary according to lowWaterMark)... or set
- // results.loadMore to false if there is nothing left
- // to fetch.
- var loadable = []
- Object.keys(results.pagers).map(function(key) {
- if (!results.pagers[key].done)
- loadable.push(results.pagers[key])
- })
- if (loadable.length == 0)
- results.loadMore = false
- else
- results.loadMore = function() {
- results.loadMore = false
- loadable.map(function(pager) {
- if (pager.items().length - pager.itemsDisplayed < results.lowWaterMark)
- pager.loadNextPage()
- })
- }
+ },
+ sessionDB: vnode.state.sessionDB,
})
})
},
@@ -195,15 +88,15 @@ window.components.collection_search = {
? m('span.label.label-xs.label-danger', 'none')
: Object.keys(sessions).sort().map(function(key) {
return [m('span.label.label-xs', {
- className: vnode.state.results.pagers[key].items() ? 'label-info' : 'label-default',
+ className: vnode.state.loader.pagers[key].items() ? 'label-info' : 'label-default',
}, key), ' ']
}),
' ',
m('a[href="/sessions"]', 'Add/remove sites'),
]),
]),
- m(window.components.collection_table_narrow, {
- results: vnode.state.results,
+ m(window.components.collection_table, {
+ loader: vnode.state.loader,
}),
])
},
diff --git a/apps/workbench/app/assets/javascripts/models/loader.js b/apps/workbench/app/assets/javascripts/models/loader.js
new file mode 100644
index 0000000..1dc7079
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/models/loader.js
@@ -0,0 +1,126 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.models = window.models || {}
+window.models.Pager = function(loadFunc) {
+ // loadFunc(filters) must return a promise for a page of results.
+ var pager = this
+ Object.assign(pager, {
+ done: false,
+ items: m.stream(),
+ thresholdItem: null,
+ loadNextPage: function() {
+ // Get the next page, if there are any more items to get.
+ if (pager.done)
+ return
+ var filters = pager.thresholdItem ? [
+ ["modified_at", "<=", pager.thresholdItem.modified_at],
+ ["uuid", "!=", pager.thresholdItem.uuid],
+ ] : []
+ loadFunc(filters).then(function(resp) {
+ var items = pager.items() || []
+ Array.prototype.push.apply(items, resp.items)
+ if (resp.items.length == 0)
+ pager.done = true
+ else
+ pager.thresholdItem = resp.items[resp.items.length-1]
+ pager.items(items)
+ })
+ },
+ })
+}
+
+// MultisiteLoader loads pages of results from multiple API sessions
+// and merges them into a single result set.
+//
+// The constructor implicitly starts an initial page load for each
+// session.
+//
+// new MultisiteLoader({loadFunc: function(session, filters){...},
+// sessionDB: new window.models.SessionDB()}
+//
+// At any given time, ml.loadMore will be either false (meaning a page
+// load is in progress or there are no more results to fetch) or a
+// function that starts loading more results.
+//
+// loadFunc() must retrieve results in "modified_at desc" order.
+window.models = window.models || {}
+window.models.MultisiteLoader = function(config) {
+ var loader = this
+ if (!(config.loadFunc && config.sessionDB))
+ throw new Error("MultisiteLoader constructor requires loadFunc and sessionDB")
+ Object.assign(loader, config, {
+ // Sorted items ready to display, merged from all pagers.
+ displayable: [],
+ pagers: {},
+ loadMore: false,
+ // Number of undisplayed items to keep on hand for each result
+ // set. When hitting "load more", if a result set already has
+ // this many additional results available, we don't bother
+ // fetching a new page. This is the _minimum_ number of rows
+ // that will be added to loader.displayable in each "load
+ // more" event (except for the case where all items are
+ // displayed).
+ lowWaterMark: 23,
+ })
+ var sessions = loader.sessionDB.loadAll()
+ m.stream.merge(Object.keys(sessions).map(function(key) {
+ var pager = new window.models.Pager(loader.loadFunc.bind(null, sessions[key]))
+ loader.pagers[key] = pager
+ pager.loadNextPage()
+ // Resolve the stream with the session key when the results
+ // arrive.
+ return pager.items.map(function() { return key })
+ })).map(function(keys) {
+ // Top (most recent) of {bottom (oldest) entry of any pager
+ // that still has more pages left to fetch}
+ var cutoff
+ keys.forEach(function(key) {
+ var pager = loader.pagers[key]
+ var items = pager.items()
+ if (items.length == 0 || pager.done)
+ return
+ var last = items[items.length-1].modified_at
+ if (!cutoff || cutoff < last)
+ cutoff = last
+ })
+ var combined = []
+ keys.forEach(function(key) {
+ var pager = loader.pagers[key]
+ pager.itemsDisplayed = 0
+ pager.items().every(function(item) {
+ if (cutoff && item.modified_at < cutoff)
+ // Some other pagers haven't caught up to this
+ // point, so don't display this item or anything
+ // after it.
+ return false
+ item.session = sessions[key]
+ combined.push(item)
+ pager.itemsDisplayed++
+ return true // continue
+ })
+ })
+ loader.displayable = combined.sort(function(a, b) {
+ return a.modified_at < b.modified_at ? 1 : -1
+ })
+ // Make a new loadMore function that hits the pagers (if
+ // necessary according to lowWaterMark)... or set
+ // loader.loadMore to false if there is nothing left to fetch.
+ var loadable = []
+ Object.keys(loader.pagers).map(function(key) {
+ if (!loader.pagers[key].done)
+ loadable.push(loader.pagers[key])
+ })
+ if (loadable.length == 0)
+ loader.loadMore = false
+ else
+ loader.loadMore = function() {
+ loader.loadMore = false
+ loadable.map(function(pager) {
+ if (pager.items().length - pager.itemsDisplayed < loader.lowWaterMark)
+ pager.loadNextPage()
+ })
+ }
+ })
+}
commit 2014757448a9ce52f2aa4f6af4ce2284c6858bb5
Author: Tom Clegg <tom at curoverse.com>
Date: Thu Aug 10 13:48:31 2017 -0400
12033: Add "load more" button.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 0c821e9..9334bcc 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -13,7 +13,7 @@ window.components.collection_table_narrow = {
m('th', 'last modified'),
])),
m('tbody', [
- vnode.attrs.items.map(function(item) {
+ vnode.attrs.results.displayable.map(function(item) {
return m('tr', [
m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, 'Show')),
m('td', item.uuid),
@@ -22,6 +22,22 @@ window.components.collection_table_narrow = {
])
}),
]),
+ m('tfoot', m('tr', [
+ m('th[colspan=4]', m('button.btn.btn-xs', {
+ className: vnode.attrs.results.loadMore ? 'btn-primary' : 'btn-default',
+ style: {
+ display: 'block',
+ width: '12em',
+ marginLeft: 'auto',
+ marginRight: 'auto',
+ },
+ disabled: !vnode.attrs.results.loadMore,
+ onclick: function() {
+ vnode.attrs.results.loadMore()
+ return false
+ },
+ }, vnode.attrs.results.loadMore ? 'Load more' : '(loading)')),
+ ])),
])
},
}
@@ -32,19 +48,22 @@ function Pager(loadFunc) {
Object.assign(pager, {
done: false,
items: m.stream(),
- lastModifiedAt: null,
+ thresholdItem: null,
loadNextPage: function() {
// Get the next page, if there are any more items to get.
if (pager.done)
return
- var filters = pager.lastModifiedAt ? [["modified_at", "<=", pager.lastModifiedAt]] : []
+ var filters = pager.thresholdItem ? [
+ ["modified_at", "<=", pager.thresholdItem.modified_at],
+ ["uuid", "!=", pager.thresholdItem.uuid],
+ ] : []
loadFunc(filters).then(function(resp) {
var items = pager.items() || []
Array.prototype.push.apply(items, resp.items)
if (resp.items.length == 0)
pager.done = true
else
- pager.lastModifiedAt = resp.items[resp.items.length-1].modified_at
+ pager.thresholdItem = resp.items[resp.items.length-1]
pager.items(items)
})
},
@@ -56,15 +75,31 @@ window.components.collection_search = {
vnode.state.sessionDB = new window.models.SessionDB()
vnode.state.searchEntered = m.stream('')
vnode.state.searchStart = m.stream('')
- // items ready to display
- vnode.state.displayItems = m.stream([])
- // {sessionKey -> Pager}
- vnode.state.pagers = {}
vnode.state.searchStart.map(function(q) {
var sessions = vnode.state.sessionDB.loadAll()
var cookie = (new Date()).getTime()
- var displayItems = m.stream([])
- vnode.state.displayItems = displayItems
+ // Each time searchStart() is called we replace the
+ // vnode.state.results stream with a new one, and use
+ // the local variable to update results in callbacks. This
+ // avoids crosstalk between AJAX calls from consecutive
+ // searches.
+ var results = {
+ // Sorted items ready to display, merged from all
+ // pagers.
+ displayable: [],
+ pagers: {},
+ loadMore: false,
+ // Number of undisplayed items to keep on hand for
+ // each result set. When hitting "load more", if a
+ // result set already has this many additional results
+ // available, we don't bother fetching a new
+ // page. This is the _minimum_ number of rows that
+ // will be added to results.displayable in each "load
+ // more" event (except for the case where all items
+ // are displayed).
+ lowWaterMark: 23,
+ }
+ vnode.state.results = results
m.stream.merge(Object.keys(sessions).map(function(key) {
var pager = new Pager(function(filters) {
if (q)
@@ -76,20 +111,62 @@ window.components.collection_search = {
},
})
})
- vnode.state.pagers[key] = pager
+ results.pagers[key] = pager
pager.loadNextPage()
+ // Resolve the stream with the session key when the
+ // results arrive.
return pager.items.map(function() { return key })
})).map(function(keys) {
+ // Top (most recent) of {bottom (oldest) entry of any
+ // pager that still has more pages left to fetch}
+ var cutoff
+ keys.forEach(function(key) {
+ var pager = results.pagers[key]
+ var items = pager.items()
+ if (items.length == 0 || pager.done)
+ return
+ var last = items[items.length-1].modified_at
+ if (!cutoff || cutoff < last)
+ cutoff = last
+ })
var combined = []
keys.forEach(function(key) {
- vnode.state.pagers[key].items().forEach(function(item) {
+ var pager = results.pagers[key]
+ pager.itemsDisplayed = 0
+ pager.items().every(function(item) {
+ if (cutoff && item.modified_at < cutoff)
+ // Some other pagers haven't caught up to
+ // this point, so don't display this item
+ // or anything after it.
+ return false
item.session = sessions[key]
combined.push(item)
+ pager.itemsDisplayed++
+ return true // continue
})
})
- displayItems(combined.sort(function(a, b) {
+ results.displayable = combined.sort(function(a, b) {
return a.modified_at < b.modified_at ? 1 : -1
- }))
+ })
+ // Make a new loadMore function that hits the pagers
+ // (if necessary according to lowWaterMark)... or set
+ // results.loadMore to false if there is nothing left
+ // to fetch.
+ var loadable = []
+ Object.keys(results.pagers).map(function(key) {
+ if (!results.pagers[key].done)
+ loadable.push(results.pagers[key])
+ })
+ if (loadable.length == 0)
+ results.loadMore = false
+ else
+ results.loadMore = function() {
+ results.loadMore = false
+ loadable.map(function(pager) {
+ if (pager.items().length - pager.itemsDisplayed < results.lowWaterMark)
+ pager.loadNextPage()
+ })
+ }
})
})
},
@@ -118,7 +195,7 @@ window.components.collection_search = {
? m('span.label.label-xs.label-danger', 'none')
: Object.keys(sessions).sort().map(function(key) {
return [m('span.label.label-xs', {
- className: vnode.state.pagers[key].items() ? 'label-info' : 'label-default',
+ className: vnode.state.results.pagers[key].items() ? 'label-info' : 'label-default',
}, key), ' ']
}),
' ',
@@ -126,7 +203,7 @@ window.components.collection_search = {
]),
]),
m(window.components.collection_table_narrow, {
- items: vnode.state.displayItems(),
+ results: vnode.state.results,
}),
])
},
commit 473c95bec9a11808bb286528a51a4dd671e1d0bb
Author: Tom Clegg <tom at curoverse.com>
Date: Wed Aug 9 18:35:19 2017 -0400
12033: Merge results from all sites into one table.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 625539c..0c821e9 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -6,15 +6,19 @@ window.components = window.components || {}
window.components.collection_table_narrow = {
view: function(vnode) {
return m('table.table.table-condensed', [
- m('thead', m('tr', m('th', vnode.attrs.key))),
+ m('thead', m('tr', [
+ m('th'),
+ m('th', 'uuid'),
+ m('th', 'name'),
+ m('th', 'last modified'),
+ ])),
m('tbody', [
- vnode.attrs.items().map(function(item) {
+ vnode.attrs.items.map(function(item) {
return m('tr', [
- m('td', [
- m('a', {href: vnode.attrs.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, item.name || '(unnamed)'),
- m('br'),
- m(window.components.datetime, {parse: item.modified_at}),
- ]),
+ m('td', m('a.btn.btn-xs.btn-default', {href: item.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, 'Show')),
+ m('td', item.uuid),
+ m('td', item.name || '(unnamed)'),
+ m('td', m(window.components.datetime, {parse: item.modified_at})),
])
}),
]),
@@ -22,37 +26,74 @@ window.components.collection_table_narrow = {
},
}
+function Pager(loadFunc) {
+ // loadFunc(filters) returns a promise for a page of results.
+ var pager = this
+ Object.assign(pager, {
+ done: false,
+ items: m.stream(),
+ lastModifiedAt: null,
+ loadNextPage: function() {
+ // Get the next page, if there are any more items to get.
+ if (pager.done)
+ return
+ var filters = pager.lastModifiedAt ? [["modified_at", "<=", pager.lastModifiedAt]] : []
+ loadFunc(filters).then(function(resp) {
+ var items = pager.items() || []
+ Array.prototype.push.apply(items, resp.items)
+ if (resp.items.length == 0)
+ pager.done = true
+ else
+ pager.lastModifiedAt = resp.items[resp.items.length-1].modified_at
+ pager.items(items)
+ })
+ },
+ })
+}
+
window.components.collection_search = {
oninit: function(vnode) {
vnode.state.sessionDB = new window.models.SessionDB()
vnode.state.searchEntered = m.stream('')
vnode.state.searchStart = m.stream('')
- vnode.state.items = {}
+ // items ready to display
+ vnode.state.displayItems = m.stream([])
+ // {sessionKey -> Pager}
+ vnode.state.pagers = {}
vnode.state.searchStart.map(function(q) {
var sessions = vnode.state.sessionDB.loadAll()
var cookie = (new Date()).getTime()
- vnode.state.cookie = cookie
- Object.keys(sessions).map(function(key) {
- if (!vnode.state.items[key])
- vnode.state.items[key] = m.stream([])
- vnode.state.items[key].dirty = true
- vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
- data: {
- filters: JSON.stringify(!q ? [] : [['any', '@@', q+':*']]),
- count: 'none',
- },
- }).then(function(resp) {
- if (cookie !== vnode.state.cookie)
- // a newer query is in progress; ignore this result.
- return
- vnode.state.items[key](resp.items)
- vnode.state.items[key].dirty = false
+ var displayItems = m.stream([])
+ vnode.state.displayItems = displayItems
+ m.stream.merge(Object.keys(sessions).map(function(key) {
+ var pager = new Pager(function(filters) {
+ if (q)
+ filters.push(['any', '@@', q+':*'])
+ return vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
+ data: {
+ filters: JSON.stringify(filters),
+ count: 'none',
+ },
+ })
})
+ vnode.state.pagers[key] = pager
+ pager.loadNextPage()
+ return pager.items.map(function() { return key })
+ })).map(function(keys) {
+ var combined = []
+ keys.forEach(function(key) {
+ vnode.state.pagers[key].items().forEach(function(item) {
+ item.session = sessions[key]
+ combined.push(item)
+ })
+ })
+ displayItems(combined.sort(function(a, b) {
+ return a.modified_at < b.modified_at ? 1 : -1
+ }))
})
})
},
view: function(vnode) {
- var items = vnode.state.items
var sessions = vnode.state.sessionDB.loadAll()
return m('form', {
onsubmit: function() {
@@ -64,7 +105,7 @@ window.components.collection_search = {
m('.col-md-6', [
m('.input-group', [
m('input#search.form-control[placeholder=Search]', {
- oninput: m.withAttr('value', debounce(200, vnode.state.searchEntered)),
+ oninput: m.withAttr('value', vnode.state.searchEntered),
}),
m('.input-group-btn', [
m('input.btn.btn-primary[type=submit][value="Search"]'),
@@ -73,45 +114,20 @@ window.components.collection_search = {
]),
m('.col-md-6', [
'Searching sites: ',
- Object.keys(items).length == 0
+ Object.keys(sessions).length == 0
? m('span.label.label-xs.label-danger', 'none')
- : Object.keys(items).sort().map(function(key) {
- return [m('span.label.label-xs.label-info', key), ' ']
+ : Object.keys(sessions).sort().map(function(key) {
+ return [m('span.label.label-xs', {
+ className: vnode.state.pagers[key].items() ? 'label-info' : 'label-default',
+ }, key), ' ']
}),
' ',
m('a[href="/sessions"]', 'Add/remove sites'),
]),
]),
- m('.row', Object.keys(items).sort().map(function(key) {
- return m('.col-md-3', {key: key, style: {
- opacity: items[key].dirty ? 0.5 : 1,
- }}, [
- m(window.components.collection_table_narrow, {
- key: key,
- session: sessions[key],
- items: items[key],
- }),
- ])
- })),
+ m(window.components.collection_table_narrow, {
+ items: vnode.state.displayItems(),
+ }),
])
},
}
-
-function debounce(t, f) {
- // Return a new function that waits until t milliseconds have
- // passed since it was last called, then calls f with its most
- // recent arguments.
- var this_was = this
- var pending
- return function() {
- var args = arguments
- if (pending) {
- console.log("debounce!")
- window.clearTimeout(pending)
- }
- pending = window.setTimeout(function() {
- pending = undefined
- f.apply(this_was, args)
- }, t)
- }
-}
commit c3af5da0100777902f4d968c5431630e713a511f
Author: Tom Clegg <tom at curoverse.com>
Date: Wed Aug 9 13:43:29 2017 -0400
12033: Use browser locale to render collection timestamps.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 7ab5047..625539c 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -13,7 +13,7 @@ window.components.collection_table_narrow = {
m('td', [
m('a', {href: vnode.attrs.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, item.name || '(unnamed)'),
m('br'),
- item.modified_at,
+ m(window.components.datetime, {parse: item.modified_at}),
]),
])
}),
@@ -39,6 +39,7 @@ window.components.collection_search = {
vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
data: {
filters: JSON.stringify(!q ? [] : [['any', '@@', q+':*']]),
+ count: 'none',
},
}).then(function(resp) {
if (cookie !== vnode.state.cookie)
diff --git a/apps/workbench/app/assets/javascripts/components/date.js b/apps/workbench/app/assets/javascripts/components/date.js
new file mode 100644
index 0000000..c3c905a
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/components/date.js
@@ -0,0 +1,10 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.components = window.components || {}
+window.components.datetime = {
+ view: function(vnode) {
+ return m('span', new Date(Date.parse(vnode.attrs.parse)).toLocaleString())
+ },
+}
commit babd40cc9e963fd87952b0b23539189b56be10cf
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Aug 8 21:51:43 2017 -0400
12033: Link collections to remote workbench sites.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index ddba2e1..7ab5047 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -11,7 +11,7 @@ window.components.collection_table_narrow = {
vnode.attrs.items().map(function(item) {
return m('tr', [
m('td', [
- m('a', {href: '/collections/'+item.uuid}, item.name || '(unnamed)'),
+ m('a', {href: vnode.attrs.session.baseURL.replace('://', '://workbench.')+'/collections/'+item.uuid}, item.name || '(unnamed)'),
m('br'),
item.modified_at,
]),
@@ -52,6 +52,7 @@ window.components.collection_search = {
},
view: function(vnode) {
var items = vnode.state.items
+ var sessions = vnode.state.sessionDB.loadAll()
return m('form', {
onsubmit: function() {
vnode.state.searchStart(vnode.state.searchEntered())
@@ -86,6 +87,7 @@ window.components.collection_search = {
}}, [
m(window.components.collection_table_narrow, {
key: key,
+ session: sessions[key],
items: items[key],
}),
])
commit 2289f184787fa9db2cf3786a1383dd2ced2b643b
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Aug 8 21:51:05 2017 -0400
12033: Fade out stale results.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
index 5bd667a..ddba2e1 100644
--- a/apps/workbench/app/assets/javascripts/components/collections.js
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -35,6 +35,7 @@ window.components.collection_search = {
Object.keys(sessions).map(function(key) {
if (!vnode.state.items[key])
vnode.state.items[key] = m.stream([])
+ vnode.state.items[key].dirty = true
vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
data: {
filters: JSON.stringify(!q ? [] : [['any', '@@', q+':*']]),
@@ -44,6 +45,7 @@ window.components.collection_search = {
// a newer query is in progress; ignore this result.
return
vnode.state.items[key](resp.items)
+ vnode.state.items[key].dirty = false
})
})
})
@@ -79,8 +81,13 @@ window.components.collection_search = {
]),
]),
m('.row', Object.keys(items).sort().map(function(key) {
- return m('.col-md-3', {key: key}, [
- m(window.components.collection_table_narrow, {key: key, items: items[key]}),
+ return m('.col-md-3', {key: key, style: {
+ opacity: items[key].dirty ? 0.5 : 1,
+ }}, [
+ m(window.components.collection_table_narrow, {
+ key: key,
+ items: items[key],
+ }),
])
})),
])
commit 6ba147122730b917392b791ed589e35656f9be14
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Aug 8 21:34:39 2017 -0400
12033: Add /collections/multisite search page.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/app/assets/javascripts/components/collections.js b/apps/workbench/app/assets/javascripts/components/collections.js
new file mode 100644
index 0000000..5bd667a
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/components/collections.js
@@ -0,0 +1,107 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.components = window.components || {}
+window.components.collection_table_narrow = {
+ view: function(vnode) {
+ return m('table.table.table-condensed', [
+ m('thead', m('tr', m('th', vnode.attrs.key))),
+ m('tbody', [
+ vnode.attrs.items().map(function(item) {
+ return m('tr', [
+ m('td', [
+ m('a', {href: '/collections/'+item.uuid}, item.name || '(unnamed)'),
+ m('br'),
+ item.modified_at,
+ ]),
+ ])
+ }),
+ ]),
+ ])
+ },
+}
+
+window.components.collection_search = {
+ oninit: function(vnode) {
+ vnode.state.sessionDB = new window.models.SessionDB()
+ vnode.state.searchEntered = m.stream('')
+ vnode.state.searchStart = m.stream('')
+ vnode.state.items = {}
+ vnode.state.searchStart.map(function(q) {
+ var sessions = vnode.state.sessionDB.loadAll()
+ var cookie = (new Date()).getTime()
+ vnode.state.cookie = cookie
+ Object.keys(sessions).map(function(key) {
+ if (!vnode.state.items[key])
+ vnode.state.items[key] = m.stream([])
+ vnode.state.sessionDB.request(sessions[key], 'arvados/v1/collections', {
+ data: {
+ filters: JSON.stringify(!q ? [] : [['any', '@@', q+':*']]),
+ },
+ }).then(function(resp) {
+ if (cookie !== vnode.state.cookie)
+ // a newer query is in progress; ignore this result.
+ return
+ vnode.state.items[key](resp.items)
+ })
+ })
+ })
+ },
+ view: function(vnode) {
+ var items = vnode.state.items
+ return m('form', {
+ onsubmit: function() {
+ vnode.state.searchStart(vnode.state.searchEntered())
+ return false
+ },
+ }, [
+ m('.row', [
+ m('.col-md-6', [
+ m('.input-group', [
+ m('input#search.form-control[placeholder=Search]', {
+ oninput: m.withAttr('value', debounce(200, vnode.state.searchEntered)),
+ }),
+ m('.input-group-btn', [
+ m('input.btn.btn-primary[type=submit][value="Search"]'),
+ ]),
+ ]),
+ ]),
+ m('.col-md-6', [
+ 'Searching sites: ',
+ Object.keys(items).length == 0
+ ? m('span.label.label-xs.label-danger', 'none')
+ : Object.keys(items).sort().map(function(key) {
+ return [m('span.label.label-xs.label-info', key), ' ']
+ }),
+ ' ',
+ m('a[href="/sessions"]', 'Add/remove sites'),
+ ]),
+ ]),
+ m('.row', Object.keys(items).sort().map(function(key) {
+ return m('.col-md-3', {key: key}, [
+ m(window.components.collection_table_narrow, {key: key, items: items[key]}),
+ ])
+ })),
+ ])
+ },
+}
+
+function debounce(t, f) {
+ // Return a new function that waits until t milliseconds have
+ // passed since it was last called, then calls f with its most
+ // recent arguments.
+ var this_was = this
+ var pending
+ return function() {
+ var args = arguments
+ if (pending) {
+ console.log("debounce!")
+ window.clearTimeout(pending)
+ }
+ pending = window.setTimeout(function() {
+ pending = undefined
+ f.apply(this_was, args)
+ }, t)
+ }
+}
diff --git a/apps/workbench/app/assets/javascripts/models/session_db.js b/apps/workbench/app/assets/javascripts/models/session_db.js
index 058d450..75fe6f9 100644
--- a/apps/workbench/app/assets/javascripts/models/session_db.js
+++ b/apps/workbench/app/assets/javascripts/models/session_db.js
@@ -79,5 +79,11 @@ window.models.SessionDB = function() {
})
// m.request(session.baseURL + 'discovery/v1/apis/arvados/v1/rest').then(function(dd) {})
},
+ request: function(session, path, opts) {
+ opts = opts || {}
+ opts.headers = opts.headers || {}
+ opts.headers.authorization = 'OAuth2 '+ session.token
+ return m.request(session.baseURL + path, opts)
+ },
})
}
diff --git a/apps/workbench/app/controllers/collections_controller.rb b/apps/workbench/app/controllers/collections_controller.rb
index f8fcf51..da7b466 100644
--- a/apps/workbench/app/controllers/collections_controller.rb
+++ b/apps/workbench/app/controllers/collections_controller.rb
@@ -16,7 +16,7 @@ class CollectionsController < ApplicationController
skip_around_filter(:require_thread_api_token,
only: [:show_file, :show_file_links])
skip_before_filter(:find_object_by_uuid,
- only: [:provenance, :show_file, :show_file_links])
+ only: [:provenance, :show_file, :show_file_links, :multisite])
# We depend on show_file to display the user agreement:
skip_before_filter :check_user_agreements, only: :show_file
skip_before_filter :check_user_profile, only: :show_file
diff --git a/apps/workbench/app/views/collections/multisite.html b/apps/workbench/app/views/collections/multisite.html
new file mode 100644
index 0000000..7e49ac9
--- /dev/null
+++ b/apps/workbench/app/views/collections/multisite.html
@@ -0,0 +1,5 @@
+<!-- Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: AGPL-3.0 -->
+
+<div data-mount-mithril="collection_search"></div>
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index 8fde2b8..8dcc7fd 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -95,6 +95,7 @@ ArvadosWorkbench::Application.routes.draw do
post 'remove_selected_files', on: :member
get 'tags', on: :member
post 'save_tags', on: :member
+ get 'multisite', on: :collection
end
get('/collections/download/:uuid/:reader_token/*file' => 'collections#show_file',
format: false)
commit 69487544de1e7fa7c636473e338fe90dde6c3c06
Author: Tom Clegg <tom at curoverse.com>
Date: Tue Aug 8 09:10:43 2017 -0400
12033: Add session-manager page.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/.gitignore b/apps/workbench/.gitignore
index 66a7adc..5fb3718 100644
--- a/apps/workbench/.gitignore
+++ b/apps/workbench/.gitignore
@@ -42,3 +42,4 @@
# npm-rails
/node_modules
+/npm-debug.log
diff --git a/apps/workbench/app/assets/javascripts/application.js b/apps/workbench/app/assets/javascripts/application.js
index d4f928b..aa589ed 100644
--- a/apps/workbench/app/assets/javascripts/application.js
+++ b/apps/workbench/app/assets/javascripts/application.js
@@ -32,8 +32,11 @@
//= require morris
//= require jquery.number.min
//= require npm-dependencies
+//= require mithril/stream/stream
//= require_tree .
+window.m = Object.assign(window.Mithril, {stream: window.m.stream})
+
jQuery(function($){
$(document).ajaxStart(function(){
$('.modal-with-loading-spinner .spinner').show();
@@ -155,7 +158,9 @@ jQuery(function($){
// Need this to trigger input validation/synchronization callbacks because some browsers
// auto-fill form fields (e.g., when navigating "back" to a page where some text
// had been entered in a search box) without triggering a change or input event.
- $('input').trigger('input');
+ $('input').each(function(el) {
+ $(el).trigger($.Event('input', {currentTarget: el}));
+ });
});
HeaderRowFixer = function(selector) {
diff --git a/apps/workbench/app/assets/javascripts/components/sessions.js b/apps/workbench/app/assets/javascripts/components/sessions.js
new file mode 100644
index 0000000..be4b7e3
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/components/sessions.js
@@ -0,0 +1,72 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+$(document).on('ready', function() {
+ var db = new window.models.SessionDB()
+ db.checkForNewToken()
+ db.fillMissingUUIDs()
+})
+
+window.components = window.components || {}
+window.components.sessions = {
+ oninit: function(vnode) {
+ vnode.state.db = new window.models.SessionDB()
+ vnode.state.hostToAdd = m.stream('')
+ },
+ view: function(vnode) {
+ var db = vnode.state.db
+ var sessions = db.loadAll()
+ return m('container', [
+ m('table.table.table-condensed.table-hover', m('tbody', [
+ Object.keys(sessions).map(function(uuidPrefix) {
+ var session = sessions[uuidPrefix]
+ return m('tr', [
+ session.token && session.user ? [
+ m('td', m('a.btn.btn-xs.btn-default', {
+ uuidPrefix: uuidPrefix,
+ onclick: m.withAttr('uuidPrefix', db.logout),
+ }, 'log out')),
+ m('td', m('span.label.label-info', 'logged in')),
+ m('td', {title: session.baseURL}, uuidPrefix),
+ m('td', session.user.username),
+ m('td', session.user.email),
+ ] : [
+ m('td', m('a.btn.btn-xs.btn-info', {
+ uuidPrefix: uuidPrefix,
+ onclick: m.withAttr('uuidPrefix', db.login),
+ }, 'log in')),
+ m('td', 'span.label.label-default', 'logged out'),
+ m('td', {title: session.baseURL}, uuidPrefix),
+ m('td'),
+ m('td'),
+ ],
+ m('td', m('a.glyphicon.glyphicon-trash', {
+ uuidPrefix: uuidPrefix,
+ onclick: m.withAttr('uuidPrefix', db.trash),
+ })),
+ ])
+ }),
+ ])),
+ m('.row', m('.col-md-6', [
+ m('form', {
+ onsubmit: function() {
+ db.login(vnode.state.hostToAdd())
+ return false
+ },
+ }, [
+ m('.input-group', [
+ m('input.form-control[type=text][name=apiHost][placeholder="API host"]', {
+ oninput: m.withAttr('value', vnode.state.hostToAdd),
+ }),
+ m('.input-group-btn', [
+ m('input.btn.btn-primary[type=submit][value="Log in"]', {
+ disabled: !vnode.state.hostToAdd(),
+ }),
+ ]),
+ ]),
+ ]),
+ ])),
+ ])
+ },
+}
diff --git a/apps/workbench/app/assets/javascripts/mithril_mount.js b/apps/workbench/app/assets/javascripts/mithril_mount.js
index fe0907e..4a85a09 100644
--- a/apps/workbench/app/assets/javascripts/mithril_mount.js
+++ b/apps/workbench/app/assets/javascripts/mithril_mount.js
@@ -2,9 +2,6 @@
//
// SPDX-License-Identifier: AGPL-3.0
-// rails_npm does "window.Mithril = require('mithril')" for us.
-var m = window.Mithril
-
$(document).on('ready arv:pane:loaded', function() {
$('[data-mount-mithril]').each(function() {
m.mount(this, window.components[$(this).data('mount-mithril')])
diff --git a/apps/workbench/app/assets/javascripts/models/session_db.js b/apps/workbench/app/assets/javascripts/models/session_db.js
new file mode 100644
index 0000000..058d450
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/models/session_db.js
@@ -0,0 +1,83 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.models = window.models || {}
+window.models.SessionDB = function() {
+ var db = this
+ Object.assign(db, {
+ loadAll: function() {
+ try {
+ return JSON.parse(window.localStorage.getItem('sessions')) || {}
+ } catch(e) {}
+ return {}
+ },
+ save: function(k, v) {
+ var sessions = db.loadAll()
+ sessions[k] = v
+ window.localStorage.setItem('sessions', JSON.stringify(sessions))
+ },
+ trash: function(k) {
+ var sessions = db.loadAll()
+ delete sessions[k]
+ window.localStorage.setItem('sessions', JSON.stringify(sessions))
+ },
+ login: function(host) {
+ // Initiate login procedure with given API host (which can
+ // optionally include scheme://).
+ //
+ // Any page that has a button that invokes login() must
+ // also call checkForNewToken() on (at least) its first
+ // render. Otherwise, the login procedure can't be
+ // completed.
+ var baseURL = host
+ if (baseURL.indexOf('://') < 0)
+ baseURL = 'https://' + baseURL
+ if (!baseURL.endsWith('/'))
+ baseURL = baseURL + '/'
+ document.location = baseURL + 'login?return_to=' + encodeURIComponent(document.location.href+'?baseURL='+encodeURIComponent(baseURL))
+ return false
+ },
+ checkForNewToken: function() {
+ // If there's a token and baseURL in the location bar (i.e.,
+ // we just landed here after a successful login), save it and
+ // scrub the location bar.
+ if (!document.location.search.startsWith('?'))
+ return
+ var params = {}
+ document.location.search.slice(1).split('&').map(function(kv) {
+ var e = kv.indexOf('=')
+ if (e < 0)
+ return
+ params[decodeURIComponent(kv.slice(0, e))] = decodeURIComponent(kv.slice(e+1))
+ })
+ if (!params.baseURL || !params.api_token)
+ // Have a query string, but it's not a login callback.
+ return
+ params.token = params.api_token
+ delete params.api_token
+ db.save(params.baseURL, params)
+ history.replaceState({}, '', document.location.origin + document.location.pathname)
+ },
+ fillMissingUUIDs: function() {
+ var sessions = db.loadAll()
+ Object.keys(sessions).map(function(key) {
+ if (key.indexOf('://') < 0)
+ return
+ // key is the baseURL placeholder. We need to get our user
+ // record to find out the cluster's real uuid prefix.
+ var session = sessions[key]
+ m.request(session.baseURL+'arvados/v1/users/current', {
+ headers: {
+ authorization: 'OAuth2 '+session.token,
+ },
+ }).then(function(user) {
+ session.user = user
+ db.save(user.uuid.slice(0, 5), session)
+ db.trash(key)
+ })
+ })
+ // m.request(session.baseURL + 'discovery/v1/apis/arvados/v1/rest').then(function(dd) {})
+ },
+ })
+}
diff --git a/apps/workbench/app/controllers/sessions_controller.rb b/apps/workbench/app/controllers/sessions_controller.rb
index d498653..f72b451 100644
--- a/apps/workbench/app/controllers/sessions_controller.rb
+++ b/apps/workbench/app/controllers/sessions_controller.rb
@@ -6,14 +6,19 @@ class SessionsController < ApplicationController
skip_around_filter :require_thread_api_token, :only => [:destroy, :index]
skip_around_filter :set_thread_api_token, :only => [:destroy, :index]
skip_before_filter :find_object_by_uuid, :only => [:destroy, :index]
+ skip_before_filter :find_objects_for_index
+ skip_before_filter :ensure_arvados_api_exists
def destroy
session.clear
redirect_to arvados_api_client.arvados_logout_url(return_to: root_url)
end
- def index
+ def logged_out
redirect_to root_url if session[:arvados_api_token]
render_index
end
+
+ def index
+ end
end
diff --git a/apps/workbench/app/views/sessions/index.html b/apps/workbench/app/views/sessions/index.html
new file mode 100644
index 0000000..ddfa8dd
--- /dev/null
+++ b/apps/workbench/app/views/sessions/index.html
@@ -0,0 +1,5 @@
+<!-- Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: AGPL-3.0 -->
+
+<div data-mount-mithril="sessions"></div>
diff --git a/apps/workbench/app/views/sessions/index.html.erb b/apps/workbench/app/views/sessions/logged_out.html.erb
similarity index 100%
rename from apps/workbench/app/views/sessions/index.html.erb
rename to apps/workbench/app/views/sessions/logged_out.html.erb
diff --git a/apps/workbench/app/views/tests/mithril.html b/apps/workbench/app/views/tests/mithril.html
new file mode 100644
index 0000000..4936f12
--- /dev/null
+++ b/apps/workbench/app/views/tests/mithril.html
@@ -0,0 +1 @@
+<div data-mount-mithril="test"></div>
diff --git a/apps/workbench/config/application.rb b/apps/workbench/config/application.rb
index a1f35c4..891dd43 100644
--- a/apps/workbench/config/application.rb
+++ b/apps/workbench/config/application.rb
@@ -52,6 +52,11 @@ module ArvadosWorkbench
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+
+ # npm-rails loads top-level modules like window.Mithril, but we
+ # also pull in some code from node_modules in application.js, like
+ # mithril/stream/stream.
+ config.assets.paths << Rails.root.join('node_modules')
end
end
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index 8aec64e..8fde2b8 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -47,8 +47,9 @@ ArvadosWorkbench::Application.routes.draw do
get '/repositories/:id/tree/:commit/*path' => 'repositories#show_tree', as: :show_repository_tree, format: false
get '/repositories/:id/blob/:commit/*path' => 'repositories#show_blob', as: :show_repository_blob, format: false
get '/repositories/:id/commit/:commit' => 'repositories#show_commit', as: :show_repository_commit
+ resources :sessions
match '/logout' => 'sessions#destroy', via: [:get, :post]
- get '/logged_out' => 'sessions#index'
+ get '/logged_out' => 'sessions#logged_out'
resources :users do
get 'choose', :on => :collection
get 'home', :on => :member
diff --git a/apps/workbench/npm_packages b/apps/workbench/npm_packages
new file mode 100644
index 0000000..56acf9f
--- /dev/null
+++ b/apps/workbench/npm_packages
@@ -0,0 +1,10 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# Run "rake npm:install"
+
+# Browserify is required.
+npm 'browserify', require: false, development: true
+
+npm 'mithril'
commit 50c0afeb2cad89f6248a36ed6b44a66ae974dbac
Author: Tom Clegg <tom at curoverse.com>
Date: Thu Aug 3 15:54:38 2017 -0400
12033: Add mithril via npm.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curoverse.com>
diff --git a/apps/workbench/.gitignore b/apps/workbench/.gitignore
index a27ac31..66a7adc 100644
--- a/apps/workbench/.gitignore
+++ b/apps/workbench/.gitignore
@@ -39,3 +39,6 @@
# Generated git-commit.version file
/git-commit.version
+
+# npm-rails
+/node_modules
diff --git a/apps/workbench/Gemfile b/apps/workbench/Gemfile
index 8e9fcbf..788dcf6 100644
--- a/apps/workbench/Gemfile
+++ b/apps/workbench/Gemfile
@@ -102,3 +102,5 @@ gem 'lograge'
gem 'logstash-event'
gem 'safe_yaml'
+
+gem 'npm-rails'
diff --git a/apps/workbench/Gemfile.lock b/apps/workbench/Gemfile.lock
index 0abe868..34db9cd 100644
--- a/apps/workbench/Gemfile.lock
+++ b/apps/workbench/Gemfile.lock
@@ -169,6 +169,8 @@ GEM
net-ssh (>= 2.6.5)
nokogiri (1.6.6.4)
mini_portile (~> 0.6.0)
+ npm-rails (0.2.1)
+ rails (>= 3.2)
oj (2.11.2)
os (0.9.6)
passenger (4.0.57)
@@ -296,6 +298,7 @@ DEPENDENCIES
mocha
morrisjs-rails
multi_json
+ npm-rails
oj
passenger
piwik_analytics
@@ -320,4 +323,4 @@ DEPENDENCIES
wiselinks
BUNDLED WITH
- 1.13.2
+ 1.15.1
diff --git a/apps/workbench/app/assets/javascripts/application.js b/apps/workbench/app/assets/javascripts/application.js
index c55bda0..d4f928b 100644
--- a/apps/workbench/app/assets/javascripts/application.js
+++ b/apps/workbench/app/assets/javascripts/application.js
@@ -31,6 +31,7 @@
//= require raphael
//= require morris
//= require jquery.number.min
+//= require npm-dependencies
//= require_tree .
jQuery(function($){
diff --git a/apps/workbench/app/assets/javascripts/components/test.js b/apps/workbench/app/assets/javascripts/components/test.js
new file mode 100644
index 0000000..809c6d7
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/components/test.js
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+window.components = window.components || {}
+window.components.test = {
+ view: function(vnode) {
+ return m('div.mithril-test-component', [
+ m('p', {
+ onclick: m.withAttr('zzz', function(){}),
+ }, [
+ 'mithril is working; rendered at t=',
+ (new Date()).getTime(),
+ 'ms (click to re-render)',
+ ]),
+ ])
+ },
+}
diff --git a/apps/workbench/app/assets/javascripts/mithril_mount.js b/apps/workbench/app/assets/javascripts/mithril_mount.js
new file mode 100644
index 0000000..fe0907e
--- /dev/null
+++ b/apps/workbench/app/assets/javascripts/mithril_mount.js
@@ -0,0 +1,12 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+// rails_npm does "window.Mithril = require('mithril')" for us.
+var m = window.Mithril
+
+$(document).on('ready arv:pane:loaded', function() {
+ $('[data-mount-mithril]').each(function() {
+ m.mount(this, window.components[$(this).data('mount-mithril')])
+ })
+})
diff --git a/apps/workbench/app/controllers/tests_controller.rb b/apps/workbench/app/controllers/tests_controller.rb
new file mode 100644
index 0000000..5d2de4e
--- /dev/null
+++ b/apps/workbench/app/controllers/tests_controller.rb
@@ -0,0 +1,9 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+class TestsController < ApplicationController
+ skip_before_filter :find_object_by_uuid
+ def mithril
+ end
+end
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index a3644e5..8aec64e 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -128,6 +128,8 @@ ArvadosWorkbench::Application.routes.draw do
match '/_health/ping', to: 'healthcheck#ping', via: [:get]
+ get '/tests/mithril', to: 'tests#mithril'
+
# Send unroutable requests to an arbitrary controller
# (ends up at ApplicationController#render_not_found)
match '*a', to: 'links#render_not_found', via: [:get, :post]
diff --git a/apps/workbench/test/integration/smoke_test.rb b/apps/workbench/test/integration/smoke_test.rb
index 9f2ade9..18973db 100644
--- a/apps/workbench/test/integration/smoke_test.rb
+++ b/apps/workbench/test/integration/smoke_test.rb
@@ -47,4 +47,10 @@ class SmokeTest < ActionDispatch::IntegrationTest
# urls += all_links_in('body')
end
end
+
+ test "mithril test page" do
+ visit page_with_token('active_trustedclient', '/tests/mithril')
+ assert_visit_success
+ assert_selector 'p', text: 'mithril is working'
+ end
end
diff --git a/build/package-build-dockerfiles/Makefile b/build/package-build-dockerfiles/Makefile
index e35056b..396370d 100644
--- a/build/package-build-dockerfiles/Makefile
+++ b/build/package-build-dockerfiles/Makefile
@@ -29,11 +29,15 @@ ubuntu1604/generated: common-generated-all
cp -rlt ubuntu1604/generated common-generated/*
GOTARBALL=go1.8.3.linux-amd64.tar.gz
+NODETARBALL=node-v6.11.2-linux-x64.tar.xz
-common-generated-all: common-generated/$(GOTARBALL)
+common-generated-all: common-generated/$(GOTARBALL) common-generated/$(NODETARBALL)
common-generated/$(GOTARBALL): common-generated
wget -cqO common-generated/$(GOTARBALL) http://storage.googleapis.com/golang/$(GOTARBALL)
+common-generated/$(NODETARBALL): common-generated
+ wget -cqO common-generated/$(NODETARBALL) https://nodejs.org/dist/v6.11.2/$(NODETARBALL)
+
common-generated:
mkdir common-generated
diff --git a/build/package-build-dockerfiles/centos7/Dockerfile b/build/package-build-dockerfiles/centos7/Dockerfile
index 0f084b3..cf120c9 100644
--- a/build/package-build-dockerfiles/centos7/Dockerfile
+++ b/build/package-build-dockerfiles/centos7/Dockerfile
@@ -20,6 +20,10 @@ RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Need to "touch" RPM database to workaround bug in interaction between
# overlayfs and yum (https://bugzilla.redhat.com/show_bug.cgi?id=1213602)
RUN touch /var/lib/rpm/* && yum -q -y install python33
diff --git a/build/package-build-dockerfiles/debian8/Dockerfile b/build/package-build-dockerfiles/debian8/Dockerfile
index f5aced7..b9998c6 100644
--- a/build/package-build-dockerfiles/debian8/Dockerfile
+++ b/build/package-build-dockerfiles/debian8/Dockerfile
@@ -22,6 +22,10 @@ RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Old versions of setuptools cannot build a schema-salad package.
RUN pip install --upgrade setuptools
diff --git a/build/package-build-dockerfiles/debian9/Dockerfile b/build/package-build-dockerfiles/debian9/Dockerfile
index 0003384..28ba9a3 100644
--- a/build/package-build-dockerfiles/debian9/Dockerfile
+++ b/build/package-build-dockerfiles/debian9/Dockerfile
@@ -21,10 +21,13 @@ RUN gpg --import /tmp/D39DC0E3.asc && \
/usr/local/rvm/bin/rvm-exec default gem install cure-fpm --version 1.6.0b
# Install golang binary
-COPY generated/go1.8.3.linux-amd64.tar.gz /usr/local/
-RUN cd /usr/local && ls && tar xzvf go1.8.3.linux-amd64.tar.gz
+ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Old versions of setuptools cannot build a schema-salad package.
RUN pip install --upgrade setuptools
diff --git a/build/package-build-dockerfiles/ubuntu1204/Dockerfile b/build/package-build-dockerfiles/ubuntu1204/Dockerfile
index bf09089..1d07db7 100644
--- a/build/package-build-dockerfiles/ubuntu1204/Dockerfile
+++ b/build/package-build-dockerfiles/ubuntu1204/Dockerfile
@@ -22,6 +22,10 @@ RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Old versions of setuptools cannot build a schema-salad package.
RUN pip install --upgrade setuptools
diff --git a/build/package-build-dockerfiles/ubuntu1404/Dockerfile b/build/package-build-dockerfiles/ubuntu1404/Dockerfile
index ecfcefc..9e77ad3 100644
--- a/build/package-build-dockerfiles/ubuntu1404/Dockerfile
+++ b/build/package-build-dockerfiles/ubuntu1404/Dockerfile
@@ -22,6 +22,10 @@ RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Old versions of setuptools cannot build a schema-salad package.
RUN pip install --upgrade setuptools
diff --git a/build/package-build-dockerfiles/ubuntu1604/Dockerfile b/build/package-build-dockerfiles/ubuntu1604/Dockerfile
index b7c02d7..e4673c8 100644
--- a/build/package-build-dockerfiles/ubuntu1604/Dockerfile
+++ b/build/package-build-dockerfiles/ubuntu1604/Dockerfile
@@ -22,6 +22,10 @@ RUN gpg --keyserver pool.sks-keyservers.net --recv-keys D39DC0E3 && \
ADD generated/go1.8.3.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
# Old versions of setuptools cannot build a schema-salad package.
RUN pip install --upgrade setuptools
diff --git a/build/run-build-packages.sh b/build/run-build-packages.sh
index a0a6c5f..af90eb6 100755
--- a/build/run-build-packages.sh
+++ b/build/run-build-packages.sh
@@ -621,6 +621,7 @@ if [[ "$?" == "0" ]] ; then
\cp config/environments/production.rb.example config/environments/production.rb -f
sed -i 's/secret_token: ~/secret_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/' config/application.yml
+ RAILS_ENV=production RAILS_GROUPS=assets bundle exec rake npm:install >/dev/null
RAILS_ENV=production RAILS_GROUPS=assets bundle exec rake assets:precompile >/dev/null
# Remove generated configuration files so they don't go in the package.
diff --git a/build/run-tests.sh b/build/run-tests.sh
index 3952b36..15e89fa 100755
--- a/build/run-tests.sh
+++ b/build/run-tests.sh
@@ -204,6 +204,8 @@ sanity_checks() {
echo -n 'gitolite: '
which gitolite \
|| fatal "No gitolite. Try: apt-get install gitolite3"
+ which npm \
+ || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v6.11.2/node-v6.11.2-linux-x64.tar.xz | sudo tar -C /usr/local xJf - && sudo ln -s ../node-v6.11.2-linux-x64/bin/{node,npm} /usr/local/bin/"
}
rotate_logfile() {
@@ -805,7 +807,8 @@ done
install_workbench() {
cd "$WORKSPACE/apps/workbench" \
&& mkdir -p tmp/cache \
- && RAILS_ENV=test bundle_install_trylocal
+ && RAILS_ENV=test bundle_install_trylocal \
+ && RAILS_ENV=test RAILS_GROUPS=assets bundle exec rake npm:install
}
do_install apps/workbench workbench
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list