12033: Add session-manager page.
authorTom Clegg <tom@curoverse.com>
Tue, 8 Aug 2017 13:10:43 +0000 (09:10 -0400)
committerTom Clegg <tom@curoverse.com>
Tue, 8 Aug 2017 15:43:49 +0000 (11:43 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@curoverse.com>

12 files changed:
apps/workbench/.gitignore
apps/workbench/app/assets/javascripts/application.js
apps/workbench/app/assets/javascripts/components/sessions.js [new file with mode: 0644]
apps/workbench/app/assets/javascripts/mithril_mount.js
apps/workbench/app/assets/javascripts/models/session_db.js [new file with mode: 0644]
apps/workbench/app/controllers/sessions_controller.rb
apps/workbench/app/views/sessions/index.html [new file with mode: 0644]
apps/workbench/app/views/sessions/logged_out.html.erb [moved from apps/workbench/app/views/sessions/index.html.erb with 100% similarity]
apps/workbench/app/views/tests/mithril.html [new file with mode: 0644]
apps/workbench/config/application.rb
apps/workbench/config/routes.rb
apps/workbench/npm_packages [new file with mode: 0644]

index 66a7adcee3a64b981b12be278374839bbd6d00a1..5fb3718f38dd8aa5a480b7561bdd1e9824be7c47 100644 (file)
@@ -42,3 +42,4 @@
 
 # npm-rails
 /node_modules
+/npm-debug.log
index d4f928be62c601f0a823e40293e031878d5c06f5..aa589ed28c7742b8103a8c63d300435fdf69eff9 100644 (file)
 //= 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 (file)
index 0000000..be4b7e3
--- /dev/null
@@ -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(),
+                            }),
+                        ]),
+                    ]),
+                ]),
+            ])),
+        ])
+    },
+}
index fe0907ed257a4dbd329842adcb009446c87529f9..4a85a091c65105a50ec82c2f65992f0140c29eae 100644 (file)
@@ -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 (file)
index 0000000..058d450
--- /dev/null
@@ -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) {})
+        },
+    })
+}
index d4986538d8635f42027d7f1e7ab561cd2c0fd94f..f72b45177e468b56779a72e38379d9f04b3ebe14 100644 (file)
@@ -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 (file)
index 0000000..ddfa8dd
--- /dev/null
@@ -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/tests/mithril.html b/apps/workbench/app/views/tests/mithril.html
new file mode 100644 (file)
index 0000000..4936f12
--- /dev/null
@@ -0,0 +1 @@
+<div data-mount-mithril="test"></div>
index a1f35c43c333e9cc31c13b7279e16951ced87af0..891dd432c0dccfedf4f773cd41748c0222124e88 100644 (file)
@@ -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
 
index 8aec64e705e97e531d26299e6359ab4159c2e853..8fde2b8f77b229ff1870d8994497350c6658eb3a 100644 (file)
@@ -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 (file)
index 0000000..56acf9f
--- /dev/null
@@ -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'