diff --git a/.bowerrc b/.bowerrc index 3336cb2dd..c0d19644a 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "remoteappmanager/static/components/" + "directory": "remoteappmanager/static/bower_components/" } diff --git a/.gitignore b/.gitignore index c5619e488..ec59a0ff8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ jupyterhub_cookie_secret doc/build doc/source/api doc/source/remoteappmanager_help.txt -remoteappmanager/static/components/ +remoteappmanager/static/bower_components/ node_modules .coverage coverage/ diff --git a/.jshintrc b/.jshintrc index 7d3590114..cc77b267f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,46 +1,48 @@ { - "passfail" : false, - "maxerr" : 100, - "browser" : true, - "jquery" : true, + "passfail" : false, + "maxerr" : 100, + "browser" : true, "predef" : [ "define", "require" ], "debug" : false, "devel" : true, - "es5" : true, - "strict" : true, + "es5" : true, + "strict" : false, "globalstrict" : false, - "asi" : false, - "laxbreak" : false, - "bitwise" : true, + "asi" : false, + "laxbreak" : false, + "bitwise" : true, "boss" : false, "unused" : true, - "curly" : true, - "eqeqeq" : true, - "eqnull" : false, - "evil" : false, - "expr" : false, - "forin" : false, - "immed" : true, - "latedef" : true, + "curly" : true, + "eqeqeq" : true, + "eqnull" : false, + "evil" : false, + "expr" : false, + "forin" : false, + "immed" : true, + "latedef" : true, "loopfunc" : false, - "noarg" : true, + "noarg" : true, "regexp" : true, - "regexdash" : false, - "scripturl" : true, - "shadow" : false, - "supernew" : false, - "undef" : true, - "newcap" : true, - "noempty" : true, - "nonew" : true, - "nomen" : true, - "onevar" : false, - "plusplus" : false, - "sub" : false, - "trailing" : true, - "white" : false, - "indent" : 4 + "regexdash" : false, + "scripturl" : true, + "shadow" : false, + "supernew" : false, + "undef" : true, + "newcap" : true, + "noempty" : true, + "nonew" : true, + "nomen" : true, + "onevar" : false, + "plusplus" : false, + "sub" : false, + "trailing" : true, + "white" : false, + "indent" : 4, + "globals": { + "module": true + } } diff --git a/Makefile b/Makefile index b5e6663d8..133c9e695 100644 --- a/Makefile +++ b/Makefile @@ -30,14 +30,14 @@ deps: else \ plat_packages="docker.io python3-venv"; \ fi; \ - sudo apt-get -qq install -o Dpkg::Options::="--force-confold" --force-yes -y $$plat_packages nodejs python3-pip - npm install - `npm bin`/bower install + sudo apt-get -qq install -o Dpkg::Options::="--force-confold" --force-yes -y $$plat_packages nodejs python3-pip + npm install + `npm bin`/bower install .PHONY: pythondeps pythondeps: pip3 -q install --upgrade pip setuptools - pip3 -q install -r requirements.txt + pip3 -q install -r requirements.txt .PHONY: devdeps devdeps: @@ -47,7 +47,7 @@ devdeps: sudo apt-get -qq install phantomjs .PHONY: develop -develop: +develop: @echo "Installing application" @echo "----------------------" python3 setup.py -q develop @@ -56,16 +56,18 @@ develop: install: @echo "Installing application" @echo "----------------------" + npm run build + npm run build-test python3 setup.py -q install .PHONY: certs -certs: +certs: @echo "Creating certificates" @echo "---------------------" -pushd jupyterhub && sh ../scripts/generate_certificate.sh && popd .PHONY: db -db: +db: @echo "Creating database" @echo "-----------------" pushd jupyterhub; \ @@ -92,7 +94,7 @@ testimages: docker pull simphonyproject/filetransfer:latest; \ docker pull simphonyproject/jupyter:latest; \ fi - + .PHONY: test test: pythontest jstest @@ -104,7 +106,7 @@ pythontest: python -m tornado.testing discover -s remoteappmanager -t . .PHONY: jstest -jstest: +jstest: @echo "Running javascript testsuite" @echo "----------------------------" `npm bin`/jshint --config .jshintrc remoteappmanager/static/js/ diff --git a/bower.json b/bower.json index 6dd59a7be..7e7d46e6e 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,6 @@ "ionicons": "~2.0.1", "jquery": "components/jquery#~2.0", "moment": "~2.7", - "requirejs": "~2.1", "datatables.net": "~1.10.12", "datatables.net-dt": "~1.10.12", "admin-lte": "~2.3.11", diff --git a/jstests/helpers.js b/jstests/helpers.js index 07bb087bb..68423b8fa 100644 --- a/jstests/helpers.js +++ b/jstests/helpers.js @@ -1,15 +1,11 @@ -define([ - "components/vue/dist/vue" -], function(Vue) { - "use strict"; - - var getRenderedText = function(Component, propsData) { +var Vue = require("vuejs"); + +var getRenderedText = function(Component, propsData) { var Ctor = Vue.extend(Component); var vm = new Ctor({ propsData: propsData }).$mount(); return vm.$el.textContent; - }; - - return { +}; + +module.exports = { getRenderedText: getRenderedText - }; -}); +}; diff --git a/jstests/tests.html b/jstests/tests.html index a039d5577..bc9a80b6d 100644 --- a/jstests/tests.html +++ b/jstests/tests.html @@ -1,20 +1,21 @@ - + RemoteAppManager Test Runner - - + -
+
+ + diff --git a/jstests/tests/home/mock_jsapi.js b/jstests/tests/home/mock_jsapi.js index 1b0659334..915e65502 100644 --- a/jstests/tests/home/mock_jsapi.js +++ b/jstests/tests/home/mock_jsapi.js @@ -1,63 +1,61 @@ -define(['jquery'], function ($) { - "use strict"; +var $ = require("jquery"); - return { - Application: { - data: { - "12345": { - image: { - name: "app1", - ui_name: "Application 1", - icon_128: "", - description: "description", - policy: { - allow_home: true, - volume_source: "", - volume_target: "", - volume_mode: "" - }, - configurables: [ - "resolution" - ] - } - }, - "67890": { - image: { - name: "app2", - ui_name: "Application 2", - icon_128: "", - description: "description", - policy: { - allow_home: true, - volume_source: "", - volume_target: "", - volume_mode: "" - }, - configurables: [] - } +module.exports = { + Application: { + data: { + "12345": { + image: { + name: "app1", + ui_name: "Application 1", + icon_128: "", + description: "description", + policy: { + allow_home: true, + volume_source: "", + volume_target: "", + volume_mode: "" + }, + configurables: [ + "resolution" + ] } }, - create: function() { - }, - delete: function() { - }, - retrieve: function(id) { - return $.when(this.data[id]); - - }, - items: function() { - return $.when(["12345", "67890"], this.data); + "67890": { + image: { + name: "app2", + ui_name: "Application 2", + icon_128: "", + description: "description", + policy: { + allow_home: true, + volume_source: "", + volume_target: "", + volume_mode: "" + }, + configurables: [] + } } }, - Container: { - create: function() { - }, - delete: function() { - }, - retrieve: function() { - }, - items: function() { - } + create: function() { + }, + delete: function() { + }, + retrieve: function(id) { + return $.when(this.data[id]); + + }, + items: function() { + return $.when(["12345", "67890"], this.data); + } + }, + Container: { + create: function() { + }, + delete: function() { + }, + retrieve: function() { + }, + items: function() { } - }; -}); + } +}; \ No newline at end of file diff --git a/jstests/tests/home/test_application_list_view.js b/jstests/tests/home/test_application_list_view.js index 1ed496d53..472485c7e 100644 --- a/jstests/tests/home/test_application_list_view.js +++ b/jstests/tests/home/test_application_list_view.js @@ -1,94 +1,90 @@ -define([ - "home/models", - "home/views/application_list_view", - "components/vue/dist/vue", - "vue/filters" -], function (models, applicationListView, Vue) { - "use strict"; - - QUnit.module("home.app_list_view"); - QUnit.test("rendering list", function (assert) { - var done = assert.async(); - - var model = new models.ApplicationListModel(); - var appListView = new applicationListView.ApplicationListView({ - data: function() { return { model: model }; } - }).$mount(); - - assert.ok(model.loading); +var Vue = require("vuejs"); +var models = require("home/models"); +var applicationListView = require("home/views/application_list_view"); +require("filters"); + +QUnit.module("home.app_list_view"); +QUnit.test("rendering list", function (assert) { + var done = assert.async(); + + var model = new models.ApplicationListModel(); + var appListView = new applicationListView.ApplicationListView({ + data: function() { return { model: model }; } + }).$mount(); + + assert.ok(model.loading); + assert.equal( + appListView.$el.querySelector("#loading-spinner").style.display, + "" + ); + + model.update().done(function() { Vue.nextTick(function() { + assert.equal( + appListView.$el.querySelector("#no-app-msg").style.display, + "none" + ); assert.equal( appListView.$el.querySelector("#loading-spinner").style.display, - "" + "none" + ); + assert.equal( + appListView.$el.querySelector("#applistentries").children.length, + model.appList.length ); - model.update().done(function() { Vue.nextTick(function() { - assert.equal( - appListView.$el.querySelector("#no-app-msg").style.display, - "none" - ); - assert.equal( - appListView.$el.querySelector("#loading-spinner").style.display, - "none" - ); - assert.equal( - appListView.$el.querySelector("#applistentries").children.length, - model.appList.length - ); - - done(); - })}); - }); + done(); + })}); +}); - QUnit.test("rendering nothing in the list", function (assert) { - var done = assert.async(); +QUnit.test("rendering nothing in the list", function (assert) { + var done = assert.async(); - var model = new models.ApplicationListModel(); - var appListView = new applicationListView.ApplicationListView({ - data: function() { return { model: model }; } - }).$mount(); + var model = new models.ApplicationListModel(); + var appListView = new applicationListView.ApplicationListView({ + data: function() { return { model: model }; } + }).$mount(); - model.loading = false; + model.loading = false; - Vue.nextTick(function() { - assert.equal( - appListView.$el.querySelector("#no-app-msg").style.display, - "" - ); - assert.equal( - appListView.$el.querySelector("#loading-spinner").style.display, - "none" - ); - assert.equal( - appListView.$el.querySelector("#applistentries").children.length, - 0 - ); + Vue.nextTick(function() { + assert.equal( + appListView.$el.querySelector("#no-app-msg").style.display, + "" + ); + assert.equal( + appListView.$el.querySelector("#loading-spinner").style.display, + "none" + ); + assert.equal( + appListView.$el.querySelector("#applistentries").children.length, + 0 + ); - done(); - }); + done(); }); +}); - QUnit.test("search form", function (assert) { - var done = assert.async(); +QUnit.test("search form", function (assert) { + var done = assert.async(); - var model = new models.ApplicationListModel(); - var appListView = new applicationListView.ApplicationListView({ - data: function() { return { model: model }; } - }).$mount(); + var model = new models.ApplicationListModel(); + var appListView = new applicationListView.ApplicationListView({ + data: function() { return { model: model }; } + }).$mount(); - model.update().done(function() { Vue.nextTick(function() { - assert.notEqual(appListView.visibleList.length, 0); + model.update().done(function() { Vue.nextTick(function() { + assert.notEqual(appListView.visibleList.length, 0); - appListView.searchInput = "heho"; + appListView.searchInput = "heho"; - Vue.nextTick(function() { - assert.equal(appListView.visibleList.length, 0); - assert.equal( - appListView.$el.querySelector("input[name=q]").value, - "heho" - ); + Vue.nextTick(function() { + assert.equal(appListView.visibleList.length, 0); + assert.equal( + appListView.$el.querySelector("input[name=q]").value, + "heho" + ); - done(); - }) - })}); - }); + done(); + }) + })}); }); diff --git a/jstests/tests/home/test_application_view.js b/jstests/tests/home/test_application_view.js index c10703cb3..f7cc61522 100644 --- a/jstests/tests/home/test_application_view.js +++ b/jstests/tests/home/test_application_view.js @@ -1,74 +1,70 @@ -define([ - "home/models", - "home/views/application_view", - "components/vue/dist/vue", - "vue/filters" -], function (models, applicationView, Vue) { - "use strict"; +var Vue = require("vuejs"); +var models = require("home/models"); +var applicationView = require("home/views/application_view"); +require("filters"); - QUnit.module("home.app_view"); - QUnit.test("rendering form", function (assert) { - var done = assert.async(); +QUnit.module("home.app_view"); +QUnit.test("rendering form", function (assert) { + var done = assert.async(); - var model = new models.ApplicationListModel(); - var appView = new applicationView.ApplicationView({ - data: function() { return { model: model }; } - }).$mount(); + var model = new models.ApplicationListModel(); + var appView = new applicationView.ApplicationView({ + data: function() { return { model: model }; } + }).$mount(); - model.update().done(function() { Vue.nextTick(function() { - assert.equal(appView.$el.children[0].tagName, 'DIV'); - assert.ok(appView.$el.children[0].classList.contains('row')); + model.update().done(function() { Vue.nextTick(function() { + assert.equal(appView.$el.children[0].tagName, 'DIV'); + assert.ok(appView.$el.children[0].classList.contains('row')); - assert.equal( - appView.$el.querySelector('.box-title').innerHTML, - model.appList[0].appData.image.ui_name - ); + assert.equal( + appView.$el.querySelector('.box-title').innerHTML, + model.appList[0].appData.image.ui_name + ); - // Simulate application starting - model.appList[0].status = 'STARTING'; + // Simulate application starting + model.appList[0].status = 'STARTING'; - assert.equal( - appView.$el.querySelector('.box-title').innerHTML, - model.appList[0].appData.image.ui_name - ); - assert.equal( - appView.$el.querySelector('#app-description').innerHTML, - model.appList[0].appData.image.description - ); + assert.equal( + appView.$el.querySelector('.box-title').innerHTML, + model.appList[0].appData.image.ui_name + ); + assert.equal( + appView.$el.querySelector('#app-description').innerHTML, + model.appList[0].appData.image.description + ); - done(); - })}); - }); + done(); + })}); +}); - QUnit.test("rendering iframe", function (assert) { - var done = assert.async(); +QUnit.test("rendering iframe", function (assert) { + var done = assert.async(); - var model = new models.ApplicationListModel(); - var appView = new applicationView.ApplicationView({ - data: function() { return { model: model }; } - }).$mount(); + var model = new models.ApplicationListModel(); + var appView = new applicationView.ApplicationView({ + data: function() { return { model: model }; } + }).$mount(); - model.update().done(function() { - // Simulate application running - model.appList[0].status = 'RUNNING'; - model.appList[0].appData.container = {}; - model.appList[0].appData.container.url_id = 'https://127.0.0.1:1234/'; + model.update().done(function() { + // Simulate application running + model.appList[0].status = 'RUNNING'; + model.appList[0].appData.container = {}; + model.appList[0].appData.container.url_id = 'https://127.0.0.1:1234/'; - Vue.nextTick(function() { - assert.equal(appView.$el.children[0].tagName, 'IFRAME'); + Vue.nextTick(function() { + assert.equal(appView.$el.children[0].tagName, 'IFRAME'); - // Render form again by selecting the other application which is stopped - model.selectedIndex = 1; + // Render form again by selecting the other application which is stopped + model.selectedIndex = 1; - Vue.nextTick(function() { - console.log(model.appList[1].ui_name) - assert.equal( - appView.$el.querySelector('.box-title').innerHTML, - model.appList[1].appData.image.ui_name - ); + Vue.nextTick(function() { + console.log(model.appList[1].ui_name) + assert.equal( + appView.$el.querySelector('.box-title').innerHTML, + model.appList[1].appData.image.ui_name + ); - done(); - }); + done(); }); }); }); diff --git a/jstests/tests/home/test_configurables.js b/jstests/tests/home/test_configurables.js index aec9bcaeb..2a7d6b763 100644 --- a/jstests/tests/home/test_configurables.js +++ b/jstests/tests/home/test_configurables.js @@ -1,25 +1,21 @@ -define([ - "home/configurables" -], function (configurables) { - "use strict"; +var configurables = require("home/configurables"); - QUnit.module("home.configurables"); - QUnit.test("instantiation", function (assert) { - var resolutionConf = new configurables.resolution.model(); +QUnit.module("home.configurables"); +QUnit.test("instantiation", function (assert) { + var resolutionConf = new configurables.resolution.model(); - assert.equal(resolutionConf.tag, "resolution"); - assert.deepEqual(resolutionConf.configDict, { resolution: "Window" }); - assert.notEqual(resolutionConf.asConfigDict().resolution, "Window"); + assert.equal(resolutionConf.tag, "resolution"); + assert.deepEqual(resolutionConf.configDict, { resolution: "Window" }); + assert.notEqual(resolutionConf.asConfigDict().resolution, "Window"); - resolutionConf.configDict = { resolution: '1280x1024' }; - assert.equal(resolutionConf.asConfigDict().resolution, '1280x1024'); - }); + resolutionConf.configDict = { resolution: '1280x1024' }; + assert.equal(resolutionConf.asConfigDict().resolution, '1280x1024'); +}); - QUnit.test("view", function (assert) { - var propsData = { configDict: { resolution: "Window" } }; - var component = new configurables.resolution.component({propsData: propsData}).$mount(); +QUnit.test("view", function (assert) { + var propsData = { configDict: { resolution: "Window" } }; + var component = new configurables.resolution.component({propsData: propsData}).$mount(); - assert.notEqual(component.$el.querySelector("select"), null); - assert.equal(component.$el.querySelector("select").children.length, 5); - }); -}); \ No newline at end of file + assert.notEqual(component.$el.querySelector("select"), null); + assert.equal(component.$el.querySelector("select").children.length, 5); +}); diff --git a/jstests/tests/home/test_models.js b/jstests/tests/home/test_models.js index fe3252dc4..2529abdd9 100644 --- a/jstests/tests/home/test_models.js +++ b/jstests/tests/home/test_models.js @@ -1,22 +1,17 @@ -define([ - "home/models" -], function (models) { - "use strict"; +var models = require("home/models"); - QUnit.module("home.models"); - QUnit.test("instantiation", function (assert) { - var model = new models.ApplicationListModel(); +QUnit.module("home.models"); +QUnit.test("instantiation", function (assert) { + var model = new models.ApplicationListModel(); - assert.equal(model.appList.length, 0); - assert.equal(model.selectedIndex, null); + assert.equal(model.appList.length, 0); + assert.equal(model.selectedIndex, null); - model.update().done(function() { - assert.equal(model.appList.length, 2); - assert.equal(model.selectedIndex, 0); + model.update().done(function() { + assert.equal(model.appList.length, 2); + assert.equal(model.selectedIndex, 0); - assert.equal(model.appList[0].appData.image.configurables[0], "resolution"); - assert.equal(model.appList[0].configurables[0].configDict.resolution, "Window"); - }); + assert.equal(model.appList[0].appData.image.configurables[0], "resolution"); + assert.equal(model.appList[0].configurables[0].configDict.resolution, "Window"); }); }); - diff --git a/jstests/tests/test_analytics.js b/jstests/tests/test_analytics.js index 3d44b9fd3..2700f6a69 100644 --- a/jstests/tests/test_analytics.js +++ b/jstests/tests/test_analytics.js @@ -1,29 +1,31 @@ -define(function(require) { - "use strict"; - var gamodule = require("gamodule"); - - QUnit.module("Google Analytics"); - QUnit.test("test without analytics", function (assert) { - var result=[]; - window.ga = function(cmd, id, auto) { - result[0] = [cmd, id, auto]; - }; - window.apidata.analytics = undefined; - var ga = gamodule.init(); - assert.notEqual(ga, undefined); - assert.equal(result.length, 0); - }); - - QUnit.test("test with analytics", function (assert) { - var result=[]; - window.ga = function(cmd, id, auto) { - result[0] = [cmd, id, auto]; - }; - window.apidata.analytics = {"tracking_id": "X"}; - var ga = gamodule.init(); - assert.notEqual(ga, undefined); - assert.equal(result[0][0], "create"); - assert.equal(result[0][1], "X"); - assert.equal(result[0][2], "auto"); - }); +var gamodule = require("gamodule"); + +window.apidata = { + base_url: "/", + prefix: "/" +}; + +QUnit.module("Google Analytics"); +QUnit.test("test without analytics", function (assert) { + var result=[]; + window.ga = function(cmd, id, auto) { + result[0] = [cmd, id, auto]; + }; + window.apidata.analytics = undefined; + var ga = gamodule.init(); + assert.notEqual(ga, undefined); + assert.equal(result.length, 0); +}); + +QUnit.test("test with analytics", function (assert) { + var result=[]; + window.ga = function(cmd, id, auto) { + result[0] = [cmd, id, auto]; + }; + window.apidata.analytics = {"tracking_id": "X"}; + var ga = gamodule.init(); + assert.notEqual(ga, undefined); + assert.equal(result[0][0], "create"); + assert.equal(result[0][1], "X"); + assert.equal(result[0][2], "auto"); }); diff --git a/jstests/tests/test_urlutils.js b/jstests/tests/test_urlutils.js index a6c9b7118..f93d7d294 100644 --- a/jstests/tests/test_urlutils.js +++ b/jstests/tests/test_urlutils.js @@ -1,8 +1,6 @@ -define([ - "urlutils" -], function (urlutils) { - QUnit.module("URL Utils"); - QUnit.test("path_join", function (assert) { - assert.equal(urlutils.url_path_join("foo", "bar", "baz"), "foo/bar/baz"); - }); +var urlutils = require("urlutils"); + +QUnit.module("URL Utils"); +QUnit.test("path_join", function (assert) { + assert.equal(urlutils.url_path_join("foo", "bar", "baz"), "foo/bar/baz"); }); diff --git a/jstests/tests/test_utils.js b/jstests/tests/test_utils.js index 6d470a5ca..e46459706 100644 --- a/jstests/tests/test_utils.js +++ b/jstests/tests/test_utils.js @@ -1,16 +1,14 @@ -define([ - "utils" -], function (utils) { - QUnit.module("Utils"); - QUnit.test("update", function (assert) { - var o1 = {"foo": "bar"}; - var o2 = {"bar": "baz"}; - - utils.update(o1, o2); - - assert.deepEqual(o1, { - "foo": "bar", - "bar": "baz" - }); - }); +var utils = require("utils"); + +QUnit.module("Utils"); +QUnit.test("update", function (assert) { + var o1 = {"foo": "bar"}; + var o2 = {"bar": "baz"}; + + utils.update(o1, o2); + + assert.deepEqual(o1, { + "foo": "bar", + "bar": "baz" + }); }); diff --git a/jstests/tests/vue/components/test_ConfirmDialog.js b/jstests/tests/vue/components/test_ConfirmDialog.js index 1dcb0ec6f..d2fea8c32 100644 --- a/jstests/tests/vue/components/test_ConfirmDialog.js +++ b/jstests/tests/vue/components/test_ConfirmDialog.js @@ -1,19 +1,15 @@ -define([ - "components/vue/dist/vue", - "admin/vue-components/toolkit/ConfirmDialog", - "jstests/helpers" -], function (Vue, ConfirmDialog, helpers) { - "use strict"; +var Vue = require("vuejs"); +var ConfirmDialog = require("toolkit-dir/ConfirmDialog"); +var helpers = require("helpers"); - QUnit.module("ConfirmDialog"); - QUnit.test("rendering", function (assert) { - assert.equal(helpers.getRenderedText(ConfirmDialog, { - title: "This is the title", - closeCallback: function() {} - }), "This is the title Cancel Ok"); +QUnit.module("ConfirmDialog"); +QUnit.test("rendering", function (assert) { + assert.equal(helpers.getRenderedText(ConfirmDialog, { + title: "This is the title", + closeCallback: function() {} + }), "This is the title Cancel Ok"); - assert.equal(helpers.getRenderedText(ConfirmDialog, { - title: "This is the title" - }), "This is the title Ok"); - }); + assert.equal(helpers.getRenderedText(ConfirmDialog, { + title: "This is the title" + }), "This is the title Ok"); }); diff --git a/jstests/tests/vue/components/test_DataTable.js b/jstests/tests/vue/components/test_DataTable.js index d7ce8ce5a..29cf10c6e 100644 --- a/jstests/tests/vue/components/test_DataTable.js +++ b/jstests/tests/vue/components/test_DataTable.js @@ -1,23 +1,19 @@ -define([ - "components/vue/dist/vue", - "admin/vue-components/toolkit/DataTable", - "jstests/helpers" -], function (Vue, DataTable, helpers) { - "use strict"; - - QUnit.module("DataTable"); - QUnit.test("rendering", function (assert) { - assert.equal(helpers.getRenderedText(DataTable, { - headers: ["foo", "bar"], - rows: [[1,2], [3,4]], - globalActions: [{ - label: "New", - callback: function() {} - }], - rowActions: [{ - label: "Remove", - callback: function() {} - }] - }), "New foobar Actions 12 Remove34 Remove"); - }); +var Vue = require("vuejs"); +var DataTable = require("toolkit-dir/DataTable"); +var helpers = require("helpers") + +QUnit.module("DataTable"); +QUnit.test("rendering", function (assert) { + assert.equal(helpers.getRenderedText(DataTable, { + headers: ["foo", "bar"], + rows: [[1,2], [3,4]], + globalActions: [{ + label: "New", + callback: function() {} + }], + rowActions: [{ + label: "Remove", + callback: function() {} + }] + }), "New foobar Actions 12 Remove34 Remove"); }); diff --git a/jstests/testsuite.js b/jstests/testsuite.js index ab7a02729..9790b1437 100644 --- a/jstests/testsuite.js +++ b/jstests/testsuite.js @@ -1,40 +1,8 @@ -(function () { - require.config({ - baseUrl: "../remoteappmanager/static/js/", - paths: { - jstests: '../../../jstests/', - components: '../components', - jquery: '../components/jquery/jquery.min', - bootstrap: '../components/bootstrap/js/bootstrap.min', - moment: "../components/moment/moment", - "jsapi/v1/resources": "../../../jstests/tests/home/mock_jsapi", - underscore: "../components/underscore/underscore-min" - }, - shim: { - bootstrap: { - deps: ["jquery"], - exports: "bootstrap" - } - } - }); - - require([ - "tests/home/test_configurables.js", - "tests/home/test_models.js", - "tests/home/test_application_list_view.js", - "tests/home/test_application_view.js", - "tests/vue/components/test_DataTable.js", - "tests/vue/components/test_ConfirmDialog.js", - "tests/test_utils.js", - "tests/test_analytics.js" - ], function() { - window.apidata = { - base_url: "/", - prefix: "/" - }; - - QUnit.load(); - QUnit.start(); - }); -}()); - +require("./tests/home/test_configurables.js"); +require("./tests/home/test_models.js"); +require("./tests/home/test_application_list_view.js"); +require("./tests/home/test_application_view.js"); +require("./tests/vue/components/test_DataTable.js"); +require("./tests/vue/components/test_ConfirmDialog.js"); +require("./tests/test_utils.js"); +require("./tests/test_analytics.js"); diff --git a/package.json b/package.json index 44d066c7e..25440cabf 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,22 @@ "type": "git", "url": "https://github.com/simphony-remote/simphony-remote.git" }, + "scripts": { + "watch": "webpack --config webpackfile.js --watch", + "build": "webpack --config webpackfile.js -p", + "watch-test": "webpack --config webpackfile-test.js --watch", + "build-test": "webpack --config webpackfile-test.js -p" + }, "dependencies": { "bower": "*", "configurable-http-proxy": "git://github.com/jupyterhub/configurable-http-proxy.git#2.0.1" }, "devDependencies": { + "babel-core": "^6.24.1", + "babel-loader": "^7.0.0", + "babel-preset-env": "^1.4.0", "jshint": "*", - "node-qunit-phantomjs": "*" + "node-qunit-phantomjs": "*", + "webpack": "2.5.1" } } diff --git a/remoteappmanager/static/js/admin-resources.js b/remoteappmanager/static/js/admin-resources.js new file mode 100644 index 000000000..bc51a6cd0 --- /dev/null +++ b/remoteappmanager/static/js/admin-resources.js @@ -0,0 +1,414 @@ +var $ = require("jquery"); +var urlUtils = require("urlutils"); +var utils = require("utils"); + +var object_to_query_args = function (obj) { + var keys = Object.keys(obj); + if (keys.length === 0) { + return ""; + } + + var result = []; + for (var idx in keys) { + var key = keys[idx]; + var value = obj[key]; + var key_enc = encodeURIComponent(key); + if ($.isArray(value)) { + for (var v in value) { + result.push(key_enc+"="+encodeURIComponent(v)); + } + } else { + result.push(key_enc+"="+encodeURIComponent(value)); + } + } + + return result.join("&"); +}; + +var API = (function () { + // Object representing the interface to the Web API. + // @param base_url : the url at which to find the web API endpoint. + var self = {}; + self.base_urlpath = "/user/admin/"; + self.default_options = { + contentType: "application/json", + cache: false, + dataType : null, + processData: false, + success: null, + error: null + }; + + self.request = function (req_type, endpoint, body, query_args) { + // Performs a request to the final endpoint + var options = {}; + utils.update(options, self.default_options); + utils.update(options, { + type: req_type, + data: body + }); + + var url = urlUtils.pathJoin( + self.base_urlpath, + "api", "v1", + urlUtils.encodeUriComponents(endpoint) + )+'/'; + + if (query_args) { + url = url + "?" + object_to_query_args(query_args); + } + return $.ajax(url, options); + }; + return self; +})(); + +var RestError = function(code, message) { + console.log("Creating error "+code+" message: "+message); + this.code = code; + this.message = message; +}; + +var fail_handler = function(promise, jqXHR) { + var status = jqXHR.status; + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + var err = new RestError(status, ""); + if (payload !== null) { + utils.update(err, payload); + } + promise.reject(err); +}; + +var create_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 201) { + // Strange situation in which the call succeeded, but + // not with a 201. Just do our best. + console.log( + "Create succeded but response with status " + + status + + " instead of 201." + ); + promise.reject(status, payload); + return; + } + + var id, location; + try { + location = jqXHR.getResponseHeader('Location'); + var url = urlUtils.parse(location); + var arr = url.pathname.replace(/\/$/, "").split('/'); + id = arr[arr.length - 1]; + } catch (e) { + console.log("Response had invalid or absent Location header"); + promise.reject(status, payload); + return; + } + promise.resolve(id, location); +}; + +var create_singleton_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 201) { + // Strange situation in which the call succeeded, but + // not with a 201. Just do our best. + console.log( + "Create succeded but response with status " + + status + + " instead of 201." + ); + promise.reject(status, payload); + return; + } + + var location; + try { + location = jqXHR.getResponseHeader('Location'); + } catch (e) { + console.log("Response had invalid or absent Location header"); + promise.reject(status, payload); + return; + } + promise.resolve(location); +}; + +var update_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 204) { + // Strange situation in which the call succeeded, but + // not with a 201. Just do our best. + console.log( + "Update succeded but response with status " + + status + + " instead of 204." + ); + promise.reject(status, payload); + return; + } + + promise.resolve(); +}; + +var delete_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 204) { + console.log( + "Delete succeded but response with status " + + status + + " instead of 204." + ); + promise.reject(status, payload); + return; + } + promise.resolve(); +}; + +var retrieve_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 200) { + console.log( + "Retrieve succeded but response with status " + + status + + " instead of 200." + ); + promise.reject(status, payload); + return; + } + + if (payload === null) { + console.log( + "Retrieve succeded but empty or invalid payload" + ); + promise.reject(status, payload); + return; + } + + promise.resolve(payload); +}; + +var Resource = function(type) { + this.type = type; + + this.create = function(representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("POST", type, body, query_args) + .done(function(data, textStatus, jqXHR) { + create_handler(promise, data, textStatus, jqXHR); + }) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.update = function(id, representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("PUT", urlUtils.pathJoin(type, id), body, query_args) + .done(function(data, textStatus, jqXHR) { + update_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.delete = function(id, query_args) { + var promise = $.Deferred(); + + API.request("DELETE", urlUtils.pathJoin(type, id), null, query_args) + .done(function(data, textStatus, jqXHR) { + delete_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.retrieve = function(id, query_args) { + var promise = $.Deferred(); + + API.request("GET", urlUtils.pathJoin(type, id), null, query_args) + .done(function(data, textStatus, jqXHR) { + retrieve_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.items = function(query_args) { + var promise = $.Deferred(); + + API.request("GET", type, null, query_args) + .done(function(data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 200) { + console.log( + "Items retrieve succeded but response with status " + + status + + " instead of 200." + ); + promise.reject(status, payload); + return; + } + + if (payload === null) { + console.log( + "Items Retrieve succeded but empty or invalid payload" + ); + promise.reject(status, payload); + return; + } + + promise.resolve( + payload.identifiers, + payload.items, + payload.offset, + payload.total); + }) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; +}; + +var SingletonResource = function(type) { + this.type = type; + this.create = function(representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("POST", type, body, query_args) + .done(function(data, textStatus, jqXHR) { + create_singleton_handler(promise, data, textStatus, jqXHR); + }) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + this.update = function(representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("PUT", type, body, query_args) + .done(function(data, textStatus, jqXHR) { + update_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + + + }; + + this.delete = function(query_args) { + var promise = $.Deferred(); + + API.request("DELETE", type, null, query_args) + .done(function(data, textStatus, jqXHR) { + delete_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.retrieve = function(query_args) { + var promise = $.Deferred(); + + API.request("GET", type, null, query_args) + .done(function(data, textStatus, jqXHR) { + retrieve_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; +}; + +module.exports = { + "Application" : new Resource("applications"), + "Accounting" : new Resource("accounting"), + "User" : new Resource("users"), + "Container" : new Resource("containers"), + "Stats" : new SingletonResource("stats"), +}; \ No newline at end of file diff --git a/remoteappmanager/static/js/admin/main.js b/remoteappmanager/static/js/admin/main.js index 4e6ae6b69..7d680f022 100644 --- a/remoteappmanager/static/js/admin/main.js +++ b/remoteappmanager/static/js/admin/main.js @@ -1,58 +1,39 @@ -/*globals: require, console*/ -require([ - "components/lodash/dist/lodash", - "components/vue/dist/vue", - "components/vue-router/dist/vue-router", - "components/vue-form/dist/vue-form", - "admin/vue-components/toolkit/toolkit", - "admin/vue-components/MainView", - "admin/vue-components/ContainersView", - "admin/vue-components/UsersView", - "admin/vue-components/ApplicationsView", - "admin/vue-components/AccountingView" -], function( - _, - Vue, - VueRouter, - VueForm, - toolkit, - MainView, - ContainersView, - UsersView, - ApplicationsView, - AccountingView - ) { - - "use strict"; +var _ = require("lodash"); +var Vue = require("vuejs"); +var VueRouter = require("vue-router"); +var VueForm = require("vue-form"); +require("toolkit"); +var MainView = require("./vue-components/MainView"); +var ContainersView = require("./vue-components/ContainersView"); +var UsersView = require("./vue-components/UsersView"); +var ApplicationsView = require("./vue-components/ApplicationsView"); +var AccountingView = require("./vue-components/AccountingView"); - // install router - Vue.use(VueRouter); - Vue.use(VueForm, { +// install router +Vue.use(VueRouter); +Vue.use(VueForm, { inputClasses: { - valid: 'form-control-success', - invalid: 'form-control-danger' + valid: 'form-control-success', + invalid: 'form-control-danger' } - }); - - Vue.filter("truncate", function(value) { - return _.truncate(value, {'length': 12 }); - } - ); +}); + +Vue.filter("truncate", function(value) { + return _.truncate(value, {'length': 12 }); +}); - var router = new VueRouter({ +var router = new VueRouter({ routes: [ - { path: '/', component: MainView }, - { path: '/containers', component: ContainersView }, - { path: '/users', component: UsersView }, - { path: '/users/:id/accounting', component: AccountingView, name: "user_accounting" }, - { path: '/applications', component: ApplicationsView } + { path: '/', component: MainView }, + { path: '/containers', component: ContainersView }, + { path: '/users', component: UsersView }, + { path: '/users/:id/accounting', component: AccountingView, name: "user_accounting" }, + { path: '/applications', component: ApplicationsView } ] - }); +}); - var vm; - vm = new Vue({ +var vm; +vm = new Vue({ el: "#app", router: router - }); - }); diff --git a/remoteappmanager/static/js/admin/vue-components/AccountingView.js b/remoteappmanager/static/js/admin/vue-components/AccountingView.js index bd4dea783..8dd2fb25a 100644 --- a/remoteappmanager/static/js/admin/vue-components/AccountingView.js +++ b/remoteappmanager/static/js/admin/vue-components/AccountingView.js @@ -1,132 +1,130 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources", - "admin/vue-components/accounting/NewAccountingDialog" -], function(Vue, resources, NewAccountingDialog) { - "use strict"; +var resources = require("admin-resources"); +var NewAccountingDialog = require("./accounting/NewAccountingDialog"); - return { +module.exports = { components: { - 'new-accounting-dialog': NewAccountingDialog + 'new-accounting-dialog': NewAccountingDialog }, - template: - '' + - '
Accounting for user {{ $route.params.id }}
' + - '
' + - '
' + - ' Error: {{communicationError}}' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
Do you want to remove accounting ' + - ' {{ removeAccountingDialog.accountingToRemove }}?' + - '
' + - '
' + - '
' + - '
', + + template: + '' + + '
Accounting for user {{ $route.params.id }}
' + + '
' + + '
' + + ' Error: {{communicationError}}' + + '
' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
Do you want to remove accounting ' + + ' {{ removeAccountingDialog.accountingToRemove }}?' + + '
' + + '
' + + '
' + + '
', + data: function () { - var self = this; - return { - table: { - headers: [ - "ID", "Image", "Workspace", "Vol. source", "Vol. target", "Readonly" - ], - rows: [], - globalActions: [ - { - label: "Create New Entry", - callback: function() { self.newAccountingDialog.show = true; } - } - ], - rowActions: [ - { - label: "Remove", - callback: this.removeAction - } - ] - }, - newAccountingDialog: { - show: false, - userId: this.$route.params.id - }, - removeAccountingDialog: { - show: false, - accountingToRemove: null - }, - communicationError: null - }; + var self = this; + return { + table: { + headers: [ + "ID", "Image", "Workspace", "Vol. source", "Vol. target", "Readonly" + ], + rows: [], + globalActions: [{ + label: "Create New Entry", + callback: function() { self.newAccountingDialog.show = true; } + }], + rowActions: [{ + label: "Remove", + callback: this.removeAction + }] + }, + + newAccountingDialog: { + show: false, + userId: this.$route.params.id + }, + + removeAccountingDialog: { + show: false, + accountingToRemove: null + }, + + communicationError: null + }; }, + mounted: function () { - this.updateTable(); + this.updateTable(); }, + methods: { - updateTable: function() { - var self = this; - this.communicationError = null; - resources.Accounting.items({filter: JSON.stringify({user_id: this.$route.params.id })}) - .done( - function (identifiers, items) { - self.table.rows = []; - identifiers.forEach(function(id) { - var item = items[id]; - self.table.rows.push([ - id, - item.image_name, - item.allow_home, - item.volume_source, - item.volume_target, - item.volume_mode === "ro"]); + updateTable: function() { + var self = this; + this.communicationError = null; + resources.Accounting.items({filter: JSON.stringify({user_id: this.$route.params.id })}) + .done(function (identifiers, items) { + self.table.rows = []; + identifiers.forEach(function(id) { + var item = items[id]; + self.table.rows.push([ + id, + item.image_name, + item.allow_home, + item.volume_source, + item.volume_target, + item.volume_mode === "ro" + ]); + }); + }) + .fail(function () { + self.communicationError = "The request could not be executed successfully"; }); - }) - .fail( - function () { - self.communicationError = "The request could not be executed successfully"; - } - ); - }, - newAccountingCreated: function() { - this.newAccountingDialog.show = false; - this.updateTable(); - }, - removeAction: function(row) { - this.removeAccountingDialog.accountingToRemove = row[0]; - this.removeAccountingDialog.show = true; - }, - closeRemoveAccountingDialog: function() { - this.removeAccountingDialog.show = false; - this.removeAccountingDialog.accountingToRemove = null; - }, - removeAccounting: function () { - var self = this; - resources.Accounting.delete(this.removeAccountingDialog.accountingToRemove) - .done(function () { - self.closeRemoveAccountingDialog(); - self.updateTable(); - }) - .fail( - function () { - self.closeRemoveAccountingDialog(); - self.communicationError = "The request could not be executed successfully"; - } - ); - } + }, + + newAccountingCreated: function() { + this.newAccountingDialog.show = false; + this.updateTable(); + }, + + removeAction: function(row) { + this.removeAccountingDialog.accountingToRemove = row[0]; + this.removeAccountingDialog.show = true; + }, + + closeRemoveAccountingDialog: function() { + this.removeAccountingDialog.show = false; + this.removeAccountingDialog.accountingToRemove = null; + }, + + removeAccounting: function () { + var self = this; + resources.Accounting.delete(this.removeAccountingDialog.accountingToRemove) + .done(function () { + self.closeRemoveAccountingDialog(); + self.updateTable(); + }) + .fail(function () { + self.closeRemoveAccountingDialog(); + self.communicationError = "The request could not be executed successfully"; + }); + } } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/ApplicationsView.js b/remoteappmanager/static/js/admin/vue-components/ApplicationsView.js index 943158ea9..82b85d25d 100644 --- a/remoteappmanager/static/js/admin/vue-components/ApplicationsView.js +++ b/remoteappmanager/static/js/admin/vue-components/ApplicationsView.js @@ -1,136 +1,133 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources", - "admin/vue-components/applications/NewApplicationDialog" -], function(Vue, resources, NewApplicationDialog) { - "use strict"; +var resources = require("admin-resources"); +var NewApplicationDialog = require("./applications/NewApplicationDialog"); - return { +module.exports = { components: { - 'new-application-dialog': NewApplicationDialog + 'new-application-dialog': NewApplicationDialog }, - template: - '' + - '
' + - '
' + - ' Error: {{communicationError}}' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
Do you want to remove Application ' + - ' {{removeApplicationDialog.applicationToRemove.name}} ' + - ' ({{removeApplicationDialog.applicationToRemove.id}})
' + - '
' + - '
' + - '
', + + template: + '' + + '
' + + '
' + + ' Error: {{communicationError}}' + + '
' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
Do you want to remove Application ' + + ' {{removeApplicationDialog.applicationToRemove.name}} ' + + ' ({{removeApplicationDialog.applicationToRemove.id}})
' + + '
' + + '
' + + '
', + data: function () { - var self=this; - return { - table: { - headers: ["ID", "Image"], - rows: [], - globalActions: [ - { - label: "Create New Entry", - callback: function() { self.newApplicationDialog.show = true; } - } - ], - rowActions: [ - { - label: "Remove", - callback: this.removeAction - } - ] - }, - newApplicationDialog: { - show: false - }, - removeApplicationDialog: { - show: false, - applicationToRemove: { - id: null, - name: "" - } - }, - communicationError: null - }; + var self=this; + return { + table: { + headers: ["ID", "Image"], + rows: [], + globalActions: [{ + label: "Create New Entry", + callback: function() { self.newApplicationDialog.show = true; } + }], + rowActions: [{ + label: "Remove", + callback: this.removeAction + }] + }, + + newApplicationDialog: { + show: false + }, + + removeApplicationDialog: { + show: false, + applicationToRemove: { + id: null, + name: "" + } + }, + + communicationError: null + }; }, + mounted: function () { - this.updateTable(); + this.updateTable(); }, + methods: { - updateTable: function() { - var self = this; - this.communicationError = null; - resources.Application.items() - .done( - function (identifiers, items) { - self.table.rows = []; - identifiers.forEach(function(id) { - var item = items[id]; - self.table.rows.push([ - id, - item.image_name - ]); + updateTable: function() { + var self = this; + this.communicationError = null; + resources.Application.items() + .done(function (identifiers, items) { + self.table.rows = []; + identifiers.forEach(function(id) { + var item = items[id]; + self.table.rows.push([ + id, + item.image_name + ]); + }); + }) + .fail(function () { + self.communicationError = "The request could not be executed successfully"; }); - }) - .fail( - function () { - self.communicationError = "The request could not be executed successfully"; - } - ); - }, - newApplicationCreated: function() { - this.newApplicationDialog.show = false; - this.updateTable(); - }, - newApplicationDialogClosed: function() { - this.newApplicationDialog.show = false; - }, - removeAction: function(row) { - this.removeApplicationDialog.applicationToRemove = { - id: row[0], - name: row[1] - }; - this.removeApplicationDialog.show = true; - }, - removeApplication: function () { - var self = this; - resources.Application.delete(this.removeApplicationDialog.applicationToRemove.id) - .done(function () { - self.closeRemoveApplicationDialog(); - self.updateTable(); - }) - .fail( - function () { - self.closeRemoveApplicationDialog(); - this.communicationError = "The request could not be executed successfully"; - } - ); - }, - closeRemoveApplicationDialog: function() { - this.removeApplicationDialog.show = false; - this.removeApplicationDialog.applicationToRemove = { - name: "", - id: null - }; - } + }, + + newApplicationCreated: function() { + this.newApplicationDialog.show = false; + this.updateTable(); + }, + + newApplicationDialogClosed: function() { + this.newApplicationDialog.show = false; + }, + + removeAction: function(row) { + this.removeApplicationDialog.applicationToRemove = { + id: row[0], + name: row[1] + }; + this.removeApplicationDialog.show = true; + }, + + removeApplication: function () { + var self = this; + resources.Application.delete(this.removeApplicationDialog.applicationToRemove.id) + .done(function () { + self.closeRemoveApplicationDialog(); + self.updateTable(); + }) + .fail(function () { + self.closeRemoveApplicationDialog(); + this.communicationError = "The request could not be executed successfully"; + }); + }, + closeRemoveApplicationDialog: function() { + this.removeApplicationDialog.show = false; + this.removeApplicationDialog.applicationToRemove = { + name: "", + id: null + }; + } } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/ContainersView.js b/remoteappmanager/static/js/admin/vue-components/ContainersView.js index b03e1d892..7a2c067ea 100644 --- a/remoteappmanager/static/js/admin/vue-components/ContainersView.js +++ b/remoteappmanager/static/js/admin/vue-components/ContainersView.js @@ -1,11 +1,7 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources" -], function(Vue, resources) { - "use strict"; +var resources = require("admin-resources"); - return { - template: +module.exports = { + template: '' + '
' + ' Error: {{communicationError}}' + @@ -24,74 +20,73 @@ define([ '
Do you want to stop container {{ stopContainerDialog.containerToStop }}?
' + ' ' + '', - data: function () { + + data: function () { return { - table: { - headers: ["URL ID", "User", "Image", "Docker ID", "Mapping ID"], - rows: [], - rowActions: [ - { - label: "Stop", - callback: this.stopAction - } - ] - }, - stopContainerDialog: { - show: false, - containerToStop: null - }, - communicationError: null + table: { + headers: ["URL ID", "User", "Image", "Docker ID", "Mapping ID"], + rows: [], + rowActions: [{ + label: "Stop", + callback: this.stopAction + }] + }, + stopContainerDialog: { + show: false, + containerToStop: null + }, + communicationError: null }; - }, - mounted: function () { + }, + + mounted: function () { this.updateTable(); - }, - methods: { + }, + + methods: { updateTable: function() { - var self = this; - this.communicationError = null; - resources.Container.items() - .done( - function (identifiers, items) { + var self = this; + this.communicationError = null; + resources.Container.items() + .done(function (identifiers, items) { self.table.rows = []; identifiers.forEach(function(id) { - var item = items[id]; - self.table.rows.push([ - id, - item.user, - item.image_name, - item.docker_id, - item.mapping_id]); + var item = items[id]; + self.table.rows.push([ + id, + item.user, + item.image_name, + item.docker_id, + item.mapping_id + ]); }); - }) - .fail( - function () { + }) + .fail(function () { self.communicationError = "The request could not be executed successfully"; - } - ); + }); }, + stopAction: function(row) { - this.stopContainerDialog.containerToStop = row[0]; - this.stopContainerDialog.show = true; + this.stopContainerDialog.containerToStop = row[0]; + this.stopContainerDialog.show = true; }, + stopContainer: function () { - var self = this; - resources.Container.delete(this.stopContainerDialog.containerToStop) + var self = this; + resources.Container.delete(this.stopContainerDialog.containerToStop) .done(function () { - self.updateTable(); - self.closeStopContainerDialog(); + self.updateTable(); + self.closeStopContainerDialog(); }) - .fail( - function () { + .fail(function () { self.closeStopContainerDialog(); self.communicationError = "The request could not be executed successfully"; - } - ); + }); }, + closeStopContainerDialog: function() { - this.stopContainerDialog.show = false; - this.stopContainerDialog.containerToStop = null; + this.stopContainerDialog.show = false; + this.stopContainerDialog.containerToStop = null; } - } - }; - }); + } +}; diff --git a/remoteappmanager/static/js/admin/vue-components/MainView.js b/remoteappmanager/static/js/admin/vue-components/MainView.js index 4f89349b8..b82cba9e4 100644 --- a/remoteappmanager/static/js/admin/vue-components/MainView.js +++ b/remoteappmanager/static/js/admin/vue-components/MainView.js @@ -1,73 +1,65 @@ -define([ - "components/vue/dist/vue.min", - "jsapi/v1/resources" -], function(Vue, resources) { - "use strict"; - - return { - template: - '
' + - '
' + - '
' + - '
Statistics
' + - '
' + - '
' + - ' Error: {{communicationError}}' + - '
' + - '
' + - '
' + - '
Realm
' + - '
{{ realm }}
' + - '
' + - '
' + - '
Total users
' + - '
{{ num_total_users }}
' + - '
' + - '
' + - '
Number of applications
' + - '
{{ num_applications }}
' + - '
' + - '
' + - '
' + - '
Active users
' + - '
{{ num_active_users }}
' + - '
' + - '
' + - '
Running containers
' + - '
{{ num_running_containers }}
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
', +var resources = require("admin-resources"); + +module.exports = { + template: + '
' + + '
' + + '
' + + '
Statistics
' + + '
' + + '
' + + ' Error: {{communicationError}}' + + '
' + + '
' + + '
' + + '
Realm
' + + '
{{ realm }}
' + + '
' + + '
' + + '
Total users
' + + '
{{ num_total_users }}
' + + '
' + + '
' + + '
Number of applications
' + + '
{{ num_applications }}
' + + '
' + + '
' + + '
' + + '
Active users
' + + '
{{ num_active_users }}
' + + '
' + + '
' + + '
Running containers
' + + '
{{ num_running_containers }}
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
', + data: function() { - return { - communicationError: null, - realm: "", - num_total_users: "", - num_applications: "", - num_active_users: "", - num_running_containers: "" - }; + return { + communicationError: null, + realm: "", + num_total_users: "", + num_applications: "", + num_active_users: "", + num_running_containers: "" + }; }, + mounted: function() { - resources.Stats.retrieve() - .done( - (function(rep) { - this.$data.realm = rep.realm; - this.$data.num_total_users = rep.num_total_users; - this.$data.num_applications = rep.num_applications; - this.$data.num_active_users = rep.num_active_users; - this.$data.num_running_containers = rep.num_running_containers; - }).bind(this) - ) - .fail( - (function() { + resources.Stats.retrieve() + .done(function(rep) { + this.realm = rep.realm; + this.num_total_users = rep.num_total_users; + this.num_applications = rep.num_applications; + this.num_active_users = rep.num_active_users; + this.num_running_containers = rep.num_running_containers; + }.bind(this)) + .fail(function() { this.communicationError = "The request could not be executed successfully"; - }).bind(this) - - ); + }.bind(this)); } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/UsersView.js b/remoteappmanager/static/js/admin/vue-components/UsersView.js index 0e77d2e42..b915f1d89 100644 --- a/remoteappmanager/static/js/admin/vue-components/UsersView.js +++ b/remoteappmanager/static/js/admin/vue-components/UsersView.js @@ -1,136 +1,130 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources", - "admin/vue-components/users/NewUserDialog" -], function(Vue, resources, NewUserDialog) { - "use strict"; +var resources = require("admin-resources"); +var NewUserDialog = require("./users/NewUserDialog"); - return { +module.exports = { components: { - 'new-user-dialog': NewUserDialog, + 'new-user-dialog': NewUserDialog, }, - template: - '' + - '
' + - ' Error: {{communicationError}}' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - '
Do you want to remove User {{removeUserDialog.userToRemove.name}} ' + - ' ({{removeUserDialog.userToRemove.id}})
' + - '
' + - '
', + + template: + '' + + '
' + + ' Error: {{communicationError}}' + + '
' + + ' ' + + ' ' + + ' ' + + ' ' + + '
Do you want to remove User {{removeUserDialog.userToRemove.name}} ' + + ' ({{removeUserDialog.userToRemove.id}})
' + + '
' + + '
', + data: function () { - var self = this; - return { - table: { - headers: ["ID", "Username"], - rows: [], - globalActions: [ - { - label: "Create New Entry", - callback: function() { self.newUserDialog.show = true; } - } - ], - rowActions: [ - { - label: "Policies", - callback: this.showPolicyAction, - type: "info" + var self = this; + return { + table: { + headers: ["ID", "Username"], + rows: [], + globalActions: [{ + label: "Create New Entry", + callback: function() { self.newUserDialog.show = true; } + }], + rowActions: [{ + label: "Policies", + callback: this.showPolicyAction, + type: "info" + }, { + label: "Remove", + callback: this.removeAction + }] }, - { - label: "Remove", - callback: this.removeAction - } - ] - }, - users: [], - newUserDialog: { - show: false - }, - removeUserDialog: { - show: false, - userToRemove: { - id: null, - name: "" - } - }, - communicationError: null - }; + users: [], + newUserDialog: { + show: false + }, + removeUserDialog: { + show: false, + userToRemove: { + id: null, + name: "" + } + }, + communicationError: null + }; }, + mounted: function () { - this.updateTable(); + this.updateTable(); }, + methods: { - updateTable: function() { - var self = this; - this.communicationError = null; - resources.User.items() - .done( - function (identifiers, items) { - self.table.rows = []; - identifiers.forEach(function(id) { - var item = items[id]; - self.table.rows.push([ - id, - item.name - ]); + updateTable: function() { + var self = this; + this.communicationError = null; + resources.User.items() + .done(function (identifiers, items){ + self.table.rows = []; + identifiers.forEach(function(id) { + var item = items[id]; + self.table.rows.push([ + id, + item.name + ]); + }); + }) + .fail(function () { + self.communicationError = "The request could not be executed successfully"; }); - }) - .fail( - function () { - self.communicationError = "The request could not be executed successfully"; - }); - }, - newUserCreated: function() { - this.newUserDialog.show = false; - this.updateTable(); - }, - showPolicyAction: function(row) { - this.$router.push({ - name: 'user_accounting', - params: { id: row[0] } - }); - }, - removeAction: function(row) { - this.removeUserDialog.userToRemove.id = row[0]; - this.removeUserDialog.userToRemove.name = row[1]; - this.removeUserDialog.show = true; - }, - closeRemoveUserDialog: function() { - this.removeUserDialog.show = false; - this.removeUserDialog.userToRemove = { - id: null, - name: "" - }; - }, - removeUser: function () { - var self = this; - resources.User.delete(this.removeUserDialog.userToRemove.id) - .done(function () { - self.closeRemoveUserDialog(); - self.updateTable(); - } - ) - .fail( - function () { - self.closeRemoveUserDialog(); - } - ); - } + }, + + newUserCreated: function() { + this.newUserDialog.show = false; + this.updateTable(); + }, + + showPolicyAction: function(row) { + this.$router.push({ + name: 'user_accounting', + params: { id: row[0] } + }); + }, + + removeAction: function(row) { + this.removeUserDialog.userToRemove.id = row[0]; + this.removeUserDialog.userToRemove.name = row[1]; + this.removeUserDialog.show = true; + }, + + closeRemoveUserDialog: function() { + this.removeUserDialog.show = false; + this.removeUserDialog.userToRemove = { + id: null, + name: "" + }; + }, + + removeUser: function () { + var self = this; + resources.User.delete(this.removeUserDialog.userToRemove.id) + .done(function() { + self.closeRemoveUserDialog(); + self.updateTable(); + }) + .fail(function() { + self.closeRemoveUserDialog(); + }); + } } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/accounting/NewAccountingDialog.js b/remoteappmanager/static/js/admin/vue-components/accounting/NewAccountingDialog.js index 5f5bd5633..2da67a9df 100644 --- a/remoteappmanager/static/js/admin/vue-components/accounting/NewAccountingDialog.js +++ b/remoteappmanager/static/js/admin/vue-components/accounting/NewAccountingDialog.js @@ -1,152 +1,150 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources" -], function(Vue, resources) { - "use strict"; +var resources = require("admin-resources"); - return { +module.exports = { template: - ' ' + - ' ' + - ' ' + - '', + ' ' + + ' ' + + ' ' + + '', + props: ['show', "userId"], + data: function () { - return { - formstate: {}, - crossValidationError: false, - communicationError: null, - model: { - image_name: '', - allow_home: false, - volume_source: '', - volume_target: '', - volume_readonly: false, - volume_source_target: [] - } - }; + return { + formstate: {}, + crossValidationError: false, + communicationError: null, + model: { + image_name: '', + allow_home: false, + volume_source: '', + volume_target: '', + volume_readonly: false, + volume_source_target: [] + } + }; }, + methods: { - close: function () { - this.$emit('closed'); - }, - fieldClassName: function (field) { - if (!field) { - return ''; - } - if ((field.$dirty || field.$submitted) && field.$invalid) { - return 'has-error'; - } else { - return ''; - } - }, - createNewAccounting: function () { - var model = this.model; - var formstate = this.formstate; + close: function () { + this.$emit('closed'); + }, - if (formstate.$invalid) { - return; - } - if ((model.volume_source.length === 0 && model.volume_target.length !== 0) || - (model.volume_source.length !== 0 && model.volume_target.length === 0)) { - this.crossValidationError = true; - return; - } + fieldClassName: function (field) { + if (!field) { + return ''; + } + if ((field.$dirty || field.$submitted) && field.$invalid) { + return 'has-error'; + } else { + return ''; + } + }, - var rep = { - user_id: this.userId, - image_name: this.model.image_name, - allow_home: this.model.allow_home, - volume_source: this.model.volume_source, - volume_target: this.model.volume_target, - volume_mode: (model.volume_readonly ? "ro" : "rw") - }; + createNewAccounting: function () { + var model = this.model; + var formstate = this.formstate; + + if (formstate.$invalid) { + return; + } + if ((model.volume_source.length === 0 && model.volume_target.length !== 0) || + (model.volume_source.length !== 0 && model.volume_target.length === 0)) { + this.crossValidationError = true; + return; + } + + var rep = { + user_id: this.userId, + image_name: this.model.image_name, + allow_home: this.model.allow_home, + volume_source: this.model.volume_source, + volume_target: this.model.volume_target, + volume_mode: (model.volume_readonly ? "ro" : "rw") + }; - resources.Accounting.create(rep) - .done(( - function () { - this.$emit('created'); - }).bind(this) - ) - .fail( - (function () { - this.communicationError = "The request could not be executed successfully"; - }).bind(this) - ); + resources.Accounting.create(rep) + .done(function () { + this.$emit('created'); + }.bind(this)) + .fail(function () { + this.communicationError = "The request could not be executed successfully"; + }.bind(this)); + }, - }, - reset: function () { - Object.assign(this.$data, this.$options.data()); - }, - validatePath: function(value) { - return (value.length === 0 || value[0] === '/'); - } + reset: function () { + Object.assign(this.$data, this.$options.data()); + }, + + validatePath: function(value) { + return (value.length === 0 || value[0] === '/'); + } }, + watch: { - "show": function (value) { - if (value) { - this.reset(); + "show": function (value) { + if (value) { + this.reset(); + } + }, + "model.volume_source": function() { + delete this.formstate.volume_source.$error.volumesInconsistent; + delete this.formstate.volume_target.$error.volumesInconsistent; + }, + "model.volume_target": function() { + delete this.formstate.volume_source.$error.volumesInconsistent; + delete this.formstate.volume_target.$error.volumesInconsistent; } - }, - "model.volume_source": function() { - delete this.formstate.volume_source.$error.volumesInconsistent; - delete this.formstate.volume_target.$error.volumesInconsistent; - }, - "model.volume_target": function() { - delete this.formstate.volume_source.$error.volumesInconsistent; - delete this.formstate.volume_target.$error.volumesInconsistent; - } } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/applications/NewApplicationDialog.js b/remoteappmanager/static/js/admin/vue-components/applications/NewApplicationDialog.js index 85986671d..85f45aa72 100644 --- a/remoteappmanager/static/js/admin/vue-components/applications/NewApplicationDialog.js +++ b/remoteappmanager/static/js/admin/vue-components/applications/NewApplicationDialog.js @@ -1,83 +1,80 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources" -], function(Vue, resources) { - "use strict"; +var resources = require("admin-resources"); - return { +module.exports = { template: - ' ' + - ' ' + - ' ' + - ' ', + ' ' + + ' ' + + ' ' + + ' ', + props: ['show'], data: function () { - return { - formstate: {}, - communicationError: null, - model: { - name: '' - } - }; + return { + formstate: {}, + communicationError: null, + model: { + name: '' + } + }; }, + methods: { - close: function () { - this.$emit('closed'); - }, - fieldClassName: function (field) { - if (!field) { - return ''; - } - if ((field.$touched || field.$submitted) && field.$invalid) { - return 'has-error'; - } else { - return ''; - } - }, - createNewApplication: function () { - if (!this.formstate.$valid) { - return; + close: function () { + this.$emit('closed'); + }, + + fieldClassName: function (field) { + if (!field) { + return ''; + } + if ((field.$touched || field.$submitted) && field.$invalid) { + return 'has-error'; + } else { + return ''; + } + }, + + createNewApplication: function () { + if (!this.formstate.$valid) { + return; + } + resources.Application.create({image_name: this.model.name}) + .done(function () { + this.$emit('created'); + }.bind(this)) + .fail(function () { + this.communicationError = "The request could not be executed successfully"; + }.bind(this)); + }, + + reset: function () { + Object.assign(this.$data, this.$options.data()); } - resources.Application.create({image_name: this.model.name}) - .done(( - function () { - this.$emit('created'); - }).bind(this) - ) - .fail( - (function () { - this.communicationError = "The request could not be executed successfully"; - }).bind(this) - ); - }, - reset: function () { - Object.assign(this.$data, this.$options.data()); - } }, + watch: { - "show": function (value) { - if (value) { - this.reset(); + "show": function (value) { + if (value) { + this.reset(); + } } - } } - }; -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/toolkit/AdminLTEBox.js b/remoteappmanager/static/js/admin/vue-components/toolkit/AdminLTEBox.js index 4d4b7b167..83aa72b84 100644 --- a/remoteappmanager/static/js/admin/vue-components/toolkit/AdminLTEBox.js +++ b/remoteappmanager/static/js/admin/vue-components/toolkit/AdminLTEBox.js @@ -1,23 +1,17 @@ -define([ -], function() { - "use strict"; - - return { +module.exports = { props: { - title: { - type: String, - default: "Box" - } + title: { + type: String, + default: "Box" + } }, template: - '
' + - '
' + - '
' + - '
{{title}}
' + - '
' + - '
' + - '
' + - '
' - }; - -}); + '
' + + '
' + + '
' + + '
{{title}}
' + + '
' + + '
' + + '
' + + '
' +}; diff --git a/remoteappmanager/static/js/admin/vue-components/toolkit/ConfirmDialog.js b/remoteappmanager/static/js/admin/vue-components/toolkit/ConfirmDialog.js index 066e48a47..c82188cf3 100644 --- a/remoteappmanager/static/js/admin/vue-components/toolkit/ConfirmDialog.js +++ b/remoteappmanager/static/js/admin/vue-components/toolkit/ConfirmDialog.js @@ -1,35 +1,30 @@ -define([ -], function() { - "use strict"; - - return { +module.exports = { props: { - title: { - type: String, - default: "Confirm" - }, - closeCallback: { - type: Function, - default: undefined - }, - okCallback: { - type: Function, - default: function() {} - } - }, + title: { + type: String, + default: "Confirm" + }, + closeCallback: { + type: Function, + default: undefined + }, + okCallback: { + type: Function, + default: function() {} + } + }, template: '' + - ' ' + - '' - }; -}); + ' ' + + '' +}; diff --git a/remoteappmanager/static/js/admin/vue-components/toolkit/DataTable.js b/remoteappmanager/static/js/admin/vue-components/toolkit/DataTable.js index e0cb0494e..3845ddac9 100644 --- a/remoteappmanager/static/js/admin/vue-components/toolkit/DataTable.js +++ b/remoteappmanager/static/js/admin/vue-components/toolkit/DataTable.js @@ -1,52 +1,46 @@ -define([ -], function() { - "use strict"; - - return { +module.exports = { props: [ - "headers", "rows", "globalActions", "rowActions" + "headers", "rows", "globalActions", "rowActions" ], methods: { - isBoolean: function(value) { - return typeof(value) === "boolean"; - }, - buttonClassFromType: function(value) { - var cls = {"btn": true}; - if (value === undefined) { - value = "danger"; + isBoolean: function(value) { + return typeof(value) === "boolean"; + }, + buttonClassFromType: function(value) { + var cls = {"btn": true}; + if (value === undefined) { + value = "danger"; + } + cls["btn-" + value] = true; + return cls; } - cls["btn-" + value] = true; - return cls; - } }, template: - '
' + - '
' + - ' ' + - '
' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - ' ' + - '
{{header}}Actions
' + - ' ' + - '
' + - '
' - }; - -}); + '
' + + '
' + + ' ' + + '
' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
{{header}}Actions
' + + ' ' + + '
' + + '
' +}; diff --git a/remoteappmanager/static/js/admin/vue-components/toolkit/ModalDialog.js b/remoteappmanager/static/js/admin/vue-components/toolkit/ModalDialog.js index d8df1d553..61a7059a6 100644 --- a/remoteappmanager/static/js/admin/vue-components/toolkit/ModalDialog.js +++ b/remoteappmanager/static/js/admin/vue-components/toolkit/ModalDialog.js @@ -1,17 +1,12 @@ -define([ -], function() { - "use strict"; - - return { +module.exports = { template: '' + - ' ' + - '' - }; -}); + ' ' + + '' +}; \ No newline at end of file diff --git a/remoteappmanager/static/js/admin/vue-components/toolkit/toolkit.js b/remoteappmanager/static/js/admin/vue-components/toolkit/toolkit.js index 32c04004c..b1018e319 100644 --- a/remoteappmanager/static/js/admin/vue-components/toolkit/toolkit.js +++ b/remoteappmanager/static/js/admin/vue-components/toolkit/toolkit.js @@ -1,28 +1,17 @@ -define([ - "components/vue/dist/vue", - "admin/vue-components/toolkit/AdminLTEBox", - "admin/vue-components/toolkit/ConfirmDialog", - "admin/vue-components/toolkit/ModalDialog", - "admin/vue-components/toolkit/DataTable" -], function( - Vue, - AdminLTEBox, - ConfirmDialog, - ModalDialog, - DataTable -) { - "use strict"; +var Vue = require("vuejs"); +var ConfirmDialog = require("./ConfirmDialog"); +var ModalDialog = require("./ModalDialog"); +var AdminLTEBox = require("./AdminLTEBox"); +var DataTable = require("./DataTable"); - Vue.component("confirm-dialog", ConfirmDialog); - Vue.component("modal-dialog", ModalDialog); - Vue.component("adminlte-box", AdminLTEBox); - Vue.component("data-table", DataTable); - - return { +Vue.component("confirm-dialog", ConfirmDialog); +Vue.component("modal-dialog", ModalDialog); +Vue.component("adminlte-box", AdminLTEBox); +Vue.component("data-table", DataTable); + +module.exports = { ConfirmDialog: ConfirmDialog, AdminLTEBox: AdminLTEBox, ModalDialog: ModalDialog, DataTable: DataTable - }; - -}); +}; diff --git a/remoteappmanager/static/js/admin/vue-components/users/NewUserDialog.js b/remoteappmanager/static/js/admin/vue-components/users/NewUserDialog.js index b7b217d4b..46d481d36 100644 --- a/remoteappmanager/static/js/admin/vue-components/users/NewUserDialog.js +++ b/remoteappmanager/static/js/admin/vue-components/users/NewUserDialog.js @@ -1,10 +1,6 @@ -define([ - "components/vue/dist/vue", - "jsapi/v1/resources" -], function(Vue, resources) { - "use strict"; +var resources = require("admin-resources"); - return { +module.exports = { template: '' + ' ' + @@ -24,56 +20,57 @@ define([ ' ' + '
' + '', + props: ['show'], data: function () { - return { - formstate: {}, - model: { - name: '' - } - }; + return { + formstate: {}, + model: { + name: '' + } + }; }, + methods: { - close: function () { - this.$emit('closed'); - }, - fieldClassName: function (field) { - if (!field) { - return ''; - } - if ((field.$touched || field.$submitted) && field.$invalid) { - return 'has-error'; - } else { - return ''; - } - }, - createNewUser: function () { - if (!this.formstate.$valid) { - return; + close: function () { + this.$emit('closed'); + }, + + fieldClassName: function (field) { + if (!field) { + return ''; + } + if ((field.$touched || field.$submitted) && field.$invalid) { + return 'has-error'; + } else { + return ''; + } + }, + + createNewUser: function () { + if (!this.formstate.$valid) { + return; + } + resources.User.create({name: this.model.name}) + .done((function () { + this.$emit('created'); + }).bind(this)) + .fail(function () { + this.$emit("closed"); + }.bind(this)); + }, + + reset: function () { + Object.assign(this.$data, this.$options.data()); } - resources.User.create({name: this.model.name}) - .done(( - function () { - this.$emit('created'); - }).bind(this) - ) - .fail( - (function () { - this.$emit("closed"); - }).bind(this) - ); - }, - reset: function () { - Object.assign(this.$data, this.$options.data()); - } }, + watch: { - "show": function (value) { - if (value) { - this.reset(); + "show": function (value) { + if (value) { + this.reset(); + } } - } } - }; -}); +}; diff --git a/remoteappmanager/static/js/gamodule.js b/remoteappmanager/static/js/gamodule.js index 09be666ed..cd71bd96e 100644 --- a/remoteappmanager/static/js/gamodule.js +++ b/remoteappmanager/static/js/gamodule.js @@ -1,35 +1,32 @@ // This module contains the setup for google analytics. // MUST not be renamed to analytics. Some blockers rely on name // matching to prevent loading. -define([], function () { - "use strict"; - function init() { - if (window.apidata.analytics !== undefined) { - window.ga('create', window.apidata.analytics.tracking_id, 'auto'); - } else { - window.ga = function() {}; - } - - return function() { - window.ga.apply(this, arguments); - }; +function init() { + if (window.apidata.analytics !== undefined) { + window.ga('create', window.apidata.analytics.tracking_id, 'auto'); + } else { + window.ga = function() {}; } - var GaObserver = function() { - this.ga = init(); + return function() { + window.ga.apply(this, arguments); }; +} - GaObserver.prototype.triggerApplicationStarting = function(name) { - this.ga("send", "event", { - eventCategory: "Application", - eventAction: "start", - eventLabel: name - }); - }; +var GaObserver = function() { + this.ga = init(); +}; - return { - init: init, // For testing purpose - GaObserver: GaObserver - }; -}); +GaObserver.prototype.triggerApplicationStarting = function(name) { + this.ga("send", "event", { + eventCategory: "Application", + eventAction: "start", + eventLabel: name + }); +}; + +module.exports = { + init: init, // For testing purpose + GaObserver: GaObserver +}; diff --git a/remoteappmanager/static/js/home-resources.js b/remoteappmanager/static/js/home-resources.js new file mode 100644 index 000000000..c5060fefd --- /dev/null +++ b/remoteappmanager/static/js/home-resources.js @@ -0,0 +1,313 @@ +var $ = require("jquery"); +var urlUtils = require("urlutils"); +var utils = require("utils"); + +var object_to_query_args = function (obj) { + var keys = Object.keys(obj); + if (keys.length === 0) { + return ""; + } + + var result = []; + for (var idx in keys) { + var key = keys[idx]; + var value = obj[key]; + var key_enc = encodeURIComponent(key); + if ($.isArray(value)) { + for (var v in value) { + result.push(key_enc+"="+encodeURIComponent(v)); + } + } else { + result.push(key_enc+"="+encodeURIComponent(value)); + } + } + + return result.join("&"); +}; + +var API = (function () { + // Object representing the interface to the Web API. + // @param base_url : the url at which to find the web API endpoint. + var self = {}; + self.base_urlpath = "/user/test/"; + self.default_options = { + contentType: "application/json", + cache: false, + dataType : null, + processData: false, + success: null, + error: null + }; + + self.request = function (req_type, endpoint, body, query_args) { + // Performs a request to the final endpoint + var options = {}; + utils.update(options, self.default_options); + utils.update(options, { + type: req_type, + data: body + }); + + var url = urlUtils.pathJoin( + self.base_urlpath, + "api", "v1", + urlUtils.encodeUriComponents(endpoint) + )+'/'; + + if (query_args) { + url = url + "?" + object_to_query_args(query_args); + } + return $.ajax(url, options); + }; + return self; +})(); + +var RestError = function(code, message) { + console.log("Creating error "+code+" message: "+message); + this.code = code; + this.message = message; +}; + +var fail_handler = function(promise, jqXHR) { + var status = jqXHR.status; + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + var err = new RestError(status, ""); + if (payload !== null) { + utils.update(err, payload); + } + promise.reject(err); +}; + +var create_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 201) { + // Strange situation in which the call succeeded, but + // not with a 201. Just do our best. + console.log( + "Create succeded but response with status " + + status + + " instead of 201." + ); + promise.reject(status, payload); + return; + } + + var id, location; + try { + location = jqXHR.getResponseHeader('Location'); + var url = urlUtils.parse(location); + var arr = url.pathname.replace(/\/$/, "").split('/'); + id = arr[arr.length - 1]; + } catch (e) { + console.log("Response had invalid or absent Location header"); + promise.reject(status, payload); + return; + } + promise.resolve(id, location); +}; + +var update_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 204) { + // Strange situation in which the call succeeded, but + // not with a 201. Just do our best. + console.log( + "Update succeded but response with status " + + status + + " instead of 204." + ); + promise.reject(status, payload); + return; + } + + promise.resolve(); +}; + +var delete_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + var payload = null; + try { + payload = JSON.parse(data); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 204) { + console.log( + "Delete succeded but response with status " + + status + + " instead of 204." + ); + promise.reject(status, payload); + return; + } + promise.resolve(); +}; + +var retrieve_handler = function(promise, data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 200) { + console.log( + "Retrieve succeded but response with status " + + status + + " instead of 200." + ); + promise.reject(status, payload); + return; + } + + if (payload === null) { + console.log( + "Retrieve succeded but empty or invalid payload" + ); + promise.reject(status, payload); + return; + } + + promise.resolve(payload); +}; + +var Resource = function(type) { + this.type = type; + + this.create = function(representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("POST", type, body, query_args) + .done(function(data, textStatus, jqXHR) { + create_handler(promise, data, textStatus, jqXHR); + }) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.update = function(id, representation, query_args) { + var body = JSON.stringify(representation); + var promise = $.Deferred(); + + API.request("PUT", urlUtils.pathJoin(type, id), body, query_args) + .done(function(data, textStatus, jqXHR) { + update_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.delete = function(id, query_args) { + var promise = $.Deferred(); + + API.request("DELETE", urlUtils.pathJoin(type, id), null, query_args) + .done(function(data, textStatus, jqXHR) { + delete_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.retrieve = function(id, query_args) { + var promise = $.Deferred(); + + API.request("GET", urlUtils.pathJoin(type, id), null, query_args) + .done(function(data, textStatus, jqXHR) { + retrieve_handler(promise, data, textStatus, jqXHR); + } + ) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; + + this.items = function(query_args) { + var promise = $.Deferred(); + + API.request("GET", type, null, query_args) + .done(function(data, textStatus, jqXHR) { + var status = jqXHR.status; + + var payload = null; + try { + payload = JSON.parse(jqXHR.responseText); + } catch (e) { + // Suppress any syntax error and discard the payload + } + + if (status !== 200) { + console.log( + "Items retrieve succeded but response with status " + + status + + " instead of 200." + ); + promise.reject(status, payload); + return; + } + + if (payload === null) { + console.log( + "Items Retrieve succeded but empty or invalid payload" + ); + promise.reject(status, payload); + return; + } + + promise.resolve( + payload.identifiers, + payload.items, + payload.offset, + payload.total); + }) + .fail(function(jqXHR) { + fail_handler(promise, jqXHR); + }); + + return promise; + }; +}; + +module.exports = { + "Container" : new Resource("containers"), + "Application" : new Resource("applications"), +}; diff --git a/remoteappmanager/static/js/home/configurables.js b/remoteappmanager/static/js/home/configurables.js index 4f4e8902e..1a1f20e83 100644 --- a/remoteappmanager/static/js/home/configurables.js +++ b/remoteappmanager/static/js/home/configurables.js @@ -1,61 +1,57 @@ -define([ - "utils", - "components/vue/dist/vue" -], function(utils, Vue) { - "use strict"; - - var resolutionTag = 'resolution'; - var resolutionComponent = Vue.component(resolutionTag + '-component', { - // Your configurable must have a "configDict" property from the model - props: ['configDict'], - - template: - '
' + - ' ' + - ' ' + - '
', - - data: function() { - return { - resolution: this.configDict.resolution, - resolutionOptions: ['Window', '1920x1080', '1280x1024', '1280x800', '1024x768'] - }; - }, - - watch: { - configDict: function() { this.resolution = this.configDict.resolution; }, // model -> view update - resolution: function() { this.$emit('update:configDict', { resolution: this.resolution }); } // view -> model update - } - }); - - // Your configurable class must implement a tag and default configDict - var ResolutionModel = function() { - this.tag = resolutionTag; - this.configDict = { resolution: 'Window' }; - }; - - ResolutionModel.prototype.asConfigDict = function() { - var resolution = this.configDict.resolution; - - if (resolution === 'Window') { - var maxSize = utils.maxIframeSize(); - resolution = maxSize[0] + 'x' + maxSize[1]; - } - - return { resolution: resolution }; - }; - - var outputConfigurables = {}; - - // Export all your configurable models here - outputConfigurables[resolutionTag] = { - model: ResolutionModel, - component: resolutionComponent - }; - - return outputConfigurables; +var Vue = require("vuejs"); +var utils = require("utils"); + +var resolutionTag = 'resolution'; +var resolutionComponent = Vue.component(resolutionTag + '-component', { + // Your configurable must have a "configDict" property from the model + props: ['configDict'], + + template: + '
' + + ' ' + + ' ' + + '
', + + data: function() { + return { + resolution: this.configDict.resolution, + resolutionOptions: ['Window', '1920x1080', '1280x1024', '1280x800', '1024x768'] + }; + }, + + watch: { + configDict: function() { this.resolution = this.configDict.resolution; }, // model -> view update + resolution: function() { this.$emit('update:configDict', { resolution: this.resolution }); } // view -> model update + } }); + +// Your configurable class must implement a tag and default configDict +var ResolutionModel = function() { + this.tag = resolutionTag; + this.configDict = { resolution: 'Window' }; +}; + +ResolutionModel.prototype.asConfigDict = function() { + var resolution = this.configDict.resolution; + + if (resolution === 'Window') { + var maxSize = utils.maxIframeSize(); + resolution = maxSize[0] + 'x' + maxSize[1]; + } + + return { resolution: resolution }; +}; + +var outputConfigurables = {}; + +// Export all your configurable models here +outputConfigurables[resolutionTag] = { + model: ResolutionModel, + component: resolutionComponent +}; + +module.exports = outputConfigurables; \ No newline at end of file diff --git a/remoteappmanager/static/js/home/controller.js b/remoteappmanager/static/js/home/controller.js index eef36c39b..2e06abbae 100644 --- a/remoteappmanager/static/js/home/controller.js +++ b/remoteappmanager/static/js/home/controller.js @@ -1,42 +1,31 @@ -/*globals: require, console*/ -require([ - "home/models", - "home/views/application_list_view", - "home/views/application_view", - "components/vue/dist/vue", - "gamodule", - "vue/filters" -], function( - models, - applicationListView, - applicationView, - Vue, - gamodule) { - "use strict"; +var gamodule = require("gamodule"); +var models = require("./models"); +var applicationListView = require("./views/application_list_view"); +var applicationView = require("./views/application_view"); +require("filters"); - // This model keeps the retrieved content from the REST query locally. - // It is only synchronized at initial load. - var model = new models.ApplicationListModel(); +// This model keeps the retrieved content from the REST query locally. +// It is only synchronized at initial load. +var model = new models.ApplicationListModel(); - // Initialize views - var appListView = new applicationListView.ApplicationListView({ - el: '#applist', - data: function() { return { model: model }; } - }); +// Initialize views +var appListView = new applicationListView.ApplicationListView({ + el: '#applist', + data: function() { return { model: model }; } +}); - var appView = new applicationView.ApplicationView({ - el: '#appview', - data: function() { return { model: model }; } - }); +var appView = new applicationView.ApplicationView({ + el: '#appview', + data: function() { return { model: model }; } +}); - // Create GA observer - var gaObserver = new gamodule.GaObserver(); +// Create GA observer +var gaObserver = new gamodule.GaObserver(); - appView.$on('startApplication', function(application) { - gaObserver.triggerApplicationStarting(application.appData.image.name); - }); +appView.$on('startApplication', function(application) { + gaObserver.triggerApplicationStarting(application.appData.image.name); +}); - appListView.$on('entryClicked', function() { appView.focusIframe(); }); +appListView.$on('entryClicked', function() { appView.focusIframe(); }); - model.update(); -}); +model.update(); diff --git a/remoteappmanager/static/js/home/models.js b/remoteappmanager/static/js/home/models.js index 03203c653..d9e3540e3 100644 --- a/remoteappmanager/static/js/home/models.js +++ b/remoteappmanager/static/js/home/models.js @@ -1,207 +1,203 @@ -define([ - "jquery", - "home/configurables", - "jsapi/v1/resources" -], function ($, configurables, resources) { - "use strict"; - - var Status = { - RUNNING: "RUNNING", - STARTING: "STARTING", - STOPPING: "STOPPING", - STOPPED: "STOPPED" - }; - - var availableApplicationsInfo = function () { - // Retrieve information from the various applications and - // connect the cascading callbacks. - // Returns a single promise. When resolved, the attached - // callbacks will be passed an array of the promises for the various - // retrieve operations, successful or not. - // This routine will go away when we provide the representation data - // inline with the items at tornado-webapi level. - - var promise = $.Deferred(); - - resources.Application.items() - .done(function (identifiers, items) { - var result = []; - Object.keys(items).forEach(function(key) { - result.push(items[key]); - }); - promise.resolve(result); - - }) - .fail(function() { - promise.resolve([]); +var $ = require("jquery"); +var resources = require("home-resources"); +var configurables = require("./configurables"); + +var Status = { + RUNNING: "RUNNING", + STARTING: "STARTING", + STOPPING: "STOPPING", + STOPPED: "STOPPED" +}; + +var availableApplicationsInfo = function () { + // Retrieve information from the various applications and + // connect the cascading callbacks. + // Returns a single promise. When resolved, the attached + // callbacks will be passed an array of the promises for the various + // retrieve operations, successful or not. + // This routine will go away when we provide the representation data + // inline with the items at tornado-webapi level. + + var promise = $.Deferred(); + + resources.Application.items() + .done(function (identifiers, items) { + var result = []; + Object.keys(items).forEach(function(key) { + result.push(items[key]); }); + promise.resolve(result); - return promise; - }; - - var ApplicationListModel = function() { - // (constructor) Model for the application list. - this.appList = []; - - // Selection index for when we click on one entry. - // Should be the index of the selected application, - // or null if no application available (first app is clicked by default). - this.selectedIndex = null; - - this.loading = true; - }; - - ApplicationListModel.prototype.update = function() { - // Requests an update of the object internal data. - // This method returns a jQuery Promise object. - // When resolved, this.data will contain a list of the retrieved - // data. Note that, in error conditions, this routine resolves - // successfully in any case, and the data is set to empty list - return $.when( - availableApplicationsInfo() - ).done(function (appData) { - // appData contains the data retrieved from the remote API - - var appList = []; - - // Sort application list by names - appData.sort(function(app1, app2) { - var app1Name = app1.image.ui_name? app1.image.ui_name: app1.image.name; - var app2Name = app2.image.ui_name? app2.image.ui_name: app2.image.name; - return app1Name < app2Name? -1: 1; - }); + }) + .fail(function() { + promise.resolve([]); + }); + + return promise; +}; + +var ApplicationListModel = function() { + // (constructor) Model for the application list. + this.appList = []; + + // Selection index for when we click on one entry. + // Should be the index of the selected application, + // or null if no application available (first app is clicked by default). + this.selectedIndex = null; + + this.loading = true; +}; + +ApplicationListModel.prototype.update = function() { + // Requests an update of the object internal data. + // This method returns a jQuery Promise object. + // When resolved, this.data will contain a list of the retrieved + // data. Note that, in error conditions, this routine resolves + // successfully in any case, and the data is set to empty list + return $.when( + availableApplicationsInfo() + ).done(function (appData) { + // appData contains the data retrieved from the remote API + + var appList = []; + + // Sort application list by names + appData.sort(function(app1, app2) { + var app1Name = app1.image.ui_name? app1.image.ui_name: app1.image.name; + var app2Name = app2.image.ui_name? app2.image.ui_name: app2.image.name; + return app1Name < app2Name? -1: 1; + }); - // Add the options for some image types - appData.forEach(function(applicationData) { - var app = { - appData: applicationData, - // Default values, will be overwritten - status: Status.STOPPED, - // If true the user will see the loading spinner (when starting the application) - delayed: true, - configurables: [], - isRunning: function() {return this.status === Status.RUNNING;}, - isStopped: function() {return this.status === Status.STOPPED;}, - isStarting: function() {return this.status === Status.STARTING;}, - isStopping: function() {return this.status === Status.STOPPING;} - }; - - this._updateConfigurables(app); - this._updateStatus(app); - - app.delayed = !app.isRunning(); - appList.push(app); - }.bind(this)); - - this.appList = appList; - - if(appList.length) {this.selectedIndex = 0;} - - this.loading = false; + // Add the options for some image types + appData.forEach(function(applicationData) { + var app = { + appData: applicationData, + // Default values, will be overwritten + status: Status.STOPPED, + // If true the user will see the loading spinner (when starting the application) + delayed: true, + configurables: [], + isRunning: function() {return this.status === Status.RUNNING;}, + isStopped: function() {return this.status === Status.STOPPED;}, + isStarting: function() {return this.status === Status.STARTING;}, + isStopping: function() {return this.status === Status.STOPPING;} + }; + + this._updateConfigurables(app); + this._updateStatus(app); + + app.delayed = !app.isRunning(); + appList.push(app); }.bind(this)); - }; - ApplicationListModel.prototype.updateIdx = function(index) { - // Refetches and updates the entry at the given index. - var app = this.appList[index]; - var mapping_id = app.appData.mapping_id; + this.appList = appList; - return resources.Application.retrieve(mapping_id) - .done(function(newData) { - app.appData = newData; + if(appList.length) {this.selectedIndex = 0;} - this._updateStatus(app); - }.bind(this)); - }; - - ApplicationListModel.prototype._updateConfigurables = function(app) { - // Contains the submodels for the configurables. - // It is a dictionary that maps a supported (by the image) configurable tag - // to its client-side model. - app.configurables = []; - - app.appData.image.configurables.forEach(function(tag) { - // If this returns null, the tag has not been recognized - // by the client. skip it and let the server deal with the - // missing data, either by using a default or throwing - // an error. - var ConfigurableCls = configurables[tag].model; - - if (ConfigurableCls !== undefined) { - app.configurables.push(new ConfigurableCls()); - } - }); - }; + this.loading = false; + }.bind(this)); +}; - ApplicationListModel.prototype._updateStatus = function(app) { - if (app.appData.container === undefined) { - app.status = Status.STOPPED; - } else { - app.status = Status.RUNNING; - } - }; +ApplicationListModel.prototype.updateIdx = function(index) { + // Refetches and updates the entry at the given index. + var app = this.appList[index]; + var mapping_id = app.appData.mapping_id; - ApplicationListModel.prototype.startApplication = function() { - var selectedIndex = this.selectedIndex; - var currentApp = this.appList[selectedIndex]; + return resources.Application.retrieve(mapping_id) + .done(function(newData) { + app.appData = newData; - currentApp.status = Status.STARTING; - currentApp.delayed = true; + this._updateStatus(app); + }.bind(this)); +}; - var configurablesData = {}; - currentApp.configurables.forEach(function(configurable) { - var tag = configurable.tag; - configurablesData[tag] = configurable.asConfigDict(); - }); +ApplicationListModel.prototype._updateConfigurables = function(app) { + // Contains the submodels for the configurables. + // It is a dictionary that maps a supported (by the image) configurable tag + // to its client-side model. + app.configurables = []; - var startPromise = $.Deferred(); + app.appData.image.configurables.forEach(function(tag) { + // If this returns null, the tag has not been recognized + // by the client. skip it and let the server deal with the + // missing data, either by using a default or throwing + // an error. + var ConfigurableCls = configurables[tag].model; - resources.Container.create({ - mapping_id: currentApp.appData.mapping_id, - configurables: configurablesData - }) - .done(function() { - this.updateIdx(selectedIndex) - .done(startPromise.resolve) - .fail(function(error) { - currentApp.status = Status.STOPPED; - startPromise.reject(error); - }); - }.bind(this)) + if (ConfigurableCls !== undefined) { + app.configurables.push(new ConfigurableCls()); + } + }); +}; + +ApplicationListModel.prototype._updateStatus = function(app) { + if (app.appData.container === undefined) { + app.status = Status.STOPPED; + } else { + app.status = Status.RUNNING; + } +}; + +ApplicationListModel.prototype.startApplication = function() { + var selectedIndex = this.selectedIndex; + var currentApp = this.appList[selectedIndex]; + + currentApp.status = Status.STARTING; + currentApp.delayed = true; + + var configurablesData = {}; + currentApp.configurables.forEach(function(configurable) { + var tag = configurable.tag; + configurablesData[tag] = configurable.asConfigDict(); + }); + + var startPromise = $.Deferred(); + + resources.Container.create({ + mapping_id: currentApp.appData.mapping_id, + configurables: configurablesData + }) + .done(function() { + this.updateIdx(selectedIndex) + .done(startPromise.resolve) .fail(function(error) { currentApp.status = Status.STOPPED; startPromise.reject(error); }); + }.bind(this)) + .fail(function(error) { + currentApp.status = Status.STOPPED; + startPromise.reject(error); + }); - return startPromise; - }; + return startPromise; +}; - ApplicationListModel.prototype.stopApplication = function(index) { - var appStopping = this.appList[index]; - appStopping.status = Status.STOPPING; +ApplicationListModel.prototype.stopApplication = function(index) { + var appStopping = this.appList[index]; + appStopping.status = Status.STOPPING; - var url_id = appStopping.appData.container.url_id; + var url_id = appStopping.appData.container.url_id; - var stopPromise = $.Deferred(); + var stopPromise = $.Deferred(); - resources.Container.delete(url_id) - .done(function() { - this.updateIdx(index) - .done(stopPromise.resolve) - .fail(function(error) { - appStopping.status = Status.STOPPED; - stopPromise.reject(error); - }); - }.bind(this)) + resources.Container.delete(url_id) + .done(function() { + this.updateIdx(index) + .done(stopPromise.resolve) .fail(function(error) { appStopping.status = Status.STOPPED; stopPromise.reject(error); }); - - return stopPromise; - }; - - return { - ApplicationListModel: ApplicationListModel - }; -}); + }.bind(this)) + .fail(function(error) { + appStopping.status = Status.STOPPED; + stopPromise.reject(error); + }); + + return stopPromise; +}; + +module.exports = { + ApplicationListModel: ApplicationListModel +}; diff --git a/remoteappmanager/static/js/home/views/application_list_view.js b/remoteappmanager/static/js/home/views/application_list_view.js index c76611aa8..4a0ee8a46 100644 --- a/remoteappmanager/static/js/home/views/application_list_view.js +++ b/remoteappmanager/static/js/home/views/application_list_view.js @@ -1,109 +1,105 @@ -define([ - "../../components/vue/dist/vue", - "admin/vue-components/toolkit/toolkit" -], function (Vue) { - 'use strict'; +var Vue = require("vuejs"); +require("toolkit"); - var ApplicationListView = Vue.extend({ - template: - '' + + '', - data: function() { - return { - 'searchInput': '', - stoppingError: { show: false, appName: '', code: '', message: '' } - }; - }, - - computed: { - visibleList: function() { - return this.model.appList.filter(function(app) { - var appName = this.$options.filters.appName(app.appData.image).toLowerCase(); - return appName.indexOf(this.searchInput.toLowerCase()) !== -1; - }.bind(this)); - } - }, + data: function() { + return { + 'searchInput': '', + stoppingError: { show: false, appName: '', code: '', message: '' } + }; + }, - methods: { - stopApplication: function(index) { - var stoppingAppName = this.$options.filters.appName( - this.model.appList[index].appData.image); - this.model.stopApplication(index).fail(function(error) { - this.stoppingError.code = error.code; - this.stoppingError.message = error.message; - this.stoppingError.appName = stoppingAppName; - this.stoppingError.show = true; - }.bind(this)); - }, - closeDialog: function() { - this.stoppingError.show = false; - }, - indexOf: function(app) { - return this.model.appList.indexOf(app); - } + computed: { + visibleList: function() { + return this.model.appList.filter(function(app) { + var appName = this.$options.filters.appName(app.appData.image).toLowerCase(); + return appName.indexOf(this.searchInput.toLowerCase()) !== -1; + }.bind(this)); } - }); + }, - return { - ApplicationListView : ApplicationListView - }; + methods: { + stopApplication: function(index) { + var stoppingAppName = this.$options.filters.appName( + this.model.appList[index].appData.image); + this.model.stopApplication(index).fail(function(error) { + this.stoppingError.code = error.code; + this.stoppingError.message = error.message; + this.stoppingError.appName = stoppingAppName; + this.stoppingError.show = true; + }.bind(this)); + }, + closeDialog: function() { + this.stoppingError.show = false; + }, + indexOf: function(app) { + return this.model.appList.indexOf(app); + } + } }); + +module.exports = { + ApplicationListView : ApplicationListView +}; diff --git a/remoteappmanager/static/js/home/views/application_view.js b/remoteappmanager/static/js/home/views/application_view.js index 8932c06b6..5b3ce899c 100644 --- a/remoteappmanager/static/js/home/views/application_view.js +++ b/remoteappmanager/static/js/home/views/application_view.js @@ -1,153 +1,149 @@ -define([ - "urlutils", - "utils", - "../../components/vue/dist/vue", - "admin/vue-components/toolkit/toolkit" -], function (urlUtils, utils, Vue) { - "use strict"; - - var ApplicationView = Vue.extend({ - template: - '' + - '
' + - ' ' + - ' ' + - '
' + - ' Code: {{startingError.code}}' + - ' {{startingError.message}}' + - '
' + - '
' + - - ' ' + - '
' + - '
' + - '
' + - '
' + - '

{{ currentApp.appData.image | appName }}

' + - '
' + - '
' + - '
' + - '

Description

' + - ' {{ currentApp.appData.image.description }}' + - - '

Policy

' + - - '
    ' + - ' ' + - '
  • ' + - ' Workspace accessible' + - '
  • ' + - '
  • ' + - ' Workspace not accessible' + - '
  • ' + - - ' ' + - '
  • ' + - ' Volume mounted: {{ appPolicy.volume_source }} → {{ appPolicy.volume_target }} ({{ appPolicy.volume_mode }})' + - '
  • ' + - '
  • ' + - ' No volumes mounted' + - '
  • ' + - '
' + - - '

Configuration

' + - '
' + - '
No configurable options for this image
' + - '
' + - ' ' + - '
' + - '
' + - '
' + - - ' ' + - ' ' + - '
' + - '
' + - '
' + - - - ' ' + - ' ' + - '
', - - data: function() { - return { - startingError: { show: false, appName: '', code: '', message: '' } - }; +var Vue = require("vuejs"); +var urlUtils = require("urlutils"); +var utils = require("utils"); +require("toolkit"); + +var ApplicationView = Vue.extend({ + template: + '' + + '
' + + ' ' + + ' ' + + '
' + + ' Code: {{startingError.code}}' + + ' {{startingError.message}}' + + '
' + + '
' + + + ' ' + + '
' + + '
' + + '
' + + '
' + + '

{{ currentApp.appData.image | appName }}

' + + '
' + + '
' + + '
' + + '

Description

' + + ' {{ currentApp.appData.image.description }}' + + + '

Policy

' + + + '
    ' + + ' ' + + '
  • ' + + ' Workspace accessible' + + '
  • ' + + '
  • ' + + ' Workspace not accessible' + + '
  • ' + + + ' ' + + '
  • ' + + ' Volume mounted: {{ appPolicy.volume_source }} → {{ appPolicy.volume_target }} ({{ appPolicy.volume_mode }})' + + '
  • ' + + '
  • ' + + ' No volumes mounted' + + '
  • ' + + '
' + + + '

Configuration

' + + '
' + + '
No configurable options for this image
' + + '
' + + ' ' + + '
' + + '
' + + '
' + + + ' ' + + ' ' + + '
' + + '
' + + '
' + + + + ' ' + + ' ' + + '
', + + data: function() { + return { + startingError: { show: false, appName: '', code: '', message: '' } + }; + }, + + computed: { + currentApp: function() { + return this.model.appList[this.model.selectedIndex] || null; }, - - computed: { - currentApp: function() { - return this.model.appList[this.model.selectedIndex] || null; - }, - appPolicy: function() { - return this.currentApp.appData.image.policy; - }, - appSource: function() { - var url = urlUtils.pathJoin( - window.apidata.base_url, - 'containers', - this.currentApp.appData.container.url_id - ); - var output = this.currentApp.delayed ? url : url + '/'; - - this.currentApp.delayed = false; - - return output; - } + appPolicy: function() { + return this.currentApp.appData.image.policy; }, - - methods: { - startApplication: function() { - var startingApp = this.currentApp; - var startingAppName = this.$options.filters.appName(startingApp.appData.image); - this.model.startApplication() - .done(function() { - this.$emit('startApplication', startingApp); - }.bind(this)) - .fail(function(error) { - this.startingError.code = error.code; - this.startingError.message = error.message; - this.startingError.appName = startingAppName; - this.startingError.show = true; - }.bind(this)); - }, - closeDialog: function() { - this.startingError.show = false; - }, - getIframeSize: function() { - return utils.maxIframeSize(); - }, - focusIframe: function() { - var iframe = this.$el.querySelector('iframe'); - if(iframe !== null) { - iframe.focus(); - } - } + appSource: function() { + var url = urlUtils.pathJoin( + window.apidata.base_url, + 'containers', + this.currentApp.appData.container.url_id + ); + var output = this.currentApp.delayed ? url : url + '/'; + + this.currentApp.delayed = false; + + return output; + } + }, + + methods: { + startApplication: function() { + var startingApp = this.currentApp; + var startingAppName = this.$options.filters.appName(startingApp.appData.image); + this.model.startApplication() + .done(function() { + this.$emit('startApplication', startingApp); + }.bind(this)) + .fail(function(error) { + this.startingError.code = error.code; + this.startingError.message = error.message; + this.startingError.appName = startingAppName; + this.startingError.show = true; + }.bind(this)); }, + closeDialog: function() { + this.startingError.show = false; + }, + getIframeSize: function() { + return utils.maxIframeSize(); + }, + focusIframe: function() { + var iframe = this.$el.querySelector('iframe'); + if(iframe !== null) { + iframe.focus(); + } + } + }, - updated: function() { this.focusIframe(); } - }); - - return { - ApplicationView : ApplicationView - }; + updated: function() { this.focusIframe(); } }); + +module.exports = { + ApplicationView : ApplicationView +}; diff --git a/remoteappmanager/static/js/urlutils.js b/remoteappmanager/static/js/urlutils.js index 1295483f4..700c7400b 100644 --- a/remoteappmanager/static/js/urlutils.js +++ b/remoteappmanager/static/js/urlutils.js @@ -5,51 +5,46 @@ // Modifications Copyright (c) Juptyer Development Team. // Distributed under the terms of the Modified BSD License. -define(function () { - "use strict"; +var pathJoin = function () { + // join a sequence of url components with '/' + var url = '', i = 0; - var pathJoin = function () { - // join a sequence of url components with '/' - var url = '', i = 0; - - for (i = 0; i < arguments.length; i += 1) { - if (arguments[i] === '') { - continue; - } - if (url.length > 0 && url[url.length-1] !== '/') { - url = url + '/' + arguments[i]; - } else { - url = url + arguments[i]; - } + for (i = 0; i < arguments.length; i += 1) { + if (arguments[i] === '') { + continue; } - url = url.replace(/\/\/+/, '/'); - return url; - }; - - var parse = function (url) { - // an `a` element with an href allows attr-access to the parsed segments of a URL - // a = parse_url("http://localhost:8888/path/name#hash") - // a.protocol = "http:" - // a.host = "localhost:8888" - // a.hostname = "localhost" - // a.port = 8888 - // a.pathname = "/path/name" - // a.hash = "#hash" - var a = document.createElement("a"); - a.href = url; - return a; - }; - - var encodeUriComponents = function (uri) { - // encode just the components of a multi-segment uri, - // leaving '/' separators - return uri.split('/').map(encodeURIComponent).join('/'); - }; - - return { - pathJoin: pathJoin, - encodeUriComponents: encodeUriComponents, - parse: parse - }; - -}); + if (url.length > 0 && url[url.length-1] !== '/') { + url = url + '/' + arguments[i]; + } else { + url = url + arguments[i]; + } + } + url = url.replace(/\/\/+/, '/'); + return url; +}; + +var parse = function (url) { + // an `a` element with an href allows attr-access to the parsed segments of a URL + // a = parse_url("http://localhost:8888/path/name#hash") + // a.protocol = "http:" + // a.host = "localhost:8888" + // a.hostname = "localhost" + // a.port = 8888 + // a.pathname = "/path/name" + // a.hash = "#hash" + var a = document.createElement("a"); + a.href = url; + return a; +}; + +var encodeUriComponents = function (uri) { + // encode just the components of a multi-segment uri, + // leaving '/' separators + return uri.split('/').map(encodeURIComponent).join('/'); +}; + +module.exports = { + pathJoin: pathJoin, + encodeUriComponents: encodeUriComponents, + parse: parse +}; diff --git a/remoteappmanager/static/js/utils.js b/remoteappmanager/static/js/utils.js index 697337fbe..ebdb3077b 100644 --- a/remoteappmanager/static/js/utils.js +++ b/remoteappmanager/static/js/utils.js @@ -5,48 +5,43 @@ // Modifications Copyright (c) Juptyer Development Team. // Distributed under the terms of the Modified BSD License. -define([ - 'jquery' -], function ($) { - "use strict"; +var $ = require("jquery"); - var all = function (promises) { - // A form of jQuery.when that handles an array of promises - // and equalises the behavior regardless if there's one or more than - // one elements. - if (!Array.isArray(promises)) { - throw new Error("$.all() must be passed an array of promises"); +var all = function (promises) { + // A form of jQuery.when that handles an array of promises + // and equalises the behavior regardless if there's one or more than + // one elements. + if (!Array.isArray(promises)) { + throw new Error("$.all() must be passed an array of promises"); + } + return $.when.apply($, promises).then(function () { + // if single argument was expanded into multiple arguments, + // then put it back into an array for consistency + if (promises.length === 1 && arguments.length > 1) { + // put arguments into an array + return [Array.prototype.slice.call(arguments, 0)]; + } else { + return Array.prototype.slice.call(arguments, 0); } - return $.when.apply($, promises).then(function () { - // if single argument was expanded into multiple arguments, - // then put it back into an array for consistency - if (promises.length === 1 && arguments.length > 1) { - // put arguments into an array - return [Array.prototype.slice.call(arguments, 0)]; - } else { - return Array.prototype.slice.call(arguments, 0); - } - }); - }; + }); +}; - var update = function (d1, d2) { - // Transfers the keys from d2 to d1. Returns d1 - $.map(d2, function (i, key) { - d1[key] = d2[key]; - }); - return d1; - }; +var update = function (d1, d2) { + // Transfers the keys from d2 to d1. Returns d1 + $.map(d2, function (i, key) { + d1[key] = d2[key]; + }); + return d1; +}; - var maxIframeSize = function () { - // Returns the current iframe viewport size - var box = document.querySelector(".content-wrapper").getBoundingClientRect(); - return [box.width, box.height]; - }; +var maxIframeSize = function () { + // Returns the current iframe viewport size + var box = document.querySelector(".content-wrapper").getBoundingClientRect(); + return [box.width, box.height]; +}; - return { - all : all, - update : update, - maxIframeSize: maxIframeSize - }; - -}); +module.exports = { + all : all, + update : update, + maxIframeSize: maxIframeSize +}; diff --git a/remoteappmanager/static/js/vue/filters.js b/remoteappmanager/static/js/vue/filters.js index 682127069..0f9acc584 100644 --- a/remoteappmanager/static/js/vue/filters.js +++ b/remoteappmanager/static/js/vue/filters.js @@ -1,22 +1,16 @@ -define([ - "components/vue/dist/vue", - "urlutils" -], function (Vue, urlUtils) { - 'use strict'; +var Vue = require("vuejs"); +var urlUtils = require("urlutils"); - Vue.filter('iconSrc', function(icon_data) { - return ( - icon_data ? - 'data:image/png;base64,' + icon_data : - urlUtils.pathJoin( - window.apidata.base_url, 'static', 'images', 'generic_appicon_128.png' - ) - ); - }); - - Vue.filter('appName', function(image) { - return image.ui_name? image.ui_name: image.name; - }); +Vue.filter('iconSrc', function(icon_data) { + return ( + icon_data ? + 'data:image/png;base64,' + icon_data : + urlUtils.pathJoin( + window.apidata.base_url, 'static', 'images', 'generic_appicon_128.png' + ) + ); +}); - return null; +Vue.filter('appName', function(image) { + return image.ui_name? image.ui_name: image.name; }); diff --git a/remoteappmanager/templates/admin/page.html b/remoteappmanager/templates/admin/page.html index 58a840ab2..d54b94795 100644 --- a/remoteappmanager/templates/admin/page.html +++ b/remoteappmanager/templates/admin/page.html @@ -13,24 +13,24 @@ - + - + - + - - + + - + {% endif %} - - - @@ -176,10 +151,11 @@ - - - + + + + {% block dialogs %} {% call macros.modal_dlg('Error', btn_label='OK') %}
diff --git a/remoteappmanager/templates/home.html b/remoteappmanager/templates/home.html index 0f165419e..0f1432f0f 100644 --- a/remoteappmanager/templates/home.html +++ b/remoteappmanager/templates/home.html @@ -10,7 +10,5 @@ {% block script_init %} {{ super() }} - + {% endblock %} diff --git a/remoteappmanager/templates/page.html b/remoteappmanager/templates/page.html index 3741e3486..1e2495c65 100644 --- a/remoteappmanager/templates/page.html +++ b/remoteappmanager/templates/page.html @@ -15,24 +15,24 @@ - + - + - + - + - + {% block stylesheet %} @@ -52,47 +52,6 @@ {% if analytics %} {% endif %} - - {% endblock %} - - {% block script_init %} - - - {% endblock %} {% block meta %} @@ -174,9 +133,9 @@
- - - + + + {% block dialogs %} {% call macros.modal_dlg('Error', btn_label='OK') %} @@ -186,6 +145,23 @@ {% endcall %} {% endblock %} + {% block script_init %} + + {% endblock %} + diff --git a/webpackfile-test.js b/webpackfile-test.js new file mode 100644 index 000000000..ec93aecab --- /dev/null +++ b/webpackfile-test.js @@ -0,0 +1,56 @@ +var path = require("path"); +var jstests = path.resolve(__dirname, "jstests"); +var components = path.resolve(__dirname, "remoteappmanager/static/bower_components"); +var js = path.resolve(__dirname, "remoteappmanager/static/js"); + +module.exports = { + entry: { + testsuite: path.resolve(jstests, "testsuite.js") + }, + + output: { + path: path.resolve(jstests, "dist"), + filename: "[name].js" + }, + + resolve: { + extensions: ["*", ".js"], + alias: { + lodash: path.resolve(components, "lodash/dist/lodash"), + jquery: path.resolve(components, "admin-lte/plugins/jQuery/jquery-2.2.3.min"), + vuejs: path.resolve(components, "vue/dist/vue"), + "vue-router": path.resolve(components, "vue-router/dist/vue-router"), + "vue-form": path.resolve(components, "vue-form/dist/vue-form"), + + // "admin-resources": path.resolve(js, "admin-resources"), + "home-resources": path.resolve(jstests, "tests/home/mock_jsapi"), + gamodule: path.resolve(js, "gamodule"), + urlutils: path.resolve(js, "urlutils"), + utils: path.resolve(js, "utils"), + + filters: path.resolve(js, "vue/filters"), + toolkit: path.resolve(js, "admin/vue-components/toolkit/toolkit"), + "toolkit-dir": path.resolve(js, "admin/vue-components/toolkit"), + + helpers: path.resolve(jstests, "helpers"), + + admin: path.resolve(js, "admin"), + home: path.resolve(js, "home"), + } + }, + + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['env'] + } + } + } + ] + } +} diff --git a/webpackfile.js b/webpackfile.js new file mode 100644 index 000000000..32596f851 --- /dev/null +++ b/webpackfile.js @@ -0,0 +1,50 @@ +var path = require("path"); +var components = path.resolve(__dirname, "remoteappmanager/static/bower_components"); +var js = path.resolve(__dirname, "remoteappmanager/static/js"); + +module.exports = { + entry: { + admin: path.resolve(js, "admin/main.js"), + user: path.resolve(js, "home/controller.js") + }, + + output: { + path: path.resolve(__dirname, "remoteappmanager/static/dist"), + filename: "[name].js" + }, + + resolve: { + extensions: ["*", ".js"], + alias: { + lodash: path.resolve(components, "lodash/dist/lodash"), + jquery: path.resolve(components, "admin-lte/plugins/jQuery/jquery-2.2.3.min"), + vuejs: path.resolve(components, "vue/dist/vue"), + "vue-router": path.resolve(components, "vue-router/dist/vue-router"), + "vue-form": path.resolve(components, "vue-form/dist/vue-form"), + + "admin-resources": path.resolve(js, "admin-resources"), + "home-resources": path.resolve(js, "home-resources"), + gamodule: path.resolve(js, "gamodule"), + urlutils: path.resolve(js, "urlutils"), + utils: path.resolve(js, "utils"), + + filters: path.resolve(js, "vue/filters"), + toolkit: path.resolve(js, "admin/vue-components/toolkit/toolkit"), + } + }, + + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['env'] + } + } + } + ] + } +}