ui: more work on loading data for /services

This commit is contained in:
Jack Pearkes 2014-04-24 13:47:14 -04:00
parent cc8c6020af
commit 3e4029c010
7 changed files with 283 additions and 26 deletions

View File

@ -29,11 +29,13 @@
</div>
<div class="col-md-2 col-sm-3">
<a class="btn btn-primary" href="#">Services</a>
{{#link-to 'services' controllers.application.getDc class='btn btn-default'}}Services{{/link-to}}
</div>
<div class="col-md-2 col-sm-3">
<a class="btn btn-default" href="#">Nodes</a>
{{#link-to 'nodes' controllers.application.getDc class='btn btn-default'}}Nodes{{/link-to}}
</div>
<div class="col-md-2 col-sm-3">
<a class="btn btn-default" href="#">Key/Value</a>
</div>
@ -55,29 +57,60 @@
</script>
<script type="text/x-handlebars" id="services">
<div class="col-md-6">
<div class="col-md-5">
{{#each service in services}}
{{#each item in model}}
<div class="list-group-item">
<div {{bind-attr class="item.hasFailingChecks:bg-orange:bg-green :list-bar"}}></div>
<div {{bind-attr class="service.hasFailingChecks:bg-orange:bg-green :list-bar"}}></div>
<h4 class="list-group-item-heading">
<a href="#" class="subtle">{{item.Name}}</a>
<small>{{item.Name}}</small>
{{#link-to 'service' controllers.application.getDc service.Name class='subtle'}}{{service.Name}}{{/link-to}}
<small>{{service.Name}}</small>
<div class="heading-helper">
<a class="subtle" href="#">{{item.checkMessage}}</a>
<a class="subtle" href="#">{{service.checkMessage}}</a>
</div>
</h4>
<ul class="list-inline">
{{#each node in item.Nodes }}
{{#each node in service.Nodes }}
<li>{{#link-to 'node' controllers.application.getDc node class='subtle'}}{{node}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
{{/each}}
</div>
<div class="col-md-6">
<div class="col-md-6 col-md-offset-1">
<div class="row">
{{#if model}}
<h2 class="no-margin">{{ model.1.ServiceName }}</h2>
<hr>
<h5>Nodes</h5>
{{#each node in model }}
<div {{bind-attr class=":panel node.hasFailingChecks:panel-warning:panel-success"}}>
<div class="panel-bar"></div>
<div class="panel-heading">
<h3 class="panel-title">
{{node.Node}}
<small>{{node.Address}}</small>
<span class="panel-note">{{node.checkMessage}}</span>
</h3>
</div>
<div class="panel-body">
{{#each check in node.Checks }}
<h4>{{ check.Name }} <small>{{ check.CheckID }}</small> <span class="pull-right"><small>{{check.Status}}</small></h4>
<hr>
{{/each}}
</div>
</div>
{{/each}}
{{/if}}
</div>
</div>
</script>

View File

@ -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']
})

View File

@ -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]);

View File

@ -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;

View File

@ -10,6 +10,7 @@
.topbar {
padding: 30px;
margin-bottom: 20px;
min-height: 100px;
border-bottom: 1px #eee solid;

View File

@ -66,6 +66,7 @@
}
&.panel-link:hover {
background-color: lighten($gray-background, 8%);
cursor: pointer;
background-color: lighten($gray-background, 8%);
}
}

View File

@ -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;