Skip to content
Browse files

Add ability to stop and down nodes.

  • Loading branch information...
1 parent c42c1b1 commit fd6eb8a502058c48de3a3b9ed60c78d513a8238d @cmeiklejohn cmeiklejohn committed Apr 15, 2013
View
8 Makefile
@@ -27,14 +27,14 @@ docs:
./rebar skip_deps=true doc
APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \
- xmerl webtool eunit syntax_tools compiler
+ xmerl webtool eunit syntax_tools compiler mnesia public_key snmp
PLT = $(HOME)/.riak_control_dialyzer_plt
check_plt: compile
- dialyzer --check_plt --plt $(PLT) --apps $(APPS)
+ dialyzer --check_plt --plt $(PLT) --apps $(APPS) deps/webmachine/ebin deps/mochiweb/ebin deps/erlydtl/ebin
build_plt: compile
- dialyzer --build_plt --output_plt $(PLT) --apps $(APPS)
+ dialyzer --build_plt --output_plt $(PLT) --apps $(APPS) deps/webmachine/ebin deps/mochiweb/ebin deps/erlydtl/ebin
dialyzer: compile
@echo
@@ -44,7 +44,7 @@ dialyzer: compile
@sleep 1
dialyzer -Wno_return -Wunmatched_returns --plt $(PLT) ebin
-cleanplt:
+clean_plt:
@echo
@echo "Are you sure? It takes about 1/2 hour to re-build."
@echo Deleting $(PLT) in 5 seconds.
View
335 priv/admin/css/compiled/style.css
@@ -307,6 +307,9 @@ th {
#nav-support a {
background-position: 14px -300px;
}
+#nav-nodes a {
+ background-position: 15px -392px;
+}
.nav-item {
width: 63px;
height: 49px;
@@ -888,6 +891,9 @@ input.gui-point-button-right:active {
#nav-cluster a {
background-position: 8px 11px;
}
+ #nav-nodes a {
+ background-position: 7px -269px;
+ }
#nav-ring a {
background-position: 8px -24px;
}
@@ -1591,6 +1597,313 @@ input.gui-point-button-right:active {
display: none;
}
}
+#nodes-page .right {
+ float: right;
+}
+#nodes-page .gui-point-button-right,
+#nodes-page .gui-rect-button {
+ margin-right: 10px;
+ margin-top: 35px;
+}
+#nodes-page #current-area {
+ max-width: 850px;
+ margin: 0 auto 45px auto;
+ padding-top: 40px;
+}
+#nodes-page #current-area > span.gui-text-flat {
+ line-height: 1.5;
+ display: block;
+}
+#nodes-page h3 {
+ font-family: 'noticia', georgia, serif;
+}
+#nodes-page .spinner-box {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ text-align: center;
+ background: rgba(255,255,255,0.05);
+ padding: 25px;
+}
+#nodes-page .spinner-box img,
+#nodes-page .spinner-box h4 {
+ vertical-align: middle;
+}
+#nodes-page .spinner-box h4 {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+ display: inline-block;
+ padding-top: 6px;
+ padding-left: 10px;
+ line-height: 1.2;
+}
+#nodes-page .gui-light {
+ vertical-align: middle;
+ padding: 9px 5px 9px 14px;
+ margin-left: -6px;
+ margin-top: -7px;
+}
+#nodes-page .field-container {
+ width: 86%;
+ vertical-align: middle;
+}
+#nodes-page .gui-field {
+ margin-right: 0;
+ overflow: hidden;
+}
+#nodes-page .list-header {
+ min-height: 18px;
+}
+#nodes-page .list-header li {
+ float: left;
+ margin-bottom: 30px;
+}
+#nodes-page .list-header li h4 {
+ display: inline-block;
+}
+#nodes-page #node-list {
+ margin-top: 20px;
+}
+#nodes-page #node-list .item1 {
+ width: 6%;
+}
+#nodes-page #node-list .item2 {
+ width: 10%;
+}
+#nodes-page #node-list .item3 {
+ width: 56%;
+}
+#nodes-page #node-list .item4 {
+ width: 13%;
+}
+#nodes-page #node-list .item5 {
+ width: 15%;
+}
+#nodes-page .node {
+ margin-bottom: 10px;
+ position: relative;
+}
+#nodes-page .node > div {
+ float: left;
+}
+#nodes-page .node > div.clear {
+ float: none;
+}
+#nodes-page .node .gui-radio-wrapper {
+ width: 25px;
+ height: 24px;
+ text-align: center;
+ display: inline-block;
+ position: relative;
+}
+#nodes-page .node .gui-radio-wrapper .disabler {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ display: none;
+}
+#nodes-page .node .gui-radio-wrapper .disabler.show {
+ display: block;
+}
+#nodes-page .node .item1 .gui-radio-wrapper {
+ margin-left: 4px;
+}
+#nodes-page .node .item2 .gui-radio-wrapper {
+ margin-left: 8px;
+}
+#nodes-page .node input[type=radio] {
+ display: inline;
+ float: none;
+}
+#nodes-page .name-box {
+ overflow: visible;
+}
+#nodes-page .ring-pct,
+#nodes-page .used-memory,
+#nodes-page .action-name {
+ font-family: 'menlo', 'consolas', 'monaco', monospace;
+}
+#nodes-page .used-memory {
+ display: inline-block;
+ padding: 0 0 0 5px;
+ vertical-align: middle;
+}
+#nodes-page .pct-box {
+ margin-top: 8px;
+ display: inline-block;
+ vertical-align: middle;
+}
+#nodes-page .pct-arrows {
+ width: 19px;
+ height: 33px;
+ background: url("/admin/ui/images/pct-arrows-sprite.png") no-repeat transparent;
+ margin: 0 5px 0 0;
+ display: inline-block;
+ vertical-align: middle;
+}
+#nodes-page .pct-gaining {
+ background-position: -19px top;
+}
+#nodes-page .pct-losing {
+ background-position: -38px top;
+}
+#nodes-page .pct-static {
+ background-position: left top;
+}
+#nodes-page .green-pct-arrow {
+ width: 19px;
+ height: 33px;
+ background: url("/admin/ui/images/pct-arrows-sprite.png") no-repeat -57px top transparent;
+ margin: 0 5px 0 0;
+ display: none;
+}
+#nodes-page .memory-box {
+ width: 175px;
+}
+#nodes-page .membar-bg {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ width: 79px;
+ height: 30px;
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+#nodes-page .membar-fg {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ background: rgba(255,255,255,0.05);
+ background: -moz-linear-gradient(top, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(255,255,255,0.4)), color-stop(100%, rgba(255,255,255,0.05)));
+ background: -webkit-linear-gradient(top, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
+ background: -o-linear-gradient(top, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
+ background: -ms-linear-gradient(top, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
+ background: linear-gradient(top bottom, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0.05) 100%);
+ -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.18);
+ -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.18);
+ box-shadow: 0 1px 0 rgba(255,255,255,0.18);
+ width: 100%;
+ height: 26px;
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 75px;
+ z-index: 5;
+ border: 2px solid #1d1d1d;
+}
+#nodes-page .mem-colors {
+ width: 75px;
+ height: 26px;
+ float: left;
+ margin: 2px 0 0 2px;
+}
+#nodes-page .membar-mem {
+ width: auto;
+ height: 26px;
+ background: url("/admin/ui/images/mem-bar-overlay.png") left top repeat transparent;
+ float: left;
+ z-index: 1;
+}
+#nodes-page .non-erlang-mem {
+ width: auto;
+ height: 26px;
+ background: url("/admin/ui/images/mem-bar-overlay.png") left top repeat transparent;
+ float: left;
+ z-index: 1;
+ background-color: rgba(65,179,243,0.8);
+}
+#nodes-page .erlang-mem {
+ width: auto;
+ height: 26px;
+ background: url("/admin/ui/images/mem-bar-overlay.png") left top repeat transparent;
+ float: left;
+ z-index: 1;
+ background-color: rgba(70,166,55,0.8);
+}
+#nodes-page .unknown-mem {
+ width: auto;
+ height: 26px;
+ background: url("/admin/ui/images/mem-bar-overlay.png") left top repeat transparent;
+ float: left;
+ z-index: 1;
+ background-color: #404040;
+ width: 100%;
+}
+#nodes-page .slide-up {
+ margin-top: -35px;
+}
+#nodes-page .semi-transparent {
+ opacity: 0.32;
+ filter: alpha(opacity=32);
+}
+@media screen and (max-width : 800px) {
+ #nodes-page #node-list .item1 {
+ width: 8%;
+ }
+ #nodes-page #node-list .item2 {
+ width: 10%;
+ }
+ #nodes-page #node-list .item3 {
+ width: 49%;
+ }
+ #nodes-page #node-list .item4 {
+ width: 15%;
+ }
+ #nodes-page #node-list .item5 {
+ width: 18%;
+ }
+ #nodes-page #node-list .field-container {
+ width: 75%;
+ }
+}
+@media screen and (max-width : 670px) {
+ #nodes-page #node-list .memory-box {
+ padding-top: 7px;
+ }
+ #nodes-page #node-list .membar-bg {
+ display: none;
+ }
+}
+@media screen and (max-width : 570px) {
+ #nodes-page #node-list .item1 {
+ width: 12%;
+ }
+ #nodes-page #node-list .item2 {
+ width: 15%;
+ }
+ #nodes-page #node-list .item3 {
+ width: 53%;
+ }
+ #nodes-page #node-list .item4 {
+ display: none;
+ }
+ #nodes-page #node-list .item5 {
+ width: 20%;
+ }
+}
+@media screen and (max-width : 400px) {
+ #nodes-page #node-list .item1 {
+ width: 12%;
+ }
+ #nodes-page #node-list .item2 {
+ width: 15%;
+ }
+ #nodes-page #node-list .item3 {
+ width: 47%;
+ }
+ #nodes-page #node-list .item4 {
+ display: none;
+ }
+ #nodes-page #node-list .item5 {
+ width: 15%;
+ }
+}
#ring-page #ring-filter {
text-align: left;
padding-left: 25px;
@@ -1615,8 +1928,30 @@ input.gui-point-button-right:active {
#ring-page tr.no-highlight:hover {
background-color: transparent;
}
+@media screen and (max-width : 750px) {
+ #ring-page #ring-filter {
+ position: absolute;
+ left: 50%;
+ top: 100px;
+ width: 200px;
+ padding-left: 0;
+ float: none;
+ margin: 66px 0 0 -100px;
+ }
+ #ring-page .pagination.first {
+ margin-top: 60px;
+ }
+}
@media screen and (max-width : 649px) {
#ring-page .gui-light span {
display: none;
}
}
+@media screen and (max-width : 500px) {
+ #ring-page #ring-filter {
+ margin-top: 40px;
+ }
+ #ring-page .pagination.first {
+ margin-top: 30px;
+ }
+}
View
4 priv/admin/css/general.styl
@@ -107,6 +107,7 @@ th
#nav-graphs a { background-position : 14px -192px }
#nav-logs a { background-position : 14px -248px }
#nav-support a { background-position : 14px -300px }
+#nav-nodes a { background-position : 15px -392px }
.nav-item
dimensions(63px, 49px)
@@ -598,6 +599,9 @@ input.gui-point-button:active, input.gui-rect-button:active, input.gui-point-but
#nav-cluster a
background-position : 8px 11px
+ #nav-nodes a
+ background-position : 7px -269px
+
#nav-ring a
background-position : 8px -24px
View
284 priv/admin/css/nodes.styl
@@ -0,0 +1,284 @@
+
+/*
+CSS to be applied ONLY on the cluster page
+*/
+
+@import reusable
+
+
+#nodes-page
+
+ .right
+ float : right
+
+ .gui-point-button-right, .gui-rect-button
+ margin-right : 10px
+ margin-top : 35px
+
+ #current-area
+ max-width : 850px
+ margin : 0 auto 45px auto
+ padding-top : 40px
+
+ & > span.gui-text-flat
+ line-height : 1.5
+ display : block
+
+ h3
+ copy-font()
+
+ .spinner-box
+ corners()
+ text-align : center
+ background : rgba(255, 255, 255, .05)
+ padding : 25px
+
+ img, h4
+ vertical-align : middle
+
+ h4
+ opaque(.8)
+ display : inline-block
+ padding-top : 6px
+ padding-left : 10px
+ line-height : 1.2
+
+ .gui-light
+ vertical-align : middle
+ padding : 9px 5px 9px 14px
+ margin-left : -6px
+ margin-top : -7px
+
+ .field-container
+ width : 86%
+ vertical-align : middle
+
+ .gui-field
+ margin-right : 0
+ overflow : hidden
+
+ .list-header
+ min-height : 18px
+
+ li
+ float : left
+ margin-bottom : 30px
+
+ h4
+ display : inline-block
+
+ #node-list
+ margin-top : 20px
+
+ .item1
+ width : 6%
+ .item2
+ width : 10%
+ .item3
+ width : 56%
+ .item4
+ width : 13%
+ .item5
+ width : 15%
+
+
+
+
+
+ .node
+ margin-bottom : 10px
+ position : relative
+
+ .node > div
+ float : left
+
+ .node > div.clear
+ float : none
+
+ .node
+
+ .gui-radio-wrapper
+ dimensions(25px, 24px)
+ text-align : center
+ display : inline-block
+ position : relative
+
+ .disabler
+ dimensions(100%, 100%)
+ absoluteLeft(0, 0);
+ opaque(0)
+ display : none
+
+ &.show
+ display : block
+
+ .item1 .gui-radio-wrapper
+ margin-left : 4px
+
+ .item2 .gui-radio-wrapper
+ margin-left : 8px
+
+ input[type=radio]
+ display : inline
+ float : none
+
+ .name-box
+ overflow : visible
+
+ .ring-pct, .used-memory, .action-name
+ monospace();
+
+ .used-memory
+ display : inline-block
+ padding : 0 0 0 5px
+ vertical-align : middle
+
+ .pct-box
+ margin-top : 8px
+ display : inline-block
+ vertical-align : middle
+
+ .pct-arrows
+ dimensions(19px, 33px)
+ background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat transparent
+ margin : 0 5px 0 0
+ display : inline-block
+ vertical-align : middle
+
+ .pct-gaining
+ background-position : -19px top
+
+ .pct-losing
+ background-position : -38px top
+
+ .pct-static
+ background-position : left top
+
+ .green-pct-arrow
+ dimensions(19px, 33px)
+ background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat -57px top transparent
+ margin : 0 5px 0 0
+ display : none
+
+ .memory-box
+ width : 175px
+
+ .membar-bg
+ corners()
+ dimensions(79px, 30px)
+ position : relative
+ display : inline-block
+ vertical-align : middle
+
+ .membar-fg
+ corners()
+ gradient(rgba(255, 255, 255, .4), rgba(255, 255, 255, .05))
+ vendor('box-shadow', 0 1px 0 lightWhite)
+ dimensions(100%, 26px)
+ absoluteLeft(0, 0)
+ width : 75px
+ z-index : 5
+ border : 2px solid #1d1d1d
+
+ .mem-colors
+ dimensions(75px, 26px)
+ float : left
+ margin : 2px 0 0 2px
+
+ .membar-mem
+ dimensions(auto, 26px)
+ background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
+ float : left
+ z-index : 1
+
+ .non-erlang-mem
+ dimensions(auto, 26px)
+ background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
+ float : left
+ z-index : 1
+ background-color : rgba(65, 179, 243, .8)
+
+ .erlang-mem
+ dimensions(auto, 26px)
+ background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
+ float : left
+ z-index : 1
+ background-color : rgba(70, 166, 55, .8)
+
+ .unknown-mem
+ dimensions(auto, 26px)
+ background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
+ float : left
+ z-index : 1
+ background-color : #404040
+ width : 100%
+
+
+ .slide-up
+ margin-top : -35px
+
+ .semi-transparent
+ opaque(.32)
+
+
+ /* RESPONSIVE MEDIA QUERIES */
+
+ @media screen and (max-width : 800px)
+
+ #node-list
+ .item1
+ width : 8%
+ .item2
+ width : 10%
+ .item3
+ width : 49%
+ .item4
+ width : 15%
+ .item5
+ width : 18%
+
+ .field-container
+ width : 75%
+
+
+ @media screen and (max-width : 670px)
+
+ #node-list
+ .memory-box
+ padding-top : 7px
+
+ .membar-bg
+ display : none
+
+ @media screen and (max-width : 570px)
+
+ #node-list
+ .item1
+ width : 12%
+ .item2
+ width : 15%
+ .item3
+ width : 53%
+ .item4
+ display : none
+ .item5
+ width : 20%
+
+ @media screen and (max-width : 400px)
+
+ #node-list
+ .item1
+ width : 12%
+ .item2
+ width : 15%
+ .item3
+ width : 47%
+ .item4
+ display : none
+ .item5
+ width : 15%
+
+
+
+
+
View
20 priv/admin/css/ring.styl
@@ -32,9 +32,29 @@ CSS to be applied ONLY on the snapshot page
/* RESPONSIVE MEDIA QUERIES */
+ @media screen and (max-width : 750px)
+
+ #ring-filter
+ absoluteLeft(50%, 100px)
+ width : 200px
+ padding-left : 0
+ float : none
+ margin : 66px 0 0 -100px
+
+ .pagination.first
+ margin-top : 60px
+
@media screen and (max-width : 649px)
.gui-light span
display : none
+ @media screen and (max-width : 500px)
+
+ #ring-filter
+ margin-top : 40px
+
+ .pagination.first
+ margin-top : 30px
+
/* END RESPONSIVE MEDIA QUERIES */
View
1 priv/admin/css/style.styl
@@ -10,4 +10,5 @@ Imports and arranges all other stylus files.
@import general
@import snapshot
@import cluster
+@import nodes
@import ring
View
BIN priv/admin/images/nav-icons-small.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN priv/admin/images/nav-icons.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2 priv/admin/js/app.js
@@ -67,8 +67,10 @@ minispade.register('app', function() {
minispade.require('core');
minispade.require('router');
+ minispade.require('shared');
minispade.require('snapshot');
minispade.require('cluster');
+ minispade.require('nodes');
minispade.require('ring');
});
View
313 priv/admin/js/cluster.js
@@ -52,6 +52,10 @@ minispade.register('cluster', function() {
* wrapper around the legacy cluster page until we rewrite it.
*/
RiakControl.ClusterController = Ember.ObjectController.extend(
+ /**
+ * Shares properties with RiakControl.NodesController
+ */
+ RiakControl.ClusterAndNodeControls,
/** @scope RiakControl.ClusterController.prototype */ {
/**
@@ -158,36 +162,6 @@ minispade.register('cluster', function() {
},
/**
- * Called by the router to start the polling interval when the page
- * is selected.
- *
- * @returns {void}
- */
- startInterval: function() {
- this._intervalId = setInterval($.proxy(this.reload, this),
- RiakControl.refreshInterval);
- },
-
- /**
- * Called by the router to stop the polling interval when the page
- * is navigated away from.
- *
- * @returns {void}
- */
- cancelInterval: function() {
- if(this._intervalId) {
- clearInterval(this._intervalId);
- }
- },
-
- /**
- * If content is loading, return true.
- *
- * @returns {boolean}
- */
- isLoading: false,
-
- /**
* Holds a boolean tracking if the ring is legacy.
*/
legacyRing: false,
@@ -198,11 +172,6 @@ minispade.register('cluster', function() {
ringNotReady: false,
/**
- * Holds the most recent error message.
- */
- errorMessage: '',
-
- /**
* Return nodes from the current cluster which have not been deleted.
*
* @returns {Ember.Array}
@@ -302,35 +271,6 @@ minispade.register('cluster', function() {
},
/**
- * Stage a change.
- *
- * @returns {void}
- */
- stageChange: function(node, action, replacement) {
- var self = this;
-
- $.ajax({
- type: 'PUT',
- url: '/admin/cluster',
- dataType: 'json',
-
- data: { changes:
- [{
- node: node,
- action: action,
- replacement: replacement
- }]
- },
-
- success: function(d) { self.reload(); },
-
- error: function (jqXHR, textStatus, errorThrown) {
- self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
- }
- });
- },
-
- /**
* There are various reasons we wouldn't want to display
* the planned cluster. If none of those reasons are present,
* go ahead and show the whole planned cluster view.
@@ -340,44 +280,8 @@ minispade.register('cluster', function() {
displayPlan: function () {
return !this.get('isLoading') && !this.get('ringNotReady') &&
!this.get('emptyPlan') && !this.get('legacyRing');
- }.property('isLoading', 'ringNotReady', 'emptyPlan', 'legacyRing'),
+ }.property('isLoading', 'ringNotReady', 'emptyPlan', 'legacyRing')
- /**
- * Whenever an ajax call returns an error, we display
- * the error for the user.
- *
- * @param {Object} jqXHR The xhr request object generated by jQuery.
- * @param {String} textStatus The status of the response.
- * @param {Error} errorThrown The error object produced by the ajax request.
- *
- * @returns {void}
- */
- displayError: function (jqXHR, textStatus, errorThrown) {
- var parsed, errors;
-
- if(jqXHR) {
- try {
- parsed = JSON.parse(jqXHR.responseText);
- errors = parsed.errors.join(', ');
- } catch(err) {
- errors = errorThrown;
- }
- } else {
- errors = errorThrown;
- }
-
- this.set('errorMessage', 'Request failed: ' + errors);
- },
-
- /**
- * The action specified on the <a> tag creating the 'x' button in the error message div.
- * By setting the 'errorMessage' property back to an empty string, the message will disappear.
- *
- * @returns {void}
- */
- hideError: function () {
- this.set('errorMessage', '');
- }
});
/**
@@ -450,6 +354,10 @@ minispade.register('cluster', function() {
* One item in the collection of current cluster views.
*/
RiakControl.CurrentClusterItemView = Ember.View.extend(
+ /**
+ * Shares properties with other views that display lists of nodes.
+ */
+ RiakControl.NodeProperties,
/** @scope RiakControl.CurrentClusterItemView.prototype */ {
/* Bindings from the model */
@@ -502,49 +410,6 @@ minispade.register('cluster', function() {
},
/**
- * Color the lights appropriately based on the node status.
- *
- * @returns {string}
- */
- indicatorLights: function() {
- var status = this.get('status');
- var reachable = this.get('reachable');
- var color;
-
- if(reachable === false) {
- color = "red";
- } else if(status === 'leaving' || status === 'joining') {
- color = "orange";
- } else if (status === 'valid') {
- color = "green";
- } else {
- color = "grey";
- }
-
- return "gui-light status-light inline-block " + color;
- }.property('reachable', 'status'),
-
- /**
- * Color the arrows in the partitions column appropriately based
- * on how ring_pct and pending_pct compare.
- *
- * @returns {String}
- */
- coloredArrows: function() {
- var current = this.get('ring_pct'),
- pending = this.get('pending_pct'),
- common = 'left pct-arrows ';
-
- if (pending > current) {
- return common + 'pct-gaining';
- } else if (pending < current) {
- return common + 'pct-losing';
- } else {
- return common + 'pct-static';
- }
- }.property('ring_pct', 'pending_pct'),
-
- /**
* In order for labels to be clickable, they need to be bound to checks/radios
* by ID. However, since these nodes are cloned by Ember, we need a way to make
* sure all of those elements get id's that don't override each other. This
@@ -553,138 +418,46 @@ minispade.register('cluster', function() {
*
* @returns {String}
*/
- node_id: function() {
+ nodeID: function() {
return Ember.guidFor(this);
}.property(),
/**
* An ID value for the leave normally radio button and corresponding label.
*/
- normal_leave_radio: function () {
- return this.get('node_id') + '_normal_leave';
- }.property('node_id'),
+ normalLeaveRadio: function () {
+ return this.get('nodeID') + '_normal_leave';
+ }.property('nodeID'),
/**
* An ID value for the force leave radio button and corresponding label.
*/
- force_leave_radio: function () {
- return this.get('node_id') + '_force_leave';
- }.property('node_id'),
+ forceLeaveRadio: function () {
+ return this.get('nodeID') + '_force_leave';
+ }.property('nodeID'),
/**
* An ID value for the replace node radio button and corresponding label.
*/
- replace_radio: function () {
- return this.get('node_id') + '_replace';
- }.property('node_id'),
+ replaceRadio: function () {
+ return this.get('nodeID') + '_replace';
+ }.property('nodeID'),
/**
* An ID value for the force replace check box and corresponding label.
*/
- force_replace_check: function () {
- return this.get('node_id') + '_force_replace';
- }.property('node_id'),
+ forceReplaceCheck: function () {
+ return this.get('nodeID') + '_force_replace';
+ }.property('nodeID'),
/**
* When there are no joining nodes, the radio button for selecting
* a node should be grayed out. This will put the proper classes
* on that radio button to gray it out when there are no joining nodes.
*/
- replace_radio_classes: function () {
+ replaceRadioClasses: function () {
return 'gui-radio-wrapper' + (this.get('controller.joiningNodesExist') ? '' : ' semi-transparent');
- }.property('controller.joiningNodesExist'),
-
- /**
- * Normalizer.
- *
- * @returns {number}
- */
- mem_divider: function() {
- return this.get('mem_total') / 100;
- }.property('mem_total'),
-
- /**
- * Compute memory ceiling.
- *
- * @returns {number}
- */
- mem_erlang_ceil: function () {
- return Math.ceil(this.get('mem_erlang') / this.get('mem_divider'));
- }.property('mem_erlang', 'mem_divider'),
-
- /**
- * Compute free memory from total and used.
- *
- * @returns {number}
- */
- mem_non_erlang: function () {
- return Math.round(
- (this.get('mem_used') / this.get('mem_divider')) - this.get('mem_erlang_ceil'));
- }.property('mem_used', 'mem_divider', 'mem_erlang_ceil'),
-
- /**
- * Compute free memory from total and used.
- *
- * @returns {number}
- */
- mem_free: function () {
- return this.get('mem_total') - this.get('mem_used');
- }.property('mem_total', 'mem_used'),
-
- /**
- * Format free memory to be a readbale version.
- *
- * @returns {number}
- */
- mem_free_readable: function () {
- return Math.round(this.get('mem_free') / this.get('mem_divider'));
- }.property('mem_free', 'mem_divider'),
-
- /**
- * Format used memory to be a readbale version.
- *
- * @returns {number}
- */
- mem_used_readable: function () {
- return Math.round((this.get('mem_total') - this.get('mem_free')) /
- this.get('mem_divider'));
- }.property('mem_total', 'mem_free', 'mem_divider'),
-
- /**
- * Return CSS style for rendering memory used by Erlang.
- *
- * @returns {number}
- */
- mem_erlang_style: function () {
- return 'width: ' + this.get('mem_erlang_ceil') + '%';
- }.property('mem_erlang_ceil'),
-
- /**
- * Return CSS style for rendering occupied non-erlang memory.
- *
- * @returns {string}
- */
- mem_non_erlang_style: function () {
- return 'width: ' + this.get('mem_non_erlang') + '%';
- }.property('mem_non_erlang'),
-
- /**
- * Return CSS style for rendering free memory.
- *
- * @returns {string}
- */
- mem_free_style: function () {
- return 'width: ' + this.get('mem_free_readable') + '%';
- }.property('mem_free_readable'),
-
- /**
- * Formatted ring percentage.
- *
- * @returns {string}
- */
- ring_pct_readable: function () {
- return Math.round(this.get('ring_pct') * 100);
- }.property('ring_pct')
+ }.property('controller.joiningNodesExist')
});
@@ -704,6 +477,10 @@ minispade.register('cluster', function() {
* One item in the collection of current cluster views.
*/
RiakControl.StagedClusterItemView = Ember.View.extend(
+ /**
+ * Shares properties with other views that display lists of nodes.
+ */
+ RiakControl.NodeProperties,
/** @scope RiakControl.StagedClusterItemView.prototype */ {
/* Bindings from the model */
@@ -716,39 +493,7 @@ minispade.register('cluster', function() {
isActionBinding: 'content.isAction',
/* Necessary rename to avoid collision */
- node_actionBinding: 'content.action',
-
- /**
- * Color the lights appropriately based on the node status.
- *
- * @returns {string}
- */
- indicatorLights: function() {
- var status = this.get('status');
- var reachable = this.get('reachable');
- var color;
-
- if(reachable === false) {
- color = "red";
- } else if(status === 'leaving' || status === 'joining') {
- color = "orange";
- } else if (status === 'valid') {
- color = "green";
- } else {
- color = "grey";
- }
-
- return "gui-light status-light inline-block " + color;
- }.property('reachable', 'status'),
-
- /**
- * Formatted ring percentage.
- *
- * @returns {string}
- */
- ring_pct_readable: function () {
- return Math.round(this.get('ring_pct') * 100);
- }.property('ring_pct')
+ node_actionBinding: 'content.action'
});
View
10 priv/admin/js/generated/templates.js
@@ -1,8 +1,10 @@
-Ember.TEMPLATES['application'] = Ember.Handlebars.compile('<div id="header"> <div id="navbar"> <a id="riak-control-logo"></a> <nav> <ul id="nav-ul"> <li id="nav-ring" class="nav-li"><a {{action showRing href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> <li id="nav-cluster" class="nav-li"><a {{action showCluster href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> <li id="nav-snapshot" class="nav-li"><a {{action showSnapshot href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> </ul> </nav> </div></div><div id="wrapper" class="split gui-text"> <section id="content-well">{{outlet}}</section> <footer> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <a id="basho-logo" href="http://www.basho.com" target="_blank"><img src="/admin/ui/images/basho-logo.png" alt=""/></a> <span class="vert-border-right"></span> </div> <div class="side-line"></div> <div class="clear"></div> <footer></div><!-- #wrapper --><div id="tooltips" class="hide"> <div id="display-tips" class="gui-text"></div></div>');
+Ember.TEMPLATES['application'] = Ember.Handlebars.compile('<div id="header"> <div id="navbar"> <a id="riak-control-logo"></a> <nav> <ul id="nav-ul"> <li id="nav-ring" class="nav-li"><a {{action showRing href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> <li id="nav-nodes" class="nav-li"><a {{action showNodes href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> <li id="nav-cluster" class="nav-li"><a {{action showCluster href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> <li id="nav-snapshot" class="nav-li"><a {{action showSnapshot href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li> </ul> </nav> </div></div><div id="wrapper" class="split gui-text"> <section id="content-well">{{outlet}}</section> <footer> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <a id="basho-logo" href="http://www.basho.com" target="_blank"><img src="/admin/ui/images/basho-logo.png" alt=""/></a> <span class="vert-border-right"></span> </div> <div class="side-line"></div> <div class="clear"></div> <footer></div><!-- #wrapper --><div id="tooltips" class="hide"> <div id="display-tips" class="gui-text"></div></div>');
Ember.TEMPLATES['snapshot'] = Ember.Handlebars.compile('<div id="snapshot-page"> <section id="title-container"> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <h1 id="snapshot-headline" class="gui-headline-bold page-title">Current Snapshot</h1> <span class="vert-border-right"></span> </div> <div class="side-line"></div> <div class="clear"></div> </section> <div class="relative health-info"> {{#if healthyCluster}} <div id="healthy-cluster"> <img id="health-indicator" src="/admin/ui/images/healthy-cluster.png" alt="" /> <section> <h2 class="gui-headline-bold has-cut">Your cluster is healthy.</h2> <h3 class="">You currently have...</h3> <ul class="gui-text bulleted"> <li><span class="emphasize monospace">0</span> Unreachable nodes</li> <li><span class="emphasize monospace">0</span> Incompatible nodes</li> <li><span class="emphasize monospace">0</span> Nodes marked as down</li> <li><span class="emphasize monospace">0</span> Nodes experiencing low memory</li> <li>Nothing to worry about because Riak is your friend</li> </ul> </section> </div> {{else}} <div id="unhealthy-cluster"> <img id="health-indicator" src="/admin/ui/images/unhealthy-cluster.png" alt="" /> <section> <h2 class="gui-headline-bold has-cut">Your cluster has problems.</h2> {{#if areUnreachableNodes}} <!-- Unreachable Nodes List --> <h3 id="unreachable-nodes-title" class="">The following nodes are currently unreachable:</h3> <ul id="unreachable-nodes-list" class="gui-text bulleted monospace"> {{#each unreachableNodes}} <li><a class="go-to-cluster" {{action showCluster href=true}}>{{name}}</a></li> {{/each}} </ul> {{/if}} {{#if areIncompatibleNodes}} <!-- Incompatible Nodes List --> <h3 id="incompatible-nodes-title" class="">The following nodes are currently incompatible with Riak Control:</h3> <ul id="incompatible-nodes-list" class="gui-text bulleted monospace"> {{#each incompatibleNodes}} <li><a class="go-to-cluster" {{action showCluster href=true}}>{{name}}</a></li> {{/each}} </ul> {{/if}} {{#if areDownNodes}} <!-- Down Nodes List --> <h3 id="down-nodes-title" class="">The following nodes are currently marked down:</h3> <ul id="down-nodes-list" class="gui-text bulleted monospace"> {{#each downNodes}} <li><a class="go-to-cluster" {{action showCluster href=true}}>{{name}}</a></li> {{/each}} </ul> {{/if}} {{#if areLowMemNodes}} <!-- Low-Mem Nodes List --> <h3 id="low_mem-nodes-title" class="">The following nodes are currently experiencing low memory:</h3> <ul id="low_mem-nodes-list" class="gui-text bulleted monospace"> {{#each lowMemNodes}} <li><a class="go-to-cluster" {{action showCluster href=true}}>{{name}}</a></li> {{/each}} </ul> {{/if}} </section> </div> {{/if}} </div> </div>');
Ember.TEMPLATES['cluster'] = Ember.Handlebars.compile('<div id="cluster-page"> <section id="title-container"> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <h1 id="cluster-headline" class="gui-headline-bold page-title">Cluster Management</h1> <span class="vert-border-right"></span> </div> <div class="side-line"></div> <div class="clear"></div> </section> <div id="add-node"> <h2 class="gui-headline">Join Nodes</h2> <span class="gui-text-flat italic">Type a node name or list of names separated by commas.</span> <table class="add-node-table"> <tr class="no-highlight"> <td id="add-node-box"> {{view RiakControl.AddNodeView id="node-to-add"}} </td> <td class="button-column"> <a class="gui-point-button gui-text-bold right" {{action addNode target="controller"}}> <span class="gui-button-msg">ADD NODES</span> </a> </td> </tr> </table> {{#if errorMessage}} <div class="error-message"> <a class="close-error gui-text" {{action hideError target="controller"}}></a> <a class="error-text offline gui-text-flat">{{errorMessage}}</a> </div> {{/if}} </div><!-- #add-node --> <div id="current-area"> <h2 class="gui-headline"> Current Cluster <span id="total-number" class="gui-text-flat italic"></span><br/> </h2> <section id="node-list"> {{#if controller.isLoading}} <div class="spinner-box"> <img id="cluster-spinner" class="spinner" src="/admin/ui/images/spinner.gif"> <h4 class="gui-headline-bold">Loading...</h4> </div> {{else}} <ul class="list-header"> <li class="item1"><h4 class="gui-headline-bold">Actions</h4></li> <li class="item2"><h4 class="gui-headline-bold">Name &amp; Status</h4></li> <li class="item3"><h4 class="gui-headline-bold">Partitions</h4></li> <li class="item4"><h4 class="gui-headline-bold">RAM Usage</h4></li> </ul> <div class="clear"></div> {{collection RiakControl.CurrentClusterView contentBinding="activeCurrentCluster"}} {{/if}} </section> </div> <div id="area-separator"> <div class="vert-line"></div> </div> <div id="planned-area"> <h2 class="gui-headline"> Staged Changes <span class="gui-text-flat italic">(Your new cluster after convergence.)</span> </h2> {{#if controller.displayPlan}} <section id="planned-list" class=""> <ul class="list-header"> <li class="item1"><h4 class="gui-headline-bold">Name &amp; Status</h4></li> <li class="item2"><h4 class="gui-headline-bold">Partitions</h4></li> <li class="item3"><h4 class="gui-headline-bold">Action</h4></li> <li class="item4"><h4 class="gui-headline-bold">Replacement</h4></li> </ul> <div class="clear"></div> {{collection RiakControl.StagedClusterView contentBinding="activeStagedCluster"}} </section> <div class="accept-plan"> <div class="gui-checkbox-wrapper"> <label for="confirmed-check">This plan is correct.</label> <input class="gui-checkbox" type="checkbox" name="confirmed" id="confirmed-check" value="accept"/> </div> <a id="commit-button" class="gui-point-button-right gui-text-bold right" {{action commitPlan target="controller"}}> <span class="gui-button-msg">COMMIT</span> </a> </div> <div class="clear-plan-box"> <span class="gui-text-flat serif"> Changed your mind? Click this button to remove all staged changes. </span> <a class="gui-rect-button gui-text-bold" {{action clearPlan target="controller"}}> <span class="gui-button-msg">CLEAR PLAN</span> </a> </div> {{else}} <section id="planned-list"> <div class="spinner-box"> {{#if controller.ringNotReady}} <h4 class="gui-headline-bold"> Cannot plan until cluster state has converged. Check "Ring Ready" in "riak-admin ring_status". </h4> {{else}} {{#if controller.legacyRing}} <h4 class="gui-headline-bold">You are currently running a legacy version of Riak that does not support staged changes.</h4> {{else}} {{#if controller.emptyPlan}} <h4 class="gui-headline-bold">Currently no staged changes to display.</h4> {{else}} {{#if controller.isLoading}} <img id="cluster-spinner" class="spinner" src="/admin/ui/images/spinner.gif"> <h4 class="gui-headline-bold">Loading...</h4> {{/if}} {{/if}} {{/if}} {{/if}} </div> </section> {{/if}} </div> <div class="clear"></div> </div>');
-Ember.TEMPLATES['ring'] = Ember.Handlebars.compile('<div id="ring-page"> <section id="title-container"> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <h1 id="ring-headline" class="gui-headline-bold page-title">Current Ring</h1> <span class="vert-border-right"></span> </div> <div class="side-line"> {{outlet partitionFilter}} </div> <div class="clear"></div> </section> <ul class="pagination gui-text"> <li name="prev"><span class="paginator" {{action prevPage href=true target="controller"}}>Prev</span></li> {{#each pages}} {{view RiakControl.PaginationItemView contentBinding="this"}} {{/each}} <li name="next"><span class="paginator" {{action nextPage href=true target="controller"}}>Next</span></li> </ul> <div class="cut"></div> <div id="partition-list"> <table class="list-table" id="ring-table"> <thead> <tr class="table-head has-cut"> <th><h3>#</h3></th> <th><h3>Owner Node</h3></th> <th><h3>KV</h3></th> <th><h3>Pipe</h3></th> <th><h3>Search</h3></th> </tr> </thead> {{#collection RiakControl.PartitionView contentBinding="controller.paginatedContent"}} {{#with view.content}} <td class="partition-number gui-text">{{i}}</td> <td class="owner-box gui-text"> <div class="owner gui-field">{{node}}</div> <div class="partition-index hide">{{index}}</div> </td> {{/with}} {{#with view}} <td class="kv-box gui-text"> <a {{bindAttr class="kvIndicator lightClasses"}}> <span class="kv-status">{{kvStatus}}</span> <span class="hide fallback-to"></span> </a> </td> <td class="pipe-box gui-text"> <a {{bindAttr class="pipeIndicator lightClasses"}}> <span class="pipe-status">{{pipeStatus}}</span> <span class="hide fallback-to"></span> </a> </td> <td class="search-box gui-text"> <a {{bindAttr class="searchIndicator lightClasses"}}> <span class="search-status">{{searchStatus}}</span> <span class="hide fallback-to"></span> </a> </td> {{/with}} {{/collection}} </table> </div> <div class="cut"></div> <ul class="pagination gui-text"> <li name="prev"><span class="paginator" {{action prevPage href=true target="controller"}}>Prev</span></li> {{#each pages}} {{view RiakControl.PaginationItemView contentBinding="this"}} {{/each}} <li name="next"><span class="paginator" {{action nextPage href=true target="controller"}}>Next</span></li> </ul> </div>');
+Ember.TEMPLATES['nodes'] = Ember.Handlebars.compile('<div id="nodes-page"> <section id="title-container"> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <h1 id="node-headline" class="gui-headline-bold page-title">Node Management</h1> <span class="vert-border-right"></span> </div> <div class="side-line"></div> <div class="clear"></div> </section> {{#if errorMessage}} <div class="error-message"> <a class="close-error gui-text" {{action hideError target="controller"}}></a> <a class="error-text offline gui-text-flat">{{errorMessage}}</a> </div> {{/if}} <div id="current-area"> <h2 class="gui-headline"> Current Cluster<br/> </h2> <span id="total-number" class="gui-text-flat italic"> Click the radio button for each node you would like to stop or mark as down, then click "APPLY" to apply your changes. If the radio button is grayed out, the action is not available due to the current status of the node. </span><br/> <section id="node-list"> {{#if controller.isLoading}} <div class="spinner-box"> <img id="cluster-spinner" class="spinner" src="/admin/ui/images/spinner.gif"> <h4 class="gui-headline-bold">Loading...</h4> </div> {{else}} <ul class="list-header"> <li class="item1"><h4 class="gui-headline-bold">Stop</h4></li> <li class="item2"><h4 class="gui-headline-bold">Down</h4></li> <li class="item3"><h4 class="gui-headline-bold">Name &amp; Status</h4></li> <li class="item4"><h4 class="gui-headline-bold">Partitions</h4></li> <li class="item5"><h4 class="gui-headline-bold">RAM Usage</h4></li> </ul> <div class="clear"></div> {{collection RiakControl.CurrentNodesView contentBinding="content"}} {{/if}} </section> <section class="buttons"> <a class="gui-point-button-right gui-text-bold right" {{action applyChanges target="controller"}}> <span class="gui-button-msg">APPLY</span> </a> <a class="gui-rect-button gui-text-bold right" {{action clearChecked target="controller"}}> <span class="gui-button-msg">CLEAR</span> </a> <div class="clear"></div> </section> </div></div>');
+Ember.TEMPLATES['ring'] = Ember.Handlebars.compile('<div id="ring-page"> <section id="title-container"> <div class="side-line"></div> <div class="title-box"> <span class="vert-border-left"></span> <h1 id="ring-headline" class="gui-headline-bold page-title">Current Ring</h1> <span class="vert-border-right"></span> </div> <div class="side-line"> {{outlet partitionFilter}} </div> <div class="clear"></div> </section> <ul class="pagination first gui-text"> <li name="prev"><span class="paginator" {{action prevPage href=true target="controller"}}>Prev</span></li> {{#each pages}} {{view RiakControl.PaginationItemView contentBinding="this"}} {{/each}} <li name="next"><span class="paginator" {{action nextPage href=true target="controller"}}>Next</span></li> </ul> <div class="cut"></div> <div id="partition-list"> <table class="list-table" id="ring-table"> <thead> <tr class="table-head has-cut"> <th><h3>#</h3></th> <th><h3>Owner Node</h3></th> <th><h3>KV</h3></th> <th><h3>Pipe</h3></th> <th><h3>Search</h3></th> </tr> </thead> {{#collection RiakControl.PartitionView contentBinding="controller.paginatedContent"}} {{#with view.content}} <td class="partition-number gui-text">{{i}}</td> <td class="owner-box gui-text"> <div class="owner gui-field">{{node}}</div> <div class="partition-index hide">{{index}}</div> </td> {{/with}} {{#with view}} <td class="kv-box gui-text"> <a {{bindAttr class="kvIndicator lightClasses"}}> <span class="kv-status">{{kvStatus}}</span> <span class="hide fallback-to"></span> </a> </td> <td class="pipe-box gui-text"> <a {{bindAttr class="pipeIndicator lightClasses"}}> <span class="pipe-status">{{pipeStatus}}</span> <span class="hide fallback-to"></span> </a> </td> <td class="search-box gui-text"> <a {{bindAttr class="searchIndicator lightClasses"}}> <span class="search-status">{{searchStatus}}</span> <span class="hide fallback-to"></span> </a> </td> {{/with}} {{/collection}} </table> </div> <div class="cut"></div> <ul class="pagination gui-text"> <li name="prev"><span class="paginator" {{action prevPage href=true target="controller"}}>Prev</span></li> {{#each pages}} {{view RiakControl.PaginationItemView contentBinding="this"}} {{/each}} <li name="next"><span class="paginator" {{action nextPage href=true target="controller"}}>Next</span></li> </ul> </div>');
Ember.TEMPLATES['partition_filter'] = Ember.Handlebars.compile('<div id="ring-filter" class="right"> <div class="gui-dropdown-wrapper"> <div class="gui-dropdown-bg gui-text">Filter by...</div> <div class="gui-dropdown-cap left"></div> {{view RiakControl.PartitionFilterSelectView id="filter" classNames="gui-dropdown" contentBinding="filters" optionLabelPath="content.name" optionValuePath="content.value" prompt="All" selectionBinding="controller.selectedPartitionFilter"}} </div></div>');
Ember.TEMPLATES['pagination_item'] = Ember.Handlebars.compile('{{#with view}}<a {{action paginateRing content href=true}}> <span {{bindAttr class="spanClasses isActive:active"}}>{{content.page_id}}</span></a>{{/with}}');
-Ember.TEMPLATES['current_cluster_item'] = Ember.Handlebars.compile('{{#with view}} <div class="node"> <div class="item1 toggle-container"> {{#view RiakControl.CurrentClusterToggleView}} <div class="actions-toggle gui-field"> <a class="slider"></a> </div> {{/view}} </div> <div class="item2 name-box gui-text"> <div {{bindAttr class="indicatorLights"}}> </div><div class="gui-text field-container inline-block"> <div class="name gui-field">{{name}}</div> </div> </div> <div class="item3 gui-text ring-pct-box"> <div {{bindAttr class="coloredArrows"}}></div> <div class="left gui-text pct-box"> <span class="ring-pct">{{ring_pct_readable}}%</span> </div> <div class="clear"></div> </div> <div class="item4 gui-text memory-box"> {{#if reachable}} <div class="membar-bg"> <div class="mem-colors"> <div class="erlang-mem mem-color" {{bindAttr style="mem_erlang_style"}} {{bindAttr name="mem_erlang_ceil"}}></div> <div class="non-erlang-mem mem-color" {{bindAttr style="mem_non_erlang_style"}} {{bindAttr name="mem_non_erlang"}}></div> <div class="unknown-mem" {{bindAttr style="mem_free_style"}} {{bindAttr name="mem_free_readable"}}></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory">{{mem_used_readable}}%</span> {{else}} <div class="membar-bg"> <div class="mem-colors"> <div class="unknown-mem" style="width: 100%"></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory"></span> {{/if}} </div> <div class="clear"></div> <!-- Actions container --> <div class="actions-container"> <div class="actions-pointer"></div> <div class="actions-box"> <h4 class="gui-headline-bold"> Use these actions to prepare this node to leave the cluster. </h4> {{#if me}} <span class="warning gui-text-flat italic"> Warning: This node is hosting Riak Control. If it leaves the cluster, Riak Control will be shut down. </span> {{/if}} <div class="replacement-controls"> <div class="gui-radio-wrapper default"> <input class="gui-radio" type="radio" value="leave" {{bindAttr name="name" id="normal_leave_radio"}} checked="checked"/> <label class="serif" {{bindAttr for="normal_leave_radio"}}>Allow this node to leave normally.</label> </div> <div class="gui-radio-wrapper"> <input class="gui-radio" type="radio" value="remove" {{bindAttr name="name" id="force_leave_radio"}} /> <label class="serif" {{bindAttr for="force_leave_radio"}}>Force this node to leave.</label> </div> <div {{bindAttr class="replace_radio_classes"}}> <input class="gui-radio" type="radio" value="replace" {{bindAttr name="name" id="replace_radio"}} /> <label class="serif" {{bindAttr for="replace_radio"}}>Choose a new node to replace this one.</label> </div> <div class="extra-actions"> <div class="right-angle-arrow"></div> {{#if controller.joiningNodesExist}} <div class="gui-dropdown-wrapper replacement-node-dropdown"> <div class="gui-dropdown-bg gui-text">Select Replacement Node</div> <div class="gui-dropdown-cap left"></div> {{view Ember.Select classNames="gui-dropdown" contentBinding="controller.joiningNodes" optionLabelPath="content.name"}} </div> <div class="gui-checkbox-wrapper"> <input class="gui-checkbox" type="checkbox" {{bindAttr name="name" id="force_replace_check"}} value="true" /> <label class="serif" {{bindAttr for="force_replace_check"}}>Force this replacement?</label> </div> <div class="clear"></div> <div class="disabler"></div> {{else}} <div class="no-joining-nodes gui-text-flat serif italic"> No new nodes are currently staged to join. </div> <div class="disabler show slide-up"></div> {{/if}} </div> <div class="clear"></div> </div> <div class="clear"></div> <span class="gui-text-flat serif italic stage-instructions">Click "STAGE" when you are ready to stage this action.</span> <a class="stage-button gui-point-button-right gui-text-bold right" {{action stageChange target="view"}}> <span class="gui-button-msg">STAGE</span> </a> <div class="clear"></div> </div> <div class="clear"></div> </div><!-- .actions-box --> <div class="clear"></div> </div><!-- .node -->{{/with}}');
-Ember.TEMPLATES['staged_cluster_item'] = Ember.Handlebars.compile('{{#with view}} <div class="node"> <div class="item1 name-box gui-text"> <div {{bindAttr class="indicatorLights"}}> </div><div class="gui-text field-container inline-block"> <div class="name gui-field">{{name}}</div> </div> </div> <div class="item2 gui-text ring-pct-box"> <div class="left gui-text pct-box"> <span class="ring-pct">{{ring_pct_readable}}%</span> </div> <div class="clear"></div> </div> {{#if isAction}} <div class="item3 action-taken gui-text"> <span class="action-name">{{node_action}}</span> </div> {{/if}} {{#if isReplaced}} <div class="item4 replacing-box"> <div class="gui-text field-container inline-block"> <div class="name gui-field">{{replacement}}</div> </div> </div> {{/if}} <div class="clear"></div> </div>{{/with}}');
+Ember.TEMPLATES['current_cluster_item'] = Ember.Handlebars.compile('{{#with view}} <div class="node"> <div class="item1 toggle-container"> {{#view RiakControl.CurrentClusterToggleView}} <div class="actions-toggle gui-field"> <a class="slider"></a> </div> {{/view}} </div> <div class="item2 name-box gui-text"> <div {{bindAttr class="indicatorLights"}}> </div><div class="gui-text field-container inline-block"> <div class="name gui-field">{{name}}</div> </div> </div> <div class="item3 gui-text ring-pct-box"> <div {{bindAttr class="coloredArrows"}}></div> <div class="left gui-text pct-box"> <span class="ring-pct">{{ringPctReadable}}%</span> </div> <div class="clear"></div> </div> <div class="item4 gui-text memory-box"> {{#if reachable}} <div class="membar-bg"> <div class="mem-colors"> <div class="erlang-mem mem-color" {{bindAttr style="memErlangStyle"}} {{bindAttr name="memErlangCeil"}}></div> <div class="non-erlang-mem mem-color" {{bindAttr style="memNonErlangStyle"}} {{bindAttr name="memNonErlang"}}></div> <div class="unknown-mem" {{bindAttr style="memFreeStyle"}} {{bindAttr name="memFreeReadable"}}></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory">{{memUsedReadable}}%</span> {{else}} <div class="membar-bg"> <div class="mem-colors"> <div class="unknown-mem" style="width: 100%"></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory"></span> {{/if}} </div> <div class="clear"></div> <!-- Actions container --> <div class="actions-container"> <div class="actions-pointer"></div> <div class="actions-box"> <h4 class="gui-headline-bold"> Use these actions to prepare this node to leave the cluster. </h4> {{#if me}} <span class="warning gui-text-flat italic"> Warning: This node is hosting Riak Control. If it leaves the cluster, Riak Control will be shut down. </span> {{/if}} <div class="replacement-controls"> <div class="gui-radio-wrapper default"> <input class="gui-radio" type="radio" value="leave" {{bindAttr name="name" id="normalLeaveRadio"}} checked="checked"/> <label class="serif" {{bindAttr for="normalLeaveRadio"}}>Allow this node to leave normally.</label> </div> <div class="gui-radio-wrapper"> <input class="gui-radio" type="radio" value="remove" {{bindAttr name="name" id="forceLeaveRadio"}} /> <label class="serif" {{bindAttr for="forceLeaveRadio"}}>Force this node to leave.</label> </div> <div {{bindAttr class="replaceRadioClasses"}}> <input class="gui-radio" type="radio" value="replace" {{bindAttr name="name" id="replaceRadio"}} /> <label class="serif" {{bindAttr for="replaceRadio"}}>Choose a new node to replace this one.</label> </div> <div class="extra-actions"> <div class="right-angle-arrow"></div> {{#if controller.joiningNodesExist}} <div class="gui-dropdown-wrapper replacement-node-dropdown"> <div class="gui-dropdown-bg gui-text">Select Replacement Node</div> <div class="gui-dropdown-cap left"></div> {{view Ember.Select classNames="gui-dropdown" contentBinding="controller.joiningNodes" optionLabelPath="content.name"}} </div> <div class="gui-checkbox-wrapper"> <input class="gui-checkbox" type="checkbox" {{bindAttr name="name" id="forceReplaceCheck"}} value="true" /> <label class="serif" {{bindAttr for="forceReplaceCheck"}}>Force this replacement?</label> </div> <div class="clear"></div> <div class="disabler"></div> {{else}} <div class="no-joining-nodes gui-text-flat serif italic"> No new nodes are currently staged to join. </div> <div class="disabler show slide-up"></div> {{/if}} </div> <div class="clear"></div> </div> <div class="clear"></div> <span class="gui-text-flat serif italic stage-instructions">Click "STAGE" when you are ready to stage this action.</span> <a class="stage-button gui-point-button-right gui-text-bold right" {{action stageChange target="view"}}> <span class="gui-button-msg">STAGE</span> </a> <div class="clear"></div> </div> <div class="clear"></div> </div><!-- .actions-box --> <div class="clear"></div> </div><!-- .node -->{{/with}}');
+Ember.TEMPLATES['current_nodes_item'] = Ember.Handlebars.compile('{{#with view}} <div class="node"> <div class="item1"> <div {{bindAttr class="stopRadioClasses"}}> <input class="gui-radio" type="radio" value="stop" {{bindAttr name="name" id="stopRadio"}} /> <div {{bindAttr class="stopDisablerClasses"}}></div> </div> </div> <div class="item2"> <div {{bindAttr class="downRadioClasses"}}> <input class="gui-radio" type="radio" value="down" {{bindAttr name="name" id="downRadio"}} /> <div {{bindAttr class="downDisablerClasses"}}></div> </div> </div> <div class="item3 name-box gui-text"> <div {{bindAttr class="indicatorLights"}}> </div><div class="gui-text field-container inline-block"> <div class="name gui-field">{{name}}</div> </div> </div> <div class="item4 gui-text ring-pct-box"> <div {{bindAttr class="coloredArrows"}}></div> <div class="left gui-text pct-box"> <span class="ring-pct">{{ringPctReadable}}%</span> </div> <div class="clear"></div> </div> <div class="item5 gui-text memory-box"> {{#if reachable}} <div class="membar-bg"> <div class="mem-colors"> <div class="erlang-mem mem-color" {{bindAttr style="memErlangStyle"}} {{bindAttr name="memErlangCeil"}}></div> <div class="non-erlang-mem mem-color" {{bindAttr style="memNonErlangStyle"}} {{bindAttr name="memNonErlang"}}></div> <div class="unknown-mem" {{bindAttr style="memFreeStyle"}} {{bindAttr name="memFreeReadable"}}></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory">{{memUsedReadable}}%</span> {{else}} <div class="membar-bg"> <div class="mem-colors"> <div class="unknown-mem" style="width: 100%"></div> </div> <div class="membar-fg"></div> </div> <span class="used-memory"></span> {{/if}} </div> <div class="clear"></div> </div><!-- .node -->{{/with}}');
+Ember.TEMPLATES['staged_cluster_item'] = Ember.Handlebars.compile('{{#with view}} <div class="node"> <div class="item1 name-box gui-text"> <div {{bindAttr class="indicatorLights"}}> </div><div class="gui-text field-container inline-block"> <div class="name gui-field">{{name}}</div> </div> </div> <div class="item2 gui-text ring-pct-box"> <div class="left gui-text pct-box"> <span class="ring-pct">{{ringPctReadable}}%</span> </div> <div class="clear"></div> </div> {{#if isAction}} <div class="item3 action-taken gui-text"> <span class="action-name">{{node_action}}</span> </div> {{/if}} {{#if isReplaced}} <div class="item4 replacing-box"> <div class="gui-text field-container inline-block"> <div class="name gui-field">{{replacement}}</div> </div> </div> {{/if}} <div class="clear"></div> </div>{{/with}}');
View
73 priv/admin/js/legacy/gui.js
@@ -43,6 +43,7 @@ $(document).ready(function() {
parent = me.parent(),
checked = me.attr('checked'),
group = $('input[type="radio"][name="' + me.attr('name') + '"]');
+
/*
* If the radio button is checked...
*/
@@ -107,7 +108,12 @@ $(document).ready(function() {
// Cluster link in navigation
$(document).on('mouseover', '#nav-cluster', function () {
- displayTips('The cluster view allows you to add and remove nodes from your cluster as well as stop Riak on various nodes or mark them as down. You will also be able to view percentages indicating how much ring data is owned by each node.');
+ displayTips('The cluster view is where you can plan and commit changes to your cluster such as adding, removing, or replacing nodes.');
+ }).on('mouseout', '#nav-cluster', emptyTips);
+
+ // Nodes link in navigation
+ $(document).on('mouseover', '#nav-nodes', function () {
+ displayTips('The nodes view contains actions that are not part of the plan and commit process. From here you can stop nodes or mark them as down.');
}).on('mouseout', '#nav-cluster', emptyTips);
// Ring link in navigation
@@ -120,44 +126,39 @@ $(document).ready(function() {
displayTips('Selecting the snapshot view commands Riak Control to gather some general information related to the current health of your cluster. It is useful in quickly determining whether or not there are any issues to be concerned about.');
}).on('mouseout', '#nav-snapshot', emptyTips);
- // Cluster View
+ // Cluster/Nodes Views
// Add new node area
$(document).on('mouseover', '#add-node table', function () {
displayTips('Type a node name (for example: dev2@127.0.0.1) into the text field and hit "Add Node" to add it to this cluster. The node will then take ownership of partitions in the ring to help ensure balanced data across the cluster.');
}).on('mouseout', '#add-node table', emptyTips);
+ // Clear down/stop radios button
+ $(document).on('mouseover', '.buttons .gui-rect-button', function () {
+ displayTips('Click this button to clear marks from all of the radio buttons on this page.');
+ }).on('mouseout', '.buttons .gui-rect-button', emptyTips);
+
+ // Apply down/stop radios button
+ $(document).on('mouseover', '.buttons .gui-point-button-right', function () {
+ displayTips('Click this button to initialize all stops and downs on currently marked nodes.');
+ }).on('mouseout', '.buttons .gui-point-button-right', emptyTips);
+
+ // Commit cluster plan button
+ $(document).on('mouseover', '#commit-button', function () {
+ displayTips('Click this button to commit your cluster plan. This will initialize all joins, leaves, and replacements as you have indicated.');
+ }).on('mouseout', '#commit-button', emptyTips).on('click', '#commit-button', emptyTips);
+
+ // Commit cluster plan button
+ $(document).on('mouseover', '.clear-plan-box .gui-rect-button', function () {
+ displayTips('Click this button to un-stage all changes made to the cluster plan.');
+ }).on('mouseout', '.clear-plan-box .gui-rect-button', emptyTips).on('click', '.clear-plan-box .gui-rect-button', emptyTips);
+
// Name of a node
$(document).on('mouseover', '.node .name', function () {
var name = $(this).text();
displayTips('This node is named ' + name + '. It is a member of this cluster.');
}).on('mouseout', '.node .name', emptyTips);
- // View actions cluster sliders
- $(document).on('mouseover', '.more-actions-slider-box .gui-slider', function () {
- displayTips('Move the slider over to view possible actions for this node. Move the slider back to hide those actions again.');
- }).on('mouseout', '.more-actions-slider-box .gui-slider', emptyTips);
-
- // Leave cluster button
- $(document).on('mouseover', '.leave-cluster-button', function () {
- displayTips('This will cause the node to begin relinquishing ownership of its data to other nodes in the cluster. You will not be able to interact with the node via Riak Control during this process. Once completed, Riak will shutdown on this node and it will leave the cluster.');
- }).on('mouseout', '.leave-cluster-button', emptyTips);
-
- // The 'Hosting Riak Control' message
- $(document).on('mouseover', '.current-host', function () {
- displayTips('This node cannot be shutdown or removed via Riak Control because it is currently hosting the app.');
- }).on('mouseout', '.current-host', emptyTips);
-
- // Markdown button
- $(document).on('mouseover', '.markdown-button', function () {
- displayTips('This button becomes active when the node becomes unreachable. While in an unreachable state, a node may hinder cluster membership changes of other nodes. Marking this node as "down" will allow other nodes to engage in membership changes unimpeded.');
- }).on('mouseout', '.markdown-button', emptyTips);
-
- // Shutdown button
- $(document).on('mouseover', '.shutdown-button', function () {
- displayTips('This button will stop the Riak process on this node. If this button is not active, your node is already unreachable. It will have to be restarted manually.');
- }).on('mouseout', '.shutdown-button', emptyTips);
-
// Ring ownership percent
$(document).on('mouseover', '.pct-box, .pct-arrows', function () {
displayTips('The portion of the ring owned by this node. When an arrow LED is lit, it is either in the process of receiving a partition from another node (up) or sending a partition to another node (down).');
@@ -168,7 +169,7 @@ $(document).ready(function() {
var parent = $(this).parent(),
erlang_mem, non_erlang_mem,
free_mem = parent.find('.unknown-mem').attr('name');
- if (free_mem.charAt(0) === '?') {
+ if (!free_mem || free_mem.charAt(0) === '?') {
displayTips('Because this node is currently unreachable or incompatible with Riak Control, Riak Control is not able to assess its memory usage.');
} else {
free_mem = parseInt(free_mem, 10);
@@ -179,20 +180,18 @@ $(document).ready(function() {
}).on('mouseout', '.membar-bg, .free-memory', emptyTips);
// Node status
- $(document).on('mouseover', '.status-box', function () {
- var mytext = $(this).find('.status').text().toLowerCase();
- if (mytext === 'valid') {
+ $(document).on('mouseover', '.status-light', function () {
+ var classes = this.className;
+ if (/\bgreen\b/.test(classes)) {
displayTips('This node is currently online and working.');
- } else if (mytext === 'incompatible') {
- displayTips('This node is incompatible with Riak Control. While in this state, Riak Control will be unable to determine status of this node.');
- } else if (mytext === 'unreachable') {
+ } else if (/\bred\b/.test(classes)) {
displayTips('This node is unreachable. Riak may need to be restarted or there may be other connectivity issues. Cluster membership changes like "join" and "leave" cannot complete until this node is reachable or marked as "down".');
- } else if (mytext === 'down') {
+ } else if (/\bblue\b/.test(classes)) {
displayTips('This node has been marked as "down". While in this state it can not be interacted with but it will not impede cluster membership changes of other nodes. To return to a "valid" state, simply restart Riak on this node.');
- } else if (mytext === 'leaving') {
+ } else if (/\borange\b/.test(classes)) {
displayTips('This node is in process of leaving the cluster. When it has finished relinquishing ownership and transferring data to other nodes, Riak will stop on this node and it will cease to be a member of the cluster. You can not interact with this node during this process.');
}
- }).on('mouseout', '.status-box', emptyTips);
+ }).on('mouseout', '.status-light', emptyTips);
// Ring View
View
183 priv/admin/js/nodes.js
@@ -0,0 +1,183 @@
+minispade.register('nodes', function() {
+
+ /**
+ * @class
+ *
+ * Node_managementView is responsible for allowing you to stop
+ * or down a node.
+ */
+ RiakControl.NodesView = Ember.View.extend(
+ /** @scope RiakControl.NodesView.prototype */ {
+ templateName: 'nodes'
+ });
+
+ /**
+ * @class
+ *
+ * NodesController is responsible for displaying the list of nodes
+ * in the cluster.
+ */
+ RiakControl.NodesController = Ember.Controller.extend(
+ /**
+ * Shares properties with RiakControl.ClusterController
+ */
+ RiakControl.ClusterAndNodeControls,
+ /** @scope RiakControl.NodesController.prototype */ {
+
+ /**
+ * Reloads the record array associated with this controller.
+ *
+ * @returns {void}
+ */
+ reload: function() {
+ this.get('content').reload();
+ },
+
+ /**
+ * Removes all checks from radio buttons.
+ *
+ * @returns {void}
+ */
+ clearChecked: function(ev) {
+ ev.preventDefault();
+
+ $('#node-list input[type=radio]').each(function(index, item) {
+ item.checked = false;
+ $(item).parent().css('background-position', 'left top');
+ });
+ },
+
+ /**
+ * Submits requests to stop and/or down nodes to the app.
+ */
+ applyChanges: function(ev) {
+ ev.preventDefault();
+
+ var self = this;
+
+ $("#node-list input[type='radio']:checked").each(function(index, item) {
+ var name = item.name,
+ action = item.value,
+ replacement;
+
+ // Empty string instead of undefined for null.
+ if(replacement === undefined) {
+ replacement = '';
+ }
+
+ self.stageChange(name, action, replacement);
+ });
+ }
+
+ });
+
+ /**
+ * @class
+ *
+ * One item in the collection of current cluster views.
+ */
+ RiakControl.CurrentNodesItemView = Ember.View.extend(
+ /**
+ * Shares properties with other views that display lists of nodes.
+ */
+ RiakControl.NodeProperties,
+ /** @scope RiakControl.CurrentNodesItemView.prototype */ {
+
+ /* Bindings from the model */
+
+ templateName: 'current_nodes_item',
+ nameBinding: 'content.name',
+ reachableBinding: 'content.reachable',
+ statusBinding: 'content.status',
+ ring_pctBinding: 'content.ring_pct',
+ pending_pctBinding: 'content.pending_pct',
+ mem_totalBinding: 'content.mem_total',
+ mem_usedBinding: 'content.mem_used',
+ mem_erlangBinding: 'content.mem_erlang',
+ meBinding: 'content.me',
+
+ /**
+ * In order for labels to be clickable, they need to be bound to checks/radios
+ * by ID. However, since these nodes are cloned by Ember, we need a way to make
+ * sure all of those elements get id's that don't override each other. This
+ * function gives us an ID string we can use as a prefix for id's on these other
+ * elements.
+ *
+ * @returns {String}
+ */
+ nodeID: function() {
+ return Ember.guidFor(this);
+ }.property(),
+
+ /**
+ * An ID value for the leave normally radio button and corresponding label.
+ */
+ stopRadio: function() {
+ return this.get('nodeID') + '_stop_node';
+ }.property('nodeID'),
+
+ /**
+ * An ID value for the force leave radio button and corresponding label.
+ */
+ downRadio: function() {
+ return this.get('nodeID') + '_down_node';
+ }.property('nodeID'),
+
+ /**
+ * A node can not be stopped when:
+ * - It is unreachable.
+ * - It is down.
+ */
+ stopRadioClasses: function() {
+ var status = this.get('status'),
+ reachable = this.get('reachable'),
+ classes = 'gui-radio-wrapper';
+ if (!reachable || status === 'down') {
+ classes += ' semi-transparent';
+ }
+ return classes;
+ }.property('status', 'reachable'),
+
+ /**
+ * A node can not be marked as down when:
+ * - It is alive and well
+ * - It is already down.
+ */
+ downRadioClasses: function() {
+ var status = this.get('status'),
+ reachable = this.get('reachable'),
+ classes = 'gui-radio-wrapper';
+ if ((reachable && status === 'valid') || status === 'down') {
+ classes += ' semi-transparent';
+ }
+ return classes;
+ }.property('status', 'reachable'),
+
+ /**
+ * When a node can't be stopped, disable the user
+ * from clicking the stop radio button.
+ */
+ stopDisablerClasses: function() {
+ return 'disabler' + (/\ssemi\-transparent$/.test(this.get('stopRadioClasses')) ? ' show' : '');
+ }.property('stopRadioClasses'),
+
+ /**
+ * When a node can't be downed, disable the user from
+ * clicking the down radio button.
+ */
+ downDisablerClasses: function() {
+ return 'disabler' + (/\ssemi\-transparent$/.test(this.get('downRadioClasses')) ? ' show' : '');
+ }.property('downRadioClasses')
+ });
+
+ /**
+ * @class
+ *
+ * Collection view for showing the current cluster.
+ */
+ RiakControl.CurrentNodesView = Ember.CollectionView.extend(
+ /** @scope RiakControl.CurrentClusterView.prototype */ {
+ itemViewClass: RiakControl.CurrentNodesItemView
+ });
+
+});
View
23 priv/admin/js/router.js
@@ -15,6 +15,8 @@ minispade.register('router', function() {
showCluster: Ember.Route.transitionTo('cluster.index'),
+ showNodes: Ember.Route.transitionTo('nodes.index'),
+
showRing: Ember.Route.transitionTo('ring.index'),
index: Ember.Route.extend({
@@ -67,6 +69,27 @@ minispade.register('router', function() {
})
}),
+ nodes: Ember.Route.extend({
+ route: 'nodes',
+
+ connectOutlets: function(router) {
+ router.get('applicationController').connectOutlet('nodes', RiakControl.Node.find());
+ $.riakControl.markNavActive('nav-nodes');
+ },
+
+ enter: function(router) {
+ router.get('nodesController').startInterval();
+ },
+
+ exit: function(router) {
+ router.get('nodesController').cancelInterval();
+ },
+
+ index: Ember.Route.extend({
+ route: '/'
+ })
+ }),
+
ring: Ember.Route.extend({
route: 'ring',
View
255 priv/admin/js/shared.js
@@ -0,0 +1,255 @@
+minispade.register('shared', function () {
+
+ /**
+ * ClusterAndNodeControls contains properties shared by:
+ * - RiakControl.ClusterController
+ * - RiakControl.NodesController
+ */
+ RiakControl.ClusterAndNodeControls = Ember.Mixin.create({
+
+ /**
+ * Stage a change.
+ *
+ * @returns {void}
+ */
+ stageChange: function(node, action, replacement) {
+ var self = this;
+
+ $.ajax({
+ type: 'PUT',
+ url: '/admin/cluster',
+ dataType: 'json',
+
+ data: { changes:
+ [{
+ node: node,
+ action: action,
+ replacement: replacement
+ }]
+ },
+
+ success: function(d) { self.reload(); },
+
+ error: function (jqXHR, textStatus, errorThrown) {
+ self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
+ }
+ });
+ },
+
+ /**
+ * If content is loading, return true.
+ *
+ * @returns {boolean}
+ */
+ isLoading: false,
+
+ /**
+ * Holds the most recent error message.
+ */
+ errorMessage: '',
+
+ /**
+ * Whenever an ajax call returns an error, we display
+ * the error for the user.
+ *
+ * @param {Object} jqXHR The xhr request object generated by jQuery.
+ * @param {String} textStatus The status of the response.
+ * @param {Error} errorThrown The error object produced by the ajax request.
+ *
+ * @returns {void}
+ */
+ displayError: function (jqXHR, textStatus, errorThrown) {
+ var parsed, errors;
+
+ if(jqXHR) {
+ try {
+ parsed = JSON.parse(jqXHR.responseText);
+ errors = parsed.errors.join(', ');
+ } catch(err) {
+ errors = errorThrown;
+ }
+ } else {
+ errors = errorThrown;
+ }
+
+ this.set('errorMessage', 'Request failed: ' + errors);
+ },
+
+ /**
+ * The action specified on the <a> tag creating the 'x' button in the error message div.
+ * By setting the 'errorMessage' property back to an empty string, the message will disappear.
+ *
+ * @returns {void}
+ */
+ hideError: function () {
+ this.set('errorMessage', '');
+ },
+
+ /**
+ * Called by the router, to start polling when this controller/view is navigated to.
+ *
+ * @returns {void}
+ */
+ startInterval: function() {
+ this._intervalId = setInterval($.proxy(this.reload, this), RiakControl.refreshInterval);
+ },
+
+ /**
+ * Called by the router, to stop polling when this controller/view is navigated away from.
+ *
+ * @returns {void}
+ */
+ cancelInterval: function() {
+ if(this._intervalId) {
+ clearInterval(this._intervalId);
+ }
+ }
+ });
+
+ /**
+ * RiakControl.NodeProperties contains properties shared by:
+ * - RiakControl.CurrentClusterItemView
+ * - RiakControl.StagedClusterItemView
+ * - RiakControl.CurrentNodesItemView
+ */
+ RiakControl.NodeProperties = Ember.Mixin.create({
+
+ /**
+ * Color the lights appropriately based on the node status.
+ *
+ * @returns {string}
+ */
+ indicatorLights: function() {
+ var status = this.get('status');
+ var reachable = this.get('reachable');
+ var color;
+
+ if(reachable === false && status !== "down") {
+ color = "red";
+ } else if (status === "down") {
+ color = "blue";
+ } else if(status === 'leaving' || status === 'joining') {
+ color = "orange";
+ } else if (status === 'valid') {
+ color = "green";
+ } else {
+ color = "grey";
+ }
+
+ return "gui-light status-light inline-block " + color;
+ }.property('reachable', 'status'),
+
+ /**
+ * Color the arrows in the partitions column appropriately based
+ * on how ring_pct and pending_pct compare.
+ *
+ * @returns {String}
+ */
+ coloredArrows: function() {
+ var current = this.get('ring_pct'),
+ pending = this.get('pending_pct'),
+ common = 'left pct-arrows ';
+
+ if (pending > current) {
+ return common + 'pct-gaining';
+ } else if (pending < current) {
+ return common + 'pct-losing';
+ } else {
+ return common + 'pct-static';
+ }
+ }.property('ring_pct', 'pending_pct'),
+
+ /**
+ * Normalizer.
+ *
+ * @returns {number}
+ */
+ memDivider: function() {
+ return this.get('mem_total') / 100;
+ }.property('mem_total'),
+
+ /**
+ * Compute memory ceiling.
+ *
+ * @returns {number}
+ */
+ memErlangCeil: function () {
+ return Math.ceil(this.get('mem_erlang') / this.get('memDivider'));
+ }.property('mem_erlang', 'memDivider'),
+
+ /**
+ * Compute free memory from total and used.
+ *
+ * @returns {number}
+ */
+ memNonErlang: function () {
+ return Math.round(
+ (this.get('mem_used') / this.get('memDivider')) - this.get('memErlangCeil'));
+ }.property('mem_used', 'memDivider', 'memErlangCeil'),
+
+ /**
+ * Compute free memory from total and used.
+ *
+ * @returns {number}
+ */
+ memFree: function () {
+ return this.get('mem_total') - this.get('mem_used');
+ }.property('mem_total', 'mem_used'),
+
+ /**
+ * Format free memory to be a readbale version.
+ *
+ * @returns {number}
+ */
+ memFreeReadable: function () {
+ return Math.round(this.get('memFree') / this.get('memDivider'));
+ }.property('memFree', 'memDivider'),
+
+ /**
+ * Format used memory to be a readbale version.
+ *
+ * @returns {number}
+ */
+ memUsedReadable: function () {
+ return Math.round((this.get('mem_total') - this.get('memFree')) /
+ this.get('memDivider'));
+ }.property('mem_total', 'memFree', 'memDivider'),
+
+ /**
+ * Return CSS style for rendering memory used by Erlang.
+ *
+ * @returns {number}
+ */
+ memErlangStyle: function () {
+ return 'width: ' + this.get('memErlangCeil') + '%';
+ }.property('memErlangCeil'),
+
+ /**
+ * Return CSS style for rendering occupied non-erlang memory.
+ *
+ * @returns {string}
+ */
+ memNonErlangStyle: function () {
+ return 'width: ' + this.get('memNonErlang') + '%';
+ }.property('memNonErlang'),
+
+ /**
+ * Return CSS style for rendering free memory.
+ *
+ * @returns {string}
+ */
+ memFreeStyle: function () {
+ return 'width: ' + this.get('memFreeReadable') + '%';
+ }.property('memFreeReadable'),
+
+ /**
+ * Formatted ring percentage.
+ *
+ * @returns {string}
+ */
+ ringPctReadable: function () {
+ return Math.round(this.get('ring_pct') * 100);
+ }.property('ring_pct')
+ });
+
+});
View
1 priv/admin/js/templates/application.hbs
@@ -4,6 +4,7 @@
<nav>
<ul id="nav-ul">
<li id="nav-ring" class="nav-li"><a {{action showRing href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li>
+ <li id="nav-nodes" class="nav-li"><a {{action showNodes href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li>
<li id="nav-cluster" class="nav-li"><a {{action showCluster href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li>
<li id="nav-snapshot" class="nav-li"><a {{action showSnapshot href=true}} class="gui-text-bold nav-item"></a><span class="indicator"></span></li>
</ul>
View
28 priv/admin/js/templates/current_cluster_item.hbs
@@ -16,21 +16,21 @@
<div class="item3 gui-text ring-pct-box">
<div {{bindAttr class="coloredArrows"}}></div>
<div class="left gui-text pct-box">
- <span class="ring-pct">{{ring_pct_readable}}%</span>
+ <span class="ring-pct">{{ringPctReadable}}%</span>
</div>
<div class="clear"></div>
</div>
<div class="item4 gui-text memory-box">
{{#if reachable}}
<div class="membar-bg">
<div class="mem-colors">
- <div class="erlang-mem mem-color" {{bindAttr style="mem_erlang_style"}} {{bindAttr name="mem_erlang_ceil"}}></div>
- <div class="non-erlang-mem mem-color" {{bindAttr style="mem_non_erlang_style"}} {{bindAttr name="mem_non_erlang"}}></div>
- <div class="unknown-mem" {{bindAttr style="mem_free_style"}} {{bindAttr name="mem_free_readable"}}></div>
+ <div class="erlang-mem mem-color" {{bindAttr style="memErlangStyle"}} {{bindAttr name="memErlangCeil"}}></div>
+ <div class="non-erlang-mem mem-color" {{bindAttr style="memNonErlangStyle"}} {{bindAttr name="memNonErlang"}}></div>
+ <div class="unknown-mem" {{bindAttr style="memFreeStyle"}} {{bindAttr name="memFreeReadable"}}></div>
</div>
<div class="membar-fg"></div>
</div>
- <span class="used-memory">{{mem_used_readable}}%</span>
+ <span class="used-memory">{{memUsedReadable}}%</span>
{{else}}
<div class="membar-bg">
<div class="mem-colors">
@@ -58,16 +58,16 @@
{{/if}}
<div class="replacement-controls">
<div class="gui-radio-wrapper default">
- <input class="gui-radio" type="radio" value="leave" {{bindAttr name="name" id="normal_leave_radio"}} checked="checked"/>
- <label class="serif" {{bindAttr for="normal_leave_radio"}}>Allow this node to leave normally.</label>
+ <input class="gui-radio" type="radio" value="leave" {{bindAttr name="name" id="normalLeaveRadio"}} checked="checked"/>
+ <label class="serif" {{bindAttr for="normalLeaveRadio"}}>Allow this node to leave normally.</label>
</div>
<div class="gui-radio-wrapper">
- <input class="gui-radio" type="radio" value="remove" {{bindAttr name="name" id="force_leave_radio"}} />
- <label class="serif" {{bindAttr for="force_leave_radio"}}>Force this node to leave.</label>
+ <input class="gui-radio" type="radio" value="remove" {{bindAttr name="name" id="forceLeaveRadio"}} />
+ <label class="serif" {{bindAttr for="forceLeaveRadio"}}>Force this node to leave.</label>
</div>
- <div {{bindAttr class="replace_radio_classes"}}>
- <input class="gui-radio" type="radio" value="replace" {{bindAttr name="name" id="replace_radio"}} />
- <label class="serif" {{bindAttr for="replace_radio"}}>Choose a new node to replace this one.</label>
+ <div {{bindAttr class="replaceRadioClasses"}}>
+ <input class="gui-radio" type="radio" value="replace" {{bindAttr name="name" id="replaceRadio"}} />
+ <label class="serif" {{bindAttr for="replaceRadio"}}>Choose a new node to replace this one.</label>
</div>
<div class="extra-actions">
<div class="right-angle-arrow"></div>
@@ -78,8 +78,8 @@
{{view Ember.Select classNames="gui-dropdown" contentBinding="controller.joiningNodes" optionLabelPath="content.name"}}
</div>
<div class="gui-checkbox-wrapper">
- <input class="gui-checkbox" type="checkbox" {{bindAttr name="name" id="force_replace_check"}} value="true" />
- <label class="serif" {{bindAttr for="force_replace_check"}}>Force this replacement?</label>
+ <input class="gui-checkbox" type="checkbox" {{bindAttr name="name" id="forceReplaceCheck"}} value="true" />
+ <label class="serif" {{bindAttr for="forceReplaceCheck"}}>Force this replacement?</label>
</div>
<div class="clear"></div>
<div class="disabler"></div>
View
52 priv/admin/js/templates/current_nodes_item.hbs
@@ -0,0 +1,52 @@
+{{#with view}}
+ <div class="node">
+ <div class="item1">
+ <div {{bindAttr class="stopRadioClasses"}}>
+ <input class="gui-radio" type="radio" value="stop" {{bindAttr name="name" id="stopRadio"}} />
+ <div {{bindAttr class="stopDisablerClasses"}}></div>
+ </div>
+ </div>
+ <div class="item2">
+ <div {{bindAttr class="downRadioClasses"}}>
+ <input class="gui-radio" type="radio" value="down" {{bindAttr name="name" id="downRadio"}} />
+ <div {{bindAttr class="downDisablerClasses"}}></div>
+ </div>
+ </div>
+ <div class="item3 name-box gui-text">
+ <div {{bindAttr class="indicatorLights"}}>
+ </div><div class="gui-text field-container inline-block">
+ <div class="name gui-field">{{name}}</div>
+ </div>
+ </div>
+ <div class="item4 gui-text ring-pct-box">
+ <div {{bindAttr class="coloredArrows"}}></div>
+ <div class="left gui-text pct-box">
+ <span class="ring-pct">{{ringPctReadable}}%</span>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div class="item5 gui-text memory-box">
+ {{#if reachable}}
+ <div class="membar-bg">
+ <div class="mem-colors">
+ <div class="erlang-mem mem-color" {{bindAttr style="memErlangStyle"}} {{bindAttr name="memErlangCeil"}}></div>
+ <div class="non-erlang-mem mem-color" {{bindAttr style="memNonErlangStyle"}} {{bindAttr name="memNonErlang"}}></div>
+ <div class="unknown-mem" {{bindAttr style="memFreeStyle"}} {{bindAttr name="memFreeReadable"}}></div>
+ </div>
+ <div class="membar-fg"></div>
+ </div>
+ <span class="used-memory">{{memUsedReadable}}%</span>
+ {{else}}
+ <div class="membar-bg">
+ <div class="mem-colors">
+ <div class="unknown-mem" style="width: 100%"></div>
+ </div>
+ <div class="membar-fg"></div>
+ </div>
+ <span class="used-memory"></span>
+ {{/if}}
+ </div>
+ <div class="clear"></div>
+ </div><!-- .node -->
+
+{{/with}}
View
58 priv/admin/js/templates/nodes.hbs
@@ -0,0 +1,58 @@
+<div id="nodes-page">
+
+ <section id="title-container">
+ <div class="side-line"></div>
+ <div class="title-box">
+ <span class="vert-border-left"></span>
+ <h1 id="node-headline" class="gui-headline-bold page-title">Node Management</h1>
+ <span class="vert-border-right"></span>
+ </div>
+ <div class="side-line"></div>
+ <div class="clear"></div>
+ </section>
+
+ {{#if errorMessage}}
+ <div class="error-message">
+ <a class="close-error gui-text" {{action hideError target="controller"}}></a>
+ <a class="error-text offline gui-text-flat">{{errorMessage}}</a>
+ </div>
+ {{/if}}
+
+ <div id="current-area">
+ <h2 class="gui-headline">
+ Current Cluster<br/>
+ </h2>
+ <span id="total-number" class="gui-text-flat italic">
+ Click the radio button for each node you would like to stop or mark as down, then click "APPLY" to apply your changes. If the radio button is grayed out, the action is not
+ available due to the current status of the node.
+ </span><br/>
+ <section id="node-list">
+ {{#if controller.isLoading}}
+ <div class="spinner-box">
+ <img id="cluster-spinner" class="spinner" src="/admin/ui/images/spinner.gif">
+ <h4 class="gui-headline-bold">Loading...</h4>
+ </div>
+ {{else}}
+ <ul class="list-header">
+ <li class="item1"><h4 class="gui-headline-bold">Stop</h4></li>
+ <li class="item2"><h4 class="gui-headline-bold">Down</h4></li>
+ <li class="item3"><h4 class="gui-headline-bold">Name &amp; Status</h4></li>
+ <li class="item4"><h4 class="gui-headline-bold">Partitions</h4></li>
+ <li class="item5"><h4 class="gui-headline-bold">RAM Usage</h4></li>
+ </ul>
+ <div class="clear"></div>
+ {{collection RiakControl.CurrentNodesView contentBinding="content"}}
+ {{/if}}
+ </section>
+ <section class="buttons">
+ <a class="gui-point-button-right gui-text-bold right" {{action applyChanges