chore: dashboard format code

This commit is contained in:
DDDHuang 2022-04-26 16:54:14 +08:00
parent 2ea66ebcee
commit 07444e3da5
19 changed files with 363 additions and 276 deletions

View File

@ -20,17 +20,18 @@
pwdhash :: binary(), pwdhash :: binary(),
description :: binary(), description :: binary(),
role = undefined :: atom(), role = undefined :: atom(),
extra = [] :: term() %% not used so far, for future extension %% not used so far, for future extension
extra = [] :: term()
}). }).
-define(ADMIN_JWT, emqx_admin_jwt). -define(ADMIN_JWT, emqx_admin_jwt).
-record(?ADMIN_JWT, { -record(?ADMIN_JWT, {
token :: binary(), token :: binary(),
username :: binary(), username :: binary(),
exptime :: integer(), exptime :: integer(),
extra = [] :: term() %% not used so far, fur future extension %% not used so far, fur future extension
extra = [] :: term()
}). }).
-define(TAB_COLLECT, emqx_collect). -define(TAB_COLLECT, emqx_collect).
@ -49,18 +50,18 @@
-define(RPC_TIMEOUT, 5000). -define(RPC_TIMEOUT, 5000).
-endif. -endif.
-define(DELTA_SAMPLER_LIST, -define(DELTA_SAMPLER_LIST, [
[ received received,
%, received_bytes %, received_bytes
, sent sent,
%, sent_bytes %, sent_bytes
, dropped dropped
]). ]).
-define(GAUGE_SAMPLER_LIST, -define(GAUGE_SAMPLER_LIST, [
[ subscriptions subscriptions,
, topics topics,
, connections connections
]). ]).
-define(SAMPLER_LIST, ?GAUGE_SAMPLER_LIST ++ ?DELTA_SAMPLER_LIST). -define(SAMPLER_LIST, ?GAUGE_SAMPLER_LIST ++ ?DELTA_SAMPLER_LIST).

View File

@ -3,16 +3,25 @@
{deps, [{emqx, {path, "../emqx"}}]}. {deps, [{emqx, {path, "../emqx"}}]}.
{edoc_opts, [{preprocess, true}]}. {edoc_opts, [{preprocess, true}]}.
{erl_opts, [warn_unused_vars, {erl_opts, [
warn_unused_vars,
warn_shadow_vars, warn_shadow_vars,
warn_unused_import, warn_unused_import,
warn_obsolete_guard, warn_obsolete_guard,
debug_info, debug_info,
{d, 'APPLICATION', emqx}]}. {d, 'APPLICATION', emqx}
{xref_checks, [undefined_function_calls, undefined_functions, ]}.
locals_not_used, deprecated_function_calls, {xref_checks, [
warnings_as_errors, deprecated_functions]}. undefined_function_calls,
undefined_functions,
locals_not_used,
deprecated_function_calls,
warnings_as_errors,
deprecated_functions
]}.
{cover_enabled, true}. {cover_enabled, true}.
{cover_opts, [verbose]}. {cover_opts, [verbose]}.
{cover_export_enabled, true}. {cover_export_enabled, true}.
{eunit_first_files, ["test/emqx_swagger_remote_schema.erl"]}. {eunit_first_files, ["test/emqx_swagger_remote_schema.erl"]}.
{project_plugins, [erlfmt]}.

View File

@ -1,7 +1,8 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_dashboard, {application, emqx_dashboard, [
[{description, "EMQX Web Dashboard"}, {description, "EMQX Web Dashboard"},
{vsn, "5.0.0"}, % strict semver, bump manually! % strict semver, bump manually!
{vsn, "5.0.0"},
{modules, []}, {modules, []},
{registered, [emqx_dashboard_sup]}, {registered, [emqx_dashboard_sup]},
{applications, [kernel, stdlib, mnesia, minirest, emqx]}, {applications, [kernel, stdlib, mnesia, minirest, emqx]},
@ -9,7 +10,8 @@
{env, []}, {env, []},
{licenses, ["Apache-2.0"]}, {licenses, ["Apache-2.0"]},
{maintainers, ["EMQX Team <contact@emqx.io>"]}, {maintainers, ["EMQX Team <contact@emqx.io>"]},
{links, [{"Homepage", "https://emqx.io/"}, {links, [
{"Homepage", "https://emqx.io/"},
{"Github", "https://github.com/emqx/emqx-dashboard"} {"Github", "https://github.com/emqx/emqx-dashboard"}
]} ]}
]}. ]}.

View File

@ -26,27 +26,31 @@
%% Mnesia bootstrap %% Mnesia bootstrap
-export([mnesia/1]). -export([mnesia/1]).
-export([ add_user/3 -export([
, force_add_user/3 add_user/3,
, remove_user/1 force_add_user/3,
, update_user/2 remove_user/1,
, lookup_user/1 update_user/2,
, change_password/2 lookup_user/1,
, change_password/3 change_password/2,
, all_users/0 change_password/3,
, check/2 all_users/0,
check/2
]). ]).
-export([ sign_token/2 -export([
, verify_token/1 sign_token/2,
, destroy_token_by_username/2 verify_token/1,
destroy_token_by_username/2
]). ]).
-export([ hash/1 -export([
, verify_hash/2 hash/1,
verify_hash/2
]). ]).
-export([ add_default_user/0 -export([
, default_username/0 add_default_user/0,
default_username/0
]). ]).
-type emqx_admin() :: #?ADMIN{}. -type emqx_admin() :: #?ADMIN{}.
@ -62,36 +66,46 @@ mnesia(boot) ->
{storage, disc_copies}, {storage, disc_copies},
{record_name, ?ADMIN}, {record_name, ?ADMIN},
{attributes, record_info(fields, ?ADMIN)}, {attributes, record_info(fields, ?ADMIN)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(add_user(binary(), binary(), binary()) -> {ok, map()} | {error, any()}). -spec add_user(binary(), binary(), binary()) -> {ok, map()} | {error, any()}.
add_user(Username, Password, Desc) add_user(Username, Password, Desc) when
when is_binary(Username), is_binary(Password) -> is_binary(Username), is_binary(Password)
->
case legal_username(Username) of case legal_username(Username) of
true -> true ->
return( return(
mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc])); mria:transaction(?DASHBOARD_SHARD, fun add_user_/3, [Username, Password, Desc])
);
false -> false ->
{error, <<"Bad Username." {error, <<
" Only upper and lower case letters, numbers and underscores are supported">>} "Bad Username."
" Only upper and lower case letters, numbers and underscores are supported"
>>}
end. end.
%% 0 - 9 or A -Z or a - z or $_ %% 0 - 9 or A -Z or a - z or $_
legal_username(<<>>) -> false; legal_username(<<>>) -> false;
legal_username(UserName) -> legal_username(UserName) -> nomatch /= re:run(UserName, "^[_a-zA-Z0-9]*$").
nomatch /= re:run(UserName, "^[_a-zA-Z0-9]*$").
%% black-magic: force overwrite a user %% black-magic: force overwrite a user
force_add_user(Username, Password, Desc) -> force_add_user(Username, Password, Desc) ->
AddFun = fun() -> AddFun = fun() ->
mnesia:write(#?ADMIN{username = Username, mnesia:write(#?ADMIN{
username = Username,
pwdhash = hash(Password), pwdhash = hash(Password),
description = Desc}) description = Desc
})
end, end,
case mria:transaction(?DASHBOARD_SHARD, AddFun) of case mria:transaction(?DASHBOARD_SHARD, AddFun) of
{atomic, ok} -> ok; {atomic, ok} -> ok;
@ -109,7 +123,7 @@ add_user_(Username, Password, Desc) ->
mnesia:abort(<<"username_already_exist">>) mnesia:abort(<<"username_already_exist">>)
end. end.
-spec(remove_user(binary()) -> {ok, any()} | {error, any()}). -spec remove_user(binary()) -> {ok, any()} | {error, any()}.
remove_user(Username) when is_binary(Username) -> remove_user(Username) when is_binary(Username) ->
Trans = fun() -> Trans = fun() ->
case lookup_user(Username) of case lookup_user(Username) of
@ -125,7 +139,7 @@ remove_user(Username) when is_binary(Username) ->
{error, Reason} {error, Reason}
end. end.
-spec(update_user(binary(), binary()) -> {ok, map()} | {error, term()}). -spec update_user(binary(), binary()) -> {ok, map()} | {error, term()}.
update_user(Username, Desc) when is_binary(Username) -> update_user(Username, Desc) when is_binary(Username) ->
return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/2, [Username, Desc])). return(mria:transaction(?DASHBOARD_SHARD, fun update_user_/2, [Username, Desc])).
@ -140,7 +154,8 @@ verify_hash(Origin, SaltHash) ->
true -> ok; true -> ok;
false -> error false -> error
end; end;
_ -> error _ ->
error
end. end.
sha256(SaltBin, Password) -> sha256(SaltBin, Password) ->
@ -174,7 +189,8 @@ change_password_hash(Username, PasswordHash) ->
{ok, Result} -> {ok, Result} ->
_ = emqx_dashboard_token:destroy_by_username(Username), _ = emqx_dashboard_token:destroy_by_username(Username),
{ok, Result}; {ok, Result};
{error, Reason} -> {error, Reason} {error, Reason} ->
{error, Reason}
end. end.
update_pwd(Username, Fun) -> update_pwd(Username, Fun) ->
@ -183,30 +199,35 @@ update_pwd(Username, Fun) ->
User = User =
case lookup_user(Username) of case lookup_user(Username) of
[Admin] -> Admin; [Admin] -> Admin;
[] -> [] -> mnesia:abort(<<"username_not_found">>)
mnesia:abort(<<"username_not_found">>)
end, end,
mnesia:write(Fun(User)) mnesia:write(Fun(User))
end, end,
return(mria:transaction(?DASHBOARD_SHARD, Trans)). return(mria:transaction(?DASHBOARD_SHARD, Trans)).
-spec lookup_user(binary()) -> [emqx_admin()].
-spec(lookup_user(binary()) -> [emqx_admin()]).
lookup_user(Username) when is_binary(Username) -> lookup_user(Username) when is_binary(Username) ->
Fun = fun() -> mnesia:read(?ADMIN, Username) end, Fun = fun() -> mnesia:read(?ADMIN, Username) end,
{atomic, User} = mria:ro_transaction(?DASHBOARD_SHARD, Fun), {atomic, User} = mria:ro_transaction(?DASHBOARD_SHARD, Fun),
User. User.
-spec(all_users() -> [map()]). -spec all_users() -> [map()].
all_users() -> all_users() ->
lists:map(fun(#?ADMIN{username = Username, lists:map(
fun(
#?ADMIN{
username = Username,
description = Desc description = Desc
}) -> }
#{username => Username, ) ->
#{
username => Username,
description => Desc description => Desc
} }
end, ets:tab2list(?ADMIN)). end,
-spec(return({atomic | aborted, term()}) -> {ok, term()} | {error, Reason :: binary()}). ets:tab2list(?ADMIN)
).
-spec return({atomic | aborted, term()}) -> {ok, term()} | {error, Reason :: binary()}.
return({atomic, Result}) -> return({atomic, Result}) ->
{ok, Result}; {ok, Result};
return({aborted, Reason}) -> return({aborted, Reason}) ->
@ -252,7 +273,7 @@ destroy_token_by_username(Username, Token) ->
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(add_default_user() -> {ok, map() | empty | default_user_exists } | {error, any()}). -spec add_default_user() -> {ok, map() | empty | default_user_exists} | {error, any()}.
add_default_user() -> add_default_user() ->
add_default_user(binenv(default_username), binenv(default_password)). add_default_user(binenv(default_username), binenv(default_password)).
@ -264,7 +285,6 @@ binenv(Key) ->
add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY(Password) -> add_default_user(Username, Password) when ?EMPTY_KEY(Username) orelse ?EMPTY_KEY(Password) ->
{ok, empty}; {ok, empty};
add_default_user(Username, Password) -> add_default_user(Username, Password) ->
case lookup_user(Username) of case lookup_user(Username) of
[] -> add_user(Username, Password, <<"administrator">>); [] -> add_user(Username, Password, <<"administrator">>);

View File

@ -60,11 +60,13 @@ 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() ->
[ "/login" [
, "/logout" "/login",
, "/users" "/logout",
, "/users/:username" "/users",
, "/users/:username/change_pwd"]. "/users/:username",
"/users/:username/change_pwd"
].
schema("/login") -> schema("/login") ->
#{ #{
@ -101,8 +103,10 @@ schema("/users") ->
tags => [<<"dashboard">>], tags => [<<"dashboard">>],
desc => ?DESC(list_users_api), desc => ?DESC(list_users_api),
responses => #{ responses => #{
200 => mk(array(hoconsc:ref(user)), 200 => mk(
#{desc => ?DESC(list_users_api)}) array(hoconsc:ref(user)),
#{desc => ?DESC(list_users_api)}
)
} }
}, },
post => #{ post => #{
@ -114,7 +118,6 @@ schema("/users") ->
} }
} }
}; };
schema("/users/:username") -> schema("/users/:username") ->
#{ #{
'operationId' => user, 'operationId' => user,
@ -135,7 +138,8 @@ schema("/users/:username") ->
responses => #{ responses => #{
204 => <<"Delete User successfully">>, 204 => <<"Delete User successfully">>,
400 => emqx_dashboard_swagger:error_codes( 400 => emqx_dashboard_swagger:error_codes(
[?BAD_REQUEST, ?NOT_ALLOWED], ?DESC(login_failed_response400)), [?BAD_REQUEST, ?NOT_ALLOWED], ?DESC(login_failed_response400)
),
404 => response_schema(404) 404 => response_schema(404)
} }
} }
@ -151,10 +155,12 @@ schema("/users/:username/change_pwd") ->
responses => #{ responses => #{
204 => <<"Update user password successfully">>, 204 => <<"Update user password successfully">>,
401 => emqx_dashboard_swagger:error_codes( 401 => emqx_dashboard_swagger:error_codes(
[?WRONG_USERNAME_OR_PWD, ?ERROR_PWD_NOT_MATCH], ?DESC(login_failed401)), [?WRONG_USERNAME_OR_PWD, ?ERROR_PWD_NOT_MATCH], ?DESC(login_failed401)
),
404 => response_schema(404), 404 => response_schema(404),
400 => emqx_dashboard_swagger:error_codes( 400 => emqx_dashboard_swagger:error_codes(
[?BAD_REQUEST], ?DESC(login_failed_response400)) [?BAD_REQUEST], ?DESC(login_failed_response400)
)
} }
} }
}. }.
@ -174,26 +180,32 @@ field(username) ->
mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>})}; mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>})};
field(username_in_path) -> field(username_in_path) ->
{username, {username,
mk(binary(), #{desc => ?DESC(username), 'maxLength' => 100, example => <<"admin">>, mk(binary(), #{
in => path, required => true})}; desc => ?DESC(username),
'maxLength' => 100,
example => <<"admin">>,
in => path,
required => true
})};
field(password) -> field(password) ->
{password, {password,
mk(binary(), #{desc => ?DESC(password), 'maxLength' => 100, example => <<"public">>})}; mk(binary(), #{desc => ?DESC(password), 'maxLength' => 100, example => <<"public">>})};
field(description) -> field(description) ->
{description, {description, mk(binary(), #{desc => ?DESC(user_description), example => <<"administrator">>})};
mk(binary(), #{desc => ?DESC(user_description), example => <<"administrator">>})};
field(token) -> field(token) ->
{token, mk(binary(), #{desc => ?DESC(token)})}; {token, mk(binary(), #{desc => ?DESC(token)})};
field(license) -> field(license) ->
{license, [ {license, [
{edition, mk(enum([community, enterprise]), {edition,
#{desc => ?DESC(license), example => community})}]}; mk(
enum([community, enterprise]),
#{desc => ?DESC(license), example => community}
)}
]};
field(version) -> field(version) ->
{version, mk(string(), #{desc => ?DESC(version), example => <<"5.0.0">>})}; {version, mk(string(), #{desc => ?DESC(version), example => <<"5.0.0">>})};
field(old_pwd) -> field(old_pwd) ->
{old_pwd, mk(binary(), #{desc => ?DESC(old_pwd)})}; {old_pwd, mk(binary(), #{desc => ?DESC(old_pwd)})};
field(new_pwd) -> field(new_pwd) ->
{new_pwd, mk(binary(), #{desc => ?DESC(new_pwd)})}. {new_pwd, mk(binary(), #{desc => ?DESC(new_pwd)})}.
@ -207,7 +219,8 @@ login(post, #{body := Params}) ->
{ok, Token} -> {ok, Token} ->
?SLOG(info, #{msg => "Dashboard login successfully", username => Username}), ?SLOG(info, #{msg => "Dashboard login successfully", username => Username}),
Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())), Version = iolist_to_binary(proplists:get_value(version, emqx_sys:info())),
{200, #{token => Token, {200, #{
token => Token,
version => Version, version => Version,
license => #{edition => emqx_release:edition()} license => #{edition => emqx_release:edition()}
}}; }};
@ -216,8 +229,10 @@ login(post, #{body := Params}) ->
{401, ?WRONG_USERNAME_OR_PWD, <<"Auth filed">>} {401, ?WRONG_USERNAME_OR_PWD, <<"Auth filed">>}
end. end.
logout(_, #{body := #{<<"username">> := Username}, logout(_, #{
headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}}) -> body := #{<<"username">> := Username},
headers := #{<<"authorization">> := <<"Bearer ", Token/binary>>}
}) ->
case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of case emqx_dashboard_admin:destroy_token_by_username(Username, Token) of
ok -> ok ->
?SLOG(info, #{msg => "Dashboard logout successfully", username => Username}), ?SLOG(info, #{msg => "Dashboard logout successfully", username => Username}),
@ -229,7 +244,6 @@ logout(_, #{body := #{<<"username">> := Username},
users(get, _Request) -> users(get, _Request) ->
{200, emqx_dashboard_admin:all_users()}; {200, emqx_dashboard_admin:all_users()};
users(post, #{body := Params}) -> users(post, #{body := Params}) ->
Desc = maps:get(<<"description">>, Params, <<"">>), Desc = maps:get(<<"description">>, Params, <<"">>),
Username = maps:get(<<"username">>, Params), Username = maps:get(<<"username">>, Params),
@ -243,8 +257,11 @@ users(post, #{body := Params}) ->
?SLOG(info, #{msg => "Create dashboard success", username => Username}), ?SLOG(info, #{msg => "Create dashboard success", username => Username}),
{200, Result}; {200, Result};
{error, Reason} -> {error, Reason} ->
?SLOG(info, #{msg => "Create dashboard failed", ?SLOG(info, #{
username => Username, reason => Reason}), msg => "Create dashboard failed",
username => Username,
reason => Reason
}),
{400, ?BAD_REQUEST, Reason} {400, ?BAD_REQUEST, Reason}
end end
end. end.
@ -257,7 +274,6 @@ user(put, #{bindings := #{username := Username}, body := Params}) ->
{error, Reason} -> {error, Reason} ->
{404, ?USER_NOT_FOUND, Reason} {404, ?USER_NOT_FOUND, Reason}
end; end;
user(delete, #{bindings := #{username := Username}}) -> user(delete, #{bindings := #{username := Username}}) ->
case Username == emqx_dashboard_admin:default_username() of case Username == emqx_dashboard_admin:default_username() of
true -> true ->

View File

@ -18,8 +18,9 @@
-behaviour(application). -behaviour(application).
-export([ start/2 -export([
, stop/1 start/2,
stop/1
]). ]).
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").
@ -33,7 +34,8 @@ start(_StartType, _StartArgs) ->
{ok, _Result} = emqx_dashboard_admin:add_default_user(), {ok, _Result} = emqx_dashboard_admin:add_default_user(),
ok = emqx_dashboard_config:add_handler(), ok = emqx_dashboard_config:add_handler(),
{ok, Sup}; {ok, Sup};
{error, Reason} -> {error, Reason} {error, Reason} ->
{error, Reason}
end. end.
stop(_State) -> stop(_State) ->

View File

@ -22,8 +22,10 @@
init(Req0, State) -> init(Req0, State) ->
?SLOG(warning, #{msg => "unexpected_api_access", request => Req0}), ?SLOG(warning, #{msg => "unexpected_api_access", request => Req0}),
Req = cowboy_req:reply(404, Req = cowboy_req:reply(
404,
#{<<"content-type">> => <<"application/json">>}, #{<<"content-type">> => <<"application/json">>},
<<"{\"code\": \"API_NOT_EXIST\", \"message\": \"Request Path Not Found\"}">>, <<"{\"code\": \"API_NOT_EXIST\", \"message\": \"Request Path Not Found\"}">>,
Req0), Req0
),
{ok, Req, State}. {ok, Req, State}.

View File

@ -16,9 +16,10 @@
-module(emqx_dashboard_cli). -module(emqx_dashboard_cli).
-export([ load/0 -export([
, admins/1 load/0,
, unload/0 admins/1,
unload/0
]). ]).
load() -> load() ->
@ -26,7 +27,6 @@ load() ->
admins(["add", Username, Password]) -> admins(["add", Username, Password]) ->
admins(["add", Username, Password, ""]); admins(["add", Username, Password, ""]);
admins(["add", Username, Password, Desc]) -> admins(["add", Username, Password, Desc]) ->
case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) of case emqx_dashboard_admin:add_user(bin(Username), bin(Password), bin(Desc)) of
{ok, _} -> {ok, _} ->
@ -34,20 +34,20 @@ admins(["add", Username, Password, Desc]) ->
{error, Reason} -> {error, Reason} ->
emqx_ctl:print("Error: ~p~n", [Reason]) emqx_ctl:print("Error: ~p~n", [Reason])
end; end;
admins(["passwd", Username, Password]) -> admins(["passwd", Username, Password]) ->
Status = emqx_dashboard_admin:change_password(bin(Username), bin(Password)), Status = emqx_dashboard_admin:change_password(bin(Username), bin(Password)),
emqx_ctl:print("~p~n", [Status]); emqx_ctl:print("~p~n", [Status]);
admins(["del", Username]) -> admins(["del", Username]) ->
Status = emqx_dashboard_admin:remove_user(bin(Username)), Status = emqx_dashboard_admin:remove_user(bin(Username)),
emqx_ctl:print("~p~n", [Status]); emqx_ctl:print("~p~n", [Status]);
admins(_) -> admins(_) ->
emqx_ctl:usage( emqx_ctl:usage(
[{"admins add <Username> <Password> <Description>", "Add dashboard user"}, [
{"admins add <Username> <Password> <Description>", "Add dashboard user"},
{"admins passwd <Username> <Password>", "Reset dashboard user password"}, {"admins passwd <Username> <Password>", "Reset dashboard user password"},
{"admins del <Username>", "Delete dashboard user" }]). {"admins del <Username>", "Delete dashboard user"}
]
).
unload() -> unload() ->
emqx_ctl:unregister_command(admins). emqx_ctl:unregister_command(admins).

View File

@ -18,11 +18,12 @@
-include_lib("emqx/include/http_api.hrl"). -include_lib("emqx/include/http_api.hrl").
-export([ all/0 -export([
, list/0 all/0,
, look_up/1 list/0,
, description/1 look_up/1,
, format/1 description/1,
format/1
]). ]).
all() -> all() ->

View File

@ -21,15 +21,17 @@
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-export([ api_spec/0 -export([
, fields/1 api_spec/0,
, paths/0 fields/1,
, schema/1 paths/0,
, namespace/0 schema/1,
namespace/0
]). ]).
-export([ error_codes/2 -export([
, error_code/2 error_codes/2,
error_code/2
]). ]).
namespace() -> "dashboard". namespace() -> "dashboard".
@ -38,8 +40,9 @@ 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() ->
[ "/error_codes" [
, "/error_codes/:code" "/error_codes",
"/error_codes/:code"
]. ].
schema("/error_codes") -> schema("/error_codes") ->
@ -60,10 +63,12 @@ schema("/error_codes/:code") ->
security => [], security => [],
description => <<"API Error Codes">>, description => <<"API Error Codes">>,
parameters => [ parameters => [
{code, hoconsc:mk(hoconsc:enum(emqx_dashboard_error_code:all()), #{ {code,
hoconsc:mk(hoconsc:enum(emqx_dashboard_error_code:all()), #{
desc => <<"API Error Codes">>, desc => <<"API Error Codes">>,
in => path, in => path,
example => hd(emqx_dashboard_error_code:all())})} example => hd(emqx_dashboard_error_code:all())
})}
], ],
responses => #{ responses => #{
200 => hoconsc:ref(?MODULE, error_code) 200 => hoconsc:ref(?MODULE, error_code)
@ -77,7 +82,6 @@ fields(error_code) ->
{description, hoconsc:mk(string(), #{desc => <<"Description">>})} {description, hoconsc:mk(string(), #{desc => <<"Description">>})}
]. ].
error_codes(_, _) -> error_codes(_, _) ->
{200, emqx_dashboard_error_code:list()}. {200, emqx_dashboard_error_code:list()}.

View File

@ -11,23 +11,26 @@
-export([api_spec/0]). -export([api_spec/0]).
-export([ paths/0 -export([
, schema/1 paths/0,
, fields/1 schema/1,
fields/1
]). ]).
-export([ monitor/2 -export([
, monitor_current/2 monitor/2,
monitor_current/2
]). ]).
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() ->
[ "/monitor" [
, "/monitor/nodes/:node" "/monitor",
, "/monitor_current" "/monitor/nodes/:node",
, "/monitor_current/nodes/:node" "/monitor_current",
"/monitor_current/nodes/:node"
]. ].
schema("/monitor") -> schema("/monitor") ->
@ -43,7 +46,6 @@ schema("/monitor") ->
} }
} }
}; };
schema("/monitor/nodes/:node") -> schema("/monitor/nodes/:node") ->
#{ #{
'operationId' => monitor, 'operationId' => monitor,
@ -57,7 +59,6 @@ schema("/monitor/nodes/:node") ->
} }
} }
}; };
schema("/monitor_current") -> schema("/monitor_current") ->
#{ #{
'operationId' => monitor_current, 'operationId' => monitor_current,
@ -69,7 +70,6 @@ schema("/monitor_current") ->
} }
} }
}; };
schema("/monitor_current/nodes/:node") -> schema("/monitor_current/nodes/:node") ->
#{ #{
'operationId' => monitor_current, 'operationId' => monitor_current,
@ -102,17 +102,19 @@ parameter_node() ->
}, },
{node, hoconsc:mk(binary(), Info)}. {node, hoconsc:mk(binary(), Info)}.
fields(sampler) -> fields(sampler) ->
Samplers = Samplers =
[{SamplerName, hoconsc:mk(integer(), #{desc => swagger_desc(SamplerName)})} [
|| SamplerName <- ?SAMPLER_LIST], {SamplerName, hoconsc:mk(integer(), #{desc => swagger_desc(SamplerName)})}
|| SamplerName <- ?SAMPLER_LIST
],
[{time_stamp, hoconsc:mk(non_neg_integer(), #{desc => <<"Timestamp">>})} | Samplers]; [{time_stamp, hoconsc:mk(non_neg_integer(), #{desc => <<"Timestamp">>})} | Samplers];
fields(sampler_current) -> fields(sampler_current) ->
Names = maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST, Names = maps:values(?DELTA_SAMPLER_RATE_MAP) ++ ?GAUGE_SAMPLER_LIST,
[{SamplerName, hoconsc:mk(integer(), #{desc => swagger_desc(SamplerName)})} [
|| SamplerName <- Names]. {SamplerName, hoconsc:mk(integer(), #{desc => swagger_desc(SamplerName)})}
|| SamplerName <- Names
].
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% API %% API
@ -141,26 +143,39 @@ monitor_current(get, #{bindings := Bindings}) ->
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% Internal %% Internal
swagger_desc(received) -> swagger_desc_format("Received messages "); swagger_desc(received) ->
swagger_desc(received_bytes) -> swagger_desc_format("Received bytes "); swagger_desc_format("Received messages ");
swagger_desc(sent) -> swagger_desc_format("Sent messages "); swagger_desc(received_bytes) ->
swagger_desc(sent_bytes) -> swagger_desc_format("Sent bytes "); swagger_desc_format("Received bytes ");
swagger_desc(dropped) -> swagger_desc_format("Dropped messages "); swagger_desc(sent) ->
swagger_desc_format("Sent messages ");
swagger_desc(sent_bytes) ->
swagger_desc_format("Sent bytes ");
swagger_desc(dropped) ->
swagger_desc_format("Dropped messages ");
swagger_desc(subscriptions) -> swagger_desc(subscriptions) ->
<<"Subscriptions at the time of sampling." <<
" Can only represent the approximate state">>; "Subscriptions at the time of sampling."
" Can only represent the approximate state"
>>;
swagger_desc(topics) -> swagger_desc(topics) ->
<<"Count topics at the time of sampling." <<
" Can only represent the approximate state">>; "Count topics at the time of sampling."
" Can only represent the approximate state"
>>;
swagger_desc(connections) -> swagger_desc(connections) ->
<<"Connections at the time of sampling." <<
" Can only represent the approximate state">>; "Connections at the time of sampling."
" Can only represent the approximate state"
swagger_desc(received_msg_rate) -> swagger_desc_format("Dropped messages ", per); >>;
swagger_desc(received_msg_rate) ->
swagger_desc_format("Dropped messages ", per);
%swagger_desc(received_bytes_rate) -> swagger_desc_format("Received bytes ", per); %swagger_desc(received_bytes_rate) -> swagger_desc_format("Received bytes ", per);
swagger_desc(sent_msg_rate) -> swagger_desc_format("Sent messages ", per); swagger_desc(sent_msg_rate) ->
swagger_desc_format("Sent messages ", per);
%swagger_desc(sent_bytes_rate) -> swagger_desc_format("Sent bytes ", per); %swagger_desc(sent_bytes_rate) -> swagger_desc_format("Sent bytes ", per);
swagger_desc(dropped_msg_rate) -> swagger_desc_format("Dropped messages ", per). swagger_desc(dropped_msg_rate) ->
swagger_desc_format("Dropped messages ", per).
swagger_desc_format(Format) -> swagger_desc_format(Format) ->
swagger_desc_format(Format, last). swagger_desc_format(Format, last).

View File

@ -28,8 +28,8 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
{ok, {{one_for_one, 10, 100}, {ok,
[ {{one_for_one, 10, 100}, [
?CHILD(emqx_dashboard_token), ?CHILD(emqx_dashboard_token),
?CHILD(emqx_dashboard_monitor), ?CHILD(emqx_dashboard_monitor),
?CHILD(emqx_dashboard_config) ?CHILD(emqx_dashboard_config)

View File

@ -407,7 +407,8 @@ trans_description(Spec, Hocon) ->
Struct -> to_bin(Struct) Struct -> to_bin(Struct)
end, end,
case Desc of case Desc of
undefined -> Spec; undefined ->
Spec;
Desc -> Desc ->
Desc1 = binary:replace(Desc, [<<"</br>\n">>, <<"\n">>], <<"</br>">>, [global]), Desc1 = binary:replace(Desc, [<<"</br>\n">>, <<"\n">>], <<"</br>">>, [global]),
Spec#{description => Desc1} Spec#{description => Desc1}

View File

@ -18,11 +18,12 @@
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").
-export([ sign/2 -export([
, verify/1 sign/2,
, lookup/1 verify/1,
, destroy/1 lookup/1,
, destroy_by_username/1 destroy/1,
destroy_by_username/1
]). ]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
@ -42,26 +43,27 @@
-export([start_link/0, salt/0]). -export([start_link/0, salt/0]).
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
code_change/3
]). ]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% jwt function %% jwt function
-spec(sign(Username :: binary(), Password :: binary()) -> -spec sign(Username :: binary(), Password :: binary()) ->
{ok, Token :: binary()} | {error, Reason :: term()}). {ok, Token :: binary()} | {error, Reason :: term()}.
sign(Username, Password) -> sign(Username, Password) ->
do_sign(Username, Password). do_sign(Username, Password).
-spec(verify(Token :: binary()) -> Result :: ok | {error, token_timeout | not_found}). -spec verify(Token :: binary()) -> Result :: ok | {error, token_timeout | not_found}.
verify(Token) -> verify(Token) ->
do_verify(Token). do_verify(Token).
-spec(destroy(KeyOrKeys :: list() | binary() | #?ADMIN_JWT{}) -> ok). -spec destroy(KeyOrKeys :: list() | binary() | #?ADMIN_JWT{}) -> ok.
destroy([]) -> destroy([]) ->
ok; ok;
destroy(JWTorTokenList) when is_list(JWTorTokenList) -> destroy(JWTorTokenList) when is_list(JWTorTokenList) ->
@ -71,12 +73,12 @@ destroy(#?ADMIN_JWT{token = Token}) ->
destroy(Token) when is_binary(Token) -> destroy(Token) when is_binary(Token) ->
do_destroy(Token). do_destroy(Token).
-spec(destroy_by_username(Username :: binary()) -> ok). -spec destroy_by_username(Username :: binary()) -> ok.
destroy_by_username(Username) -> destroy_by_username(Username) ->
do_destroy_by_username(Username). do_destroy_by_username(Username).
%% @doc create 4 bytes salt. %% @doc create 4 bytes salt.
-spec(salt() -> binary()). -spec salt() -> binary().
salt() -> salt() ->
<<X:16/big-unsigned-integer>> = crypto:strong_rand_bytes(2), <<X:16/big-unsigned-integer>> = crypto:strong_rand_bytes(2),
iolist_to_binary(io_lib:format("~4.16.0b", [X])). iolist_to_binary(io_lib:format("~4.16.0b", [X])).
@ -88,8 +90,13 @@ mnesia(boot) ->
{storage, disc_copies}, {storage, disc_copies},
{record_name, ?ADMIN_JWT}, {record_name, ?ADMIN_JWT},
{attributes, record_info(fields, ?ADMIN_JWT)}, {attributes, record_info(fields, ?ADMIN_JWT)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]). {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% jwt apply %% jwt apply
@ -116,9 +123,11 @@ do_verify(Token)->
case ExpTime > erlang:system_time(millisecond) of case ExpTime > erlang:system_time(millisecond) of
true -> true ->
NewJWT = JWT#?ADMIN_JWT{exptime = jwt_expiration_time()}, NewJWT = JWT#?ADMIN_JWT{exptime = jwt_expiration_time()},
{atomic, Res} = mria:transaction(?DASHBOARD_SHARD, {atomic, Res} = mria:transaction(
?DASHBOARD_SHARD,
fun mnesia:write/1, fun mnesia:write/1,
[NewJWT]), [NewJWT]
),
Res; Res;
_ -> _ ->
{error, token_timeout} {error, token_timeout}
@ -137,7 +146,7 @@ do_destroy_by_username(Username) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% jwt internal util function %% jwt internal util function
-spec(lookup(Token :: binary()) -> {ok, #?ADMIN_JWT{}} | {error, not_found}). -spec lookup(Token :: binary()) -> {ok, #?ADMIN_JWT{}} | {error, not_found}.
lookup(Token) -> lookup(Token) ->
Fun = fun() -> mnesia:read(?TAB, Token) end, Fun = fun() -> mnesia:read(?TAB, Token) end,
case mria:ro_transaction(?DASHBOARD_SHARD, Fun) of case mria:ro_transaction(?DASHBOARD_SHARD, Fun) of
@ -210,8 +219,9 @@ timer_clean(Pid) ->
-dialyzer({nowarn_function, clean_expired_jwt/1}). -dialyzer({nowarn_function, clean_expired_jwt/1}).
clean_expired_jwt(Now) -> clean_expired_jwt(Now) ->
Spec = [{#?ADMIN_JWT{exptime = '$1', token = '$2', _ = '_'}, Spec = [{#?ADMIN_JWT{exptime = '$1', token = '$2', _ = '_'}, [{'<', '$1', Now}], ['$2']}],
[{'<', '$1', Now}], ['$2']}], {atomic, JWTList} = mria:ro_transaction(
{atomic, JWTList} = mria:ro_transaction(?DASHBOARD_SHARD, ?DASHBOARD_SHARD,
fun() -> mnesia:select(?TAB, Spec) end), fun() -> mnesia:select(?TAB, Spec) end
),
ok = destroy(JWTList). ok = destroy(JWTList).

View File

@ -18,9 +18,10 @@
-behaviour(emqx_bpapi). -behaviour(emqx_bpapi).
-export([ introduced_in/0 -export([
, do_sample/2 introduced_in/0,
, current_rate/1 do_sample/2,
current_rate/1
]). ]).
-include("emqx_dashboard.hrl"). -include("emqx_dashboard.hrl").

View File

@ -185,4 +185,3 @@ t_clean_token(_) ->
timer:sleep(5), timer:sleep(5),
{error, not_found} = emqx_dashboard_admin:verify_token(Token2), {error, not_found} = emqx_dashboard_admin:verify_token(Token2),
ok. ok.

View File

@ -158,7 +158,8 @@ banned(post, #{body := Body}) ->
{400, 'BAD_REQUEST', list_to_binary(Reason)}; {400, 'BAD_REQUEST', list_to_binary(Reason)};
Ban -> Ban ->
case emqx_banned:create(Ban) of case emqx_banned:create(Ban) of
{ok, Banned} -> {200, format(Banned)}; {ok, Banned} ->
{200, format(Banned)};
{error, {already_exist, Old}} -> {error, {already_exist, Old}} ->
OldBannedFormat = emqx_json:encode(format(Old)), OldBannedFormat = emqx_json:encode(format(Old)),
{400, 'ALREADY_EXISTS', OldBannedFormat} {400, 'ALREADY_EXISTS', OldBannedFormat}

View File

@ -33,3 +33,5 @@ b168102615e574df15ec6a91304747b4637a9171
b4451823350ec46126c49ca915b4b169dd4cf49e b4451823350ec46126c49ca915b4b169dd4cf49e
# reformat apps/emqx_auto_subscribe and apps/emqx_conf # reformat apps/emqx_auto_subscribe and apps/emqx_conf
a4feb3e6e95c18cb531416112e57520c5ba00d40 a4feb3e6e95c18cb531416112e57520c5ba00d40
# reformat apps/emqx_dashboard
fda246814b38ced2d229c42307c447970b09f3ab

View File

@ -17,6 +17,7 @@ APPS+=( 'apps/emqx_management')
APPS+=( 'apps/emqx_psk') APPS+=( 'apps/emqx_psk')
APPS+=( 'apps/emqx_plugin_libs' 'apps/emqx_machine' 'apps/emqx_statsd' ) APPS+=( 'apps/emqx_plugin_libs' 'apps/emqx_machine' 'apps/emqx_statsd' )
APPS+=( 'apps/emqx_auto_subscribe' 'apps/emqx_conf') APPS+=( 'apps/emqx_auto_subscribe' 'apps/emqx_conf')
APPS+=( 'apps/emqx_dashboard')
for app in "${APPS[@]}"; do for app in "${APPS[@]}"; do
echo "$app ..." echo "$app ..."