├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── dialyzer.ignore-warnings
├── include
└── riak_control.hrl
├── plugins
├── rebar_js_concatenator_plugin.erl
├── rebar_js_handlebars_plugin.erl
└── rebar_js_stylus_plugin.erl
├── priv
├── admin
│ ├── css
│ │ ├── cluster.styl
│ │ ├── compiled
│ │ │ └── style.css
│ │ ├── fonts.styl
│ │ ├── general.styl
│ │ ├── nodes.styl
│ │ ├── reset.styl
│ │ ├── reusable.styl
│ │ ├── ring.styl
│ │ ├── snapshot.styl
│ │ └── style.styl
│ ├── favicon.ico
│ ├── fonts
│ │ ├── noticiatext-italic-webfont.eot
│ │ ├── noticiatext-italic-webfont.svg
│ │ ├── noticiatext-italic-webfont.ttf
│ │ ├── noticiatext-italic-webfont.woff
│ │ ├── noticiatext-regular-webfont.eot
│ │ ├── noticiatext-regular-webfont.svg
│ │ ├── noticiatext-regular-webfont.ttf
│ │ ├── noticiatext-regular-webfont.woff
│ │ ├── titilliumtext25l002-webfont.eot
│ │ ├── titilliumtext25l002-webfont.svg
│ │ ├── titilliumtext25l002-webfont.ttf
│ │ ├── titilliumtext25l002-webfont.woff
│ │ ├── titilliumtext25l005-webfont.eot
│ │ ├── titilliumtext25l005-webfont.svg
│ │ ├── titilliumtext25l005-webfont.ttf
│ │ └── titilliumtext25l005-webfont.woff
│ ├── images
│ │ ├── action-button-sprite.png
│ │ ├── actions-toggle-slider.png
│ │ ├── actions-toggle-text.png
│ │ ├── add-phase.png
│ │ ├── area-pointer.png
│ │ ├── basho-logo.png
│ │ ├── blue-rect.png
│ │ ├── checkbox-sprite.png
│ │ ├── close-error.png
│ │ ├── cut-bg.png
│ │ ├── dropdown-bg.png
│ │ ├── dropdown-left.png
│ │ ├── dropdown-right.png
│ │ ├── expand.gif
│ │ ├── field-bg.png
│ │ ├── fieldcap-left.png
│ │ ├── fieldcap-right.png
│ │ ├── gui-bg.jpg
│ │ ├── header-bg.png
│ │ ├── healthy-cluster.png
│ │ ├── hide-show-toggle.png
│ │ ├── leavecluster-sprite.png
│ │ ├── leaving-slider.png
│ │ ├── light-sprite.png
│ │ ├── markdown-sprite.png
│ │ ├── mem-bar-overlay.png
│ │ ├── membar-bg.png
│ │ ├── membar-fg.png
│ │ ├── nav-icons-small.png
│ │ ├── nav-icons.png
│ │ ├── node-bg.png
│ │ ├── orange-arrow.png
│ │ ├── pct-arrows-sprite.png
│ │ ├── pointbutton-right-sprite.png
│ │ ├── pointbutton-sprite.png
│ │ ├── pointer.png
│ │ ├── powerbutton-sprite.png
│ │ ├── radio-sprite.png
│ │ ├── rectbutton-sprite.png
│ │ ├── resizer-bg.png
│ │ ├── riak-control-logo-small.png
│ │ ├── riak-control-logo.png
│ │ ├── riak-transparent-small.png
│ │ ├── right-angle-arrow.png
│ │ ├── ring-indicator.png
│ │ ├── slider-groove.png
│ │ ├── slider-handle.png
│ │ ├── spinner.gif
│ │ ├── stagebutton-sprite.png
│ │ ├── tooltip-mark.png
│ │ └── unhealthy-cluster.png
│ └── js
│ │ ├── app.js
│ │ ├── cluster.js
│ │ ├── core.js
│ │ ├── generated
│ │ └── .gitkeep
│ │ ├── legacy
│ │ └── gui.js
│ │ ├── nodes.js
│ │ ├── ring.js
│ │ ├── router.js
│ │ ├── shared.js
│ │ ├── snapshot.js
│ │ ├── templates
│ │ ├── all_unavailable_chart.hbs
│ │ ├── application.hbs
│ │ ├── cluster.hbs
│ │ ├── current_cluster_item.hbs
│ │ ├── current_nodes_item.hbs
│ │ ├── degenerate_preflist_chart.hbs
│ │ ├── loading.hbs
│ │ ├── nodes.hbs
│ │ ├── pagination_item.hbs
│ │ ├── partition.hbs
│ │ ├── quorum_unavailable_chart.hbs
│ │ ├── ring.hbs
│ │ ├── snapshot.hbs
│ │ └── staged_cluster_item.hbs
│ │ └── vendor
│ │ ├── d3.v3.min.js
│ │ ├── ember-1.0.0.js
│ │ ├── ember-data-1.0.0-beta.1.js
│ │ ├── handlebars-1.0.0.js
│ │ ├── jquery-1.10.2.js
│ │ ├── jquery-1.10.2.min.js
│ │ ├── jquery-ui-1.8.16.custom.min.js
│ │ └── minispade.js
└── riak_control.schema
├── rebar
├── rebar.config
├── src
├── riak_control.app.src
├── riak_control.erl
├── riak_control_app.erl
├── riak_control_ring.erl
├── riak_control_routes.erl
├── riak_control_security.erl
├── riak_control_session.erl
├── riak_control_sup.erl
├── riak_control_wm_cluster.erl
├── riak_control_wm_gui.erl
├── riak_control_wm_nodes.erl
└── riak_control_wm_partitions.erl
├── templates
└── index.dtl
├── test
├── cert.pem
├── javascripts
│ ├── admin
│ ├── data
│ │ └── mock_responses.js
│ ├── index.html
│ ├── pages_test.js
│ ├── runner.js
│ ├── support
│ │ ├── jquery.mockjax.js
│ │ ├── qunit-1.12.0.css
│ │ └── qunit-1.12.0.js
│ └── test_helper.js
├── key.pem
└── riak_control_schema_test.erl
└── tools.mk
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | ebin/*
3 | .eunit/*
4 | doc/*
5 | deps/*
6 | erl_crash.dump
7 | EUnit-SASL.log
8 | priv/admin/js/generated/templates.js
9 | priv/admin/js/generated/vendor.js
10 | tags
11 | erln8.config
12 | .local_dialyzer_plt
13 | dialyzer_unhandled_warnings
14 | dialyzer_warnings
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: erlang
2 | notifications:
3 | webhooks: http://basho-engbot.herokuapp.com/travis?key=062744d781a235c60843701b05260c77d4a082b1
4 | email: eng@basho.com
5 | otp_release:
6 | - R15B01
7 | - R15B
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: rel deps test
2 |
3 | all: deps compile
4 |
5 | compile: deps
6 | @./rebar compile
7 |
8 | app:
9 | @./rebar compile skip_deps=true
10 |
11 | deps:
12 | @./rebar get-deps
13 |
14 | clean:
15 | @./rebar clean
16 |
17 | distclean: clean
18 | @./rebar delete-deps
19 |
20 | test: all test_erlang test_javascript
21 |
22 | test_erlang: all
23 | @./rebar skip_deps=true eunit
24 |
25 | define MISSING_PHANTOM_MESSAGE
26 | PhantomJS is missing to run the javascript tests, to install on your OS do the following
27 |
28 | - MacOS via homebrew run `brew install phantomjs`
29 | - Ubuntu `apt-get install phantomjs`
30 | - Other linux distros should check http://phantomjs.org/download.html to download a binary build
31 |
32 | or visit http://phantomjs.org/ for more information.
33 | endef
34 | export MISSING_PHANTOM_MESSAGE
35 |
36 | test_javascript: all
37 | ifeq ($(strip $(shell command -v phantomjs > /dev/null 2>&1 || echo 1)),)
38 | @echo "==> riak_control (qunit)"
39 | @phantomjs test/javascripts/runner.js test/javascripts/index.html
40 | else
41 | @echo "$$MISSING_PHANTOM_MESSAGE"
42 | endif
43 |
44 | include tools.mk
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Riak Control
2 |
3 | Riak Control is a OTP application included in the Riak database which
4 | provides a user interface for cluster planning and visibility. As Riak
5 | Control comes bundled with Riak, you do not need to install this
6 | application separately.
7 |
8 | ## Enabling Riak Control
9 |
10 | For more information on enabling Riak Control, see the
11 | [docs](http://docs.basho.com/riak/latest/ops/advanced/riak-control/).
12 |
--------------------------------------------------------------------------------
/dialyzer.ignore-warnings:
--------------------------------------------------------------------------------
1 | Unknown types:
2 | webmachine_dispatcher:matchterm/0
3 | wrq:reqdata/0
4 |
--------------------------------------------------------------------------------
/include/riak_control.hrl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 |
21 | -type version() :: integer().
22 | -type index() :: integer().
23 | -type status() :: valid | invalid | down | leaving | incompatible | transitioning.
24 | -type home() :: primary | fallback | undefined.
25 | -type service() :: {atom(), home()}.
26 | -type services() :: [service()].
27 | -type owner() :: atom().
28 | -type vnode() :: {{atom(),atom()},atom()}.
29 | -type handoff() :: {atom(),integer(),atom()}.
30 | -type online() :: boolean().
31 | -type ring() :: riak_core_ring:riak_core_ring().
32 | -type handoffs() :: [handoff()].
33 | -type vnodes() :: [vnode()].
34 | -type plan() :: [] | legacy | ring_not_ready | unavailable.
35 | -type single_n_val() :: pos_integer().
36 | -type n_vals() :: [pos_integer()].
37 |
38 | -type stage_error() :: nodedown
39 | | already_leaving
40 | | not_member
41 | | only_member
42 | | is_claimant
43 | | invalid_replacement
44 | | already_replacement
45 | | not_reachable
46 | | not_single_node
47 | | self_join.
48 |
49 | -type action() :: leave
50 | | remove
51 | | {replace, node()}
52 | | {force_replace, node()}.
53 |
54 | -type claim_percentage() :: number().
55 |
56 | -type change() :: {node(), action()}.
57 |
58 | -record(partition_info,
59 | { index :: index(),
60 | partition :: integer(),
61 | owner :: owner(),
62 | vnodes :: services(),
63 | handoffs :: handoffs() }).
64 |
65 | -define(PARTITION_INFO, #partition_info).
66 | -type partition() :: ?PARTITION_INFO{}.
67 | -type partitions() :: [partition()].
68 |
69 | %% Riak 1.3
70 | -record(member_info,
71 | { node :: atom(),
72 | status :: status(),
73 | reachable :: boolean(),
74 | vnodes :: vnodes(),
75 | handoffs :: handoffs(),
76 | ring_pct :: float(),
77 | pending_pct :: float(),
78 | mem_total :: integer(),
79 | mem_used :: integer(),
80 | mem_erlang :: integer() }).
81 |
82 | %% Riak 1.4.1+
83 | -record(member_info_v2,
84 | { node :: atom(),
85 | status :: status(),
86 | reachable :: boolean(),
87 | vnodes :: vnodes(),
88 | handoffs :: handoffs(),
89 | ring_pct :: float(),
90 | pending_pct :: float(),
91 | mem_total :: integer(),
92 | mem_used :: integer(),
93 | mem_erlang :: integer(),
94 | action :: action(),
95 | replacement :: node() }).
96 |
97 | -define(MEMBER_INFO, #member_info_v2).
98 | -type member() :: ?MEMBER_INFO{}.
99 | -type members() :: [member()].
100 |
101 | %% These two should always match, in terms of webmachine dispatcher
102 | %% logic, and ADMIN_BASE_PATH should always end with a /
103 | -define(ADMIN_BASE_PATH, "/admin/").
104 | -define(ADMIN_BASE_ROUTE, ["admin"]).
105 |
106 | %% Value for WWW-Authenticate header
107 | -define(ADMIN_AUTH_HEAD, "Basic realm=riak").
108 |
109 | %% Names of HTTP header fields
110 | -define(HEAD_CTYPE, "Content-Type").
111 |
--------------------------------------------------------------------------------
/plugins/rebar_js_concatenator_plugin.erl:
--------------------------------------------------------------------------------
1 | %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
2 | %% ex: ts=4 sw=4 et
3 | %% -------------------------------------------------------------------
4 | %%
5 | %% rebar: Erlang Build Tools
6 | %%
7 | %% Copyright (c) 2012 Christopher Meiklejohn (christopher.meiklejohn@gmail.com)
8 | %%
9 | %% Permission is hereby granted, free of charge, to any person obtaining a copy
10 | %% of this software and associated documentation files (the "Software"), to deal
11 | %% in the Software without restriction, including without limitation the rights
12 | %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | %% copies of the Software, and to permit persons to whom the Software is
14 | %% furnished to do so, subject to the following conditions:
15 | %%
16 | %% The above copyright notice and this permission notice shall be included in
17 | %% all copies or substantial portions of the Software.
18 | %%
19 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | %% THE SOFTWARE.
26 | %% -------------------------------------------------------------------
27 |
28 | %% The rebar_js_concatenator_plugin module is a plugin for rebar that concatenates
29 | %% javascript files.
30 | %%
31 | %% Configuration options should be placed in rebar.config under
32 | %% 'js_concatenator'. Available options include:
33 | %%
34 | %% doc_root: where to find javascript files to concatenate
35 | %% "priv/assets/javascripts" by default
36 | %%
37 | %% out_dir: where to put concatenated javascript files
38 | %% "priv/assets/javascripts" by default
39 | %%
40 | %% concatenations: list of tuples describing each transformation.
41 | %% empty list by default
42 | %%
43 | %% The default settings are the equivalent of:
44 | %% {js_concatenator, [
45 | %% {out_dir, "priv/assets/javascripts"},
46 | %% {doc_root, "priv/assets/javascripts"},
47 | %% {concatenations, []}
48 | %% ]}.
49 | %%
50 | %% An example of compiling a series of javascript files:
51 | %%
52 | %% {js_concatenator, [
53 | %% {out_dir, "priv/assets/javascripts"},
54 | %% {doc_root, "priv/assets/javascripts"},
55 | %% {concatenations, [
56 | %% {"vendor.js", ["ember.js", "jquery.js"], []},
57 | %% {"application.js", ["models.js", "controllers.js"], []}
58 | %% ]}
59 | %% ]}.
60 | %%
61 |
62 | -module(rebar_js_concatenator_plugin).
63 |
64 | -ifdef(TEST).
65 | -include_lib("eunit/include/eunit.hrl").
66 | -endif.
67 |
68 | -export([compile/2,
69 | clean/2]).
70 |
71 | -export([concatenate/1,
72 | concatenate_files/1]).
73 |
74 | %% ===================================================================
75 | %% Public API
76 | %% ===================================================================
77 |
78 | compile(Config, _AppFile) ->
79 | Options = options(Config),
80 | Concatenations = option(concatenations, Options),
81 | OutDir = option(out_dir, Options),
82 | DocRoot = option(doc_root, Options),
83 | Targets = [{normalize_path(Destination, OutDir),
84 | normalize_paths(Sources, DocRoot),
85 | ConcatOptions} || {Destination, Sources, ConcatOptions} <- Concatenations],
86 | build_each(Targets).
87 |
88 | clean(Config, _AppFile) ->
89 | Options = options(Config),
90 | Concatenations = option(concatenations, Options),
91 | OutDir = option(out_dir, Options),
92 | Targets = [normalize_path(Destination, OutDir) ||
93 | {Destination, _Sources, _ConcatOptions} <- Concatenations],
94 | delete_each(Targets).
95 |
96 | %% @spec concatenate(list()) -> binary()
97 | %% @doc Given a list of sources, concatenate and return.
98 | concatenate(Sources) ->
99 | ListSources = [case is_binary(Source) of true ->
100 | binary_to_list(Source); false -> Source end || Source <- Sources],
101 | list_to_binary(lists:flatten(ListSources)).
102 |
103 | %% @spec concatenate_files(list()) -> list()
104 | %% @doc Given a list of source files, concatenate and return.
105 | concatenate_files(Sources) ->
106 | concatenate([read(Source) || Source <- Sources]).
107 |
108 | %% ===================================================================
109 | %% Internal functions
110 | %% ===================================================================
111 |
112 | options(Config) ->
113 | rebar_config:get_local(Config, js_concatenator, []).
114 |
115 | option(Option, Options) ->
116 | proplists:get_value(Option, Options, default(Option)).
117 |
118 | default(doc_root) -> "priv/assets/javascripts";
119 | default(out_dir) -> "priv/assets/javascripts";
120 | default(concatenations) -> [].
121 |
122 | normalize_paths(Paths, Basedir) ->
123 | lists:foldr(fun(X, Acc) -> [normalize_path(X, Basedir) | Acc] end, [], Paths).
124 | normalize_path(Path, Basedir) ->
125 | filename:join([Basedir, Path]).
126 |
127 | build_each([]) ->
128 | ok;
129 | build_each([{Destination, Sources, ConcatOptions} | Rest]) ->
130 | case any_needs_concat(Sources, Destination) of
131 | true ->
132 | Contents = concatenate_files(Sources),
133 | case file:write_file(Destination, Contents, [write]) of
134 | ok ->
135 | io:format("Built asset ~s~n", [Destination]),
136 | case lists:member(uglify, ConcatOptions) of
137 | true ->
138 | uglify(Destination);
139 | false ->
140 | ok
141 | end;
142 | {error, Reason} ->
143 | rebar_log:log(error, "Building asset ~s failed:~n ~p~n",
144 | [Destination, Reason]),
145 | rebar_utils:abort()
146 | end;
147 | false ->
148 | ok
149 | end,
150 | build_each(Rest).
151 |
152 | uglify(Source) ->
153 | Destination = uglify_destination(Source),
154 | rebar_js_uglifier_plugin:compress(Source, Destination, []).
155 |
156 | uglify_destination(Source) ->
157 | Outdir = filename:dirname(Source),
158 | Basename = filename:basename(Source, ".js"),
159 | normalize_path(lists:flatten(Basename ++ ".min.js"), Outdir).
160 |
161 | read(File) ->
162 | case file:read_file(File) of
163 | {ok, Binary} ->
164 | Binary;
165 | {error, Reason} ->
166 | rebar_log:log(error, "Reading asset ~s failed during concatenation:~n ~p~n",
167 | [File, Reason]),
168 | rebar_utils:abort()
169 | end.
170 |
171 | any_needs_concat(Sources, Destination) ->
172 | lists:any(fun(X) -> needs_concat(X, Destination) end, Sources).
173 | needs_concat(Source, Destination) ->
174 | filelib:last_modified(Destination) < filelib:last_modified(Source).
175 |
176 | delete_each([]) ->
177 | ok;
178 | delete_each([First | Rest]) ->
179 | case file:delete(First) of
180 | ok ->
181 | ok;
182 | {error, enoent} ->
183 | ok;
184 | {error, Reason} ->
185 | rebar_log:log(error, "Failed to delete ~s: ~p\n", [First, Reason])
186 | end,
187 | delete_each(Rest).
188 |
189 | -ifdef(TEST).
190 |
191 | concatenate_test() ->
192 | ListSource1 = "ping",
193 | ListSource2 = "pong",
194 | ListOutput = concatenate([ListSource1, ListSource2]),
195 | ?assertEqual(<<"pingpong">>, ListOutput),
196 | BinarySource1 = <<"ping">>,
197 | BinarySource2 = <<"pong">>,
198 | BinaryOutput = concatenate([BinarySource1, BinarySource2]),
199 | ?assertEqual(<<"pingpong">>, BinaryOutput).
200 |
201 | -endif.
202 |
--------------------------------------------------------------------------------
/plugins/rebar_js_handlebars_plugin.erl:
--------------------------------------------------------------------------------
1 | %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
2 | %% ex: ts=4 sw=4 et
3 | %% -------------------------------------------------------------------
4 | %%
5 | %% rebar: Erlang Build Tools
6 | %%
7 | %% Copyright (c) 2012 Christopher Meiklejohn (christopher.meiklejohn@gmail.com)
8 | %%
9 | %% Permission is hereby granted, free of charge, to any person obtaining a copy
10 | %% of this software and associated documentation files (the "Software"), to deal
11 | %% in the Software without restriction, including without limitation the rights
12 | %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | %% copies of the Software, and to permit persons to whom the Software is
14 | %% furnished to do so, subject to the following conditions:
15 | %%
16 | %% The above copyright notice and this permission notice shall be included in
17 | %% all copies or substantial portions of the Software.
18 | %%
19 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | %% THE SOFTWARE.
26 | %% -------------------------------------------------------------------
27 |
28 | %% The rebar_js_handlebars_plugin module is a plugin for rebar that
29 | %% wraps templates in compiler statements.
30 | %%
31 | %% Configuration options should be placed in rebar.config under
32 | %% 'js_handlebars'. Available options include:
33 | %%
34 | %% doc_root: where to find javascript files to concatenate
35 | %% "priv/assets/javascripts" by default
36 | %%
37 | %% out_dir: where to put concatenated javascript files
38 | %% "priv/www/javascripts" by default
39 | %%
40 | %% templates: list of tuples of format {Destination, [Sources]}
41 | %% empty list by default.
42 | %%
43 | %% source_ext: file extension to truncate to derive module name
44 | %% ".hbs" by default
45 | %%
46 | %% The default settings are the equivalent of:
47 | %%
48 | %% {js_handlebars, [
49 | %% {doc_root, "priv/assets/javascripts"},
50 | %% {out_dir, "priv/www/javascripts"},
51 | %% {target, "Ember.TEMPLATES"},
52 | %% {compiler, "Ember.Handlebars.compile"},
53 | %% {source_ext,".hbs"},
54 | %% {templates, []}
55 | %% ]}.
56 | %%
57 | %% An example of compiling a series of templates files:
58 | %%
59 | %% {js_handlebars, [
60 | %% {doc_root, "priv/assets/javascripts"},
61 | %% {out_dir, "priv/www/javascripts"},
62 | %% {target, "Ember.TEMPLATES"},
63 | %% {compiler, "Ember.Handlebars.compile"},
64 | %% {templates, [{"templates.js", ["sidebar.hbs", "application.hbs"]}]}
65 | %% ]}.
66 | %%
67 |
68 | -module(rebar_js_handlebars_plugin).
69 |
70 | -ifdef(TEST).
71 | -include_lib("eunit/include/eunit.hrl").
72 | -endif.
73 |
74 | -export([compile/2,
75 | clean/2]).
76 |
77 | -export([handlebars/4]).
78 |
79 | %% ===================================================================
80 | %% Public API
81 | %% ===================================================================
82 |
83 | compile(Config, _AppFile) ->
84 | Options = options(Config),
85 | OutDir = option(out_dir, Options),
86 | DocRoot = option(doc_root, Options),
87 | Templates = option(templates, Options),
88 | Targets = [{normalize_path(Destination, OutDir),
89 | normalize_paths(Sources, DocRoot), Options} || {Destination, Sources} <- Templates],
90 | build_each(Targets).
91 |
92 | clean(Config, _AppFile) ->
93 | Options = options(Config),
94 | OutDir = option(out_dir, Options),
95 | Templates = option(templates, Options),
96 | Targets = [normalize_path(Destination, OutDir) || {Destination, _} <- Templates],
97 | delete_each(Targets).
98 |
99 | %% @spec handlebars(list(), list(), list(), list()) -> binary()
100 | %% @doc Generate a handlebars compiler line.
101 | handlebars(Name, Body, Target, Compiler) ->
102 | Targeted = lists:flatten([Target, "['" ++ ensure_list(Name) ++ "']"]),
103 | Compiled = lists:flatten([Compiler, "('" ++ ensure_list(Body) ++ "');\n"]),
104 | list_to_binary(lists:flatten([Targeted, " = " ++ Compiled])).
105 |
106 | %% ===================================================================
107 | %% Internal functions
108 | %% ===================================================================
109 |
110 | options(Config) ->
111 | rebar_config:get_local(Config, js_handlebars, []).
112 |
113 | option(Option, Options) ->
114 | proplists:get_value(Option, Options, default(Option)).
115 |
116 | default(doc_root) -> "priv/assets/javascripts";
117 | default(out_dir) -> "priv/www/javascripts";
118 | default(target) -> "Ember.TEMPLATES";
119 | default(compiler) -> "Ember.Handlebars.compile";
120 | default(source_ext)-> ".hbs";
121 | default(templates) -> [].
122 |
123 | ensure_list(Object) ->
124 | case is_binary(Object) of
125 | true ->
126 | binary_to_list(Object);
127 | false ->
128 | Object
129 | end.
130 |
131 | read(File) ->
132 | case file:read_file(File) of
133 | {ok, Binary} ->
134 | list_to_binary(re:replace(binary_to_list(Binary), "\\n+", "",
135 | [global]));
136 | {error, Reason} ->
137 | rebar_log:log(error, "Reading asset ~s failed during compilation:~n ~p~n",
138 | [File, Reason]),
139 | rebar_utils:abort()
140 | end.
141 |
142 | normalize_paths(Paths, Basedir) ->
143 | lists:foldr(fun(X, Acc) -> [normalize_path(X, Basedir) | Acc] end, [], Paths).
144 | normalize_path(Path, Basedir) ->
145 | filename:join([Basedir, Path]).
146 |
147 | build_each([]) ->
148 | ok;
149 | build_each([{Destination, Sources, Options} | Rest]) ->
150 | Target = option(target, Options),
151 | Compiler = option(compiler, Options),
152 | SourceExt = option(source_ext, Options),
153 | Contents = [handlebars(filename:basename(Source, SourceExt), read(Source), Target, Compiler)
154 | || Source <- Sources],
155 | Concatenated = rebar_js_concatenator_plugin:concatenate(Contents),
156 | case file:write_file(Destination, Concatenated, [write]) of
157 | ok ->
158 | io:format("Compiled handlebars asset ~s~n", [Destination]);
159 | {error, Reason} ->
160 | rebar_log:log(error, "Handlebars compliation of ~s failed:~n ~p~n",
161 | [Destination, Reason]),
162 | rebar_utils:abort()
163 | end,
164 | build_each(Rest).
165 |
166 | delete_each([]) ->
167 | ok;
168 | delete_each([First | Rest]) ->
169 | case file:delete(First) of
170 | ok ->
171 | ok;
172 | {error, enoent} ->
173 | ok;
174 | {error, Reason} ->
175 | rebar_log:log(error, "Failed to delete ~s: ~p\n", [First, Reason])
176 | end,
177 | delete_each(Rest).
178 |
179 | %% ===================================================================
180 | %% Tests
181 | %% ===================================================================
182 |
183 | -ifdef(TEST).
184 |
185 | handlebars_test() ->
186 | Expected = <<"Ember.TEMPLATES['foo'] = Ember.Handlebars.compile('
bar ');\n">>,
187 | ListName = "foo",
188 | ListBody = "bar ",
189 | ListTarget = "Ember.TEMPLATES",
190 | ListCompiler = "Ember.Handlebars.compile",
191 | ListOutput = handlebars(ListName, ListBody, ListTarget, ListCompiler),
192 | ?assertEqual(Expected, ListOutput),
193 |
194 | BinaryName = list_to_binary(ListName),
195 | BinaryBody = list_to_binary(ListBody),
196 | BinaryTarget = list_to_binary(ListTarget),
197 | BinaryCompiler = list_to_binary(ListCompiler),
198 | BinaryOutput = handlebars(BinaryName, BinaryBody, BinaryTarget, BinaryCompiler),
199 | ?assertEqual(Expected, BinaryOutput).
200 |
201 | -endif.
202 |
--------------------------------------------------------------------------------
/plugins/rebar_js_stylus_plugin.erl:
--------------------------------------------------------------------------------
1 | %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
2 | %% ex: ts=4 sw=4 et
3 | %% -------------------------------------------------------------------
4 | %%
5 | %% rebar: Erlang Build Tools
6 | %%
7 | %% Copyright (c) 2012 Christopher Meiklejohn (cmeiklejohn@basho.com)
8 | %%
9 | %% Permission is hereby granted, free of charge, to any person obtaining a copy
10 | %% of this software and associated documentation files (the "Software"), to deal
11 | %% in the Software without restriction, including without limitation the rights
12 | %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | %% copies of the Software, and to permit persons to whom the Software is
14 | %% furnished to do so, subject to the following conditions:
15 | %%
16 | %% The above copyright notice and this permission notice shall be included in
17 | %% all copies or substantial portions of the Software.
18 | %%
19 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | %% THE SOFTWARE.
26 | %% -------------------------------------------------------------------
27 |
28 | %% The rebar_js_stylus_plugin module is a plugin for rebar that
29 | %% preprocesses stylus stylesheet files.
30 | %%
31 | %% Configuration options should be placed in rebar.config under
32 | %% 'js_stylus'. Available options include:
33 | %%
34 | %% stylus_path: path to the uglify executable
35 | %% "/usr/local/bin/stylus" by default
36 | %%
37 | %% doc_root: where to find .styl files to compile
38 | %% "priv/assets/stylesheets" by default
39 | %%
40 | %% out_dir: where to put preprocessed css files
41 | %% "priv/www/stylesheets" by default
42 | %%
43 | %% stylesheets: stylus files to compile
44 | %% [] by default
45 | %%
46 | %% The default settings are the equivalent of:
47 | %% {js_stylus, [
48 | %% {stylus, "/usr/local/bin/stylus"},
49 | %% {doc_root, "priv/assets/stylesheets"},
50 | %% {out_dir, "priv/www/stylesheets"}
51 | %% {stylesheets, []}
52 | %% ]}.
53 | %%
54 |
55 | -module(rebar_js_stylus_plugin).
56 |
57 | -ifdef(TEST).
58 | -include_lib("eunit/include/eunit.hrl").
59 | -endif.
60 |
61 | -export([compile/2,
62 | clean/2]).
63 |
64 | %% ===================================================================
65 | %% Public API
66 | %% ===================================================================
67 |
68 |
69 | compile(Config, _AppFile) ->
70 | Options = options(Config),
71 | DocRoot = option(doc_root, Options),
72 | Stylesheets = option(stylesheets, Options),
73 | Targets = [normalize_path(Stylesheet, DocRoot)
74 | || Stylesheet <- Stylesheets],
75 | process(Targets, Options).
76 |
77 | process([], _Options) ->
78 | ok;
79 | process(Targets, Options) ->
80 | OutDir = option(out_dir, Options),
81 | DocRoot = option(doc_root, Options),
82 | Stylus = option(stylus_path, Options),
83 | case stylus_is_present(Stylus) of
84 | true ->
85 | Cmd = lists:flatten([Stylus, " -o ", OutDir, " ",
86 | string:join(Targets, " ")]),
87 | ShOpts = [{use_stdout, false}, return_on_error],
88 | case rebar_utils:sh(Cmd, ShOpts) of
89 | {ok, _} ->
90 | io:format("Creating stylesheet assets in ~s~n", [OutDir]),
91 | ok;
92 | {error, Reason} ->
93 | rebar_log:log(error, "Stylus asset processing failed failed:~n ~p~n",
94 | [Reason]),
95 | rebar_utils:abort()
96 | end,
97 | ok;
98 | false ->
99 | rebar_log:log(warn,
100 | "Bypassing stylesheet processing of ~s: stylus missing.~n", [DocRoot]),
101 | ok
102 | end.
103 |
104 | clean(_Config, _AppFile) ->
105 | ok.
106 |
107 | %% ===================================================================
108 | %% Internal functions
109 | %% ===================================================================
110 |
111 | options(Config) ->
112 | rebar_config:get_local(Config, js_stylus, []).
113 |
114 | option(Option, Options) ->
115 | proplists:get_value(Option, Options, default(Option)).
116 |
117 | default(doc_root) -> "priv/assets/stylesheets";
118 | default(out_dir) -> "priv/www/stylesheets";
119 | default(stylus_path) -> "/usr/local/bin/stylus";
120 | default(stylesheets) -> [].
121 |
122 | normalize_path(Path, Basedir) -> filename:join([Basedir, Path]).
123 |
124 | stylus_is_present(Stylus) -> filelib:is_file(Stylus).
125 |
--------------------------------------------------------------------------------
/priv/admin/css/cluster.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | CSS to be applied ONLY on the cluster page
4 | */
5 |
6 | @import reusable
7 |
8 |
9 | #cluster-page
10 |
11 | .warning
12 | display : block
13 | padding : 10px 0 0
14 | color : riakred
15 |
16 | #current-area, #planned-area
17 | float : left
18 | width : 47%
19 |
20 | #current-area
21 | margin : 0 0 45px 0
22 |
23 |
24 | #area-separator
25 | width : 6%
26 | height : 450px
27 | float : left
28 | text-align : center
29 |
30 | div
31 | margin : 0 auto
32 | width : 1px
33 | height : 100%
34 | background : lightWhite
35 |
36 | span.gui-text-flat
37 | line-height : 1.5
38 |
39 | h3
40 | copy-font()
41 |
42 | .actions-container
43 | css-transition(height .4s ease, margin-bottom .4s ease)
44 | overflow : hidden
45 | height : 0
46 | margin-bottom : 0
47 |
48 | .open .actions-container
49 | css-transition(height .4s ease, margin-bottom .4s ease)
50 | height : 325px
51 | margin-bottom : 25px
52 |
53 | .open .node.taller .actions-container
54 | height : 375px
55 |
56 | .gui-light
57 | vertical-align : middle
58 | padding : 9px 5px 9px 14px
59 | margin-left : -6px
60 | margin-top : -7px
61 |
62 | .field-container
63 | width : 75%
64 | vertical-align : middle
65 |
66 | .gui-field
67 | margin-right : 0
68 | overflow : hidden
69 |
70 | #add-node
71 | padding-top : 40px
72 | margin-bottom : 45px
73 |
74 | .add-node-table tr td
75 | padding-top : 25px
76 |
77 | .list-header
78 | min-height : 18px
79 |
80 | li
81 | float : left
82 | margin-bottom : 30px
83 |
84 | h4
85 | display : inline-block
86 |
87 | #node-list, #planned-list
88 | margin-top : 20px
89 |
90 | #node-list
91 | .item1
92 | width : 18%
93 | .item2
94 | width : 42%
95 | .item3
96 | width : 18%
97 | .item4
98 | width : 22%
99 |
100 | .node
101 | margin-bottom : 10px
102 | position : relative
103 |
104 | .node > div
105 | float : left
106 |
107 | .node > div.clear
108 | float : none
109 |
110 | .toggle-container
111 | overflow : visible
112 | padding : 0
113 |
114 | .actions-toggle
115 | dimensions(82px, 30px)
116 | position : relative
117 | overflow : visible
118 | padding : 0
119 | background : url('/admin/ui/images/actions-toggle-text.png') -5px top no-repeat rgba(0, 0, 0, .3)
120 | margin-top : 3px
121 |
122 | a
123 | dimensions(48px, 34px)
124 | css-transition(left .2s ease)
125 | position : absolute
126 | display : inline-block
127 | background : url('/admin/ui/images/actions-toggle-slider.png') left top no-repeat transparent
128 | top : 0
129 | left : -5px
130 |
131 | .open .actions-toggle a
132 | css-transition(left .2s ease)
133 | left : 38px
134 |
135 | .name-box
136 | overflow : visible
137 |
138 | .ring-pct, .used-memory, .action-name
139 | monospace();
140 |
141 | .used-memory
142 | display : inline-block
143 | padding : 0 0 0 5px
144 | vertical-align : middle
145 |
146 | .action-name
147 | display : inline-block
148 | padding-top : 8px
149 |
150 | .replacing-box .field-container
151 | width : 100%
152 |
153 | #planned-list
154 | .item1
155 | width : 40%
156 | .item2
157 | width : 17%
158 | .item3
159 | width : 17%
160 | .item4
161 | width : 26%
162 |
163 |
164 | .plan-details-header
165 | margin-top : 30px
166 | padding : 10px 0
167 |
168 | .accept-plan
169 | position : relative
170 | padding : 15px 0
171 | margin-top : 15px
172 |
173 | .clear-plan-box
174 | border-top : 1px solid lightWhite
175 | margin-top : 25px
176 | padding-top : 25px
177 |
178 | a
179 | display : block
180 | margin-top : 15px
181 | margin-left : -5px
182 |
183 | .plan-details
184 | @extend .gui-text-flat
185 | copy-font()
186 | line-height : 1.5
187 |
188 | #commit-button
189 | absoluteRight(0, 12px)
190 |
191 | .button-column
192 | text-align : right
193 |
194 | .pct-box
195 | margin-top : 8px
196 | display : inline-block
197 | vertical-align : middle
198 |
199 | .pct-arrows
200 | dimensions(19px, 33px)
201 | background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat transparent
202 | margin : 0 5px 0 0
203 | display : inline-block
204 | vertical-align : middle
205 |
206 | .pct-gaining
207 | background-position : -19px top
208 |
209 | .pct-losing
210 | background-position : -38px top
211 |
212 | .pct-static
213 | background-position : left top
214 |
215 | .green-pct-arrow
216 | dimensions(19px, 33px)
217 | background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat -57px top transparent
218 | margin : 0 5px 0 0
219 | display : none
220 |
221 | .error-message
222 | copy-font()
223 | vendor('box-shadow', 0 2px 3px darkened)
224 | corners()
225 | font-size : 14px
226 | font-style : italic
227 | color : mediumGray
228 | padding : 8px 10px 9px 12px
229 | background : rgba(232, 78, 78, .5)
230 | border : 1px solid lighten(rgba(232, 78, 78, .5), 15%)
231 |
232 | .error-link
233 | headline-bold()
234 | padding-left : 5px
235 |
236 | &:hover
237 | text-decoration : underline
238 |
239 | .close-error
240 | dimensions(16px, 16px)
241 | opaque(.6)
242 | cursor : pointer
243 | display : inline-block
244 | float : right
245 | vertical-align : middle
246 | background : url('/admin/ui/images/close-error.png') left top no-repeat transparent
247 |
248 | &:hover
249 | opaque(1)
250 |
251 |
252 | .memory-box
253 | width : 175px
254 |
255 | .membar-bg
256 | corners()
257 | dimensions(79px, 30px)
258 | position : relative
259 | display : inline-block
260 | vertical-align : middle
261 |
262 | .membar-fg
263 | corners()
264 | gradient(rgba(255, 255, 255, .4), rgba(255, 255, 255, .05))
265 | vendor('box-shadow', 0 1px 0 lightWhite)
266 | dimensions(100%, 26px)
267 | absoluteLeft(0, 0)
268 | width : 75px
269 | z-index : 5
270 | border : 2px solid #1d1d1d
271 |
272 | .mem-colors
273 | dimensions(75px, 26px)
274 | float : left
275 | margin : 2px 0 0 2px
276 |
277 | .membar-mem
278 | dimensions(auto, 26px)
279 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
280 | float : left
281 | z-index : 1
282 |
283 | .non-erlang-mem
284 | dimensions(auto, 26px)
285 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
286 | float : left
287 | z-index : 1
288 | background-color : rgba(65, 179, 243, .8)
289 |
290 | .erlang-mem
291 | dimensions(auto, 26px)
292 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
293 | float : left
294 | z-index : 1
295 | background-color : rgba(70, 166, 55, .8)
296 |
297 | .unknown-mem
298 | dimensions(auto, 26px)
299 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
300 | float : left
301 | z-index : 1
302 | background-color : #404040
303 | width : 100%
304 |
305 | .actions-pointer
306 | dimensions(72px, 13px)
307 | background : url('/admin/ui/images/pointer.png') center top no-repeat transparent
308 | margin-top : 8px
309 |
310 | .actions-box
311 | corners()
312 | width : 92%
313 | background : darkened
314 | margin-bottom : 15px
315 | border-bottom : 1px solid lightWhite
316 | padding : 18px
317 | margin-right : 33px
318 | position : relative
319 |
320 | & > h4
321 | display : block
322 | border-bottom : 1px solid lightWhite
323 | padding : 0 0 12px
324 |
325 |
326 | .actions-box > div > span
327 | copy-font()
328 | color : lightGray
329 | padding-left : 3px
330 |
331 | .extra-actions
332 | opaque(.32)
333 | padding-bottom : 25px
334 | height : 35px
335 | position : relative
336 |
337 | & > div
338 | float : left
339 |
340 | .gui-checkbox-wrapper
341 | margin-left : 15px
342 |
343 | &.active
344 | opaque(1)
345 |
346 | .replacement-controls
347 | width : auto
348 | margin-top : 18px
349 | border-bottom : 1px solid lightWhite
350 | margin-bottom : 10px
351 |
352 | div
353 | margin-bottom : 8px
354 |
355 | select
356 | margin-top : -38px
357 |
358 | .replacement-node-dropdown
359 | margin-bottom : 5px
360 |
361 | .right-angle-arrow
362 | dimensions(15px, 18px)
363 | background : url('/admin/ui/images/right-angle-arrow.png') left top no-repeat transparent
364 | margin : 0 10px 0 10px
365 |
366 | .disabler
367 | background : red
368 | opaque(.2)
369 | absoluteLeft(0, 0)
370 | dimensions(100%, 100%)
371 | background : transparent
372 |
373 | .stage-button
374 | float : right
375 |
376 | .stage-instructions
377 | float : left
378 | display : block
379 | width : 50%
380 |
381 | .no-joining-nodes
382 | padding-top : 6px
383 |
384 | .slide-up
385 | margin-top : -35px
386 |
387 | .semi-transparent
388 | opaque(.32)
389 |
390 |
391 | /* RESPONSIVE MEDIA QUERIES */
392 |
393 | @media screen and (max-width : 1200px)
394 |
395 | #area-separator
396 | display : none
397 |
398 | #current-area, #planned-area
399 | width : 100%
400 |
401 | #planned-area
402 | padding-top : 30px
403 | border-top : 1px solid lightWhite
404 |
405 | .field-container
406 | width : 90%
407 |
408 | #node-list
409 | .item1
410 | width : 100px
411 | .item2
412 | width : 500px
413 | .item3
414 | width : 100px
415 | .item4
416 | width : 150px
417 |
418 | #planned-list
419 | .item1
420 | width : 300px
421 | .item2
422 | width : 100px
423 | margin-left : 25px
424 | .item3
425 | width : 100px
426 | .item4
427 | width : 350px
428 |
429 | @media screen and (max-width : 950px)
430 |
431 | #node-list
432 | .item1
433 | width : 100px
434 | .item2
435 | width : 400px
436 | .item3
437 | width : 100px
438 | .item4
439 | width : 150px
440 |
441 | #planned-list
442 | .item1
443 | width : 275px
444 | .item2
445 | width : 100px
446 | .item3
447 | width : 100px
448 | .item4
449 | width : 225px
450 |
451 | @media screen and (max-width : 850px)
452 |
453 | #node-list
454 | .item1
455 | width : 100px
456 | .item2
457 | width : 300px
458 |
459 | .field-container
460 | width : 75%
461 |
462 | .item3
463 | width : 100px
464 | .item4
465 | width : 150px
466 |
467 | #planned-list
468 | .item3
469 | display : none
470 |
471 | .used-memory
472 | display : none
473 |
474 | @media screen and (max-width : 700px)
475 |
476 | #node-list
477 | .item1
478 | width : 100px
479 | .item2
480 | width : 200px
481 |
482 | .field-container
483 | width : 75%
484 |
485 | .item3
486 | width : 100px
487 | .item4
488 | width : 150px
489 |
490 | #planned-list
491 | .item1
492 | width : 200px
493 | .item2
494 | width : 100px
495 | margin-left : 0
496 | .item4
497 | width : 200px
498 |
499 | .gui-light
500 | display : none
501 |
502 | @media screen and (max-width : 600px)
503 |
504 | #node-list
505 | .item1
506 | display : none
507 | .item2
508 | width : 40%
509 | .field-container
510 | width : 95%
511 | .item4
512 | width : 100px
513 |
514 | #planned-list
515 | .item1
516 | width : 40%
517 | .field-container
518 | width : 95%
519 | .item2
520 | width : 20%
521 | margin-right : 2%
522 | .item4
523 | width : 38%
524 | .field-container
525 | width : 95%
526 |
527 | .actions-container
528 | display : none
529 |
--------------------------------------------------------------------------------
/priv/admin/css/fonts.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | @font-face styles
4 | */
5 |
6 | @font-face {
7 | font-family: 'noticia';
8 | src: url('/admin/ui/fonts/noticiatext-italic-webfont.eot');
9 | src: url('/admin/ui/fonts/noticiatext-italic-webfont.eot?#iefix') format('embedded-opentype'),
10 | url('/admin/ui/fonts/noticiatext-italic-webfont.woff') format('woff'),
11 | url('/admin/ui/fonts/noticiatext-italic-webfont.ttf') format('truetype'),
12 | url('/admin/ui/fonts/noticiatext-italic-webfont.svg#noticia_textitalic') format('svg');
13 | font-weight: normal;
14 | font-style: italic;
15 |
16 | }
17 |
18 | @font-face {
19 | font-family: 'noticia';
20 | src: url('/admin/ui/fonts/noticiatext-regular-webfont.eot');
21 | src: url('/admin/ui/fonts/noticiatext-regular-webfont.eot?#iefix') format('embedded-opentype'),
22 | url('/admin/ui/fonts/noticiatext-regular-webfont.woff') format('woff'),
23 | url('/admin/ui/fonts/noticiatext-regular-webfont.ttf') format('truetype'),
24 | url('/admin/ui/fonts/noticiatext-regular-webfont.svg#noticia_textregular') format('svg');
25 | font-weight: normal;
26 | font-style: normal;
27 |
28 | }
29 |
30 | @font-face {
31 | font-family: 'titillium';
32 | src: url('/admin/ui/fonts/titilliumtext25l002-webfont.eot');
33 | src: url('/admin/ui/fonts/titilliumtext25l002-webfont.eot?#iefix') format('embedded-opentype'),
34 | url('/admin/ui/fonts/titilliumtext25l002-webfont.woff') format('woff'),
35 | url('/admin/ui/fonts/titilliumtext25l002-webfont.ttf') format('truetype'),
36 | url('/admin/ui/fonts/titilliumtext25l002-webfont.svg#titilliumtext25l600_wt') format('svg');
37 | font-weight: bold;
38 | font-style: normal;
39 |
40 | }
41 |
42 | @font-face {
43 | font-family: 'titillium';
44 | src: url('/admin/ui/fonts/titilliumtext25l005-webfont.eot');
45 | src: url('/admin/ui/fonts/titilliumtext25l005-webfont.eot?#iefix') format('embedded-opentype'),
46 | url('/admin/ui/fonts/titilliumtext25l005-webfont.woff') format('woff'),
47 | url('/admin/ui/fonts/titilliumtext25l005-webfont.ttf') format('truetype'),
48 | url('/admin/ui/fonts/titilliumtext25l005-webfont.svg#titilliumtext25l1_wt') format('svg');
49 | font-weight: normal;
50 | font-style: normal;
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/priv/admin/css/nodes.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | CSS to be applied ONLY on the cluster page
4 | */
5 |
6 | @import reusable
7 |
8 |
9 | #nodes-page
10 |
11 | .right
12 | float : right
13 |
14 | .gui-point-button-right, .gui-rect-button
15 | margin-right : 10px
16 | margin-top : 35px
17 |
18 | #current-area
19 | max-width : 850px
20 | margin : 0 auto 45px auto
21 | padding-top : 40px
22 |
23 | & > span.gui-text-flat
24 | line-height : 1.5
25 | display : block
26 |
27 | h3
28 | copy-font()
29 |
30 | .spinner-box
31 | corners()
32 | text-align : center
33 | background : rgba(255, 255, 255, .05)
34 | padding : 25px
35 |
36 | img, h4
37 | vertical-align : middle
38 |
39 | h4
40 | opaque(.8)
41 | display : inline-block
42 | padding-top : 6px
43 | padding-left : 10px
44 | line-height : 1.2
45 |
46 | .gui-light
47 | vertical-align : middle
48 | padding : 9px 5px 9px 14px
49 | margin-left : -6px
50 | margin-top : -7px
51 |
52 | .field-container
53 | width : 86%
54 | vertical-align : middle
55 |
56 | .gui-field
57 | margin-right : 0
58 | overflow : hidden
59 |
60 | .list-header
61 | min-height : 18px
62 |
63 | li
64 | float : left
65 | margin-bottom : 30px
66 |
67 | h4
68 | display : inline-block
69 |
70 | #node-list
71 | margin-top : 20px
72 |
73 | .item1
74 | width : 6%
75 | .item2
76 | width : 10%
77 | .item3
78 | width : 56%
79 | .item4
80 | width : 13%
81 | .item5
82 | width : 15%
83 |
84 |
85 |
86 |
87 |
88 | .node
89 | margin-bottom : 10px
90 | position : relative
91 |
92 | .node > div
93 | float : left
94 |
95 | .node > div.clear
96 | float : none
97 |
98 | .node
99 |
100 | .gui-radio-wrapper
101 | dimensions(25px, 24px)
102 | text-align : center
103 | display : inline-block
104 | position : relative
105 |
106 | .disabler
107 | dimensions(100%, 100%)
108 | absoluteLeft(0, 0);
109 | opaque(0)
110 | display : none
111 |
112 | &.show
113 | display : block
114 |
115 | .item1 .gui-radio-wrapper
116 | margin-left : 4px
117 |
118 | .item2 .gui-radio-wrapper
119 | margin-left : 8px
120 |
121 | input[type=radio]
122 | display : inline
123 | float : none
124 |
125 | .name-box
126 | overflow : visible
127 |
128 | .ring-pct, .used-memory, .action-name
129 | monospace();
130 |
131 | .used-memory
132 | display : inline-block
133 | padding : 0 0 0 5px
134 | vertical-align : middle
135 |
136 | .pct-box
137 | margin-top : 8px
138 | display : inline-block
139 | vertical-align : middle
140 |
141 | .pct-arrows
142 | dimensions(19px, 33px)
143 | background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat transparent
144 | margin : 0 5px 0 0
145 | display : inline-block
146 | vertical-align : middle
147 |
148 | .pct-gaining
149 | background-position : -19px top
150 |
151 | .pct-losing
152 | background-position : -38px top
153 |
154 | .pct-static
155 | background-position : left top
156 |
157 | .green-pct-arrow
158 | dimensions(19px, 33px)
159 | background : url('/admin/ui/images/pct-arrows-sprite.png') no-repeat -57px top transparent
160 | margin : 0 5px 0 0
161 | display : none
162 |
163 | .memory-box
164 | width : 175px
165 |
166 | .membar-bg
167 | corners()
168 | dimensions(79px, 30px)
169 | position : relative
170 | display : inline-block
171 | vertical-align : middle
172 |
173 | .membar-fg
174 | corners()
175 | gradient(rgba(255, 255, 255, .4), rgba(255, 255, 255, .05))
176 | vendor('box-shadow', 0 1px 0 lightWhite)
177 | dimensions(100%, 26px)
178 | absoluteLeft(0, 0)
179 | width : 75px
180 | z-index : 5
181 | border : 2px solid #1d1d1d
182 |
183 | .mem-colors
184 | dimensions(75px, 26px)
185 | float : left
186 | margin : 2px 0 0 2px
187 |
188 | .membar-mem
189 | dimensions(auto, 26px)
190 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
191 | float : left
192 | z-index : 1
193 |
194 | .non-erlang-mem
195 | dimensions(auto, 26px)
196 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
197 | float : left
198 | z-index : 1
199 | background-color : rgba(65, 179, 243, .8)
200 |
201 | .erlang-mem
202 | dimensions(auto, 26px)
203 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
204 | float : left
205 | z-index : 1
206 | background-color : rgba(70, 166, 55, .8)
207 |
208 | .unknown-mem
209 | dimensions(auto, 26px)
210 | background : url('/admin/ui/images/mem-bar-overlay.png') left top repeat transparent
211 | float : left
212 | z-index : 1
213 | background-color : #404040
214 | width : 100%
215 |
216 |
217 | .slide-up
218 | margin-top : -35px
219 |
220 | .semi-transparent
221 | opaque(.32)
222 |
223 |
224 | /* RESPONSIVE MEDIA QUERIES */
225 |
226 | @media screen and (max-width : 800px)
227 |
228 | #node-list
229 | .item1
230 | width : 8%
231 | .item2
232 | width : 10%
233 | .item3
234 | width : 49%
235 | .item4
236 | width : 15%
237 | .item5
238 | width : 18%
239 |
240 | .field-container
241 | width : 75%
242 |
243 |
244 | @media screen and (max-width : 670px)
245 |
246 | #node-list
247 | .memory-box
248 | padding-top : 7px
249 |
250 | .membar-bg
251 | display : none
252 |
253 | @media screen and (max-width : 570px)
254 |
255 | #node-list
256 | .item1
257 | width : 12%
258 | .item2
259 | width : 15%
260 | .item3
261 | width : 53%
262 | .item4
263 | display : none
264 | .item5
265 | width : 20%
266 |
267 | @media screen and (max-width : 400px)
268 |
269 | #node-list
270 | .item1
271 | width : 12%
272 | .item2
273 | width : 15%
274 | .item3
275 | width : 47%
276 | .item4
277 | display : none
278 | .item5
279 | width : 15%
280 |
281 |
282 |
283 |
284 |
285 |
--------------------------------------------------------------------------------
/priv/admin/css/reset.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | http://meyerweb.com/eric/tools/css/reset/
4 | v2.0 | 20110126
5 | License: none (public domain)
6 | */
7 |
8 | html, body, div, span, applet, object, iframe,
9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
10 | a, abbr, acronym, address, big, cite, code,
11 | del, dfn, em, img, ins, kbd, q, s, samp,
12 | small, strike, strong, sub, sup, tt, var,
13 | b, u, i, center,
14 | dl, dt, dd, ol, ul, li,
15 | fieldset, form, label, legend,
16 | table, caption, tbody, tfoot, thead, tr, th, td,
17 | article, aside, canvas, details, embed,
18 | figure, figcaption, footer, header, hgroup,
19 | menu, nav, output, ruby, section, summary,
20 | time, mark, audio, video {
21 | margin: 0;
22 | padding: 0;
23 | border: 0;
24 | font-size: 100%;
25 | font: inherit;
26 | vertical-align: baseline;
27 | }
28 | /* HTML5 display-role reset for older browsers */
29 | article, aside, details, figcaption, figure,
30 | footer, header, hgroup, menu, nav, section {
31 | display: block;
32 | }
33 | body {
34 | line-height: 1;
35 | }
36 | ol, ul {
37 | list-style: none;
38 | }
39 | blockquote, q {
40 | quotes: none;
41 | }
42 | blockquote:before, blockquote:after,
43 | q:before, q:after {
44 | content: '';
45 | content: none;
46 | }
47 | table {
48 | border-collapse: collapse;
49 | border-spacing: 0;
50 | }
51 | td {
52 | vertical-align: top;
53 | }
54 |
--------------------------------------------------------------------------------
/priv/admin/css/reusable.styl:
--------------------------------------------------------------------------------
1 |
2 | /* Variables */
3 | coverup = rgba(0, 0, 0, .85)
4 | darkened = rgba(0, 0, 0, .35)
5 | darkGray = #2d2d2d
6 | mediumGray = #cccccc
7 | lightGray = #efefef
8 | lightWhite = rgba(255, 255, 255, .18)
9 | highlight = rgba(255, 255, 255, .08)
10 | offlight = #767676
11 | riakorange = #f99d33
12 | lightorange = lighten(riakorange, 33%)
13 | riakred = #f05f5f
14 |
15 | /* End Variables */
16 |
17 | /* Functions */
18 | vendor(prop, args)
19 | -webkit-{prop} : args
20 | -moz-{prop} : args
21 | -ms-{prop} : args
22 | -o-{prop} : args
23 | {prop} : args
24 |
25 | gradient(lightcolor, darkcolor)
26 | background : darkcolor
27 | background : -moz-linear-gradient( top, lightcolor 0%, darkcolor 100%)
28 | background : -webkit-gradient(linear, left top, left bottom, color-stop(0%, lightcolor), color-stop(100%, darkcolor))
29 | background : -webkit-linear-gradient( top, lightcolor 0%, darkcolor 100%)
30 | background : -o-linear-gradient( top, lightcolor 0%, darkcolor 100%)
31 | background : -ms-linear-gradient( top, lightcolor 0%, darkcolor 100%)
32 | background : linear-gradient( top bottom, lightcolor 0%, darkcolor 100%)
33 |
34 | radialGradient(lightcolor, darkcolor)
35 | background-image : -moz-radial-gradient( center, lightcolor 0%, darkcolor 100%)
36 | background-image : -webkit-gradient(radial, center, color-stop(0%, lightcolor), color-stop(100%, darkcolor))
37 | background-image : -webkit-radial-gradient( center, lightcolor 0%, darkcolor 100%)
38 | background-image : -o-radial-gradient( center, lightcolor 0%, darkcolor 100%)
39 | background-image : -ms-radial-gradient( center, lightcolor 0%, darkcolor 100%)
40 | background-image : radial-gradient( center, lightcolor 0%, darkcolor 100%)
41 |
42 | corners(curve = 4px)
43 | vendor('border-radius', curve)
44 |
45 | singleCorner(vert, horiz, args = 4px)
46 | -webkit-border-{vert}-{horiz}-radius : args
47 | -moz-border-radius-{vert}{horiz} : args
48 | border-{vert}-{horiz}-radius : args
49 |
50 | textStroke(args = 1px #fff)
51 | vendor('text-stroke', args)
52 |
53 | textShadow(args = 0 2px 0 #000)
54 | text-shadow : args
55 |
56 | opaque(dec = 1)
57 | opacity : dec
58 | filter : unquote('alpha(opacity=' + (dec * 100) + ')')
59 |
60 | repeatingBg(path, bgcolor = transparent)
61 | background : url(path) left top repeat bgcolor
62 |
63 | hSize(fs, ls, pb) // General stylings for h1, h2, h3, etc tags
64 | font-size : fs
65 | letter-spacing : ls
66 | padding-bottom : pb
67 |
68 | dimensions(wUnit = auto, hUnit = auto)
69 | width : wUnit
70 | height : hUnit
71 |
72 | absoluteLeft(lUnit, tUnit)
73 | position : absolute
74 | left : lUnit
75 | top : tUnit
76 |
77 | absoluteRight(rUnit, tUnit)
78 | position : absolute
79 | right : rUnit
80 | top : tUnit
81 |
82 | transition(args)
83 | -webkit-transition : args
84 | -moz-transition : args
85 | -o-transition : args
86 | transition : args
87 |
88 | headline-font()
89 | font-family : 'titillium', helvetica, arial, sans-serif
90 |
91 | copy-font()
92 | font-family : 'noticia', georgia, serif
93 |
94 | monospace()
95 | font-family : 'menlo', 'consolas', 'monaco', monospace
96 |
97 | copy-bold()
98 | copy-font()
99 | font-weight : bold
100 |
101 | headline-bold()
102 | headline-font()
103 | font-weight : bold
104 |
105 | // Applies a css transition
106 | css-transition(args)
107 | -webkit-transition : args
108 | -moz-transition : args
109 | -ms-transition : args
110 | transition : args
111 |
112 | // Applies a css transform
113 | css-transform(args)
114 | -webkit-transform : rotate(args)
115 | -moz-transform : rotate(args)
116 | -o-transform : rotate(args)
117 | transform : rotate(args)
118 |
119 |
120 | /* End Functions */
121 |
--------------------------------------------------------------------------------
/priv/admin/css/snapshot.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | CSS to be applied ONLY on the snapshot page
4 | */
5 |
6 | @import reusable
7 |
8 | #snapshot-page
9 |
10 | #healthy-cluster, #unhealthy-cluster
11 | padding-top : 25px
12 |
13 | section
14 | padding-left : 336px
15 |
16 | h2
17 | padding-top : 20px
18 |
19 | h3
20 | margin-top : 30px
21 | line-height : 1.5
22 |
23 | h4
24 | copy-font()
25 | margin-top : 20px
26 | font-size: 14px
27 | font-style: italic
28 | line-height : 1.5
29 |
30 | ul
31 | margin : 10px 0 0 20px
32 |
33 | li
34 | headline-font()
35 | line-height : 1.5
36 | font-size : 15px
37 |
38 | a
39 | color : lightGray
40 |
41 | #health-indicator
42 | float : left
43 | margin-right : 25px
44 |
45 | #healthy-cluster #health-indicator
46 | dimensions(337px, 310px)
47 |
48 | #unhealthy-cluster #health-indicator
49 | dimensions(311px, 309px)
50 |
51 | /* RESPONSIVE MEDIA QUERIES */
52 |
53 | @media screen and (max-width : 899px)
54 |
55 | #healthy-cluster #health-indicator
56 | dimensions(279px, 249px)
57 |
58 | #unhealthy-cluster #health-indicator
59 | dimensions(250px, 249px)
60 |
61 | #healthy-cluster section, #unhealthy-cluster section
62 | padding-left : 275px
63 |
64 | @media screen and (max-width : 499px)
65 |
66 | .health-info
67 | text-align : center
68 |
69 | #health-indicator
70 | margin : 0 auto
71 | float : none
72 | display : block
73 |
74 | #healthy-cluster section, #unhealthy-cluster section
75 | padding-left : 0
76 | margin : 0 auto
77 | text-align : left
78 |
79 |
80 | /* END RESPONSIVE MEDIA QUERIES */
81 |
82 |
--------------------------------------------------------------------------------
/priv/admin/css/style.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Main Stylus CSS File
4 | Imports and arranges all other stylus files.
5 | */
6 |
7 | @import reusable
8 | @import reset
9 | @import fonts
10 | @import general
11 | @import snapshot
12 | @import cluster
13 | @import nodes
14 | @import ring
15 |
--------------------------------------------------------------------------------
/priv/admin/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/favicon.ico
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-italic-webfont.eot
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-italic-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-italic-webfont.ttf
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-italic-webfont.woff
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-regular-webfont.eot
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-regular-webfont.ttf
--------------------------------------------------------------------------------
/priv/admin/fonts/noticiatext-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/noticiatext-regular-webfont.woff
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l002-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l002-webfont.eot
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l002-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l002-webfont.ttf
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l002-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l002-webfont.woff
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l005-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l005-webfont.eot
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l005-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l005-webfont.ttf
--------------------------------------------------------------------------------
/priv/admin/fonts/titilliumtext25l005-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/fonts/titilliumtext25l005-webfont.woff
--------------------------------------------------------------------------------
/priv/admin/images/action-button-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/action-button-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/actions-toggle-slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/actions-toggle-slider.png
--------------------------------------------------------------------------------
/priv/admin/images/actions-toggle-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/actions-toggle-text.png
--------------------------------------------------------------------------------
/priv/admin/images/add-phase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/add-phase.png
--------------------------------------------------------------------------------
/priv/admin/images/area-pointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/area-pointer.png
--------------------------------------------------------------------------------
/priv/admin/images/basho-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/basho-logo.png
--------------------------------------------------------------------------------
/priv/admin/images/blue-rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/blue-rect.png
--------------------------------------------------------------------------------
/priv/admin/images/checkbox-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/checkbox-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/close-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/close-error.png
--------------------------------------------------------------------------------
/priv/admin/images/cut-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/cut-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/dropdown-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/dropdown-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/dropdown-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/dropdown-left.png
--------------------------------------------------------------------------------
/priv/admin/images/dropdown-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/dropdown-right.png
--------------------------------------------------------------------------------
/priv/admin/images/expand.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/expand.gif
--------------------------------------------------------------------------------
/priv/admin/images/field-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/field-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/fieldcap-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/fieldcap-left.png
--------------------------------------------------------------------------------
/priv/admin/images/fieldcap-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/fieldcap-right.png
--------------------------------------------------------------------------------
/priv/admin/images/gui-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/gui-bg.jpg
--------------------------------------------------------------------------------
/priv/admin/images/header-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/header-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/healthy-cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/healthy-cluster.png
--------------------------------------------------------------------------------
/priv/admin/images/hide-show-toggle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/hide-show-toggle.png
--------------------------------------------------------------------------------
/priv/admin/images/leavecluster-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/leavecluster-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/leaving-slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/leaving-slider.png
--------------------------------------------------------------------------------
/priv/admin/images/light-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/light-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/markdown-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/markdown-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/mem-bar-overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/mem-bar-overlay.png
--------------------------------------------------------------------------------
/priv/admin/images/membar-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/membar-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/membar-fg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/membar-fg.png
--------------------------------------------------------------------------------
/priv/admin/images/nav-icons-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/nav-icons-small.png
--------------------------------------------------------------------------------
/priv/admin/images/nav-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/nav-icons.png
--------------------------------------------------------------------------------
/priv/admin/images/node-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/node-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/orange-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/orange-arrow.png
--------------------------------------------------------------------------------
/priv/admin/images/pct-arrows-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/pct-arrows-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/pointbutton-right-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/pointbutton-right-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/pointbutton-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/pointbutton-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/pointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/pointer.png
--------------------------------------------------------------------------------
/priv/admin/images/powerbutton-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/powerbutton-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/radio-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/radio-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/rectbutton-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/rectbutton-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/resizer-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/resizer-bg.png
--------------------------------------------------------------------------------
/priv/admin/images/riak-control-logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/riak-control-logo-small.png
--------------------------------------------------------------------------------
/priv/admin/images/riak-control-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/riak-control-logo.png
--------------------------------------------------------------------------------
/priv/admin/images/riak-transparent-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/riak-transparent-small.png
--------------------------------------------------------------------------------
/priv/admin/images/right-angle-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/right-angle-arrow.png
--------------------------------------------------------------------------------
/priv/admin/images/ring-indicator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/ring-indicator.png
--------------------------------------------------------------------------------
/priv/admin/images/slider-groove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/slider-groove.png
--------------------------------------------------------------------------------
/priv/admin/images/slider-handle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/slider-handle.png
--------------------------------------------------------------------------------
/priv/admin/images/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/spinner.gif
--------------------------------------------------------------------------------
/priv/admin/images/stagebutton-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/stagebutton-sprite.png
--------------------------------------------------------------------------------
/priv/admin/images/tooltip-mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/tooltip-mark.png
--------------------------------------------------------------------------------
/priv/admin/images/unhealthy-cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/images/unhealthy-cluster.png
--------------------------------------------------------------------------------
/priv/admin/js/app.js:
--------------------------------------------------------------------------------
1 | minispade.register('app', function() {
2 |
3 | /**
4 | * RiakControl Ember application.
5 | */
6 | RiakControl = Ember.Application.create();
7 |
8 | /**
9 | * How often to refresh/poll the server for changes in cluster
10 | * and partition status
11 | */
12 | RiakControl.refreshInterval = 1000;
13 |
14 | /**
15 | * Provide reload functionality for recordarrays.
16 | */
17 | DS.RecordArray.reopen({
18 | reload: function() {
19 | Ember.assert("Can only reload base RecordArrays",
20 | this.constructor === DS.RecordArray);
21 | var store = this.get('store');
22 | store.findAll(this.get('type'));
23 | }
24 | });
25 |
26 | /**
27 | * Data store configuration.
28 | */
29 | RiakControl.Store = DS.Store.extend({
30 | adapter: DS.RESTAdapter.reopen({ namespace: 'admin' })
31 | });
32 |
33 | RiakControl.store = RiakControl.Store.create();
34 |
35 | // Automatically add CSRF tokens to AJAX requests.
36 | $(document).ajaxSend(function(elm, xhr, s){
37 | var csrf_token = $('meta[name=csrf_token]').prop('content');
38 |
39 | if(s.type === 'POST' || s.type === 'PUT') {
40 | xhr.setRequestHeader('X-CSRF-Token', csrf_token);
41 | }
42 | });
43 |
44 | // Set default content-type for AJAX requests.
45 | // From: http://stackoverflow.com/questions/1749272/jquery-how-to-put-json-via-ajax
46 | $.ajaxSetup({
47 | contentType: 'application/json',
48 | processData: false
49 | });
50 |
51 | // Prefilter for stringifying.
52 | // From: http://stackoverflow.com/questions/1749272/jquery-how-to-put-json-via-ajax
53 | $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
54 | if(options.data) {
55 | options.data = JSON.stringify(options.data);
56 | }
57 | });
58 |
59 | minispade.require('core');
60 | minispade.require('router');
61 | minispade.require('shared');
62 | minispade.require('snapshot');
63 | minispade.require('cluster');
64 | minispade.require('nodes');
65 | minispade.require('ring');
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/priv/admin/js/core.js:
--------------------------------------------------------------------------------
1 | minispade.register('core', function() {
2 |
3 | /** Handle an array type. */
4 | var none = Ember.isNone;
5 |
6 | DS.ArrayTransform = DS.Transform.extend({
7 | deserialize: function(serialized) {
8 | return none(serialized) ? [] : serialized;
9 | },
10 |
11 | serialize: function(deserialized) {
12 | return none(deserialized) ? [] : deserialized;
13 | },
14 | });
15 |
16 | RiakControl.register('transform:array', DS.ArrayTransform);
17 |
18 | /**
19 | * @class
20 | *
21 | * Node models a Riak Node participating in a cluster.
22 | */
23 | RiakControl.Node = DS.Model.extend(
24 | /** @scope RiakControl.Node.prototype */ {
25 |
26 | /**
27 | * Use the node name and ip address as the
28 | * unique identifier for the node.
29 | */
30 | primaryKey: 'name',
31 |
32 | /** The id attribute contains the name after deserializing */
33 | name: Ember.computed.alias('id'),
34 |
35 | status: DS.attr("string"),
36 |
37 | reachable: DS.attr("boolean"),
38 |
39 | ring_pct: DS.attr("number"),
40 |
41 | pending_pct: DS.attr("number"),
42 |
43 | mem_total: DS.attr("number"),
44 |
45 | mem_used: DS.attr("number"),
46 |
47 | mem_erlang: DS.attr("number"),
48 |
49 | low_mem: DS.attr("boolean"),
50 |
51 | /**
52 | * This boolean attribute determines if the node
53 | * responsible for the API requests is running
54 | * Riak Control.
55 | */
56 | me: DS.attr("boolean"),
57 |
58 | /**
59 | * Whether this node is currently the claimant or not.
60 | */
61 | claimant: DS.attr("boolean")
62 | });
63 |
64 | /** Register serializer that respects custom primary key */
65 | RiakControl.register('serializer:node', DS.RESTSerializer.extend({
66 | primaryKey: 'name'
67 | }));
68 | });
69 |
--------------------------------------------------------------------------------
/priv/admin/js/generated/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/priv/admin/js/generated/.gitkeep
--------------------------------------------------------------------------------
/priv/admin/js/legacy/gui.js:
--------------------------------------------------------------------------------
1 | // Framework for the cool UI tricks
2 | //
3 | // TODO: These should be moved to didInsertElement hooks on the
4 | // Ember Views once they are no longer needed by the pages scheduled
5 | // for deprecation.
6 | //
7 | // Set up a safe, global namespace in the jQuery object.
8 | $(document).ready(function() {
9 | $.riakControl = $.riakControl || {};
10 |
11 | // MARK THE ACTIVE NAV ICON WITH THE PROPER CLASS
12 | $.riakControl.markNavActive = $.riakControl.markNavActive || function markNavActive(id) {
13 | Ember.run.next(function() {
14 | var listItems = $('nav li'), activeItem = $("#" + id);
15 | listItems.each(function (index, each) {
16 | if (each !== activeItem[0]) {
17 | $(each).removeClass('active');
18 | } else {
19 | $(each).addClass('active');
20 | }
21 | });
22 | });
23 | };
24 |
25 | // MAKE CHECKBOXES WORK WHEN YOU CLICK THEM
26 | $(document).on('change', '.gui-checkbox', function(e) {
27 | var me = $(this),
28 | parent = me.parent(),
29 | checked = me.prop('checked');
30 |
31 | if (checked) {
32 | parent.css('background-position', 'left bottom');
33 | } else {
34 | parent.css('background-position', 'left top');
35 | }
36 | });
37 |
38 | // MAKE RADIO BUTTONS WORK WHEN YOU CLICK THEM
39 | $(document).on('change', '.gui-radio', function(e) {
40 | var me = $(this),
41 | parent = me.parent(),
42 | checked = me.prop('checked'),
43 | group = $('input[type="radio"][name="' + me.prop('name') + '"]');
44 |
45 | /*
46 | * If the radio button is checked...
47 | */
48 | if (checked) {
49 | /*
50 | * Change the position of the background image sprite.
51 | */
52 | parent.css('background-position', 'left bottom');
53 | /*
54 | * Loop over all other radio buttons in the group and set their
55 | * background positions to reflect the unchecked state.
56 | */
57 | group.each(function (index, item) {
58 | var $item = $(item);
59 | if ($item[0] !== me[0]) {
60 | $item.parent().css('background-position', 'left top');
61 | }
62 | });
63 | /*
64 | * If the checked radio button is the 'replace' radio button...
65 | */
66 | if (me.prop('value') === 'replace') {
67 | /*
68 | * Enable the extra replacement actions.
69 | */
70 | parent.parent().find('.extra-actions').addClass('active').find('.disabler').hide();
71 | /*
72 | * Otherwise disable the replacement actions.
73 | */
74 | } else {
75 | parent.parent().find('.extra-actions').removeClass('active').find('.disabler').show();
76 | }
77 | }
78 | });
79 |
80 |
81 | });
82 |
--------------------------------------------------------------------------------
/priv/admin/js/nodes.js:
--------------------------------------------------------------------------------
1 | minispade.register('nodes', function() {
2 |
3 | /**
4 | * @class
5 | *
6 | * NodesController is responsible for displaying the list of nodes
7 | * in the cluster.
8 | */
9 | RiakControl.NodesController = Ember.Controller.extend(
10 | /**
11 | * Shares properties with RiakControl.ClusterController
12 | */
13 | RiakControl.ClusterAndNodeControls,
14 | /** @scope RiakControl.NodesController.prototype */ {
15 |
16 | /**
17 | * Reloads the record array associated with this controller.
18 | *
19 | * @returns {void}
20 | */
21 | reload: function() {
22 | this.get('content').reload();
23 | },
24 |
25 | actions: {
26 | /**
27 | * Removes all checks from radio buttons.
28 | *
29 | * @returns {void}
30 | */
31 | clearChecked: function() {
32 | $('#node-list input[type=radio]').each(function(index, item) {
33 | item.checked = false;
34 | $(item).parent().css('background-position', 'left top');
35 | });
36 | },
37 |
38 | /**
39 | * Submits requests to stop and/or down nodes to the app.
40 | */
41 | applyChanges: function() {
42 | var self = this;
43 |
44 | $("#node-list input[type='radio']:checked").each(function(index, item) {
45 | var name = item.name,
46 | action = item.value,
47 | replacement;
48 |
49 | // Empty string instead of undefined for null.
50 | if(replacement === undefined) {
51 | replacement = '';
52 | }
53 |
54 | self.send('stageChange', name, action, replacement);
55 | });
56 |
57 | self.send('clearChecked');
58 | }
59 | }
60 |
61 | });
62 |
63 | /**
64 | * @class
65 | *
66 | * One item in the collection of current cluster views.
67 | */
68 | RiakControl.CurrentNodesItemView = Ember.View.extend(
69 | /**
70 | * Shares properties with other views that display lists of nodes.
71 | */
72 | RiakControl.NodeProperties,
73 | /** @scope RiakControl.CurrentNodesItemView.prototype */ {
74 |
75 | /* Bindings from the model */
76 |
77 | templateName: 'current_nodes_item',
78 | nameBinding: 'content.name',
79 | reachableBinding: 'content.reachable',
80 | statusBinding: 'content.status',
81 | ring_pctBinding: 'content.ring_pct',
82 | pending_pctBinding: 'content.pending_pct',
83 | mem_totalBinding: 'content.mem_total',
84 | mem_usedBinding: 'content.mem_used',
85 | mem_erlangBinding: 'content.mem_erlang',
86 | meBinding: 'content.me',
87 |
88 | /**
89 | * In order for labels to be clickable, they need to be bound to checks/radios
90 | * by ID. However, since these nodes are cloned by Ember, we need a way to make
91 | * sure all of those elements get id's that don't override each other. This
92 | * function gives us an ID string we can use as a prefix for id's on these other
93 | * elements.
94 | *
95 | * @returns {String}
96 | */
97 | nodeID: function() {
98 | return Ember.guidFor(this);
99 | }.property(),
100 |
101 | /**
102 | * An ID value for the leave normally radio button and corresponding label.
103 | */
104 | stopRadio: function() {
105 | return this.get('nodeID') + '_stop_node';
106 | }.property('nodeID'),
107 |
108 | /**
109 | * An ID value for the force leave radio button and corresponding label.
110 | */
111 | downRadio: function() {
112 | return this.get('nodeID') + '_down_node';
113 | }.property('nodeID'),
114 |
115 | /**
116 | * A node can not be stopped when:
117 | * - It is unreachable.
118 | * - It is down.
119 | */
120 | stopRadioClasses: function() {
121 | var status = this.get('status'),
122 | reachable = this.get('reachable'),
123 | classes = 'gui-radio-wrapper';
124 | if (!reachable || status === 'down') {
125 | classes += ' semi-transparent';
126 | }
127 | return classes;
128 | }.property('status', 'reachable'),
129 |
130 | /**
131 | * A node can not be marked as down when:
132 | * - It is alive and well
133 | * - It is already down.
134 | */
135 | downRadioClasses: function() {
136 | var status = this.get('status'),
137 | reachable = this.get('reachable'),
138 | classes = 'gui-radio-wrapper';
139 | if ((reachable && status === 'valid') || status === 'down') {
140 | classes += ' semi-transparent';
141 | }
142 | return classes;
143 | }.property('status', 'reachable'),
144 |
145 | /**
146 | * When a node can't be stopped, disable the user
147 | * from clicking the stop radio button.
148 | */
149 | stopDisablerClasses: function() {
150 | return 'disabler' + (/\ssemi\-transparent$/.test(this.get('stopRadioClasses')) ? ' show' : '');
151 | }.property('stopRadioClasses'),
152 |
153 | /**
154 | * When a node can't be downed, disable the user from
155 | * clicking the down radio button.
156 | */
157 | downDisablerClasses: function() {
158 | return 'disabler' + (/\ssemi\-transparent$/.test(this.get('downRadioClasses')) ? ' show' : '');
159 | }.property('downRadioClasses')
160 | });
161 |
162 | /**
163 | * @class
164 | *
165 | * Collection view for showing the current cluster.
166 | */
167 | RiakControl.CurrentNodesView = Ember.CollectionView.extend(
168 | /** @scope RiakControl.CurrentClusterView.prototype */ {
169 | itemViewClass: RiakControl.CurrentNodesItemView
170 | });
171 |
172 | });
173 |
--------------------------------------------------------------------------------
/priv/admin/js/router.js:
--------------------------------------------------------------------------------
1 | minispade.register('router', function() {
2 |
3 | /**
4 | * @class
5 | *
6 | * Base application router for the RiakControl application. At this
7 | * point its responsibilities include either rendering the new pure
8 | * ember-only components, or using the legacy pub/sub implementation
9 | * to render older pages which are scheduled for deprecation.
10 | */
11 | RiakControl.Router.map(function() {
12 | /** @scope Ember.RouterDSL callback */
13 |
14 | this.route('snapshot');
15 | this.route('cluster');
16 | this.route('nodes');
17 | this.route('ring');
18 | });
19 |
20 | RiakControl.IndexRoute = Ember.Route.extend(
21 | /** @scope Ember.Route.prototype */ {
22 |
23 | redirect: function() {
24 | this.transitionTo('snapshot');
25 | }
26 | });
27 |
28 | RiakControl.SnapshotRoute = Ember.Route.extend(
29 | /** @scope Ember.Route.prototype */ {
30 |
31 | model: function() {
32 | return this.store.find('node');
33 | },
34 |
35 | renderTemplate: function() {
36 | this.render('snapshot');
37 | $.riakControl.markNavActive('nav-snapshot');
38 | },
39 |
40 | activate: function() {
41 | this.controllerFor('snapshot').startInterval();
42 | },
43 |
44 | deactivate: function() {
45 | this.controllerFor('snapshot').cancelInterval();
46 | }
47 | });
48 |
49 | RiakControl.ClusterRoute = Ember.Route.extend(
50 | /** @scope Ember.Route.prototype */ {
51 |
52 | model: function() {
53 | var cluster = this.controllerFor('cluster');
54 |
55 | /*
56 | * Don't return a promise if we have previously
57 | * failed to load data.
58 | */
59 | if (cluster.get('cannotLoad') === true) {
60 | return cluster.get('content');
61 |
62 | } else {
63 | return cluster.load().then(function (d) {
64 | return d;
65 | });
66 | }
67 | },
68 |
69 | /*
70 | * In the event of an error, the cannotLoad property
71 | * will be set to true. This way we can still
72 | * transition into the state without getting caught
73 | * in an infinit .load() loop.
74 | */
75 | events: {
76 | error: function () {
77 | this.transitionTo('cluster');
78 | }
79 | },
80 |
81 | renderTemplate: function() {
82 | this.render('cluster');
83 | $.riakControl.markNavActive('nav-cluster');
84 | },
85 |
86 | activate: function() {
87 | this.controllerFor('cluster').startInterval();
88 | },
89 |
90 | deactivate: function() {
91 | this.controllerFor('cluster').cancelInterval();
92 | }
93 | });
94 |
95 | RiakControl.NodesRoute = Ember.Route.extend(
96 | /** @scope Ember.Route.prototype */ {
97 |
98 | model: function() {
99 | return this.store.find('node');
100 | },
101 |
102 | renderTemplate: function() {
103 | this.render('nodes');
104 | $.riakControl.markNavActive('nav-nodes');
105 | },
106 |
107 | activate: function() {
108 | this.controllerFor('nodes').startInterval();
109 | },
110 |
111 | deactivate: function() {
112 | this.controllerFor('nodes').cancelInterval();
113 | }
114 | });
115 |
116 | RiakControl.RingRoute = Ember.Route.extend(
117 | /** @scope Ember.Route.prototype */ {
118 |
119 | model: function() {
120 | var ring = this.controllerFor('ring');
121 |
122 | /*
123 | * Don't return a promise if we have previously
124 | * failed to load data.
125 | */
126 | if (ring.get('cannotLoad') === true) {
127 | return ring.get('content');
128 |
129 | } else {
130 | return ring.load().then(function (d) {
131 | return d;
132 | });
133 | }
134 | },
135 |
136 | /*
137 | * In the event of an error, the cannotLoad property
138 | * will be set to true. This way we can still
139 | * transition into the state without getting caught
140 | * in an infinit .load() loop.
141 | */
142 | events: {
143 | error: function () {
144 | this.transitionTo('ring');
145 | }
146 | },
147 |
148 | renderTemplate: function() {
149 | this.render('ring');
150 | $.riakControl.markNavActive('nav-ring');
151 | },
152 |
153 | activate: function() {
154 | this.controllerFor('ring').startInterval();
155 | },
156 |
157 | deactivate: function() {
158 | this.controllerFor('ring').cancelInterval();
159 | }
160 | });
161 |
162 | RiakControl.LoadingRoute = Ember.Route.extend({});
163 | });
164 |
--------------------------------------------------------------------------------
/priv/admin/js/shared.js:
--------------------------------------------------------------------------------
1 | minispade.register('shared', function () {
2 |
3 | /**
4 | * ClusterAndNodeControls contains properties shared by:
5 | * - RiakControl.ClusterController
6 | * - RiakControl.NodesController
7 | * - RiakControl.RingController
8 | */
9 | RiakControl.ClusterAndNodeControls = Ember.Mixin.create({
10 |
11 | actions: {
12 | /**
13 | * Stage a change.
14 | *
15 | * @returns {void}
16 | */
17 | stageChange: function(node, action, replacement, success, failure) {
18 | var self = this,
19 | ajax = $.ajax({
20 | type: 'PUT',
21 | url: '/admin/cluster',
22 | dataType: 'json',
23 | data: { changes:
24 | [{
25 | node: node,
26 | action: action,
27 | replacement: replacement
28 | }]
29 | }
30 | });
31 |
32 | ajax.then(
33 |
34 | // success...
35 | function(d) {
36 | if(success) {
37 | success();
38 | }
39 |
40 | self.reload();
41 | },
42 |
43 | // error...
44 | function (jqXHR, textStatus, errorThrown) {
45 | if(failure) {
46 | failure();
47 | }
48 |
49 | self.get('displayError').call(self, jqXHR, textStatus, errorThrown);
50 | }
51 | );
52 |
53 | },
54 |
55 | /**
56 | * The action specified on the tag creating the 'x' button in the error message div.
57 | * By setting the 'errorMessage' property back to an empty string, the message will disappear.
58 | *
59 | * @returns {void}
60 | */
61 | hideError: function () {
62 | this.set('errorMessage', '');
63 | }
64 | },
65 |
66 | /**
67 | * If content is loading, return true.
68 | *
69 | * @returns {boolean}
70 | */
71 | isLoading: false,
72 |
73 | /**
74 | * Holds the most recent error message.
75 | */
76 | errorMessage: '',
77 |
78 | /**
79 | * Whenever an ajax call returns an error, we display
80 | * the error for the user.
81 | *
82 | * @param {Object} jqXHR The xhr request object generated by jQuery.
83 | * @param {String} textStatus The status of the response.
84 | * @param {Error} errorThrown The error object produced by the ajax request.
85 | *
86 | * @returns {void}
87 | */
88 | displayError: function (jqXHR, textStatus, errorThrown) {
89 | var parsed, errors;
90 |
91 | if(jqXHR) {
92 | try {
93 | parsed = JSON.parse(jqXHR.responseText);
94 | errors = parsed.errors.join(', ');
95 | } catch(err) {
96 | errors = errorThrown;
97 | }
98 | } else {
99 | errors = errorThrown;
100 | }
101 |
102 | this.set('errorMessage', 'Request failed: ' + errors);
103 | },
104 |
105 | /**
106 | * Called by the router, to start polling when this controller/view is navigated to.
107 | *
108 | * @returns {void}
109 | */
110 | startInterval: function() {
111 | this._intervalId = setInterval($.proxy(this.reload, this), RiakControl.refreshInterval);
112 | },
113 |
114 | /**
115 | * Called by the router, to stop polling when this controller/view is navigated away from.
116 | *
117 | * @returns {void}
118 | */
119 | cancelInterval: function() {
120 | if(this._intervalId) {
121 | clearInterval(this._intervalId);
122 | }
123 | }
124 | });
125 |
126 | /**
127 | * RiakControl.NodeProperties contains properties shared by:
128 | * - RiakControl.CurrentClusterItemView
129 | * - RiakControl.StagedClusterItemView
130 | * - RiakControl.CurrentNodesItemView
131 | */
132 | RiakControl.NodeProperties = Ember.Mixin.create({
133 |
134 | /**
135 | * In the current cluster area, the host node has extra content in its
136 | * actions box so it needs to be a little taller than the others.
137 | *
138 | * @returns {String}
139 | */
140 | hostNodeClasses: function () {
141 | return this.get('me') ? 'node taller' : 'node';
142 | }.property('me'),
143 |
144 | /**
145 | * Color the lights appropriately based on the node status.
146 | *
147 | * @returns {string}
148 | */
149 | indicatorLights: function() {
150 | var status = this.get('status');
151 | var reachable = this.get('reachable');
152 | var color;
153 |
154 | if(reachable === false && status !== "down") {
155 | color = "red";
156 | } else if (status === "down") {
157 | color = "blue";
158 | } else if(status === 'leaving' || status === 'joining') {
159 | color = "orange";
160 | } else if (status === 'valid') {
161 | color = "green";
162 | } else {
163 | color = "grey";
164 | }
165 |
166 | return "gui-light status-light inline-block " + color;
167 | }.property('reachable', 'status'),
168 |
169 | /**
170 | * Color the arrows in the partitions column appropriately based
171 | * on how ring_pct and pending_pct compare.
172 | *
173 | * @returns {String}
174 | */
175 | coloredArrows: function() {
176 | var current = this.get('ring_pct'),
177 | pending = this.get('pending_pct'),
178 | common = 'left pct-arrows ';
179 |
180 | if (pending > current) {
181 | return common + 'pct-gaining';
182 | } else if (pending < current) {
183 | return common + 'pct-losing';
184 | } else {
185 | return common + 'pct-static';
186 | }
187 | }.property('ring_pct', 'pending_pct'),
188 |
189 | /**
190 | * Normalizer.
191 | *
192 | * @returns {number}
193 | */
194 | memDivider: function() {
195 | return this.get('mem_total') / 100;
196 | }.property('mem_total'),
197 |
198 | /**
199 | * Compute memory ceiling.
200 | *
201 | * @returns {number}
202 | */
203 | memErlangCeil: function () {
204 | return Math.ceil(this.get('mem_erlang') / this.get('memDivider'));
205 | }.property('mem_erlang', 'memDivider'),
206 |
207 | /**
208 | * Compute free memory from total and used.
209 | *
210 | * @returns {number}
211 | */
212 | memNonErlang: function () {
213 | return Math.round(
214 | (this.get('mem_used') / this.get('memDivider')) - this.get('memErlangCeil'));
215 | }.property('mem_used', 'memDivider', 'memErlangCeil'),
216 |
217 | /**
218 | * Compute free memory from total and used.
219 | *
220 | * @returns {number}
221 | */
222 | memFree: function () {
223 | return this.get('mem_total') - this.get('mem_used');
224 | }.property('mem_total', 'mem_used'),
225 |
226 | /**
227 | * Format free memory to be a readbale version.
228 | *
229 | * @returns {number}
230 | */
231 | memFreeReadable: function () {
232 | return Math.round(this.get('memFree') / this.get('memDivider'));
233 | }.property('memFree', 'memDivider'),
234 |
235 | /**
236 | * Format used memory to be a readbale version.
237 | *
238 | * @returns {number}
239 | */
240 | memUsedReadable: function () {
241 | return Math.round((this.get('mem_total') - this.get('memFree')) /
242 | this.get('memDivider'));
243 | }.property('mem_total', 'memFree', 'memDivider'),
244 |
245 | /**
246 | * Return CSS style for rendering memory used by Erlang.
247 | *
248 | * @returns {number}
249 | */
250 | memErlangStyle: function () {
251 | return 'width: ' + this.get('memErlangCeil') + '%';
252 | }.property('memErlangCeil'),
253 |
254 | /**
255 | * Return CSS style for rendering occupied non-erlang memory.
256 | *
257 | * @returns {string}
258 | */
259 | memNonErlangStyle: function () {
260 | return 'width: ' + this.get('memNonErlang') + '%';
261 | }.property('memNonErlang'),
262 |
263 | /**
264 | * Return CSS style for rendering free memory.
265 | *
266 | * @returns {string}
267 | */
268 | memFreeStyle: function () {
269 | return 'width: ' + this.get('memFreeReadable') + '%';
270 | }.property('memFreeReadable'),
271 |
272 | /**
273 | * Formatted ring percentage.
274 | *
275 | * @returns {string}
276 | */
277 | ringPctReadable: function () {
278 | return Math.round(this.get('ring_pct') * 100);
279 | }.property('ring_pct')
280 | });
281 |
282 | });
283 |
--------------------------------------------------------------------------------
/priv/admin/js/snapshot.js:
--------------------------------------------------------------------------------
1 | minispade.register('snapshot', function() {
2 |
3 | /**
4 | * @class
5 | *
6 | * SnapshotController is responsible for rendering of the snapshot page and
7 | * deriving overall cluster state from a selection of nodes.
8 | */
9 | RiakControl.SnapshotController = Ember.ObjectController.extend(
10 | /** @scope RiakControl.SnapshotController.prototype */ {
11 |
12 | /**
13 | * Reloads the record array associated with this controller.
14 | *
15 | * @returns {void}
16 | */
17 | reload: function() {
18 | this.get('content').reload();
19 | },
20 |
21 | /**
22 | * Called by the router, to start polling when this controller/view is navigated to.
23 | *
24 | * @returns {void}
25 | */
26 | startInterval: function() {
27 | this._intervalId = setInterval($.proxy(this.reload, this), RiakControl.refreshInterval);
28 | },
29 |
30 | /**
31 | * Called by the router, to stop polling when this controller/view is navigated away from.
32 | *
33 | * @returns {void}
34 | */
35 | cancelInterval: function() {
36 | if(this._intervalId) {
37 | clearInterval(this._intervalId);
38 | }
39 | },
40 |
41 | /**
42 | * Filter nodes and return just nodes classified as unreachable.
43 | *
44 | * @returns {Array} filtered list of nodes
45 | */
46 | unreachableNodes: function() {
47 | return this.get('content').filterProperty('reachable', false);
48 | }.property('content.@each.reachable'),
49 |
50 | /**
51 | * Determine if there are any unreachable nodes.
52 | *
53 | * @returns {Boolean}
54 | */
55 | areUnreachableNodes: function() {
56 | return this.get('unreachableNodes.length') > 0;
57 | }.property('content.@each.reachable'),
58 |
59 | /**
60 | * Filter nodes and return just nodes classified as incompatible.
61 | *
62 | * @returns {Array} filtered list of nodes
63 | */
64 | incompatibleNodes: function() {
65 | return this.get('content').filterProperty('status', 'incompatible');
66 | }.property('content.@each.status'),
67 |
68 | /**
69 | * Determine if there are any incompatible nodes.
70 | *
71 | * @returns {Boolean}
72 | */
73 | areIncompatibleNodes: function() {
74 | return this.get('incompatibleNodes.length') > 0;
75 | }.property('content.@each.status'),
76 |
77 | /**
78 | * Filter nodes and return just nodes classified as down.
79 | *
80 | * @returns {Array} filtered list of nodes
81 | */
82 | downNodes: function() {
83 | return this.get('content').filterProperty('status', 'down');
84 | }.property('content.@each.status'),
85 |
86 | /**
87 | * Determine if there are any down nodes.
88 | *
89 | * @returns {Boolean}
90 | */
91 | areDownNodes: function() {
92 | return this.get('downNodes.length') > 0;
93 | }.property('content.@each.status'),
94 |
95 | /**
96 | * Filter nodes and return just nodes classified as low-memory nodes.
97 | *
98 | * @returns {Array} filtered list of nodes
99 | */
100 | lowMemNodes: function() {
101 | return this.get('content').filterProperty('low_mem', true);
102 | }.property('content.@each.low_mem'),
103 |
104 | /**
105 | * Determine if there are any low-memory nodes.
106 | *
107 | * @returns {Boolean}
108 | */
109 | areLowMemNodes: function() {
110 | return this.get('lowMemNodes.length') > 0;
111 | }.property('content.@each.low_mem'),
112 |
113 | /**
114 | * Derive overall state of the cluster based on node status.
115 | *
116 | * @return {Boolean} whether the cluster is happy or not
117 | */
118 | healthyCluster: function() {
119 | var areUnreachableNodes = this.get('areUnreachableNodes');
120 | var areIncompatibleNodes = this.get('areIncompatibleNodes');
121 | var areDownNodes = this.get('areDownNodes');
122 | var areLowMemNodes = this.get('areLowMemNodes');
123 |
124 | return !(areUnreachableNodes || areIncompatibleNodes || areDownNodes || areLowMemNodes);
125 | }.property('areUnreachableNodes', 'areIncompatibleNodes', 'areDownNodes', 'areLowMemNodes')
126 | });
127 |
128 | });
129 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/all_unavailable_chart.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{view.normalizedAbnormal}}%
4 | of partitions have all primary replicas on down or unreachable nodes.
5 |
6 |
7 |
8 | DETAILS
9 |
10 |
11 |
12 |
13 | Preference lists where all primary replicas are unavailable.
14 |
15 | {{#if allUnavailableExist}}
16 | {{allUnavailableCount}} partitions have all of their
17 | primary replicas located on down or unreachable nodes. It is
18 | recommended that you work to restore service to the unreachable nodes.
19 | {{else}}
20 | No partitions have all of their primary replicas located on down or
21 | unreachable nodes. There is nothing to worry about.
22 | {{/if}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/cluster.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Cluster Management
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{#if standalone}}
16 |
Join Node
17 |
Type the name of a node in an existing cluster to join this node to.
18 | {{else}}
19 |
Add Node
20 |
Type the name of a node to add to this cluster.
21 | {{/if}}
22 |
38 | {{#if errorMessage}}
39 |
43 | {{/if}}
44 |
45 |
46 |
47 |
48 |
49 | Current Cluster
50 |
51 |
52 |
53 | {{#if controller.isLoading}}
54 |
55 |
56 |
Loading...
57 |
58 | {{else}}
59 |
65 |
66 | {{collection RiakControl.CurrentClusterView contentBinding="activeCurrentCluster"}}
67 | {{/if}}
68 |
69 |
70 |
71 |
72 |
75 |
76 |
77 |
78 |
79 | Staged Changes
80 | (Your new cluster after convergence.)
81 |
82 |
83 | {{#if controller.displayPlan}}
84 |
85 |
91 |
92 | {{collection RiakControl.StagedClusterView contentBinding="activeStagedCluster"}}
93 |
94 |
103 |
104 |
105 | Changed your mind? Click this button to remove all staged changes.
106 |
107 |
108 | CLEAR PLAN
109 |
110 |
111 | {{else}}
112 |
113 |
114 | {{#if controller.ringNotReady}}
115 |
116 | Please wait, the ring is converging.
117 |
118 | {{else}}
119 | {{#if controller.legacyRing}}
120 |
You are currently running a legacy version of Riak that does not support staged changes.
121 | {{else}}
122 | {{#if controller.emptyPlan}}
123 |
Currently no staged changes to display.
124 | {{else}}
125 | {{#if controller.isLoading}}
126 |
127 |
Loading...
128 | {{/if}}
129 | {{/if}}
130 | {{/if}}
131 | {{/if}}
132 |
133 |
134 | {{/if}}
135 |
136 |
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/current_cluster_item.hbs:
--------------------------------------------------------------------------------
1 | {{#with view}}
2 |
3 |
4 | {{#view RiakControl.CurrentClusterToggleView}}
5 |
8 | {{/view}}
9 |
10 |
16 |
17 |
18 |
19 | {{ringPctReadable}}%
20 |
21 |
22 |
23 |
24 | {{#if reachable}}
25 |
33 |
{{memUsedReadable}}%
34 | {{else}}
35 |
41 |
42 | {{/if}}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Use these actions to prepare this node to leave the cluster.
52 |
53 | {{#if me}}
54 |
55 | Warning: This node is hosting Riak Control. If it leaves the
56 | cluster, Riak Control will be shut down.
57 |
58 | {{/if}}
59 |
60 |
61 |
62 | Allow this node to leave normally.
63 |
64 |
65 |
66 | Force this node to leave.
67 |
68 |
69 |
70 | Choose a new node to replace this one.
71 |
72 |
98 |
99 |
100 |
101 |
Click "STAGE" when you are ready to stage this action.
102 |
103 | STAGE
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | {{/with}}
113 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/current_nodes_item.hbs:
--------------------------------------------------------------------------------
1 | {{#with view}}
2 |
3 |
9 |
15 |
21 |
22 |
23 |
24 | {{ringPctReadable}}%
25 |
26 |
27 |
28 |
29 | {{#if reachable}}
30 |
38 |
{{memUsedReadable}}%
39 | {{else}}
40 |
46 |
47 | {{/if}}
48 |
49 |
50 |
51 |
52 | {{/with}}
53 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/degenerate_preflist_chart.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{view.normalizedAbnormal}}%
4 | of partitions have primary replicas that do not live on a unique set
5 | of nodes.
6 |
7 |
8 |
9 | DETAILS
10 |
11 |
12 |
13 |
14 | Preference lists which do not contain replicas on distinct nodes.
15 |
16 | {{#if degeneratesExist}}
17 | {{degenerateCount}} partitions have multiple replicas
18 | located on the same node. It is highly recommended that you add
20 | nodes to resolve this and ensure the high availability and fault
21 | tolerance of your data.
22 | {{else}}
23 | All partitions have replicas on distinct nodes. There is nothing to
24 | worry about.
25 | {{/if}}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/loading.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
...
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Loading...
17 |
18 |
19 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/nodes.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Node Management
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{#if errorMessage}}
15 |
19 | {{/if}}
20 |
21 |
22 |
23 | Current Cluster
24 |
25 |
26 | 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
27 | available due to the current status of the node.
28 |
29 |
30 | {{#if controller.isLoading}}
31 |
32 |
33 |
Loading...
34 |
35 | {{else}}
36 |
43 |
44 | {{collection RiakControl.CurrentNodesView contentBinding="content"}}
45 | {{/if}}
46 |
47 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/pagination_item.hbs:
--------------------------------------------------------------------------------
1 | {{#with view}}
2 |
3 | {{content.page_id}}
4 |
5 | {{/with}}
6 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/partition.hbs:
--------------------------------------------------------------------------------
1 | {{#with view}}
2 |
3 | {{/with}}
4 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/quorum_unavailable_chart.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{view.normalizedAbnormal}}%
4 | of partitions have a majority of primary replicas down or unreachable
5 | nodes.
6 |
7 |
8 |
9 | DETAILS
10 |
11 |
12 |
13 |
14 | Preference lists where a majority of primary replicas are unavailable.
15 |
16 | {{#if quorumUnavailableExist}}
17 | {{quorumUnavailableCount}} partitions have a majority
18 | of their replicas located on down or unreachable nodes. It is
19 | recommended that you work to restore service to the unreachable nodes.
20 | {{else}}
21 | No partitions have a majority of their replicas located on down or
22 | unreachable nodes. There is nothing to worry about.
23 | {{/if}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/ring.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Ring Status
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{#if errorMessage}}
15 |
19 | {{/if}}
20 |
21 |
22 |
23 |
24 |
Availability
25 |
26 |
27 |
See visualizations using a different n_val.
28 |
44 | {{view RiakControl.NValSelectView
45 | contentBinding="content.n_vals"
46 | valueBinding="content.selected"
47 | dataBinding="content"
48 | selectionBinding="content.selected"
49 | }}
50 |
51 |
52 | {{view RiakControl.DegeneratePreflistChart}}
53 |
54 |
55 | {{view RiakControl.QuorumUnavailableChart}}
56 |
57 |
58 | {{view RiakControl.AllUnavailableChart}}
59 |
60 |
61 |
62 |
63 |
64 |
Partitions
65 |
66 |
67 |
68 | {{collection RiakControl.PartitionsView contentBinding="content"}}
69 |
70 |
71 |
72 |
73 |
74 |
Partition Information
75 |
76 | {{#if partitionSelected}}
77 |
78 |
79 |
Index
80 |
81 | {{selectedPartition.index}}
82 |
83 |
84 |
85 | {{#if selectedPartition.unavailable}}
86 |
87 |
Unavailable Primaries
88 |
89 | {{#each selectedPartition.unavailable_nodes}}
90 | {{this}}
91 | {{/each}}
92 |
93 |
94 | {{/if}}
95 |
96 |
97 | {{#if selectedPartition.all_nodes.length}}
98 |
All Primaries
99 |
100 | {{#each selectedPartition.all_nodes}}
101 | {{this}}
102 | {{/each}}
103 |
104 | {{else}}
105 |
No Primaries To Display
106 | {{/if}}
107 |
108 |
109 | {{else}}
110 |
111 | Click on a partition square to view its more information.
112 |
113 | {{/if}}
114 |
115 |
116 |
117 | Legend
118 |
119 | Only fallbacks available.
120 |
121 | Majority of primary replicas down.
122 |
123 | Replicas do not live on unique nodes.
124 |
125 | Available.
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/snapshot.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Current Snapshot
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{#if healthyCluster}}
16 |
17 |
18 |
19 | Your cluster is healthy.
20 | You currently have...
21 |
22 | 0 Unreachable nodes
23 | 0 Incompatible nodes
24 | 0 Nodes marked as down
25 | 0 Nodes experiencing low memory
26 | Nothing to worry about because Riak is your friend
27 |
28 |
29 |
30 | {{else}}
31 |
32 |
33 |
34 | Your cluster has problems.
35 |
36 | {{#if areUnreachableNodes}}
37 |
38 | The following nodes are currently unreachable:
39 |
40 | {{#each unreachableNodes}}
41 | {{name}}
42 | {{/each}}
43 |
44 | {{/if}}
45 |
46 | {{#if areIncompatibleNodes}}
47 |
48 | Some
49 | information about the following nodes may be temporarily
50 | unavailable:
51 |
52 | {{#each incompatibleNodes}}
53 | {{name}}
54 | {{/each}}
55 |
56 | This may be triggered by using control in the middle
57 | of a rolling upgrade or during startup of the node.
58 | {{/if}}
59 |
60 | {{#if areDownNodes}}
61 |
62 | The following nodes are currently marked down:
63 |
64 | {{#each downNodes}}
65 | {{name}}
66 | {{/each}}
67 |
68 | {{/if}}
69 |
70 | {{#if areLowMemNodes}}
71 |
72 | The following nodes are currently experiencing low memory:
73 |
74 | {{#each lowMemNodes}}
75 | {{name}}
76 | {{/each}}
77 |
78 | {{/if}}
79 |
80 |
81 | {{/if}}
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/priv/admin/js/templates/staged_cluster_item.hbs:
--------------------------------------------------------------------------------
1 | {{#with view}}
2 |
3 |
9 |
10 |
11 | {{ringPctReadable}}%
12 |
13 |
14 |
15 | {{#if isAction}}
16 |
17 | {{node_action}}
18 |
19 | {{/if}}
20 | {{#if isReplaced}}
21 |
22 |
23 |
{{content.replacement}}
24 |
25 |
26 | {{/if}}
27 |
28 |
29 | {{/with}}
30 |
--------------------------------------------------------------------------------
/priv/admin/js/vendor/minispade.js:
--------------------------------------------------------------------------------
1 | if (typeof document !== "undefined") {
2 | (function() {
3 | minispade = {
4 | root: null,
5 | modules: {},
6 | loaded: {},
7 |
8 | globalEval: function(data) {
9 | if ( data ) {
10 | var ev = "ev";
11 | var execScript = "execScript";
12 |
13 | // We use execScript on Internet Explorer
14 | // We use an anonymous function so that context is window
15 | // rather than jQuery in Firefox
16 | ( window[execScript] || function( data ) {
17 | window[ ev+"al" ].call( window, data );
18 | } )( data );
19 | }
20 | },
21 |
22 | require: function(name) {
23 | var loaded = minispade.loaded[name];
24 | var mod = minispade.modules[name];
25 |
26 | if (!loaded) {
27 | if (mod) {
28 | minispade.loaded[name] = true;
29 |
30 | if (typeof mod === "string") {
31 | this.globalEval(mod);
32 | } else {
33 | mod();
34 | }
35 | } else {
36 | if (minispade.root && name.substr(0,minispade.root.length) !== minispade.root) {
37 | return minispade.require(minispade.root+name);
38 | } else {
39 | throw "The module '" + name + "' could not be found";
40 | }
41 | }
42 | }
43 |
44 | return loaded;
45 | },
46 |
47 | register: function(name, callback) {
48 | minispade.modules[name] = callback;
49 | }
50 | };
51 | })();
52 | }
53 |
--------------------------------------------------------------------------------
/priv/riak_control.schema:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %% riak_control config
3 | %% @doc Set to 'off' to disable the admin panel.
4 | {mapping, "riak_control", "riak_control.enabled", [
5 | {default, off},
6 | {datatype, flag}
7 | ]}.
8 |
9 | %% @doc Authentication mode used for access to the admin panel.
10 | {mapping, "riak_control.auth.mode", "riak_control.auth", [
11 | {default, off},
12 | {datatype, {enum, [off, userlist]}}
13 | ]}.
14 |
15 | {translation,
16 | "riak_control.auth",
17 | fun(Conf) ->
18 | case cuttlefish:conf_get("riak_control.auth.mode", Conf) of
19 | userlist -> userlist;
20 | off -> none;
21 | _ -> none
22 | end
23 | end}.
24 |
25 | %% @doc If riak control's authentication mode (riak_control.auth.mode)
26 | %% is set to 'userlist' then this is the list of usernames and
27 | %% passwords for access to the admin panel.
28 | %% To create users with given names, add entries of the format:
29 | %% riak_control.auth.user.USERNAME.password = PASSWORD
30 | %% replacing USERNAME with the desired username and PASSWORD with the
31 | %% desired password for that user.
32 | %% @see riak_control.auth.mode
33 | {mapping, "riak_control.auth.user.$username.password", "riak_control.userlist", [
34 | {commented, "pass"},
35 | {include_default, "admin"}
36 | ]}.
37 |
38 | {translation,
39 | "riak_control.userlist",
40 | fun(Conf) ->
41 | UserList = cuttlefish_variable:filter_by_prefix("riak_control.auth.user", Conf),
42 | Users = [ {Username, Password} || {[_, _, _, Username, _], Password} <- UserList ],
43 | case Users of
44 | [] ->
45 | cuttlefish:unset();
46 | _ -> Users
47 | end
48 | end}.
49 |
--------------------------------------------------------------------------------
/rebar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basho/riak_control/72bbb7a6d4d594b4f6590f297a93f60236217fbe/rebar
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %% -*- mode: erlang -*-
2 |
3 | {erl_opts, [warnings_as_errors, {parse_transform, lager_transform}, debug_info]}.
4 |
5 | {edoc_opts, [{preprocess, true}]}.
6 |
7 | {erlydtl_opts, [
8 | {compiler_options, [report, return, debug_info]}]}.
9 |
10 | {cover_enabled, true}.
11 |
12 | {xref_checks, []}.
13 | {xref_queries, [{"(XC - UC) || (XU - X - B - \"(cluster_info|dtrace)\" : Mod)", []}]}.
14 |
15 | {deps, [
16 | {webmachine, "1.10.8", {git, "git://github.com/basho/webmachine.git", {tag, "1.10.8-basho1"}}},
17 | {riak_core, ".*", {git, "https://github.com/basho/riak_core.git", {tag, "2.1.10"}}},
18 | {erlydtl, ".*", {git, "https://github.com/basho/erlydtl.git", "d20b53f0"}}
19 | ]}.
20 |
21 | {plugin_dir, "plugins"}.
22 |
23 | {plugins, [rebar_js_handlebars_plugin,
24 | rebar_js_stylus_plugin,
25 | rebar_js_concatenator_plugin]}.
26 |
27 | {js_handlebars, [
28 | {doc_root, "priv/admin/js/templates"},
29 | {out_dir, "priv/admin/js"},
30 | {target, "Ember.TEMPLATES"},
31 | {compiler, "Ember.Handlebars.compile"},
32 | {templates, [{"generated/templates.js", ["application.hbs",
33 | "snapshot.hbs",
34 | "cluster.hbs",
35 | "nodes.hbs",
36 | "ring.hbs",
37 | "current_cluster_item.hbs",
38 | "current_nodes_item.hbs",
39 | "staged_cluster_item.hbs",
40 | "partition.hbs",
41 | "degenerate_preflist_chart.hbs",
42 | "quorum_unavailable_chart.hbs",
43 | "all_unavailable_chart.hbs",
44 | "loading.hbs"]}]}
45 | ]}.
46 |
47 | {js_concatenator, [
48 | {doc_root, "priv/admin/js/vendor"},
49 | {out_dir, "priv/admin/js"},
50 | {concatenations, [{"generated/vendor.js", ["jquery-1.10.2.js",
51 | "jquery-ui-1.8.16.custom.min.js",
52 | "handlebars-1.0.0.js",
53 | "ember-1.0.0.js",
54 | "ember-data-1.0.0-beta.1.js",
55 | "minispade.js",
56 | "d3.v3.min.js"], []}]}
57 | ]}.
58 |
59 | {js_stylus, [
60 | {stylus_path, "/usr/local/bin/stylus"},
61 | {doc_root, "priv/admin/css"},
62 | {out_dir, "priv/admin/css/compiled"},
63 | {stylesheets, ["style.styl"]}
64 | ]}.
65 |
--------------------------------------------------------------------------------
/src/riak_control.app.src:
--------------------------------------------------------------------------------
1 | {application, riak_control,
2 | [
3 | {description, "Riak Admin Interface"},
4 | {vsn, git},
5 | {registered, []},
6 | {applications, [
7 | kernel,
8 | stdlib,
9 | webmachine,
10 | riak_core
11 | ]},
12 | {mod, { riak_control_app, []}},
13 | {env, []}
14 | ]}.
15 |
--------------------------------------------------------------------------------
/src/riak_control.erl:
--------------------------------------------------------------------------------
1 | %% ------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% ------------------------------------------------------------------
20 | %%
21 | %% @doc Utilities needed by many pieces of the riak_control app.
22 | %%
23 | -module(riak_control).
24 |
25 | -export([is_app_up/1,
26 | priv_dir/0]).
27 |
28 | %% @doc Find out if any nodes are running a given Riak app.
29 | -spec is_app_up(atom()) -> boolean().
30 | is_app_up(App) ->
31 | riak_core_node_watcher:nodes(App) /= [].
32 |
33 | %% @doc Path for the /priv dir of the riak_control app.
34 | -spec priv_dir() -> string().
35 | priv_dir() ->
36 | code:priv_dir(?MODULE).
37 |
--------------------------------------------------------------------------------
/src/riak_control_app.erl:
--------------------------------------------------------------------------------
1 | %% ------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% ------------------------------------------------------------------
20 | %%
21 | %% @doc Application.
22 |
23 | -module(riak_control_app).
24 |
25 | -behaviour(application).
26 |
27 | %% Application callbacks
28 | -export([start/2,
29 | stop/1]).
30 |
31 | %% ==================================================================
32 | %% Application callbacks
33 | %% ==================================================================
34 |
35 | start(_StartType, _StartArgs) ->
36 | case riak_control_sup:start_link() of
37 | {error, Reason} ->
38 | {error, Reason};
39 | {ok, Pid} ->
40 | riak_core_capability:register({riak_control, member_info_version},
41 | [v1, v0],
42 | v0),
43 | {ok, Pid}
44 | end.
45 |
46 | stop(_State) ->
47 | ok.
48 |
--------------------------------------------------------------------------------
/src/riak_control_ring.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2013 Basho Technologies, Inc.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Helper utilities for dealing with the ring.
22 |
23 | -module(riak_control_ring).
24 |
25 | -export([ring/0,
26 | n_val/0,
27 | status/2,
28 | status/3]).
29 |
30 | -include("riak_control.hrl").
31 |
32 | %% @doc Return the ring.
33 | -spec ring() -> riak_core_ring:riak_core_ring().
34 | ring() ->
35 | {ok, Ring} = riak_core_ring_manager:get_my_ring(),
36 | Ring.
37 |
38 | %% @doc Return the current default n_val.
39 | -spec n_val() -> number().
40 | n_val() ->
41 | {ok, Props} = application:get_env(riak_core, default_bucket_props),
42 | {n_val, NVal} = lists:keyfind(n_val, 1, Props),
43 | NVal.
44 |
45 | %% @doc Return list of nodes, available partition and quorum.
46 | -spec status(riak_core_ring:riak_core_ring(), list(node())) ->
47 | list({number(), number(), number()}).
48 | status(Ring, Unavailable) ->
49 | NVal = n_val(),
50 | Quorum = (NVal div 2) + 1,
51 | status(Ring, NVal, Quorum, Unavailable).
52 |
53 | %% @doc Return list of nodes, available partition and quorum.
54 | -spec status(riak_core_ring:riak_core_ring(), number(), list(node())) ->
55 | list({number(), number(), number()}).
56 | status(Ring, NVal, Unavailable) ->
57 | Quorum = mochinum:int_ceil(NVal / 2),
58 | status(Ring, NVal, Quorum, Unavailable).
59 |
60 | %% @doc Return list of nodes, available partition and quorum.
61 | -spec status(riak_core_ring:riak_core_ring(), number(), number(), list(node())) ->
62 | list({number(), number(), number()}).
63 | status(Ring, NVal, Quorum, Unavailable) ->
64 | Preflists = riak_core_ring:all_preflists(Ring, NVal),
65 | {Status, _, _, _} = lists:foldr(fun fold_preflist_proplist/2,
66 | {[], NVal, Quorum, Unavailable},
67 | Preflists),
68 | lists:usort(fun sort_preflist_proplist/2, Status).
69 |
70 | %% @doc Perform a binary conversion of the index to be returned to th
71 | %% user.
72 | -spec pretty_index(number()) -> binary().
73 | pretty_index(Index) ->
74 | list_to_binary(integer_to_list(Index)).
75 |
76 | %% @doc Sort the preference-as-proplist data structures by index.
77 | -spec sort_preflist_proplist(list(), list()) -> boolean().
78 | sort_preflist_proplist(A, B) ->
79 | proplists:get_value(index, A) < proplists:get_value(index, B).
80 |
81 | %% @doc Fold over the preference lists and generate a data structure
82 | %% representing partitions in the cluster along with primary
83 | %% replicas.
84 | -spec fold_preflist_proplist(list(),
85 | {list(), integer(), integer(), list(node())}) ->
86 | {list(), integer(), integer(), list(node())}.
87 | fold_preflist_proplist(Preflist,
88 | {Status0, NVal, Quorum, Unavailable}) ->
89 | [{Index, _}|_] = Preflist,
90 |
91 | All = [Node || {_, Node} <- Preflist],
92 |
93 | {Down, Up} = lists:partition(fun(Node) ->
94 | lists:member(Node, Unavailable) end, All),
95 |
96 | UUp = lists:usort(Up),
97 | UAll = lists:usort(All),
98 | UDown = lists:usort(Down),
99 |
100 | Status = [[{quorum, Quorum},
101 | {distinct, length(UAll) =:= length(All)},
102 | {index, pretty_index(Index)},
103 | {available, length(Up)},
104 | {unavailable_nodes, UDown},
105 | {available_nodes, UUp},
106 | {all_nodes, UAll}]|Status0],
107 |
108 | {Status, NVal, Quorum, Unavailable}.
109 |
--------------------------------------------------------------------------------
/src/riak_control_routes.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Route helpers.
22 |
23 | -module(riak_control_routes).
24 |
25 | -export([admin_route/1,
26 | nodes_route/0,
27 | cluster_route/0,
28 | handoffs_route/0,
29 | partitions_route/0]).
30 |
31 | %% @doc Provide a resource that all resources sit under.
32 | -spec admin_route(list()) -> list().
33 | admin_route(Rest) ->
34 | ["admin"|Rest].
35 |
36 | %% @doc Return route for node resource.
37 | -spec nodes_route() -> list().
38 | nodes_route() ->
39 | admin_route(["nodes"]).
40 |
41 | %% @doc Return route for node resource.
42 | -spec handoffs_route() -> list().
43 | handoffs_route() ->
44 | admin_route(["handoffs"]).
45 |
46 | %% @doc Return route for partition resource.
47 | -spec partitions_route() -> list().
48 | partitions_route() ->
49 | admin_route(["partitions"]).
50 |
51 | %% @doc Return route for cluster resource.
52 | -spec cluster_route() -> list().
53 | cluster_route() ->
54 | admin_route(["cluster"]).
55 |
--------------------------------------------------------------------------------
/src/riak_control_security.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc SSL and Authorization enforcement for administration URLs.
22 |
23 | -module(riak_control_security).
24 |
25 | -export([scheme_is_available/2,
26 | enforce_auth/2,
27 | csrf_token/2,
28 | is_valid_csrf_token/2,
29 | is_null_origin/1,
30 | is_protected/2]).
31 |
32 | -include("riak_control.hrl").
33 |
34 | -type context() :: term() | undefined.
35 | -type csrf_token() :: list() | undefined.
36 |
37 | %% @doc Enforce use of HTTPS only when a valid auth scheme is enabled.
38 | -spec scheme_is_available(wrq:reqdata(), context()) ->
39 | {boolean(), wrq:reqdata(), context()}.
40 | scheme_is_available(RD, Ctx) ->
41 | case app_helper:get_env(riak_control, force_ssl, undefined) of
42 | undefined ->
43 | %% Handle upgrade, where we want to preserve existing
44 | %% behavior.
45 | case app_helper:get_env(riak_control, auth, none) of
46 | none ->
47 | {true, RD, Ctx};
48 | _ ->
49 | redirect_if_not_ssl(RD, Ctx)
50 | end;
51 | true ->
52 | redirect_if_not_ssl(RD, Ctx);
53 | _ ->
54 | {true, RD, Ctx}
55 | end.
56 |
57 | %% @doc Redirect if request is not SSL.
58 | -spec redirect_if_not_ssl(wrq:reqdata(), context()) ->
59 | {boolean(), wrq:reqdata(), context()}.
60 | redirect_if_not_ssl(ReqData, Context) ->
61 | case wrq:scheme(ReqData) of
62 | https ->
63 | {true, ReqData, Context};
64 | _ ->
65 | https_redirect(ReqData, Context)
66 | end.
67 |
68 | %% @doc Perform http redirect to ssl.
69 | -spec https_redirect(wrq:reqdata(), context()) ->
70 | {{halt, 303}, wrq:reqdata(), context()}.
71 | https_redirect(RD,Ctx) ->
72 | Path = wrq:raw_path(RD),
73 | Host = wrq:sock(RD),
74 | Bindings = app_helper:get_env(riak_core, https, []),
75 | Location = case lists:keyfind(Host, 1, Bindings) of
76 | {_, Port} ->
77 | ["https://", Host, ":", integer_to_list(Port), Path];
78 | _ ->
79 | ["https://", Host, Path]
80 | end,
81 | {{halt,303}, wrq:set_resp_header("Location", Location, RD), Ctx}.
82 |
83 | %% @doc Intended to be called from a webmachine resource's
84 | %% is_authorized function. The return value is a valid resource
85 | %% return value (`{Result, ReqData, Context}').
86 | %%
87 | %% This function checks for valid authentication in the request. If
88 | %% the authentication is valid, `true' is returned. If it is invalid,
89 | %% the value for the response WWW-Authenticate header is returned.
90 | %%
91 | %% The correct credentials are controled by the appenv
92 | %% `riak_control:auth'. Valid values include:
93 | %%
94 | %% - `userlist' :: `riak_control:userlist' will contain a list of
95 | %% {"user","pass"} pairs that are used.
96 | %%
97 | %% - `none' :: No authentication.
98 | %%
99 | -spec enforce_auth(wrq:reqdata(), context()) ->
100 | {boolean(), wrq:reqdata(), context()}.
101 | enforce_auth(RD, Ctx) ->
102 | case app_helper:get_env(riak_control,auth,none) of
103 | none ->
104 | {true, RD, Ctx};
105 | Auth ->
106 | case wrq:get_req_header("authorization", RD) of
107 | "Basic "++Base64 ->
108 | enforce_basic_auth(RD, Ctx, Base64, Auth);
109 | _ ->
110 | {?ADMIN_AUTH_HEAD, RD, Ctx}
111 | end
112 | end.
113 |
114 | %% @doc Enforce basic auth.
115 | -spec enforce_basic_auth(wrq:reqdata(), context(), term(), atom()) ->
116 | {boolean(), wrq:reqdata(), context()}.
117 | enforce_basic_auth(RD, Ctx, Base64, Auth) ->
118 | Str = base64:mime_decode_to_string(Base64),
119 | case string:tokens(Str, ":") of
120 | [User, Pass] ->
121 | enforce_user_pass(RD, Ctx, User, Pass, Auth);
122 | _ ->
123 | {?ADMIN_AUTH_HEAD, RD, Ctx}
124 | end.
125 |
126 | %% @doc Enforce user and password match.
127 | -spec enforce_user_pass(wrq:reqdata(), context(), nonempty_string(),
128 | nonempty_string(), atom()) ->
129 | {boolean(), wrq:reqdata(), context()}.
130 | enforce_user_pass(RD, Ctx, User, Pass, Auth) ->
131 | case valid_userpass(User, Pass, Auth) of
132 | true ->
133 | {true, RD, Ctx};
134 | _ ->
135 | {?ADMIN_AUTH_HEAD, RD, Ctx}
136 | end.
137 |
138 | %% @doc Validate username and password given a particular auth style.
139 | -spec valid_userpass(nonempty_string(), nonempty_string(),
140 | atom()) -> boolean().
141 | valid_userpass(_User, _Pass, none) ->
142 | true;
143 | valid_userpass(User, Pass, userlist) ->
144 | Users = app_helper:get_env(riak_control, userlist, []),
145 | proplists:get_value(User, Users) == Pass;
146 | valid_userpass(_User, _Pass, _Auth) ->
147 | error_logger:warning_msg("Unknown auth type '~p'", [_Auth]),
148 | false.
149 |
150 | %% @doc Generate a new CSRF token.
151 | -spec csrf_token(wrq:reqdata(), context()) -> csrf_token().
152 | csrf_token(ReqData, Context) ->
153 | case get_csrf_token(ReqData, Context) of
154 | undefined ->
155 | binary_to_list(riak_core_base64url:encode(crypto:rand_bytes(256)));
156 | Token ->
157 | Token
158 | end.
159 |
160 | %% @doc Get the CSRF token from the cookie.
161 | -spec get_csrf_token(wrq:reqdata(), context()) -> csrf_token().
162 | get_csrf_token(ReqData, _Context) ->
163 | wrq:get_cookie_value("csrf_token", ReqData).
164 |
165 | %% @doc Ensure this request contains a valid csrf protection token.
166 | -spec is_valid_csrf_token(wrq:reqdata(), context()) -> boolean().
167 | is_valid_csrf_token(ReqData, Context) ->
168 | HeaderToken = wrq:get_req_header("X-CSRF-Token", ReqData),
169 | CookieToken = get_csrf_token(ReqData, Context),
170 | HeaderToken /= undefined andalso HeaderToken == CookieToken.
171 |
172 | %% @doc Is this a protected method?
173 | -spec is_protected_method(wrq:reqdata()) -> boolean().
174 | is_protected_method(ReqData) ->
175 | Method = wrq:method(ReqData),
176 | Method == 'POST' orelse Method == 'PUT'.
177 |
178 | %% @doc Is this a protected?
179 | -spec is_protected(wrq:reqdata(), context()) -> boolean().
180 | is_protected(ReqData, Context) ->
181 | (is_null_origin(ReqData) or
182 | not is_valid_csrf_token(ReqData, Context)) and
183 | is_protected_method(ReqData).
184 |
185 | %% @doc Check if the Origin header is "null". This is useful to look for
186 | %% attempts at CSRF, but is not a complete answer to the problem.
187 | -spec is_null_origin(wrq:reqdata()) -> boolean().
188 | is_null_origin(ReqData) ->
189 | case wrq:get_req_header("Origin", ReqData) of
190 | "null" ->
191 | true;
192 | _ ->
193 | false
194 | end.
195 |
--------------------------------------------------------------------------------
/src/riak_control_sup.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Application supervisor.
22 |
23 | -module(riak_control_sup).
24 |
25 | -behaviour(supervisor).
26 |
27 | %% API
28 | -export([start_link/0]).
29 |
30 | %% Supervisor callbacks
31 | -export([init/1]).
32 |
33 | %% Helper macro for declaring children of supervisor
34 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
35 |
36 | %% ===================================================================
37 | %% API functions
38 | %% ===================================================================
39 |
40 | start_link() ->
41 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
42 |
43 | %% ===================================================================
44 | %% Supervisor callbacks
45 | %% ===================================================================
46 |
47 | init([]) ->
48 | RiakControlSession={riak_control_session,
49 | {riak_control_session, start_link, []},
50 | permanent,
51 | 5000,
52 | worker,
53 | [riak_control_session]},
54 |
55 | %% determine if riak_control is enabled or not
56 | case app_helper:get_env(riak_control, enabled, false) of
57 | true ->
58 | Resources = [riak_control_wm_gui,
59 | riak_control_wm_cluster,
60 | riak_control_wm_nodes,
61 | riak_control_wm_partitions],
62 | Routes = lists:append([Resource:routes() || Resource <- Resources]),
63 | _ = [webmachine_router:add_route(R) || R <- Routes],
64 |
65 | %% start riak control
66 | {ok, { {one_for_one, 5, 10}, [RiakControlSession] } };
67 | _ ->
68 | {ok, { {one_for_one, 5, 10}, [] } }
69 | end.
70 |
--------------------------------------------------------------------------------
/src/riak_control_wm_gui.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Provides a resource for serving up the GUI skeleton.
22 |
23 | -module(riak_control_wm_gui).
24 |
25 | -export([routes/0,
26 | init/1,
27 | content_types_provided/2,
28 | resource_exists/2,
29 | previously_existed/2,
30 | moved_permanently/2,
31 | to_resource/2,
32 | is_authorized/2,
33 | service_available/2
34 | ]).
35 |
36 | %% riak_control and webmachine dependencies
37 | -include("riak_control.hrl").
38 | -include_lib("webmachine/include/webmachine.hrl").
39 |
40 | -record(ctx, {
41 | base_url,
42 | base_path
43 | }).
44 | -type context() :: #ctx{}.
45 |
46 | %% mappings to the various content types supported for this resource
47 | -define(CONTENT_TYPES,[{"text/css", to_resource},
48 | {"text/html",to_resource},
49 | {"text/plain",to_resource},
50 | {"text/javascript",to_resource}
51 | ]).
52 |
53 | %% defines the webmachine routes this module handles
54 | -spec routes() -> [webmachine_dispatcher:matchterm()].
55 | routes() ->
56 | [{riak_control_routes:admin_route([]),?MODULE,index},
57 | {riak_control_routes:admin_route(["ui",'*']),?MODULE,undefined},
58 | {riak_control_routes:admin_route(["ui","index.html"]),?MODULE,oldindex}
59 | ].
60 |
61 | %% entry-point for the resource from webmachine
62 | -spec init(any()) -> {ok, any()}.
63 | init(Resource) -> {ok,Resource}.
64 |
65 | %% redirect to the index page if no file given
66 | -spec moved_permanently(wrq:reqdata(), oldindex|context()) ->
67 | {false|{true, string()}, wrq:reqdata(), index|context()}.
68 | moved_permanently(Req,oldindex) -> {{true,"/admin"},Req,index};
69 | moved_permanently(Req,Ctx) -> {false,Req,Ctx}.
70 |
71 | %% the index file isn't here
72 | -spec previously_existed(wrq:reqdata(), context()|oldindex) ->
73 | {boolean(), wrq:reqdata(), context()|oldindex}.
74 | previously_existed(Req,oldindex) -> {true,Req,oldindex};
75 | previously_existed(Req,Ctx) -> {false,Req,Ctx}.
76 |
77 | %% a resource other than the index is here
78 | -spec resource_exists(wrq:reqdata(), context()|oldindex) ->
79 | {boolean(), wrq:reqdata(), context()|oldindex}.
80 | resource_exists (Req,oldindex) -> {false,Req,oldindex};
81 | resource_exists (Req,Ctx) -> {true,Req,Ctx}.
82 |
83 | %% redirect to SSL port if using HTTP
84 | -spec service_available(wrq:reqdata(), context()) ->
85 | {boolean() | {halt, non_neg_integer()}, wrq:reqdata(), context()}.
86 | service_available(RD,C) ->
87 | riak_control_security:scheme_is_available(RD,C).
88 |
89 | %% validate username and password
90 | -spec is_authorized(wrq:reqdata(), context()) ->
91 | {true | string(), wrq:reqdata(), context()}.
92 | is_authorized(RD,C) ->
93 | riak_control_security:enforce_auth(RD,C).
94 |
95 | %% return the list of available content types for webmachine
96 | -spec content_types_provided(wrq:reqdata(), context()) ->
97 | {[{ContentType::string(), HandlerFunction::atom()}],
98 | wrq:reqdata(), context()}.
99 | content_types_provided(Req,Ctx=index) ->
100 | {[{"text/html", to_resource}],Req, Ctx};
101 | content_types_provided(Req,Ctx) ->
102 | Index = file_path(Req),
103 | MimeType = webmachine_util:guess_mime(Index),
104 | {[{MimeType, to_resource}],Req, Ctx}.
105 |
106 | %% return file path
107 | -spec file_path(wrq:reqdata()) -> string().
108 | file_path(Req) ->
109 | Path=wrq:path_tokens(Req),
110 | filename:join([riak_control:priv_dir(),"admin"] ++ Path).
111 |
112 | %% loads a resource file from disk and returns it
113 | -spec get_file(wrq:reqdata()) ->
114 | binary().
115 | get_file(Req) ->
116 | Index = file_path(Req),
117 | {ok,Source}=file:read_file(Index),
118 | Source.
119 |
120 | %% respond to an index request
121 | -spec to_resource(wrq:reqdata(), context()) ->
122 | {binary(), wrq:reqdata(), context()}.
123 | to_resource(Req,Ctx=index) ->
124 | Token = riak_control_security:csrf_token(Req, Ctx),
125 | {ok, Content} = index_dtl:render([{csrf_token, Token}]),
126 | {Content, wrq:set_resp_header("Set-Cookie", "csrf_token="++Token++"; httponly", Req), Ctx};
127 |
128 | %% respond to a file request
129 | to_resource(Req,Ctx) ->
130 | {get_file(Req),Req,Ctx}.
131 |
--------------------------------------------------------------------------------
/src/riak_control_wm_nodes.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Returns a list of all nodes, and information about their
22 | %% membership in the cluster.
23 |
24 | -module(riak_control_wm_nodes).
25 |
26 | -export([routes/0,
27 | init/1,
28 | to_json/2,
29 | forbidden/2,
30 | is_authorized/2,
31 | service_available/2,
32 | content_types_provided/2]).
33 |
34 | -include("riak_control.hrl").
35 | -include_lib("webmachine/include/webmachine.hrl").
36 |
37 | %% @doc Return routes this resource should respond to.
38 | -spec routes() -> [webmachine_dispatcher:matchterm()].
39 | routes() ->
40 | [{riak_control_routes:nodes_route(), ?MODULE, []}].
41 |
42 | %% @doc Initialize resource.
43 | -spec init([]) -> {ok, undefined}.
44 | init([]) ->
45 | {ok, undefined}.
46 |
47 | %% @doc Prevent requests coming from an invalid origin.
48 | -spec forbidden(wrq:reqdata(), undefined) ->
49 | {boolean(), wrq:reqdata(), undefined}.
50 | forbidden(ReqData, Context) ->
51 | {riak_control_security:is_protected(ReqData, Context), ReqData, Context}.
52 |
53 | %% @doc Handle SSL requests.
54 | -spec service_available(wrq:reqdata(), undefined) ->
55 | {boolean(), wrq:reqdata(), undefined}.
56 | service_available(ReqData, Context) ->
57 | riak_control_security:scheme_is_available(ReqData, Context).
58 |
59 | %% @doc Ensure user has access.
60 | -spec is_authorized(wrq:reqdata(), undefined) ->
61 | {boolean(), wrq:reqdata(), undefined}.
62 | is_authorized(ReqData, Context) ->
63 | riak_control_security:enforce_auth(ReqData, Context).
64 |
65 | %% @doc Return content-types which are provided.
66 | -spec content_types_provided(wrq:reqdata(), undefined) ->
67 | {list(), wrq:reqdata(), undefined}.
68 | content_types_provided(ReqData, Context) ->
69 | {[{"application/json", to_json}], ReqData, Context}.
70 |
71 | %% @doc Return the current cluster, along with a plan if it's available.
72 | -spec to_json(wrq:reqdata(), undefined) -> {binary(), wrq:reqdata(), undefined}.
73 | to_json(ReqData, Context) ->
74 | %% Get the current node list.
75 | {ok, _V, RawNodes} = riak_control_session:get_nodes(),
76 |
77 | Nodes = [jsonify_node(Node) || Node=?MEMBER_INFO{} <- RawNodes],
78 | Encoded = mochijson2:encode({struct, [{nodes, Nodes}]}),
79 |
80 | {Encoded, ReqData, Context}.
81 |
82 | %% @doc Turn a node into a proper struct for serialization.
83 | -spec jsonify_node(member()) -> {struct, list()}.
84 | jsonify_node(Node) ->
85 | LWM=app_helper:get_env(riak_control,low_mem_watermark,0.1),
86 | MemUsed = Node?MEMBER_INFO.mem_used,
87 | MemTotal = Node?MEMBER_INFO.mem_total,
88 | Reachable = Node?MEMBER_INFO.reachable,
89 | LowMem = low_mem(Reachable, MemUsed, MemTotal, LWM),
90 | {struct,[{"name",Node?MEMBER_INFO.node},
91 | {"status",Node?MEMBER_INFO.status},
92 | {"reachable",Reachable},
93 | {"ring_pct",Node?MEMBER_INFO.ring_pct},
94 | {"pending_pct",Node?MEMBER_INFO.pending_pct},
95 | {"mem_total",MemTotal},
96 | {"mem_used",MemUsed},
97 | {"mem_erlang",Node?MEMBER_INFO.mem_erlang},
98 | {"low_mem",LowMem},
99 | {"me",Node?MEMBER_INFO.node == node()},
100 | {"action",Node?MEMBER_INFO.action},
101 | {"replacement",Node?MEMBER_INFO.replacement}]}.
102 |
103 | %% @doc Determine if a node has low memory.
104 | -spec low_mem(boolean(), number() | atom(), number() | atom(), number())
105 | -> boolean().
106 | low_mem(Reachable, MemUsed, MemTotal, LWM) ->
107 | case Reachable of
108 | false ->
109 | false;
110 | true ->
111 | %% There is a race where the node is online, but memsup is
112 | %% still starting so memory is unavailable.
113 | case MemTotal of
114 | undefined ->
115 | false;
116 | _ ->
117 | 1.0 - (MemUsed/MemTotal) < LWM
118 | end
119 | end.
120 |
--------------------------------------------------------------------------------
/src/riak_control_wm_partitions.erl:
--------------------------------------------------------------------------------
1 | %% -------------------------------------------------------------------
2 | %%
3 | %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved.
4 | %%
5 | %% This file is provided to you under the Apache License,
6 | %% Version 2.0 (the "License"); you may not use this file
7 | %% except in compliance with the License. You may obtain
8 | %% a copy of the License at
9 | %%
10 | %% http://www.apache.org/licenses/LICENSE-2.0
11 | %%
12 | %% Unless required by applicable law or agreed to in writing,
13 | %% software distributed under the License is distributed on an
14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | %% KIND, either express or implied. See the License for the
16 | %% specific language governing permissions and limitations
17 | %% under the License.
18 | %%
19 | %% -------------------------------------------------------------------
20 | %%
21 | %% @doc Returns a list of all partitions, how many primary replicas
22 | %% are available, what the current n_val and quorum
23 | %% configuration is, as well as the unavailable nodes for
24 | %% each partition.
25 |
26 | -module(riak_control_wm_partitions).
27 |
28 | -export([routes/0,
29 | init/1,
30 | content_types_provided/2,
31 | to_json/2,
32 | is_authorized/2,
33 | service_available/2,
34 | forbidden/2]).
35 |
36 | -include("riak_control.hrl").
37 | -include_lib("webmachine/include/webmachine.hrl").
38 |
39 | -define(CONTENT_TYPES, [{"application/json",to_json}]).
40 |
41 | -define(VNODE_TYPES, [riak_kv,riak_pipe,riak_search]).
42 |
43 | -record(context, {partitions, default_n_val}).
44 | -type context() :: #context{}.
45 |
46 | %% @doc Route handling.
47 | -spec routes() -> [webmachine_dispatcher:matchterm()].
48 | routes() ->
49 | [{riak_control_routes:partitions_route(), ?MODULE, []}].
50 |
51 | %% @doc Get partition list at the start of the request.
52 | -spec init(list()) -> {ok, context()}.
53 | init([]) ->
54 | {ok, _, Partitions} = riak_control_session:get_partitions(),
55 | {ok, _, DefNVal} = riak_control_session:get_default_n_val(),
56 | {ok, #context{partitions=Partitions, default_n_val=DefNVal}}.
57 |
58 | %% @doc Validate origin.
59 | -spec forbidden(wrq:reqdata(), context()) ->
60 | {boolean(), wrq:reqdata(), context()}.
61 | forbidden(ReqData, Context) ->
62 | {riak_control_security:is_null_origin(ReqData), ReqData, Context}.
63 |
64 | %% @doc Determine if it's available.
65 | -spec service_available(wrq:reqdata(), context()) ->
66 | {boolean() | {halt, non_neg_integer()}, wrq:reqdata(), context()}.
67 | service_available(ReqData, Context) ->
68 | riak_control_security:scheme_is_available(ReqData, Context).
69 |
70 | %% @doc Handle authorization.
71 | -spec is_authorized(wrq:reqdata(), context()) ->
72 | {true | string(), wrq:reqdata(), context()}.
73 | is_authorized(ReqData, Context) ->
74 | riak_control_security:enforce_auth(ReqData, Context).
75 |
76 | %% @doc Return available content types.
77 | -spec content_types_provided(wrq:reqdata(), context()) ->
78 | {[{ContentType::string(), HandlerFunction::atom()}], wrq:reqdata(), context()}.
79 | content_types_provided(ReqData, Context) ->
80 | {?CONTENT_TYPES, ReqData, Context}.
81 |
82 | %% @doc Return a list of partitions.
83 | -spec to_json(wrq:reqdata(),context()) ->
84 | {iolist(), wrq:reqdata(), context()}.
85 | to_json(ReqData, Context=#context{partitions=Partitions, default_n_val=DefNVal}) ->
86 | Encoded = mochijson2:encode({struct,[{partitions, Partitions},
87 | {default_n_val, DefNVal}]}),
88 | {Encoded, ReqData, Context}.
89 |
--------------------------------------------------------------------------------
/templates/index.dtl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Riak Control
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICvTCCAiYCCQDgxT3HogRJ/TANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
3 | VVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEf
4 | MB0GA1UEChMWQmFzaG8gVGVjaG5vbG9naWVzIEluYzENMAsGA1UECxMEUmlhazEY
5 | MBYGA1UEAxMPSmVmZnJleSBNYXNzdW5nMR0wGwYJKoZIhvcNAQkBFg5qZWZmQGJh
6 | c2hvLmNvbTAeFw0xMTEwMzExNjQ3NTNaFw0yMTEwMjgxNjQ3NTNaMIGiMQswCQYD
7 | VQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAGA1UEBxMJQ2FtYnJp
8 | ZGdlMR8wHQYDVQQKExZCYXNobyBUZWNobm9sb2dpZXMgSW5jMQ0wCwYDVQQLEwRS
9 | aWFrMRgwFgYDVQQDEw9KZWZmcmV5IE1hc3N1bmcxHTAbBgkqhkiG9w0BCQEWDmpl
10 | ZmZAYmFzaG8uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkys/CB8Ce
11 | fO19JsFiL2K5pODMWFmXxfQgvARB2rIvoJ4R4mNKI639xRbR+gCPreJvZRw8trgD
12 | 8sARFv8J1SlqqYRDN8zfMlvolXh6Atujou/LwjUpTA3pMe9lZrWU1+JZOMAk79lz
13 | O/1etfR12By0SqNfDgUMIIZST7i3Fw2IkwIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
14 | ABTOXMBYoEn0biwqaDcZzInZvZHupFkmkOABumgJ5xVDQ/9LIzy9mfg0Ko+JERlM
15 | 0w0dliu2sfFoXLps9EohjIzrU1B3CwSNvNqPmcj9V4k0iXrsvDfbG9eJ9nYaUY0Y
16 | L5I9/KAOIf3fEmnFbjtmyiLVhrM6kBB3fvoAVQfwL6cZ
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/test/javascripts/admin:
--------------------------------------------------------------------------------
1 | ../../priv/admin/
--------------------------------------------------------------------------------
/test/javascripts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Riak Control Test Suite
6 |
7 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/javascripts/pages_test.js:
--------------------------------------------------------------------------------
1 | module("Pages", {
2 | setup: function() {
3 | RiakControl.reset();
4 | RiakControl.Node.FIXTURES = [{
5 | id: "low_mem_node",
6 | status: "valid",
7 | reachable: true,
8 | ring_pct: 0.1875,
9 | pending_pct: 0.1875,
10 | mem_total: 100,
11 | mem_used: 100,
12 | mem_erlang: 100,
13 | low_mem: true,
14 | me: true,
15 | claimant: false
16 | }];
17 |
18 | $.mockjax({
19 | url: "/admin/cluster",
20 | responseText: MockResponses.ClusterResponse
21 | });
22 |
23 | $.mockjax({
24 | url: "/admin/partitions",
25 | responseText: MockResponses.PartitionsResponse
26 | });
27 |
28 | $.mockjaxSettings.logging = false;
29 | },
30 |
31 | teardown: function() {
32 | $.mockjaxClear();
33 | }
34 | });
35 |
36 | test("snapshot page shows problematic nodes", function() {
37 | visit("/").then(function() {
38 | equal(find("#snapshot-headline").text(), "Current Snapshot");
39 | equal(find("#low_mem-nodes-list li").length, 1, "Detects 1 low memory node");
40 | });
41 | });
42 |
43 | test("cluster page lists changes", function() {
44 | visit("/cluster").then(function() {
45 | equal(find("#cluster-headline").text(), "Cluster Management");
46 | ok(find("#planned-area").text().indexOf("leave") !== -1, "Displays the leaving node");
47 | });
48 | });
49 |
50 | test("nodes page lists nodes", function() {
51 | visit("/nodes").then(function() {
52 | equal(find("#node-headline").text(), "Node Management");
53 | ok(find("#node-list").text().indexOf("low_mem_node") !== -1, "Displays the low mem node")
54 | });
55 | })
56 |
57 | test("ring page shows partitions", function() {
58 | visit("/ring").then(function() {
59 | equal(find("#ring-headline").text(), "Ring Status");
60 | equal(find("#partition-container div div.ember-view").length, 64);
61 | });
62 | })
63 |
--------------------------------------------------------------------------------
/test/javascripts/runner.js:
--------------------------------------------------------------------------------
1 | /*
2 | * QtWebKit-powered headless test runner using PhantomJS
3 | *
4 | * PhantomJS binaries: http://phantomjs.org/download.html
5 | * Requires PhantomJS 1.6+ (1.7+ recommended)
6 | *
7 | * Run with:
8 | * phantomjs runner.js [url-of-your-qunit-testsuite]
9 | *
10 | * e.g.
11 | * phantomjs runner.js http://localhost/qunit/test/index.html
12 | */
13 |
14 | /*global phantom:false, require:false, console:false, window:false, QUnit:false */
15 |
16 | (function() {
17 | 'use strict';
18 |
19 | var url, page, timeout,
20 | args = require('system').args;
21 |
22 | // arg[0]: scriptName, args[1...]: arguments
23 | if (args.length < 2 || args.length > 3) {
24 | console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite] [timeout-in-seconds]');
25 | phantom.exit(1);
26 | }
27 |
28 | url = args[1];
29 | page = require('webpage').create();
30 | if (args[2] !== undefined) {
31 | timeout = parseInt(args[2], 10);
32 | }
33 |
34 | // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`)
35 | page.onConsoleMessage = function(msg) {
36 | console.log(msg);
37 | };
38 |
39 | page.onInitialized = function() {
40 | page.evaluate(addLogging);
41 | };
42 |
43 | page.onCallback = function(message) {
44 | var result,
45 | failed;
46 |
47 | if (message) {
48 | if (message.name === 'QUnit.done') {
49 | result = message.data;
50 | failed = !result || result.failed;
51 |
52 | phantom.exit(failed ? 1 : 0);
53 | }
54 | }
55 | };
56 |
57 | page.open(url, function(status) {
58 | if (status !== 'success') {
59 | console.error('Unable to access network: ' + status);
60 | phantom.exit(1);
61 | } else {
62 | // Cannot do this verification with the 'DOMContentLoaded' handler because it
63 | // will be too late to attach it if a page does not have any script tags.
64 | var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); });
65 | if (qunitMissing) {
66 | console.error('The `QUnit` object is not present on this page.');
67 | phantom.exit(1);
68 | }
69 |
70 | // Set a timeout on the test running, otherwise tests with async problems will hang forever
71 | if (typeof timeout === 'number') {
72 | setTimeout(function() {
73 | console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...');
74 | phantom.exit(1);
75 | }, timeout * 1000);
76 | }
77 |
78 | // Do nothing... the callback mechanism will handle everything!
79 | }
80 | });
81 |
82 | function addLogging() {
83 | window.document.addEventListener('DOMContentLoaded', function() {
84 | var currentTestAssertions = [];
85 |
86 | QUnit.log(function(details) {
87 | var response;
88 |
89 | // Ignore passing assertions
90 | if (details.result) {
91 | return;
92 | }
93 |
94 | response = details.message || '';
95 |
96 | if (typeof details.expected !== 'undefined') {
97 | if (response) {
98 | response += ', ';
99 | }
100 |
101 | response += 'expected: ' + details.expected + ', but was: ' + details.actual;
102 | }
103 |
104 | if (details.source) {
105 | response += "\n" + details.source;
106 | }
107 |
108 | currentTestAssertions.push('Failed assertion: ' + response);
109 | });
110 |
111 | QUnit.testDone(function(result) {
112 | var i,
113 | len,
114 | name = result.module + ': ' + result.name;
115 |
116 | if (result.failed) {
117 | console.log('Test failed: ' + name);
118 |
119 | for (i = 0, len = currentTestAssertions.length; i < len; i++) {
120 | console.log(' ' + currentTestAssertions[i]);
121 | }
122 | }
123 |
124 | currentTestAssertions.length = 0;
125 | });
126 |
127 | QUnit.done(function(result) {
128 | console.log('Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.');
129 |
130 | if (typeof window.callPhantom === 'function') {
131 | window.callPhantom({
132 | 'name': 'QUnit.done',
133 | 'data': result
134 | });
135 | }
136 | });
137 | }, false);
138 | }
139 | })();
140 |
--------------------------------------------------------------------------------
/test/javascripts/support/qunit-1.12.0.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.12.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://qunitjs.com
5 | *
6 | * Copyright 2012 jQuery Foundation and other contributors
7 | * Released under the MIT license.
8 | * http://jquery.org/license
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 5px 5px 0 0;
42 | -moz-border-radius: 5px 5px 0 0;
43 | -webkit-border-top-right-radius: 5px;
44 | -webkit-border-top-left-radius: 5px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-testrunner-toolbar label {
58 | display: inline-block;
59 | padding: 0 .5em 0 .1em;
60 | }
61 |
62 | #qunit-banner {
63 | height: 5px;
64 | }
65 |
66 | #qunit-testrunner-toolbar {
67 | padding: 0.5em 0 0.5em 2em;
68 | color: #5E740B;
69 | background-color: #eee;
70 | overflow: hidden;
71 | }
72 |
73 | #qunit-userAgent {
74 | padding: 0.5em 0 0.5em 2.5em;
75 | background-color: #2b81af;
76 | color: #fff;
77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
78 | }
79 |
80 | #qunit-modulefilter-container {
81 | float: right;
82 | }
83 |
84 | /** Tests: Pass/Fail */
85 |
86 | #qunit-tests {
87 | list-style-position: inside;
88 | }
89 |
90 | #qunit-tests li {
91 | padding: 0.4em 0.5em 0.4em 2.5em;
92 | border-bottom: 1px solid #fff;
93 | list-style-position: inside;
94 | }
95 |
96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
97 | display: none;
98 | }
99 |
100 | #qunit-tests li strong {
101 | cursor: pointer;
102 | }
103 |
104 | #qunit-tests li a {
105 | padding: 0.5em;
106 | color: #c2ccd1;
107 | text-decoration: none;
108 | }
109 | #qunit-tests li a:hover,
110 | #qunit-tests li a:focus {
111 | color: #000;
112 | }
113 |
114 | #qunit-tests li .runtime {
115 | float: right;
116 | font-size: smaller;
117 | }
118 |
119 | .qunit-assert-list {
120 | margin-top: 0.5em;
121 | padding: 0.5em;
122 |
123 | background-color: #fff;
124 |
125 | border-radius: 5px;
126 | -moz-border-radius: 5px;
127 | -webkit-border-radius: 5px;
128 | }
129 |
130 | .qunit-collapsed {
131 | display: none;
132 | }
133 |
134 | #qunit-tests table {
135 | border-collapse: collapse;
136 | margin-top: .2em;
137 | }
138 |
139 | #qunit-tests th {
140 | text-align: right;
141 | vertical-align: top;
142 | padding: 0 .5em 0 0;
143 | }
144 |
145 | #qunit-tests td {
146 | vertical-align: top;
147 | }
148 |
149 | #qunit-tests pre {
150 | margin: 0;
151 | white-space: pre-wrap;
152 | word-wrap: break-word;
153 | }
154 |
155 | #qunit-tests del {
156 | background-color: #e0f2be;
157 | color: #374e0c;
158 | text-decoration: none;
159 | }
160 |
161 | #qunit-tests ins {
162 | background-color: #ffcaca;
163 | color: #500;
164 | text-decoration: none;
165 | }
166 |
167 | /*** Test Counts */
168 |
169 | #qunit-tests b.counts { color: black; }
170 | #qunit-tests b.passed { color: #5E740B; }
171 | #qunit-tests b.failed { color: #710909; }
172 |
173 | #qunit-tests li li {
174 | padding: 5px;
175 | background-color: #fff;
176 | border-bottom: none;
177 | list-style-position: inside;
178 | }
179 |
180 | /*** Passing Styles */
181 |
182 | #qunit-tests li li.pass {
183 | color: #3c510c;
184 | background-color: #fff;
185 | border-left: 10px solid #C6E746;
186 | }
187 |
188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
189 | #qunit-tests .pass .test-name { color: #366097; }
190 |
191 | #qunit-tests .pass .test-actual,
192 | #qunit-tests .pass .test-expected { color: #999999; }
193 |
194 | #qunit-banner.qunit-pass { background-color: #C6E746; }
195 |
196 | /*** Failing Styles */
197 |
198 | #qunit-tests li li.fail {
199 | color: #710909;
200 | background-color: #fff;
201 | border-left: 10px solid #EE5757;
202 | white-space: pre;
203 | }
204 |
205 | #qunit-tests > li:last-child {
206 | border-radius: 0 0 5px 5px;
207 | -moz-border-radius: 0 0 5px 5px;
208 | -webkit-border-bottom-right-radius: 5px;
209 | -webkit-border-bottom-left-radius: 5px;
210 | }
211 |
212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
213 | #qunit-tests .fail .test-name,
214 | #qunit-tests .fail .module-name { color: #000000; }
215 |
216 | #qunit-tests .fail .test-actual { color: #EE5757; }
217 | #qunit-tests .fail .test-expected { color: green; }
218 |
219 | #qunit-banner.qunit-fail { background-color: #EE5757; }
220 |
221 |
222 | /** Result */
223 |
224 | #qunit-testresult {
225 | padding: 0.5em 0.5em 0.5em 2.5em;
226 |
227 | color: #2b81af;
228 | background-color: #D2E0E6;
229 |
230 | border-bottom: 1px solid white;
231 | }
232 | #qunit-testresult .module-name {
233 | font-weight: bold;
234 | }
235 |
236 | /** Fixture */
237 |
238 | #qunit-fixture {
239 | position: absolute;
240 | top: -10000px;
241 | left: -10000px;
242 | width: 1000px;
243 | height: 1000px;
244 | }
245 |
--------------------------------------------------------------------------------
/test/javascripts/test_helper.js:
--------------------------------------------------------------------------------
1 | RiakControl.Store.reopen({
2 | adapter: DS.FixtureAdapter,
3 | simulateRemoteResponse: false
4 | });
5 |
6 | RiakControl.store = RiakControl.Store.create();
7 | // fixtures are assigned in the tests where they are needed
8 | RiakControl.Node.FIXTURES = [];
9 |
10 | RiakControl.rootElement = "#ember-testing";
11 | RiakControl.setupForTesting();
12 | RiakControl.injectTestHelpers();
13 |
--------------------------------------------------------------------------------
/test/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXAIBAAKBgQCkys/CB8CefO19JsFiL2K5pODMWFmXxfQgvARB2rIvoJ4R4mNK
3 | I639xRbR+gCPreJvZRw8trgD8sARFv8J1SlqqYRDN8zfMlvolXh6Atujou/LwjUp
4 | TA3pMe9lZrWU1+JZOMAk79lzO/1etfR12By0SqNfDgUMIIZST7i3Fw2IkwIDAQAB
5 | AoGANAW2eolZ/G5xxo2ChQ1yfCqZsMi/V9NtExxnt6Zjk/d/jyPJtnD3D2K1ponm
6 | vXTmQ8ZGmMAR7WUnzv1Ue/UoAntcyXwKAm+T+2IUJiir/qzYKLSn8FJ3wA+OWYKs
7 | 1nSryi54IuNenKUslxMPDPk/0bM6nZS2AvbNPYhX7a8evXkCQQDNdsvDO3Ofn0pJ
8 | +3bMLH5Ch/adrJ0TfF1H8n9pxiuq813ppffXsyzv3haTbGHOtEM5tILYbHmO16h1
9 | vY+hhoHHAkEAzVMVRaDefjp1qKfoyRm9ySa3GgH71t1dvm1jGTRxNk9M8pekLDz+
10 | GWyTzffM2/+8Xz4RFzLjqAoAjzBGMeAC1QJAANiycjV2fnvbhH6CuMieJIwG2hNx
11 | +jiS8c7v83GbkHK8OlAyuzLDxqE1mpnhtUZM2JoDx/x6a7o7uXB0fQfe1QJBAKxi
12 | d/aYhJS4IjaymqfUm9m5TntgdP9FpcIOdugfdnmhhLochLK7lp7j4QhJZ07B3Hae
13 | Vp0Clc5sb2HIpvaS2+0CQEV5NxPjavmlCQksQvU2OAQvTW3Sm9lahTl4XvdVxfj0
14 | G9sZ2erg7MIo2LF4V6FM6Hbfoj/FhAMOXlXUoUGs1uI=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/test/riak_control_schema_test.erl:
--------------------------------------------------------------------------------
1 | -module(riak_control_schema_test).
2 |
3 | -include_lib("eunit/include/eunit.hrl").
4 | -compile(export_all).
5 |
6 | basic_schema_test() ->
7 | lager:start(),
8 | Config = cuttlefish_unit:generate_config("../priv/riak_control.schema", []),
9 |
10 | cuttlefish_unit:assert_config(Config, "riak_control.enabled", false),
11 | cuttlefish_unit:assert_config(Config, "riak_control.auth", none),
12 | cuttlefish_unit:assert_not_configured(Config, "riak_control.userlist"),
13 | ok.
14 |
15 | override_test() ->
16 | Conf = [
17 | {["riak_control"], on},
18 | {["riak_control", "auth", "mode"], userlist}
19 | ],
20 | Config = cuttlefish_unit:generate_config("../priv/riak_control.schema", Conf),
21 | cuttlefish_unit:assert_config(Config, "riak_control.enabled", true),
22 | cuttlefish_unit:assert_config(Config, "riak_control.auth", userlist),
23 | cuttlefish_unit:assert_not_configured(Config, "riak_control.userlist"),
24 | ok.
25 |
26 | userlist_schema_test() ->
27 | CuttlefishConf = [
28 | {["riak_control", "auth", "mode"], userlist},
29 | {["riak_control", "auth", "user", "dev", "password"], "1234"},
30 | {["riak_control", "auth", "user", "admin", "password"], "5678"}
31 | ],
32 |
33 | Config = cuttlefish_unit:generate_config("../priv/riak_control.schema", CuttlefishConf),
34 |
35 | cuttlefish_unit:assert_config(Config, "riak_control.userlist.dev", "1234"),
36 | cuttlefish_unit:assert_config(Config, "riak_control.userlist.admin", "5678"),
37 |
38 | %% Other than userlist, which we overrode in CuttlefishConf, the others should still be set to defaults:
39 | cuttlefish_unit:assert_config(Config, "riak_control.enabled", false),
40 | cuttlefish_unit:assert_config(Config, "riak_control.auth", userlist),
41 | ok.
42 |
--------------------------------------------------------------------------------
/tools.mk:
--------------------------------------------------------------------------------
1 | REBAR ?= ./rebar
2 |
3 | test: compile
4 | ${REBAR} eunit skip_deps=true
5 |
6 | docs:
7 | ${REBAR} doc skip_deps=true
8 |
9 | xref: compile
10 | ${REBAR} xref skip_deps=true
11 |
12 | PLT ?= $(HOME)/.combo_dialyzer_plt
13 | LOCAL_PLT = .local_dialyzer_plt
14 | DIALYZER_FLAGS ?= -Wunmatched_returns
15 |
16 | ${PLT}: compile
17 | @if [ -f $(PLT) ]; then \
18 | dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \
19 | dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \
20 | else \
21 | dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \
22 | fi
23 |
24 | ${LOCAL_PLT}: compile
25 | @if [ -d deps ]; then \
26 | if [ -f $(LOCAL_PLT) ]; then \
27 | dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \
28 | dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \
29 | else \
30 | dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \
31 | fi \
32 | fi
33 |
34 | dialyzer: ${PLT} ${LOCAL_PLT}
35 | @echo "==> $(shell basename $(shell pwd)) (dialyzer)"
36 | @if [ -f $(LOCAL_PLT) ]; then \
37 | PLTS="$(PLT) $(LOCAL_PLT)"; \
38 | else \
39 | PLTS=$(PLT); \
40 | fi; \
41 | if [ -f dialyzer.ignore-warnings ]; then \
42 | if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \
43 | echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \
44 | exit 1; \
45 | fi; \
46 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \
47 | egrep -v "^[[:space:]]*(done|Checking|Proceeding|Compiling)" dialyzer_warnings | grep -F -f dialyzer.ignore-warnings -v > dialyzer_unhandled_warnings ; \
48 | cat dialyzer_unhandled_warnings ; \
49 | [ $$(cat dialyzer_unhandled_warnings | wc -l) -eq 0 ] ; \
50 | else \
51 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \
52 | fi
53 |
54 | cleanplt:
55 | @echo
56 | @echo "Are you sure? It takes several minutes to re-build."
57 | @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds.
58 | @echo
59 | sleep 5
60 | rm $(PLT)
61 | rm $(LOCAL_PLT)
62 |
63 |
--------------------------------------------------------------------------------