emqx/apps/emqx_dashboard/src/emqx_dashboard_schema.erl

309 lines
8.7 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2024 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_schema).
-include_lib("hocon/include/hoconsc.hrl").
-export([
roots/0,
fields/1,
namespace/0,
desc/1,
https_converter/2
]).
namespace() -> dashboard.
roots() -> ["dashboard"].
fields("dashboard") ->
[
{listeners,
?HOCON(
?R_REF("listeners"),
#{desc => ?DESC(listeners)}
)},
{default_username, fun default_username/1},
{default_password, fun default_password/1},
{sample_interval,
?HOCON(
emqx_schema:timeout_duration_s(),
#{
default => <<"10s">>,
desc => ?DESC(sample_interval),
importance => ?IMPORTANCE_HIDDEN,
validator => fun validate_sample_interval/1
}
)},
{token_expired_time,
?HOCON(
emqx_schema:duration(),
#{
default => <<"60m">>,
desc => ?DESC(token_expired_time)
}
)},
{cors, fun cors/1},
{swagger_support, fun swagger_support/1},
{i18n_lang, fun i18n_lang/1},
{bootstrap_users_file,
?HOCON(
binary(),
#{
desc => ?DESC(bootstrap_users_file),
required => false,
default => <<>>,
deprecated => {since, "5.1.0"},
importance => ?IMPORTANCE_HIDDEN
}
)}
] ++ sso_fields();
fields("listeners") ->
[
{"http",
?HOCON(
?R_REF("http"),
#{
desc => "TCP listeners",
required => {false, recursively}
}
)},
{"https",
?HOCON(
?R_REF("https"),
#{
desc => "SSL listeners",
required => {false, recursively},
converter => fun ?MODULE:https_converter/2
}
)}
];
fields("http") ->
[
enable(true),
bind(18083)
| common_listener_fields()
];
fields("https") ->
[
enable(false),
bind(18084),
ssl_options()
| common_listener_fields()
];
fields("ssl_options") ->
server_ssl_options().
ssl_options() ->
{"ssl_options",
?HOCON(
?R_REF("ssl_options"),
#{
required => true,
desc => ?DESC(ssl_options),
importance => ?IMPORTANCE_HIGH
}
)}.
server_ssl_options() ->
emqx_schema:server_ssl_opts_schema(#{}, true).
common_listener_fields() ->
[
{"num_acceptors",
?HOCON(
integer(),
#{
default => erlang:system_info(schedulers_online),
desc => ?DESC(num_acceptors),
importance => ?IMPORTANCE_MEDIUM
}
)},
{"max_connections",
?HOCON(
integer(),
#{
default => 512,
desc => ?DESC(max_connections),
importance => ?IMPORTANCE_HIGH
}
)},
{"backlog",
?HOCON(
integer(),
#{
default => 1024,
desc => ?DESC(backlog),
importance => ?IMPORTANCE_LOW
}
)},
{"send_timeout",
?HOCON(
emqx_schema:duration(),
#{
default => <<"10s">>,
desc => ?DESC(send_timeout),
importance => ?IMPORTANCE_LOW
}
)},
{"inet6",
?HOCON(
boolean(),
#{
default => false,
desc => ?DESC(inet6),
importance => ?IMPORTANCE_LOW
}
)},
{"ipv6_v6only",
?HOCON(
boolean(),
#{
default => false,
desc => ?DESC(ipv6_v6only),
importance => ?IMPORTANCE_LOW
}
)},
{"proxy_header",
?HOCON(
boolean(),
#{
desc => ?DESC(proxy_header),
default => false,
importance => ?IMPORTANCE_MEDIUM
}
)}
].
enable(Bool) ->
{"enable",
?HOCON(
boolean(),
#{
default => Bool,
required => false,
%% deprecated because we use port number =:= 0 to disable
deprecated => {since, "5.1.0"},
importance => ?IMPORTANCE_HIDDEN,
desc => ?DESC(listener_enable)
}
)}.
bind(Port) ->
{"bind",
?HOCON(
emqx_schema:ip_port(),
#{
default => 0,
required => false,
example => "0.0.0.0:" ++ integer_to_list(Port),
importance => ?IMPORTANCE_HIGH,
desc => ?DESC(bind)
}
)}.
desc("dashboard") ->
?DESC(desc_dashboard);
desc("listeners") ->
?DESC(desc_listeners);
desc("http") ->
?DESC(desc_http);
desc("https") ->
?DESC(desc_https);
desc("ssl_options") ->
?DESC(ssl_options);
desc(_) ->
undefined.
default_username(type) -> binary();
default_username(default) -> <<"admin">>;
default_username(required) -> true;
default_username(desc) -> ?DESC(default_username);
default_username('readOnly') -> true;
%% username is hidden but password is not,
%% this is because we want to force changing 'admin' user's password.
%% instead of suggesting to create a new user --- which could be
%% more prone to leaving behind 'admin' user's password unchanged without detection.
default_username(importance) -> ?IMPORTANCE_HIDDEN;
default_username(_) -> undefined.
default_password(type) -> binary();
default_password(default) -> <<"public">>;
default_password(required) -> true;
default_password('readOnly') -> true;
default_password(sensitive) -> true;
default_password(converter) -> fun emqx_schema:password_converter/2;
default_password(desc) -> ?DESC(default_password);
default_password(importance) -> ?IMPORTANCE_LOW;
default_password(_) -> undefined.
cors(type) -> boolean();
cors(default) -> false;
cors(required) -> false;
cors(desc) -> ?DESC(cors);
cors(_) -> undefined.
swagger_support(type) -> boolean();
swagger_support(default) -> true;
swagger_support(desc) -> ?DESC(swagger_support);
swagger_support(_) -> undefined.
%% TODO: change it to string type
%% It will be up to the dashboard package which languages to support
i18n_lang(type) -> ?ENUM([en, zh]);
i18n_lang(default) -> en;
i18n_lang('readOnly') -> true;
i18n_lang(desc) -> ?DESC(i18n_lang);
i18n_lang(importance) -> ?IMPORTANCE_HIDDEN;
i18n_lang(_) -> undefined.
validate_sample_interval(Second) ->
case Second >= 1 andalso Second =< 60 andalso (60 rem Second =:= 0) of
true ->
ok;
false ->
Msg = "must be between 1 and 60 and be a divisor of 60.",
{error, Msg}
end.
https_converter(undefined, _Opts) ->
%% no https listener configured
undefined;
https_converter(Conf, Opts) ->
convert_ssl_layout(Conf, Opts).
convert_ssl_layout(Conf = #{<<"ssl_options">> := _}, _Opts) ->
Conf;
convert_ssl_layout(Conf = #{}, _Opts) ->
Keys = lists:map(fun({K, _}) -> list_to_binary(K) end, server_ssl_options()),
SslOpts = maps:with(Keys, Conf),
Conf1 = maps:without(Keys, Conf),
Conf1#{<<"ssl_options">> => SslOpts}.
-if(?EMQX_RELEASE_EDITION == ee).
sso_fields() ->
[
{sso,
?HOCON(
?R_REF(emqx_dashboard_sso_schema, sso),
#{required => {false, recursively}}
)}
].
-else.
sso_fields() ->
[].
-endif.