launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06808
[Merge] lp:~huwshimi/maas/dashboard-events into lp:maas
Huw Wilkins has proposed merging lp:~huwshimi/maas/dashboard-events into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~huwshimi/maas/dashboard-events/+merge/98576
This branch ties all the dashboard stuff together, including, animated chart, updating from events, hovers, design etc.
The tests are horrible, but hopefully they should test enough for now.
--
https://code.launchpad.net/~huwshimi/maas/dashboard-events/+merge/98576
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~huwshimi/maas/dashboard-events into lp:maas.
=== modified file 'src/maasserver/static/css/modifiers.css'
--- src/maasserver/static/css/modifiers.css 2012-03-05 05:52:32 +0000
+++ src/maasserver/static/css/modifiers.css 2012-03-21 05:48:06 +0000
@@ -22,3 +22,22 @@
vertical-align: text-bottom;
margin-right: 3px;
}
+/* Spacing */
+.space-top {
+ margin-top: 20px;
+ }
+.space-top-none {
+ margin-top: 0;
+ }
+.space-bottom-none {
+ margin-bottom: 0;
+ }
+.space-bottom-small {
+ margin-bottom: 5px;
+ }
+.pad-top {
+ margin-top: 20px;
+ }
+.pad-top-large {
+ margin-top: 40px;
+ }
=== modified file 'src/maasserver/static/css/typography.css'
--- src/maasserver/static/css/typography.css 2012-02-09 00:42:27 +0000
+++ src/maasserver/static/css/typography.css 2012-03-21 05:48:06 +0000
@@ -17,6 +17,10 @@
font-size: 22px;
line-height: 26px;
}
+h2.super-size {
+ font-size: 120px;
+ line-height: 100px;
+ }
h3 {
margin-top: 16px;
margin-bottom: 8px;
@@ -27,6 +31,15 @@
width: auto;
margin-bottom: 0.8em;
}
+p.large {
+ font-size: 24px;
+ }
+p.medium {
+ font-size: 16px;
+ }
+p.secondary {
+ color: #AEA79F;
+ }
pre, code, samp, tt, .console {
font-family: 'Ubuntu Mono', monospace;
margin-bottom: 0.8em;
=== modified file 'src/maasserver/static/js/node_views.js'
--- src/maasserver/static/js/node_views.js 2012-03-15 13:58:32 +0000
+++ src/maasserver/static/js/node_views.js 2012-03-21 05:48:06 +0000
@@ -144,45 +144,264 @@
*/
module.NodesDashboard = Y.Base.create(
'nodesDashboard', module.NodeListLoader, [], {
-
- plural_template: (
- '<h2>{nb_nodes} nodes in this cluster</h2><div id="chart" />'),
- singular_template: (
- '<h2>{nb_nodes} node in this cluster</h2><div id="chart" />'),
+ all_template: ('node{plural} in this MAAS'),
+ deployed_template: ('node{plural} deployed'),
+ commissioned_template: ('node{plural} commissioned'),
+ queued_template: ('node{plural} queued'),
+ offline_template: ('node{plural} offline'),
+ added_template: ('node{plural} added but never seen'),
+ reserved_template:
+ ('{nodes} node{plural} running without a registered service.'),
+ retired_template: ('{nodes} retired node{plural} not represented.'),
initializer: function(config) {
- this.append = config.append;
- // Prepare spinnerNode.
+ this.srcNode = config.srcNode;
+ this.summaryNode = Y.one(config.summaryNode);
+ this.numberNode = Y.one(config.numberNode);
+ this.descriptionNode = Y.one(config.descriptionNode);
+ this.reservedNode = Y.one(config.reservedNode);
+ this.retiredNode = Y.one(config.retiredNode);
+ this.deployed_nodes = 0;
+ this.commissioned_nodes = 0;
+ this.queued_nodes = 0;
+ this.reserved_nodes = 0;
+ this.offline_nodes = 0;
+ this.added_nodes = 0;
+ this.retired_nodes = 0;
+ this.fade_out = new Y.Anim({
+ node: this.summaryNode,
+ to: {opacity: 0},
+ duration: 0.1,
+ easing: 'easeIn'
+ });
+ this.fade_in = new Y.Anim({
+ node: this.summaryNode,
+ to: {opacity: 1},
+ duration: 0.2,
+ easing: 'easeIn'
+ });
+ // Prepare spinnerNode.
this.spinnerNode = Y.Node.create('<img />')
.set('src', MAAS_config.uris.statics + 'img/spinner.gif');
+ // Set up the chart
+ this.chart = new Y.maas.nodes_chart.NodesChartWidget({
+ node_id: 'chart',
+ width: 300
+ });
+
+ // Set up the event listeners for node changes
+ Y.on('Node.updated', function(e, widget) {
+ widget.updateNode('updated', e.instance);
+ }, null, this);
+
+ Y.on('Node.created', function(e, widget) {
+ widget.updateNode('created', e.instance);
+ }, null, this);
+
+ Y.on('Node.deleted', function(e, widget) {
+ widget.updateNode('deleted', e.instance);
+ }, null, this);
+
+ // Set up the hovers for changing the dashboard text
+ var events = [
+ {event: 'hover.offline.over', template: this.offline_template},
+ {event: 'hover.offline.out'},
+ {event: 'hover.added.over', template: this.added_template},
+ {event: 'hover.added.out'},
+ {event: 'hover.deployed.over', template: this.deployed_template},
+ {event: 'hover.deployed.out'},
+ {
+ event: 'hover.commissioned.over',
+ template: this.commissioned_template
+ },
+ {event: 'hover.commissioned.out'},
+ {event: 'hover.queued.over', template: this.queued_template},
+ {event: 'hover.queued.out'}
+ ];
+ for (var ev in events) {
+ this.chart.on(events[ev].event, function(e, template, widget) {
+ if (Y.Lang.isValue(e.nodes)) {
+ widget.setSummary(true, e.nodes, template, true);
+ }
+ else {
+ // Set the text to the default
+ widget.setSummary(true);
+ }
+ }, null, events[ev].template, this);
+ }
},
/**
- * Display a dashboard of the nodes (right now a simple count).
+ * Display a dashboard of the nodes.
*
* @method display
*/
display: function () {
- var size = this.modelList.size();
- var template = (size === 1) ?
- this.singular_template : this.plural_template;
- Y.one(this.container).setContent(
- Y.Lang.sub(template, {nb_nodes: size}));
-
- if (!this.container.inDoc()) {
- Y.one(this.append).empty().append(this.container, 0);
+ /* Set up the initial node/status counts. This needs to happen here
+ so that this.modelList exists.
+ */
+ if (!Y.Lang.isValue(this.nodes)) {
+ this.nodes = {};
+ for (var i=0; i<this.modelList.size(); i++) {
+ var node = this.modelList.item(i);
+ var status = node.get('status');
+ this.updateStatus('add', status);
+ this.nodes[node.get('system_id')] = node.get('status');
+ }
}
+ // Update the chart with the new node/status counts
+ this.chart.updateChart();
+ // Set the default text on the dashboard
+ this.setSummary(false);
+ this.setNodeText(
+ this.reservedNode, this.reserved_template, this.reserved_nodes);
+ this.setNodeText(
+ this.retiredNode, this.retired_template, this.retired_nodes);
},
loadNodesStarted: function() {
- Y.one(this.append).insert(this.spinnerNode, 0);
+ Y.one(this.srcNode).insert(this.spinnerNode, 0);
},
loadNodesEnded: function() {
this.spinnerNode.remove();
+ },
+
+ /**
+ * Update the nodes in the chart.
+ */
+ updateNode: function(action, node) {
+ var update_chart = false;
+ if (action == 'created') {
+ this.nodes[node.system_id] = node.status;
+ update_chart = this.updateStatus('add', node.status);
+ }
+ else if (action == 'deleted') {
+ delete this.nodes[node.system_id];
+ update_chart = this.updateStatus('remove', node.status);
+ }
+ else if (action == 'updated') {
+ previous_status = this.nodes[node.system_id];
+ this.nodes[node.system_id] = node.status;
+ update_remove = this.updateStatus('remove', previous_status);
+ update_add = this.updateStatus('add', node.status);
+ if (update_remove || update_add) {
+ update_chart = true;
+ }
+ }
+
+ if (update_chart) {
+ // Update the chart with the new node/status counts
+ this.chart.updateChart();
+ }
+
+ if (action != 'updated') {
+ /* Set the default text on the dashboard. We only need to do this
+ if the total number of nodes has changed.
+ */
+ this.setSummary(true);
+ }
+ },
+
+ /**
+ * Update the number of nodes for a status.
+ */
+ updateStatus: function(action, status) {
+ var update_chart = false;
+ /* This seems like an ugly way to calculate the change, but it stops
+ duplication of checking for the action for each status.
+ */
+ if (action == 'add') {
+ var node_counter = 1;
+ }
+ else if (action == 'remove') {
+ var node_counter = -1;
+ }
+
+ /* TODO: The commissioned status currently doesn't exist, but once it
+ does it should be added here too.
+ */
+ if (status == 0) {
+ // Added nodes
+ this.added_nodes += node_counter;
+ this.chart.set('added_nodes', this.added_nodes);
+ update_chart = true;
+ }
+ else if (status == 1 || status == 2 || status == 3) {
+ // Offline nodes
+ this.offline_nodes += node_counter;
+ this.chart.set('offline_nodes', this.offline_nodes);
+ update_chart = true;
+ }
+ else if (status == 4) {
+ // Queued nodes
+ this.queued_nodes += node_counter;
+ this.chart.set('queued_nodes', this.queued_nodes);
+ update_chart = true;
+ }
+ else if (status == 5) {
+ // Reserved nodes
+ this.reserved_nodes += node_counter;
+ this.setNodeText(
+ this.reservedNode,
+ this.reserved_template,
+ this.reserved_nodes
+ );
+ }
+ else if (status == 6) {
+ // Deployed nodes
+ this.deployed_nodes += node_counter;
+ this.chart.set('deployed_nodes', this.deployed_nodes);
+ update_chart = true;
+ }
+ else if (status == 7) {
+ // Retired nodes
+ this.retired_nodes += node_counter;
+ this.setNodeText(
+ this.retiredNode, this.retired_template, this.retired_nodes);
+ }
+
+ return update_chart;
+ },
+
+ /**
+ * Set the text for the number of nodes for a status.
+ */
+ setSummary: function(animate, nodes, template) {
+ // By default we just want to display the total nodes.
+ if (!nodes || !template) {
+ nodes = this.modelList.size();
+ template = this.all_template;
+ }
+ plural = (nodes === 1) ? '' : 's';
+ text = Y.Lang.sub(template, {plural: plural})
+
+ if (animate) {
+ this.fade_out.run();
+ this.fade_out.on('end', function (e, self, nodes, text) {
+ self.numberNode.setContent(nodes);
+ self.descriptionNode.setContent(text);
+ self.fade_in.run();
+ }, null, this, nodes, text);
+ }
+ else {
+ this.numberNode.setContent(nodes);
+ this.descriptionNode.setContent(text);
+ }
+ },
+
+ /**
+ * Set the text from a template for a DOM node.
+ */
+ setNodeText: function(element, template, nodes) {
+ plural = (nodes === 1) ? '' : 's';
+ text = Y.Lang.sub(template, {plural: plural, nodes: nodes})
+ element.setContent(text);
}
});
-}, '0.1', {'requires': ['view', 'io', 'maas.node', 'maas.node_add']}
+}, '0.1', {'requires': [
+ 'view', 'io', 'maas.node', 'maas.node_add', 'maas.nodes_chart',
+ 'maas.morph', 'anim']}
);
=== modified file 'src/maasserver/static/js/nodes_chart.js'
--- src/maasserver/static/js/nodes_chart.js 2012-03-19 07:13:21 +0000
+++ src/maasserver/static/js/nodes_chart.js 2012-03-21 05:48:06 +0000
@@ -157,6 +157,8 @@
var outer_nodes = [
{
nodes: deployed_nodes,
+ name: 'deployed_nodes',
+ colour: OUTER_COLOURS[0],
events: {
over: 'hover.deployed.over',
out: 'hover.deployed.out'
@@ -164,6 +166,8 @@
},
{
nodes: commissioned_nodes,
+ name: 'commissioned_nodes',
+ colour: OUTER_COLOURS[2],
events: {
over: 'hover.commissioned.over',
out: 'hover.commissioned.out'
@@ -171,6 +175,8 @@
},
{
nodes: queued_nodes,
+ name: 'queued_nodes',
+ colour: OUTER_COLOURS[1],
events: {
over: 'hover.queued.over',
out: 'hover.queued.out'
@@ -203,24 +209,24 @@
var slice = r.path();
slice.attr({
segment: segment,
- fill: OUTER_COLOURS[i],
+ fill: outer_nodes[i].colour,
stroke: STROKE_COLOUR,
'stroke-width': STROKE_WIDTH
});
Y.one(slice.node).on(
'hover',
+ function(e, over, out, name, widget) {
+ widget.fire(over, {nodes: widget.get(name)});
+ },
function(e, over, out, nodes, widget) {
- widget.fire(over, {nodes: nodes});
- },
- function(e, over, out, nodes, widget) {
- widget.fire(out);
- },
- null,
- outer_nodes[i].events.over,
- outer_nodes[i].events.out,
- outer_nodes[i].nodes,
- this
- );
+ widget.fire(out);
+ },
+ null,
+ outer_nodes[i].events.over,
+ outer_nodes[i].events.out,
+ outer_nodes[i].name,
+ this
+ );
this._outer_paths.push(slice);
}
else {
@@ -243,14 +249,15 @@
Y.one(this._offline_circle[0].node).on(
'hover',
function(e, widget) {
- widget.fire(
- 'hover.offline.over', {nodes: offline_nodes});
- },
- function(e, widget) {
- widget.fire('hover.offline.out');
- },
- null,
- this);
+ widget.fire(
+ 'hover.offline.over',
+ {nodes: widget.get('offline_nodes')});
+ },
+ function(e, widget) {
+ widget.fire('hover.offline.out');
+ },
+ null,
+ this);
}
}
else {
@@ -272,13 +279,15 @@
Y.one(this._added_circle[0].node).on(
'hover',
function(e, widget) {
- widget.fire('hover.added.over', {nodes: added_nodes});
- },
- function(e, widget) {
- widget.fire('hover.added.out');
- },
- null,
- this);
+ widget.fire(
+ 'hover.added.over',
+ {nodes: widget.get('added_nodes')});
+ },
+ function(e, widget) {
+ widget.fire('hover.added.out');
+ },
+ null,
+ this);
}
else {
if (added_nodes != total_nodes) {
@@ -293,19 +302,8 @@
},
initializer: function(cfg) {
- /* Publish the hover events. */
- this.publish('hover.offline.over');
- this.publish('hover.offline.out');
- this.publish('hover.added.over');
- this.publish('hover.added.out');
- this.publish('hover.deployed.over');
- this.publish('hover.deployed.out');
- this.publish('hover.commissioned.over');
- this.publish('hover.commissioned.out');
- this.publish('hover.queued.over');
- this.publish('hover.queued.out');
-
- r = Raphael(this.get('node_id'));
+ canvas_size = this.get('width') + STROKE_WIDTH * 2;
+ r = Raphael(this.get('node_id'), canvas_size, canvas_size);
r.customAttributes.segment = function (x, y, r, a1, a2) {
var flag = (a2 - a1) > 180;
if (a1 == 0 && a2 == 360) {
=== modified file 'src/maasserver/static/js/tests/test_node_views.html'
--- src/maasserver/static/js/tests/test_node_views.html 2012-03-15 13:58:32 +0000
+++ src/maasserver/static/js/tests/test_node_views.html 2012-03-21 05:48:06 +0000
@@ -4,12 +4,14 @@
<title>Test maas.node_views</title>
<!-- YUI and test setup -->
+ <script type="text/javascript" src="../../jslibs/raphael/raphael-min.js"></script>
<script type="text/javascript" src="../testing/yui_test_conf.js"></script>
<script type="text/javascript" src="../../jslibs/yui/tests/build/yui/yui.js"></script>
<script type="text/javascript" src="../testing/testrunner.js"></script>
<script type="text/javascript" src="../testing/testing.js"></script>
<script type="text/javascript" src="../node.js"></script>
<script type="text/javascript" src="../node_add.js"></script>
+ <script type="text/javascript" src="../nodes_chart.js"></script>
<!-- The module under test -->
<script type="text/javascript" src="../node_views.js"></script>
<!-- The test suite -->
@@ -27,6 +29,14 @@
</head>
<body>
<span id="suite">maas.node_views.tests</span>
- <div id="placeholder"></div>
+ <div id="dashboard">
+ <div id="chart"></div>
+ <div id="summary">
+ <h2 id="nodes-number"></h2>
+ <p id="nodes-description"></p>
+ </div>
+ <p id="reserved-nodes"></p>
+ <p id="retired-nodes"></p>
+ </div>
</body>
</html>
=== modified file 'src/maasserver/static/js/tests/test_node_views.js'
--- src/maasserver/static/js/tests/test_node_views.js 2012-03-15 13:58:32 +0000
+++ src/maasserver/static/js/tests/test_node_views.js 2012-03-21 05:48:06 +0000
@@ -66,39 +66,349 @@
suite.add(new Y.maas.testing.TestCase({
name: 'test-node-views-NodeDashBoard',
+ setUp : function () {
+ this.data = [
+ {system_id: 'sys1', hostname: 'host1', status: 0},
+ {system_id: 'sys2', hostname: 'host2', status: 0},
+ {system_id: 'sys3', hostname: 'host3', status: 1},
+ {system_id: 'sys4', hostname: 'host4', status: 2},
+ {system_id: 'sys5', hostname: 'host5', status: 2},
+ {system_id: 'sys6', hostname: 'host6', status: 3},
+ {system_id: 'sys7', hostname: 'host7', status: 4},
+ {system_id: 'sys8', hostname: 'host8', status: 4},
+ {system_id: 'sys9', hostname: 'host9', status: 5},
+ {system_id: 'sys10', hostname: 'host10', status: 5},
+ {system_id: 'sys11', hostname: 'host11', status: 5},
+ {system_id: 'sys12', hostname: 'host12', status: 6},
+ {system_id: 'sys13', hostname: 'host13', status: 7}
+ ];
+ },
+
+ testInitializer: function() {
+ var view = create_dashboard_view(this.data, this);
+ this.addCleanup(function() { view.destroy(); });
+ view.render();
+ Y.Assert.areNotEqual(
+ '',
+ Y.one('#chart').get('text'),
+ 'The chart node should have been populated');
+
+ // Chart hovers should be set up
+ Y.one(view.chart._offline_circle[0].node).simulate('mouseover');
+ this.wait(function() {
+ Y.Assert.areEqual(
+ '4',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of offline nodes should be set');
+ Y.Assert.areEqual(
+ 'nodes offline',
+ Y.one('#nodes-description').get('text'),
+ 'The text should be set with nodes as a plural');
+ }, 500);
+
+ Y.one(view.chart._offline_circle[0].node).simulate('mouseout');
+ this.wait(function() {
+ Y.Assert.areEqual(
+ '13',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ Y.Assert.areEqual(
+ 'nodes in this MAAS',
+ Y.one('#nodes-description').get('text'),
+ 'The default text should be set');
+ }, 500);
+ },
+
testDisplay: function() {
- var response = Y.JSON.stringify([
- {system_id: '3', hostname: 'dan'},
- {system_id: '4', hostname: 'dee'}
- ]);
- this.mockSuccess(response, module);
- var view = new Y.maas.node_views.NodesDashboard(
- {append: '#placeholder'});
+ var view = create_dashboard_view(this.data, this);
this.addCleanup(function() { view.destroy(); });
view.render();
- Y.Assert.areEqual(
- '2 nodes in this cluster',
- Y.one('#placeholder').get('text'));
+ for (var node in this.data){
+ Y.Assert.areEqual(
+ this.data[node].status,
+ view.nodes[this.data[node].system_id],
+ 'The list of nodes should have been populated');
+ }
+ Y.Assert.areEqual(
+ '13',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ Y.Assert.areEqual(
+ 'nodes in this MAAS',
+ Y.one('#nodes-description').get('text'),
+ 'The summary text should be set');
+ Y.Assert.areEqual(
+ '3 nodes running without a registered service.',
+ Y.one('#reserved-nodes').get('text'),
+ 'The reserved text should be set');
+ Y.Assert.areEqual(
+ '1 retired node not represented.',
+ Y.one('#retired-nodes').get('text'),
+ 'The retired text should be set');
},
- testDisplayUpdate: function() {
- // The display is updated when new nodes are added.
- this.mockSuccess(Y.JSON.stringify([]), module);
- var view = new Y.maas.node_views.NodesDashboard(
- {append: '#placeholder'});
+ testUpdateNode: function() {
+ /* TODO: check updateNode is fired by Node.created, Node.updated
+ and Node.deleted.
+ */
+ var view = create_dashboard_view(this.data, this);
+ var node = {system_id: 'sys14', hostname: 'host14', status: 0};
this.addCleanup(function() { view.destroy(); });
view.render();
+ Y.Assert.areEqual(
+ '13',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ // Check node creation
+ Y.Assert.areEqual(
+ 2,
+ view.added_nodes,
+ 'Check the initial number of nodes for the status');
Y.maas.node_add.AddNodeDispatcher.fire(
- Y.maas.node_add.NODE_ADDED_EVENT, {},
- {system_id: '4', hostname: 'dan'});
- Y.Assert.areEqual(1, view.modelList.size());
- Y.Assert.areEqual(
- '1 node in this cluster',
- Y.one('#placeholder').get('text'));
+ Y.maas.node_add.NODE_ADDED_EVENT, {}, node);
+ view.updateNode('created', node);
+ Y.Assert.areEqual(
+ 0,
+ view.nodes['sys14'],
+ 'The node and status should be recorded');
+ Y.Assert.areEqual(
+ 3,
+ view.added_nodes,
+ 'The status should have one extra node');
+ Y.Assert.areEqual(
+ 3,
+ view.chart.get('added_nodes'),
+ 'The chart status number should also be updated');
+ this.wait(function() {
+ Y.Assert.areEqual(
+ '14',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should have been updated');
+ }, 500);
+ // Check node updating
+ node.status = 6;
+ Y.Assert.areEqual(
+ 1,
+ view.deployed_nodes,
+ 'Check the initial number of nodes for the new status');
+ view.updateNode('updated', node);
+ Y.Assert.areEqual(
+ 6,
+ view.nodes['sys14'],
+ 'The node status should have been updated');
+ Y.Assert.areEqual(
+ 2,
+ view.deployed_nodes,
+ 'The new status should have one extra node');
+ Y.Assert.areEqual(
+ 2,
+ view.chart.get('deployed_nodes'),
+ 'The new chart status number should also be updated');
+ Y.Assert.areEqual(
+ 2,
+ view.added_nodes,
+ 'The old status should have one less node');
+ Y.Assert.areEqual(
+ 2,
+ view.chart.get('added_nodes'),
+ 'The old chart status number should also be updated');
+ this.wait(function() {
+ Y.Assert.areEqual(
+ Y.one('#nodes-number').get('text'),
+ '14',
+ 'The total number of nodes should not have been updated');
+ }, 500);
+
+ // Check node deleting
+ view.updateNode('deleted', node);
+ Y.Assert.isUndefined(
+ view.nodes['sys14'],
+ 'The node status should have been deleted');
+ Y.Assert.areEqual(
+ 1,
+ view.deployed_nodes,
+ 'The status should have one less node');
+ Y.Assert.areEqual(
+ 1,
+ view.chart.get('deployed_nodes'),
+ 'The chart status number should also be updated');
+ this.wait(function() {
+ Y.Assert.areEqual(
+ '13',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should have been updated');
+ }, 500);
+ },
+
+ testUpdateStatus: function() {
+ var view = create_dashboard_view(this.data, this);
+ this.addCleanup(function() { view.destroy(); });
+ view.render();
+ // Add a node to a status that also updates the chart
+ Y.Assert.areEqual(
+ 2,
+ view.added_nodes,
+ 'Check the initial number of nodes for the status');
+ var result = view.updateStatus('add', 0);
+ Y.Assert.areEqual(
+ 3,
+ view.added_nodes,
+ 'The status should have one extra node');
+ Y.Assert.areEqual(
+ 3,
+ view.chart.get('added_nodes'),
+ 'The chart status number should also be updated');
+ Y.Assert.isTrue(
+ result,
+ 'This status needs to update the chart, so it should return true');
+ // Remove a node from a status
+ result = view.updateStatus('remove', 0);
+ Y.Assert.areEqual(
+ 2,
+ view.added_nodes,
+ 'The status should have one less node');
+ Y.Assert.areEqual(
+ 2,
+ view.chart.get('added_nodes'),
+ 'The chart status number should also be updated');
+ // Check a status that also updates text
+ Y.Assert.areEqual(
+ 3,
+ view.reserved_nodes,
+ 'Check the initial number of nodes for the reserved status');
+ result = view.updateStatus('add', 5);
+ Y.Assert.areEqual(
+ 4,
+ view.reserved_nodes,
+ 'The status should have one extra node');
+ Y.Assert.areEqual(
+ '4 nodes running without a registered service.',
+ Y.one('#reserved-nodes').get('text'),
+ 'The dashboard reserved text should be updated');
+ Y.Assert.isFalse(
+ result,
+ 'This status should not to update the chart');
+ },
+
+ testSetSummary: function() {
+ // Test the default summary, with more than one node
+ var data = [
+ {system_id: 'sys9', hostname: 'host9', status: 5}
+ ];
+ var view = create_dashboard_view(data, this);
+ this.addCleanup(function() { view.destroy(); });
+ view.render();
+ view.setSummary(false);
+ Y.Assert.areEqual(
+ '1',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ Y.Assert.areEqual(
+ 'node in this MAAS',
+ Y.one('#nodes-description').get('text'),
+ 'The text should be set with nodes as singular');
+
+ // Test the default summary, with one node
+ view = create_dashboard_view(this.data, this);
+ view.render();
+ view.setSummary(false);
+ Y.Assert.areEqual(
+ '13',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ Y.Assert.areEqual(
+ 'nodes in this MAAS',
+ Y.one('#nodes-description').get('text'),
+ 'The text should be set with nodes as a plural');
+
+ // Test the animation runs if we want it too
+ var fade_out_anim = false;
+ var fade_in_anim = false;
+ view.fade_out.on('end', function() {
+ fade_out_anim = true;
+ });
+ view.fade_in.on('end', function() {
+ fade_in_anim = true;
+ });
+ view.setSummary(true);
+ this.wait(function() {
+ Y.Assert.isTrue(
+ fade_out_anim,
+ 'The fade out animation should have run');
+ Y.Assert.isTrue(
+ fade_in_anim,
+ 'The fade in animation should have run');
+ }, 500);
+
+ // Test the animation doesn't run if we don't want it too
+ fade_out_anim = false;
+ fade_in_anim = false;
+ view.setSummary(false);
+ this.wait(function() {
+ Y.Assert.isFalse(
+ fade_out_anim,
+ 'The fade out animation should not have run');
+ Y.Assert.isFalse(
+ fade_in_anim,
+ 'The fade in animation should not have run');
+ }, 500);
+
+ // Test we can set the summary for a particular status (multiple nodes)
+ view = create_dashboard_view(this.data, this);
+ view.render();
+ view.setSummary(false, 1, view.queued_template);
+ Y.Assert.areEqual(
+ '1',
+ Y.one('#nodes-number').get('text'),
+ 'The total number of nodes should be set');
+ Y.Assert.areEqual(
+ 'node queued',
+ Y.one('#nodes-description').get('text'),
+ 'The text should be set with nodes as a plural');
+ },
+
+ testSetNodeText: function() {
+ var view = create_dashboard_view(this.data, this);
+ this.addCleanup(function() { view.destroy(); });
+ view.render();
+ view.setNodeText(
+ view.reservedNode, view.reserved_template, view.reserved_nodes);
+ Y.Assert.areEqual(
+ '3 nodes running without a registered service.',
+ Y.one('#reserved-nodes').get('text'),
+ 'The text should be set with nodes as a plural');
+
+ var data = [
+ {system_id: 'sys9', hostname: 'host9', status: 5}
+ ];
+ view = create_dashboard_view(data, this);
+ view.render();
+ view.setNodeText(
+ view.reservedNode, view.reserved_template, view.reserved_nodes);
+ Y.Assert.areEqual(
+ '1 node running without a registered service.',
+ Y.one('#reserved-nodes').get('text'),
+ 'The text should be set with nodes as singular');
+ },
+
+ tearDown : function () {
+ Y.one('#chart').set('text', '');
}
-
}));
+function create_dashboard_view(data, self) {
+ var response = Y.JSON.stringify(data);
+ self.mockSuccess(response, module);
+ var view = new Y.maas.node_views.NodesDashboard({
+ srcNode: '#dashboard',
+ summaryNode: '#summary',
+ numberNode: '#nodes-number',
+ descriptionNode: '#nodes-description',
+ reservedNode: '#reserved-nodes',
+ retiredNode: '#retired-nodes'});
+ return view;
+}
+
namespace.suite = suite;
=== modified file 'src/maasserver/templates/maasserver/index.html'
--- src/maasserver/templates/maasserver/index.html 2012-03-19 03:24:16 +0000
+++ src/maasserver/templates/maasserver/index.html 2012-03-21 05:48:06 +0000
@@ -13,52 +13,26 @@
<script type="text/javascript">
<!--
YUI().use(
- 'maas.node_add', 'maas.node','maas.node_views', 'maas.utils', 'maas.nodes_chart',
+ 'maas.node_add', 'maas.node','maas.node_views', 'maas.utils',
'maas.longpoll',
function (Y) {
Y.on('load', function() {
// Create Dashboard view.
- var view_container = Y.Node.create('<div />')
- .set('id', 'dashboard');
- Y.one('#content').append(view_container);
- var view = new Y.maas.node_views.NodesDashboard(
- {'append': '#dashboard'});
- view.render(view_container);
- // Create 'Add node' link.
- var add_node_link = Y.Node.create('<a />')
- .set('id', 'addnode')
- .set('text', "Add node")
- .set('href', '#')
- .addClass('button');
- Y.one('#content').append(add_node_link);
- add_node_link.on('click', Y.maas.node_add.showAddNodeWidget);
+ var view = new Y.maas.node_views.NodesDashboard({
+ srcNode: '#dashboard',
+ summaryNode: '#summary',
+ numberNode: '#nodes-number',
+ descriptionNode: '#nodes-description',
+ reservedNode: '#reserved-nodes',
+ retiredNode: '#retired-nodes'});
+ view.render();
+ Y.one('#addnode').on('click', Y.maas.node_add.showAddNodeWidget);
// Setup TitleEditWidget.
var title_widget = new Y.maas.utils.TitleEditWidget(
{srcNode: '.page-title-form'});
title_widget.render();
-
- // Show the chart
- var cfg = {
- node_id: 'chart',
- width: 300
- };
- var chart = new Y.maas.nodes_chart.NodesChartWidget(cfg);
-
- // Sample event listeners.
- Y.on("Node.updated", function() {
- Y.log("node updated");
- });
-
- Y.on("Node.created", function() {
- Y.log("node created");
- });
-
- Y.on("Node.deleted", function() {
- Y.log("node deleted");
- });
-
// Start longpoll.
{% if longpoll_queue and LONGPOLL_PATH %}
Y.later(0, Y.maas.longpoll, function() {
@@ -85,5 +59,16 @@
{% endblock %}
{% block content %}
- <div id="chart"></div>
+ <div id="dashboard" class="pad-top">
+ <div id="chart" class="block size6"></div>
+ <div class="block block size8">
+ <div id="summary">
+ <h2 id="nodes-number" class="super-size pad-top-large"></h2>
+ <p id="nodes-description" class="large"></p>
+ </div>
+ <p id="reserved-nodes" class="medium space-bottom-small"></p>
+ <p id="retired-nodes" class="secondary medium space-top-none"></p>
+ <a href="#" id="addnode" class="button right space-top">Add node</a>
+ </div>
+ </div>
{% endblock %}
Follow ups