diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index 4e997da9b72..5a2fd18d237 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -32,11 +32,16 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +%% WebAdmin +-export([webadmin_menu_node/3, webadmin_page_node/4]). -include("logger.hrl"). -include("ejabberd_commands.hrl"). +-include("ejabberd_http.hrl"). +-include("ejabberd_web_admin.hrl"). -include_lib("public_key/include/public_key.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). +-include_lib("xmpp/include/xmpp.hrl"). -define(CALL_TIMEOUT, timer:minutes(10)). @@ -108,6 +113,8 @@ init([]) -> ejabberd_hooks:add(config_reloaded, ?MODULE, register_certfiles, 40), ejabberd_hooks:add(ejabberd_started, ?MODULE, ejabberd_started, 110), ejabberd_hooks:add(config_reloaded, ?MODULE, ejabberd_started, 110), + ejabberd_hooks:add(webadmin_menu_node, ?MODULE, webadmin_menu_node, 110), + ejabberd_hooks:add(webadmin_page_node, ?MODULE, webadmin_page_node, 110), ejabberd_commands:register_commands(get_commands_spec()), register_certfiles(), {ok, #state{}}. @@ -153,6 +160,8 @@ terminate(_Reason, _State) -> ejabberd_hooks:delete(config_reloaded, ?MODULE, register_certfiles, 40), ejabberd_hooks:delete(ejabberd_started, ?MODULE, ejabberd_started, 110), ejabberd_hooks:delete(config_reloaded, ?MODULE, ejabberd_started, 110), + ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, webadmin_menu_node, 110), + ejabberd_hooks:delete(webadmin_page_node, ?MODULE, webadmin_page_node, 110), ejabberd_commands:unregister_commands(get_commands_spec()). code_change(_OldVsn, State, _Extra) -> @@ -547,6 +556,21 @@ list_certificates() -> {Domain, Path, sets:is_element(E, Used)} end, Known)). +%%%=================================================================== +%%% WebAdmin +%%%=================================================================== + +webadmin_menu_node(Acc, _Node, _Lang) -> + Acc ++ [{<<"acme">>, <<"ACME">>}]. + +webadmin_page_node(_, Node, #request{path = [<<"acme">>]} = R, _Lang) -> + Head = ?H1GLraw(<<"ACME Certificates">>, <<"admin/configuration/basic/#acme">>, <<"ACME">>), + Set = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [request_certificate, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [revoke_certificate, R])], + Get = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [list_certificates, R])], + {stop, Head ++ Get ++ Set}; +webadmin_page_node(Acc, _, _, _) -> Acc. + %%%=================================================================== %%% Other stuff %%%=================================================================== diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 28215491ca6..fead2a678c8 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -39,7 +39,9 @@ dump_config/1, convert_to_yaml/2, %% Cluster - join_cluster/1, leave_cluster/1, list_cluster/0, + join_cluster/1, leave_cluster/1, + list_cluster/0, list_cluster_detailed/0, + get_cluster_node_details3/0, %% Erlang update_list/0, update/1, update/0, %% Accounts @@ -50,7 +52,7 @@ %% Purge DB delete_expired_messages/0, delete_old_messages/1, %% Mnesia - set_master/1, + get_master/0, set_master/1, backup_mnesia/1, restore_mnesia/1, dump_mnesia/1, dump_table/2, load_mnesia/1, mnesia_info/0, mnesia_table_info/1, @@ -61,13 +63,20 @@ clear_cache/0, gc/0, get_commands_spec/0, - delete_old_messages_batch/4, delete_old_messages_status/1, delete_old_messages_abort/1]). + delete_old_messages_batch/4, delete_old_messages_status/1, delete_old_messages_abort/1, + %% WebAdmin + echo/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --include("logger.hrl"). +-export([web_menu_node/3, web_page_node/4]). + +-include_lib("xmpp/include/xmpp.hrl"). -include("ejabberd_commands.hrl"). +-include("ejabberd_http.hrl"). +-include("ejabberd_web_admin.hrl"). +-include("logger.hrl"). -record(state, {}). @@ -77,6 +86,8 @@ start_link() -> init([]) -> process_flag(trap_exit, true), ejabberd_commands:register_commands(get_commands_spec()), + ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50), + ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50), {ok, #state{}}. handle_call(Request, From, State) -> @@ -92,6 +103,8 @@ handle_info(Info, State) -> {noreply, State}. terminate(_Reason, _State) -> + ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50), + ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50), ejabberd_commands:unregister_commands(get_commands_spec()). code_change(_OldVsn, State, _Extra) -> @@ -179,8 +192,9 @@ get_commands_spec() -> desc = "Update the given module", longdesc = "To update all the possible modules, use `all`.", module = ?MODULE, function = update, - args_example = ["mod_vcard"], + args_example = ["all"], args = [{module, string}], + result_example = {ok, <<"Updated modules: mod_configure, mod_vcard">>}, result = {res, restuple}}, #ejabberd_commands{name = register, tags = [accounts], @@ -225,14 +239,12 @@ get_commands_spec() -> #ejabberd_commands{name = join_cluster, tags = [cluster], desc = "Join this node into the cluster handled by Node", - longdesc = "This command works only with ejabberdctl, " - "not mod_http_api or other code that runs inside the " - "same ejabberd node that will be joined.", + note = "improved in 24.xx", module = ?MODULE, function = join_cluster, args_desc = ["Nodename of the node to join"], args_example = [<<"ejabberd1@machine7">>], args = [{node, binary}], - result = {res, rescode}}, + result = {res, restuple}}, #ejabberd_commands{name = leave_cluster, tags = [cluster], desc = "Remove and shutdown Node from the running cluster", longdesc = "This command can be run from any running " @@ -247,11 +259,27 @@ get_commands_spec() -> result = {res, rescode}}, #ejabberd_commands{name = list_cluster, tags = [cluster], - desc = "List nodes that are part of the cluster handled by Node", + desc = "List running nodes that are part of this cluster", module = ?MODULE, function = list_cluster, result_example = [ejabberd1@machine7, ejabberd1@machine8], args = [], result = {nodes, {list, {node, atom}}}}, + #ejabberd_commands{name = list_cluster_detailed, tags = [cluster], + desc = "List nodes (both running and known) and some stats", + note = "added in 24.xx", + module = ?MODULE, function = list_cluster_detailed, + args = [], + result_example = [{'ejabberd@localhost', "true", + "The node ejabberd is started. Status...", + 7, 348, 60, none}], + result = {nodes, {list, {node, {tuple, [{name, atom}, + {running, string}, + {status, string}, + {online_users, integer}, + {processes, integer}, + {uptime_seconds, integer}, + {master_node, atom} + ]}}}}}, #ejabberd_commands{name = import_file, tags = [mnesia], desc = "Import user data from jabberd14 spool file", @@ -377,6 +405,12 @@ get_commands_spec() -> args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"], args = [{host, string}, {file, string}], result = {res, rescode}}, + #ejabberd_commands{name = get_master, tags = [cluster], + desc = "Get master node of the clustered Mnesia tables", + note = "added in 24.xx", + longdesc = "If there is no master, returns `none`.", + module = ?MODULE, function = get_master, + result = {nodename, atom}}, #ejabberd_commands{name = set_master, tags = [cluster], desc = "Set master node of the clustered Mnesia tables", longdesc = "If `nodename` is set to `self`, then this " @@ -472,10 +506,61 @@ get_commands_spec() -> desc = "Generate Unix manpage for current ejabberd version", note = "added in 20.01", module = ejabberd_doc, function = man, - args = [], result = {res, restuple}} + args = [], result = {res, restuple}}, + + #ejabberd_commands{name = webadmin_host_user_queue, tags = [internal], + desc = "Generate WebAdmin offline queue HTML", + module = mod_offline, function = webadmin_host_user_queue, + args = [{user, binary}, {host, binary}, {query, any}, {lang, binary}], + result = {res, any}}, + + #ejabberd_commands{name = webadmin_host_last_activity, tags = [internal], + desc = "Generate WebAdmin Last Activity HTML", + module = ejabberd_web_admin, function = webadmin_host_last_activity, + args = [{host, binary}, {query, any}, {lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_host_srg, tags = [internal], + desc = "Generate WebAdmin Shared Roster Group HTML", + module = mod_shared_roster, function = webadmin_host_srg, + args = [{host, binary}, {query, any}, {lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_host_srg_group, tags = [internal], + desc = "Generate WebAdmin Shared Roster Group HTML for a group", + module = mod_shared_roster, function = webadmin_host_srg_group, + args = [{host, binary}, {group, binary}, {query, any}, {lang, binary}], + result = {res, any}}, + + #ejabberd_commands{name = webadmin_node_contrib, tags = [internal], + desc = "Generate WebAdmin ejabberd-contrib HTML", + module = ext_mod, function = webadmin_node_contrib, + args = [{node, atom}, {query, any}, {lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_node_db, tags = [internal], + desc = "Generate WebAdmin Mnesia database HTML", + module = ejabberd_web_admin, function = webadmin_node_db, + args = [{node, atom}, {query, any}, {lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_node_db_table, tags = [internal], + desc = "Generate WebAdmin Mnesia database HTML for a table", + module = ejabberd_web_admin, function = webadmin_node_db_table, + args = [{node, atom}, {table, binary}, {lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_node_db_table_page, tags = [internal], + desc = "Generate WebAdmin Mnesia database HTML for a table content", + module = ejabberd_web_admin, function = webadmin_node_db_table_page, + args = [{node, atom}, {table, binary}, {lang, binary}, {page, integer}], + result = {res, any}}, + + #ejabberd_commands{name = echo, tags = [internal], + desc = "Return the same sentence that was provided", + module = ?MODULE, function = echo, + args_desc = ["Sentence to echoe"], + args_example = [<<"Test Sentence">>], + args = [{sentence, binary}], + result = {sentence, string}, + result_example = "Test Sentence"} ]. - %%% %%% Server management %%% @@ -491,7 +576,7 @@ status() -> {value, {_, _, Version}} -> {ok, io_lib:format("ejabberd ~s is running in that node", [Version])} end, - {Is_running, String1 ++ String2}. + {Is_running, String1 ++ " " ++String2}. stop() -> _ = supervisor:terminate_child(ejabberd_sup, ejabberd_sm), @@ -583,8 +668,14 @@ update_list() -> [atom_to_list(Beam) || Beam <- UpdatedBeams]. update("all") -> - [update_module(ModStr) || ModStr <- update_list()], - {ok, []}; + ResList = [{ModStr, update_module(ModStr)} || ModStr <- update_list()], + String = case string:join([Mod || {Mod, {ok, _}} <- ResList], ", ") of + [] -> + "No modules updated"; + ModulesString -> + "Updated modules: " ++ ModulesString + end, + {ok, String}; update(ModStr) -> update_module(ModStr). @@ -593,7 +684,10 @@ update_module(ModuleNameBin) when is_binary(ModuleNameBin) -> update_module(ModuleNameString) -> ModuleName = list_to_atom(ModuleNameString), case ejabberd_update:update([ModuleName]) of - {ok, _Res} -> {ok, []}; + {ok, []} -> + {ok, "Not updated: "++ModuleNameString}; + {ok, [{ModuleName, _}]} -> + {ok, "Updated: "++ModuleNameString}; {error, Reason} -> {error, Reason} end. @@ -681,7 +775,25 @@ convert_to_yaml(In, Out) -> %%% join_cluster(NodeBin) -> - ejabberd_cluster:join(list_to_atom(binary_to_list(NodeBin))). + Node = list_to_atom(binary_to_list(NodeBin)), + IsNodes = lists:member(Node, ejabberd_cluster:get_nodes()), + IsKnownNodes = lists:member(Node, ejabberd_cluster:get_known_nodes()), + Ping = net_adm:ping(Node), + join_cluster(Node, IsNodes, IsKnownNodes, Ping). + +join_cluster(_Node, true, _IsKnownNodes, _Ping) -> + {error, "This node already joined that running node."}; +join_cluster(_Node, _IsNodes, true, _Ping) -> + {error, "This node already joined that known node."}; +join_cluster(_Node, _IsNodes, _IsKnownNodes, pang) -> + {error, "This node cannot reach that node."}; +join_cluster(Node, false, false, pong) -> + case timer:apply_after(1000, ejabberd_cluster, join, [Node]) of + {ok, _} -> + {ok, "Trying to join the cluster, wait a few seconds and check the list of nodes."}; + Error -> + {error, io_lib:format("Can't join cluster: ~p", [Error])} + end. leave_cluster(NodeBin) -> ejabberd_cluster:leave(list_to_atom(binary_to_list(NodeBin))). @@ -689,6 +801,33 @@ leave_cluster(NodeBin) -> list_cluster() -> ejabberd_cluster:get_nodes(). +list_cluster_detailed() -> + KnownNodes = ejabberd_cluster:get_known_nodes(), + RunningNodes = ejabberd_cluster:get_nodes(), + [get_cluster_node_details(Node, RunningNodes) || Node <- KnownNodes]. + +get_cluster_node_details(Node, RunningNodes) -> + get_cluster_node_details2(Node, lists:member(Node, RunningNodes)). + +get_cluster_node_details2(Node, false) -> + {Node, "false", "", -1, -1, -1, "unknown"}; +get_cluster_node_details2(Node, true) -> + try ejabberd_cluster:call(Node, ejabberd_admin, get_cluster_node_details3, []) of + Result -> Result + catch + E:R -> + Status = io_lib:format("~p: ~p", [E, R]), + {Node, "true", Status, -1, -1, -1, "unknown"} + end. + +get_cluster_node_details3() -> + {ok, StatusString} = status(), + UptimeSeconds = mod_admin_extra:stats(<<"uptimeseconds">>), + Processes = mod_admin_extra:stats(<<"processes">>), + OnlineUsers = mod_admin_extra:stats(<<"onlineusersnode">>), + GetMaster = get_master(), + {node(), "true", StatusString, OnlineUsers, Processes, UptimeSeconds, GetMaster}. + %%% %%% Migration management %%% @@ -791,6 +930,12 @@ delete_old_messages_abort(Server) -> %%% Mnesia management %%% +get_master() -> + case mnesia:table_info(session, master_nodes) of + [] -> none; + [Node] -> Node + end. + set_master("self") -> set_master(node()); set_master(NodeString) when is_list(NodeString) -> @@ -798,7 +943,7 @@ set_master(NodeString) when is_list(NodeString) -> set_master(Node) when is_atom(Node) -> case mnesia:set_master_nodes([Node]) of ok -> - {ok, ""}; + {ok, "ok"}; {error, Reason} -> String = io_lib:format("Can't set master node ~p at node ~p:~n~p", [Node, node(), Reason]), @@ -1008,3 +1153,87 @@ is_my_host(Host) -> try ejabberd_router:is_my_host(Host) catch _:{invalid_domain, _} -> false end. + +%%% +%%% Web Admin +%%% + +web_menu_node(Acc, _Node, _Lang) -> + Acc ++ [{<<"cluster">>, <<"Clustering">>}, + {<<"update">>, <<"Code Update">>}, + {<<"config">>, <<"Configuration File">>}, + {<<"logs">>, <<"Logs">>}, + {<<"stop">>, <<"Stop Node">>}]. + +web_page_node(_, Node, #request{path = [<<"cluster">>]} = R, _Lang) -> + {ok, Names} = net_adm:names(), + NodeNames = lists:join(", ", [Name || {Name, _Port} <- Names]), + Hint = list_to_binary(io_lib:format("Hint: Erlang nodes found in this machine that may be running ejabberd: ~s", [NodeNames])), + Head = ?H1GLraw(<<"Clustering">>, <<"admin/guide/clustering/">>, <<"Clustering">>), + Set1 = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [join_cluster, R, [], + [{style, danger}]]), + ?XE(<<"blockquote">>,[?C(Hint)]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [leave_cluster, R, [], + [{style, danger}]])], + Set2 = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [set_master, R, [], + [{style, danger}]])], + timer:sleep(100), % leaving a cluster takes a while, let's delay the get commands + Get1 = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [list_cluster_detailed, R, [], + [{result_links, [{name, node, 3, <<"">>}]}]])], + Get2 = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [get_master, R, [], + [{result_named, true}, + {result_links, [{nodename, node, 3, <<"">>}]}]])], + {stop, Head ++ Get1 ++ Set1 ++ Get2 ++ Set2}; + +web_page_node(_, Node, #request{path = [<<"update">>]} = R, _Lang) -> + Head = [?XC(<<"h1">>, <<"Code Update">>)], + Set = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [update, R])], + Get = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [update_list, R])], + {stop, Head ++ Get ++ Set}; + +web_page_node(_, Node, #request{path = [<<"config">>]} = R, _Lang) -> + Res = ?H1GLraw(<<"Configuration File">>, + <<"admin/configuration/file-format/">>, + <<"File Format">>) ++ + [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [convert_to_yaml, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [dump_config, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [reload_config, R])], + {stop, Res}; + +web_page_node(_, Node, #request{path = [<<"stop">>]} = R, _Lang) -> + Res = [?XC(<<"h1">>, <<"Stop This Node">>), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [restart, R, [], [{style, danger}]]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [stop_kindly, R, [], [{style, danger}]]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [stop, R, [], [{style, danger}]]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [halt, R, [], [{style, danger}]])], + {stop, Res}; + +web_page_node(_, Node, #request{path = [<<"logs">>]} = R, _Lang) -> + Res = ?H1GLraw(<<"Logs">>, <<"admin/configuration/basic/#logging">>, <<"Logging">>) ++ + [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [set_loglevel, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [get_loglevel, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [reopen_log, R]), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [rotate_log, R])], + {stop, Res}; +web_page_node(Acc, _, _, _) -> Acc. + +echo(Sentence) -> + Sentence. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index ef2f81b942b..7b920fa8f29 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -37,6 +37,12 @@ make_table/2, make_table/4, term_to_id/1, id_to_term/1]). +%% Internal commands +-export([webadmin_host_last_activity/3, + webadmin_node_db/3, + webadmin_node_db_table/3, + webadmin_node_db_table_page/4]). + -include_lib("xmpp/include/xmpp.hrl"). -include("ejabberd_commands.hrl"). -include("ejabberd_http.hrl"). @@ -107,7 +113,7 @@ get_menu_items(global, cluster, Lang, JID, Level) -> end, Items); get_menu_items(Host, cluster, Lang, JID, Level) -> - {_Base, _, Items} = make_host_menu(Host, [], Lang, JID, Level), + {_Base, _, Items} = make_host_menu(Host, [], [], Lang, JID, Level), lists:map(fun ({URI, Name}) -> {<>, Name}; ({URI, Name, _SubMenu}) -> @@ -277,18 +283,26 @@ get_auth_account2(HostOfRule, AccessRule, User, Server, %%%% make_xhtml make_xhtml(Els, Host, Lang, JID, Level) -> - make_xhtml(Els, Host, cluster, Lang, JID, Level). + make_xhtml(Els, Host, cluster, unspecified, Lang, JID, Level). + +make_xhtml(Els, Host, Username, Lang, JID, Level) when + (Username == unspecified) or (is_binary(Username)) -> + make_xhtml(Els, Host, cluster, Username, Lang, JID, Level); + +make_xhtml(Els, Host, Node, Lang, JID, Level) -> + make_xhtml(Els, Host, Node, unspecified, Lang, JID, Level). -spec make_xhtml([xmlel()], Host::global | binary(), Node::cluster | atom(), + Username::unspecified | binary(), Lang::binary(), jid(), Level::integer()) -> {200, [html], xmlel()}. -make_xhtml(Els, Host, Node, Lang, JID, Level) -> +make_xhtml(Els, Host, Node, Username, Lang, JID, Level) -> Base = get_base_path_sum(0, 0, Level), - MenuItems = make_navigation(Host, Node, Lang, JID, Level), + MenuItems = make_navigation(Host, Node, Username, Lang, JID, Level), {200, [html], #xmlel{name = <<"html">>, attrs = @@ -465,6 +479,7 @@ process_admin(Host, #request{path = [], lang = Lang}, AJID) -> || {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(), @@ -485,8 +500,6 @@ process_admin(_Host, #request{path = [<<"additions.js">>]}, _) -> [{<<"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), process_admin(_Host, #request{path = [<<"sortable.min.css">>]}, _) -> {200, [{<<"Content-Type">>, <<"text/css">>}, last_modified(), @@ -498,120 +511,142 @@ process_admin(_Host, #request{path = [<<"sortable.min.js">>]}, _) -> last_modified(), cache_control_public()], sortable_js()}; +process_admin(global, #request{path = [<<"vhosts">> | RPath], lang = Lang} = R, AJID) -> + Hosts = case make_command_raw_value(registered_vhosts, R, []) of + Hs when is_list(Hs) -> + Hs; + _ -> + {User, Server} = R#request.us, + ?INFO_MSG("Access to WebAdmin page vhosts/ for account ~s@~s was denied", [User, Server]), + [] + end, + Level = 1 + length(RPath), + Table = make_table( + 20, RPath, + [<<"Host">>, {<<"Registered Users">>, right}, {<<"Online Users">>, right}], + [{make_command(echo, + R, + [{<<"sentence">>, Host}], + [{only, value}, + {result_links, [{sentence, host, Level, <<"">>}]} + ]), + make_command(stats_host, + R, + [{<<"name">>, <<"registeredusers">>}, + {<<"host">>, Host}], + [{only, value}, + {result_links, [{stat, arg_host, Level, <<"users/">>}]} + ]), + make_command(stats_host, + R, + [{<<"name">>, <<"onlineusers">>}, + {<<"host">>, Host}], + [{only, value}, + {result_links, [{stat, arg_host, Level, <<"online-users/">>}]}]) + } || Host <- Hosts]), + VhostsElements = [make_command(registered_vhosts, R, [], [{only, presentation}]), + make_command(stats_host, R, [], [{only, presentation}]), + ?XE(<<"blockquote">>, [Table]) + ], make_xhtml((?H1GL((translate:translate(Lang, ?T("Virtual Hosts"))), <<"basic/#xmpp-domains">>, ?T("XMPP Domains"))) - ++ 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), + ++ VhostsElements, + global, Lang, AJID, Level); + +process_admin(Host, #request{path = [<<"users">>, <<"diapason">>, Diap | RPath], + lang = Lang} = R, AJID) + when is_binary(Host) -> + Level = 5 + length(RPath), + Res = list_users_in_diapason(Host, Level, 30, RPath, R, Diap), + make_xhtml([?XCT(<<"h1">>, ?T("Users"))] ++ Res, Host, + Lang, AJID, Level); + +process_admin(Host, #request{path = [<<"users">>, <<"top">>, Attribute | RPath], + lang = Lang} = R, AJID) + when is_binary(Host) -> + Level = 5 + length(RPath), + Res = list_users_top(Host, Level, 30, RPath, R, Attribute), 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), + Lang, AJID, Level); + +process_admin(Host, #request{path = [<<"users">> | RPath], + lang = Lang} = R, AJID) + when is_binary(Host) -> + Level = 3 + length(RPath), + Res = list_users(Host, Level, 30, RPath, R), 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), + Lang, AJID, Level); + +process_admin(Host, #request{path = [<<"online-users">> | RPath], + lang = Lang} = R, AJID) + when is_binary(Host) -> + Level = 3 + length(RPath), + Res = [make_command(connected_users_vhost, R, [{<<"host">>, Host}], + [{table_options, {2, RPath}}, + {result_links, [{sessions, user, Level, <<"">>}]}])], make_xhtml([?XCT(<<"h1">>, ?T("Online Users"))] ++ Res, - Host, Lang, AJID, 3); + Host, Lang, AJID, Level); 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, + q = Query, lang = Lang} = R, AJID) + when is_binary(Host) -> PageH1 = ?H1GL(translate:translate(Lang, ?T("Users Last Activity")), <<"modules/#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")), <<"modules/#mod_stats">>, <<"mod_stats">>), - Level = case Host of - global -> 1; - _ -> 3 - end, - make_xhtml(PageH1 ++ Res, Host, Lang, AJID, Level); + Res = make_command(webadmin_host_last_activity, + R, + [{<<"host">>, Host}, + {<<"query">>, Query}, + {<<"lang">>, Lang}], + []), + make_xhtml(PageH1 ++ [Res], Host, Lang, AJID, 3); process_admin(Host, #request{path = [<<"user">>, U], - q = Query, lang = Lang}, AJID) -> + lang = Lang} = R, 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) + true -> + Res = user_info(U, Host, R, Lang), + make_xhtml(Res, Host, U, 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), +process_admin(Host, #request{path = [<<"nodes">>], lang = Lang} = R, AJID) -> Level = case Host of - global -> 1; - _ -> 3 - end, + global -> 1; + _ -> 3 + end, + Res = ?H1GLraw(<<"Nodes">>, <<"admin/guide/clustering/">>, <<"Clustering">>) ++ + [make_command(list_cluster, R, [], [{result_links, [{node, node, 1, <<"">>}]}])], make_xhtml(Res, Host, Lang, AJID, Level); process_admin(Host, #request{path = [<<"node">>, SNode | NPath], - q = Query, lang = Lang}, AJID) -> + lang = Lang} = Request, 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) + false -> + make_xhtml([?XCT(<<"h1">>, ?T("Node not found"))], Host, + Lang, AJID, 2); + Node -> + Res = get_node(Host, Node, NPath, Request#request{path = NPath}, 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 -> +process_admin(Host, #request{path = Path, lang = Lang} = Request, AJID) -> + {Username, RPath} = case Path of + [<<"user">>, U | UPath] -> {U, UPath}; + _ -> {unspecified, Path} + end, + Request2 = Request#request{path = RPath}, + Res = case {Host, Username} of + {global, _} -> ejabberd_hooks:run_fold( - webadmin_page_main, Host, [], [Request]); - _ -> + webadmin_page_main, Host, [], [Request2]); + {_, unspecified} -> ejabberd_hooks:run_fold( - webadmin_page_host, Host, [], [Host, Request]) + webadmin_page_host, Host, [], [Host, Request2]); + {_Host, Username} -> + ejabberd_hooks:run_fold( + webadmin_page_hostuser, Host, [], [Host, Username, Request2]) end, Level = case Host of global -> length(Request#request.path); @@ -623,7 +658,7 @@ process_admin(Host, #request{lang = Lang} = Request, AJID) -> make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang, AJID, Level), 404); - _ -> make_xhtml(Res, Host, Lang, AJID, Level) + _ -> make_xhtml(Res, Host, Username, Lang, AJID, Level) end. term_to_id(T) -> base64:encode((term_to_binary(T))). @@ -633,9 +668,6 @@ id_to_term(I) -> binary_to_term(base64:decode(I)). %%%================================== %%%% list_vhosts -list_vhosts(Lang, JID) -> - list_vhosts2(Lang, list_vhosts_allowed(JID)). - list_vhosts_allowed(JID) -> Hosts = ejabberd_option:hosts(), lists:filter(fun (Host) -> @@ -645,40 +677,6 @@ list_vhosts_allowed(JID) -> end, Hosts). -list_vhosts2(Lang, Hosts) -> - SHosts = lists:sort(Hosts), - [?XE(<<"table">>, - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Host")), - ?XACT(<<"td">>, - [{<<"class">>, <<"alignright">>}], - ?T("Registered Users")), - ?XACT(<<"td">>, - [{<<"class">>, <<"alignright">>}], - ?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)]), - ?XAE(<<"td">>, - [{<<"class">>, <<"alignright">>}], - [?AC(<<"../server/", Host/binary, "/users/">>, - pretty_string_int(RegisteredUsers))]), - ?XAE(<<"td">>, - [{<<"class">>, <<"alignright">>}], - [?AC(<<"../server/", Host/binary, "/online-users/">>, - pretty_string_int(OnlineUsers))])]) - end, - SHosts)))])]. - maybe_disclaimer_not_admin(MenuItems, AJID, _Lang) -> case {MenuItems, list_vhosts_allowed(AJID)} of {[_], []} -> @@ -695,186 +693,153 @@ maybe_disclaimer_not_admin(MenuItems, AJID, _Lang) -> %%%================================== %%%% 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(<>) - 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 +%% emacs-indent-begin +%% emacs-untabify-begin + +list_users(Host, Level, PageSize, RPath, R) -> + Usernames = case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of + As when is_list(As) -> + As; + _ -> + {Aser, Aerver} = R#request.us, + ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied", [Aser, Aerver]), + [] + end, + case length(Usernames) of + N when N =< 10 -> + list_users(Host, Level, PageSize, RPath, R, Usernames); + N when N > 10 -> + list_users_diapason(Host, R, Usernames, N) end. -list_users_in_diapason(Host, Diap, Lang, URLFunc) -> - Users = ejabberd_auth:get_users(Host), - SUsers = lists:sort([{S, U} || {U, S} <- Users]), +list_users(Host, Level, PageSize, RPath, R, Usernames) -> + Columns = [<<"user">>, + {<<"offline">>, right}, + {<<"roster">>, right}, + {<<"timestamp">>, left}, + {<<"status">>, left}], + Rows = [{make_command(echo, + R, + [{<<"sentence">>, jid:encode(jid:make(Username, Host))}], + [{only, raw_and_value}, + {result_links, [{sentence, user, Level, <<"">>}]} + ]), + make_command(get_offline_count, + R, + [{<<"user">>, Username}, + {<<"host">>, Host}], + [{only, raw_and_value}, + {result_links, [{value, arg_host, Level, + <<"user/", Username/binary, "/queue/">>}]} + ]), + make_command(get_roster_count, + R, + [{<<"user">>, Username}, + {<<"host">>, Host}], + [{only, raw_and_value}, + {result_links, [{value, arg_host, Level, + <<"user/", Username/binary, "/roster/">>}]} + ]), + ?C(element(1, make_command_raw_value(get_last, R, [{<<"user">>, Username}, {<<"host">>, Host}]))), + ?C(element(2, make_command_raw_value(get_last, R, [{<<"user">>, Username}, {<<"host">>, Host}])))} + || Username <- Usernames], + [make_command(register, R, [{<<"host">>, Host}], []), + make_command(registered_users, R, [], [{only, presentation}]), + make_command(get_offline_count, R, [], [{only, presentation}]), + make_command(get_roster_count, R, [], [{only, presentation}]), + make_command(get_last, R, [], [{only, presentation}]), + make_table(PageSize, RPath, Columns, Rows)]. + +list_users_diapason(Host, R, Usernames, N) -> + URLFunc = fun url_func/1, + SUsers = [{Host, U} || U <- Usernames], + NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1, + M = trunc(N / NParts) + 1, + FUsers = 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)), + [make_command(register, R, [{<<"host">>, Host}], []), + make_command(get_offline_count, R, [], [{only, presentation}]), + ?AC(<<"top/offline/">>, <<"View Top Offline Queues">>), + make_command(get_roster_count, R, [], [{only, presentation}]), + ?AC(<<"top/roster/">>, <<"View Top Rosters">>), + make_command(get_last, R, [], [{only, presentation}]), + ?AC(<<"top/last/">>, <<"View Top-Oldest Last Activity">>), + make_command(registered_users, R, [], [{only, presentation}])] + ++ FUsers. + +list_users_in_diapason(Host, Level, PageSize, RPath, R, Diap) -> + Usernames = case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of + As when is_list(As) -> + As; + _ -> + {Aser, Aerver} = R#request.us, + ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied", [Aser, Aerver]), + [] + end, + SUsers = lists:sort([{Host, U} || U <- Usernames]), [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")), - ?XACT(<<"td">>, - [{<<"class">>, <<"alignright">>}], - ?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)))]), - ?XAE(<<"td">>, - [{<<"class">>, <<"alignright">>}], - 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. + Usernames2 = [U || {_, U} <- Sub], + list_users(Host, Level, PageSize, RPath, R, Usernames2). + +list_users_top(Host, Level, PageSize, RPath, R, Operation) -> + Usernames = case make_command_raw_value(registered_users, R, [{<<"host">>, Host}]) of + As when is_list(As) -> + As; + _ -> + {Aser, Aerver} = R#request.us, + ?INFO_MSG("Access to WebAdmin page users/ for account ~s@~s was denied", [Aser, Aerver]), + [] + end, + {Command, Reverse} = case Operation of + <<"roster">> -> {get_roster_count, true}; + <<"offline">> -> {get_offline_count, true}; + <<"last">> -> {get_last, false} + end, + UsernamesCounts = [{U, + make_command(Command, + R, + [{<<"user">>, U}, + {<<"host">>, Host}], + [{only, raw_value}, + {result_links, [{value, arg_host, Level, + <<"user/", U/binary, "/roster/">>}]} + ])} + || U <- Usernames], + USorted = lists:keysort(2, UsernamesCounts), + UReversed = case Reverse of + true -> lists:reverse(USorted); + false -> USorted + end, + Usernames2 = [U || {U, _} <- lists:sublist(UReversed, 100)], + list_users(Host, Level, PageSize, RPath, R, Usernames2). 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 + true -> + case mod_last_opt:db_type(Server) of + mnesia -> [{<<"last-activity">>, ?T("Last Activity")}]; + _ -> [] + end; + false -> + [] end. us_to_list({User, Server}) -> @@ -883,156 +848,59 @@ us_to_list({User, Server}) -> su_to_list({Server, User}) -> jid:encode({User, Server, <<"">>}). +%% emacs-indent-end +%% emacs-untabify-end + %%%================================== -%%%% get_stats +%%%% last-activity -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:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(RegisteredUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Online Users:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(OnlineUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Outgoing s2s Connections:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(OutS2SNumber)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Incoming s2s Connections:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (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:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(RegisteredUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Online Users:")), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (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). +webadmin_host_last_activity(Host, Query, Lang) -> + ?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, + [?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. -user_info(User, Server, Query, Lang) -> +%%%================================== +%%%% get_stats + +user_info(User, Server, #request{q = Query} = R, 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, - <> - end, - case direction(Lang) of - [{_, <<"rtl">>}] -> ?LI([?C((<>))]); - _ -> ?LI([?C((<>))]) - 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, + LServer, [], [User, Server, R, Lang]), [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("User ~ts"), [us_to_list(US)])))] ++ @@ -1044,16 +912,21 @@ user_info(User, Server, Query, Lang) -> ++ [?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, - ?INPUTTD(<<"submit">>, <<"removeuser">>, - ?T("Remove User"))]))]. + ([make_command(user_sessions_info, R, + [{<<"user">>, User}, {<<"host">>, Server}], + [{result_links, [{node, node, 4, <<>>}]}]), + make_command(change_password, R, + [{<<"user">>, User}, {<<"host">>, Server}], + [{style, danger}]), + make_command(get_last, R, + [{<<"user">>, User}, {<<"host">>, Server}], + [])] ++ + UserItems ++ + [?P, + make_command(unregister, R, + [{<<"user">>, User}, {<<"host">>, Server}], + [{style, danger}]) + ]))]. user_parse_query(User, Server, Query) -> lists:foldl(fun ({Action, _Value}, Acc) @@ -1147,33 +1020,6 @@ histogram([], _Integral, _Current, Count, Hist) -> %%%================================== %%%% 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). @@ -1185,35 +1031,127 @@ search_running_node(SNode, [Node | Nodes]) -> _ -> search_running_node(SNode, Nodes) end. -get_node(global, Node, [], Query, Lang) -> - Res = node_parse_query(Node, Query), +get_node(global, Node, [], _Request, Lang) -> Base = get_base_path(global, Node, 2), - BaseItems = [{<<"db">>, <<"Database">>}, - {<<"backup">>, <<"Backup">>}, - {<<"stats">>, <<"Statistics">>}, - {<<"update">>, <<"Update">>}], + BaseItems = [{<<"db">>, <<"Mnesia Tables">>}, + {<<"backup">>, <<"Mnesia Backup">>}], MenuItems = make_menu_items(global, Node, Base, Lang, BaseItems), [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("Node ~p"), [Node])))] ++ - case Res of - ok -> [?XREST(?T("Submitted"))]; - error -> [?XREST(?T("Bad format"))]; - nothing -> [] - end - ++ - [?XE(<<"ul">>, MenuItems), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?INPUTT(<<"submit">>, <<"restart">>, ?T("Restart")), - ?C(<<" ">>), - ?INPUTTD(<<"submit">>, <<"stop">>, ?T("Stop"))])]; -get_node(Host, Node, [], _Query, Lang) -> + [?XE(<<"ul">>, MenuItems)]; +get_node(Host, Node, [], _Request, Lang) -> Base = get_base_path(Host, Node, 4), MenuItems2 = make_menu_items(Host, Node, Base, Lang, []), [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("Node ~p"), [Node]))), ?XE(<<"ul">>, MenuItems2)]; -get_node(global, Node, [<<"db">>], Query, Lang) -> +get_node(global, Node, [<<"db">>], #request{q = Query} = R, Lang) -> + Head = ?XC(<<"h1">>, + (str:translate_and_format(Lang, + ?T("Database Tables at ~p"), + [Node]))), + Res = make_command(webadmin_node_db, R, [{<<"node">>, Node}, + {<<"query">>, Query}, + {<<"lang">>, Lang}], []), + [Head, Res]; +get_node(global, Node, [<<"db">>, TableName], R, Lang) -> + Head = ?XC(<<"h1">>, + (str:translate_and_format(Lang, + ?T("Database Tables at ~p"), + [Node]))), + Res = make_command(webadmin_node_db_table, R, [{<<"node">>, Node}, + {<<"table">>, TableName}, + {<<"lang">>, Lang}], []), + [Head, Res]; +get_node(global, Node, [<<"db">>, TableName, PageNumber], R, Lang) -> + Head = ?XC(<<"h1">>, + (str:translate_and_format(Lang, + ?T("Database Tables at ~p"), + [Node]))), + Res = make_command(webadmin_node_db_table_page, R, [{<<"node">>, Node}, + {<<"table">>, TableName}, + {<<"lang">>, Lang}, + {<<"page">>, PageNumber} + ], []), + [Head, Res]; +get_node(global, Node, [<<"backup">>], R, Lang) -> + Types = [{<<"#binary">>, <<"Binary">>}, + {<<"#plaintext">>, <<"Plain Text">>}, + {<<"#piefxis">>, <<"PIEXFIS (XEP-0227)">>}, + {<<"#sql">>, <<"SQL">>}, + {<<"#prosody">>, <<"Prosody">>}, + {<<"#jabberd14">>, <<"jabberd 1.4">>}], + + [?XC(<<"h1">>, (str:translate_and_format(Lang, ?T("Backup of ~p"), [Node]))), + ?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.")), + ?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- Types]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"binary">>}], <<"Binary">>), + ?XCT(<<"p">>, ?T("Store binary backup:")), + ?XE(<<"blockquote">>, [make_command(backup, R)]), + ?XCT(<<"p">>, ?T("Restore binary backup immediately:")), + ?XE(<<"blockquote">>, [make_command(restore, R, [], [{style, danger}])]), + ?XCT(<<"p">>, ?T("Restore binary backup after next ejabberd " + "restart (requires less memory):")), + ?XE(<<"blockquote">>, [make_command(install_fallback, R, [], [{style, danger}])]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"plaintext">>}], <<"Plain Text">>), + ?XCT(<<"p">>, ?T("Store plain text backup:")), + ?XE(<<"blockquote">>, [make_command(dump, R)]), + ?XCT(<<"p">>, ?T("Restore plain text backup immediately:")), + ?XE(<<"blockquote">>, [make_command(load, R, [], [{style, danger}])]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"piefxis">>}], <<"PIEFXIS (XEP-0227)">>), + ?XCT(<<"p">>, ?T("Import users data from a PIEFXIS file (XEP-0227):")), + ?XE(<<"blockquote">>, [make_command(import_piefxis, R)]), + ?XCT(<<"p">>, ?T("Export data of all users in the server to PIEFXIS files (XEP-0227):")), + ?XE(<<"blockquote">>, [make_command(export_piefxis, R)]), + ?XCT(<<"p">>, ?T("Export data of users in a host to PIEFXIS files (XEP-0227):")), + ?XE(<<"blockquote">>, [make_command(export_piefxis_host, R)]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"sql">>}], <<"SQL">>), + ?XCT(<<"p">>, ?T("Export all tables as SQL queries to a file:")), + ?XE(<<"blockquote">>, [make_command(export2sql, R)]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"prosody">>}], <<"Prosody">>), + ?XCT(<<"p">>, ?T("Import data from Prosody:")), + ?XE(<<"blockquote">>, [make_command(import_prosody, R)]), + + ?X(<<"hr">>), + ?XAC(<<"h2">>, [{<<"id">>, <<"jabberd14">>}], <<"jabberd 1.4">>), + ?XCT(<<"p">>, ?T("Import user data from jabberd14 spool file:")), + ?XE(<<"blockquote">>, [make_command(import_file, R)]), + ?XCT(<<"p">>, ?T("Import users data from jabberd14 spool directory:")), + ?XE(<<"blockquote">>, [make_command(import_dir, R)]) + ]; +get_node(Host, Node, _NPath, Request, Lang) -> + Res = case Host of + global -> + ejabberd_hooks:run_fold(webadmin_page_node, Host, [], + [Node, Request, Lang]); + _ -> + ejabberd_hooks:run_fold(webadmin_page_hostnode, Host, [], + [Host, Node, Request, Lang]) + end, + case Res of + [] -> [?XC(<<"h1">>, <<"Not Found">>)]; + _ -> Res + end. + +%%%================================== +%%%% node parse + +webadmin_node_db(Node, Query, Lang) -> case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of {badrpc, _Reason} -> [?XCT(<<"h1">>, ?T("RPC Call Error"))]; @@ -1272,11 +1210,6 @@ get_node(global, Node, [<<"db">>], Query, Lang) -> (pretty_string_int(MemoryB)))]) end, STables), - [?XC(<<"h1">>, - (str:translate_and_format(Lang, ?T("Database Tables at ~p"), - [Node])) - )] - ++ ResS ++ [?XAE(<<"form">>, [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], @@ -1300,326 +1233,8 @@ get_node(global, Node, [<<"db">>], Query, Lang) -> [?INPUTT(<<"submit">>, <<"submit">>, ?T("Submit"))])])]))])])] - end; -get_node(global, Node, [<<"db">>, TableName], _Query, Lang) -> - make_table_view(Node, TableName, Lang); -get_node(global, Node, [<<"db">>, TableName, PageNumber], _Query, Lang) -> - make_table_elements_view(Node, TableName, Lang, binary_to_integer(PageNumber)); -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:translate_and_format(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])), - UpTimeDate = uptime_date(Node), - 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]), - ?H1GL(str:translate_and_format(Lang, ?T("Statistics of ~p"), [Node]), - <<"modules/#mod_stats">>, - <<"mod_stats">>) ++ [ - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Uptime:")), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - UpTimeS)]), - ?XE(<<"tr">>, - [?X(<<"td">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - UpTimeDate)]), - ?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:translate_and_format(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. -uptime_date(Node) -> - Localtime = ejabberd_cluster:call(Node, erlang, localtime, []), - Now = calendar:datetime_to_gregorian_seconds(Localtime), - {Wall, _} = ejabberd_cluster:call(Node, erlang, statistics, [wall_clock]), - LastRestart = Now - (Wall div 1000), - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:gregorian_seconds_to_datetime(LastRestart), - str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, Second]). - -%%%================================== -%%%% 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>>}], @@ -1684,102 +1299,6 @@ node_db_parse_query(Node, Tables, Query) -> 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(<>, 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(<>, - 1, Query), - ejabberd_cluster:call(Node, ejabberd_piefxis, - export_host, - [Path, Host]); - <<"export_sql_file">> -> - {value, {_, Host}} = - lists:keysearch(<>, - 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, <<"">>)). @@ -1838,12 +1357,8 @@ pretty_print_xml(#xmlel{name = Name, attrs = Attrs, end]. url_func({user_diapason, From, To}) -> - <<(integer_to_binary(From))/binary, "-", - (integer_to_binary(To))/binary, "/">>; -url_func({users_queue, Prefix, User, _Server}) -> - <>; -url_func({user, Prefix, User, _Server}) -> - <>. + <<"diapason/", (integer_to_binary(From))/binary, "-", + (integer_to_binary(To))/binary, "/">>. last_modified() -> {<<"Last-Modified">>, @@ -1867,7 +1382,7 @@ pretty_string_int(String) when is_binary(String) -> %%%================================== %%%% mnesia table view -make_table_view(Node, STable, Lang) -> +webadmin_node_db_table(Node, STable, Lang) -> Table = misc:binary_to_atom(STable), TInfo = ejabberd_cluster:call(Node, mnesia, table_info, [Table, all]), {value, {storage_type, Type}} = lists:keysearch(storage_type, 1, TInfo), @@ -1909,7 +1424,7 @@ make_table_view(Node, STable, Lang) -> ])]), ?XC(<<"pre">>, TableInfo)]. -make_table_elements_view(Node, STable, Lang, PageNumber) -> +webadmin_node_db_table_page(Node, STable, Lang, PageNumber) -> Table = misc:binary_to_atom(STable), TInfo = ejabberd_cluster:call(Node, mnesia, table_info, [Table, all]), {value, {storage_type, Type}} = lists:keysearch(storage_type, 1, TInfo), @@ -1956,19 +1471,22 @@ get_table_content(Node, Table, _Type, PageNumber, PageSize) -> %%%================================== %%%% navigation menu -make_navigation(Host, Node, Lang, JID, Level) -> - Menu = make_navigation_menu(Host, Node, Lang, JID, Level), +make_navigation(Host, Node, Username, Lang, JID, Level) -> + Menu = make_navigation_menu(Host, Node, Username, Lang, JID, Level), make_menu_items(Lang, Menu). -spec make_navigation_menu(Host::global | binary(), Node::cluster | atom(), + Username::unspecified | binary(), Lang::binary(), JID::jid(), Level::integer()) -> Menu::{URL::binary(), Title::binary()} | {URL::binary(), Title::binary(), [Menu::any()]}. -make_navigation_menu(Host, Node, Lang, JID, Level) -> +make_navigation_menu(Host, Node, Username, Lang, JID, Level) -> HostNodeMenu = make_host_node_menu(Host, Node, Lang, JID, Level), - HostMenu = make_host_menu(Host, HostNodeMenu, Lang, + HostUserMenu = make_host_user_menu(Host, Username, Lang, + JID, Level), + HostMenu = make_host_menu(Host, HostNodeMenu, HostUserMenu, Lang, JID, Level), NodeMenu = make_node_menu(Host, Node, Lang, Level), make_server_menu(HostMenu, NodeMenu, Lang, JID, Level). @@ -1995,18 +1513,30 @@ make_host_node_menu(Host, Node, Lang, JID, Level) -> || Tuple <- HostNodeFixed, is_allowed_path(Host, Tuple, JID)], {HostNodeBase, iolist_to_binary(atom_to_list(Node)), - HostNodeFixed2}. + lists:keysort(2, HostNodeFixed2)}. + +make_host_user_menu(global, _, _Lang, _JID, _Level) -> + {<<"">>, <<"">>, []}; +make_host_user_menu(_, unspecified, _Lang, _JID, _Level) -> + {<<"">>, <<"">>, []}; +make_host_user_menu(Host, Username, Lang, JID, Level) -> + HostNodeBase = get_base_path(Host, Username, Level), + HostNodeFixed = get_menu_items_hook({hostuser, Host, Username}, Lang), + HostNodeFixed2 = [Tuple + || Tuple <- HostNodeFixed, + is_allowed_path(Host, Tuple, JID)], + {HostNodeBase, Username, + lists:keysort(2, HostNodeFixed2)}. -make_host_menu(global, _HostNodeMenu, _Lang, _JID, _Level) -> +make_host_menu(global, _HostNodeMenu, _HostUserMenu, _Lang, _JID, _Level) -> {<<"">>, <<"">>, []}; -make_host_menu(Host, HostNodeMenu, Lang, JID, Level) -> +make_host_menu(Host, HostNodeMenu, HostUserMenu, Lang, JID, Level) -> HostBase = get_base_path(Host, cluster, Level), - HostFixed = [{<<"users">>, ?T("Users")}, + HostFixed = [{<<"users">>, ?T("Users"), HostUserMenu}, {<<"online-users">>, ?T("Online Users")}], HostFixedAdditional = get_lastactivity_menuitem_list(Host) ++ - [{<<"nodes">>, ?T("Nodes"), HostNodeMenu}, - {<<"stats">>, ?T("Statistics")}] + [{<<"nodes">>, ?T("Nodes"), HostNodeMenu}] ++ get_menu_items_hook({host, Host}, Lang), HostFixedAll = HostFixed ++ lists:keysort(2, HostFixedAdditional), HostFixed2 = [Tuple @@ -2018,10 +1548,8 @@ 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")}] + NodeFixed = [{<<"db">>, ?T("Mnesia Tables")}, + {<<"backup">>, ?T("Mnesia Backup")}] ++ get_menu_items_hook({node, Node}, Lang), {NodeBase, iolist_to_binary(atom_to_list(Node)), lists:keysort(2, NodeFixed)}; @@ -2032,9 +1560,7 @@ 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}], - FixedAdditional = - [{<<"stats">>, ?T("Statistics")}] - ++ get_menu_items_hook(server, Lang), + FixedAdditional = get_menu_items_hook(server, Lang), FixedAll = Fixed ++ lists:keysort(2, FixedAdditional), Fixed2 = [Tuple || Tuple <- FixedAll, @@ -2044,6 +1570,9 @@ make_server_menu(HostMenu, NodeMenu, Lang, JID, Level) -> get_menu_items_hook({hostnode, Host, Node}, Lang) -> ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node, Lang]); +get_menu_items_hook({hostuser, Host, Username}, Lang) -> + ejabberd_hooks:run_fold(webadmin_menu_hostuser, Host, + [], [Host, Username, Lang]); get_menu_items_hook({host, Host}, Lang) -> ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host, Lang]); diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 3c3182415b4..e148c5ecdc6 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -37,13 +37,14 @@ config_dir/0, get_commands_spec/0]). -export([modules_configs/0, module_ebin_dir/1]). -export([compile_erlang_file/2, compile_elixir_file/2]). --export([web_menu_node/3, web_page_node/5, get_page/3]). +-export([web_menu_node/3, web_page_node/4, webadmin_node_contrib/3]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("ejabberd_commands.hrl"). +-include("ejabberd_http.hrl"). -include("ejabberd_web_admin.hrl"). -include("logger.hrl"). -include("translate.hrl"). @@ -927,23 +928,27 @@ parse_details(Body) -> web_menu_node(Acc, _Node, Lang) -> Acc ++ [{<<"contrib">>, translate:translate(Lang, ?T("Contrib Modules"))}]. -web_page_node(_, Node, [<<"contrib">>], Query, Lang) -> - Res = rpc:call(Node, ?MODULE, get_page, [Node, Query, Lang]), - {stop, Res}; -web_page_node(Acc, _, _, _, _) -> - Acc. - -get_page(Node, Query, Lang) -> - QueryRes = list_modules_parse_query(Query), +web_page_node(_, Node, #request{path = [<<"contrib">>], q = Query} = R, Lang) -> Title = ?H1GL(translate:translate(Lang, ?T("Contrib Modules")), <<"../../developer/extending-ejabberd/modules/#ejabberd-contrib">>, <<"ejabberd-contrib">>), + Res = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [webadmin_node_contrib, R, [{<<"node">>, Node}, + {<<"query">>, Query}, + {<<"lang">>, Lang}], + []])], + {stop, Title ++ Res}; +web_page_node(Acc, _, _, _) -> + Acc. + +webadmin_node_contrib(Node, Query, Lang) -> + QueryRes = list_modules_parse_query(Query), Contents = get_content(Node, Query, Lang), Result = case QueryRes of ok -> [?XREST(?T("Submitted"))]; nothing -> [] end, - Title ++ Result ++ Contents. + Result ++ Contents. get_module_home(Module, Attrs) -> case get_module_information(home, Attrs) of diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 8616bdd2491..f0284e52027 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -59,7 +59,7 @@ % Roster add_rosteritem/7, delete_rosteritem/4, - get_roster/2, push_roster/3, + get_roster/2, get_roster_count/2, push_roster/3, push_roster_all/1, push_alltoall/2, push_roster_item/5, build_roster_item/3, @@ -80,9 +80,17 @@ % Stats stats/1, stats/2 ]). +-export([web_menu_main/2, web_page_main/2, + web_menu_host/3, web_page_host/3, + web_menu_hostuser/4, web_page_hostuser/4, + web_menu_hostnode/4, web_page_hostnode/5, + web_menu_node/3, web_page_node/4]). +-import(ejabberd_web_admin, [make_command/4, make_table/2]). -include("ejabberd_commands.hrl"). +-include("ejabberd_http.hrl"). +-include("ejabberd_web_admin.hrl"). -include("mod_roster.hrl"). -include("mod_privacy.hrl"). -include("ejabberd_sm.hrl"). @@ -94,7 +102,17 @@ %%% start(_Host, _Opts) -> - ejabberd_commands:register_commands(?MODULE, get_commands_spec()). + ejabberd_commands:register_commands(?MODULE, get_commands_spec()), + {ok, [{hook, webadmin_menu_main, web_menu_main, 50, global}, + {hook, webadmin_page_main, web_page_main, 50, global}, + {hook, webadmin_menu_host, web_menu_host, 50}, + {hook, webadmin_page_host, web_page_host, 50}, + {hook, webadmin_menu_hostuser, web_menu_hostuser, 50}, + {hook, webadmin_page_hostuser, web_page_hostuser, 50}, + {hook, webadmin_menu_hostnode, web_menu_hostnode, 50}, + {hook, webadmin_page_hostnode, web_page_hostnode, 50}, + {hook, webadmin_menu_node, web_menu_node, 50, global}, + {hook, webadmin_page_node, web_page_node, 50, global}]}. stop(Host) -> case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of @@ -670,6 +688,16 @@ get_commands_spec() -> {pending, string}, {groups, {list, {group, string}}} ]}}}}}, + #ejabberd_commands{name = get_roster_count, tags = [roster], + desc = "Get number of contacts in a local user roster", + note = "added in 24.xx", + policy = user, + module = ?MODULE, function = get_roster_count, + args = [], + args_rename = [{server, host}], + result_example = 5, + result_desc = "Number", + result = {value, integer}}, #ejabberd_commands{name = push_roster, tags = [roster], desc = "Push template roster from file to a user", longdesc = "The text file must contain an erlang term: a list " @@ -836,6 +864,18 @@ get_commands_spec() -> result_example = 5, result_desc = "Number", result = {value, integer}}, + #ejabberd_commands{name = get_offline_messages, + tags = [internal, offline], + desc = "Get the offline messages", + policy = user, + module = mod_offline, function = get_offline_messages, + args = [], + result = {queue, {list, {messages, {tuple, [{time, string}, + {from, string}, + {to, string}, + {packet, string} + ]}}}}}, + #ejabberd_commands{name = send_message, tags = [stanza], desc = "Send a message to a local or remote bare of full JID", longdesc = "When sending a groupchat message to a MUC room, " @@ -1586,6 +1626,15 @@ make_roster_xmlrpc(Roster) -> end, Roster). +get_roster_count(User, Server) -> + case jid:make(User, Server) of + error -> + throw({error, "Invalid 'user'/'server'"}); + #jid{luser = U, lserver = S} -> + Items = ejabberd_hooks:run_fold(roster_get, S, [], [{U, S}]), + length(Items) + end. + %%----------------------------- %% Push Roster from file %%----------------------------- @@ -1915,6 +1964,204 @@ num_prio(Priority) when is_integer(Priority) -> num_prio(_) -> -1. +%%% +%%% Web Admin +%%% + +%% emacs-indent-begin +%% emacs-untabify-begin + +%%% Main + +web_menu_main(Acc, _Lang) -> + Acc ++ [{<<"stats">>, <<"Statistics">>}]. + +web_page_main(_, #request{path = [<<"stats">>]} = R) -> + Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>) ++ + [make_command(stats_host, R, [], [{only, presentation}]), + make_command(incoming_s2s_number, R, [], [{only, presentation}]), + make_command(outgoing_s2s_number, R, [], [{only, presentation}]), + make_table( + [<<"Stat Name">>, {<<"Stat value">>, right}], + [{?C(<<"Registered Users:">>), + make_command(stats, R, [{<<"name">>, <<"registeredusers">>}], [{only, value}])}, + {?C(<<"Online Users:">>), + make_command(stats, R, [{<<"name">>, <<"onlineusers">>}], [{only, value}])}, + {?C(<<"S2S Connections Incoming:">>), + make_command(incoming_s2s_number, R, [], [{only, value}])}, + {?C(<<"S2S Connections Outgoing:">>), + make_command(outgoing_s2s_number, R, [], [{only, value}])} + ]) + ], + {stop, Res}; +web_page_main(Acc, _) -> Acc. + +%%% Host + +web_menu_host(Acc, _Host, _Lang) -> + Acc ++ [{<<"stats">>, <<"Statistics">>}]. + +web_page_host(_, Host, #request{path = [<<"stats">>]} = R) -> + Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>) ++ + [make_command(stats_host, R, [], [{only, presentation}]), + make_table( + [<<"Stat Name">>, {<<"Stat value">>, right}], + [{?C(<<"Registered Users:">>), + make_command(stats_host, R, + [{<<"host">>, Host}, + {<<"name">>, <<"registeredusers">>}], + [{only, value}, + {result_links, [{stat, arg_host, 3, <<"users">>}]} + ])}, + {?C(<<"Online Users:">>), + make_command(stats_host, R, + [{<<"host">>, Host}, + {<<"name">>, <<"onlineusers">>}], + [{only, value}, + {result_links, [{stat, arg_host, 3, <<"online-users">>}]} + ])} + ]) + ], + {stop, Res}; +web_page_host(Acc, _, _) -> Acc. + +%%% HostUser + +web_menu_hostuser(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"auth">>, <<"Authentication">>}, + {<<"private">>, <<"Private XML Storage">>}, + {<<"session">>, <<"Sessions">>}, + {<<"vcard">>, <<"vCard">>}]. + +web_page_hostuser(_, Host, User, #request{path = [<<"auth">>]} = R) -> + Res = ?H1GLraw(<<"Authentication">>, <<"admin/configuration/authentication/">>, <<"Authentication">>) ++ + [make_command(register, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(check_account, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(check_password, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(check_password_hash, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(change_password, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}]), + make_command(ban_account, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}]), + make_command(unregister, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}])], + {stop, Res}; + +web_page_hostuser(_, Host, User, #request{path = [<<"private">>]} = R) -> + Res = ?H1GL(<<"Private XML Storage">>, <<"modules/#mod_private">>, <<"mod_private">>) ++ + [make_command(private_set, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(private_get, R, [{<<"user">>, User}, {<<"host">>, Host}], [])], + {stop, Res}; + +web_page_hostuser(_, Host, User, #request{path = [<<"session">>]} = R) -> + Head = [?XC(<<"h1">>, <<"Sessions">>), ?BR], + Set = [make_command(resource_num, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(set_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(kick_user, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}]), + make_command(kick_session, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}])], + timer:sleep(100), % kicking sessions takes a while, let's delay the get commands + Get = [make_command(user_sessions_info, R, [{<<"user">>, User}, {<<"host">>, Host}], + [{result_links, [{node, node, 5, <<>>}]}]), + make_command(user_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(get_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(num_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], [])], + {stop, Head ++ Get ++ Set}; + +web_page_hostuser(_, Host, User, #request{path = [<<"vcard">>]} = R) -> + Head = ?H1GL(<<"vCard">>, <<"modules/#mod_vcard">>, <<"mod_vcard">>), + Set = [make_command(set_nickname, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(set_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(set_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(set_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])], + timer:sleep(100), % setting vcard takes a while, let's delay the get commands + FieldNames = [<<"VERSION">>, <<"FN">>, <<"NICKNAME">>, <<"BDAY">>], + FieldNames2 = [{<<"N">>, <<"FAMILY">>}, + {<<"N">>, <<"GIVEN">>}, + {<<"N">>, <<"MIDDLE">>}, + {<<"ADR">>, <<"CTRY">>}, + {<<"ADR">>, <<"LOCALITY">>}, + {<<"EMAIL">>, <<"USERID">>}], + Get = [make_command(get_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + ?XE(<<"blockquote">>,[make_table( + [<<"Name">>, <<"Value">>], + [{?C(FieldName), + make_command(get_vcard, R, + [{<<"user">>, User}, + {<<"host">>, Host}, + {<<"name">>, FieldName}], + [{only, value}])} + || FieldName <- FieldNames] + )]), + make_command(get_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + ?XE(<<"blockquote">>,[make_table( + [<<"Name">>, <<"Subname">>, <<"Value">>], + [{?C(FieldName), ?C(FieldSubName), + make_command(get_vcard2, R, + [{<<"user">>, User}, + {<<"host">>, Host}, + {<<"name">>, FieldName}, + {<<"subname">>, FieldSubName}], + [{only, value}])} + || {FieldName, FieldSubName} <- FieldNames2] + )]), + make_command(get_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])], + {stop, Head ++ Get ++ Set}; + +web_page_hostuser(Acc, _, _, _) -> Acc. + +%%% HostNode + +web_menu_hostnode(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"modules">>, <<"Modules">>}]. + +web_page_hostnode(_, Host, Node, #request{path = [<<"modules">>]} = R, _Lang) -> + Res = ?H1GLraw(<<"Modules">>, <<"admin/configuration/modules/">>, <<"Modules Options">>) ++ + [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [restart_module, R, [{<<"host">>, Host}], []]) + ], + {stop, Res}; + +web_page_hostnode(Acc, _Host, _Node, _Request, _Lang) -> + Acc. + +%%% Node + +web_menu_node(Acc, _Node, _Lang) -> + Acc ++ [{<<"stats">>, <<"Statistics">>}]. + +web_page_node(_, Node, #request{path = [<<"stats">>]} = R, _Lang) -> + UpSecs = ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [stats, R, + [{<<"name">>, <<"uptimeseconds">>}], + [{only, value}]]), + UpDaysBin = integer_to_binary(binary_to_integer(fxml:get_tag_cdata(UpSecs)) div 24000), + UpDays = #xmlel{name = <<"code">>, attrs = [], children = [{xmlcdata, UpDaysBin}]}, + Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>) ++ + [make_command(stats, R, [], [{only, presentation}]), + make_table( + [<<"Stat Name">>, {<<"Stat value">>, right}], + [{?C(<<"Online Users in this node:">>), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [stats, R, + [{<<"name">>, <<"onlineusersnode">>}], + [{only, value}]])}, + {?C(<<"Uptime Seconds:">>), + UpSecs}, + {?C(<<"Uptime Seconds (rounded to days):">>), + UpDays}, + {?C(<<"Processes:">>), + ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, + [stats, R, + [{<<"name">>, <<"processes">>}], + [{only, value}]])} + ]) + ], + {stop, Res}; +web_page_node(Acc, _, _, _) -> Acc. + +%% emacs-indent-end +%% emacs-untabify-end + +%%% +%%% Document +%%% + mod_options(_) -> []. mod_doc() -> diff --git a/src/mod_mix_pam.erl b/src/mod_mix_pam.erl index fcca0c33787..e2ebc2bb7a9 100644 --- a/src/mod_mix_pam.erl +++ b/src/mod_mix_pam.erl @@ -33,8 +33,8 @@ remove_user/2, process_iq/1, get_mix_roster_items/2, - webadmin_user/4, - webadmin_page/3]). + webadmin_user/5, + webadmin_menu_hostuser/4, webadmin_page_hostuser/4]). -include_lib("xmpp/include/xmpp.hrl"). -include("logger.hrl"). @@ -69,7 +69,8 @@ start(Host, Opts) -> ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:add(roster_get, Host, ?MODULE, get_mix_roster_items, 50), ejabberd_hooks:add(webadmin_user, Host, ?MODULE, webadmin_user, 50), - ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, webadmin_page, 50), + ejabberd_hooks:add(webadmin_menu_hostuser, Host, ?MODULE, webadmin_menu_hostuser, 50), + ejabberd_hooks:add(webadmin_page_hostuser, Host, ?MODULE, webadmin_page_hostuser, 50), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0, ?MODULE, process_iq), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_2, ?MODULE, process_iq); Err -> @@ -82,7 +83,8 @@ stop(Host) -> ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:delete(roster_get, Host, ?MODULE, get_mix_roster_items, 50), ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, webadmin_user, 50), - ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, webadmin_page, 50), + ejabberd_hooks:delete(webadmin_menu_hostuser, Host, ?MODULE, webadmin_menu_hostuser, 50), + ejabberd_hooks:delete(webadmin_page_hostuser, Host, ?MODULE, webadmin_page_hostuser, 50), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_2). @@ -469,7 +471,7 @@ delete_cache(Mod, JID, Channel) -> %%%=================================================================== %%% Webadmin interface %%%=================================================================== -webadmin_user(Acc, User, Server, Lang) -> +webadmin_user(Acc, User, Server, _Request, Lang) -> QueueLen = case get_channels({jid:nodeprep(User), jid:nameprep(Server), <<>>}) of {ok, Channels} -> length(Channels); error -> -1 @@ -482,12 +484,13 @@ webadmin_user(Acc, User, Server, Lang) -> ?C(<<" | ">>), FQueueView]. -webadmin_page(_, Host, - #request{us = _US, path = [<<"user">>, U, <<"mix_channels">>], - lang = Lang} = _Request) -> +webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"mix_channels">>, <<"MIX Channels">>}]. + +webadmin_page_hostuser(_, Host, U, #request{path = [<<"mix_channels">>], lang = Lang}) -> Res = web_mix_channels(U, Host, Lang), {stop, Res}; -webadmin_page(Acc, _, _) -> Acc. +webadmin_page_hostuser(Acc, _, _, _) -> Acc. web_mix_channels(User, Server, Lang) -> LUser = jid:nodeprep(User), diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 484700fc6e1..2d354a1260a 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -39,15 +39,20 @@ get_room_occupants_number/2, send_direct_invitation/5, change_room_option/4, get_room_options/2, set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3, - web_menu_main/2, web_page_main/2, web_menu_host/3, subscribe_room/4, subscribe_room_many/3, unsubscribe_room/2, get_subscribers/2, get_room_serverhost/1, - web_page_host/3, + web_menu_main/2, web_page_main/2, + web_menu_host/3, web_page_host/3, + web_menu_hostuser/4, web_page_hostuser/4, + webadmin_muc/1, + webadmin_host_muc_rooms/3, mod_opt_type/1, mod_options/1, get_commands_spec/0, find_hosts/1, room_diagnostics/2, get_room_pid/2, get_room_history/2]). +-import(ejabberd_web_admin, [make_command/4]). + -include("logger.hrl"). -include_lib("xmpp/include/xmpp.hrl"). -include("mod_muc.hrl"). @@ -66,7 +71,10 @@ start(_Host, _Opts) -> {ok, [{hook, webadmin_menu_main, web_menu_main, 50, global}, {hook, webadmin_page_main, web_page_main, 50, global}, {hook, webadmin_menu_host, web_menu_host, 50}, - {hook, webadmin_page_host, web_page_host, 50}]}. + {hook, webadmin_page_host, web_page_host, 50}, + {hook, webadmin_menu_hostuser, web_menu_hostuser, 50}, + {hook, webadmin_page_hostuser, web_page_hostuser, 50} + ]}. stop(Host) -> case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of @@ -478,7 +486,18 @@ get_commands_spec() -> result = {history, {list, {entry, {tuple, [{timestamp, string}, - {message, string}]}}}}} + {message, string}]}}}}}, + + #ejabberd_commands{name = webadmin_muc, tags = [internal], + desc = "Generate WebAdmin MUC HTML", + module = ?MODULE, function = webadmin_muc, + args = [{lang, binary}], + result = {res, any}}, + #ejabberd_commands{name = webadmin_host_muc_rooms, tags = [internal], + desc = "Generate WebAdmin MUC Rooms HTML", + module = ?MODULE, function = webadmin_host_muc_rooms, + args = [{host, any}, {query, any}, {lang, binary}], + result = {res, any}} ]. @@ -598,36 +617,25 @@ web_menu_host(Acc, _Host, Lang) -> ?XC(<<"td">>, integer_to_binary(N)) ])). -web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) -> - OnlineRoomsNumber = lists:foldl( - fun(Host, Acc) -> - Acc + mod_muc:count_online_rooms(Host) - end, 0, find_hosts(global)), +web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) -> PageTitle = translate:translate(Lang, ?T("Multi-User Chat")), - Res = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>) ++ - [?XCT(<<"h3">>, ?T("Statistics")), - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber) - ]) - ]), - ?XE(<<"ul">>, [?LI([?ACT(<<"rooms/">>, ?T("List of rooms"))])]) - ], - {stop, Res}; + Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>), + Res = [make_command(webadmin_muc, R, [{<<"lang">>, Lang}], [])], + {stop, Title ++ Res}; -web_page_main(_, #request{path=[<<"muc">>, <<"rooms">>], q = Q, lang = Lang} = _Request) -> - Sort_query = get_sort_query(Q), - Res = make_rooms_page(global, Lang, Sort_query), - {stop, Res}; +web_page_main(_, #request{path = [<<"muc">>, <<"rooms">>], q = Q, lang = Lang} = R) -> + PageTitle = translate:translate(Lang, ?T("Multi-User Chat")), + Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>), + Res = [make_command(webadmin_host_muc_rooms, R, [{<<"host">>, global}, {<<"query">>, Q}, {<<"lang">>, Lang}], [])], + {stop, Title ++ Res}; web_page_main(Acc, _) -> Acc. -web_page_host(_, Host, - #request{path = [<<"muc">>], - q = Q, - lang = Lang} = _Request) -> - Sort_query = get_sort_query(Q), - Res = make_rooms_page(Host, Lang, Sort_query), - {stop, Res}; +web_page_host(_, Host, #request{path = [<<"muc">>], q = Q, lang = Lang} = R) -> + PageTitle = translate:translate(Lang, ?T("Multi-User Chat")), + Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>), + Res = [make_command(webadmin_host_muc_rooms, R, [{<<"host">>, Host}, {<<"query">>, Q}, {<<"lang">>, Lang}], [])], + {stop, Title ++ Res}; web_page_host(Acc, _, _) -> Acc. @@ -646,7 +654,21 @@ get_sort_query2(Q) -> false -> {ok, {reverse, abs(Integer)}} end. -make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> +webadmin_muc(Lang) -> + OnlineRoomsNumber = lists:foldl( + fun(Host, Acc) -> + Acc + mod_muc:count_online_rooms(Host) + end, 0, find_hosts(global)), + [?XCT(<<"h3">>, ?T("Statistics")), + ?XAE(<<"table">>, [], + [?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber) + ]) + ]), + ?XE(<<"ul">>, [?LI([?ACT(<<"rooms/">>, ?T("List of rooms"))])]) + ]. + +webadmin_host_muc_rooms(Host, Q, Lang) -> + {Sort_direction, Sort_column} = get_sort_query(Q), Service = find_service(Host), Rooms_names = get_online_rooms(Service), Rooms_infos = build_info_rooms(Rooms_names), @@ -678,8 +700,6 @@ make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) -> end, 1, Titles), - PageTitle = translate:translate(Lang, ?T("Multi-User Chat")), - ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>) ++ [?XCT(<<"h2">>, ?T("Chatrooms")), ?XE(<<"table">>, [?XE(<<"thead">>, @@ -767,6 +787,49 @@ justcreated_to_binary(J) when is_integer(J) -> justcreated_to_binary(J) when is_atom(J) -> misc:atom_to_binary(J). +%%-------------------- +%% Web Admin Host User + +web_menu_hostuser(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>}, + {<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>}, + {<<"muc-sub">>, <<"MUC Rooms Subscriptions">>}, + {<<"muc-register">>, <<"MUC Service Registration">>}]. + +web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) -> + Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>) ++ + [make_command(get_user_rooms, + R, + [{<<"user">>, User}, {<<"host">>, Host}], + [{table_options, {2, RPath}}])], + {stop, Res}; + +web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) -> + Jid = jid:encode(jid:make(User, Host)), + Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>) ++ + [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []), + make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])], + {stop, Res}; + +web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">>]} = R) -> + Subs = make_command(get_user_subscriptions, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + Res = ?H1GLraw(<<"MUC Rooms Subscriptions">>, + <<"developer/xmpp-clients-bots/extensions/muc-sub/">>, + <<"MUC/Sub">>) ++ + [Subs, + make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []), + make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])], + {stop, Res}; + +web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) -> + Jid = jid:encode(jid:make(User, Host)), + Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>) ++ + [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []), + make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])], + {stop, Res}; + +web_page_hostuser(Acc, _, _, _) -> Acc. + %%---------------------------- %% Create/Delete Room %%---------------------------- diff --git a/src/mod_offline.erl b/src/mod_offline.erl index f50452876f2..f6e083a4475 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -60,13 +60,17 @@ find_x_expire/2, c2s_handle_info/2, c2s_copy_session/2, - webadmin_page/3, - webadmin_user/4, + get_offline_messages/2, + webadmin_menu_hostuser/4, + webadmin_page_hostuser/4, + webadmin_user/5, webadmin_user_parse_query/5, c2s_handle_bind2_inline/1]). -export([mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]). +-import(ejabberd_web_admin, [make_command/4, make_command/2]). + -deprecated({get_queue_length,2}). -include("logger.hrl"). @@ -133,7 +137,8 @@ start(Host, Opts) -> {hook, c2s_handle_info, c2s_handle_info, 50}, {hook, c2s_copy_session, c2s_copy_session, 50}, {hook, c2s_handle_bind2_inline, c2s_handle_bind2_inline, 50}, - {hook, webadmin_page_host, webadmin_page, 50}, + {hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50}, + {hook, webadmin_page_hostuser, webadmin_page_hostuser, 50}, {hook, webadmin_user, webadmin_user, 50}, {hook, webadmin_user_parse_query, webadmin_user_parse_query, 50}, {iq_handler, ejabberd_sm, ?NS_FLEX_OFFLINE, handle_offline_query}]}. @@ -730,12 +735,39 @@ discard_warn_sender(Packet, Reason) -> ok end. -webadmin_page(_, Host, - #request{us = _US, path = [<<"user">>, U, <<"queue">>], - q = Query, lang = Lang} = - _Request) -> - Res = user_queue(U, Host, Query, Lang), {stop, Res}; -webadmin_page(Acc, _, _) -> Acc. +%%% +%%% Commands +%%% + +get_offline_messages(User, Server) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + Mod = gen_mod:db_mod(LServer, ?MODULE), + HdrsAll = case Mod:read_message_headers(LUser, LServer) of + error -> []; + L -> L + end, + format_user_queue(HdrsAll). + +%%% +%%% WebAdmin +%%% + +webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"queue">>, <<"Offline Queue">>}]. + +webadmin_page_hostuser(_, Host, U, + #request{us = _US, path = [<<"queue">> | RPath], + lang = Lang} = R) -> + US = {U, Host}, + PageTitle = str:translate_and_format(Lang, ?T("~ts's Offline Messages Queue"), [us_to_list(US)]), + Head = ?H1GL(PageTitle, <<"modules/#mod_offline">>, <<"mod_offline">>), + Res = make_command(get_offline_messages, R, [{<<"user">>, U}, + {<<"host">>, Host}], + [{table_options, {10, RPath}}, + {result_links, [{packet, paragraph, 1, <<"">>}]}]), + {stop, Head ++ [Res]}; +webadmin_page_hostuser(Acc, _, _, _) -> Acc. get_offline_els(LUser, LServer) -> [Packet || {_Seq, Packet} <- read_messages(LUser, LServer)]. @@ -939,8 +971,7 @@ count_mam_messages(LUser, LServer, ReadMsgs) -> format_user_queue(Hdrs) -> lists:map( - fun({Seq, From, To, TS, El}) -> - ID = integer_to_binary(Seq), + fun({_Seq, From, To, TS, El}) -> FPacket = ejabberd_web_admin:pretty_print_xml(El), SFrom = jid:encode(From), STo = jid:encode(To), @@ -956,14 +987,7 @@ format_user_queue(Hdrs) -> {_, _, _} = Now -> format_time(Now) end, - ?XE(<<"tr">>, - [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo), - ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?XC(<<"pre">>, FPacket)])]) + {Time, SFrom, STo, FPacket} end, Hdrs). format_time(Now) -> @@ -971,111 +995,18 @@ format_time(Now) -> str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", [Year, Month, Day, Hour, Minute, Second]). -user_queue(User, Server, Query, Lang) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - Mod = gen_mod:db_mod(LServer, ?MODULE), - user_queue_parse_query(LUser, LServer, Query), - HdrsAll = case Mod:read_message_headers(LUser, LServer) of - error -> []; - L -> L - end, - Hdrs = get_messages_subset(User, Server, HdrsAll), - FMsgs = format_user_queue(Hdrs), - PageTitle = str:translate_and_format(Lang, ?T("~ts's Offline Messages Queue"), [us_to_list(US)]), - (?H1GL(PageTitle, <<"modules/#mod_offline">>, <<"mod_offline">>)) - ++ [?XREST(?T("Submitted"))] ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XE(<<"table">>, - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?X(<<"td">>), ?XCT(<<"td">>, ?T("Time")), - ?XCT(<<"td">>, ?T("From")), - ?XCT(<<"td">>, ?T("To")), - ?XCT(<<"td">>, ?T("Packet"))])]), - ?XE(<<"tbody">>, - if FMsgs == [] -> - [?XE(<<"tr">>, - [?XAC(<<"td">>, [{<<"colspan">>, <<"4">>}], - <<" ">>)])]; - true -> FMsgs - end)]), - ?BR, - ?INPUTTD(<<"submit">>, <<"delete">>, - ?T("Delete Selected"))])]. - -user_queue_parse_query(LUser, LServer, Query) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - case user_queue_parse_query(LUser, LServer, Query, Mod, false) of - true -> - flush_cache(Mod, LUser, LServer); - false -> - ok - end; - _ -> - ok - end. - -user_queue_parse_query(LUser, LServer, Query, Mod, Acc) -> - case lists:keytake(<<"selected">>, 1, Query) of - {value, {_, Seq}, Query2} -> - NewAcc = case catch binary_to_integer(Seq) of - I when is_integer(I), I>=0 -> - Mod:remove_message(LUser, LServer, I), - true; - _ -> - Acc - end, - user_queue_parse_query(LUser, LServer, Query2, Mod, NewAcc); - false -> - Acc - end. - us_to_list({User, Server}) -> jid:encode({User, Server, <<"">>}). get_queue_length(LUser, LServer) -> count_offline_messages(LUser, LServer). -get_messages_subset(User, Host, MsgsAll) -> - MaxOfflineMsgs = case get_max_user_messages(User, Host) of - Number when is_integer(Number) -> Number; - _ -> 100 - end, - Length = length(MsgsAll), - get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll). - -get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 -> - MsgsAll; -get_messages_subset2(Max, Length, MsgsAll) -> - FirstN = Max, - {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll), - MsgsLastN = lists:nthtail(Length - FirstN - FirstN, - Msgs2), - NoJID = jid:make(<<"...">>, <<"...">>), - Seq = <<"0">>, - IntermediateMsg = #xmlel{name = <<"...">>, attrs = [], - children = []}, - MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN. - -webadmin_user(Acc, User, Server, Lang) -> - QueueLen = count_offline_messages(jid:nodeprep(User), - jid:nameprep(Server)), - FQueueLen = ?C(integer_to_binary(QueueLen)), - FQueueView = ?AC(<<"queue/">>, - ?T("View Queue")), - Acc ++ - [?XCT(<<"h3">>, ?T("Offline Messages:")), - FQueueLen, - ?C(<<" | ">>), - FQueueView, - ?C(<<" | ">>), - ?INPUTTD(<<"submit">>, <<"removealloffline">>, - ?T("Remove All Offline Messages"))]. +webadmin_user(Acc, User, Server, R, _Lang) -> + Acc ++ [make_command(get_offline_count, R, [{<<"user">>, User}, {<<"host">>, Server}], [])]. + +%%% +%%% +%%% -spec delete_all_msgs(binary(), binary()) -> {atomic, any()}. delete_all_msgs(User, Server) -> diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 498146d6348..04a35b76c97 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -46,13 +46,16 @@ import_start/2, import_stop/2, is_subscribed/2, c2s_self_presence/1, in_subscription/2, out_subscription/1, set_items/3, remove_user/2, - get_jid_info/4, encode_item/1, webadmin_page/3, - webadmin_user/4, get_versioning_feature/2, + get_jid_info/4, encode_item/1, get_versioning_feature/2, roster_version/2, mod_doc/0, mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3, process_rosteritems/5, depends/2, set_item_and_notify_clients/3]). +-export([webadmin_page_hostuser/4, webadmin_menu_hostuser/4, webadmin_user/5]). + +-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]). + -include("logger.hrl"). -include_lib("xmpp/include/xmpp.hrl"). -include("mod_roster.hrl"). @@ -98,7 +101,8 @@ start(Host, Opts) -> {hook, remove_user, remove_user, 50}, {hook, c2s_self_presence, c2s_self_presence, 50}, {hook, c2s_post_auth_features, get_versioning_feature, 50}, - {hook, webadmin_page_host, webadmin_page, 50}, + {hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50}, + {hook, webadmin_page_hostuser, webadmin_page_hostuser, 50}, {hook, webadmin_user, webadmin_user, 50}, {iq_handler, ejabberd_sm, ?NS_ROSTER, process_iq}]}. @@ -1016,205 +1020,85 @@ process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -webadmin_page(_, Host, - #request{us = _US, path = [<<"user">>, U, <<"roster">>], - q = Query, lang = Lang} = - _Request) -> - Res = user_roster(U, Host, Query, Lang), {stop, Res}; -webadmin_page(Acc, _, _) -> Acc. - -user_roster(User, Server, Query, Lang) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - Items1 = get_roster(LUser, LServer), - Res = user_roster_parse_query(User, Server, Items1, - Query), - Items = get_roster(LUser, LServer), - SItems = lists:sort(Items), - FItems = case SItems of - [] -> [?CT(?T("None"))]; - _ -> - [?XE(<<"table">>, - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, ?T("Jabber ID")), - ?XCT(<<"td">>, ?T("Nickname")), - ?XCT(<<"td">>, ?T("Subscription")), - ?XCT(<<"td">>, ?T("Pending")), - ?XCT(<<"td">>, ?T("Groups"))])]), - ?XE(<<"tbody">>, - (lists:map(fun (R) -> - Groups = lists:flatmap(fun - (Group) -> - [?C(Group), - ?BR] - end, - R#roster.groups), - Pending = - ask_to_pending(R#roster.ask), - TDJID = - build_contact_jid_td(R#roster.jid), - ?XE(<<"tr">>, - [TDJID, - ?XAC(<<"td">>, - [{<<"class">>, - <<"valign">>}], - (R#roster.name)), - ?XAC(<<"td">>, - [{<<"class">>, - <<"valign">>}], - (iolist_to_binary(atom_to_list(R#roster.subscription)))), - ?XAC(<<"td">>, - [{<<"class">>, - <<"valign">>}], - (iolist_to_binary(atom_to_list(Pending)))), - ?XAE(<<"td">>, - [{<<"class">>, - <<"valign">>}], - Groups), - if Pending == in -> - ?XAE(<<"td">>, - [{<<"class">>, - <<"valign">>}], - [?INPUTT(<<"submit">>, - <<"validate", - (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, - ?T("Validate"))]); - true -> ?X(<<"td">>) - end, - ?XAE(<<"td">>, - [{<<"class">>, - <<"valign">>}], - [?INPUTTD(<<"submit">>, - <<"remove", - (ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>, - ?T("Remove"))])]) - end, - SItems)))])] - end, - PageTitle = str:translate_and_format(Lang, ?T("Roster of ~ts"), [us_to_list(US)]), - (?H1GL(PageTitle, <<"modules/#mod_roster">>, <<"mod_roster">>)) - ++ - case Res of - ok -> [?XREST(?T("Submitted"))]; - error -> [?XREST(?T("Bad format"))]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - ( [?P, ?INPUT(<<"text">>, <<"newjid">>, <<"">>), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"addjid">>, - ?T("Add Jabber ID"))] - ++ FItems))]. - -build_contact_jid_td(RosterJID) -> - ContactJID = jid:make(RosterJID), - JIDURI = case {ContactJID#jid.luser, - ContactJID#jid.lserver} - of - {<<"">>, _} -> <<"">>; - {CUser, CServer} -> - case lists:member(CServer, ejabberd_option:hosts()) of - false -> <<"">>; - true -> - <<"../../../../../server/", CServer/binary, "/user/", - CUser/binary, "/">> - end - end, - case JIDURI of - <<>> -> - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], - (jid:encode(RosterJID))); - URI when is_binary(URI) -> - ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?AC(JIDURI, (jid:encode(RosterJID)))]) - end. - -user_roster_parse_query(User, Server, Items, Query) -> - case lists:keysearch(<<"addjid">>, 1, Query) of - {value, _} -> - case lists:keysearch(<<"newjid">>, 1, Query) of - {value, {_, SJID}} -> - try jid:decode(SJID) of - JID -> - user_roster_subscribe_jid(User, Server, JID), ok - catch _:{bad_jid, _} -> - error - end; - false -> error - end; - false -> - case catch user_roster_item_parse_query(User, Server, - Items, Query) - of - submitted -> ok; - {'EXIT', _Reason} -> error; - _ -> nothing - end - end. - -user_roster_subscribe_jid(User, Server, JID) -> - UJID = jid:make(User, Server), - Presence = #presence{from = UJID, to = JID, type = subscribe}, - out_subscription(Presence), - ejabberd_router:route(Presence). - -user_roster_item_parse_query(User, Server, Items, - Query) -> - lists:foreach(fun (R) -> - JID = R#roster.jid, - case lists:keysearch(<<"validate", - (ejabberd_web_admin:term_to_id(JID))/binary>>, - 1, Query) - of - {value, _} -> - JID1 = jid:make(JID), - UJID = jid:make(User, Server), - Pres = #presence{from = UJID, to = JID1, - type = subscribed}, - out_subscription(Pres), - ejabberd_router:route(Pres), - throw(submitted); - false -> - case lists:keysearch(<<"remove", - (ejabberd_web_admin:term_to_id(JID))/binary>>, - 1, Query) - of - {value, _} -> - UJID = jid:make(User, Server), - RosterItem = #roster_item{ - jid = jid:make(JID), - subscription = remove}, - process_iq_set( - #iq{type = set, - from = UJID, - to = UJID, - id = p1_rand:get_string(), - sub_els = [#roster_query{ - items = [RosterItem]}]}), - throw(submitted); - false -> ok - end - end - end, - Items), - nothing. - -us_to_list({User, Server}) -> - jid:encode({User, Server, <<"">>}). - -webadmin_user(Acc, User, Server, Lang) -> - QueueLen = length(get_roster(jid:nodeprep(User), jid:nameprep(Server))), - FQueueLen = ?C(integer_to_binary(QueueLen)), - FQueueView = ?AC(<<"roster/">>, ?T("View Roster")), - Acc ++ - [?XCT(<<"h3">>, ?T("Roster:")), - FQueueLen, - ?C(<<" | ">>), - FQueueView]. +%% emacs-indent-begin +%% emacs-untabify-begin + +webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) -> + Acc ++ [{<<"roster">>, <<"Roster">>}]. + +webadmin_page_hostuser(_, Host, Username, #request{path = [<<"roster">> | RPath]} = R) -> + Head = ?H1GL(<<"Roster">>, <<"modules/#mod_roster">>, <<"mod_roster">>), + %% Execute twice: first to perform the action, the second to get new roster + _ = make_webadmin_roster_table(Host, Username, R, RPath), + RV2 = make_webadmin_roster_table(Host, Username, R, RPath), + Set = [make_command(add_rosteritem, R, + [{<<"localuser">>, Username}, + {<<"localhost">>, Host}], + []), + make_command(push_roster, R, + [{<<"user">>, Username}, + {<<"host">>, Host}], + [])], + Get = [make_command(get_roster, R, [], [{only, presentation}]), + make_command(delete_rosteritem, R, [], [{only, presentation}]), + RV2], + {stop, Head ++ Get ++ Set}; + +webadmin_page_hostuser(Acc, _, _, _) -> Acc. + +make_webadmin_roster_table(Host, Username, R, RPath) -> + Contacts = case make_command_raw_value(get_roster, R, + [{<<"user">>, Username}, {<<"host">>, Host}]) of + Cs when is_list(Cs) -> Cs; + _ -> [] + end, + Level = 5 + length(RPath), + Columns = [<<"jid">>, <<"nick">>, <<"subscription">>, <<"pending">>, <<"groups">>, <<"">>], + Rows = lists:map( + fun({Jid, Nick, Subscriptions, Pending, Groups}) -> + {JidSplit, ProblematicBin} = + try jid:decode(Jid) of + #jid{} = J -> + {jid:split(J), <<"">>} + catch _:{bad_jid, _} -> + ?INFO_MSG("Error parsing contact of ~s@~s that is invalid JID: ~s", + [Username, Host, Jid]), + {{<<"000--error-parsing-jid">>, <<"localhost">>, <<"">>}, + <<", Error parsing JID: ", Jid/binary>>} + end, + {make_command(echo, R, + [{<<"sentence">>, jid:encode(JidSplit)}], + [{only, raw_and_value}, + {result_links, [{sentence, user, Level, <<"">>}]}]), + ?C(<>), + ?C(Subscriptions), + ?C(Pending), + ?C(Groups), + make_command(delete_rosteritem, R, + [{<<"localuser">>, Username}, + {<<"localhost">>, Host}, + {<<"user">>, element(1, JidSplit)}, + {<<"host">>, element(2, JidSplit)}], + [{only, button}, {style, danger}, + {input_name_append, ejabberd_web_admin:term_to_id( + [Username, + Host, + element(1, JidSplit), + element(2, JidSplit)] + )}]) + } + end, + lists:keysort(1, Contacts)), + Table = make_table(20, RPath, Columns, Rows), + ?XE(<<"blockquote">>,[Table]). + +webadmin_user(Acc, User, Server, R, _Lang) -> + Acc ++ [make_command(get_roster_count, R, + [{<<"user">>, User}, {<<"host">>, Server}], + [])]. + +%% emacs-indent-end +%% emacs-untabify-end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spec has_duplicated_groups([binary()]) -> boolean(). diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 447ed46665a..53aba4fd19d 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -31,6 +31,7 @@ -export([start/2, stop/1, reload/3, export/1, import_info/0, webadmin_menu/3, webadmin_page/3, + webadmin_host_srg/3, webadmin_host_srg_group/4, get_user_roster/2, get_jid_info/4, import/5, process_item/2, import_start/2, in_subscription/2, out_subscription/1, c2s_self_presence/1, @@ -41,6 +42,7 @@ is_user_in_group/3, add_user_to_group/3, opts_to_binary/1, remove_user_from_group/3, mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]). +-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/2, make_table/4]). -include("logger.hrl"). -include_lib("xmpp/include/xmpp.hrl"). @@ -870,19 +872,26 @@ webadmin_menu(Acc, _Host, Lang) -> webadmin_page(_, Host, #request{us = _US, path = [<<"shared-roster">>], - q = Query, lang = Lang} = - _Request) -> - Res = list_shared_roster_groups(Host, Query, Lang), - {stop, Res}; + q = Query, lang = Lang} = R) -> + Head = ?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), <<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>), + Res = make_command(webadmin_host_srg, R, [{<<"host">>, Host}, + {<<"query">>, Query}, + {<<"lang">>, Lang}], []), + {stop, Head ++ [Res]}; + webadmin_page(_, Host, #request{us = _US, path = [<<"shared-roster">>, Group], - q = Query, lang = Lang} = - _Request) -> - Res = shared_roster_group(Host, Group, Query, Lang), - {stop, Res}; + q = Query, lang = Lang} = R) -> + Head = ?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), <<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>), + Res = make_command(webadmin_host_srg_group, R, [{<<"host">>, Host}, + {<<"group">>, Group}, + {<<"query">>, Query}, + {<<"lang">>, Lang}], []), + {stop, Head ++ [Res]}; + webadmin_page(Acc, _, _) -> Acc. -list_shared_roster_groups(Host, Query, Lang) -> +webadmin_host_srg(Host, Query, Lang) -> Res = list_sr_groups_parse_query(Host, Query), SRGroups = list_groups(Host), FGroups = (?XAE(<<"table">>, [], @@ -911,9 +920,6 @@ list_shared_roster_groups(Host, Query, Lang) -> ?C(<<" ">>), ?INPUTT(<<"submit">>, <<"addnew">>, ?T("Add New"))])])]))])), - (?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), - <<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>)) - ++ case Res of ok -> [?XREST(?T("Submitted"))]; error -> [?XREST(?T("Bad format"))]; @@ -956,7 +962,7 @@ list_sr_groups_parse_delete(Host, Query) -> SRGroups), ok. -shared_roster_group(Host, Group, Query, Lang) -> +webadmin_host_srg_group(Host, Group, Query, Lang) -> Res = shared_roster_group_parse_query(Host, Group, Query), GroupOpts = get_group_opts(Host, Group), @@ -1018,9 +1024,6 @@ shared_roster_group(Host, Group, Query, Lang) -> list_to_binary(FDisplayedGroups))]), ?XE(<<"td">>, [?CT(?T("Groups that will be displayed to the members"))]) ])])])), - (?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))), - <<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>)) - ++ [?XC(<<"h2">>, translate:translate(Lang, ?T("Group")))] ++ case Res of ok -> [?XREST(?T("Submitted"))];