diff options
Diffstat (limited to 'web/client.js')
-rw-r--r-- | web/client.js | 455 |
1 files changed, 226 insertions, 229 deletions
diff --git a/web/client.js b/web/client.js index 0dd510a..926bbcb 100644 --- a/web/client.js +++ b/web/client.js @@ -3,246 +3,243 @@ * See LICENSE file for license details */ -define( - [ - "aconf/blocking", - "aconf/dom", - "aconf/error", - "aconf/navigation", - "aconf/path", - "aconf/statusbar", - "aconf/transaction", - "aconf/type", - "aconf/widget/inline", - "jquery", - "underscore", - "aconf/layout/stacked", - "aconf/layout/tabular", - "domReady" - ], - function( - blocking, - dom, - formatError, - navi, - pth, - statusBar, - txnMgr, - type, - Inline, - $, - _ - ) { - return function() { - $("#login").submit(function() { - - $.ajax("/login", { - type: "POST", - data: JSON.stringify({ - username: $("#username").val(), - password: $("#password").val() - }) - }).done(function(data, status, xhr) { - - txnMgr = txnMgr( - xhr.getResponseHeader("X-AConf-Auth-Token"), - xhr.getResponseHeader("X-AConf-Save-Required") == "1" - ); - - - var objPath; - - function renderObject(path, data) { - if (path) objPath = path; - - return ( - data ? $.Deferred().resolve(data) : - txnMgr.query(objPath) - ).done(function(data) { - var layout = data.meta.widget; - var name = pth.split(objPath).pop(); - (layout ? - require("aconf/layout/" + layout) : - Inline).extend({ - createEl: function() { - return $("#content").empty(); - }, - wrap: function() { return this.el; }, - requestData: function(value, meta) { - return $.Deferred() - .resolve(data, data.meta); - } - }).new( - { - get: function(name) { return objPath; }, - status: function(name) { return null; }, - match: function(filter) { return true; } - }, - name, - {}, - 0, - true, - false - ); - }).fail(function() { - var comps = pth.split(objPath); - comps.pop(); - comps.unshift("/"); - navi.setPath(pth.join.apply(undefined, comps)); - }); - }; - - $("#content").on("reload", function(event, txnValid) { - statusBar.validationReady(txnValid); - renderObject(); - event.stopPropagation(); +angular.module( + "aconf", ["ngRoute", "ui.bootstrap", "ui.sortable"] + +).config(function($routeProvider) { + $routeProvider.when("/login", { + templateUrl: "login.html", controller: "aconfLoginCtrl" + }).otherwise({templateUrl: "view.html", controller: "aconfObjCtrl"}); + +}).value("aconfErrorFormat", function(resp) { + if (_.isString(resp)) return resp; + if (_.isString(resp.data)) return resp.data; + if (resp.status == 422) return _.values(resp.data).join("\n"); + return JSON.stringify(resp); + +}).factory("aconfExclusive", function($modal, $q, $rootScope) { + var blocked = false; + $rootScope.$on("$locationChangeStart", function(event) { + if (blocked) event.preventDefault(); + }); + function unblock() { blocked = false; } + + function res(task) { + return $q(function(resolve, reject) { + blocked = true; + + $modal.open({ + backdrop: "static", + templateUrl: "wait.html", + controller: function($modalInstance) { + $modalInstance.rendered.then(function() { + task().then( + $modalInstance.close, $modalInstance.dismiss + ); }); + $modalInstance.result.then(resolve, reject); + $modalInstance.result.then(unblock, unblock); + } + }); + }); + } + Object.defineProperty(res, "isRunning", {get: function() { + return blocked; + }}); + return res; +}).controller( + "aconfCtrl", + function( + $location, $route, $scope, aconfErrorFormat, aconfExclusive, aconfTxn + ) { - function render() { - var path = navi.getPath(); - - function renderMenu( - target, path, current, selectFirst - ) { - var def = $.Deferred(); - - txnMgr.query(path).done(function(data) { - if (data.meta.type != "model") { - def.reject(data); - return; - } - - var tnFields = []; - var extraFields = false; - - _.each(data.meta.fields, function(field) { - if (!field.visible) return; - - if (!type.isTreeNode(field)) - extraFields = true; - else if (data.data[field.name]) - tnFields.push(field); - }); - - if (!tnFields.length) { - def.reject(data); - return; - } - - function addItem( - path, ui_name, status, current - ) { - var el = $("<li>").html( - dom.objectRef(path).text(ui_name) - ); - dom.setStatus(el, status); - if (current) el.addClass("current"); - target.append(el); - } - - if (extraFields) { - addItem(path, "General", null, !current); - selectFirst = false; - } - - _.each(tnFields, function(field, i) { - addItem( - data.get(field.name), - field["ui-name"], - data.status(field.name), - current == field.name || ( - !current && !i && selectFirst - ) - ); - field.visible = false; - }); - - def.resolve( - extraFields ? data : tnFields[0].name - ); - }); - - return def; - } + $scope.loggedIn = function(token, saveRequired) { + $scope.txnMgr = aconfTxn(token, saveRequired); + $location.path("/config"); + }; + + $scope.validationReady = function(txnValid) { + if (txnValid != true) { + $scope.txnStatus = "invalid"; + $scope.status = aconfErrorFormat( + txnValid || "Some values need checking" + ); + } + else if ($scope.txnMgr.isPristine()) { + $scope.txnStatus = null; + $scope.status = ""; + } + else { + $scope.txnStatus = "changed"; + $scope.status = "There are changes to be committed"; + } + }; + + function clearState() { + $scope.txnStatus = null; + $scope.status = ""; + $route.reload(); + } + $scope.commit = function() { + aconfExclusive(function() { + return $scope.txnMgr.commit(); + }).then(clearState, function(resp) { + $scope.validationReady( + "Commit failed: " + aconfErrorFormat(resp) + ); + }); + }; - if (path > "/") $("#content").text("Loading..."); + $scope.revert = function() { + $scope.txnMgr.abort(); + clearState(); + }; - txnMgr.start().done(function() { - var comps = pth.split(path); - renderMenu( - $("#modules").empty(), "/", comps[0], false + $scope.logout = function() { + $scope.txnMgr.logout().then(function() { + $location.path("/login"); + }); + }; + } +).controller("aconfLoginCtrl", function($http, $scope) { + $scope.login = function() { + $http({ + url: "/login", + method: "POST", + data: {username: $scope.username, password: $scope.password} + }).success(function(data, status, headers) { + $scope.loggedIn( + headers("X-AConf-Auth-Token"), + headers("X-AConf-Save-Required") == "1" + ); + }).error(function() { + $scope.failed = true; + $scope.password = ""; + }); + } + $("#username").focus(); + +}).controller( + "aconfObjCtrl", + function($location, $q, $scope, aconfPath, aconfType) { + $scope.txnMgr.start().then(function() { + + function menuItems(path, current, data) { + if (data.meta.type != "model") return; + + var items = []; + var extraFields = false; + var extraStatus = null; + + function makeItem(path, label, active, klass) { + var item = {path: path, label: label}; + if (active) item.class = "active"; + else if (klass) item.class = klass; + return item; + } + + _.each(data.meta.fields, function(field) { + if (!field.visible) return; + var status = data.status(field.name); + + if (!aconfType.isTreeNode(field)) { + extraFields = true; + if (!extraStatus || status == "invalid") + extraStatus = status; + } + else { + var p = data.get(field.name); + if (p) + items.push( + makeItem( + p, + field["ui-name"], + aconfPath.isSubordinate(current, p), + status + ) ); - var tabs = $("#tabs").empty(); - - if (path == "/") return; - - function renderTabs(p) { - p = pth.join(p, comps.shift()); - renderMenu(tabs, p, comps[0], true) - .done(function(data) { - var tabLevel = !comps.length; - renderObject( - ( - tabLevel && _.isString(data) - ) ? pth.join(p, data) : path, - tabLevel && _.isObject(data) ? - data : null - ); - }) - .fail(function(data) { - if (comps.length) renderTabs(p); - else renderObject(p, data); - }); - } - renderTabs("/"); - }); + field.visible = false; } + }); - - function clearState() { - statusBar.reset(); - render(); - blocking.disable(); - } - - $("#commit").click(function() { - blocking.enable(); - txnMgr.commit().done(clearState).fail(function(xhr) { - statusBar.setError( - formatError("Commit failed", xhr), "txn" - ); - }) - }); - $("#revert").click(function() { - blocking.enable(); - txnMgr.abort(); - clearState(); - }); - - $("#logout").click(function() { - txnMgr.logout().done(function() { - $("body").html($("<p>").text("Logged out")); + if (!items.length) return; + + if (extraFields) + items.unshift(makeItem( + path, "General", current == path, extraStatus + )); + else if (current == path) { + current = items[0].path; + items[0].class = "active"; + } + + return {items: items, current: current}; + }; + + var path = $location.path().substring(7) || "/"; + $scope.txnMgr.query("/").then(function(data) { + var modules = menuItems("/", path, data); + $scope.modules = modules.items; + if (path == "/") path = modules.current; + + function renderObject(path, data) { + (data ? $q.when(data) : $scope.txnMgr.query(path)) + .then(function(data) { + $scope.data = data; + }, function() { + var comps = aconfPath.split(path); + comps.pop(); + comps.unshift("/config"); + $location.path(aconfPath.join.apply(undefined, comps)); }); + } + + var comps = aconfPath.split(path); + + function renderTabs(p) { + p = aconfPath.join(p, comps.shift()); + $scope.txnMgr.query(p).then(function(data) { + var menu = menuItems(p, path, data); + if (menu) { + $scope.tabs = menu.items; + renderObject( + menu.current, p == menu.current ? data : null + ); + } + else if (comps.length) renderTabs(p); + else renderObject(p, data); }); - - statusBar.reset(); - $("#content").empty(); - - $(window).bind("hashchange", function() { - if (!blocking.isEnabled()) render(); - }); - navi.setPath("/"); - - }).fail(function() { - statusBar.setError("Login failed", "login"); - }); + } - return false; + renderTabs("/"); }); - - $("#username").focus(); - } + }); } -); +).directive("aconfMenu", function() { + return { + restrict: "A", + scope: {aconfMenu: "="}, + templateUrl: "directive/menu.html" + }; +}).directive("aconfLayout", function() { + return { + restrict: "E", + scope: {node: "=", onValidate: "&"}, + template: '<aconf-fields node="node" ng-if="node"></aconf-fields>', + controller: function($scope) { + function validationReady(txnValid) { + $scope.onValidate({txnValid: txnValid}); + }; + + this.query = function(path) { + return $scope.node.txnMgr.query(path); + }; + this.updateRequest = function(task) { + task.then(validationReady, validationReady); + }; + this.validationReady = validationReady; + } + }; +}).run(function($location) { $location.path("/login"); }); |