24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-02 21:17:12 +02:00
xmpp.chapril.org-ejabberd/src/ejabberd_web_admin.erl
Paweł Chmielowski 8d571adca8 Verify http host in web admin only if authentication is missing host
This should allow access to web admin through ip address or just served
from domain not defined in ejabberd hosts
2019-10-09 10:31:02 +02:00

1893 lines
62 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : ejabberd_web_admin.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Administration web interface
%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
%%%% definitions
-module(ejabberd_web_admin).
-author('alexey@process-one.net').
-export([process/2, list_users/4,
list_users_in_diapason/4, pretty_print_xml/1,
term_to_id/1]).
-include("logger.hrl").
-include("xmpp.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("translate.hrl").
-define(INPUTATTRS(Type, Name, Value, Attrs),
?XA(<<"input">>,
(Attrs ++
[{<<"type">>, Type}, {<<"name">>, Name},
{<<"value">>, Value}]))).
%%%==================================
%%%% get_acl_access
%% @spec (Path::[string()], Method) -> {HostOfRule, [AccessRule]}
%% where Method = 'GET' | 'POST'
%% All accounts can access those URLs
get_acl_rule([], _) -> {<<"localhost">>, [all]};
get_acl_rule([<<"style.css">>], _) ->
{<<"localhost">>, [all]};
get_acl_rule([<<"logo.png">>], _) ->
{<<"localhost">>, [all]};
get_acl_rule([<<"logo-fill.png">>], _) ->
{<<"localhost">>, [all]};
get_acl_rule([<<"favicon.ico">>], _) ->
{<<"localhost">>, [all]};
get_acl_rule([<<"additions.js">>], _) ->
{<<"localhost">>, [all]};
%% This page only displays vhosts that the user is admin:
get_acl_rule([<<"vhosts">>], _) ->
{<<"localhost">>, [all]};
%% The pages of a vhost are only accessible if the user is admin of that vhost:
get_acl_rule([<<"server">>, VHost | _RPath], Method)
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{VHost, [configure, webadmin_view]};
get_acl_rule([<<"server">>, VHost | _RPath], 'POST') ->
{VHost, [configure]};
%% Default rule: only global admins can access any other random page
get_acl_rule(_RPath, Method)
when Method =:= 'GET' orelse Method =:= 'HEAD' ->
{global, [configure, webadmin_view]};
get_acl_rule(_RPath, 'POST') ->
{global, [configure]}.
%%%==================================
%%%% Menu Items Access
get_jid(Auth, HostHTTP, Method) ->
case get_auth_admin(Auth, HostHTTP, [], Method) of
{ok, {User, Server}} ->
jid:make(User, Server);
{unauthorized, Error} ->
?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]),
throw({unauthorized, Auth})
end.
get_menu_items(global, cluster, Lang, JID, Level) ->
{_Base, _, Items} = make_server_menu([], [], Lang, JID, Level),
lists:map(fun ({URI, Name}) ->
{<<URI/binary, "/">>, Name};
({URI, Name, _SubMenu}) ->
{<<URI/binary, "/">>, Name}
end,
Items);
get_menu_items(Host, cluster, Lang, JID, Level) ->
{_Base, _, Items} = make_host_menu(Host, [], Lang, JID, Level),
lists:map(fun ({URI, Name}) ->
{<<URI/binary, "/">>, Name};
({URI, Name, _SubMenu}) ->
{<<URI/binary, "/">>, Name}
end,
Items).
%% get_menu_items(Host, Node, Lang, JID) ->
%% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
%% lists:map(
%% fun({URI, Name}) ->
%% {Base++URI++"/", Name};
%% ({URI, Name, _SubMenu}) ->
%% {Base++URI++"/", Name}
%% end,
%% Items
%% ).
is_allowed_path(BasePath, {Path, _}, JID) ->
is_allowed_path(BasePath ++ [Path], JID);
is_allowed_path(BasePath, {Path, _, _}, JID) ->
is_allowed_path(BasePath ++ [Path], JID).
is_allowed_path([<<"admin">> | Path], JID) ->
is_allowed_path(Path, JID);
is_allowed_path(Path, JID) ->
{HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'),
any_rules_allowed(HostOfRule, AccessRule, JID).
%% @spec(Path) -> URL
%% where Path = [string()]
%% URL = string()
%% Convert ["admin", "user", "tom"] -> "/admin/user/tom/"
%%path_to_url(Path) ->
%% "/" ++ string:join(Path, "/") ++ "/".
%% @spec(URL) -> Path
%% where Path = [string()]
%% URL = string()
%% Convert "admin/user/tom" -> ["admin", "user", "tom"]
url_to_path(URL) -> str:tokens(URL, <<"/">>).
%%%==================================
%%%% process/2
process([<<"server">>, SHost | RPath] = Path,
#request{auth = Auth, lang = Lang, host = HostHTTP,
method = Method} =
Request) ->
Host = jid:nameprep(SHost),
case ejabberd_router:is_my_host(Host) of
true ->
case get_auth_admin(Auth, HostHTTP, Path, Method) of
{ok, {User, Server}} ->
AJID = get_jid(Auth, HostHTTP, Method),
process_admin(Host,
Request#request{path = RPath,
us = {User, Server}},
AJID);
{unauthorized, <<"no-auth-provided">>} ->
{401,
[{<<"WWW-Authenticate">>,
<<"basic realm=\"ejabberd\"">>}],
ejabberd_web:make_xhtml([?XCT(<<"h1">>,
?T("Unauthorized"))])};
{unauthorized, Error} ->
{BadUser, _BadPass} = Auth,
{IPT, _Port} = Request#request.ip,
IPS = ejabberd_config:may_hide_data(misc:ip_to_list(IPT)),
?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
[BadUser, IPS, Error]),
{401,
[{<<"WWW-Authenticate">>,
<<"basic realm=\"auth error, retry login "
"to ejabberd\"">>}],
ejabberd_web:make_xhtml([?XCT(<<"h1">>,
?T("Unauthorized"))])}
end;
false -> ejabberd_web:error(not_found)
end;
process(RPath,
#request{auth = Auth, lang = Lang, host = HostHTTP,
method = Method} =
Request) ->
case get_auth_admin(Auth, HostHTTP, RPath, Method) of
{ok, {User, Server}} ->
AJID = get_jid(Auth, HostHTTP, Method),
process_admin(global,
Request#request{path = RPath,
us = {User, Server}},
AJID);
{unauthorized, <<"no-auth-provided">>} ->
{401,
[{<<"WWW-Authenticate">>,
<<"basic realm=\"ejabberd\"">>}],
ejabberd_web:make_xhtml([?XCT(<<"h1">>,
?T("Unauthorized"))])};
{unauthorized, Error} ->
{BadUser, _BadPass} = Auth,
{IPT, _Port} = Request#request.ip,
IPS = ejabberd_config:may_hide_data(misc:ip_to_list(IPT)),
?WARNING_MSG("Access of ~p from ~p failed with error: ~p",
[BadUser, IPS, Error]),
{401,
[{<<"WWW-Authenticate">>,
<<"basic realm=\"auth error, retry login "
"to ejabberd\"">>}],
ejabberd_web:make_xhtml([?XCT(<<"h1">>,
?T("Unauthorized"))])}
end.
get_auth_admin(Auth, HostHTTP, RPath, Method) ->
case Auth of
{SJID, Pass} ->
{HostOfRule, AccessRule} = get_acl_rule(RPath, Method),
try jid:decode(SJID) of
#jid{user = <<"">>, server = User} ->
case ejabberd_router:is_my_host(HostHTTP) of
true ->
get_auth_account(HostOfRule, AccessRule, User, HostHTTP,
Pass);
_ ->
{unauthorized, <<"missing-server">>}
end;
#jid{user = User, server = Server} ->
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass)
catch _:{bad_jid, _} ->
{unauthorized, <<"badformed-jid">>}
end;
invalid -> {unauthorized, <<"no-auth-provided">>};
undefined -> {unauthorized, <<"no-auth-provided">>}
end.
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass) ->
case lists:member(Server, ejabberd_config:get_option(hosts)) of
true -> get_auth_account2(HostOfRule, AccessRule, User, Server, Pass);
false -> {unauthorized, <<"inexistent-host">>}
end.
get_auth_account2(HostOfRule, AccessRule, User, Server,
Pass) ->
case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case any_rules_allowed(HostOfRule, AccessRule,
jid:make(User, Server))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
end;
false ->
case ejabberd_auth:user_exists(User, Server) of
true -> {unauthorized, <<"bad-password">>};
false -> {unauthorized, <<"inexistent-account">>}
end
end.
%%%==================================
%%%% make_xhtml
make_xhtml(Els, Host, Lang, JID, Level) ->
make_xhtml(Els, Host, cluster, Lang, JID, Level).
%% @spec (Els, Host, Node, Lang, JID) -> {200, [html], xmlelement()}
%% where Host = global | string()
%% Node = cluster | atom()
%% JID = jid()
make_xhtml(Els, Host, Node, Lang, JID, Level) ->
Base = get_base_path_sum(0, 0, Level),
MenuItems = make_navigation(Host, Node, Lang, JID, Level),
{200, [html],
#xmlel{name = <<"html">>,
attrs =
[{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
{<<"xml:lang">>, Lang}, {<<"lang">>, Lang}]++direction(Lang),
children =
[#xmlel{name = <<"head">>, attrs = [],
children =
[?XCT(<<"title">>, ?T("ejabberd Web Admin")),
#xmlel{name = <<"meta">>,
attrs =
[{<<"http-equiv">>, <<"Content-Type">>},
{<<"content">>,
<<"text/html; charset=utf-8">>}],
children = []},
#xmlel{name = <<"script">>,
attrs =
[{<<"src">>,
<<Base/binary, "additions.js">>},
{<<"type">>, <<"text/javascript">>}],
children = [?C(<<" ">>)]},
#xmlel{name = <<"link">>,
attrs =
[{<<"href">>,
<<Base/binary, "favicon.ico">>},
{<<"type">>, <<"image/x-icon">>},
{<<"rel">>, <<"shortcut icon">>}],
children = []},
#xmlel{name = <<"link">>,
attrs =
[{<<"href">>,
<<Base/binary, "style.css">>},
{<<"type">>, <<"text/css">>},
{<<"rel">>, <<"stylesheet">>}],
children = []}]},
?XE(<<"body">>,
[?XAE(<<"div">>, [{<<"id">>, <<"container">>}],
[?XAE(<<"div">>, [{<<"id">>, <<"header">>}],
[?XE(<<"h1">>,
[?ACT(Base,
<<"ejabberd Web Admin">>)])]),
?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}],
[?XE(<<"ul">>, MenuItems)]),
?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els),
?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}],
[{xmlcdata, <<"">>}])]),
?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}],
[?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}],
[?XE(<<"p">>,
[?AC(<<"https://www.ejabberd.im/">>, <<"ejabberd">>),
?C(<<" (c) 2002-2019 ">>),
?AC(<<"https://www.process-one.net/">>, <<"ProcessOne, leader in messaging and push solutions">>)]
)])])])]}}.
direction(<<"he">>) -> [{<<"dir">>, <<"rtl">>}];
direction(_) -> [].
get_base_path(Host, Node, Level) ->
SumHost = case Host of
global -> 0;
_ -> -2
end,
SumNode = case Node of
cluster -> 0;
_ -> -2
end,
get_base_path_sum(SumHost, SumNode, Level).
get_base_path_sum(SumHost, SumNode, Level) ->
iolist_to_binary(lists:duplicate(Level + SumHost + SumNode, "../")).
%%%==================================
%%%% css & images
additions_js() ->
case misc:read_js("admin.js") of
{ok, JS} -> JS;
{error, _} -> <<>>
end.
css(Host) ->
case misc:read_css("admin.css") of
{ok, CSS} ->
Base = get_base_path(Host, cluster, 0),
re:replace(CSS, <<"@BASE@">>, Base, [{return, binary}]);
{error, _} ->
<<>>
end.
favicon() ->
case misc:read_img("favicon.png") of
{ok, ICO} -> ICO;
{error, _} -> <<>>
end.
logo() ->
case misc:read_img("admin-logo.png") of
{ok, Img} -> Img;
{error, _} -> <<>>
end.
logo_fill() ->
case misc:read_img("admin-logo-fill.png") of
{ok, Img} -> Img;
{error, _} -> <<>>
end.
%%%==================================
%%%% process_admin
process_admin(global, #request{path = [], lang = Lang}, AJID) ->
make_xhtml((?H1GL((translate:translate(Lang, ?T("Administration"))), <<"">>,
<<"Contents">>))
++
[?XE(<<"ul">>,
[?LI([?ACT(MIU, MIN)])
|| {MIU, MIN}
<- get_menu_items(global, cluster, Lang, AJID, 0)])],
global, Lang, AJID, 0);
process_admin(Host, #request{path = [], lang = Lang}, AJID) ->
make_xhtml([?XCT(<<"h1">>, ?T("Administration")),
?XE(<<"ul">>,
[?LI([?ACT(MIU, MIN)])
|| {MIU, MIN}
<- get_menu_items(Host, cluster, Lang, AJID, 2)])],
Host, Lang, AJID, 2);
process_admin(Host, #request{path = [<<"style.css">>]}, _) ->
{200,
[{<<"Content-Type">>, <<"text/css">>}, last_modified(),
cache_control_public()],
css(Host)};
process_admin(_Host, #request{path = [<<"favicon.ico">>]}, _) ->
{200,
[{<<"Content-Type">>, <<"image/x-icon">>},
last_modified(), cache_control_public()],
favicon()};
process_admin(_Host, #request{path = [<<"logo.png">>]}, _) ->
{200,
[{<<"Content-Type">>, <<"image/png">>}, last_modified(),
cache_control_public()],
logo()};
process_admin(_Host, #request{path = [<<"logo-fill.png">>]}, _) ->
{200,
[{<<"Content-Type">>, <<"image/png">>}, last_modified(),
cache_control_public()],
logo_fill()};
process_admin(_Host, #request{path = [<<"additions.js">>]}, _) ->
{200,
[{<<"Content-Type">>, <<"text/javascript">>},
last_modified(), cache_control_public()],
additions_js()};
process_admin(global, #request{path = [<<"vhosts">>], lang = Lang}, AJID) ->
Res = list_vhosts(Lang, AJID),
make_xhtml((?H1GL((translate:translate(Lang, ?T("Virtual Hosts"))),
<<"virtual-hosting">>, ?T("Virtual Hosting")))
++ Res,
global, Lang, AJID, 1);
process_admin(Host, #request{path = [<<"users">>], q = Query,
lang = Lang}, AJID)
when is_binary(Host) ->
Res = list_users(Host, Query, Lang, fun url_func/1),
make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host,
Lang, AJID, 3);
process_admin(Host, #request{path = [<<"users">>, Diap],
lang = Lang}, AJID)
when is_binary(Host) ->
Res = list_users_in_diapason(Host, Diap, Lang,
fun url_func/1),
make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host,
Lang, AJID, 4);
process_admin(Host, #request{path = [<<"online-users">>],
lang = Lang}, AJID)
when is_binary(Host) ->
Res = list_online_users(Host, Lang),
make_xhtml([?XCT(<<"h1">>, ?T("Online Users"))] ++ Res,
Host, Lang, AJID, 3);
process_admin(Host, #request{path = [<<"last-activity">>],
q = Query, lang = Lang}, AJID)
when is_binary(Host) ->
?DEBUG("Query: ~p", [Query]),
Month = case lists:keysearch(<<"period">>, 1, Query) of
{value, {_, Val}} -> Val;
_ -> <<"month">>
end,
Res = case lists:keysearch(<<"ordinary">>, 1, Query) of
{value, {_, _}} ->
list_last_activity(Host, Lang, false, Month);
_ -> list_last_activity(Host, Lang, true, Month)
end,
PageH1 = ?H1GL(translate:translate(Lang, ?T("Users Last Activity")), <<"mod-last">>, <<"mod_last">>),
make_xhtml(PageH1 ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?CT(?T("Period: ")),
?XAE(<<"select">>, [{<<"name">>, <<"period">>}],
(lists:map(fun ({O, V}) ->
Sel = if O == Month ->
[{<<"selected">>,
<<"selected">>}];
true -> []
end,
?XAC(<<"option">>,
(Sel ++
[{<<"value">>, O}]),
V)
end,
[{<<"month">>, translate:translate(Lang, ?T("Last month"))},
{<<"year">>, translate:translate(Lang, ?T("Last year"))},
{<<"all">>,
translate:translate(Lang, ?T("All activity"))}]))),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"ordinary">>,
?T("Show Ordinary Table")),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"integral">>,
?T("Show Integral Table"))])]
++ Res,
Host, Lang, AJID, 3);
process_admin(Host, #request{path = [<<"stats">>], lang = Lang}, AJID) ->
Res = get_stats(Host, Lang),
PageH1 = ?H1GL(translate:translate(Lang, ?T("Statistics")), <<"mod-stats">>, <<"mod_stats">>),
Level = case Host of
global -> 1;
_ -> 3
end,
make_xhtml(PageH1 ++ Res, Host, Lang, AJID, Level);
process_admin(Host, #request{path = [<<"user">>, U],
q = Query, lang = Lang}, AJID) ->
case ejabberd_auth:user_exists(U, Host) of
true ->
Res = user_info(U, Host, Query, Lang),
make_xhtml(Res, Host, Lang, AJID, 4);
false ->
make_xhtml([?XCT(<<"h1">>, ?T("Not Found"))], Host,
Lang, AJID, 4)
end;
process_admin(Host, #request{path = [<<"nodes">>], lang = Lang}, AJID) ->
Res = get_nodes(Lang),
Level = case Host of
global -> 1;
_ -> 3
end,
make_xhtml(Res, Host, Lang, AJID, Level);
process_admin(Host, #request{path = [<<"node">>, SNode | NPath],
q = Query, lang = Lang}, AJID) ->
case search_running_node(SNode) of
false ->
make_xhtml([?XCT(<<"h1">>, ?T("Node not found"))], Host,
Lang, AJID, 2);
Node ->
Res = get_node(Host, Node, NPath, Query, Lang),
Level = case Host of
global -> 2 + length(NPath);
_ -> 4 + length(NPath)
end,
make_xhtml(Res, Host, Node, Lang, AJID, Level)
end;
%%%==================================
%%%% process_admin default case
process_admin(Host, #request{lang = Lang} = Request, AJID) ->
Res = case Host of
global ->
ejabberd_hooks:run_fold(
webadmin_page_main, Host, [], [Request]);
_ ->
ejabberd_hooks:run_fold(
webadmin_page_host, Host, [], [Host, Request])
end,
Level = case Host of
global -> length(Request#request.path);
_ -> 2 + length(Request#request.path)
end,
case Res of
[] ->
setelement(1,
make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang,
AJID, Level),
404);
_ -> make_xhtml(Res, Host, Lang, AJID, Level)
end.
term_to_id(T) -> base64:encode((term_to_binary(T))).
%%%==================================
%%%% list_vhosts
list_vhosts(Lang, JID) ->
Hosts = ejabberd_option:hosts(),
HostsAllowed = lists:filter(fun (Host) ->
any_rules_allowed(Host,
[configure, webadmin_view],
JID)
end,
Hosts),
list_vhosts2(Lang, HostsAllowed).
list_vhosts2(Lang, Hosts) ->
SHosts = lists:sort(Hosts),
[?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Host")),
?XCT(<<"td">>, ?T("Registered Users")),
?XCT(<<"td">>, ?T("Online Users"))])]),
?XE(<<"tbody">>,
(lists:map(fun (Host) ->
OnlineUsers =
length(ejabberd_sm:get_vh_session_list(Host)),
RegisteredUsers =
ejabberd_auth:count_users(Host),
?XE(<<"tr">>,
[?XE(<<"td">>,
[?AC(<<"../server/", Host/binary,
"/">>,
Host)]),
?XC(<<"td">>,
(pretty_string_int(RegisteredUsers))),
?XC(<<"td">>,
(pretty_string_int(OnlineUsers)))])
end,
SHosts)))])].
%%%==================================
%%%% list_users
list_users(Host, Query, Lang, URLFunc) ->
Res = list_users_parse_query(Query, Host),
Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
FUsers = case length(SUsers) of
N when N =< 100 ->
[list_given_users(Host, SUsers, <<"../">>, Lang,
URLFunc)];
N ->
NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1))
+ 1,
M = trunc(N / NParts) + 1,
lists:flatmap(fun (K) ->
L = K + M - 1,
Last = if L < N ->
su_to_list(lists:nth(L,
SUsers));
true ->
su_to_list(lists:last(SUsers))
end,
Name = <<(su_to_list(lists:nth(K,
SUsers)))/binary,
$\s, 226, 128, 148, $\s,
Last/binary>>,
[?AC((URLFunc({user_diapason, K, L})),
Name),
?BR]
end,
lists:seq(1, N, M))
end,
case Res of
%% Parse user creation query and try register:
ok -> [?XREST(?T("Submitted"))];
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
([?XE(<<"table">>,
[?XE(<<"tr">>,
[?XC(<<"td">>, <<(translate:translate(Lang, ?T("User")))/binary, ":">>),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"newusername">>, <<"">>)]),
?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]),
?XE(<<"tr">>,
[?XC(<<"td">>, <<(translate:translate(Lang, ?T("Password")))/binary, ":">>),
?XE(<<"td">>,
[?INPUT(<<"password">>, <<"newuserpassword">>,
<<"">>)]),
?X(<<"td">>)]),
?XE(<<"tr">>,
[?X(<<"td">>),
?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}],
[?INPUTT(<<"submit">>, <<"addnewuser">>,
?T("Add User"))]),
?X(<<"td">>)])]),
?P]
++ FUsers))].
list_users_parse_query(Query, Host) ->
case lists:keysearch(<<"addnewuser">>, 1, Query) of
{value, _} ->
{value, {_, Username}} =
lists:keysearch(<<"newusername">>, 1, Query),
{value, {_, Password}} =
lists:keysearch(<<"newuserpassword">>, 1, Query),
try jid:decode(<<Username/binary, "@",
Host/binary>>)
of
#jid{user = User, server = Server} ->
case ejabberd_auth:try_register(User, Server, Password)
of
{error, _Reason} -> error;
_ -> ok
end
catch _:{bad_jid, _} ->
error
end;
false -> nothing
end.
list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
N1 = binary_to_integer(S1),
N2 = binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
[list_given_users(Host, Sub, <<"../../">>, Lang,
URLFunc)].
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
ModOffline = get_offlinemsg_module(Host),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("User")),
?XCT(<<"td">>, ?T("Offline Messages")),
?XCT(<<"td">>, ?T("Last Activity"))])]),
?XE(<<"tbody">>,
(lists:map(fun (_SU = {Server, User}) ->
US = {User, Server},
QueueLenStr = get_offlinemsg_length(ModOffline,
User,
Server),
FQueueLen = [?AC((URLFunc({users_queue, Prefix,
User, Server})),
QueueLenStr)],
FLast = case
ejabberd_sm:get_user_resources(User,
Server)
of
[] ->
case get_last_info(User, Server) of
not_found -> translate:translate(Lang, ?T("Never"));
{ok, Shift, _Status} ->
TimeStamp = {Shift div
1000000,
Shift rem
1000000,
0},
{{Year, Month, Day},
{Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year,
Month,
Day,
Hour,
Minute,
Second]))
end;
_ -> translate:translate(Lang, ?T("Online"))
end,
?XE(<<"tr">>,
[?XE(<<"td">>,
[?AC((URLFunc({user, Prefix,
misc:url_encode(User),
Server})),
(us_to_list(US)))]),
?XE(<<"td">>, FQueueLen),
?XC(<<"td">>, FLast)])
end,
Users)))]).
get_offlinemsg_length(ModOffline, User, Server) ->
case ModOffline of
none -> <<"disabled">>;
_ ->
pretty_string_int(ModOffline:count_offline_messages(User,Server))
end.
get_offlinemsg_module(Server) ->
case gen_mod:is_loaded(Server, mod_offline) of
true -> mod_offline;
false -> none
end.
get_lastactivity_menuitem_list(Server) ->
case gen_mod:is_loaded(Server, mod_last) of
true ->
case mod_last_opt:db_type(Server) of
mnesia -> [{<<"last-activity">>, ?T("Last Activity")}];
_ -> []
end;
false ->
[]
end.
get_last_info(User, Server) ->
case gen_mod:is_loaded(Server, mod_last) of
true ->
mod_last:get_last_info(User, Server);
false ->
not_found
end.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
su_to_list({Server, User}) ->
jid:encode({User, Server, <<"">>}).
%%%==================================
%%%% get_stats
get_stats(global, Lang) ->
OnlineUsers = ejabberd_sm:connected_users_number(),
RegisteredUsers = lists:foldl(fun (Host, Total) ->
ejabberd_auth:count_users(Host)
+ Total
end,
0, ejabberd_option:hosts()),
OutS2SNumber = ejabberd_s2s:outgoing_s2s_number(),
InS2SNumber = ejabberd_s2s:incoming_s2s_number(),
[?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Registered Users:")),
?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Online Users:")),
?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Outgoing s2s Connections:")),
?XC(<<"td">>, (pretty_string_int(OutS2SNumber)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Incoming s2s Connections:")),
?XC(<<"td">>, (pretty_string_int(InS2SNumber)))])])])];
get_stats(Host, Lang) ->
OnlineUsers =
length(ejabberd_sm:get_vh_session_list(Host)),
RegisteredUsers =
ejabberd_auth:count_users(Host),
[?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Registered Users:")),
?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Online Users:")),
?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])].
list_online_users(Host, _Lang) ->
Users = [{S, U}
|| {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)],
SUsers = lists:usort(Users),
lists:flatmap(fun ({_S, U} = SU) ->
[?AC(<<"../user/",
(misc:url_encode(U))/binary, "/">>,
(su_to_list(SU))),
?BR]
end,
SUsers).
user_info(User, Server, Query, Lang) ->
LServer = jid:nameprep(Server),
US = {jid:nodeprep(User), LServer},
Res = user_parse_query(User, Server, Query),
Resources = ejabberd_sm:get_user_resources(User,
Server),
FResources =
case Resources of
[] -> [?CT(?T("None"))];
_ ->
[?XE(<<"ul">>,
(lists:map(
fun (R) ->
FIP = case
ejabberd_sm:get_user_info(User,
Server,
R)
of
offline -> <<"">>;
Info
when
is_list(Info) ->
Node =
proplists:get_value(node,
Info),
Conn =
proplists:get_value(conn,
Info),
{IP, Port} =
proplists:get_value(ip,
Info),
ConnS = case Conn of
c2s ->
<<"plain">>;
c2s_tls ->
<<"tls">>;
c2s_compressed ->
<<"zlib">>;
c2s_compressed_tls ->
<<"tls+zlib">>;
http_bind ->
<<"http-bind">>;
websocket ->
<<"websocket">>;
_ ->
<<"unknown">>
end,
<<ConnS/binary,
"://",
(misc:ip_to_list(IP))/binary,
":",
(integer_to_binary(Port))/binary,
"#",
(misc:atom_to_binary(Node))/binary>>
end,
case direction(Lang) of
[{_, <<"rtl">>}] -> ?LI([?C((<<FIP/binary, " - ", R/binary>>))]);
_ -> ?LI([?C((<<R/binary, " - ", FIP/binary>>))])
end
end,
lists:sort(Resources))))]
end,
FPassword = [?INPUT(<<"text">>, <<"password">>, <<"">>),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"chpassword">>,
?T("Change Password"))],
UserItems = ejabberd_hooks:run_fold(webadmin_user,
LServer, [], [User, Server, Lang]),
LastActivity = case ejabberd_sm:get_user_resources(User,
Server)
of
[] ->
case get_last_info(User, Server) of
not_found -> translate:translate(Lang, ?T("Never"));
{ok, Shift, _Status} ->
TimeStamp = {Shift div 1000000,
Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day,
Hour, Minute,
Second]))
end;
_ -> translate:translate(Lang, ?T("Online"))
end,
[?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("User ~ts")),
[us_to_list(US)])))]
++
case Res of
ok -> [?XREST(?T("Submitted"))];
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
([?XCT(<<"h3">>, ?T("Connected Resources:"))] ++
FResources ++
[?XCT(<<"h3">>, ?T("Password:"))] ++
FPassword ++
[?XCT(<<"h3">>, ?T("Last Activity"))] ++
[?C(LastActivity)] ++
UserItems ++
[?P,
?INPUTT(<<"submit">>, <<"removeuser">>,
?T("Remove User"))]))].
user_parse_query(User, Server, Query) ->
lists:foldl(fun ({Action, _Value}, Acc)
when Acc == nothing ->
user_parse_query1(Action, User, Server, Query);
({_Action, _Value}, Acc) -> Acc
end,
nothing, Query).
user_parse_query1(<<"password">>, _User, _Server,
_Query) ->
nothing;
user_parse_query1(<<"chpassword">>, User, Server,
Query) ->
case lists:keysearch(<<"password">>, 1, Query) of
{value, {_, Password}} ->
ejabberd_auth:set_password(User, Server, Password), ok;
_ -> error
end;
user_parse_query1(<<"removeuser">>, User, Server,
_Query) ->
ejabberd_auth:remove_user(User, Server), ok;
user_parse_query1(Action, User, Server, Query) ->
case ejabberd_hooks:run_fold(webadmin_user_parse_query,
Server, [], [Action, User, Server, Query])
of
[] -> nothing;
Res -> Res
end.
list_last_activity(Host, Lang, Integral, Period) ->
TimeStamp = erlang:system_time(second),
case Period of
<<"all">> -> TS = 0, Days = infinity;
<<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366;
_ -> TS = TimeStamp - 31 * 86400, Days = 31
end,
case catch mnesia:dirty_select(last_activity,
[{{last_activity, {'_', Host}, '$1', '_'},
[{'>', '$1', TS}],
[{trunc,
{'/', {'-', TimeStamp, '$1'}, 86400}}]}])
of
{'EXIT', _Reason} -> [];
Vals ->
Hist = histogram(Vals, Integral),
if Hist == [] -> [?CT(?T("No Data"))];
true ->
Left = if Days == infinity -> 0;
true -> Days - length(Hist)
end,
Tail = if Integral ->
lists:duplicate(Left, lists:last(Hist));
true -> lists:duplicate(Left, 0)
end,
Max = lists:max(Hist),
[?XAE(<<"ol">>,
[{<<"id">>, <<"lastactivity">>},
{<<"start">>, <<"0">>}],
[?XAE(<<"li">>,
[{<<"style">>,
<<"width:",
(integer_to_binary(trunc(90 * V / Max)))/binary,
"%;">>}],
[{xmlcdata, pretty_string_int(V)}])
|| V <- Hist ++ Tail])]
end
end.
histogram(Values, Integral) ->
histogram(lists:sort(Values), Integral, 0, 0, []).
histogram([H | T], Integral, Current, Count, Hist)
when Current == H ->
histogram(T, Integral, Current, Count + 1, Hist);
histogram([H | _] = Values, Integral, Current, Count,
Hist)
when Current < H ->
if Integral ->
histogram(Values, Integral, Current + 1, Count,
[Count | Hist]);
true ->
histogram(Values, Integral, Current + 1, 0,
[Count | Hist])
end;
histogram([], _Integral, _Current, Count, Hist) ->
if Count > 0 -> lists:reverse([Count | Hist]);
true -> lists:reverse(Hist)
end.
%%%==================================
%%%% get_nodes
get_nodes(Lang) ->
RunningNodes = ejabberd_cluster:get_nodes(),
StoppedNodes = ejabberd_cluster:get_known_nodes()
-- RunningNodes,
FRN = if RunningNodes == [] -> ?CT(?T("None"));
true ->
?XE(<<"ul">>,
(lists:map(fun (N) ->
S = iolist_to_binary(atom_to_list(N)),
?LI([?AC(<<"../node/", S/binary, "/">>,
S)])
end,
lists:sort(RunningNodes))))
end,
FSN = if StoppedNodes == [] -> ?CT(?T("None"));
true ->
?XE(<<"ul">>,
(lists:map(fun (N) ->
S = iolist_to_binary(atom_to_list(N)),
?LI([?C(S)])
end,
lists:sort(StoppedNodes))))
end,
[?XCT(<<"h1">>, ?T("Nodes")),
?XCT(<<"h3">>, ?T("Running Nodes")), FRN,
?XCT(<<"h3">>, ?T("Stopped Nodes")), FSN].
search_running_node(SNode) ->
RunningNodes = ejabberd_cluster:get_nodes(),
search_running_node(SNode, RunningNodes).
search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
case iolist_to_binary(atom_to_list(Node)) of
SNode -> Node;
_ -> search_running_node(SNode, Nodes)
end.
get_node(global, Node, [], Query, Lang) ->
Res = node_parse_query(Node, Query),
Base = get_base_path(global, Node, 2),
MenuItems2 = make_menu_items(global, Node, Base, Lang),
[?XC(<<"h1">>,
(str:format(translate:translate(Lang, ?T("Node ~p")), [Node])))]
++
case Res of
ok -> [?XREST(?T("Submitted"))];
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XE(<<"ul">>,
([?LI([?ACT(<<"db/">>, ?T("Database"))]),
?LI([?ACT(<<"backup/">>, ?T("Backup"))]),
?LI([?ACT(<<"stats/">>, ?T("Statistics"))]),
?LI([?ACT(<<"update/">>, ?T("Update"))])]
++ MenuItems2)),
?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?INPUTT(<<"submit">>, <<"restart">>, ?T("Restart")),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"stop">>, ?T("Stop"))])];
get_node(Host, Node, [], _Query, Lang) ->
Base = get_base_path(Host, Node, 4),
MenuItems2 = make_menu_items(Host, Node, Base, Lang),
[?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("Node ~p")), [Node]))),
?XE(<<"ul">>, MenuItems2)];
get_node(global, Node, [<<"db">>], Query, Lang) ->
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, _Reason} ->
[?XCT(<<"h1">>, ?T("RPC Call Error"))];
Tables ->
ResS = case node_db_parse_query(Node, Tables, Query) of
nothing -> [];
ok -> [?XREST(?T("Submitted"))]
end,
STables = lists:sort(Tables),
Rows = lists:map(fun (Table) ->
STable =
iolist_to_binary(atom_to_list(Table)),
TInfo = case ejabberd_cluster:call(Node, mnesia,
table_info,
[Table, all])
of
{badrpc, _} -> [];
I -> I
end,
{Type, Size, Memory} = case
{lists:keysearch(storage_type,
1,
TInfo),
lists:keysearch(size,
1,
TInfo),
lists:keysearch(memory,
1,
TInfo)}
of
{{value,
{storage_type,
T}},
{value, {size, S}},
{value,
{memory, M}}} ->
{T, S, M};
_ -> {unknown, 0, 0}
end,
?XE(<<"tr">>,
[?XC(<<"td">>, STable),
?XE(<<"td">>,
[db_storage_select(STable, Type,
Lang)]),
?XAC(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(Size))),
?XAC(<<"td">>,
[{<<"class">>, <<"alignright">>}],
(pretty_string_int(Memory)))])
end,
STables),
[?XC(<<"h1">>,
(str:format(translate:translate(Lang, ?T("Database Tables at ~p")),
[Node]))
)]
++
ResS ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?XAE(<<"table">>, [],
[?XE(<<"thead">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Name")),
?XCT(<<"td">>, ?T("Storage Type")),
?XCT(<<"td">>, ?T("Elements")),
?XCT(<<"td">>, ?T("Memory"))])]),
?XE(<<"tbody">>,
(Rows ++
[?XE(<<"tr">>,
[?XAE(<<"td">>,
[{<<"colspan">>, <<"4">>},
{<<"class">>, <<"alignright">>}],
[?INPUTT(<<"submit">>,
<<"submit">>,
?T("Submit"))])])]))])])]
end;
get_node(global, Node, [<<"backup">>], Query, Lang) ->
HomeDirRaw = case {os:getenv("HOME"), os:type()} of
{EnvHome, _} when is_list(EnvHome) -> list_to_binary(EnvHome);
{false, {win32, _Osname}} -> <<"C:/">>;
{false, _} -> <<"/tmp/">>
end,
HomeDir = filename:nativename(HomeDirRaw),
ResS = case node_backup_parse_query(Node, Query) of
nothing -> [];
ok -> [?XREST(?T("Submitted"))];
{error, Error} ->
[?XRES(<<(translate:translate(Lang, ?T("Error")))/binary, ": ",
((str:format("~p", [Error])))/binary>>)]
end,
[?XC(<<"h1">>, (str:format(translate:translate(Lang, ?T("Backup of ~p")), [Node])))]
++
ResS ++
[?XCT(<<"p">>,
?T("Please note that these options will "
"only backup the builtin Mnesia database. "
"If you are using the ODBC module, you "
"also need to backup your SQL database "
"separately.")),
?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Store binary backup:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"storepath">>,
(filename:join(HomeDir,
"ejabberd.backup")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"store">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Restore binary backup immediately:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"restorepath">>,
(filename:join(HomeDir,
"ejabberd.backup")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"restore">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Restore binary backup after next ejabberd "
"restart (requires less memory):")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"fallbackpath">>,
(filename:join(HomeDir,
"ejabberd.backup")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"fallback">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Store plain text backup:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"dumppath">>,
(filename:join(HomeDir,
"ejabberd.dump")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"dump">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Restore plain text backup immediately:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"loadpath">>,
(filename:join(HomeDir,
"ejabberd.dump")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"load">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Import users data from a PIEFXIS file "
"(XEP-0227):")),
?XE(<<"td">>,
[?INPUT(<<"text">>,
<<"import_piefxis_filepath">>,
(filename:join(HomeDir,
"users.xml")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"import_piefxis_file">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Export data of all users in the server "
"to PIEFXIS files (XEP-0227):")),
?XE(<<"td">>,
[?INPUT(<<"text">>,
<<"export_piefxis_dirpath">>,
HomeDir)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"export_piefxis_dir">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XE(<<"td">>,
[?CT(?T("Export data of users in a host to PIEFXIS "
"files (XEP-0227):")),
?C(<<" ">>),
make_select_host(Lang, <<"export_piefxis_host_dirhost">>)]),
?XE(<<"td">>,
[?INPUT(<<"text">>,
<<"export_piefxis_host_dirpath">>,
HomeDir)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>,
<<"export_piefxis_host_dir">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XE(<<"td">>,
[?CT(?T("Export all tables as SQL queries "
"to a file:")),
?C(<<" ">>),
make_select_host(Lang, <<"export_sql_filehost">>)]),
?XE(<<"td">>,
[?INPUT(<<"text">>,
<<"export_sql_filepath">>,
(filename:join(HomeDir,
"db.sql")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"export_sql_file">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Import user data from jabberd14 spool "
"file:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"import_filepath">>,
(filename:join(HomeDir,
"user1.xml")))]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"import_file">>,
?T("OK"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>,
?T("Import users data from jabberd14 spool "
"directory:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"import_dirpath">>,
<<"/var/spool/jabber/">>)]),
?XE(<<"td">>,
[?INPUTT(<<"submit">>, <<"import_dir">>,
?T("OK"))])])])])])];
get_node(global, Node, [<<"stats">>], _Query, Lang) ->
UpTime = ejabberd_cluster:call(Node, erlang, statistics,
[wall_clock]),
UpTimeS = (str:format("~.3f",
[element(1, UpTime) / 1000])),
CPUTime = ejabberd_cluster:call(Node, erlang, statistics, [runtime]),
CPUTimeS = (str:format("~.3f",
[element(1, CPUTime) / 1000])),
OnlineUsers = ejabberd_sm:connected_users_number(),
TransactionsCommitted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_commits]),
TransactionsAborted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_failures]),
TransactionsRestarted = ejabberd_cluster:call(Node, mnesia,
system_info, [transaction_restarts]),
TransactionsLogged = ejabberd_cluster:call(Node, mnesia, system_info,
[transaction_log_writes]),
[?XC(<<"h1">>,
(str:format(translate:translate(Lang, ?T("Statistics of ~p")), [Node]))),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Uptime:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
UpTimeS)]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("CPU Time:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
CPUTimeS)]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Online Users:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(OnlineUsers)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Transactions Committed:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(TransactionsCommitted)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Transactions Aborted:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(TransactionsAborted)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Transactions Restarted:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(TransactionsRestarted)))]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Transactions Logged:")),
?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
(pretty_string_int(TransactionsLogged)))])])])];
get_node(global, Node, [<<"update">>], Query, Lang) ->
ejabberd_cluster:call(Node, code, purge, [ejabberd_update]),
Res = node_update_parse_query(Node, Query),
ejabberd_cluster:call(Node, code, load_file, [ejabberd_update]),
{ok, _Dir, UpdatedBeams, Script, LowLevelScript,
Check} =
ejabberd_cluster:call(Node, ejabberd_update, update_info, []),
Mods = case UpdatedBeams of
[] -> ?CT(?T("None"));
_ ->
BeamsLis = lists:map(fun (Beam) ->
BeamString =
iolist_to_binary(atom_to_list(Beam)),
?LI([?INPUT(<<"checkbox">>,
<<"selected">>,
BeamString),
?C(BeamString)])
end,
UpdatedBeams),
SelectButtons = [?BR,
?INPUTATTRS(<<"button">>, <<"selectall">>,
?T("Select All"),
[{<<"onClick">>,
<<"selectAll()">>}]),
?C(<<" ">>),
?INPUTATTRS(<<"button">>, <<"unselectall">>,
?T("Unselect All"),
[{<<"onClick">>,
<<"unSelectAll()">>}])],
?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}],
(BeamsLis ++ SelectButtons))
end,
FmtScript = (?XC(<<"pre">>,
(str:format("~p", [Script])))),
FmtLowLevelScript = (?XC(<<"pre">>,
(str:format("~p", [LowLevelScript])))),
[?XC(<<"h1">>,
(str:format(translate:translate(Lang, ?T("Update ~p")), [Node])))]
++
case Res of
ok -> [?XREST(?T("Submitted"))];
{error, ErrorText} ->
[?XREST(<<"Error: ", ErrorText/binary>>)];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?XCT(<<"h2">>, ?T("Update plan")),
?XCT(<<"h3">>, ?T("Modified modules")), Mods,
?XCT(<<"h3">>, ?T("Update script")), FmtScript,
?XCT(<<"h3">>, ?T("Low level update script")),
FmtLowLevelScript, ?XCT(<<"h3">>, ?T("Script check")),
?XC(<<"pre">>, (misc:atom_to_binary(Check))),
?BR,
?INPUTT(<<"submit">>, <<"update">>, ?T("Update"))])];
get_node(Host, Node, NPath, Query, Lang) ->
Res = case Host of
global ->
ejabberd_hooks:run_fold(webadmin_page_node, Host, [],
[Node, NPath, Query, Lang]);
_ ->
ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [],
[Host, Node, NPath, Query, Lang])
end,
case Res of
[] -> [?XC(<<"h1">>, <<"Not Found">>)];
_ -> Res
end.
%%%==================================
%%%% node parse
node_parse_query(Node, Query) ->
case lists:keysearch(<<"restart">>, 1, Query) of
{value, _} ->
case ejabberd_cluster:call(Node, init, restart, []) of
{badrpc, _Reason} -> error;
_ -> ok
end;
_ ->
case lists:keysearch(<<"stop">>, 1, Query) of
{value, _} ->
case ejabberd_cluster:call(Node, init, stop, []) of
{badrpc, _Reason} -> error;
_ -> ok
end;
_ -> nothing
end
end.
make_select_host(Lang, Name) ->
?XAE(<<"select">>,
[{<<"name">>, Name}],
(lists:map(fun (Host) ->
?XACT(<<"option">>,
([{<<"value">>, Host}]), Host)
end,
ejabberd_config:get_option(hosts)))).
db_storage_select(ID, Opt, Lang) ->
?XAE(<<"select">>,
[{<<"name">>, <<"table", ID/binary>>}],
(lists:map(fun ({O, Desc}) ->
Sel = if O == Opt ->
[{<<"selected">>, <<"selected">>}];
true -> []
end,
?XACT(<<"option">>,
(Sel ++
[{<<"value">>,
iolist_to_binary(atom_to_list(O))}]),
Desc)
end,
[{ram_copies, ?T("RAM copy")},
{disc_copies, ?T("RAM and disc copy")},
{disc_only_copies, ?T("Disc only copy")},
{unknown, ?T("Remote copy")},
{delete_content, ?T("Delete content")},
{delete_table, ?T("Delete table")}]))).
node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) ->
nothing;
node_db_parse_query(Node, Tables, Query) ->
lists:foreach(fun (Table) ->
STable = iolist_to_binary(atom_to_list(Table)),
case lists:keysearch(<<"table", STable/binary>>, 1,
Query)
of
{value, {_, SType}} ->
Type = case SType of
<<"unknown">> -> unknown;
<<"ram_copies">> -> ram_copies;
<<"disc_copies">> -> disc_copies;
<<"disc_only_copies">> ->
disc_only_copies;
<<"delete_content">> -> delete_content;
<<"delete_table">> -> delete_table;
_ -> false
end,
if Type == false -> ok;
Type == delete_content ->
mnesia:clear_table(Table);
Type == delete_table ->
mnesia:delete_table(Table);
Type == unknown ->
mnesia:del_table_copy(Table, Node);
true ->
case mnesia:add_table_copy(Table, Node,
Type)
of
{aborted, _} ->
mnesia:change_table_copy_type(Table,
Node,
Type);
_ -> ok
end
end;
_ -> ok
end
end,
Tables),
ok.
node_backup_parse_query(_Node, [{nokey, <<>>}]) ->
nothing;
node_backup_parse_query(Node, Query) ->
lists:foldl(fun (Action, nothing) ->
case lists:keysearch(Action, 1, Query) of
{value, _} ->
case lists:keysearch(<<Action/binary, "path">>, 1,
Query)
of
{value, {_, Path}} ->
Res = case Action of
<<"store">> ->
ejabberd_cluster:call(Node, mnesia, backup,
[binary_to_list(Path)]);
<<"restore">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
restore, [Path]);
<<"fallback">> ->
ejabberd_cluster:call(Node, mnesia,
install_fallback,
[binary_to_list(Path)]);
<<"dump">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
dump_to_textfile,
[Path]);
<<"load">> ->
ejabberd_cluster:call(Node, mnesia,
load_textfile,
[binary_to_list(Path)]);
<<"import_piefxis_file">> ->
ejabberd_cluster:call(Node, ejabberd_piefxis,
import_file, [Path]);
<<"export_piefxis_dir">> ->
ejabberd_cluster:call(Node, ejabberd_piefxis,
export_server, [Path]);
<<"export_piefxis_host_dir">> ->
{value, {_, Host}} =
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
ejabberd_cluster:call(Node, ejabberd_piefxis,
export_host,
[Path, Host]);
<<"export_sql_file">> ->
{value, {_, Host}} =
lists:keysearch(<<Action/binary,
"host">>,
1, Query),
ejabberd_cluster:call(Node, ejd2sql,
export, [Host, Path]);
<<"import_file">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
import_file, [Path]);
<<"import_dir">> ->
ejabberd_cluster:call(Node, ejabberd_admin,
import_dir, [Path])
end,
case Res of
{error, Reason} -> {error, Reason};
{badrpc, Reason} -> {badrpc, Reason};
_ -> ok
end;
OtherError -> {error, OtherError}
end;
_ -> nothing
end;
(_Action, Res) -> Res
end,
nothing,
[<<"store">>, <<"restore">>, <<"fallback">>, <<"dump">>,
<<"load">>, <<"import_file">>, <<"import_dir">>,
<<"import_piefxis_file">>, <<"export_piefxis_dir">>,
<<"export_piefxis_host_dir">>, <<"export_sql_file">>]).
node_update_parse_query(Node, Query) ->
case lists:keysearch(<<"update">>, 1, Query) of
{value, _} ->
ModulesToUpdateStrings =
proplists:get_all_values(<<"selected">>, Query),
ModulesToUpdate = [misc:binary_to_atom(M)
|| M <- ModulesToUpdateStrings],
case ejabberd_cluster:call(Node, ejabberd_update, update,
[ModulesToUpdate])
of
{ok, _} -> ok;
{error, Error} ->
?ERROR_MSG("~p~n", [Error]),
{error, (str:format("~p", [Error]))};
{badrpc, Error} ->
?ERROR_MSG("Bad RPC: ~p~n", [Error]),
{error,
<<"Bad RPC: ", ((str:format("~p", [Error])))/binary>>}
end;
_ -> nothing
end.
pretty_print_xml(El) ->
list_to_binary(pretty_print_xml(El, <<"">>)).
pretty_print_xml({xmlcdata, CData}, Prefix) ->
IsBlankCData = lists:all(
fun($\f) -> true;
($\r) -> true;
($\n) -> true;
($\t) -> true;
($\v) -> true;
($ ) -> true;
(_) -> false
end, binary_to_list(CData)),
if IsBlankCData ->
[];
true ->
[Prefix, CData, $\n]
end;
pretty_print_xml(#xmlel{name = Name, attrs = Attrs,
children = Els},
Prefix) ->
[Prefix, $<, Name,
case Attrs of
[] -> [];
[{Attr, Val} | RestAttrs] ->
AttrPrefix = [Prefix,
str:copies(<<" ">>, byte_size(Name) + 2)],
[$\s, Attr, $=, $', fxml:crypt(Val) | [$',
lists:map(fun ({Attr1,
Val1}) ->
[$\n,
AttrPrefix,
Attr1, $=,
$',
fxml:crypt(Val1),
$']
end,
RestAttrs)]]
end,
if Els == [] -> <<"/>\n">>;
true ->
OnlyCData = lists:all(fun ({xmlcdata, _}) -> true;
(#xmlel{}) -> false
end,
Els),
if OnlyCData ->
[$>, fxml:get_cdata(Els), $<, $/, Name, $>, $\n];
true ->
[$>, $\n,
lists:map(fun (E) ->
pretty_print_xml(E, [Prefix, <<" ">>])
end,
Els),
Prefix, $<, $/, Name, $>, $\n]
end
end].
url_func({user_diapason, From, To}) ->
<<(integer_to_binary(From))/binary, "-",
(integer_to_binary(To))/binary, "/">>;
url_func({users_queue, Prefix, User, _Server}) ->
<<Prefix/binary, "user/", User/binary, "/queue/">>;
url_func({user, Prefix, User, _Server}) ->
<<Prefix/binary, "user/", User/binary, "/">>.
last_modified() ->
{<<"Last-Modified">>,
<<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
cache_control_public() ->
{<<"Cache-Control">>, <<"public">>}.
%% Transform 1234567890 into "1,234,567,890"
pretty_string_int(Integer) when is_integer(Integer) ->
pretty_string_int(integer_to_binary(Integer));
pretty_string_int(String) when is_binary(String) ->
{_, Result} = lists:foldl(fun (NewNumber, {3, Result}) ->
{1, <<NewNumber, $,, Result/binary>>};
(NewNumber, {CountAcc, Result}) ->
{CountAcc + 1, <<NewNumber, Result/binary>>}
end,
{0, <<"">>}, lists:reverse(binary_to_list(String))),
Result.
%%%==================================
%%%% navigation menu
%% @spec (Host, Node, Lang, JID::jid()) -> [LI]
make_navigation(Host, Node, Lang, JID, Level) ->
Menu = make_navigation_menu(Host, Node, Lang, JID, Level),
make_menu_items(Lang, Menu).
%% @spec (Host, Node, Lang, JID::jid()) -> Menu
%% where Host = global | string()
%% Node = cluster | string()
%% Lang = string()
%% Menu = {URL, Title} | {URL, Title, [Menu]}
%% URL = string()
%% Title = string()
make_navigation_menu(Host, Node, Lang, JID, Level) ->
HostNodeMenu = make_host_node_menu(Host, Node, Lang,
JID, Level),
HostMenu = make_host_menu(Host, HostNodeMenu, Lang,
JID, Level),
NodeMenu = make_node_menu(Host, Node, Lang, Level),
make_server_menu(HostMenu, NodeMenu, Lang, JID, Level).
%% @spec (Host, Node, Base, Lang) -> [LI]
make_menu_items(global, cluster, Base, Lang) ->
HookItems = get_menu_items_hook(server, Lang),
make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(global, Node, Base, Lang) ->
HookItems = get_menu_items_hook({node, Node}, Lang),
make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, cluster, Base, Lang) ->
HookItems = get_menu_items_hook({host, Host}, Lang),
make_menu_items(Lang, {Base, <<"">>, HookItems});
make_menu_items(Host, Node, Base, Lang) ->
HookItems = get_menu_items_hook({hostnode, Host, Node},
Lang),
make_menu_items(Lang, {Base, <<"">>, HookItems}).
make_host_node_menu(global, _, _Lang, _JID, _Level) ->
{<<"">>, <<"">>, []};
make_host_node_menu(_, cluster, _Lang, _JID, _Level) ->
{<<"">>, <<"">>, []};
make_host_node_menu(Host, Node, Lang, JID, Level) ->
HostNodeBase = get_base_path(Host, Node, Level),
HostNodeFixed = get_menu_items_hook({hostnode, Host, Node}, Lang),
HostNodeBasePath = url_to_path(HostNodeBase),
HostNodeFixed2 = [Tuple
|| Tuple <- HostNodeFixed,
is_allowed_path(HostNodeBasePath, Tuple, JID)],
{HostNodeBase, iolist_to_binary(atom_to_list(Node)),
HostNodeFixed2}.
make_host_menu(global, _HostNodeMenu, _Lang, _JID, _Level) ->
{<<"">>, <<"">>, []};
make_host_menu(Host, HostNodeMenu, Lang, JID, Level) ->
HostBase = get_base_path(Host, cluster, Level),
HostFixed = [{<<"users">>, ?T("Users")},
{<<"online-users">>, ?T("Online Users")}]
++
get_lastactivity_menuitem_list(Host) ++
[{<<"nodes">>, ?T("Nodes"), HostNodeMenu},
{<<"stats">>, ?T("Statistics")}]
++ get_menu_items_hook({host, Host}, Lang),
HostBasePath = url_to_path(HostBase),
HostFixed2 = [Tuple
|| Tuple <- HostFixed,
is_allowed_path(HostBasePath, Tuple, JID)],
{HostBase, Host, HostFixed2}.
make_node_menu(_Host, cluster, _Lang, _Level) ->
{<<"">>, <<"">>, []};
make_node_menu(global, Node, Lang, Level) ->
NodeBase = get_base_path(global, Node, Level),
NodeFixed = [{<<"db">>, ?T("Database")},
{<<"backup">>, ?T("Backup")},
{<<"stats">>, ?T("Statistics")},
{<<"update">>, ?T("Update")}]
++ get_menu_items_hook({node, Node}, Lang),
{NodeBase, iolist_to_binary(atom_to_list(Node)),
NodeFixed};
make_node_menu(_Host, _Node, _Lang, _Level) ->
{<<"">>, <<"">>, []}.
make_server_menu(HostMenu, NodeMenu, Lang, JID, Level) ->
Base = get_base_path(global, cluster, Level),
Fixed = [{<<"vhosts">>, ?T("Virtual Hosts"), HostMenu},
{<<"nodes">>, ?T("Nodes"), NodeMenu},
{<<"stats">>, ?T("Statistics")}]
++ get_menu_items_hook(server, Lang),
BasePath = url_to_path(Base),
Fixed2 = [Tuple
|| Tuple <- Fixed,
is_allowed_path(BasePath, Tuple, JID)],
{Base, <<"">>, Fixed2}.
get_menu_items_hook({hostnode, Host, Node}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host,
[], [Host, Node, Lang]);
get_menu_items_hook({host, Host}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_host, Host, [],
[Host, Lang]);
get_menu_items_hook({node, Node}, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_node, [],
[Node, Lang]);
get_menu_items_hook(server, Lang) ->
ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]).
%% @spec (Lang::string(), Menu) -> [LI]
%% where Menu = {MURI::string(), MName::string(), Items::[Item]}
%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu}
make_menu_items(Lang, Menu) ->
lists:reverse(make_menu_items2(Lang, 1, Menu)).
make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) ->
Res = case MName of
<<"">> -> [];
_ -> [make_menu_item(header, Deep, MURI, MName, Lang)]
end,
make_menu_items2(Lang, Deep, Menu, Res).
make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res;
make_menu_items2(Lang, Deep,
{MURI, MName, [Item | Items]}, Res) ->
Res2 = case Item of
{IURI, IName} ->
[make_menu_item(item, Deep,
<<MURI/binary, IURI/binary, "/">>, IName, Lang)
| Res];
{IURI, IName, SubMenu} ->
ResTemp = [make_menu_item(item, Deep,
<<MURI/binary, IURI/binary, "/">>,
IName, Lang)
| Res],
ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu),
ResSubMenu ++ ResTemp
end,
make_menu_items2(Lang, Deep, {MURI, MName, Items},
Res2).
make_menu_item(header, 1, URI, Name, _Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}],
[?AC(URI, Name)])]);
make_menu_item(header, 2, URI, Name, _Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}],
[?AC(URI, Name)])]);
make_menu_item(header, 3, URI, Name, _Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}],
[?AC(URI, Name)])]);
make_menu_item(item, 1, URI, Name, Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}],
[?ACT(URI, Name)])]);
make_menu_item(item, 2, URI, Name, Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}],
[?ACT(URI, Name)])]);
make_menu_item(item, 3, URI, Name, Lang) ->
?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}],
[?ACT(URI, Name)])]).
any_rules_allowed(Host, Access, Entity) ->
lists:any(
fun(Rule) ->
allow == acl:match_rule(Host, Rule, Entity)
end, Access).
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: