summaryrefslogtreecommitdiffstats
path: root/web/client.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/client.js')
-rw-r--r--web/client.js455
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"); });