From 440aa705096d2f2a9db6d7983fc5d922c3bbc952 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Tue, 4 Nov 2014 15:41:47 -0800 Subject: [PATCH] modelName in xos models, log window, deferred display of detail when collection not ready, beef up detailShower/listViewShower --- .../core/xoslib/dashboards/xosAdminSite.html | 15 +- .../core/xoslib/static/css/xosAdminSite.css | 9 +- .../core/xoslib/static/js/xosAdminSite.js | 34 +--- .../xoslib/static/js/xoslib/xos-backbone.js | 41 +++-- .../core/xoslib/static/js/xoslib/xosHelper.js | 160 +++++++++++++++--- .../core/xoslib/templates/xosAdmin.html | 11 +- 6 files changed, 191 insertions(+), 79 deletions(-) diff --git a/planetstack/core/xoslib/dashboards/xosAdminSite.html b/planetstack/core/xoslib/dashboards/xosAdminSite.html index 95dd688..1096a2a 100644 --- a/planetstack/core/xoslib/dashboards/xosAdminSite.html +++ b/planetstack/core/xoslib/dashboards/xosAdminSite.html @@ -20,8 +20,6 @@ nav
-content -
@@ -33,12 +31,13 @@ content
-log - -
-
-
-
+ + + + + + +
statusoperationcodemessage
diff --git a/planetstack/core/xoslib/static/css/xosAdminSite.css b/planetstack/core/xoslib/static/css/xosAdminSite.css index e73a51f..5148def 100644 --- a/planetstack/core/xoslib/static/css/xosAdminSite.css +++ b/planetstack/core/xoslib/static/css/xosAdminSite.css @@ -6,12 +6,15 @@ color:blue; text-decoration:underline; } +#logTable td, th { + border: 1px solid black; +} #navigationPanel { position: absolute; top: 100px; left: 0; - width: 130px; + width: 220px; bottom: 0; overflow: hidden; background-color: #F0F0F0; @@ -31,7 +34,7 @@ #logPanel { position: absolute; top: auto; - left: 130px; + left: 220px; right: 0; bottom: 0; width: auto; @@ -44,7 +47,7 @@ position: fixed; top: 100px; bottom: 100px; - left: 130px; + left: 220px; right: 0; overflow: auto; background: #f0F0E0; diff --git a/planetstack/core/xoslib/static/js/xosAdminSite.js b/planetstack/core/xoslib/static/js/xosAdminSite.js index 3d87ca6..660556e 100644 --- a/planetstack/core/xoslib/static/js/xosAdminSite.js +++ b/planetstack/core/xoslib/static/js/xosAdminSite.js @@ -1,13 +1,7 @@ OBJS = ['deployment', 'image', 'networkTemplate', 'network', 'networkSliver', 'networkDeployment', 'node', 'service', 'site', 'slice', 'sliceDeployment', 'slicePrivilege', 'sliver', 'user', 'sliceRole', 'userDeployment']; NAV_OBJS = ['deployment', 'site', 'slice', 'user']; -function assert(outcome, description) { - if (!outcome) { - console.log(description); - } -} - -XOSAdminApp = new XOSApplication(); +XOSAdminApp = new XOSApplication({logTableId: "#logTable"}); XOSAdminApp.addRegions({ navigation: "#navigationPanel", @@ -32,7 +26,6 @@ XOSAdminApp.updateNavigationPanel = function() { for (var index in NAV_OBJS) { name = NAV_OBJS[index]; collection_name = name+"s"; - //nav_url = "/" + collection_name; nav_url = "#" + collection_name; id = "nav-"+name; icon_class = ICON_CLASSES[collection_name] || "icon-cog"; @@ -89,27 +82,6 @@ XOSAdminApp.initRouter = function() { var api = {}; var routes = {}; - function listViewShower(listViewName) { - return function() { - XOSAdminApp.detail.show(new XOSAdminApp[listViewName]); - } - }; - - function detailShower(detailName, collection_name) { - shower = function(model_id) { - model = xos[collection_name].get(model_id); - if (model == undefined) { - $("#detail").html("not ready yet"); - return; - } - detailViewClass = XOSAdminApp[detailName]; - detailView = new detailViewClass({model: model}); - XOSAdminApp.detail.show(detailView); - detailView.showLinkedItems(); - } - return shower; - }; - for (var index in OBJS) { name = OBJS[index]; collection_name = name + "s"; @@ -118,13 +90,13 @@ XOSAdminApp.initRouter = function() { listViewName = collection_name + "ListView"; detailViewName = collection_name + "DetailView"; - api[api_command] = listViewShower(listViewName); + api[api_command] = XOSAdminApp.listViewShower(listViewName, "detail"); routes[nav_url] = api_command; nav_url = collection_name + "/:id"; api_command = "detail" + collection_name.charAt(0).toUpperCase() + collection_name.slice(1); - api[api_command] = detailShower(detailViewName, collection_name); + api[api_command] = XOSAdminApp.detailShower(detailViewName, collection_name, "detail"); routes[nav_url] = api_command; }; diff --git a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js index 6216396..b2395d7 100644 --- a/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js +++ b/planetstack/core/xoslib/static/js/xoslib/xos-backbone.js @@ -65,13 +65,19 @@ if (! window.XOSLIB_LOADED ) { }, initialize: function(){ + this.isLoaded = false; this.sortVar = 'name'; this.sortOrder = 'asc'; + this.on( "sort", this.sorted ); }, relatedCollections: [], foreignCollections: [], + sorted: function() { + this.isLoaded = true; + }, + simpleComparator: function( model ){ parts=this.sortVar.split("."); result = model.get(parts[0]); @@ -182,102 +188,103 @@ if (! window.XOSLIB_LOADED ) { function xoslib() { // basic REST - this.sliver = XOSModel.extend({ urlRoot: SLIVER_API }); + this.sliver = XOSModel.extend({ urlRoot: SLIVER_API, modelName: "sliver" }); this.sliverCollection = XOSCollection.extend({ urlRoot: SLIVER_API, relatedCollections: {"networkSlivers": "sliver"}, foreignCollections: ["slices", "deployments", "images", "nodes", "users"], model: this.sliver}); this.slivers = new this.sliverCollection(); - this.slice = XOSModel.extend({ urlRoot: SLICE_API }); + this.slice = XOSModel.extend({ urlRoot: SLICE_API, modelName: "slice" }); this.sliceCollection = XOSCollection.extend({ urlRoot: SLICE_API, relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice"}, foreignCollections: ["services", "sites"], model: this.slice}); this.slices = new this.sliceCollection(); - this.sliceDeployment = XOSModel.extend({ urlRoot: SLICEDEPLOYMENT_API }); + this.sliceDeployment = XOSModel.extend({ urlRoot: SLICEDEPLOYMENT_API, modelName: "sliceDeployment" }); this.sliceDeploymentCollection = XOSCollection.extend({ urlRoot: SLICEDEPLOYMENT_API, foreignCollections: ["slices", "deployments"], model: this.slice}); this.sliceDeployments = new this.sliceDeploymentCollection(); - this.slicePrivilege = XOSModel.extend({ urlRoot: SLICEPRIVILEGE_API }); + this.slicePrivilege = XOSModel.extend({ urlRoot: SLICEPRIVILEGE_API, modelName: "slicePrivilege" }); this.slicePrivilegeCollection = XOSCollection.extend({ urlRoot: SLICEPRIVILEGE_API, foreignCollections: ["slices", "users", "sliceRoles"], model: this.slice}); this.slicePrivileges = new this.slicePrivilegeCollection(); - this.sliceRole = XOSModel.extend({ urlRoot: SLICEROLE_API }); + this.sliceRole = XOSModel.extend({ urlRoot: SLICEROLE_API, modelName: "sliceRole" }); this.sliceRoleCollection = XOSCollection.extend({ urlRoot: SLICEROLE_API, model: this.slice}); this.sliceRoles = new this.sliceRoleCollection(); - this.node = XOSModel.extend({ urlRoot: NODE_API }); + this.node = XOSModel.extend({ urlRoot: NODE_API, modelName: "node" }); this.nodeCollection = XOSCollection.extend({ urlRoot: NODE_API, foreignCollections: ["sites", "deployments"], model: this.node}); this.nodes = new this.nodeCollection(); - this.site = XOSModel.extend({ urlRoot: SITE_API }); + this.site = XOSModel.extend({ urlRoot: SITE_API, modelName: "site" }); this.siteCollection = XOSCollection.extend({ urlRoot: SITE_API, model: this.site}); this.sites = new this.siteCollection(); - this.user = XOSModel.extend({ urlRoot: USER_API }); + this.user = XOSModel.extend({ urlRoot: USER_API, modelName: "user" }); this.userCollection = XOSCollection.extend({ urlRoot: USER_API, relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"}, foreignCollections: ["sites"], model: this.user}); this.users = new this.userCollection(); - this.userDeployment = XOSModel.extend({ urlRoot: USER_API }); + this.userDeployment = XOSModel.extend({ urlRoot: USERDEPLOYMENT_API, modelName: "userDeployment" }); this.userDeploymentCollection = XOSCollection.extend({ urlRoot: USERDEPLOYMENT_API, foreignCollections: ["users","deployments"], model: this.user}); this.userDeployments = new this.userDeploymentCollection(); - this.deployment = XOSModel.extend({ urlRoot: DEPLOYMENT_API }); + this.deployment = XOSModel.extend({ urlRoot: DEPLOYMENT_API, modelName: "deployment" }); this.deploymentCollection = XOSCollection.extend({ urlRoot: DEPLOYMENT_API, relatedCollections: {"slivers": "deployment", "networkDeployments": "deployment", "userDeployments": "deployment"}, model: this.deployment}); this.deployments = new this.deploymentCollection(); - this.image = XOSModel.extend({ urlRoot: IMAGE_API }); + this.image = XOSModel.extend({ urlRoot: IMAGE_API, modelName: "image" }); this.imageCollection = XOSCollection.extend({ urlRoot: IMAGE_API, model: this.image}); this.images = new this.imageCollection(); - this.networkTemplate = XOSModel.extend({ urlRoot: NETWORKTEMPLATE_API }); + this.networkTemplate = XOSModel.extend({ urlRoot: NETWORKTEMPLATE_API, modelName: "networkTemplate" }); this.networkTemplateCollection = XOSCollection.extend({ urlRoot: NETWORKTEMPLATE_API, model: this.networkTemplate}); this.networkTemplates = new this.networkTemplateCollection(); - this.network = XOSModel.extend({ urlRoot: NETWORK_API }); + this.network = XOSModel.extend({ urlRoot: NETWORK_API, modelName: "network" }); this.networkCollection = XOSCollection.extend({ urlRoot: NETWORK_API, relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"}, foreignCollections: ["slices", "networkTemplates"], model: this.network}); this.networks = new this.networkCollection(); - this.networkSliver = XOSModel.extend({ urlRoot: NETWORKSLIVER_API }); + this.networkSliver = XOSModel.extend({ urlRoot: NETWORKSLIVER_API, modelName: "networkSliver" }); this.networkSliverCollection = XOSCollection.extend({ urlRoot: NETWORKSLIVER_API, model: this.networkSliver}); this.networkSlivers = new this.networkSliverCollection(); - this.networkDeployment = XOSModel.extend({ urlRoot: NETWORKDEPLOYMENT_API }); + this.networkDeployment = XOSModel.extend({ urlRoot: NETWORKDEPLOYMENT_API, modelName: "networkDeployment" }); this.networkDeploymentCollection = XOSCollection.extend({ urlRoot: NETWORKDEPLOYMENT_API, model: this.networkDeployment}); this.networkDeployments = new this.networkDeploymentCollection(); - this.service = XOSModel.extend({ urlRoot: SERVICE_API }); + this.service = XOSModel.extend({ urlRoot: SERVICE_API, modelName: "sliver" }); this.serviceCollection = XOSCollection.extend({ urlRoot: SERVICE_API, model: this.service}); this.services = new this.serviceCollection(); // enhanced REST - this.slicePlus = XOSModel.extend({ urlRoot: SLICEPLUS_API, relatedCollections: {'slivers': "slice"} }); + this.slicePlus = XOSModel.extend({ urlRoot: SLICEPLUS_API, modelName: "slicePlus" }); this.slicePlusCollection = XOSCollection.extend({ urlRoot: SLICEPLUS_API, + relatedCollections: {'slivers': "slice"}, model: this.slicePlus}); this.slicesPlus = new this.slicePlusCollection(); diff --git a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js index 0a1be26..0ba1c3d 100644 --- a/planetstack/core/xoslib/static/js/xoslib/xosHelper.js +++ b/planetstack/core/xoslib/static/js/xoslib/xosHelper.js @@ -1,3 +1,15 @@ +function assert(outcome, description) { + if (!outcome) { + console.log(description); + } +} + +HTMLView = Marionette.ItemView.extend({ + render: function() { + this.$el.append(this.options.html); + }, +}); + XOSApplication = Marionette.Application.extend({ detailBoxId: "#detailBox", errorBoxId: "#errorBox", @@ -6,27 +18,131 @@ XOSApplication = Marionette.Application.extend({ successCloseButtonId: "#close-success-box", errorTemplate: "#xos-error-template", successTemplate: "#xos-success-template", + logMessageCount: 0, - hideError: function(result) { - $(this.errorBoxId).hide(); - $(this.successBoxId).hide(); + hideError: function() { + if (this.logWindowId) { + } else { + $(this.errorBoxId).hide(); + $(this.successBoxId).hide(); + } }, showSuccess: function(result) { - $(this.successBoxId).show(); - $(this.successBoxId).html(_.template($(this.successTemplate).html())(result)); - $(this.successCloseButtonId).unbind().bind('click', function() { - $(this.successBoxId).hide(); - }); + if (this.logTableId) { + result["success"] = "success"; + this.appendLogWindow(result); + } else { + $(this.successBoxId).show(); + $(this.successBoxId).html(_.template($(this.successTemplate).html())(result)); + $(this.successCloseButtonId).unbind().bind('click', function() { + $(this.successBoxId).hide(); + }); + } }, showError: function(result) { - $(this.errorBoxId).show(); - $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result)); - $(this.errorCloseButtonId).unbind().bind('click', function() { - $(this.errorBoxId).hide(); - }); + if (this.logTableId) { + result["success"] = "failure"; + this.appendLogWindow(result); + } else { + $(this.errorBoxId).show(); + $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result)); + $(this.errorCloseButtonId).unbind().bind('click', function() { + $(this.errorBoxId).hide(); + }); + } + }, + + showInformational: function(result) { + if (this.logTableId) { + result["success"] = "information"; + return this.appendLogWindow(result); + } else { + return undefined; + } + }, + + appendLogWindow: function(result) { + // compute a new logMessageId for this log message + logMessageId = "logMessage" + this.logMessageCount; + this.logMessageCount = this.logMessageCount + 1; + result["logMessageId"] = logMessageId; + + logMessageTemplate=$("#xos-log-template").html(); + assert(logMessageTemplate != undefined, "logMessageTemplate is undefined"); + newRow = _.template(logMessageTemplate, result); + assert(newRow != undefined, "newRow is undefined"); + + if (result["infoMsgId"] != undefined) { + // We were passed the logMessageId of an informational message, + // and the caller wants us to replace that message with our own. + // i.e. replace an informational message with a success or an error. + console.log(result["infoMsgId"]); + console.log($("."+result["infoMsgId"])); + $("#"+result["infoMsgId"]).replaceWith(newRow); + } else { + // Create a brand new log message rather than replacing one. + logTableBody = $(this.logTableId + " tbody"); + logTableBody.prepend(newRow); + } + return logMessageId; }, + + hideLinkedItems: function(result) { + index=0; + while (index<4) { + this["linkedObjs" + (index+1)].empty(); + index = index + 1; + } + }, + + listViewShower: function(listViewName, regionName) { + var app=this; + return function() { + app[regionName].show(new app[listViewName]); + app.hideLinkedItems(); + } + }, + + detailShower: function(detailName, collection_name, regionName) { + var app=this; + showModelId = function(model_id) { + showModel = function(model) { + console.log(app); + detailViewClass = app[detailName]; + detailView = new detailViewClass({model: model}); + app[regionName].show(detailView); + detailView.showLinkedItems(); + } + + collection = xos[collection_name]; + model = collection.get(model_id); + if (model == undefined) { + if (!collection.isLoaded) { + // If the model cannot be found, then maybe it's because + // we haven't finished loading the collection yet. So wait for + // the sort event to complete, then try again. + collection.once("sort", function() { + collection = xos[collection_name]; + model = collection.get(model_id); + if (model == undefined) { + // We tried. It's not here. Complain to the user. + app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name})); + } else { + showModel(model); + } + }); + } else { + // The collection was loaded, the user must just be asking for something we don't have. + app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name})); + } + } else { + showModel(model); + } + } + return showModelId; + }, }); /* XOSDetailView @@ -53,21 +169,27 @@ XOSDetailView = Marionette.ItemView.extend({ this.dirty = true; }, - saveError: function(model, result, xhr) { + saveError: function(model, result, xhr, infoMsgId) { + result["what"] = "save " + model.__proto__.modelName; + result["infoMsgId"] = infoMsgId; this.app.showError(result); }, - saveSuccess: function(model, result, xhr) { - this.app.showSuccess({status: xhr.xhr.status, statusText: xhr.xhr.statusText}); + saveSuccess: function(model, result, xhr, infoMsgId) { + result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText}; + result["what"] = "save " + model.__proto__.modelName; + result["infoMsgId"] = infoMsgId; + this.app.showSuccess(result); }, submitClicked: function(e) { this.app.hideError(); e.preventDefault(); + var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} ); var data = Backbone.Syphon.serialize(this); - var thisView = this; - this.model.save(data, {error: function(model, result, xhr) { thisView.saveError(model, result, xhr); }, - success: function(model, result, xhr) { thisView.saveSuccess(model, result, xhr); }}); + var that = this; + this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);}, + success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}}); this.dirty = false; }, diff --git a/planetstack/core/xoslib/templates/xosAdmin.html b/planetstack/core/xoslib/templates/xosAdmin.html index 3093b73..8e4588c 100644 --- a/planetstack/core/xoslib/templates/xosAdmin.html +++ b/planetstack/core/xoslib/templates/xosAdmin.html @@ -22,13 +22,22 @@
+ + -- 2.43.0