Merged bug23358 into default
This commit is contained in:
commit
4d48777127
|
|
@ -4,7 +4,7 @@ DEPS=rabbitmq-mochiweb rabbitmq-server rabbitmq-erlang-client rabbitmq-managemen
|
|||
INTERNAL_DEPS=webmachine
|
||||
RUNTIME_DEPS=webmachine
|
||||
|
||||
TEST_APPS=crypto inets mochiweb rabbit_mochiweb webmachine rabbit_management_agent rabbit_management amqp_client
|
||||
TEST_APPS=crypto inets mochiweb rabbit_mochiweb webmachine rabbit_management_agent amqp_client rabbit_management
|
||||
TEST_ARGS=-rabbit_mochiweb port 55672
|
||||
START_RABBIT_IN_TESTS=true
|
||||
TEST_COMMANDS=rabbit_mgmt_test_all:all_tests()
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@
|
|||
{mod, {rabbit_mgmt_app, []}},
|
||||
{env, [{http_log_dir, none}]},
|
||||
{applications, [kernel, stdlib, rabbit, rabbit_mochiweb, webmachine,
|
||||
rabbit_management_agent]}]}.
|
||||
amqp_client, rabbit_management_agent]}]}.
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ table.form { margin-bottom: 0.5em; }
|
|||
table.form th { text-align: right; vertical-align: top; }
|
||||
table.form input[type=text] { width: 200px; }
|
||||
table.form select { width: 200px; }
|
||||
table.form select.narrow { width: 80px; }
|
||||
table.form select.narrow { width: 110px; }
|
||||
table.form .multifield { margin: 0; padding: 0; }
|
||||
table.form .multifield p { margin: 0; padding: 0; }
|
||||
|
||||
|
|
@ -110,6 +110,8 @@ table.bindings { margin-bottom: 2em; }
|
|||
td.binding-endpoint span.object { border: 2px solid #bbb; padding: 10px; border-radius: 10px; -moz-border-radius: 10px; }
|
||||
td.binding-endpoint span.arrow { font-size: 200%; }
|
||||
|
||||
#no-password { display: none; }
|
||||
|
||||
#scratch { display: none; }
|
||||
|
||||
tr.alt1 td {
|
||||
|
|
|
|||
|
|
@ -352,6 +352,15 @@ function postprocess() {
|
|||
$('.multifield input').live('blur', function() {
|
||||
update_multifields();
|
||||
});
|
||||
$('#has-password').change(function() {
|
||||
if ($(this).val() == 'true') {
|
||||
$('#password').slideDown(100);
|
||||
$('#no-password').slideUp(100);
|
||||
} else {
|
||||
$('#password').slideUp(100);
|
||||
$('#no-password').slideDown(100);
|
||||
}
|
||||
});
|
||||
if (! user_administrator) {
|
||||
$('.administrator-only').remove();
|
||||
}
|
||||
|
|
@ -497,7 +506,7 @@ function sync_post(sammy, path_template) {
|
|||
}
|
||||
|
||||
function sync_req(type, params0, path_template) {
|
||||
var params = collapse_multifields(params0);
|
||||
var params = params_magic(params0);
|
||||
var path;
|
||||
try {
|
||||
path = fill_path_template(path_template, params);
|
||||
|
|
@ -556,6 +565,11 @@ function fill_path_template(template, params) {
|
|||
// Better suggestions appreciated
|
||||
var INTEGER_ARGUMENTS = map(['x-expires']);
|
||||
|
||||
function params_magic(params) {
|
||||
return maybe_remove_password(
|
||||
collapse_multifields(params));
|
||||
}
|
||||
|
||||
function collapse_multifields(params0) {
|
||||
var params = {};
|
||||
for (key in params0) {
|
||||
|
|
@ -586,6 +600,14 @@ function collapse_multifields(params0) {
|
|||
return params;
|
||||
}
|
||||
|
||||
function maybe_remove_password(params) {
|
||||
if (params['has-password'] == 'false') {
|
||||
delete params['password'];
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function debug(str) {
|
||||
$('<p>' + str + '</p>').appendTo('#debug');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Consumers</h2>
|
||||
<div class="hider updatable">
|
||||
<%= format('consumers', {'mode': 'channel', 'consumers': channel.consumer_details}) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (statistics_level == 'fine') { %>
|
||||
<div class="section">
|
||||
<h2>Message Rates</h2>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@
|
|||
<th>SSL</th>
|
||||
<td><%= fmt_boolean(connection.ssl) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Authentication</th>
|
||||
<td><%= connection.auth_mechanism %></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="facts">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<% if (consumers.length > 0) { %>
|
||||
<table class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if (mode == 'queue') { %>
|
||||
<th>Channel</th>
|
||||
<th>Consumer Tag</th>
|
||||
<% } else { %>
|
||||
<th>Consumer Tag</th>
|
||||
<th>Queue</th>
|
||||
<% } %>
|
||||
<th>Ack Required</th>
|
||||
<th>Exclusive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<%
|
||||
for (var i = 0; i < consumers.length; i++) {
|
||||
var consumer = consumers[i];
|
||||
%>
|
||||
<tr<%= alt_rows(i) %>>
|
||||
<% if (mode == 'queue') { %>
|
||||
<td><%= link_channel(consumer.channel_details.name) %></td>
|
||||
<td><%= consumer.consumer_tag %></td>
|
||||
<% } else { %>
|
||||
<td><%= consumer.consumer_tag %></td>
|
||||
<td><%= link_queue(consumer.queue_details.vhost, consumer.queue_details.name) %></td>
|
||||
<% } %>
|
||||
<td class="c"><%= fmt_boolean(consumer.ack_required) %></td>
|
||||
<td class="c"><%= fmt_boolean(consumer.exclusive) %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>... no consumers ...</p>
|
||||
<% } %>
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div class="section">
|
||||
<div class="section-hidden">
|
||||
<h2>Applications</h2>
|
||||
<div class="hider updatable">
|
||||
<% if (node.running) { %>
|
||||
|
|
@ -115,3 +115,13 @@
|
|||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-hidden">
|
||||
<h2>Registry</h2>
|
||||
<div class="hider updatable">
|
||||
<h3>Exchange Types</h3>
|
||||
<%= format('registry', {'list': node.exchange_types, 'node': node, 'show_enabled': false} ) %>
|
||||
<h3>Authentication Mechanisms</h3>
|
||||
<%= format('registry', {'list': node.auth_mechanisms, 'node': node, 'show_enabled': true} ) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-hidden">
|
||||
<h2>Consumers</h2>
|
||||
<div class="hider updatable">
|
||||
<%= format('consumers', {'mode': 'queue', 'consumers': queue.consumer_details}) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (statistics_level == 'fine') { %>
|
||||
<div class="section-hidden">
|
||||
<h2>Message Rates</h2>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<% if (node.running) { %>
|
||||
<table class="list">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<% if (show_enabled) { %>
|
||||
<th>Enabled</th>
|
||||
<% } %>
|
||||
</tr>
|
||||
<%
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var item = list[i];
|
||||
%>
|
||||
<tr<%= alt_rows(i)%>>
|
||||
<td><%= item.name %></td>
|
||||
<td><%= item.description %></td>
|
||||
<% if (show_enabled) { %>
|
||||
<td class="c"><%= fmt_boolean(item.enabled) %></td>
|
||||
<% } %>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
<% } else {%>
|
||||
<p>...node not running...</p>
|
||||
<% } %>
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
<h1>User: <b><%= user.name %></b></h1>
|
||||
|
||||
<p>Administrator: <%= fmt_boolean(user.administrator) %></p>
|
||||
<div class="section">
|
||||
<h2>Overview</h2>
|
||||
<div class="hider">
|
||||
<table class="facts">
|
||||
<tr>
|
||||
<th>Administrator</th>
|
||||
<td><%= fmt_boolean(user.administrator) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Can log in with password</th>
|
||||
<td><%= fmt_boolean(user.password_hash.length > 0) %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<span class="br"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= format('permissions', {'mode': 'user', 'permissions': permissions, 'vhosts': vhosts, 'parent': user}) %>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,22 @@
|
|||
<td><input type="text" name="username"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label>Password:</label></th>
|
||||
<td><input type="text" name="password"/></td>
|
||||
<th>
|
||||
<label>
|
||||
<select name="has-password" id="has-password" class="narrow">
|
||||
<option value="true">Password:</option>
|
||||
<option value="false">No password</option>
|
||||
</select>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<div id="password">
|
||||
<input type="text" name="password" />
|
||||
</div>
|
||||
<div id="no-password">
|
||||
User cannot log in using password.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label>Administrator:</label></th>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
-record(state, {tables}).
|
||||
-define(FINE_STATS_TYPES, [channel_queue_stats, channel_exchange_stats,
|
||||
channel_queue_exchange_stats]).
|
||||
-define(TABLES, [queue_stats, connection_stats, channel_stats] ++
|
||||
-define(TABLES, [queue_stats, connection_stats, channel_stats, consumers] ++
|
||||
?FINE_STATS_TYPES).
|
||||
|
||||
-define(DELIVER_GET, [deliver, deliver_no_ack, get, get_no_ack]).
|
||||
|
|
@ -91,56 +91,32 @@ start_link() ->
|
|||
Else
|
||||
end.
|
||||
|
||||
get_queues(Qs) ->
|
||||
safe_call({get_queues, Qs, list}, Qs).
|
||||
get_queues(Qs) -> safe_call({get_queues, Qs, list}, Qs).
|
||||
get_queue(Q) -> safe_call({get_queues, [Q], detail}, [Q]).
|
||||
get_exchanges(Xs) -> safe_call({get_exchanges, Xs, list}, Xs).
|
||||
get_exchange(X) -> safe_call({get_exchanges, [X], detail}, [X]).
|
||||
get_connections() -> safe_call(get_connections).
|
||||
get_connection(Name) -> safe_call({get_connection, Name}).
|
||||
get_channels() -> safe_call(get_channels).
|
||||
get_channel(Name) -> safe_call({get_channel, Name}).
|
||||
get_overview(Username) -> safe_call({get_overview, Username}).
|
||||
get_overview() -> safe_call({get_overview, all}).
|
||||
|
||||
get_queue(Q) ->
|
||||
safe_call({get_queues, [Q], detail}, [Q]).
|
||||
|
||||
get_exchanges(Xs) ->
|
||||
safe_call({get_exchanges, Xs, list}, Xs).
|
||||
|
||||
get_exchange(X) ->
|
||||
safe_call({get_exchanges, [X], detail}, [X]).
|
||||
|
||||
get_connections() ->
|
||||
safe_call(get_connections).
|
||||
|
||||
get_connection(Name) ->
|
||||
safe_call({get_connection, Name}).
|
||||
|
||||
get_channels() ->
|
||||
safe_call(get_channels).
|
||||
|
||||
get_channel(Name) ->
|
||||
safe_call({get_channel, Name}).
|
||||
|
||||
get_overview(Username) ->
|
||||
safe_call({get_overview, Username}).
|
||||
|
||||
get_overview() ->
|
||||
safe_call({get_overview, all}).
|
||||
|
||||
safe_call(Term) ->
|
||||
safe_call(Term, []).
|
||||
safe_call(Term) -> safe_call(Term, []).
|
||||
|
||||
safe_call(Term, Item) ->
|
||||
try
|
||||
gen_server:call({global, ?MODULE}, Term, infinity)
|
||||
catch exit:{noproc, _} ->
|
||||
Item
|
||||
catch exit:{noproc, _} -> Item
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
pget(Key, List) ->
|
||||
pget(Key, List, unknown).
|
||||
pget(Key, List) -> pget(Key, List, unknown).
|
||||
|
||||
pget(Key, List, Default) ->
|
||||
proplists:get_value(Key, List, Default).
|
||||
pget(Key, List, Default) -> proplists:get_value(Key, List, Default).
|
||||
|
||||
pset(Key, Value, List) ->
|
||||
[{Key, Value} | proplists:delete(Key, List)].
|
||||
pset(Key, Value, List) -> [{Key, Value} | proplists:delete(Key, List)].
|
||||
|
||||
id(Pid) when is_pid(Pid) -> rabbit_mgmt_format:pid(Pid);
|
||||
id(List) -> rabbit_mgmt_format:pid(pget(pid, List)).
|
||||
|
|
@ -149,8 +125,7 @@ add(unknown, _) -> unknown;
|
|||
add(_, unknown) -> unknown;
|
||||
add(A, B) -> A + B.
|
||||
|
||||
lookup_element(Table, Key) ->
|
||||
lookup_element(Table, Key, 2).
|
||||
lookup_element(Table, Key) -> lookup_element(Table, Key, 2).
|
||||
|
||||
lookup_element(Table, Key, Pos) ->
|
||||
try ets:lookup_element(Table, Key, Pos)
|
||||
|
|
@ -223,101 +198,45 @@ if_unknown(Val, _Def) -> Val.
|
|||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
augment(Items, Funs, Tables) ->
|
||||
Augmented = [augment(K, Items, Fun, Tables) || {K, Fun} <- Funs] ++ Items,
|
||||
[{K, V} || {K, V} <- Augmented, V =/= unknown].
|
||||
|
||||
augment(K, Items, Fun, Tables) ->
|
||||
Key = details_key(K),
|
||||
case pget(K, Items) of
|
||||
none -> {Key, unknown};
|
||||
unknown -> {Key, unknown};
|
||||
Id -> {Key, Fun(Id, Tables)}
|
||||
end.
|
||||
|
||||
augment_channel_pid(Pid, Tables) ->
|
||||
Ch = lookup_element(
|
||||
orddict:fetch(channel_stats, Tables),
|
||||
{Pid, create}),
|
||||
Conn = lookup_element(
|
||||
orddict:fetch(connection_stats, Tables),
|
||||
{pget(connection, Ch), create}),
|
||||
[{number, pget(number, Ch)},
|
||||
{name, pget(name, Ch)},
|
||||
{connection_name, pget(name, Conn)},
|
||||
{peer_address, pget(peer_address, Conn)},
|
||||
{peer_port, pget(peer_port, Conn)}].
|
||||
|
||||
augment_connection_pid(Pid, Tables) ->
|
||||
Conn = lookup_element(orddict:fetch(connection_stats, Tables),
|
||||
{Pid, create}),
|
||||
[{peer_address, pget(peer_address, Conn)},
|
||||
{peer_port, pget(peer_port, Conn)},
|
||||
{name, pget(name, Conn)}].
|
||||
|
||||
augment_queue_pid(Pid, _Tables) ->
|
||||
%% TODO This should be in rabbit_amqqueue?
|
||||
[Q] = mnesia:dirty_match_object(
|
||||
rabbit_queue,
|
||||
#amqqueue{pid = rabbit_misc:string_to_pid(Pid), _ = '_'}),
|
||||
Name = Q#amqqueue.name,
|
||||
[{name, Name#resource.name},
|
||||
{vhost, Name#resource.virtual_host}].
|
||||
|
||||
augment_msg_stats(Stats, Tables) ->
|
||||
[augment_msg_stats_items(Props, Tables) || Props <- Stats].
|
||||
|
||||
augment_msg_stats_items(Props, Tables) ->
|
||||
augment(Props, [{connection, fun augment_connection_pid/2},
|
||||
{channel, fun augment_channel_pid/2},
|
||||
{queue, fun augment_queue_pid/2}], Tables).
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
init([]) ->
|
||||
{ok, #state{tables = orddict:from_list(
|
||||
[{Key, ets:new(anon, [private])} ||
|
||||
Key <- ?TABLES])}}.
|
||||
|
||||
handle_call({get_queues, Qs0, Mode}, _From, State = #state{tables = Tables}) ->
|
||||
FineStats = case Mode of
|
||||
FineSpecs = case Mode of
|
||||
list -> ?FINE_STATS_QUEUE_LIST;
|
||||
detail -> ?FINE_STATS_QUEUE_DETAIL
|
||||
end,
|
||||
Qs1 = merge_stats(Qs0, FineStats, queue_stats, Tables),
|
||||
Qs1 = queue_stats(Qs0, FineSpecs, Tables),
|
||||
Qs2 = [[{messages, add(pget(messages_ready, Q),
|
||||
pget(messages_unacknowledged, Q))} | Q] || Q <- Qs1],
|
||||
Qs3 = [augment(Q, [{owner_pid, fun augment_connection_pid/2}], Tables) ||
|
||||
Q <- Qs2],
|
||||
{reply, Qs3, State};
|
||||
{reply, Qs2, State};
|
||||
|
||||
handle_call({get_exchanges, Xs, Mode}, _From,
|
||||
State = #state{tables = Tables}) ->
|
||||
FineStats = case Mode of
|
||||
FineSpecs = case Mode of
|
||||
list -> ?FINE_STATS_EXCHANGE_LIST;
|
||||
detail -> ?FINE_STATS_EXCHANGE_DETAIL
|
||||
end,
|
||||
{reply, merge_stats(Xs, FineStats, none, Tables),
|
||||
State};
|
||||
{reply, exchange_stats(Xs, FineSpecs, Tables), State};
|
||||
|
||||
handle_call(get_connections, _From, State = #state{tables = Tables}) ->
|
||||
Conns = created_events(connection_stats, Tables),
|
||||
{reply, merge_stats(Conns, ?FINE_STATS_NONE, connection_stats, Tables),
|
||||
State};
|
||||
{reply, connection_stats(Conns, Tables), State};
|
||||
|
||||
handle_call({get_connection, Name}, _From, State = #state{tables = Tables}) ->
|
||||
Conns = created_event(Name, connection_stats, Tables),
|
||||
[Res] = merge_stats(Conns, ?FINE_STATS_NONE, connection_stats, Tables),
|
||||
[Res] = connection_stats(Conns, Tables),
|
||||
{reply, result_or_error(Res), State};
|
||||
|
||||
handle_call(get_channels, _From, State = #state{tables = Tables}) ->
|
||||
Chs = created_events(channel_stats, Tables),
|
||||
Res = merge_stats(Chs, ?FINE_STATS_CHANNEL_LIST, channel_stats, Tables),
|
||||
{reply, Res, State};
|
||||
{reply, channel_stats(Chs, ?FINE_STATS_CHANNEL_LIST, Tables), State};
|
||||
|
||||
handle_call({get_channel, Name}, _From, State = #state{tables = Tables}) ->
|
||||
Chs = created_event(Name, channel_stats, Tables),
|
||||
[Res] = merge_stats(Chs, ?FINE_STATS_CHANNEL_DETAIL, channel_stats, Tables),
|
||||
[Res] = channel_stats(Chs, ?FINE_STATS_CHANNEL_DETAIL, Tables),
|
||||
{reply, result_or_error(Res), State};
|
||||
|
||||
handle_call({get_overview, Username}, _From, State = #state{tables = Tables}) ->
|
||||
|
|
@ -325,10 +244,9 @@ handle_call({get_overview, Username}, _From, State = #state{tables = Tables}) ->
|
|||
all -> rabbit_access_control:list_vhosts();
|
||||
_ -> rabbit_mgmt_util:vhosts(Username)
|
||||
end,
|
||||
Qs0 = lists:append(
|
||||
[[rabbit_mgmt_format:queue(Q) || Q <- rabbit_amqqueue:list(V)]
|
||||
|| V <- VHosts]),
|
||||
Qs1 = merge_stats(Qs0, ?FINE_STATS_NONE, queue_stats, Tables),
|
||||
Qs0 = [rabbit_mgmt_format:queue(Q) || V <- VHosts,
|
||||
Q <- rabbit_amqqueue:list(V)],
|
||||
Qs1 = basic_queue_stats(Qs0, Tables),
|
||||
Totals0 = sum(Qs1, [messages_ready, messages_unacknowledged]),
|
||||
Totals = [{messages, add(pget(messages_ready, Totals0),
|
||||
pget(messages_unacknowledged, Totals0))}|Totals0],
|
||||
|
|
@ -339,13 +257,13 @@ handle_call({get_overview, Username}, _From, State = #state{tables = Tables}) ->
|
|||
get_fine_stats(
|
||||
[], [R || R = {Id, _, _}
|
||||
<- ets:tab2list(orddict:fetch(Type, Tables)),
|
||||
Filter(augment_msg_stats_items(
|
||||
format_id(Id), Tables), Name)])
|
||||
Filter(augment_msg_stats(format_id(Id), Tables),
|
||||
Name)])
|
||||
end,
|
||||
Publish = F(channel_exchange_stats, exchange),
|
||||
Consume = F(channel_queue_stats, queue_details),
|
||||
{reply, [{message_stats, Publish ++ Consume}, {queue_totals, Totals}],
|
||||
State};
|
||||
{reply, [{message_stats, Publish ++ Consume},
|
||||
{queue_totals, Totals}], State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, not_understood, State}.
|
||||
|
|
@ -428,6 +346,14 @@ handle_event(Event = #event{type = channel_closed,
|
|||
Type <- ?FINE_STATS_TYPES],
|
||||
{ok, State};
|
||||
|
||||
handle_event(#event{type = consumer_created, props = Props}, State) ->
|
||||
handle_consumer(fun(Table, Id, P) -> ets:insert(Table, {Id, P}) end,
|
||||
Props, State);
|
||||
|
||||
handle_event(#event{type = consumer_deleted, props = Props}, State) ->
|
||||
handle_consumer(fun(Table, Id, _P) -> ets:delete(Table, Id) end,
|
||||
Props, State);
|
||||
|
||||
handle_event(_Event, State) ->
|
||||
{ok, State}.
|
||||
|
||||
|
|
@ -461,6 +387,14 @@ handle_deleted(TName, #event{props = [{pid, Pid}]},
|
|||
ets:delete(Table, {id(Pid), stats}),
|
||||
{ok, State}.
|
||||
|
||||
handle_consumer(Fun, Props,
|
||||
State = #state{tables = Tables}) ->
|
||||
P = rabbit_mgmt_format:format(
|
||||
Props, [{fun rabbit_mgmt_format:pid/1, [queue, channel]}]),
|
||||
Table = orddict:fetch(consumers, Tables),
|
||||
Fun(Table, {pget(queue, P), pget(channel, P)}, P),
|
||||
{ok, State}.
|
||||
|
||||
handle_fine_stats(Type, Props, Timestamp, State = #state{tables = Tables}) ->
|
||||
case pget(Type, Props) of
|
||||
unknown ->
|
||||
|
|
@ -530,31 +464,30 @@ format_id({ChPid, QPid, #resource{name=XName, virtual_host=XVhost}}) ->
|
|||
[{channel, ChPid}, {queue, QPid},
|
||||
{exchange, [{name, XName}, {vhost, XVhost}]}].
|
||||
|
||||
merge_stats(Objs, FineSpecs, Type, Tables) ->
|
||||
WithCoarse =
|
||||
case Type of
|
||||
none ->
|
||||
Objs;
|
||||
_ ->
|
||||
Table = orddict:fetch(Type, Tables),
|
||||
[Obj ++ zero_old_rates(
|
||||
lookup_element(Table, {pget(pid, Obj), stats}))
|
||||
|| Obj <- Objs]
|
||||
end,
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
merge_stats(Objs, Funs) ->
|
||||
[lists:foldl(fun (Fun, Props) -> Fun(Props) ++ Props end, Obj, Funs)
|
||||
|| Obj <- Objs].
|
||||
|
||||
basic_stats_fun(Type, Tables) ->
|
||||
Table = orddict:fetch(Type, Tables),
|
||||
fun (Props) ->
|
||||
zero_old_rates(lookup_element(Table, {pget(pid, Props), stats}))
|
||||
end.
|
||||
|
||||
fine_stats_fun(FineSpecs, Tables) ->
|
||||
FineStats = [{AttachName, AttachBy,
|
||||
get_fine_stats(FineStatsType, GroupBy, Tables)}
|
||||
|| {FineStatsType, GroupBy, AttachName, AttachBy}
|
||||
<- FineSpecs],
|
||||
augment_msg_stats(merge_fine_stats(WithCoarse, FineStats, Tables), Tables).
|
||||
fun (Props) ->
|
||||
lists:foldl(fun (FineStat, StatProps) ->
|
||||
fine_stat(Props, StatProps, FineStat, Tables)
|
||||
end, [], FineStats)
|
||||
end.
|
||||
|
||||
merge_fine_stats(Stats, [], _Tables) ->
|
||||
Stats;
|
||||
merge_fine_stats(Stats, [{AttachName, AttachBy, Dict} | Rest], Tables) ->
|
||||
merge_fine_stats([merge_fine_stats0(AttachName, AttachBy,
|
||||
Props, Dict, Tables)
|
||||
|| Props <- Stats], Rest, Tables).
|
||||
|
||||
merge_fine_stats0(AttachName, AttachBy, Props, Dict, Tables) ->
|
||||
fine_stat(Props, StatProps, {AttachName, AttachBy, Dict}, Tables) ->
|
||||
Id = case AttachBy of
|
||||
exchange ->
|
||||
[{name, pget(name, Props)}, {vhost, pget(vhost, Props)}];
|
||||
|
|
@ -562,19 +495,29 @@ merge_fine_stats0(AttachName, AttachBy, Props, Dict, Tables) ->
|
|||
pget(pid, Props)
|
||||
end,
|
||||
case dict:find({AttachBy, Id}, Dict) of
|
||||
{ok, Stats} -> [{AttachName, pget(AttachName, Props, []) ++
|
||||
{ok, Stats} -> [{AttachName, pget(AttachName, StatProps, []) ++
|
||||
augment_fine_stats(Stats, Tables)} |
|
||||
proplists:delete(AttachName, Props)];
|
||||
error -> Props
|
||||
proplists:delete(AttachName, StatProps)];
|
||||
error -> StatProps
|
||||
end.
|
||||
|
||||
augment_fine_stats(Dict, Tables) when element(1, Dict) == dict ->
|
||||
[[{stats, augment_fine_stats(Stats, Tables)} |
|
||||
augment_msg_stats_items([IdTuple], Tables)]
|
||||
augment_msg_stats([IdTuple], Tables)]
|
||||
|| {IdTuple, Stats} <- dict:to_list(Dict)];
|
||||
augment_fine_stats(Stats, _Tables) ->
|
||||
Stats.
|
||||
|
||||
consumer_details_fun(PatternFun, Tables) ->
|
||||
Table = orddict:fetch(consumers, Tables),
|
||||
fun ([]) -> [];
|
||||
(Props) -> Pattern = PatternFun(Props),
|
||||
[{consumer_details,
|
||||
[augment_msg_stats(Obj, Tables)
|
||||
|| Obj <- lists:append(
|
||||
ets:match(Table, {Pattern, '$1'}))]}]
|
||||
end.
|
||||
|
||||
zero_old_rates(Stats) -> [maybe_zero_rate(S) || S <- Stats].
|
||||
|
||||
maybe_zero_rate({Key, Val}) ->
|
||||
|
|
@ -587,8 +530,87 @@ maybe_zero_rate({Key, Val}) ->
|
|||
false -> {Key, Val}
|
||||
end.
|
||||
|
||||
is_details(Key) ->
|
||||
lists:suffix("_details", atom_to_list(Key)).
|
||||
is_details(Key) -> lists:suffix("_details", atom_to_list(Key)).
|
||||
|
||||
details_key(Key) ->
|
||||
list_to_atom(atom_to_list(Key) ++ "_details").
|
||||
details_key(Key) -> list_to_atom(atom_to_list(Key) ++ "_details").
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
augment_msg_stats(Props, Tables) ->
|
||||
(augment_msg_stats_fun(Tables))(Props) ++ Props.
|
||||
|
||||
augment_msg_stats_fun(Tables) ->
|
||||
Funs = [{connection, fun augment_connection_pid/2},
|
||||
{channel, fun augment_channel_pid/2},
|
||||
{queue, fun augment_queue_pid/2},
|
||||
{owner_pid, fun augment_connection_pid/2}],
|
||||
fun (Props) -> augment(Props, Funs, Tables) end.
|
||||
|
||||
augment(Items, Funs, Tables) ->
|
||||
Augmented = [augment(K, Items, Fun, Tables) || {K, Fun} <- Funs],
|
||||
[{K, V} || {K, V} <- Augmented, V =/= unknown].
|
||||
|
||||
augment(K, Items, Fun, Tables) ->
|
||||
Key = details_key(K),
|
||||
case pget(K, Items) of
|
||||
none -> {Key, unknown};
|
||||
unknown -> {Key, unknown};
|
||||
Id -> {Key, Fun(Id, Tables)}
|
||||
end.
|
||||
|
||||
augment_channel_pid(Pid, Tables) ->
|
||||
Ch = lookup_element(orddict:fetch(channel_stats, Tables),
|
||||
{Pid, create}),
|
||||
Conn = lookup_element(orddict:fetch(connection_stats, Tables),
|
||||
{pget(connection, Ch), create}),
|
||||
[{name, pget(name, Ch)},
|
||||
{number, pget(number, Ch)},
|
||||
{connection_name, pget(name, Conn)},
|
||||
{peer_address, pget(peer_address, Conn)},
|
||||
{peer_port, pget(peer_port, Conn)}].
|
||||
|
||||
augment_connection_pid(Pid, Tables) ->
|
||||
Conn = lookup_element(orddict:fetch(connection_stats, Tables),
|
||||
{Pid, create}),
|
||||
[{name, pget(name, Conn)},
|
||||
{peer_address, pget(peer_address, Conn)},
|
||||
{peer_port, pget(peer_port, Conn)}].
|
||||
|
||||
augment_queue_pid(Pid, _Tables) ->
|
||||
%% TODO This should be in rabbit_amqqueue?
|
||||
case mnesia:dirty_match_object(
|
||||
rabbit_queue,
|
||||
#amqqueue{pid = rabbit_misc:string_to_pid(Pid), _ = '_'}) of
|
||||
[Q] -> Name = Q#amqqueue.name,
|
||||
[{name, Name#resource.name},
|
||||
{vhost, Name#resource.virtual_host}];
|
||||
[] -> [] %% Queue went away before we could get its details.
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------------
|
||||
|
||||
basic_queue_stats(Objs, Tables) ->
|
||||
merge_stats(Objs, [basic_stats_fun(queue_stats, Tables),
|
||||
augment_msg_stats_fun(Tables)]).
|
||||
|
||||
queue_stats(Objs, FineSpecs, Tables) ->
|
||||
merge_stats(Objs, [basic_stats_fun(queue_stats, Tables),
|
||||
consumer_details_fun(
|
||||
fun (Props) -> {pget(pid, Props), '_'} end, Tables),
|
||||
fine_stats_fun(FineSpecs, Tables),
|
||||
augment_msg_stats_fun(Tables)]).
|
||||
|
||||
exchange_stats(Objs, FineSpecs, Tables) ->
|
||||
merge_stats(Objs, [fine_stats_fun(FineSpecs, Tables),
|
||||
augment_msg_stats_fun(Tables)]).
|
||||
|
||||
connection_stats(Objs, Tables) ->
|
||||
merge_stats(Objs, [basic_stats_fun(connection_stats, Tables),
|
||||
augment_msg_stats_fun(Tables)]).
|
||||
|
||||
channel_stats(Objs, FineSpecs, Tables) ->
|
||||
merge_stats(Objs, [basic_stats_fun(channel_stats, Tables),
|
||||
consumer_details_fun(
|
||||
fun (Props) -> {'_', pget(pid, Props)} end, Tables),
|
||||
fine_stats_fun(FineSpecs, Tables),
|
||||
augment_msg_stats_fun(Tables)]).
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
-export([is_authorized/2, is_authorized_admin/2, vhost/1]).
|
||||
-export([is_authorized_vhost/2, is_authorized/3, is_authorized_user/3]).
|
||||
-export([bad_request/3, id/2, parse_bool/1]).
|
||||
-export([with_decode/4, not_found/3, amqp_request/4]).
|
||||
-export([with_decode/4, with_decode_opts/4, not_found/3, amqp_request/4]).
|
||||
-export([all_or_one_vhost/2, with_decode_vhost/4, reply/3, filter_vhost/3]).
|
||||
-export([filter_user/3, with_decode/5, redirect/2, args/1, vhosts/1]).
|
||||
-export([reply_list/3, reply_list/4, sort_list/4, destination_type/1]).
|
||||
|
|
@ -72,7 +72,7 @@ is_authorized(ReqData, Context, Fun) ->
|
|||
is_admin = IsAdmin}};
|
||||
false -> Unauthorized
|
||||
end;
|
||||
refused ->
|
||||
{refused, _, _} ->
|
||||
Unauthorized
|
||||
end;
|
||||
_ ->
|
||||
|
|
@ -176,6 +176,21 @@ id0(Key, ReqData) ->
|
|||
error -> none
|
||||
end.
|
||||
|
||||
%% TODO unify this with the function below after bug23384 is merged
|
||||
with_decode_opts(Keys, ReqData, Context, Fun) ->
|
||||
Body = wrq:req_body(ReqData),
|
||||
case decode(Keys, Body) of
|
||||
{error, Reason} -> bad_request(Reason, ReqData, Context);
|
||||
_Values -> try
|
||||
{ok, Obj0} = decode(Body),
|
||||
Obj = [{list_to_atom(binary_to_list(K)), V} ||
|
||||
{K, V} <- Obj0],
|
||||
Fun(Obj)
|
||||
catch {error, Error} ->
|
||||
bad_request(Error, ReqData, Context)
|
||||
end
|
||||
end.
|
||||
|
||||
with_decode(Keys, ReqData, Context, Fun) ->
|
||||
with_decode(Keys, wrq:req_body(ReqData), ReqData, Context, Fun).
|
||||
|
||||
|
|
@ -190,20 +205,22 @@ with_decode(Keys, Body, ReqData, Context, Fun) ->
|
|||
end.
|
||||
|
||||
decode(Keys, Body) ->
|
||||
{Res, Json} = try
|
||||
{struct, J} = mochijson2:decode(Body),
|
||||
{ok, J}
|
||||
catch error:_ -> {error, not_json}
|
||||
end,
|
||||
case Res of
|
||||
ok -> Results =
|
||||
[get_or_missing(list_to_binary(atom_to_list(K)), Json) ||
|
||||
K <- Keys],
|
||||
case [E || E = {key_missing, _} <- Results] of
|
||||
[] -> Results;
|
||||
Errors -> {error, Errors}
|
||||
end;
|
||||
_ -> {Res, Json}
|
||||
case decode(Body) of
|
||||
{ok, J} -> Results =
|
||||
[get_or_missing(list_to_binary(atom_to_list(K)), J) ||
|
||||
K <- Keys],
|
||||
case [E || E = {key_missing, _} <- Results] of
|
||||
[] -> Results;
|
||||
Errors -> {error, Errors}
|
||||
end;
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
decode(Body) ->
|
||||
try
|
||||
{struct, J} = mochijson2:decode(Body),
|
||||
{ok, J}
|
||||
catch error:_ -> {error, not_json}
|
||||
end.
|
||||
|
||||
with_decode_vhost(Keys, ReqData, Context, Fun) ->
|
||||
|
|
|
|||
|
|
@ -179,18 +179,7 @@ clean_value(A) -> A.
|
|||
%%--------------------------------------------------------------------
|
||||
|
||||
add_user(User) ->
|
||||
case proplists:is_defined(password_hash, User) of
|
||||
true ->
|
||||
%% ver > 2.1.1
|
||||
rabbit_mgmt_wm_user:put_user_hashed(pget(name, User),
|
||||
pget(password_hash, User),
|
||||
pget(administrator, User));
|
||||
false ->
|
||||
%% ver == 2.1.1
|
||||
rabbit_mgmt_wm_user:put_user(pget(name, User),
|
||||
pget(password, User),
|
||||
pget(administrator, User))
|
||||
end.
|
||||
rabbit_mgmt_wm_user:put_user(User).
|
||||
|
||||
add_vhost(VHost) ->
|
||||
VHostName = pget(name, VHost),
|
||||
|
|
|
|||
|
|
@ -54,16 +54,6 @@ all_nodes() ->
|
|||
make_entry(Node, Type, Running) ->
|
||||
[{name, Node}, {type, Type}, {running, Running}]
|
||||
++ case Running of
|
||||
true -> rabbit_mgmt_external_stats:info(Node) ++
|
||||
[{applications, applications(Node)}];
|
||||
true -> rabbit_mgmt_external_stats:info(Node);
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
applications(Node) ->
|
||||
case rpc:call(Node, application, which_applications, []) of
|
||||
{badrpc, _Reason} -> [];
|
||||
Applications -> rabbit_mgmt_util:sort_list(
|
||||
[rabbit_mgmt_format:application(A) ||
|
||||
A <- Applications],
|
||||
[], "name", false)
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
-export([init/1, resource_exists/2, to_json/2,
|
||||
content_types_provided/2, content_types_accepted/2,
|
||||
is_authorized/2, allowed_methods/2, accept_content/2,
|
||||
delete_resource/2, put_user/3, put_user_hashed/3]).
|
||||
delete_resource/2, put_user/1]).
|
||||
|
||||
-include("rabbit_mgmt.hrl").
|
||||
-include_lib("webmachine/include/webmachine.hrl").
|
||||
|
|
@ -46,11 +46,11 @@ to_json(ReqData, Context) ->
|
|||
rabbit_mgmt_util:reply(rabbit_mgmt_format:user(User), ReqData, Context).
|
||||
|
||||
accept_content(ReqData, Context) ->
|
||||
User = rabbit_mgmt_util:id(user, ReqData),
|
||||
rabbit_mgmt_util:with_decode(
|
||||
[password, administrator], ReqData, Context,
|
||||
fun([Password, IsAdmin]) ->
|
||||
put_user(User, Password, IsAdmin),
|
||||
Username = rabbit_mgmt_util:id(user, ReqData),
|
||||
rabbit_mgmt_util:with_decode_opts(
|
||||
[administrator], ReqData, Context,
|
||||
fun(User) ->
|
||||
put_user([{name, Username} | User]),
|
||||
{true, ReqData, Context}
|
||||
end).
|
||||
|
||||
|
|
@ -67,31 +67,33 @@ is_authorized(ReqData, Context) ->
|
|||
user(ReqData) ->
|
||||
rabbit_access_control:lookup_user(rabbit_mgmt_util:id(user, ReqData)).
|
||||
|
||||
put_user(User, Password, IsAdmin) ->
|
||||
put_user0(
|
||||
User, IsAdmin,
|
||||
fun (User0) ->
|
||||
rabbit_access_control:change_password(User0, Password)
|
||||
end).
|
||||
put_user(User) ->
|
||||
case {proplists:is_defined(password, User),
|
||||
proplists:is_defined(password_hash, User)} of
|
||||
{true, _} ->
|
||||
Pass = proplists:get_value(password, User),
|
||||
put_user(User, Pass, fun rabbit_access_control:change_password/2);
|
||||
{_, true} ->
|
||||
Hash = base64:decode(proplists:get_value(password_hash, User)),
|
||||
put_user(User, Hash,
|
||||
fun rabbit_access_control:change_password_hash/2);
|
||||
_ ->
|
||||
put_user(User, <<>>,
|
||||
fun rabbit_access_control:change_password_hash/2)
|
||||
end.
|
||||
|
||||
put_user_hashed(User, PasswordHash64, IsAdmin) ->
|
||||
PasswordHash = base64:decode(PasswordHash64),
|
||||
put_user0(
|
||||
User, IsAdmin,
|
||||
fun (User0) ->
|
||||
rabbit_access_control:change_password_hash(User0, PasswordHash)
|
||||
end).
|
||||
|
||||
put_user0(User, IsAdmin, PWFun) ->
|
||||
case rabbit_access_control:lookup_user(User) of
|
||||
put_user(User, PWArg, PWFun) ->
|
||||
Username = proplists:get_value(name, User),
|
||||
IsAdmin = proplists:get_value(administrator, User),
|
||||
case rabbit_access_control:lookup_user(Username) of
|
||||
{error, not_found} ->
|
||||
rabbit_access_control:add_user(
|
||||
User, rabbit_guid:binstring_guid("tmp_"));
|
||||
Username, rabbit_guid:binstring_guid("tmp_"));
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
PWFun(User),
|
||||
PWFun(Username, PWArg),
|
||||
case rabbit_mgmt_util:parse_bool(IsAdmin) of
|
||||
true -> rabbit_access_control:set_admin(User);
|
||||
false -> rabbit_access_control:clear_admin(User)
|
||||
true -> rabbit_access_control:set_admin(Username);
|
||||
false -> rabbit_access_control:clear_admin(Username)
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ test_channel_aggregation(Conn, Chan) ->
|
|||
[{publish, 100}], pget(publishes, Ch))
|
||||
end].
|
||||
|
||||
test_exchange_aggregation(Conn, Chan) ->
|
||||
test_exchange_aggregation(_Conn, Chan) ->
|
||||
X1 = <<"exchange-aggregation">>,
|
||||
declare_exchange(Chan, X1),
|
||||
Q1 = declare_queue(Chan),
|
||||
|
|
@ -229,7 +229,7 @@ test_exchange_aggregation(Conn, Chan) ->
|
|||
[{publish, 100}], pget(outgoing, X))
|
||||
end].
|
||||
|
||||
test_queue_aggregation(Conn, Chan) ->
|
||||
test_queue_aggregation(_Conn, Chan) ->
|
||||
X1 = <<"queue-aggregation-1">>,
|
||||
X2 = <<"queue-aggregation-2">>,
|
||||
declare_exchange(Chan, X1),
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ users_test() ->
|
|||
http_get("/users/myuser", ?NOT_FOUND),
|
||||
http_put_raw("/users/myuser", "Something not JSON", ?BAD_REQUEST),
|
||||
http_put("/users/myuser", [{flim, <<"flam">>}], ?BAD_REQUEST),
|
||||
http_put("/users/myuser", [{password, <<"myuser">>},
|
||||
http_put("/users/myuser", [{administrator, false}], ?NO_CONTENT),
|
||||
http_put("/users/myuser", [{password_hash,
|
||||
<<"IECV6PZI/Invh0DL187KFpkO5Jc=">>},
|
||||
{administrator, false}], ?NO_CONTENT),
|
||||
http_put("/users/myuser", [{password, <<"password">>},
|
||||
{administrator, true}], ?NO_CONTENT),
|
||||
|
|
@ -458,8 +460,8 @@ permissions_amqp_test() ->
|
|||
|
||||
get_conn(Username, Password) ->
|
||||
{ok, Conn} = amqp_connection:start(network, #amqp_params{
|
||||
username = Username,
|
||||
password = Password}),
|
||||
username = list_to_binary(Username),
|
||||
password = list_to_binary(Password)}),
|
||||
LocalPort = rabbit_mgmt_test_db:local_port(Conn),
|
||||
ConnPath = binary_to_list(
|
||||
rabbit_mgmt_format:print(
|
||||
|
|
|
|||
Loading…
Reference in New Issue