diff --git a/ui/index.html b/ui/index.html index 9cd10a0ff5..6cf504f6b7 100644 --- a/ui/index.html +++ b/ui/index.html @@ -29,11 +29,13 @@
- Services + {{#link-to 'services' controllers.application.getDc class='btn btn-default'}}Services{{/link-to}}
+
- Nodes + {{#link-to 'nodes' controllers.application.getDc class='btn btn-default'}}Nodes{{/link-to}}
+
Key/Value
@@ -55,29 +57,60 @@ diff --git a/ui/javascripts/app.js b/ui/javascripts/app.js index 12be2e2703..c74b2355e9 100755 --- a/ui/javascripts/app.js +++ b/ui/javascripts/app.js @@ -7,6 +7,7 @@ window.App = Ember.Application.create({ App.Router.map(function() { this.route("index", { path: "/" }); this.route("services", { path: "/:dc/services" }); + this.route("service", { path: "/:dc/services/:name" }); this.route("nodes", { path: "/:dc/nodes" }); this.route("node", { path: "/:dc/nodes/:name" }); this.route("kv", { path: "/:dc/kv" }); @@ -36,13 +37,6 @@ App.ApplicationController = Ember.Controller.extend({ }.property("getDc") }); -// -// path: /:dc/services -// -App.ServicesController = Ember.Controller.extend({ - needs: ['application'] -}) - // // Superclass to be used by all of the main routes below. All routes // but the IndexRoute share the need to have a datacenter set. @@ -140,12 +134,84 @@ App.Service = Ember.Object.extend({ }.property('hasFailingChecks') }); +// +// A Consul Node +// +App.Node = Ember.Object.extend({ + // + // The number of failing checks within the service. + // + failingChecks: function() { + return this.get('Checks').filterBy('Status', 'critical').get('length'); + }.property('failingChecks'), + + // + // The number of passing checks within the service. + // + passingChecks: function() { + return this.get('Checks').filterBy('Status', 'passing').get('length'); + }.property('passingChecks'), + + // + // The formatted message returned for the user which represents the + // number of checks failing or passing. Returns `1 passing` or `2 failing` + // + checkMessage: function() { + if (this.get('hasFailingChecks') === false) { + return this.get('passingChecks') + ' passing'; + } else { + return this.get('failingChecks') + ' failing'; + } + }.property('checkMessage'), + + // + // Boolean of whether or not there are failing checks in the service. + // This is used to set color backgrounds and so on. + // + hasFailingChecks: function() { + return (this.get('failingChecks') > 0); + }.property('hasFailingChecks') +}); + // // Display all the services, allow to drill down into the specific services. // App.ServicesRoute = App.BaseRoute.extend({ - model: function() { - return [App.Service.create(window.fixtures.services[0]), App.Service.create(window.fixtures.services[1])]; + // + // Set the services as the routes default model to be called in + // the template as {{model}} + // + setupController: function(controller, model) { + // + // Since we have 2 column layout, we need to also display the + // list of services on the left. Hence setting the attribute + // {{services}} on the controller. + // + controller.set('services', [App.Service.create(window.fixtures.services[0]), App.Service.create(window.fixtures.services[1])]); + } +}); + +// +// Display an individual service, as well as the global services in the left +// column. +// +App.ServiceRoute = App.BaseRoute.extend({ + // + // Set the model on the route. We look up the specific service + // by it's identifier passed via the route + // + model: function(params) { + return [App.Node.create(window.fixtures.services_full[params.name][0]), App.Node.create(window.fixtures.services_full[params.name][1])]; + }, + + setupController: function(controller, model) { + controller.set('content', model); + // + // Since we have 2 column layout, we need to also display the + // list of services on the left. Hence setting the attribute + // {{services}} on the controller. + // + controller.set('services', [App.Service.create(window.fixtures.services[0]), App.Service.create(window.fixtures.services[1])]); } }); @@ -153,6 +219,33 @@ App.ServicesRoute = App.BaseRoute.extend({ // Services // App.ServicesView = Ember.View.extend({ + templateName: 'services', layoutName: 'default_layout' }) +// +// Services +// +App.ServiceView = Ember.View.extend({ + templateName: 'services', + layoutName: 'default_layout' +}) + +// +// path: /:dc/services +// +App.ServicesController = Ember.Controller.extend({ + needs: ['application'] +}) + +// +// path: /:dc/services/:name +// +App.ServiceController = Ember.Controller.extend({ + // + // We use the same template as we do for the services + // array and have a simple conditional to display the nested + // individual service resource. + // + needs: ['application'] +}) diff --git a/ui/javascripts/fixtures.js b/ui/javascripts/fixtures.js index 79146a0acb..aadd069532 100644 --- a/ui/javascripts/fixtures.js +++ b/ui/javascripts/fixtures.js @@ -27,8 +27,7 @@ fixtures.services = [ ], "Nodes": [ "node-10-0-1-109", - "node-10-0-1-102", - "node-10-0-1-103" + "node-10-0-3-84" ] }, { @@ -48,13 +47,114 @@ fixtures.services = [ } ], "Nodes": [ - "node-10-0-1-109", - "node-10-0-1-102", - "node-10-0-1-103" + "node-10-0-1-103", + "node-10-0-1-104" ] }, ] +// This is both of the fixture services full response. We +// would just expect one of these, inside of the top level +// key. We require that key just for the fixture lookup. +fixtures.services_full = { + "vagrant-cloud-http": [ + // A node + { + "ServicePort": 80, + "ServiceTags": null, + "ServiceName": "vagrant-cloud-http", + "ServiceID": "vagrant-cloud-http", + "Address": "10.0.1.109", + "Node": "node-10-0-1-109", + "Checks": [ + { + "ServiceName": "", + "ServiceID": "", + "Notes": "", + "Status": "critical", + "Name": "Serf Health Status", + "CheckID": "serfHealth", + "Node": "node-10-0-3-83" + } + ] + }, + // A node + { + "ServicePort": 80, + "ServiceTags": null, + "ServiceName": "vagrant-cloud-http", + "ServiceID": "vagrant-cloud-http", + "Address": "10.0.3.83", + "Node": "node-10-0-3-84", + "Checks": [ + { + "ServiceName": "", + "ServiceID": "", + "Notes": "", + "Status": "passing", + "Name": "Serf Health Status", + "CheckID": "serfHealth", + "Node": "node-10-0-3-84" + } + ] + } + ], + "vagrant-share-mux": [ + // A node + { + "ServicePort": 80, + "ServiceTags": null, + "ServiceName": "vagrant-share-mux", + "ServiceID": "vagrant-share-mux", + "Address": "10.0.1.104", + "Node": "node-10-0-1-104", + "Checks": [ + { + "ServiceName": "vagrant-share-mux", + "ServiceID": "vagrant-share-mux", + "Notes": "", + "Output": "200 ok", + "Status": "passing", + "Name": "Foo Heathly", + "CheckID": "fooHealth", + "Node": "node-10-0-1-104" + } + ] + }, + // A node + { + "ServicePort": 80, + "ServiceTags": null, + "ServiceName": "vagrant-share-mux", + "ServiceID": "vagrant-share-mux", + "Address": "10.0.1.103", + "Node": "node-10-0-1-103", + "Checks": [ + { + "ServiceName": "", + "ServiceID": "", + "Notes": "", + "Output": "foobar baz", + "Status": "passing", + "Name": "Baz Status", + "CheckID": "bazHealth", + "Node": "node-10-0-1-103" + }, + { + "ServiceName": "", + "ServiceID": "", + "Notes": "", + "Output": "foobar baz", + "Status": "passing", + "Name": "Serf Health Status", + "CheckID": "serfHealth", + "Node": "node-10-0-1-103" + } + ] + } + ] +} + fixtures.dcs = ['nyc1', 'sf1', 'sg1'] localStorage.setItem("current_dc", fixtures.dcs[0]); diff --git a/ui/styles/_buttons.scss b/ui/styles/_buttons.scss index 45654b7594..96bf161995 100644 --- a/ui/styles/_buttons.scss +++ b/ui/styles/_buttons.scss @@ -13,7 +13,12 @@ .btn-default { } - &.btn-primary { + &.active { + box-shadow: none; + -webkit-box-shadow: none; + } + + &.btn-primary, &.active { color: $purple; background-color: transparent; border: 2px solid $purple; diff --git a/ui/styles/_nav.scss b/ui/styles/_nav.scss index 355be343ea..d74a0ba467 100644 --- a/ui/styles/_nav.scss +++ b/ui/styles/_nav.scss @@ -10,6 +10,7 @@ .topbar { padding: 30px; + margin-bottom: 20px; min-height: 100px; border-bottom: 1px #eee solid; diff --git a/ui/styles/_panels.scss b/ui/styles/_panels.scss index ed0198e42a..79b80a0c7c 100644 --- a/ui/styles/_panels.scss +++ b/ui/styles/_panels.scss @@ -66,6 +66,7 @@ } &.panel-link:hover { - background-color: lighten($gray-background, 8%); + cursor: pointer; + background-color: lighten($gray-background, 8%); } } diff --git a/ui/styles/base.scss b/ui/styles/base.scss index 44980c3e7d..c06065d526 100644 --- a/ui/styles/base.scss +++ b/ui/styles/base.scss @@ -6,6 +6,26 @@ @import "buttons"; @import "lists"; +@media (min-width: 768px) { // + 18 + .container { + width: 750px; + } +} +@media (min-width: 992px) { // + 22 + .container { + width: 970px; + } +} +@media (min-width: 1200px) { // + 30 + .container { + width: 1400px; + } +} + + +.no-margin { + margin: 0; +} .vertical-center { margin-top: 200px; @@ -17,6 +37,10 @@ } } +.bordered { + border-left: 2px solid $gray-background; +} + .bg-purple { background-color: $purple;