commit
cfb6c4b0be
|
@ -178,7 +178,6 @@ stats_fun() ->
|
||||||
undefined ->
|
undefined ->
|
||||||
ok;
|
ok;
|
||||||
Size ->
|
Size ->
|
||||||
emqx_stats:setstat('routes.count', 'routes.max', Size),
|
|
||||||
emqx_stats:setstat('topics.count', 'topics.max', Size)
|
emqx_stats:setstat('topics.count', 'topics.max', Size)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -116,12 +116,6 @@
|
||||||
'subscriptions.shared.max'
|
'subscriptions.shared.max'
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Route stats
|
|
||||||
-define(ROUTE_STATS, [
|
|
||||||
'routes.count',
|
|
||||||
'routes.max'
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% Retained stats
|
%% Retained stats
|
||||||
-define(RETAINED_STATS, [
|
-define(RETAINED_STATS, [
|
||||||
'retained.count',
|
'retained.count',
|
||||||
|
@ -213,7 +207,6 @@ init(#{tick_ms := TickMs}) ->
|
||||||
?CHANNEL_STATS,
|
?CHANNEL_STATS,
|
||||||
?SESSION_STATS,
|
?SESSION_STATS,
|
||||||
?PUBSUB_STATS,
|
?PUBSUB_STATS,
|
||||||
?ROUTE_STATS,
|
|
||||||
?RETAINED_STATS
|
?RETAINED_STATS
|
||||||
]),
|
]),
|
||||||
true = ets:insert(?TAB, [{Name, 0} || Name <- Stats]),
|
true = ets:insert(?TAB, [{Name, 0} || Name <- Stats]),
|
||||||
|
|
|
@ -328,9 +328,7 @@ systopic_stats() ->
|
||||||
<<"subscriptions/shared/max">>,
|
<<"subscriptions/shared/max">>,
|
||||||
<<"subscriptions/shared/count">>,
|
<<"subscriptions/shared/count">>,
|
||||||
<<"topics/max">>,
|
<<"topics/max">>,
|
||||||
<<"topics/count">>,
|
<<"topics/count">>
|
||||||
<<"routes/max">>,
|
|
||||||
<<"routes/count">>
|
|
||||||
],
|
],
|
||||||
?LET(
|
?LET(
|
||||||
{Nodename, T},
|
{Nodename, T},
|
||||||
|
|
|
@ -1412,17 +1412,9 @@ authenticator_examples() ->
|
||||||
|
|
||||||
status_metrics_example() ->
|
status_metrics_example() ->
|
||||||
#{
|
#{
|
||||||
metrics => #{
|
status_metrics => #{
|
||||||
matched => 0,
|
summary => <<"Authn status metrics">>,
|
||||||
success => 0,
|
value => #{
|
||||||
failed => 0,
|
|
||||||
rate => 0.0,
|
|
||||||
rate_last5m => 0.0,
|
|
||||||
rate_max => 0.0
|
|
||||||
},
|
|
||||||
node_metrics => [
|
|
||||||
#{
|
|
||||||
node => node(),
|
|
||||||
metrics => #{
|
metrics => #{
|
||||||
matched => 0,
|
matched => 0,
|
||||||
success => 0,
|
success => 0,
|
||||||
|
@ -1430,16 +1422,29 @@ status_metrics_example() ->
|
||||||
rate => 0.0,
|
rate => 0.0,
|
||||||
rate_last5m => 0.0,
|
rate_last5m => 0.0,
|
||||||
rate_max => 0.0
|
rate_max => 0.0
|
||||||
}
|
},
|
||||||
|
node_metrics => [
|
||||||
|
#{
|
||||||
|
node => node(),
|
||||||
|
metrics => #{
|
||||||
|
matched => 0,
|
||||||
|
success => 0,
|
||||||
|
failed => 0,
|
||||||
|
rate => 0.0,
|
||||||
|
rate_last5m => 0.0,
|
||||||
|
rate_max => 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
status => connected,
|
||||||
|
node_status => [
|
||||||
|
#{
|
||||||
|
node => node(),
|
||||||
|
status => connected
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
status => connected,
|
|
||||||
node_status => [
|
|
||||||
#{
|
|
||||||
node => node(),
|
|
||||||
status => connected
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}.
|
}.
|
||||||
|
|
||||||
request_user_create_examples() ->
|
request_user_create_examples() ->
|
||||||
|
|
|
@ -1,88 +1 @@
|
||||||
|
# TODO: Doc
|
||||||
emqx-dashboard
|
|
||||||
==============
|
|
||||||
|
|
||||||
EMQX Dashboard
|
|
||||||
|
|
||||||
REST API
|
|
||||||
--------
|
|
||||||
|
|
||||||
The prefix of REST API is '/api/v4/'.
|
|
||||||
|
|
||||||
Method | Path | Description
|
|
||||||
-------|---------------------------------------|------------------------------------
|
|
||||||
GET | /nodes/ | A list of nodes in the cluster
|
|
||||||
GET | /nodes/:node | Lookup a node in the cluster
|
|
||||||
GET | /brokers/ | A list of brokers in the cluster
|
|
||||||
GET | /brokers/:node | Get broker info of a node
|
|
||||||
GET | /metrics/ | A list of metrics of all nodes in the cluster
|
|
||||||
GET | /nodes/:node/metrics/ | A list of metrics of a node
|
|
||||||
GET | /stats/ | A list of stats of all nodes in the cluster
|
|
||||||
GET | /nodes/:node/stats/ | A list of stats of a node
|
|
||||||
GET | /nodes/:node/clients/ | A list of clients on a node
|
|
||||||
GET | /listeners/ | A list of listeners in the cluster
|
|
||||||
GET | /nodes/:node/listeners | A list of listeners on the node
|
|
||||||
GET | /nodes/:node/sessions/ | A list of sessions on a node
|
|
||||||
GET | /subscriptions/:clientid | A list of subscriptions of a client
|
|
||||||
GET | /nodes/:node/subscriptions/:clientid | A list of subscriptions of a client on the node
|
|
||||||
GET | /nodes/:node/subscriptions/ | A list of subscriptions on a node
|
|
||||||
PUT | /clients/:clientid/clean_authz_cache | Clean Authorization cache of a client
|
|
||||||
GET | /configs/ | Get all configs
|
|
||||||
GET | /nodes/:node/configs/ | Get all configs of a node
|
|
||||||
GET | /nodes/:node/plugin_configs/:plugin | Get configurations of a plugin on the node
|
|
||||||
DELETE | /clients/:clientid | Kick out a client
|
|
||||||
GET | /alarms/:node | List alarms of a node
|
|
||||||
GET | /alarms/ | List all alarms
|
|
||||||
GET | /plugins/ | List all plugins in the cluster
|
|
||||||
GET | /nodes/:node/plugins/ | List all plugins on a node
|
|
||||||
GET | /routes/ | List routes
|
|
||||||
POST | /nodes/:node/plugins/:plugin/load | Load a plugin
|
|
||||||
GET | /clients/:clientid | Lookup a client in the cluster
|
|
||||||
GET | nodes/:node/clients/:clientid | Lookup a client on node
|
|
||||||
GET | nodes/:node/sessions/:clientid | Lookup a session in the cluster
|
|
||||||
GET | nodes/:node/sessions/:clientid | Lookup a session on the node
|
|
||||||
POST | /mqtt/publish | Publish a MQTT message
|
|
||||||
POST | /mqtt/subscribe | Subscribe a topic
|
|
||||||
POST | /nodes/:node/plugins/:plugin/unload | Unload a plugin
|
|
||||||
POST | /mqtt/unsubscribe | Unsubscribe a topic
|
|
||||||
PUT | /configs/:app | Update config of an application in the cluster
|
|
||||||
PUT | /nodes/:node/configs/:app | Update config of an application on a node
|
|
||||||
PUT | /nodes/:node/plugin_configs/:plugin | Update configurations of a plugin on the node
|
|
||||||
|
|
||||||
Build
|
|
||||||
-----
|
|
||||||
|
|
||||||
make && make ct
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
------------
|
|
||||||
|
|
||||||
```
|
|
||||||
dashboard.listener = 18083
|
|
||||||
|
|
||||||
dashboard.listener.acceptors = 2
|
|
||||||
|
|
||||||
dashboard.listener.max_clients = 512
|
|
||||||
```
|
|
||||||
|
|
||||||
Load Plugin
|
|
||||||
-----------
|
|
||||||
|
|
||||||
```
|
|
||||||
./bin/emqx_ctl plugins load emqx_dashboard
|
|
||||||
```
|
|
||||||
|
|
||||||
Login
|
|
||||||
-----
|
|
||||||
|
|
||||||
URL: http://host:18083
|
|
||||||
|
|
||||||
Username: admin
|
|
||||||
|
|
||||||
Password: public
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
Apache License Version 2.0
|
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
-define(GAUGE_SAMPLER_LIST,
|
-define(GAUGE_SAMPLER_LIST,
|
||||||
[ subscriptions
|
[ subscriptions
|
||||||
, routes
|
, topics
|
||||||
, connections
|
, connections
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ start_listeners(Listeners) ->
|
||||||
}}},
|
}}},
|
||||||
Dispatch = [ {"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}
|
Dispatch = [ {"/", cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}
|
||||||
, {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}
|
, {"/static/[...]", cowboy_static, {priv_dir, emqx_dashboard, "www/static"}}
|
||||||
|
, {?BASE_PATH ++ "/[...]", emqx_dashboard_bad_api, []}
|
||||||
, {'_', cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}
|
, {'_', cowboy_static, {priv_file, emqx_dashboard, "www/index.html"}}
|
||||||
],
|
],
|
||||||
BaseMinirest = #{
|
BaseMinirest = #{
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_bad_api).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
|
-export([init/2]).
|
||||||
|
|
||||||
|
init(Req0, State) ->
|
||||||
|
?SLOG(warning, #{msg => "unexpected_api_access", request => Req0}),
|
||||||
|
Req = cowboy_req:reply(404,
|
||||||
|
#{<<"content-type">> => <<"application/json">>},
|
||||||
|
<<"{\"code\": \"API_NOT_EXIST\", \"message\": \"Request Path Not Found\"}">>,
|
||||||
|
Req0),
|
||||||
|
{ok, Req, State}.
|
|
@ -352,7 +352,7 @@ count_map(M1, M2) ->
|
||||||
lists:foldl(Fun, #{}, ?SAMPLER_LIST).
|
lists:foldl(Fun, #{}, ?SAMPLER_LIST).
|
||||||
|
|
||||||
value(connections) -> emqx_stats:getstat('connections.count');
|
value(connections) -> emqx_stats:getstat('connections.count');
|
||||||
value(routes) -> emqx_stats:getstat('routes.count');
|
value(topics) -> emqx_stats:getstat('topics.count');
|
||||||
value(subscriptions) -> emqx_stats:getstat('subscriptions.count');
|
value(subscriptions) -> emqx_stats:getstat('subscriptions.count');
|
||||||
value(received) -> emqx_metrics:val('messages.received');
|
value(received) -> emqx_metrics:val('messages.received');
|
||||||
value(received_bytes) -> emqx_metrics:val('bytes.received');
|
value(received_bytes) -> emqx_metrics:val('bytes.received');
|
||||||
|
|
|
@ -148,8 +148,8 @@ swagger_desc(dropped) -> swagger_desc_format("Dropped messages ");
|
||||||
swagger_desc(subscriptions) ->
|
swagger_desc(subscriptions) ->
|
||||||
<<"Subscriptions at the time of sampling."
|
<<"Subscriptions at the time of sampling."
|
||||||
" Can only represent the approximate state">>;
|
" Can only represent the approximate state">>;
|
||||||
swagger_desc(routes) ->
|
swagger_desc(topics) ->
|
||||||
<<"Routes at the time of sampling."
|
<<"Count topics at the time of sampling."
|
||||||
" Can only represent the approximate state">>;
|
" Can only represent the approximate state">>;
|
||||||
swagger_desc(connections) ->
|
swagger_desc(connections) ->
|
||||||
<<"Connections at the time of sampling."
|
<<"Connections at the time of sampling."
|
||||||
|
|
|
@ -109,10 +109,7 @@ schema_with_example(Type, Example) ->
|
||||||
|
|
||||||
-spec(schema_with_examples(hocon_schema:type(), map()) -> hocon_schema:field_schema_map()).
|
-spec(schema_with_examples(hocon_schema:type(), map()) -> hocon_schema:field_schema_map()).
|
||||||
schema_with_examples(Type, Examples) ->
|
schema_with_examples(Type, Examples) ->
|
||||||
%% Swagger can dynamically distinguish if there are multiple examples.
|
hoconsc:mk(Type, #{examples => #{<<"examples">> => Examples}}).
|
||||||
%% But explicitly declaring examples as plural
|
|
||||||
%% may cause some example structures to be incorrectly identified.
|
|
||||||
schema_with_example(Type, Examples).
|
|
||||||
|
|
||||||
-spec(error_codes(list(atom())) -> hocon_schema:fields()).
|
-spec(error_codes(list(atom())) -> hocon_schema:fields()).
|
||||||
error_codes(Codes) ->
|
error_codes(Codes) ->
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_dashboard_bad_api_SUITE).
|
||||||
|
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/http_api.hrl").
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-define(SERVER, "http://127.0.0.1:18083/api/v5").
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
mria:start(),
|
||||||
|
application:load(emqx_dashboard),
|
||||||
|
emqx_common_test_helpers:start_apps([emqx_conf, emqx_dashboard], fun set_special_configs/1),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
set_special_configs(emqx_dashboard) ->
|
||||||
|
Config = #{
|
||||||
|
default_username => <<"admin">>,
|
||||||
|
default_password => <<"public">>,
|
||||||
|
listeners => [#{
|
||||||
|
protocol => http,
|
||||||
|
port => 18083
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
emqx_config:put([emqx_dashboard], Config),
|
||||||
|
ok;
|
||||||
|
set_special_configs(_) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
end_per_suite(Config) ->
|
||||||
|
end_suite(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_suite() ->
|
||||||
|
application:unload(emqx_management),
|
||||||
|
emqx_common_test_helpers:stop_apps([emqx_dashboard]).
|
||||||
|
|
||||||
|
t_bad_api_path(_) ->
|
||||||
|
Url = ?SERVER ++ "/for/test/some/path/not/exist",
|
||||||
|
{error,{"HTTP/1.1", 404, "Not Found"}} = request(Url),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
request(Url) ->
|
||||||
|
Request = {Url, []},
|
||||||
|
case httpc:request(get, Request, [], []) of
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason};
|
||||||
|
{ok, {{"HTTP/1.1", Code, _}, _, Return} }
|
||||||
|
when Code >= 200 andalso Code =< 299 ->
|
||||||
|
{ok, emqx_json:decode(Return, [return_maps])};
|
||||||
|
{ok, {Reason, _, _}} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
|
@ -71,10 +71,6 @@
|
||||||
, do_list_subscriptions/0
|
, do_list_subscriptions/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Routes
|
|
||||||
-export([ lookup_routes/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
%% PubSub
|
%% PubSub
|
||||||
-export([ subscribe/2
|
-export([ subscribe/2
|
||||||
, do_subscribe/2
|
, do_subscribe/2
|
||||||
|
@ -97,23 +93,13 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Common Table API
|
%% Common Table API
|
||||||
-export([ item/2
|
-export([ max_row_limit/0
|
||||||
, max_row_limit/0
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ return/0
|
|
||||||
, return/1]).
|
|
||||||
|
|
||||||
-define(APP, emqx_management).
|
-define(APP, emqx_management).
|
||||||
|
|
||||||
-elvis([{elvis_style, god_modules, disable}]).
|
-elvis([{elvis_style, god_modules, disable}]).
|
||||||
|
|
||||||
%% TODO: remove these function after all api use minirest version 1.X
|
|
||||||
return() ->
|
|
||||||
ok.
|
|
||||||
return(_Response) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Node Info
|
%% Node Info
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -183,8 +169,8 @@ get_stats() ->
|
||||||
GlobalStatsKeys =
|
GlobalStatsKeys =
|
||||||
[ 'retained.count'
|
[ 'retained.count'
|
||||||
, 'retained.max'
|
, 'retained.max'
|
||||||
, 'routes.count'
|
, 'topics.count'
|
||||||
, 'routes.max'
|
, 'topics.max'
|
||||||
, 'subscriptions.shared.count'
|
, 'subscriptions.shared.count'
|
||||||
, 'subscriptions.shared.max'
|
, 'subscriptions.shared.max'
|
||||||
],
|
],
|
||||||
|
@ -335,7 +321,8 @@ call_client(Node, ClientId, Req) ->
|
||||||
do_list_subscriptions() ->
|
do_list_subscriptions() ->
|
||||||
case check_row_limit([mqtt_subproperty]) of
|
case check_row_limit([mqtt_subproperty]) of
|
||||||
false -> throw(max_row_limit);
|
false -> throw(max_row_limit);
|
||||||
ok -> [item(subscription, Sub) || Sub <- ets:tab2list(mqtt_subproperty)]
|
ok -> [#{topic => Topic, clientid => ClientId, options => Options}
|
||||||
|
|| {{Topic, ClientId}, Options} <- ets:tab2list(mqtt_subproperty)]
|
||||||
end.
|
end.
|
||||||
|
|
||||||
list_subscriptions(Node) ->
|
list_subscriptions(Node) ->
|
||||||
|
@ -357,13 +344,6 @@ lookup_subscriptions(ClientId) ->
|
||||||
lookup_subscriptions(Node, ClientId) ->
|
lookup_subscriptions(Node, ClientId) ->
|
||||||
wrap_rpc(emqx_broker_proto_v1:list_client_subscriptions(Node, ClientId)).
|
wrap_rpc(emqx_broker_proto_v1:list_client_subscriptions(Node, ClientId)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Routes
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
lookup_routes(Topic) ->
|
|
||||||
emqx_router:lookup_routes(Topic).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% PubSub
|
%% PubSub
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -463,18 +443,6 @@ create_banned(Banned) ->
|
||||||
delete_banned(Who) ->
|
delete_banned(Who) ->
|
||||||
emqx_banned:delete(Who).
|
emqx_banned:delete(Who).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Common Table API
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
item(subscription, {{Topic, ClientId}, Options}) ->
|
|
||||||
#{topic => Topic, clientid => ClientId, options => Options};
|
|
||||||
|
|
||||||
item(route, #route{topic = Topic, dest = Node}) ->
|
|
||||||
#{topic => Topic, node => Node};
|
|
||||||
item(route, {Topic, Node}) ->
|
|
||||||
#{topic => Topic, node => Node}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Internal Functions.
|
%% Internal Functions.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -93,12 +93,6 @@ fields(node_stats_data) ->
|
||||||
, { 'retained.max'
|
, { 'retained.max'
|
||||||
, mk( integer(), #{ desc => <<"Historical maximum number of retained messages">>
|
, mk( integer(), #{ desc => <<"Historical maximum number of retained messages">>
|
||||||
, example => 0})}
|
, example => 0})}
|
||||||
, { 'routes.count'
|
|
||||||
, mk( integer(), #{ desc => <<"Number of current routes">>
|
|
||||||
, example => 0})}
|
|
||||||
, { 'routes.max'
|
|
||||||
, mk( integer(), #{ desc => <<"Historical maximum number of routes">>
|
|
||||||
, example => 0})}
|
|
||||||
, { 'sessions.count'
|
, { 'sessions.count'
|
||||||
, mk( integer(), #{ desc => <<"Number of current sessions">>
|
, mk( integer(), #{ desc => <<"Number of current sessions">>
|
||||||
, example => 0})}
|
, example => 0})}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_mgmt_api_routes).
|
-module(emqx_mgmt_api_topics).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("typerefl/include/types.hrl").
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
@ -28,28 +28,28 @@
|
||||||
, fields/1
|
, fields/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ routes/2
|
-export([ topics/2
|
||||||
, route/2
|
, topic/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ query/4]).
|
-export([ query/4]).
|
||||||
|
|
||||||
-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
|
-define(TOPIC_NOT_FOUND, 'TOPIC_NOT_FOUND').
|
||||||
|
|
||||||
-define(ROUTES_QSCHEMA, [{<<"topic">>, binary}, {<<"node">>, atom}]).
|
-define(TOPICS_QUERY_SCHEMA, [{<<"topic">>, binary}, {<<"node">>, atom}]).
|
||||||
|
|
||||||
|
|
||||||
api_spec() ->
|
api_spec() ->
|
||||||
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}).
|
||||||
|
|
||||||
paths() ->
|
paths() ->
|
||||||
["/routes", "/routes/:topic"].
|
["/topics", "/topics/:topic"].
|
||||||
|
|
||||||
schema("/routes") ->
|
schema("/topics") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => routes,
|
'operationId' => topics,
|
||||||
get => #{
|
get => #{
|
||||||
description => <<"EMQX Topics List">>,
|
description => <<"Topics list">>,
|
||||||
parameters => [
|
parameters => [
|
||||||
topic_param(query),
|
topic_param(query),
|
||||||
node_param(),
|
node_param(),
|
||||||
|
@ -64,11 +64,11 @@ schema("/routes") ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
schema("/routes/:topic") ->
|
schema("/topics/:topic") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => route,
|
'operationId' => topic,
|
||||||
get => #{
|
get => #{
|
||||||
description => <<"EMQX Topic List">>,
|
description => <<"Lookup topic info by name">>,
|
||||||
parameters => [topic_param(path)],
|
parameters => [topic_param(path)],
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => hoconsc:mk(hoconsc:ref(topic), #{}),
|
200 => hoconsc:mk(hoconsc:ref(topic), #{}),
|
||||||
|
@ -94,21 +94,21 @@ fields(meta) ->
|
||||||
|
|
||||||
%%%==============================================================================================
|
%%%==============================================================================================
|
||||||
%% parameters trans
|
%% parameters trans
|
||||||
routes(get, #{query_string := Qs}) ->
|
topics(get, #{query_string := Qs}) ->
|
||||||
do_list(generate_topic(Qs)).
|
do_list(generate_topic(Qs)).
|
||||||
|
|
||||||
route(get, #{bindings := Bindings}) ->
|
topic(get, #{bindings := Bindings}) ->
|
||||||
lookup(generate_topic(Bindings)).
|
lookup(generate_topic(Bindings)).
|
||||||
|
|
||||||
%%%==============================================================================================
|
%%%==============================================================================================
|
||||||
%% api apply
|
%% api apply
|
||||||
do_list(Params) ->
|
do_list(Params) ->
|
||||||
Response = emqx_mgmt_api:node_query(
|
Response = emqx_mgmt_api:node_query(
|
||||||
node(), Params, emqx_route, ?ROUTES_QSCHEMA, {?MODULE, query}),
|
node(), Params, emqx_route, ?TOPICS_QUERY_SCHEMA, {?MODULE, query}),
|
||||||
emqx_mgmt_util:generate_response(Response).
|
emqx_mgmt_util:generate_response(Response).
|
||||||
|
|
||||||
lookup(#{topic := Topic}) ->
|
lookup(#{topic := Topic}) ->
|
||||||
case emqx_mgmt:lookup_routes(Topic) of
|
case emqx_router:lookup_routes(Topic) of
|
||||||
[] ->
|
[] ->
|
||||||
{404, #{code => ?TOPIC_NOT_FOUND, message => <<"Topic not found">>}};
|
{404, #{code => ?TOPIC_NOT_FOUND, message => <<"Topic not found">>}};
|
||||||
[Route] ->
|
[Route] ->
|
|
@ -31,7 +31,7 @@
|
||||||
broker/1,
|
broker/1,
|
||||||
cluster/1,
|
cluster/1,
|
||||||
clients/1,
|
clients/1,
|
||||||
routes/1,
|
topics/1,
|
||||||
subscriptions/1,
|
subscriptions/1,
|
||||||
plugins/1,
|
plugins/1,
|
||||||
listeners/1,
|
listeners/1,
|
||||||
|
@ -164,17 +164,17 @@ if_client(ClientId, Fun) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% @doc Routes Command
|
%% @doc Topics Command
|
||||||
|
|
||||||
routes(["list"]) ->
|
topics(["list"]) ->
|
||||||
dump(emqx_route);
|
dump(emqx_route);
|
||||||
routes(["show", Topic]) ->
|
topics(["show", Topic]) ->
|
||||||
Routes = ets:lookup(emqx_route, bin(Topic)),
|
Routes = ets:lookup(emqx_route, bin(Topic)),
|
||||||
[print({emqx_route, Route}) || Route <- Routes];
|
[print({emqx_topic, Route}) || Route <- Routes];
|
||||||
routes(_) ->
|
topics(_) ->
|
||||||
emqx_ctl:usage([
|
emqx_ctl:usage([
|
||||||
{"routes list", "List all routes"},
|
{"topics list", "List all topics"},
|
||||||
{"routes show <Topic>", "Show a route"}
|
{"topics show <Topic>", "Show a topic"}
|
||||||
]).
|
]).
|
||||||
|
|
||||||
subscriptions(["list"]) ->
|
subscriptions(["list"]) ->
|
||||||
|
@ -750,9 +750,9 @@ print({client, {ClientId, ChanPid}}) ->
|
||||||
end,
|
end,
|
||||||
[format(K, maps:get(K, Info1)) || K <- InfoKeys]
|
[format(K, maps:get(K, Info1)) || K <- InfoKeys]
|
||||||
);
|
);
|
||||||
print({emqx_route, #route{topic = Topic, dest = {_, Node}}}) ->
|
print({emqx_topic, #route{topic = Topic, dest = {_, Node}}}) ->
|
||||||
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
||||||
print({emqx_route, #route{topic = Topic, dest = Node}}) ->
|
print({emqx_topic, #route{topic = Topic, dest = Node}}) ->
|
||||||
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
emqx_ctl:print("~ts -> ~ts~n", [Topic, Node]);
|
||||||
print(#plugin{name = Name, descr = Descr, active = Active}) ->
|
print(#plugin{name = Name, descr = Descr, active = Active}) ->
|
||||||
emqx_ctl:print(
|
emqx_ctl:print(
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
-module(emqx_mgmt_api_routes_SUITE).
|
-module(emqx_mgmt_api_topics_SUITE).
|
||||||
|
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
@ -36,7 +36,8 @@ t_nodes_api(_) ->
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, Topic),
|
{ok, _, _} = emqtt:subscribe(Client, Topic),
|
||||||
|
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["routes"]),
|
%% list all
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["topics"]),
|
||||||
{ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path),
|
{ok, Response} = emqx_mgmt_api_test_util:request_api(get, Path),
|
||||||
RoutesData = emqx_json:decode(Response, [return_maps]),
|
RoutesData = emqx_json:decode(Response, [return_maps]),
|
||||||
Meta = maps:get(<<"meta">>, RoutesData),
|
Meta = maps:get(<<"meta">>, RoutesData),
|
||||||
|
@ -48,8 +49,8 @@ t_nodes_api(_) ->
|
||||||
?assertEqual(Topic, maps:get(<<"topic">>, Route)),
|
?assertEqual(Topic, maps:get(<<"topic">>, Route)),
|
||||||
?assertEqual(atom_to_binary(node(), utf8), maps:get(<<"node">>, Route)),
|
?assertEqual(atom_to_binary(node(), utf8), maps:get(<<"node">>, Route)),
|
||||||
|
|
||||||
%% get routes/:topic
|
%% get topics/:topic
|
||||||
RoutePath = emqx_mgmt_api_test_util:api_path(["routes", Topic]),
|
RoutePath = emqx_mgmt_api_test_util:api_path(["topics", Topic]),
|
||||||
{ok, RouteResponse} = emqx_mgmt_api_test_util:request_api(get, RoutePath),
|
{ok, RouteResponse} = emqx_mgmt_api_test_util:request_api(get, RoutePath),
|
||||||
RouteData = emqx_json:decode(RouteResponse, [return_maps]),
|
RouteData = emqx_json:decode(RouteResponse, [return_maps]),
|
||||||
?assertEqual(Topic, maps:get(<<"topic">>, RouteData)),
|
?assertEqual(Topic, maps:get(<<"topic">>, RouteData)),
|
|
@ -218,10 +218,6 @@ emqx_connections_max 0
|
||||||
emqx_retained_count 3
|
emqx_retained_count 3
|
||||||
# TYPE emqx_retained_max gauge
|
# TYPE emqx_retained_max gauge
|
||||||
emqx_retained_max 3
|
emqx_retained_max 3
|
||||||
# TYPE emqx_routes_count gauge
|
|
||||||
emqx_routes_count 0
|
|
||||||
# TYPE emqx_routes_max gauge
|
|
||||||
emqx_routes_max 0
|
|
||||||
# TYPE emqx_sessions_count gauge
|
# TYPE emqx_sessions_count gauge
|
||||||
emqx_sessions_count 0
|
emqx_sessions_count 0
|
||||||
# TYPE emqx_sessions_max gauge
|
# TYPE emqx_sessions_max gauge
|
||||||
|
|
|
@ -248,12 +248,6 @@ emqx_collect(emqx_subscriptions_shared_count, Stats) ->
|
||||||
emqx_collect(emqx_subscriptions_shared_max, Stats) ->
|
emqx_collect(emqx_subscriptions_shared_max, Stats) ->
|
||||||
gauge_metric(?C('subscriptions.shared.max', Stats));
|
gauge_metric(?C('subscriptions.shared.max', Stats));
|
||||||
|
|
||||||
%% routes
|
|
||||||
emqx_collect(emqx_routes_count, Stats) ->
|
|
||||||
gauge_metric(?C('routes.count', Stats));
|
|
||||||
emqx_collect(emqx_routes_max, Stats) ->
|
|
||||||
gauge_metric(?C('routes.max', Stats));
|
|
||||||
|
|
||||||
%% retained
|
%% retained
|
||||||
emqx_collect(emqx_retained_count, Stats) ->
|
emqx_collect(emqx_retained_count, Stats) ->
|
||||||
gauge_metric(?C('retained.count', Stats));
|
gauge_metric(?C('retained.count', Stats));
|
||||||
|
@ -517,8 +511,6 @@ emqx_stats() ->
|
||||||
, emqx_subscriptions_max
|
, emqx_subscriptions_max
|
||||||
, emqx_subscriptions_shared_count
|
, emqx_subscriptions_shared_count
|
||||||
, emqx_subscriptions_shared_max
|
, emqx_subscriptions_shared_max
|
||||||
, emqx_routes_count
|
|
||||||
, emqx_routes_max
|
|
||||||
, emqx_retained_count
|
, emqx_retained_count
|
||||||
, emqx_retained_max
|
, emqx_retained_max
|
||||||
].
|
].
|
||||||
|
|
Loading…
Reference in New Issue