Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #71 from basho/csm-cluster-management

Add cluster management.
  • Loading branch information...
commit c42c1b18b7d9ff66cd5383034b7fb14ea3e0ab9e 2 parents e8af124 + 53b755d
@cmeiklejohn cmeiklejohn authored
Showing with 2,768 additions and 1,645 deletions.
  1. +5 −4 Makefile
  2. +24 −1 include/riak_control.hrl
  3. +386 −121 priv/admin/css/cluster.styl
  4. +553 −196 priv/admin/css/compiled/style.css
  5. +82 −29 priv/admin/css/general.styl
  6. +17 −1 priv/admin/css/reusable.styl
  7. +12 −3 priv/admin/css/snapshot.styl
  8. BIN  priv/admin/images/actions-toggle-slider.png
  9. BIN  priv/admin/images/actions-toggle-text.png
  10. BIN  priv/admin/images/nav-icons-small.png
  11. BIN  priv/admin/images/pointbutton-right-sprite.png
  12. BIN  priv/admin/images/pointer.png
  13. BIN  priv/admin/images/radio-sprite.png
  14. BIN  priv/admin/images/riak-control-logo-small.png
  15. BIN  priv/admin/images/right-angle-arrow.png
  16. BIN  priv/admin/images/stagebutton-sprite.png
  17. +24 −0 priv/admin/js/app.js
  18. +742 −1 priv/admin/js/cluster.js
  19. +4 −2 priv/admin/js/generated/templates.js
  20. +0 −71 priv/admin/js/legacy/actions.js
  21. +0 −630 priv/admin/js/legacy/cluster.js
  22. +64 −52 priv/admin/js/legacy/gui.js
  23. +5 −2 priv/admin/js/ring.js
  24. +11 −3 priv/admin/js/router.js
  25. +0 −7 priv/admin/js/templates/application.hbs
  26. +96 −113 priv/admin/js/templates/cluster.hbs
  27. +107 −0 priv/admin/js/templates/current_cluster_item.hbs
  28. +29 −0 priv/admin/js/templates/staged_cluster_item.hbs
  29. +1 −1  rebar.config
  30. +381 −0 src/admin_cluster.erl
  31. +0 −67 src/admin_cluster_down.erl
  32. +0 −79 src/admin_cluster_join.erl
  33. +26 −0 src/admin_gui.erl
  34. +0 −90 src/admin_node.erl
  35. +0 −63 src/admin_node_leave.erl
  36. +0 −63 src/admin_node_stop.erl
  37. +24 −0 src/admin_nodes.erl
  38. +14 −0 src/admin_partitions.erl
  39. +16 −15 src/admin_routes.erl
  40. +37 −17 src/riak_control_security.erl
  41. +107 −8 src/riak_control_session.erl
  42. +1 −5 src/riak_control_sup.erl
  43. +0 −1  templates/index.dtl
View
9 Makefile
@@ -31,12 +31,10 @@ APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \
PLT = $(HOME)/.riak_control_dialyzer_plt
check_plt: compile
- dialyzer --check_plt --plt $(PLT) --apps $(APPS) \
- deps/*/ebin
+ dialyzer --check_plt --plt $(PLT) --apps $(APPS)
build_plt: compile
- dialyzer --build_plt --output_plt $(PLT) --apps $(APPS) \
- deps/*/ebin
+ dialyzer --build_plt --output_plt $(PLT) --apps $(APPS)
dialyzer: compile
@echo
@@ -53,3 +51,6 @@ cleanplt:
@echo
sleep 5
rm $(PLT)
+
+typer:
+ typer --annotate -I ../ --plt $(PLT) -r src
View
25 include/riak_control.hrl
@@ -31,6 +31,27 @@
-type ring() :: riak_core_ring:riak_core_ring().
-type handoffs() :: [handoff()].
-type vnodes() :: [vnode()].
+-type plan() :: [] | legacy | ring_not_ready | unavailable.
+
+-type stage_error() :: nodedown
+ | already_leaving
+ | not_member
+ | only_member
+ | is_claimant
+ | invalid_replacement
+ | already_replacement
+ | not_reachable
+ | not_single_node
+ | self_join.
+
+-type action() :: leave
+ | remove
+ | {replace, node()}
+ | {force_replace, node()}.
+
+-type claim_percentage() :: number().
+
+-type change() :: {node(), action()}.
-record(partition_info,
{ index :: index(),
@@ -50,7 +71,9 @@
pending_pct :: float(),
mem_total :: integer(),
mem_used :: integer(),
- mem_erlang :: integer()
+ mem_erlang :: integer(),
+ action :: action(),
+ replacement :: node()
}).
-type partitions() :: [#partition_info{}].
View
507 priv/admin/css/cluster.styl
@@ -4,52 +4,206 @@ CSS to be applied ONLY on the cluster page
*/
@import reusable
+
#cluster-page
-
+
+ .warning
+ display : block
+ padding : 10px 0 0
+ color : riakred
+
+ #current-area, #planned-area
+ float : left
+ width : 47%
+
+ #current-area
+ margin : 0 0 45px 0
+
+
+ #area-separator
+ width : 6%
+ height : 450px
+ float : left
+ text-align : center
+
+ div
+ margin : 0 auto
+ width : 1px
+ height : 100%
+ background : lightWhite
+
+ span.gui-text-flat
+ line-height : 1.5
+
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
+
+ .actions-container
+ css-transition(height .4s ease, margin-bottom .4s ease)
+ overflow : hidden
+ height : 0
+ margin-bottom : 0
+
+ .open .actions-container
+ css-transition(height .4s ease, margin-bottom .4s ease)
+ height : 375px
+ margin-bottom : 25px
+
+ .gui-light
+ vertical-align : middle
+ padding : 9px 5px 9px 14px
+ margin-left : -6px
+ margin-top : -7px
+
+ .field-container
+ width : 75%
+ vertical-align : middle
+
+ .gui-field
+ margin-right : 0
+ overflow : hidden
+
#add-node
padding-top : 40px
- margin-bottom : 25px
+ margin-bottom : 45px
.add-node-table tr td
padding-top : 25px
-
- .more-node-actions
- display : none
- vendor('box-shadow', 0 3px 5px darkened)
- .more-node-actions td
- padding : 0 5px
-
- .button-column
- text-align : right
+ .list-header
+ min-height : 18px
- .switch-box
- dimensions(125px, auto)
- vertical-align : top
-
- div
- width : 88px
- padding-left : 4px
-
- &.on > div
- vendor('box-shadow', inset 0 1px 3px darkened)
- singleCorner(top, left, 4px)
- singleCorner(top, right, 4px)
- background : darkened
+ li
+ float : left
+ margin-bottom : 30px
+ h4
+ display : inline-block
- .status-box
- dimensions(170px, auto)
-
- .actions-header
- padding-left : 6px
+ #node-list, #planned-list
+ margin-top : 20px
+
+ #node-list
+ .item1
+ width : 18%
+ .item2
+ width : 42%
+ .item3
+ width : 18%
+ .item4
+ width : 22%
+
+ .node
+ margin-bottom : 10px
+ position : relative
+
+ .node > div
+ float : left
+
+ .node > div.clear
+ float : none
+
+ .toggle-container
+ overflow : visible
+ padding : 0
+
+ .actions-toggle
+ dimensions(82px, 30px)
+ position : relative
+ overflow : visible
+ padding : 0
+ background : url('/admin/ui/images/actions-toggle-text.png') -5px top no-repeat rgba(0, 0, 0, .3)
+ margin-top : 3px
+
+ a
+ dimensions(48px, 34px)
+ css-transition(left .2s ease)
+ position : absolute
+ display : inline-block
+ background : url('/admin/ui/images/actions-toggle-slider.png') left top no-repeat transparent
+ top : 0
+ left : -5px
+
+ .open .actions-toggle a
+ css-transition(left .2s ease)
+ left : 38px
+
+ .name-box
+ overflow : visible
+
+ .ring-pct, .used-memory, .action-name
+ monospace();
+
+ .used-memory
+ display : inline-block
+ padding : 0 0 0 5px
+ vertical-align : middle
+
+ .action-name
+ display : inline-block
+ padding-top : 8px
+
+ .replacing-box .field-container
+ width : 100%
+ #planned-list
+ .item1
+ width : 40%
+ .item2
+ width : 17%
+ .item3
+ width : 17%
+ .item4
+ width : 26%
+ .plan-details-header
+ margin-top : 30px
+ padding : 10px 0
+
+ .accept-plan
+ position : relative
+ padding : 15px 0
+ margin-top : 15px
+
+ .clear-plan-box
+ border-top : 1px solid lightWhite
+ margin-top : 25px
+ padding-top : 25px
+
+ a
+ display : block
+ margin-top : 15px
+ margin-left : -5px
+
+ .plan-details
+ @extend .gui-text-flat
+ copy-font()
+ line-height : 1.5
+
+ #commit-button
+ absoluteRight(0, 12px)
+
+ .button-column
+ text-align : right
+
.pct-box
margin-top : 8px
display : inline-block
@@ -77,11 +231,14 @@ CSS to be applied ONLY on the cluster page
margin : 0 5px 0 0
display : none
- #node-error
- headline-font()
+ .error-message
+ copy-font()
vendor('box-shadow', 0 2px 3px darkened)
corners()
- padding : 8px 10px 8px 12px
+ font-size : 14px
+ font-style : italic
+ color : mediumGray
+ padding : 8px 10px 9px 12px
background : rgba(232, 78, 78, .5)
border : 1px solid lighten(rgba(232, 78, 78, .5), 15%)
@@ -104,8 +261,6 @@ CSS to be applied ONLY on the cluster page
&:hover
opaque(1)
- .ring_pct-box
- width : 150px
.memory-box
width : 175px
@@ -160,121 +315,231 @@ CSS to be applied ONLY on the cluster page
background-color : #404040
width : 100%
- .action-button
- dimensions(45px, 45px)
- gradient(#616161, #434343)
- corners()
- vendor('box-shadow', 0 2px 3px darkened)
- display : block
- margin-bottom : 5px
-
- &:hover
- gradient(lighten(#616161, 10%), #434343)
-
- &:active, &.pressed
- gradient(#434343, #515151)
-
- .disabled.action-button
- gradient(#434343, #515151) !important
-
- .action-icon
- dimensions(45px, 45px)
- vendor('box-shadow', inset 0 1px 2px lightWhite)
- corners()
- display : block
- background : url('/admin/ui/images/action-button-sprite.png') left top no-repeat transparent
+ .actions-pointer
+ dimensions(72px, 13px)
+ background : url('/admin/ui/images/pointer.png') center top no-repeat transparent
+ margin-top : 8px
.actions-box
- singleCorner(bottom, left, 10px)
- singleCorner(bottom, right, 10px)
- singleCorner(top, left, 4px)
+ corners()
+ width : 92%
background : darkened
- margin-top : -6px
margin-bottom : 15px
border-bottom : 1px solid lightWhite
- padding : 15px
+ padding : 18px
margin-right : 33px
- display : none
+ position : relative
+
+ & > h4
+ display : block
+ border-bottom : 1px solid lightWhite
+ padding : 0 0 12px
+
.actions-box > div > span
- headline-bold()
+ copy-font()
color : lightGray
padding-left : 3px
-
- .markdown-box, .shutdown-box, .leave-cluster-box
- margin-bottom : 10px
- margin-right : 15px
- .shutdown-button .action-icon
- background-position : left bottom
-
- .markdown-button .action-icon
- background-position : 1px 1px
+ .extra-actions
+ opaque(.32)
+ padding-bottom : 25px
+ height : 35px
+ position : relative
+
+ & > div
+ float : left
+
+ .gui-checkbox-wrapper
+ margin-left : 15px
- .leave-cluster-button .action-icon
- background-position : 1px -44px
+ &.active
+ opaque(1)
.replacement-controls
- float : left
- width : auto
+ width : auto
+ margin-top : 18px
+ border-bottom : 1px solid lightWhite
+ margin-bottom : 10px
+
+ div
+ margin-bottom : 8px
+
+ select
+ margin-top : -38px
.replacement-node-dropdown
- width : 100%
- float : left
margin-bottom : 5px
+ .right-angle-arrow
+ dimensions(15px, 18px)
+ background : url('/admin/ui/images/right-angle-arrow.png') left top no-repeat transparent
+ margin : 0 10px 0 10px
+
+ .disabler
+ background : red
+ opaque(.2)
+ absoluteLeft(0, 0)
+ dimensions(100%, 100%)
+ background : transparent
+
+ .stage-button
+ float : right
+
+ .stage-instructions
+ float : left
+ display : block
+ width : 50%
+
+ .no-joining-nodes
+ padding-top : 6px
+
+ .slide-up
+ margin-top : -35px
+
+ .semi-transparent
+ opaque(.32)
+
+
/* RESPONSIVE MEDIA QUERIES */
- @media screen and (min-width : 1100px)
+ @media screen and (max-width : 1200px)
+
+ #area-separator
+ display : none
+
+ #current-area, #planned-area
+ width : 100%
+
+ #planned-area
+ padding-top : 30px
+ border-top : 1px solid lightWhite
+
+ .field-container
+ width : 90%
+
+ #node-list
+ .item1
+ width : 100px
+ .item2
+ width : 500px
+ .item3
+ width : 100px
+ .item4
+ width : 150px
+
+ #planned-list
+ .item1
+ width : 300px
+ .item2
+ width : 100px
+ margin-left : 25px
+ .item3
+ width : 100px
+ .item4
+ width : 350px
+
+ @media screen and (max-width : 950px)
+
+ #node-list
+ .item1
+ width : 100px
+ .item2
+ width : 400px
+ .item3
+ width : 100px
+ .item4
+ width : 150px
+
+ #planned-list
+ .item1
+ width : 275px
+ .item2
+ width : 100px
+ .item3
+ width : 100px
+ .item4
+ width : 225px
+
+ @media screen and (max-width : 850px)
+
+ #node-list
+ .item1
+ width : 100px
+ .item2
+ width : 300px
+
+ .field-container
+ width : 75%
- .replacement-controls
- width : 250px
+ .item3
+ width : 100px
+ .item4
+ width : 150px
- @media screen and (max-width : 959px)
+ #planned-list
+ .item3
+ display : none
- .free-memory
+ .used-memory
display : none
-
- .actions-box
- margin-right : 0
-
- .switch-box
- width : 100px
-
- .switch-box div
- width : 78px
- padding-left : 0
-
- .switch-box > div
- width : auto
- padding : 0
- text-align : center
-
- .actions-header
- padding-left : 0
- text-align : center
- @media screen and (max-width : 699px)
+ @media screen and (max-width : 700px)
- .status-box, .ring_pct-box, .memory-box
- width : auto
+ #node-list
+ .item1
+ width : 100px
+ .item2
+ width : 200px
+
+ .field-container
+ width : 75%
+
+ .item3
+ width : 100px
+ .item4
+ width : 150px
+
+ #planned-list
+ .item1
+ width : 200px
+ .item2
+ width : 100px
+ margin-left : 0
+ .item4
+ width : 200px
+
+ .gui-light
+ display : none
+
+ @media screen and (max-width : 600px)
+
+ #node-list
+ .item1
+ display : none
+ .item2
+ width : 40%
+ .field-container
+ width : 95%
+ .item4
+ width : 100px
+
+ #planned-list
+ .item1
+ width : 40%
+ .field-container
+ width : 95%
+ .item2
+ width : 20%
+ margin-right : 2%
+ .item4
+ width : 38%
+ .field-container
+ width : 95%
- .status-box
- text-align : center
-
- .gui-light span
+ .actions-container
display : none
-
- .markdown-box, .shutdown-box, .leave-cluster-box
- margin-right : 10px
- .replacement-controls
- margin-right : 0
-
- @media screen and (max-width : 499px)
- .switch-box div
- padding: 0 7px 0 0
- margin : 0 0 0 5px
- /* END RESPONSIVE MEDIA QUERIES */
+
View
749 priv/admin/css/compiled/style.css
@@ -412,6 +412,7 @@ footer {
}
footer {
padding: 0 25px 0;
+ margin-bottom: 25px;
}
footer .title-box {
width: 16%;
@@ -484,7 +485,8 @@ footer .side-line {
margin-top: -30px;
}
.gui-point-button,
-.gui-rect-button {
+.gui-rect-button,
+.gui-point-button-right {
width: 137px;
height: 40px;
display: inline-block;
@@ -495,6 +497,9 @@ footer .side-line {
.gui-point-button {
background: url("/admin/ui/images/pointbutton-sprite.png") left top no-repeat transparent;
}
+.gui-point-button-right {
+ background: url("/admin/ui/images/pointbutton-right-sprite.png") left top no-repeat transparent;
+}
.gui-rect-button {
background: url("/admin/ui/images/rectbutton-sprite.png") left top no-repeat transparent;
}
@@ -506,28 +511,38 @@ footer .side-line {
padding-top: 8px;
text-align: center;
}
+.gui-point-button-right .gui-button-msg {
+ padding-right: 11px;
+}
.gui-point-button:hover,
-.gui-rect-button:hover {
+.gui-rect-button:hover,
+.gui-point-button-right:hover {
background-position: left -40px;
}
.gui-point-button:active,
.gui-point-button.pressed,
.gui-rect-button:active,
-.gui-rect-button.pressed {
+.gui-rect-button.pressed,
+.gui-point-button-right:active,
+.gui-point-button-right.pressed {
background-position: left -80px !important;
}
-.gui-point-button:active .gui-button-msg,
-.gui-point-button.pressed .gui-button-msg,
-.gui-rect-button:active .gui-button-msg,
-.gui-rect-button.pressed .gui-button-msg {
+.gui-point-button:active .gui-button-msg,
+.gui-point-button.pressed .gui-button-msg,
+.gui-rect-button:active .gui-button-msg,
+.gui-rect-button.pressed .gui-button-msg,
+.gui-point-button-right:active .gui-button-msg,
+.gui-point-button-right.pressed .gui-button-msg {
padding-top: 9px;
}
input.gui-point-button,
-input.gui-rect-button {
+input.gui-rect-button,
+input.gui-point-button-right {
padding-bottom: 12px;
}
input.gui-point-button:active,
-input.gui-rect-button:active {
+input.gui-rect-button:active,
+input.gui-point-button-right:active {
padding-bottom: 9px;
}
.button-column {
@@ -547,21 +562,42 @@ input.gui-rect-button:active {
.gui-switch.off {
background-position: left bottom;
}
-.gui-checkbox-wrapper {
+.gui-checkbox-wrapper,
+.gui-radio-wrapper {
width: auto;
height: 24px;
background: url("/admin/ui/images/checkbox-sprite.png") left top no-repeat transparent;
vertical-align: middle;
margin-top: 5px;
}
-.gui-checkbox-wrapper span {
+.gui-checkbox-wrapper.default,
+.gui-radio-wrapper.default {
+ background-position: left bottom;
+}
+.gui-checkbox-wrapper span,
+.gui-radio-wrapper span,
+.gui-checkbox-wrapper label,
+.gui-radio-wrapper label {
font-family: 'titillium', helvetica, arial, sans-serif;
font-weight: bold;
display: inline-block;
margin: 5px 0 0 8px;
color: #efefef;
}
-.gui-checkbox {
+.gui-checkbox-wrapper span.serif,
+.gui-radio-wrapper span.serif,
+.gui-checkbox-wrapper label.serif,
+.gui-radio-wrapper label.serif {
+ font-family: 'noticia', georgia, serif;
+ font-weight: normal;
+ color: #ccc;
+ font-style: italic;
+}
+.gui-radio-wrapper {
+ background: url("/admin/ui/images/radio-sprite.png") left top no-repeat transparent;
+}
+.gui-checkbox,
+.gui-radio {
width: 25px;
height: 24px;
opacity: 0;
@@ -570,8 +606,8 @@ input.gui-rect-button:active {
float: left;
margin: 0;
}
-.gui-checkbox[checked=checked],
-.gui-checkbox-wrapper .gui-text {
+.gui-checkbox-wrapper .gui-text,
+.gui-radio-wrapper .gui-text {
width: auto;
height: 24px;
display: block;
@@ -661,7 +697,8 @@ input.gui-rect-button:active {
}
.gui-text-flat,
.gui-text,
-.gui-text-bold {
+.gui-text-bold,
+#cluster-page .plan-details {
text-shadow: none;
color: #ccc;
font-size: 14px;
@@ -693,18 +730,18 @@ input.gui-rect-button:active {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
- -webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- -moz-box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- background: rgba(0,0,0,0.3);
- padding: 8px;
+ -webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,0.35);
+ -moz-box-shadow: inset 0 1px 3px rgba(0,0,0,0.35);
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.35);
+ background: rgba(0,0,0,0.35);
+ padding: 10px;
border-bottom: 1px solid rgba(255,255,255,0.18);
margin-right: 2em;
}
.gui-input {
width: 100%;
height: auto;
- padding: 6px 8px;
+ padding: 8px;
border-left: 0;
border-right: 0;
border-top: 0;
@@ -714,7 +751,7 @@ input.gui-rect-button:active {
}
.gui-light {
background: url("/admin/ui/images/light-sprite.png") left top no-repeat transparent;
- padding: 9px 0 9px 33px;
+ padding: 11px 0 9px 33px;
min-height: 14px;
min-width: 14px;
display: inline-block;
@@ -756,6 +793,9 @@ input.gui-rect-button:active {
font-family: 'noticia', georgia, serif;
font-style: italic;
}
+.serif {
+ font-family: 'noticia', georgia, serif;
+}
.left {
float: left;
}
@@ -766,9 +806,12 @@ input.gui-rect-button:active {
.unreachable {
color: #f05f5f;
}
+.inline-block {
+ display: inline-block;
+}
@media screen and (min-width : 1100px) {
#wrapper {
- width: 1100px;
+ max-width: 1500px;
}
}
@media screen and (max-width : 699px) {
@@ -813,6 +856,41 @@ input.gui-rect-button:active {
}
}
@media screen and (max-width : 499px) {
+ #header {
+ height: 44px;
+ }
+ #navbar,
+ #nav-ul {
+ height: 41px;
+ }
+ .nav-li {
+ width: 45px;
+ height: 41px;
+ }
+ .nav-li .nav-item {
+ width: 45px;
+ height: 41px;
+ background: url("/admin/ui/images/nav-icons-small.png") center bottom no-repeat transparent;
+ }
+ .nav-li .indicator {
+ width: 45px;
+ height: 2px;
+ }
+ #riak-control-logo {
+ width: 123px;
+ height: 32px;
+ background: url("/admin/ui/images/riak-control-logo-small.png") right bottom no-repeat transparent;
+ margin: 6px 0 0 10px;
+ }
+ #nav-snapshot a {
+ background-position: 8px -237px;
+ }
+ #nav-cluster a {
+ background-position: 8px 11px;
+ }
+ #nav-ring a {
+ background-position: 8px -24px;
+ }
h1 {
font-size: 30px;
letter-spacing: 0;
@@ -841,21 +919,32 @@ input.gui-rect-button:active {
}
#snapshot-page #healthy-cluster li,
#snapshot-page #unhealthy-cluster li {
- font-family: 'menlo', 'consolas', 'monaco', monospace;
+ font-family: 'titillium', helvetica, arial, sans-serif;
line-height: 1.5;
+ font-size: 15px;
}
#snapshot-page #healthy-cluster li a,
#snapshot-page #unhealthy-cluster li a {
color: #efefef;
}
#snapshot-page #health-indicator {
- width: 311px;
- height: 309px;
float: left;
margin-right: 25px;
}
+#snapshot-page #healthy-cluster #health-indicator {
+ width: 337px;
+ height: 310px;
+}
+#snapshot-page #unhealthy-cluster #health-indicator {
+ width: 311px;
+ height: 309px;
+}
@media screen and (max-width : 899px) {
- #snapshot-page #health-indicator {
+ #snapshot-page #healthy-cluster #health-indicator {
+ width: 279px;
+ height: 249px;
+ }
+ #snapshot-page #unhealthy-cluster #health-indicator {
width: 250px;
height: 249px;
}
@@ -880,55 +969,238 @@ input.gui-rect-button:active {
text-align: left;
}
}
+#cluster-page .warning {
+ display: block;
+ padding: 10px 0 0;
+ color: #f05f5f;
+}
+#cluster-page #current-area,
+#cluster-page #planned-area {
+ float: left;
+ width: 47%;
+}
+#cluster-page #current-area {
+ margin: 0 0 45px 0;
+}
+#cluster-page #area-separator {
+ width: 6%;
+ height: 450px;
+ float: left;
+ text-align: center;
+}
+#cluster-page #area-separator div {
+ margin: 0 auto;
+ width: 1px;
+ height: 100%;
+ background: rgba(255,255,255,0.18);
+}
+#cluster-page span.gui-text-flat {
+ line-height: 1.5;
+}
#cluster-page h3 {
font-family: 'noticia', georgia, serif;
}
+#cluster-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;
+}
+#cluster-page .spinner-box img,
+#cluster-page .spinner-box h4 {
+ vertical-align: middle;
+}
+#cluster-page .spinner-box h4 {
+ opacity: 0.8;
+ filter: alpha(opacity=80);
+ display: inline-block;
+ padding-top: 6px;
+ padding-left: 10px;
+ line-height: 1.2;
+}
+#cluster-page .actions-container {
+ -webkit-transition: height 0.4s ease;
+ -moz-transition: height 0.4s ease;
+ -ms-transition: height 0.4s ease;
+ -webkit-transition: height 0.4s ease;
+ -moz-transition: height 0.4s ease;
+ -o-transition: height 0.4s ease;
+ transition: height 0.4s ease;
+ overflow: hidden;
+ height: 0;
+ margin-bottom: 0;
+}
+#cluster-page .open .actions-container {
+ -webkit-transition: height 0.4s ease;
+ -moz-transition: height 0.4s ease;
+ -ms-transition: height 0.4s ease;
+ -webkit-transition: height 0.4s ease;
+ -moz-transition: height 0.4s ease;
+ -o-transition: height 0.4s ease;
+ transition: height 0.4s ease;
+ height: 375px;
+ margin-bottom: 25px;
+}
+#cluster-page .gui-light {
+ vertical-align: middle;
+ padding: 9px 5px 9px 14px;
+ margin-left: -6px;
+ margin-top: -7px;
+}
+#cluster-page .field-container {
+ width: 75%;
+ vertical-align: middle;
+}
+#cluster-page .gui-field {
+ margin-right: 0;
+ overflow: hidden;
+}
#cluster-page #add-node {
padding-top: 40px;
- margin-bottom: 25px;
+ margin-bottom: 45px;
}
#cluster-page .add-node-table tr td {
padding-top: 25px;
}
-#cluster-page .more-node-actions {
- display: none;
- -webkit-box-shadow: 0 3px 5px rgba(0,0,0,0.3);
- -moz-box-shadow: 0 3px 5px rgba(0,0,0,0.3);
- box-shadow: 0 3px 5px rgba(0,0,0,0.3);
+#cluster-page .list-header {
+ min-height: 18px;
}
-#cluster-page .more-node-actions td {
- padding: 0 5px;
+#cluster-page .list-header li {
+ float: left;
+ margin-bottom: 30px;
}
-#cluster-page .button-column {
- text-align: right;
+#cluster-page .list-header li h4 {
+ display: inline-block;
}
-#cluster-page .switch-box {
- width: 125px;
- height: auto;
- vertical-align: top;
+#cluster-page #node-list,
+#cluster-page #planned-list {
+ margin-top: 20px;
}
-#cluster-page .switch-box div {
- width: 88px;
- padding-left: 4px;
-}
-#cluster-page .switch-box.on > div {
- -webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- -moz-box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
- -webkit-border-top-left-radius: 4px;
- -moz-border-radius-topleft: 4px;
- border-top-left-radius: 4px;
- -webkit-border-top-right-radius: 4px;
- -moz-border-radius-topright: 4px;
- border-top-right-radius: 4px;
- background: rgba(0,0,0,0.3);
-}
-#cluster-page .status-box {
- width: 170px;
- height: auto;
+#cluster-page #node-list .item1 {
+ width: 18%;
+}
+#cluster-page #node-list .item2 {
+ width: 42%;
+}
+#cluster-page #node-list .item3 {
+ width: 18%;
+}
+#cluster-page #node-list .item4 {
+ width: 22%;
+}
+#cluster-page .node {
+ margin-bottom: 10px;
+ position: relative;
+}
+#cluster-page .node > div {
+ float: left;
+}
+#cluster-page .node > div.clear {
+ float: none;
+}
+#cluster-page .toggle-container {
+ overflow: visible;
+ padding: 0;
+}
+#cluster-page .toggle-container .actions-toggle {
+ width: 82px;
+ height: 30px;
+ position: relative;
+ overflow: visible;
+ padding: 0;
+ background: url("/admin/ui/images/actions-toggle-text.png") -5px top no-repeat rgba(0,0,0,0.3);
+ margin-top: 3px;
+}
+#cluster-page .toggle-container .actions-toggle a {
+ width: 48px;
+ height: 34px;
+ -webkit-transition: left 0.2s ease;
+ -moz-transition: left 0.2s ease;
+ -ms-transition: left 0.2s ease;
+ -webkit-transition: left 0.2s ease;
+ -moz-transition: left 0.2s ease;
+ -o-transition: left 0.2s ease;
+ transition: left 0.2s ease;
+ position: absolute;
+ display: inline-block;
+ background: url("/admin/ui/images/actions-toggle-slider.png") left top no-repeat transparent;
+ top: 0;
+ left: -5px;
+}
+#cluster-page .open .actions-toggle a {
+ -webkit-transition: left 0.2s ease;
+ -moz-transition: left 0.2s ease;
+ -ms-transition: left 0.2s ease;
+ -webkit-transition: left 0.2s ease;
+ -moz-transition: left 0.2s ease;
+ -o-transition: left 0.2s ease;
+ transition: left 0.2s ease;
+ left: 38px;
+}
+#cluster-page .name-box {
+ overflow: visible;
+}
+#cluster-page .ring-pct,
+#cluster-page .used-memory,
+#cluster-page .action-name {
+ font-family: 'menlo', 'consolas', 'monaco', monospace;
+}
+#cluster-page .used-memory {
+ display: inline-block;
+ padding: 0 0 0 5px;
+ vertical-align: middle;
+}
+#cluster-page .action-name {
+ display: inline-block;
+ padding-top: 8px;
+}
+#cluster-page .replacing-box .field-container {
+ width: 100%;
+}
+#cluster-page #planned-list .item1 {
+ width: 40%;
+}
+#cluster-page #planned-list .item2 {
+ width: 17%;
+}
+#cluster-page #planned-list .item3 {
+ width: 17%;
+}
+#cluster-page #planned-list .item4 {
+ width: 26%;
+}
+#cluster-page .plan-details-header {
+ margin-top: 30px;
+ padding: 10px 0;
+}
+#cluster-page .accept-plan {
+ position: relative;
+ padding: 15px 0;
+ margin-top: 15px;
+}
+#cluster-page .clear-plan-box {
+ border-top: 1px solid rgba(255,255,255,0.18);
+ margin-top: 25px;
+ padding-top: 25px;
+}
+#cluster-page .clear-plan-box a {
+ display: block;
+ margin-top: 15px;
+ margin-left: -5px;
+}
+#cluster-page .plan-details {
+ font-family: 'noticia', georgia, serif;
+ line-height: 1.5;
}
-#cluster-page .actions-header {
- padding-left: 6px;
+#cluster-page #commit-button {
+ position: absolute;
+ right: 0;
+ top: 12px;
+}
+#cluster-page .button-column {
+ text-align: right;
}
#cluster-page .pct-box {
margin-top: 8px;
@@ -959,27 +1231,30 @@ input.gui-rect-button:active {
margin: 0 5px 0 0;
display: none;
}
-#cluster-page #node-error {
- font-family: 'titillium', helvetica, arial, sans-serif;
- -webkit-box-shadow: 0 2px 3px rgba(0,0,0,0.3);
- -moz-box-shadow: 0 2px 3px rgba(0,0,0,0.3);
- box-shadow: 0 2px 3px rgba(0,0,0,0.3);
+#cluster-page .error-message {
+ font-family: 'noticia', georgia, serif;
+ -webkit-box-shadow: 0 2px 3px rgba(0,0,0,0.35);
+ -moz-box-shadow: 0 2px 3px rgba(0,0,0,0.35);
+ box-shadow: 0 2px 3px rgba(0,0,0,0.35);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
- padding: 8px 10px 8px 12px;
+ font-size: 14px;
+ font-style: italic;
+ color: #ccc;
+ padding: 8px 10px 9px 12px;
background: rgba(232,78,78,0.5);
border: 1px solid rgba(235,105,105,0.5);
}
-#cluster-page #node-error .error-link {
+#cluster-page .error-message .error-link {
font-family: 'titillium', helvetica, arial, sans-serif;
font-weight: bold;
padding-left: 5px;
}
-#cluster-page #node-error .error-link:hover {
+#cluster-page .error-message .error-link:hover {
text-decoration: underline;
}
-#cluster-page #node-error .close-error {
+#cluster-page .error-message .close-error {
width: 16px;
height: 16px;
opacity: 0.6;
@@ -990,13 +1265,10 @@ input.gui-rect-button:active {
vertical-align: middle;
background: url("/admin/ui/images/close-error.png") left top no-repeat transparent;
}
-#cluster-page #node-error .close-error:hover {
+#cluster-page .error-message .close-error:hover {
opacity: 1;
filter: alpha(opacity=100);
}
-#cluster-page .ring_pct-box {
- width: 150px;
-}
#cluster-page .memory-box {
width: 175px;
}
@@ -1071,167 +1343,252 @@ input.gui-rect-button:active {
background-color: #404040;
width: 100%;
}
-#cluster-page .action-button {
- width: 45px;
- height: 45px;
- background: #434343;
- background: -moz-linear-gradient(top, #616161 0%, #434343 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #616161), color-stop(100%, #434343));
- background: -webkit-linear-gradient(top, #616161 0%, #434343 100%);
- background: -o-linear-gradient(top, #616161 0%, #434343 100%);
- background: -ms-linear-gradient(top, #616161 0%, #434343 100%);
- background: linear-gradient(top bottom, #616161 0%, #434343 100%);
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: 0 2px 3px rgba(0,0,0,0.3);
- -moz-box-shadow: 0 2px 3px rgba(0,0,0,0.3);
- box-shadow: 0 2px 3px rgba(0,0,0,0.3);
- display: block;
- margin-bottom: 5px;
+#cluster-page .actions-pointer {
+ width: 72px;
+ height: 13px;
+ background: url("/admin/ui/images/pointer.png") center top no-repeat transparent;
+ margin-top: 8px;
}
-#cluster-page .action-button:hover {
- background: #434343;
- background: -moz-linear-gradient(top, #717171 0%, #434343 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #717171), color-stop(100%, #434343));
- background: -webkit-linear-gradient(top, #717171 0%, #434343 100%);
- background: -o-linear-gradient(top, #717171 0%, #434343 100%);
- background: -ms-linear-gradient(top, #717171 0%, #434343 100%);
- background: linear-gradient(top bottom, #717171 0%, #434343 100%);
-}
-#cluster-page .action-button:active,
-#cluster-page .action-button.pressed {
- background: #515151;
- background: -moz-linear-gradient(top, #434343 0%, #515151 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #434343), color-stop(100%, #515151));
- background: -webkit-linear-gradient(top, #434343 0%, #515151 100%);
- background: -o-linear-gradient(top, #434343 0%, #515151 100%);
- background: -ms-linear-gradient(top, #434343 0%, #515151 100%);
- background: linear-gradient(top bottom, #434343 0%, #515151 100%);
-}
-#cluster-page .disabled.action-button {
- background: #515151;
- background: -moz-linear-gradient(top, #434343 0%, #515151 100%);
- background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #434343), color-stop(100%, #515151));
- background: -webkit-linear-gradient(top, #434343 0%, #515151 100%);
- background: -o-linear-gradient(top, #434343 0%, #515151 100%);
- background: -ms-linear-gradient(top, #434343 0%, #515151 100%);
- background: linear-gradient(top bottom, #434343 0%, #515151 100%);
-}
-#cluster-page .action-icon {
- width: 45px;
- height: 45px;
- -webkit-box-shadow: inset 0 1px 2px rgba(255,255,255,0.18);
- -moz-box-shadow: inset 0 1px 2px rgba(255,255,255,0.18);
- box-shadow: inset 0 1px 2px rgba(255,255,255,0.18);
+#cluster-page .actions-box {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
- display: block;
- background: url("/admin/ui/images/action-button-sprite.png") left top no-repeat transparent;
-}
-#cluster-page .actions-box {
- -webkit-border-bottom-left-radius: 10px;
- -moz-border-radius-bottomleft: 10px;
- border-bottom-left-radius: 10px;
- -webkit-border-bottom-right-radius: 10px;
- -moz-border-radius-bottomright: 10px;
- border-bottom-right-radius: 10px;
- -webkit-border-top-left-radius: 4px;
- -moz-border-radius-topleft: 4px;
- border-top-left-radius: 4px;
- background: rgba(0,0,0,0.3);
- margin-top: -6px;
+ width: 92%;
+ background: rgba(0,0,0,0.35);
margin-bottom: 15px;
border-bottom: 1px solid rgba(255,255,255,0.18);
- padding: 15px;
+ padding: 18px;
margin-right: 33px;
- display: none;
+ position: relative;
+}
+#cluster-page .actions-box > h4 {
+ display: block;
+ border-bottom: 1px solid rgba(255,255,255,0.18);
+ padding: 0 0 12px;
}
#cluster-page .actions-box > div > span {
- font-family: 'titillium', helvetica, arial, sans-serif;
- font-weight: bold;
+ font-family: 'noticia', georgia, serif;
color: #efefef;
padding-left: 3px;
}
-#cluster-page .markdown-box,
-#cluster-page .shutdown-box,
-#cluster-page .leave-cluster-box {
- margin-bottom: 10px;
- margin-right: 15px;
+#cluster-page .extra-actions {
+ opacity: 0.32;
+ filter: alpha(opacity=32);
+ padding-bottom: 25px;
+ height: 35px;
+ position: relative;
}
-#cluster-page .shutdown-button .action-icon {
- background-position: left bottom;
+#cluster-page .extra-actions > div {
+ float: left;
}
-#cluster-page .markdown-button .action-icon {
- background-position: 1px 1px;
+#cluster-page .extra-actions .gui-checkbox-wrapper {
+ margin-left: 15px;
}
-#cluster-page .leave-cluster-button .action-icon {
- background-position: 1px -44px;
+#cluster-page .extra-actions.active {
+ opacity: 1;
+ filter: alpha(opacity=100);
}
#cluster-page .replacement-controls {
- float: left;
width: auto;
+ margin-top: 18px;
+ border-bottom: 1px solid rgba(255,255,255,0.18);
+ margin-bottom: 10px;
+}
+#cluster-page .replacement-controls div {
+ margin-bottom: 8px;
+}
+#cluster-page .replacement-controls select {
+ margin-top: -38px;
}
#cluster-page .replacement-node-dropdown {
+ margin-bottom: 5px;
+}
+#cluster-page .right-angle-arrow {
+ width: 15px;
+ height: 18px;
+ background: url("/admin/ui/images/right-angle-arrow.png") left top no-repeat transparent;
+ margin: 0 10px 0 10px;
+}
+#cluster-page .disabler {
+ background: #f00;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+ position: absolute;
+ left: 0;
+ top: 0;
width: 100%;
+ height: 100%;
+ background: transparent;
+}
+#cluster-page .stage-button {
+ float: right;
+}
+#cluster-page .stage-instructions {
float: left;
- margin-bottom: 5px;
+ display: block;
+ width: 50%;
}
-@media screen and (min-width : 1100px) {
- #cluster-page .replacement-controls {
- width: 250px;
- }
+#cluster-page .no-joining-nodes {
+ padding-top: 6px;
+}
+#cluster-page .slide-up {
+ margin-top: -35px;
}
-@media screen and (max-width : 959px) {
- #cluster-page .free-memory {
+#cluster-page .semi-transparent {
+ opacity: 0.32;
+ filter: alpha(opacity=32);
+}
+@media screen and (max-width : 1200px) {
+ #cluster-page #area-separator {
display: none;
}
- #cluster-page .actions-box {
- margin-right: 0;
+ #cluster-page #current-area,
+ #cluster-page #planned-area {
+ width: 100%;
}
- #cluster-page .switch-box {
+ #cluster-page #planned-area {
+ padding-top: 30px;
+ border-top: 1px solid rgba(255,255,255,0.18);
+ }
+ #cluster-page .field-container {
+ width: 90%;
+ }
+ #cluster-page #node-list .item1 {
width: 100px;
}
- #cluster-page .switch-box div {
- width: 78px;
- padding-left: 0;
+ #cluster-page #node-list .item2 {
+ width: 500px;
}
- #cluster-page .switch-box > div {
- width: auto;
- padding: 0;
- text-align: center;
+ #cluster-page #node-list .item3 {
+ width: 100px;
}
- #cluster-page .actions-header {
- padding-left: 0;
- text-align: center;
+ #cluster-page #node-list .item4 {
+ width: 150px;
+ }
+ #cluster-page #planned-list .item1 {
+ width: 300px;
+ }
+ #cluster-page #planned-list .item2 {
+ width: 100px;
+ margin-left: 25px;
+ }
+ #cluster-page #planned-list .item3 {
+ width: 100px;
+ }
+ #cluster-page #planned-list .item4 {
+ width: 350px;
}
}
-@media screen and (max-width : 699px) {
- #cluster-page .status-box,
- #cluster-page .ring_pct-box,
- #cluster-page .memory-box {
- width: auto;
+@media screen and (max-width : 950px) {
+ #cluster-page #node-list .item1 {
+ width: 100px;
}
- #cluster-page .status-box {
- text-align: center;
+ #cluster-page #node-list .item2 {
+ width: 400px;
+ }
+ #cluster-page #node-list .item3 {
+ width: 100px;
+ }
+ #cluster-page #node-list .item4 {
+ width: 150px;
+ }
+ #cluster-page #planned-list .item1 {
+ width: 275px;
+ }
+ #cluster-page #planned-list .item2 {
+ width: 100px;
+ }
+ #cluster-page #planned-list .item3 {
+ width: 100px;
+ }
+ #cluster-page #planned-list .item4 {
+ width: 225px;
+ }
+}
+@media screen and (max-width : 850px) {
+ #cluster-page #node-list .item1 {
+ width: 100px;
}
- #cluster-page .gui-light span {
+ #cluster-page #node-list .item2 {
+ width: 300px;
+ }
+ #cluster-page #node-list .item2 .field-container {
+ width: 75%;
+ }
+ #cluster-page #node-list .item3 {
+ width: 100px;
+ }
+ #cluster-page #node-list .item4 {
+ width: 150px;
+ }
+ #cluster-page #planned-list .item3 {
display: none;
}
- #cluster-page .markdown-box,
- #cluster-page .shutdown-box,
- #cluster-page .leave-cluster-box {
- margin-right: 10px;
+ #cluster-page .used-memory {
+ display: none;
}
- #cluster-page .replacement-controls {
- margin-right: 0;
+}
+@media screen and (max-width : 700px) {
+ #cluster-page #node-list .item1 {
+ width: 100px;
+ }
+ #cluster-page #node-list .item2 {
+ width: 200px;
+ }
+ #cluster-page #node-list .item2 .field-container {
+ width: 75%;
+ }
+ #cluster-page #node-list .item3 {
+ width: 100px;
+ }
+ #cluster-page #node-list .item4 {
+ width: 150px;
+ }
+ #cluster-page #planned-list .item1 {
+ width: 200px;
+ }
+ #cluster-page #planned-list .item2 {
+ width: 100px;
+ margin-left: 0;
+ }
+ #cluster-page #planned-list .item4 {
+ width: 200px;
+ }
+ #cluster-page .gui-light {
+ display: none;
}
}
-@media screen and (max-width : 499px) {
- #cluster-page .switch-box div {
- padding: 0 7px 0 0;
- margin: 0 0 0 5px;
+@media screen and (max-width : 600px) {
+ #cluster-page #node-list .item1 {
+ display: none;
+ }
+ #cluster-page #node-list .item2 {
+ width: 40%;
+ }
+ #cluster-page #node-list .item2 .field-container {
+ width: 95%;
+ }
+ #cluster-page #node-list .item4 {
+ width: 100px;
+ }
+ #cluster-page #planned-list .item1 {
+ width: 40%;
+ }
+ #cluster-page #planned-list .item1 .field-container {
+ width: 95%;
+ }
+ #cluster-page #planned-list .item2 {
+ width: 20%;
+ margin-right: 2%;
+ }
+ #cluster-page #planned-list .item4 {
+ width: 38%;
+ }
+ #cluster-page #planned-list .item4 .field-container {
+ width: 95%;
+ }
+ #cluster-page .actions-container {
+ display: none;
}
}
#ring-page #ring-filter {
View
111 priv/admin/css/general.styl
@@ -190,7 +190,8 @@ th
right : 0
footer
- padding : 0 25px 0
+ padding : 0 25px 0
+ margin-bottom : 25px
.title-box
dimensions(16%, auto)
@@ -259,15 +260,16 @@ footer
/* CODE FOR POINT BUTTONS */
-.gui-point-button, .gui-rect-button
+.gui-point-button, .gui-rect-button, .gui-point-button-right
dimensions(137px, 40px)
display : inline-block
cursor : pointer
border : 0
margin-top : 3px
-.gui-point-button { background: url('/admin/ui/images/pointbutton-sprite.png') left top no-repeat transparent; }
-.gui-rect-button { background: url('/admin/ui/images/rectbutton-sprite.png') left top no-repeat transparent; }
+.gui-point-button { background: url('/admin/ui/images/pointbutton-sprite.png') left top no-repeat transparent; }
+.gui-point-button-right { background: url('/admin/ui/images/pointbutton-right-sprite.png') left top no-repeat transparent; }
+.gui-rect-button { background: url('/admin/ui/images/rectbutton-sprite.png') left top no-repeat transparent; }
.gui-button-msg
headline-bold()
@@ -276,21 +278,28 @@ footer
padding-top : 8px
text-align : center
-.gui-point-button:hover, .gui-rect-button:hover
+.gui-point-button-right .gui-button-msg
+ padding-right : 11px
+
+.gui-point-button:hover, .gui-rect-button:hover, .gui-point-button-right:hover
background-position : left -40px
-.gui-point-button:active, .gui-point-button.pressed, .gui-rect-button:active, .gui-rect-button.pressed
+.gui-point-button:active, .gui-point-button.pressed,
+.gui-rect-button:active, .gui-rect-button.pressed,
+.gui-point-button-right:active, .gui-point-button-right.pressed
background-position : left -80px !important
-.gui-point-button:active .gui-button-msg,
-.gui-point-button.pressed .gui-button-msg,
-.gui-rect-button:active .gui-button-msg,
-.gui-rect-button.pressed .gui-button-msg { padding-top : 9px; }
+.gui-point-button:active .gui-button-msg,
+.gui-point-button.pressed .gui-button-msg,
+.gui-rect-button:active .gui-button-msg,
+.gui-rect-button.pressed .gui-button-msg,
+.gui-point-button-right:active .gui-button-msg,
+.gui-point-button-right.pressed .gui-button-msg { padding-top : 9px; }
-input.gui-point-button, input.gui-rect-button
+input.gui-point-button, input.gui-rect-button, input.gui-point-button-right
padding-bottom : 12px
-input.gui-point-button:active, input.gui-rect-button:active
+input.gui-point-button:active, input.gui-rect-button:active, input.gui-point-button-right:active
padding-bottom : 9px
.button-column
@@ -314,39 +323,47 @@ input.gui-point-button:active, input.gui-rect-button:active
/* END CODE FOR TOGGLE SWITCHES */
-/* CODE FOR CHECKBOXES */
+/* CODE FOR CHECKBOXES AND RADIO BUTTONS */
-.gui-checkbox-wrapper
+.gui-checkbox-wrapper, .gui-radio-wrapper
dimensions(auto, 24px)
background : url('/admin/ui/images/checkbox-sprite.png') left top no-repeat transparent
vertical-align : middle
margin-top : 5px
+
+ &.default
+ background-position : left bottom
- span
+ span, label
headline-bold()
display : inline-block
margin : 5px 0 0 8px
color : lightGray
-.gui-checkbox
+ &.serif
+ copy-font()
+ font-weight : normal
+ color : mediumGray
+ font-style : italic
+
+.gui-radio-wrapper
+ background : url('/admin/ui/images/radio-sprite.png') left top no-repeat transparent
+
+.gui-checkbox, .gui-radio
dimensions(25px, 24px)
opaque(0)
display : inline-block
float : left
- margin : 0
-
-.gui-checkbox[checked=checked]
-
-
+ margin : 0
-.gui-checkbox-wrapper .gui-text
+.gui-checkbox-wrapper .gui-text, .gui-radio-wrapper .gui-text
dimensions(auto, 24px)
display : block
float : left
padding-left : 5px
font-size : 15px
-/* END CODE FOR CHECKBOXES */
+/* END CODE FOR CHECKBOXES AND RADIO BUTTONS */
/* CODE FOR PAGINATION */
.pagination
@@ -434,14 +451,14 @@ input.gui-point-button:active, input.gui-rect-button:active
corners()
vendor('box-shadow', inset 0 1px 3px darkened)
background : darkened
- padding : 8px
+ padding : 10px
border-bottom : 1px solid lightWhite
margin-right : 2em
.gui-input
@extend .gui-field
dimensions(100%, auto)
- padding : 6px 8px
+ padding : 8px
border-left : 0
border-right : 0
border-top : 0
@@ -452,7 +469,7 @@ input.gui-point-button:active, input.gui-rect-button:active
.gui-light
background : url('/admin/ui/images/light-sprite.png') left top no-repeat transparent
- padding : 9px 0 9px 33px
+ padding : 11px 0 9px 33px
min-height : 14px
min-width : 14px
display : inline-block
@@ -495,6 +512,9 @@ input.gui-point-button:active, input.gui-rect-button:active
copy-font()
font-style : italic
+.serif
+ copy-font()
+
.left
float : left
@@ -504,13 +524,16 @@ input.gui-point-button:active, input.gui-rect-button:active
.unreachable
color : riakred
+.inline-block
+ display : inline-block
+
/* RESPONSIVE MEDIA QUERIES */
@media screen and (min-width : 1100px)
#wrapper
- width : 1100px
+ max-width : 1500px
@media screen and (max-width : 699px)
@@ -547,8 +570,38 @@ input.gui-point-button:active, input.gui-rect-button:active
width : 212px
@media screen and (max-width : 499px)
+
+ #header
+ height : 44px
+
+ #navbar, #nav-ul
+ height : 41px
+
+ .nav-li
+ dimensions(45px, 41px)
+
+ .nav-item
+ dimensions(45px, 41px)
+ background : url('/admin/ui/images/nav-icons-small.png') center bottom no-repeat transparent
+
+ .indicator
+ dimensions(45px, 2px)
+
+ #riak-control-logo
+ dimensions(123px, 32px)
+ background : url('/admin/ui/images/riak-control-logo-small.png') right bottom no-repeat transparent
+ margin : 6px 0 0 10px
+
+ #nav-snapshot a
+ background-position : 8px -237px
+
+ #nav-cluster a
+ background-position : 8px 11px
+
+ #nav-ring a
+ background-position : 8px -24px
- h1
- hSize(30px, 0, 5px)
+ h1
+ hSize(30px, 0, 5px)
/* END RESPONSIVE MEDIA QUERIES */
View
18 priv/admin/css/reusable.styl
@@ -1,7 +1,7 @@
/* Variables */
coverup = rgba(0, 0, 0, .85)
-darkened = rgba(0, 0, 0, .3)
+darkened = rgba(0, 0, 0, .35)
darkGray = #2d2d2d
mediumGray = #cccccc
lightGray = #efefef
@@ -92,4 +92,20 @@ headline-bold()
headline-font()
font-weight : bold
+// Applies a css transition
+css-transition(args) {
+ -webkit-transition : args
+ -moz-transition : args
+ -ms-transition : args
+ transition : args
+}
+
+// Applies a css transform
+css-transform(args) {
+ -webkit-transform : rotate(args)
+ -moz-transform : rotate(args)
+ -o-transform : rotate(args)
+ transform : rotate(args)
+}
+
/* End Functions */
View
15 priv/admin/css/snapshot.styl
@@ -23,22 +23,31 @@ CSS to be applied ONLY on the snapshot page
margin : 10px 0 0 20px
li
- monospace()
+ headline-font()
line-height : 1.5
+ font-size : 15px
a
color : lightGray
#health-indicator
- dimensions(311px, 309px)
float : left
margin-right : 25px
+ #healthy-cluster #health-indicator
+ dimensions(337px, 310px)
+
+ #unhealthy-cluster #health-indicator
+ dimensions(311px, 309px)
+
/* RESPONSIVE MEDIA QUERIES */
@media screen and (max-width : 899px)
- #health-indicator
+ #healthy-cluster #health-indicator
+ dimensions(279px, 249px)
+
+ #unhealthy-cluster #health-indicator
dimensions(250px, 249px)
#healthy-cluster section, #unhealthy-cluster section
View
BIN  priv/admin/images/actions-toggle-slider.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/actions-toggle-text.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-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/pointbutton-right-sprite.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/pointer.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/radio-sprite.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/riak-control-logo-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/right-angle-arrow.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/stagebutton-sprite.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
24 priv/admin/js/app.js
@@ -41,6 +41,30 @@ minispade.register('app', function() {
RiakControl.store = RiakControl.Store.create();
+ // Automatically add CSRF tokens to AJAX requests.
+ $("body").bind("ajaxSend", function(elm, xhr, s){
+ var csrf_token = $('meta[name=csrf_token]').attr('content');
+
+ if(s.type === 'POST' || s.type === 'PUT') {
+ xhr.setRequestHeader('X-CSRF-Token', csrf_token);
+ }
+ });
+
+ // Set default content-type for AJAX requests.
+ // From: http://stackoverflow.com/questions/1749272/jquery-how-to-put-json-via-ajax
+ $.ajaxSetup({
+ contentType: 'application/json',
+ processData: false
+ });
+
+ // Prefilter for stringifying.
+ // From: http://stackoverflow.com/questions/1749272/jquery-how-to-put-json-via-ajax
+ $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
+ if(options.data) {
+ options.data = JSON.stringify(options.data);
+ }
+ });
+
minispade.require('core');
minispade.require('router');
minispade.require('snapshot');
View
743 priv/admin/js/cluster.js
@@ -3,11 +3,410 @@ minispade.register('cluster', function() {
/**
* @class
*
+ * Responsible for modeling one specific cluster node.
+ */
+ RiakControl.CurrentClusterNode = Ember.Object.extend(
+ /** @scope RiakControl.CurrentClusterNode.prototype */ {});
+
+ /**
+ * @class
+ *
+ * Responsible for modeling one specific cluster node.
+ */
+ RiakControl.StagedClusterNode = Ember.Object.extend(
+ /** @scope RiakControl.StagedClusterNode.prototype */ {
+
+ /**
+ * Does the node have a replacement?
+ *
+ * @returns {boolean}
+ */
+ isReplaced: function() {
+ return this.get('replacement') !== "undefined" ? true : false;
+ }.property('replacement'),
+
+ /**
+ * Is the node taking an action?
+ *
+ * @returns {boolean}
+ */
+ isAction: function() {
+ return this.get('action') !== "undefined" ? true : false;
+ }.property('action')
+
+ });
+
+ /**
+ * @class
+ *
+ * Responsible for modeling the current and staged cluster.
+ */
+ RiakControl.CurrentAndPlannedCluster = Ember.Object.extend(
+ /** @scope RiakControl.CurrentAndPlannedCluster.prototype */ {});
+
+ /**
+ * @class
+ *
* ClusterController is responsible for display the list of nodes
* in the cluster. This controller is basically a placeholder and
* wrapper around the legacy cluster page until we rewrite it.
*/
- RiakControl.ClusterController = Ember.ArrayController.extend();
+ RiakControl.ClusterController = Ember.ObjectController.extend(
+ /** @scope RiakControl.ClusterController.prototype */ {
+
+ /**
+ * Refresh a particular cluster, giving a cluster returned as JSON,
+ * and a cluster modeled in Ember.
+ *
+ * Not computationally efficient at all, but explicit for debugging
+ * the Ember bindings and propagation.
+ *
+ * @returns {void}
+ */
+ refresh: function(newCluster, existingCluster, nodeFactory) {
+ newCluster.forEach(function(node) {
+ var exists = existingCluster.findProperty('name', node.name);
+
+ // If it doesn't exist yet, add it. If it does, update it.
+ if(exists !== undefined) {
+ exists.setProperties(node);
+ } else {
+ existingCluster.pushObject(nodeFactory.create(node));
+ }
+ });
+
+ // Iterate the cluster removing nodes that shouldn't
+ // be there.
+ var changesOccurred = false;
+ var replacementCluster = [];
+
+ existingCluster.forEach(function(node, i) {
+ var exists = newCluster.findProperty('name', node.name);
+
+ if(exists === undefined) {
+ node.destroy();
+ changesOccurred = true;
+ } else {
+ replacementCluster.pushObject(node);
+ }
+ });
+
+ if(changesOccurred) {
+ existingCluster.set('[]', replacementCluster.get('[]'));
+ }
+ },
+
+ /**
+ * Load data from server.
+ *
+ * @returns {void}
+ */
+ load: function() {
+ var self = this;
+
+ $.ajax({
+ type: 'GET',
+ url: '/admin/cluster',
+ dataType: 'json',
+
+ success: function(d) {
+ var updatedCurrentCluster = d.cluster.current;
+ var currentCurrentCluster = self.get('content.currentCluster');
+
+ self.refresh(updatedCurrentCluster,
+ currentCurrentCluster, RiakControl.CurrentClusterNode);
+
+ var updatedStagedCluster = d.cluster.staged;
+
+ if(updatedStagedCluster === 'ring_not_ready') {
+ self.set('ringNotReady', true);
+ } else {
+ self.set('ringNotReady', false);
+ }
+
+ if(updatedStagedCluster === 'legacy') {
+ self.set('legacyRing', true);
+ } else {
+ self.set('legacyRing', false);
+ }
+
+ if($.isArray(updatedStagedCluster)) {
+ var currentStagedCluster = self.get('content.stagedCluster');
+
+ self.refresh(updatedStagedCluster,
+ currentStagedCluster, RiakControl.StagedClusterNode);
+ }
+ },
+
+ error: function (jqXHR, textStatus, errorThrown) {
+ if(jqXHR.status === 404 || jqXHR.status === 0) {
+ self.get('displayError').call(self, undefined, undefined, "The node hosting Riak Control is unavailable.");
+ } else {
+ self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
+ }
+ }
+ });
+ },
+
+ /**
+ * Reload data from server.
+ *
+ * @returns {void}
+ */
+ reload: function() {
+ this.load();
+ },
+
+ /**
+ * 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,
+
+ /**
+ * Holds a boolean tracking if the ring is not yet ready.
+ */
+ ringNotReady: false,
+
+ /**
+ * Holds the most recent error message.
+ */
+ errorMessage: '',
+
+ /**
+ * Return nodes from the current cluster which have not been deleted.
+ *
+ * @returns {Ember.Array}
+ */
+ activeCurrentCluster: function() {
+ return this.get('content.currentCluster').filterProperty('isDestroyed', false);
+ }.property('content.currentCluster', 'content.currentCluster.@each'),
+
+ /**
+ * Return list of nodes joining the cluster.
+ *
+ * @returns {Ember.Array}
+ */
+ joiningNodes: function() {
+ return this.get('activeCurrentCluster').filterProperty('status', 'joining');
+ }.property('activeCurrentCluster', 'activeCurrentCluster.@each'),
+
+ /**
+ * Determines whether or not we have any joining nodes.
+ *
+ * @returns {Boolean}
+ */
+ joiningNodesExist: function () {
+ return this.get('joiningNodes').length ? true : false;
+ }.property('joiningNodes'),
+
+ /**
+ * Holds a boolean tracking whether or not there are any stages in our plan.
+ */
+ emptyPlan: function() {
+ return !this.get('activeStagedCluster.length') > 0;
+ }.property('activeStagedCluster', 'activeStagedCluster.length'),
+
+ /**
+ * Return nodes from the staged cluster which have not been deleted.
+ *
+ * @returns {Ember.Array}
+ */
+ activeStagedCluster: function() {
+ return this.get('content.stagedCluster').filterProperty('isDestroyed', false);
+ }.property('content.stagedCluster', 'content.stagedCluster.@each'),
+
+ commitPlan: function(ev) {
+ ev.preventDefault();
+
+ var self = this;
+ var confirmed = $(document).find("[name='confirmed']:checked").val();
+
+ if(confirmed) {
+ $.ajax({
+ type: 'POST',
+ url: '/admin/cluster',
+ dataType: 'json',
+ success: function(d) { self.reload(); },
+ error: function (jqXHR, textStatus, errorThrown) {
+ self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
+ }
+ });
+ } else {
+ self.get('displayError')(undefined, undefined, "Please confirm the plan.");
+ }
+ },
+
+ /**
+ * Clear the currently staged cluster plan.
+ *
+ * @returns {void}
+ */
+ clearPlan: function(ev) {
+ ev.preventDefault();
+
+ var self = this;
+
+ $.ajax({
+ type: 'DELETE',
+ url: '/admin/cluster',
+ dataType: 'json',
+ success: function(d) { self.reload(); },
+ error: function (jqXHR, textStatus, errorThrown) {
+ self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
+ }
+ });
+ },
+
+ /**
+ * Add a new node.
+ *
+ * @returns {void}
+ */
+ addNode: function(ev) {
+ ev.preventDefault();
+
+ var self = this;
+ var node = this.get('addNodeField');
+
+ this.stageChange(node, "join", "");
+ },
+
+ /**
+ * 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.
+ *
+ * @returns {boolean}
+ */
+ displayPlan: function () {
+ return !this.get('isLoading') && !this.get('ringNotReady') &&
+ !this.get('emptyPlan') && !this.get('legacyRing');