modelName in xos models, log window, deferred display of detail when collection not...
authorScott Baker <smbaker@gmail.com>
Tue, 4 Nov 2014 23:41:47 +0000 (15:41 -0800)
committerScott Baker <smbaker@gmail.com>
Tue, 4 Nov 2014 23:41:47 +0000 (15:41 -0800)
planetstack/core/xoslib/dashboards/xosAdminSite.html
planetstack/core/xoslib/static/css/xosAdminSite.css
planetstack/core/xoslib/static/js/xosAdminSite.js
planetstack/core/xoslib/static/js/xoslib/xos-backbone.js
planetstack/core/xoslib/static/js/xoslib/xosHelper.js
planetstack/core/xoslib/templates/xosAdmin.html

index 95dd688..1096a2a 100644 (file)
@@ -20,8 +20,6 @@ nav
 </div>
 
 <div id="contentPanel">
-content
-
 <div id="detailBox">
 <div id="detail"></div>
 <div id="linkedObjs1"></div>
@@ -33,12 +31,13 @@ content
 </div>
 
 <div id="logPanel">
-log
-
-<div id="successBox">
-</div>
-<div id="errorBox">
-</div>
+<table id="logTable">
+<thead>
+<tr><th>status</th><th>operation</th><th>code</th><th>message</th></tr>
+</thead>
+<tbody>
+</tbody>
+</table>
 
 </div>
 
index e73a51f..5148def 100644 (file)
@@ -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;
index 3d87ca6..660556e 100644 (file)
@@ -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) {\r
         name = NAV_OBJS[index];\r
         collection_name = name+"s";\r
-        //nav_url = "/" + collection_name;\r
         nav_url = "#" + collection_name;\r
         id = "nav-"+name;\r
         icon_class = ICON_CLASSES[collection_name] || "icon-cog";\r
@@ -89,27 +82,6 @@ XOSAdminApp.initRouter = function() {
     var api = {};\r
     var routes = {};\r
 \r
-    function listViewShower(listViewName) {\r
-        return function() {\r
-            XOSAdminApp.detail.show(new XOSAdminApp[listViewName]);\r
-        }\r
-    };\r
-\r
-    function detailShower(detailName, collection_name) {\r
-        shower = function(model_id) {\r
-            model = xos[collection_name].get(model_id);\r
-            if (model == undefined) {\r
-                $("#detail").html("not ready yet");\r
-                return;\r
-            }\r
-            detailViewClass = XOSAdminApp[detailName];\r
-            detailView = new detailViewClass({model: model});\r
-            XOSAdminApp.detail.show(detailView);\r
-            detailView.showLinkedItems();\r
-        }\r
-        return shower;\r
-    };\r
-\r
     for (var index in OBJS) {\r
         name = OBJS[index];\r
         collection_name = name + "s";\r
@@ -118,13 +90,13 @@ XOSAdminApp.initRouter = function() {
         listViewName = collection_name + "ListView";\r
         detailViewName = collection_name + "DetailView";\r
 \r
-        api[api_command] = listViewShower(listViewName);\r
+        api[api_command] = XOSAdminApp.listViewShower(listViewName, "detail");\r
         routes[nav_url] = api_command;\r
 \r
         nav_url = collection_name + "/:id";\r
         api_command = "detail" + collection_name.charAt(0).toUpperCase() + collection_name.slice(1);\r
 \r
-        api[api_command] = detailShower(detailViewName, collection_name);\r
+        api[api_command] = XOSAdminApp.detailShower(detailViewName, collection_name, "detail");\r
         routes[nav_url] = api_command;\r
     };\r
 \r
index 6216396..b2395d7 100644 (file)
@@ -65,13 +65,19 @@ if (! window.XOSLIB_LOADED ) {
                  },
 
         initialize: function(){
+          this.isLoaded = false;
           this.sortVar = 'name';\r
           this.sortOrder = 'asc';\r
+          this.on( "sort", this.sorted );\r
         },\r
 \r
         relatedCollections: [],\r
         foreignCollections: [],\r
 \r
+        sorted: function() {\r
+            this.isLoaded = true;\r
+        },\r
+\r
         simpleComparator: function( model ){\r
           parts=this.sortVar.split(".");\r
           result = model.get(parts[0]);\r
@@ -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();
 
index 0a1be26..0ba1c3d 100644 (file)
@@ -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) {\r
+            this["linkedObjs" + (index+1)].empty();\r
+            index = index + 1;\r
+        }\r
+    },\r
+\r
+    listViewShower: function(listViewName, regionName) {\r
+        var app=this;\r
+        return function() {\r
+            app[regionName].show(new app[listViewName]);\r
+            app.hideLinkedItems();\r
+        }\r
+    },\r
+\r
+    detailShower: function(detailName, collection_name, regionName) {\r
+        var app=this;\r
+        showModelId = function(model_id) {\r
+            showModel = function(model) {\r
+                                console.log(app);\r
+                detailViewClass = app[detailName];\r
+                detailView = new detailViewClass({model: model});\r
+                app[regionName].show(detailView);\r
+                detailView.showLinkedItems();\r
+            }\r
+\r
+            collection = xos[collection_name];\r
+            model = collection.get(model_id);\r
+            if (model == undefined) {\r
+                if (!collection.isLoaded) {\r
+                    // If the model cannot be found, then maybe it's because\r
+                    // we haven't finished loading the collection yet. So wait for\r
+                    // the sort event to complete, then try again.\r
+                    collection.once("sort", function() {\r
+                        collection = xos[collection_name];\r
+                        model = collection.get(model_id);\r
+                        if (model == undefined) {\r
+                            // We tried. It's not here. Complain to the user.\r
+                            app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
+                        } else {\r
+                            showModel(model);\r
+                        }\r
+                    });\r
+                } else {\r
+                    // The collection was loaded, the user must just be asking for something we don't have.\r
+                    app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
+                }\r
+            } else {\r
+                showModel(model);\r
+            }\r
+        }\r
+        return showModelId;\r
+    },\r
 });
 
 /* XOSDetailView
@@ -53,21 +169,27 @@ XOSDetailView = Marionette.ItemView.extend({
                 this.dirty = true;\r
             },\r
 \r
-            saveError: function(model, result, xhr) {\r
+            saveError: function(model, result, xhr, infoMsgId) {\r
+                result["what"] = "save " + model.__proto__.modelName;\r
+                result["infoMsgId"] = infoMsgId;\r
                 this.app.showError(result);\r
             },\r
 \r
-            saveSuccess: function(model, result, xhr) {\r
-                this.app.showSuccess({status: xhr.xhr.status, statusText: xhr.xhr.statusText});\r
+            saveSuccess: function(model, result, xhr, infoMsgId) {\r
+                result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};\r
+                result["what"] = "save " + model.__proto__.modelName;\r
+                result["infoMsgId"] = infoMsgId;\r
+                this.app.showSuccess(result);\r
             },
 
             submitClicked: function(e) {
                 this.app.hideError();\r
                 e.preventDefault();\r
+                var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );\r
                 var data = Backbone.Syphon.serialize(this);\r
-                var thisView = this;\r
-                this.model.save(data, {error: function(model, result, xhr) { thisView.saveError(model, result, xhr); },\r
-                                       success: function(model, result, xhr) { thisView.saveSuccess(model, result, xhr); }});\r
+                var that = this;\r
+                this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},\r
+                                       success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});\r
                 this.dirty = false;\r
             },
 
index 3093b73..8e4588c 100644 (file)
   <button class="btn btn-default btn-xosnav" onclick="<%= router %>.navigate('<%= routeUrl %>', {trigger: true})"><%= name %></button><br>
 </script>
 
+<script type="text/template" id="xos-log-template">
+  <tr id="<%= logMessageId %>">
+     <td><%= success %></td>
+     <td><%= what %></td>
+     <td><%= status %></td>
+     <td><%= statusText %></td>
+  </tr>
+</script>
+
 <script type="text/template" id="xos-navbutton">
   <li>
       <a href="<%= routeUrl %>">
           <i class="<%= iconClass %>"></i>
           <%= name %>
       </a>
-  </lid>
+  </li>
 </script>
 
 <!-- Deployment -->