diff options
Diffstat (limited to 'src/web')
-rw-r--r-- | src/web/ejabberd_web.erl | 33 | ||||
-rw-r--r-- | src/web/ejabberd_web_admin.erl | 394 |
2 files changed, 281 insertions, 146 deletions
diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl index 3f2867507..70f6e3ee7 100644 --- a/src/web/ejabberd_web.erl +++ b/src/web/ejabberd_web.erl @@ -51,17 +51,46 @@ make_xhtml(Els) -> process_get({_, true}, #request{us = US, + path = ["admin", "server", SHost | RPath], + q = Query, + lang = Lang} = Request) -> + Host = jlib:nameprep(SHost), + case lists:member(Host, ?MYHOSTS) of + true -> + case US of + {User, Server} -> + case acl:match_rule( + Host, configure, jlib:make_jid(User, Server, "")) of + deny -> + {401, [], make_xhtml([?XC("h1", "Not Allowed")])}; + allow -> + ejabberd_web_admin:process_admin( + Host, Request#request{path = RPath}) + end; + undefined -> + {401, + [{"WWW-Authenticate", "basic realm=\"ejabberd\""}], + ejabberd_web:make_xhtml([{xmlelement, "h1", [], + [{xmlcdata, "401 Unauthorized"}]}])} + end; + false -> + {404, [], make_xhtml([?XC("h1", "Not found")])} + end; + +process_get({_, true}, + #request{us = US, path = ["admin" | RPath], q = Query, lang = Lang} = Request) -> case US of {User, Server} -> - case acl:match_rule(configure, jlib:make_jid(User, Server, "")) of + case acl:match_rule( + global, configure, jlib:make_jid(User, Server, "")) of deny -> {401, [], make_xhtml([?XC("h1", "Not Allowed")])}; allow -> ejabberd_web_admin:process_admin( - Request#request{path = RPath}) + global, Request#request{path = RPath}) end; undefined -> {401, diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index 354dc2d0a..8da73fd5c 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -14,7 +14,7 @@ -vsn('$Revision$ '). %% External exports --export([process_admin/1]). +-export([process_admin/2]). -include("ejabberd.hrl"). -include("jlib.hrl"). @@ -52,7 +52,7 @@ {"size", Size}])). -define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)). -make_xhtml(Els, Lang) -> +make_xhtml(Els, global, Lang) -> {200, [html], {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}, {"xml:lang", Lang}, @@ -76,16 +76,60 @@ make_xhtml(Els, Lang) -> [?XE("ul", [?LI([?ACT("/admin/acls/", "Access Control Lists")]), ?LI([?ACT("/admin/access/", "Access Rules")]), - ?LI([?ACT("/admin/users/", "Users")]), - ?LI([?ACT("/admin/online-users/", "Online Users")]), - ?LI([?ACT("/admin/last-activity/", "Last Activity")]), ?LI([?ACT("/admin/nodes/", "Nodes")]), ?LI([?ACT("/admin/stats/", "Statistics")]) + ] + )]), + ?XAE("div", + [{"id", "content"}], + Els), + ?XAE("div", + [{"id", "clearcopyright"}], + [{xmlcdata, ""}])]), + ?XAE("div", + [{"id", "copyrightouter"}], + [?XAE("div", + [{"id", "copyright"}], + [?XCT("p", + "ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One") + ])])]) + ]}}; + +make_xhtml(Els, Host, Lang) -> + Base = "/admin/server/" ++ Host ++ "/", + {200, [html], + {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}, + {"xml:lang", Lang}, + {"lang", Lang}], + [{xmlelement, "head", [], + [{xmlelement, "meta", [{"http-equiv", "Content-Type"}, + {"content", "text/html; charset=utf-8"}], []}, + {xmlelement, "link", [{"href", Base ++ "style.css"}, + {"type", "text/css"}, + {"rel", "stylesheet"}], []}]}, + ?XE("body", + [?XAE("div", + [{"id", "container"}], + [?XAE("div", + [{"id", "header"}], + [?XE("h1", + [?ACT(Base, "ejabberd administration")] + )]), + ?XAE("div", + [{"id", "navigation"}], + [?XE("ul", + [?LI([?ACT(Base ++ "acls/", "Access Control Lists")]), + ?LI([?ACT(Base ++ "access/", "Access Rules")]), + ?LI([?ACT(Base ++ "users/", "Users")]), + ?LI([?ACT(Base ++ "online-users/", "Online Users")]), + ?LI([?ACT(Base ++ "last-activity/", "Last Activity")]), + ?LI([?ACT(Base ++ "nodes/", "Nodes")]), + ?LI([?ACT(Base ++ "stats/", "Statistics")]) ] ++ case lists:member(mod_shared_roster, - gen_mod:loaded_modules()) of + gen_mod:loaded_modules(Host)) of true -> - [?LI([?ACT("/admin/shared-roster/", "Shared Roster")])]; + [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])]; false -> [] end @@ -105,7 +149,14 @@ make_xhtml(Els, Lang) -> ])])]) ]}}. -css() -> " +css(Host) -> + Base = case Host of + global -> + "/admin/"; + _ -> + "/admin/server/" ++ Host ++ "/" + end, + " html,body { background: white; margin: 0; @@ -130,7 +181,7 @@ html>body #container { height: 55px; padding: 0; margin: 0; - background: transparent url(\"/admin/logo-fill.png\"); + background: transparent url(\"" ++ Base ++ "logo-fill.png\"); } #header h1 a { @@ -141,7 +192,7 @@ html>body #container { height: 55px; padding: 0; margin: 0; - background: transparent url(\"/admin/logo.png\") no-repeat; + background: transparent url(\"" ++ Base ++ "logo.png\") no-repeat; display: block; text-indent: -700em; } @@ -466,7 +517,8 @@ logo_fill() -> "1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO" "RK5CYII="). -process_admin(#request{us = US, +process_admin(global, + #request{us = US, path = [], q = Query, lang = Lang} = Request) -> @@ -476,41 +528,63 @@ process_admin(#request{us = US, ?ACT("/admin/acls-raw/", "(raw)")]), ?LI([?ACT("/admin/access/", "Access Rules"), ?C(" "), ?ACT("/admin/access-raw/", "(raw)")]), - ?LI([?ACT("/admin/users/", "Users")]), - ?LI([?ACT("/admin/online-users/", "Online Users")]), - ?LI([?ACT("/admin/last-activity/", "Last Activity")]), ?LI([?ACT("/admin/nodes/", "Nodes")]), ?LI([?ACT("/admin/stats/", "Statistics")]) + ] + ) + ], global, Lang); + +process_admin(Host, + #request{us = US, + path = [], + q = Query, + lang = Lang} = Request) -> + Base = "/admin/server/" ++ Host ++ "/", + make_xhtml([?XCT("h1", "ejabberd administration"), + ?XE("ul", + [?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "), + ?ACT(Base ++ "acls-raw/", "(raw)")]), + ?LI([?ACT(Base ++ "access/", "Access Rules"), ?C(" "), + ?ACT(Base ++ "access-raw/", "(raw)")]), + ?LI([?ACT(Base ++ "users/", "Users")]), + ?LI([?ACT(Base ++ "online-users/", "Online Users")]), + ?LI([?ACT(Base ++ "last-activity/", "Last Activity")]), + ?LI([?ACT(Base ++ "nodes/", "Nodes")]), + ?LI([?ACT(Base ++ "stats/", "Statistics")]) ] ++ case lists:member(mod_shared_roster, - gen_mod:loaded_modules()) of + gen_mod:loaded_modules(Host)) of true -> - [?LI([?ACT("/admin/shared-roster/", "Shared Roster")])]; + [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])]; false -> [] end ) - ], Lang); + ], Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["style.css"], q = Query, lang = Lang} = Request) -> - {200, [{"Content-Type", "text/css"}], css()}; + {200, [{"Content-Type", "text/css"}], css(Host)}; -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["logo.png"], q = Query, lang = Lang} = Request) -> {200, [{"Content-Type", "image/png"}], logo()}; -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["logo-fill.png"], q = Query, lang = Lang} = Request) -> {200, [{"Content-Type", "image/png"}], logo_fill()}; -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["acls-raw"], q = Query, lang = Lang} = Request) -> @@ -520,7 +594,7 @@ process_admin(#request{us = US, {ok, Tokens, _} -> case erl_parse:parse_term(Tokens) of {ok, NewACLs} -> - case acl:add_list(NewACLs, true) of + case acl:add_list(Host, NewACLs, true) of ok -> ok; _ -> @@ -535,7 +609,11 @@ process_admin(#request{us = US, _ -> nothing end, - ACLs = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])), + ACLs = lists:flatten( + io_lib:format( + "~p.", [lists:keysort( + 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, + [], [{{acl, '$1', '$2'}}]}]))])), make_xhtml([?XCT("h1", "ejabberd access control lists configuration")] ++ case Res of ok -> [?CT("submitted"), ?P]; @@ -550,9 +628,10 @@ process_admin(#request{us = US, ?BR, ?INPUTT("submit", "submit", "Submit") ]) - ], Lang); + ], Host, Lang); -process_admin(#request{method = Method, +process_admin(Host, + #request{method = Method, us = US, path = ["acls"], q = Query, @@ -560,12 +639,12 @@ process_admin(#request{method = Method, ?INFO_MSG("query: ~p", [Query]), Res = case Method of 'POST' -> - case catch acl_parse_query(Query) of + case catch acl_parse_query(Host, Query) of {'EXIT', _} -> error; NewACLs -> - ?INFO_MSG("NewACLs: ~p", [NewACLs]), - case acl:add_list(NewACLs, true) of + ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]), + case acl:add_list(Host, NewACLs, true) of ok -> ?INFO_MSG("NewACLs: ok", []), ok; @@ -576,7 +655,9 @@ process_admin(#request{method = Method, _ -> nothing end, - ACLs = lists:keysort(2, ets:tab2list(acl)), + ACLs = lists:keysort( + 2, ets:select(acl, [{{acl, {'$1', Host}, '$2'}, + [], [{{acl, '$1', '$2'}}]}])), make_xhtml([?XCT("h1", "ejabberd access control lists configuration")] ++ case Res of ok -> [?CT("submitted"), ?P]; @@ -591,9 +672,10 @@ process_admin(#request{method = Method, ?C(" "), ?INPUTT("submit", "submit", "Submit") ]) - ], Lang); + ], Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["access-raw"], q = Query, lang = Lang} = Request) -> @@ -601,17 +683,18 @@ process_admin(#request{us = US, fun(Rs) -> mnesia:transaction( fun() -> - Os = mnesia:select(config, - [{{config, {access, '$1'}, '$2'}, - [], - ['$_']}]), + Os = mnesia:select( + config, + [{{config, {access, '$1', Host}, '$2'}, + [], + ['$_']}]), lists:foreach(fun(O) -> mnesia:delete_object(O) end, Os), lists:foreach( fun({access, Name, Rules}) -> mnesia:write({config, - {access, Name}, + {access, Name, Host}, Rules}) end, Rs) end) @@ -641,7 +724,7 @@ process_admin(#request{us = US, lists:flatten( io_lib:format( "~p.", [ets:select(config, - [{{config, {access, '$1'}, '$2'}, + [{{config, {access, '$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}])])), make_xhtml([?XCT("h1", "ejabberd access rules configuration")] ++ @@ -658,9 +741,10 @@ process_admin(#request{us = US, ?BR, ?INPUTT("submit", "submit", "Submit") ]) - ], Lang); + ], Host, Lang); -process_admin(#request{method = Method, +process_admin(Host, + #request{method = Method, us = US, path = ["access"], q = Query, @@ -668,7 +752,7 @@ process_admin(#request{method = Method, ?INFO_MSG("query: ~p", [Query]), Res = case Method of 'POST' -> - case catch access_parse_query(Query) of + case catch access_parse_query(Host, Query) of {'EXIT', _} -> error; ok -> @@ -679,7 +763,7 @@ process_admin(#request{method = Method, end, AccessRules = ets:select(config, - [{{config, {access, '$1'}, '$2'}, + [{{config, {access, '$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}]), make_xhtml([?XCT("h1", "ejabberd access rules configuration")] ++ @@ -694,9 +778,10 @@ process_admin(#request{method = Method, ?BR, ?INPUTT("submit", "delete", "Delete Selected") ]) - ], Lang); + ], Host, Lang); -process_admin(#request{method = Method, +process_admin(Host, + #request{method = Method, us = US, path = ["access", SName], q = Query, @@ -708,7 +793,7 @@ process_admin(#request{method = Method, case parse_access_rule(String) of {ok, Rs} -> ejabberd_config:add_global_option( - {access, Name}, Rs), + {access, Name, Host}, Rs), ok; _ -> error @@ -716,7 +801,7 @@ process_admin(#request{method = Method, _ -> nothing end, - Rules = case ejabberd_config:get_global_option({access, Name}) of + Rules = case ejabberd_config:get_global_option({access, Name, Host}) of undefined -> []; Rs1 -> @@ -734,34 +819,38 @@ process_admin(#request{method = Method, ?BR, ?INPUTT("submit", "submit", "Submit") ]) - ], Lang); + ], Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["users"], q = Query, - lang = Lang} = Request) -> - Res = list_users(Query, Lang), - make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); + lang = Lang} = Request) when is_list(Host) -> + Res = list_users(Host, Query, Lang), + make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["users", Diap], q = Query, - lang = Lang} = Request) -> - Res = list_users_in_diapason(Diap, Lang), - make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); + lang = Lang} = Request) when is_list(Host) -> + Res = list_users_in_diapason(Host, Diap, Lang), + make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["online-users"], q = Query, - lang = Lang} = Request) -> - Res = list_online_users(Lang), - make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); + lang = Lang} = Request) when is_list(Host) -> + Res = list_online_users(Host, Lang), + make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Host, Lang); -process_admin(#request{method = Method, +process_admin(Host, + #request{method = Method, us = US, path = ["last-activity"], q = Query, - lang = Lang} = Request) -> + lang = Lang} = Request) when is_list(Host) -> ?INFO_MSG("query: ~p", [Query]), Month = case lists:keysearch("period", 1, Query) of {value, {_, Val}} -> @@ -771,9 +860,9 @@ process_admin(#request{method = Method, end, Res = case lists:keysearch("ordinary", 1, Query) of {value, {_, _}} -> - list_last_activity(Lang, false, Month); + list_last_activity(Host, Lang, false, Month); _ -> - list_last_activity(Lang, true, Month) + list_last_activity(Host, Lang, true, Month) end, make_xhtml([?XCT("h1", "Users last activity")] ++ [?XAE("form", [{"method", "post"}], @@ -795,71 +884,80 @@ process_admin(#request{method = Method, ?C(" "), ?INPUTT("submit", "integral", "Show Integral Table") ])] ++ - Res, Lang); + Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["stats"], q = Query, lang = Lang} = Request) -> - Res = get_stats(Lang), - make_xhtml([?XCT("h1", "ejabberd stats")] ++ Res, Lang); + Res = get_stats(Host, Lang), + make_xhtml([?XCT("h1", "ejabberd stats")] ++ Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["user", U], q = Query, lang = Lang} = Request) -> - Res = user_info(U, Query, Lang), - make_xhtml(Res, Lang); + Res = user_info(U, Host, Query, Lang), + make_xhtml(Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["user", U, "queue"], q = Query, lang = Lang} = Request) -> - Res = user_queue(U, Query, Lang), - make_xhtml(Res, Lang); + Res = user_queue(U, Host, Query, Lang), + make_xhtml(Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["user", U, "roster"], q = Query, lang = Lang} = Request) -> - Res = user_roster(U, Query, Lang, true), - make_xhtml(Res, Lang); + Res = user_roster(U, Host, Query, Lang, true), + make_xhtml(Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["nodes"], q = Query, lang = Lang} = Request) -> Res = get_nodes(Lang), - make_xhtml(Res, Lang); + make_xhtml(Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["node", SNode | NPath], q = Query, lang = Lang} = Request) -> case search_running_node(SNode) of false -> - make_xhtml([?XCT("h1", "Node not found")], Lang); + make_xhtml([?XCT("h1", "Node not found")], Host, Lang); Node -> - Res = get_node(Node, NPath, Query, Lang), - make_xhtml(Res, Lang) + Res = get_node(Host, Node, NPath, Query, Lang), + make_xhtml(Res, Host, Lang) end; -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["shared-roster"], q = Query, lang = Lang} = Request) -> Res = list_shared_roster_groups(Query, Lang), - make_xhtml(Res, Lang); + make_xhtml(Res, Host, Lang); -process_admin(#request{us = US, +process_admin(Host, + #request{us = US, path = ["shared-roster", Group], q = Query, lang = Lang} = Request) -> Res = shared_roster_group(Group, Query, Lang), - make_xhtml(Res, Lang); + make_xhtml(Res, Host, Lang); -process_admin(#request{lang = Lang}) -> - setelement(1, make_xhtml([?XC("h1", "Not found")], Lang), 404). +process_admin(Host, + #request{lang = Lang}) -> + setelement(1, make_xhtml([?XC("h1", "Not found")], Host, Lang), 404). @@ -924,8 +1022,9 @@ term_to_id(T) -> jlib:encode_base64(binary_to_list(term_to_binary(T))). -acl_parse_query(Query) -> - ACLs = ets:tab2list(acl), +acl_parse_query(Host, Query) -> + ACLs = ets:select(acl, [{{acl, {'$1', Host}, '$2'}, + [], [{{acl, '$1', '$2'}}]}]), case lists:keysearch("submit", 1, Query) of {value, _} -> acl_parse_submit(ACLs, Query); @@ -977,7 +1076,7 @@ string_to_spec("server", Val) -> {server, Val}; string_to_spec("user_server", Val) -> #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val), - {user_server, U, S}; + {user, U, S}; string_to_spec("raw", Val) -> {ok, Tokens, _} = erl_scan:string(Val ++ "."), {ok, NewSpec} = erl_parse:parse_term(Tokens), @@ -1016,31 +1115,31 @@ access_rules_to_xhtml(AccessRules, Lang) -> )] )]). -access_parse_query(Query) -> +access_parse_query(Host, Query) -> AccessRules = ets:select(config, - [{{config, {access, '$1'}, '$2'}, + [{{config, {access, '$1', Host}, '$2'}, [], [{{access, '$1', '$2'}}]}]), case lists:keysearch("addnew", 1, Query) of {value, _} -> - access_parse_addnew(AccessRules, Query); + access_parse_addnew(AccessRules, Host, Query); _ -> case lists:keysearch("delete", 1, Query) of {value, _} -> - access_parse_delete(AccessRules, Query) + access_parse_delete(AccessRules, Host, Query) end end. -access_parse_addnew(AccessRules, Query) -> +access_parse_addnew(AccessRules, Host, Query) -> case lists:keysearch("namenew", 1, Query) of {value, {_, String}} when String /= "" -> Name = list_to_atom(String), - ejabberd_config:add_global_option({access, Name}, []), + ejabberd_config:add_global_option({access, Name, Host}, []), ok end. -access_parse_delete(AccessRules, Query) -> +access_parse_delete(AccessRules, Host, Query) -> lists:foreach( fun({access, Name, _Rules} = AccessRule) -> ID = term_to_id(AccessRule), @@ -1048,7 +1147,7 @@ access_parse_delete(AccessRules, Query) -> true -> mnesia:transaction( fun() -> - mnesia:delete({config, {access, Name}}) + mnesia:delete({config, {access, Name, Host}}) end); _ -> ok @@ -1091,9 +1190,9 @@ parse_access_rule(Text) -> -list_users(Query, Lang) -> +list_users(Host, Query, Lang) -> Res = list_users_parse_query(Query), - Users = ejabberd_auth:dirty_get_registered_users(), + Users = ejabberd_auth:get_vh_registered_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), FUsers = case length(SUsers) of @@ -1162,8 +1261,8 @@ list_users_parse_query(Query) -> end. -list_users_in_diapason(Diap, Lang) -> - Users = ejabberd_auth:dirty_get_registered_users(), +list_users_in_diapason(Host, Diap, Lang) -> + Users = ejabberd_auth:get_vh_registered_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), {ok, [S1, S2]} = regexp:split(Diap, "-"), N1 = list_to_integer(S1), @@ -1184,7 +1283,7 @@ list_given_users(Users, Prefix, Lang) -> US = {User, Server}, QueueLen = length(mnesia:dirty_read({offline_msg, US})), FQueueLen = [?AC(Prefix ++ "user/" ++ - User ++ "@" ++ Server ++ "/queue/", + User ++ "/queue/", integer_to_list(QueueLen))], FLast = case ejabberd_sm:get_user_resources(User, Server) of @@ -1208,8 +1307,7 @@ list_given_users(Users, Prefix, Lang) -> ?T("Online") end, ?XE("tr", - [?XE("td", [?AC(Prefix ++ "user/" ++ - us_to_list(US) ++ "/", + [?XE("td", [?AC(Prefix ++ "user/" ++ User ++ "/", us_to_list(US))]), ?XE("td", FQueueLen), ?XC("td", FLast)]) @@ -1223,14 +1321,13 @@ su_to_list({Server, User}) -> jlib:jid_to_string({User, Server, ""}). -get_stats(Lang) -> +get_stats(global, Lang) -> OnlineUsers = mnesia:table_info(presence, size), AuthUsers = mnesia:table_info(session, size), RegisteredUsers = mnesia:table_info(passwd, size), S2SConns = ejabberd_s2s:dirty_get_connections(), S2SConnections = length(S2SConns), S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])), - [?XAE("table", [], [?XE("tbody", [?XE("tr", [?XCT("td", "Registered users"), @@ -1244,22 +1341,31 @@ get_stats(Lang) -> ?XE("tr", [?XCT("td", "Outgoing S2S servers"), ?XC("td", integer_to_list(S2SServers))]) ]) + ])]; + +get_stats(Host, Lang) -> + OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)), + RegisteredUsers = length(ejabberd_auth:get_vh_registered_users(Host)), + [?XAE("table", [], + [?XE("tbody", + [?XE("tr", [?XCT("td", "Registered users"), + ?XC("td", integer_to_list(RegisteredUsers))]), + ?XE("tr", [?XCT("td", "Online users"), + ?XC("td", integer_to_list(OnlineUsers))]) + ]) ])]. -list_online_users(_Lang) -> - Users = [{S, U} || {U, S, R} <- ejabberd_sm:dirty_get_sessions_list()], +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(SU) -> - [?AC("../user/" ++ su_to_list(SU) ++ "/", su_to_list(SU)), ?BR] + fun({S, U} = SU) -> + [?AC("../user/" ++ U ++ "/", su_to_list(SU)), ?BR] end, SUsers). -user_info(SUser, Query, Lang) -> - UJID = jlib:string_to_jid(SUser), - User = UJID#jid.user, - Server = UJID#jid.server, - US = {UJID#jid.luser, UJID#jid.lserver}, +user_info(User, Server, Query, Lang) -> + US = {jlib:nodeprep(User), jlib:nameprep(Server)}, Res = user_parse_query(User, Server, Query), Resources = ejabberd_sm:get_user_resources(User, Server), FResources = @@ -1315,11 +1421,8 @@ user_parse_query(User, Server, Query) -> end. -user_queue(SUser, Query, Lang) -> - UJID = jlib:string_to_jid(SUser), - User = UJID#jid.user, - Server = UJID#jid.server, - US = {UJID#jid.luser, UJID#jid.lserver}, +user_queue(User, Server, Query, Lang) -> + US = {jlib:nodeprep(User), jlib:nameprep(Server)}, Res = user_queue_parse_query(US, Query), Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, US})), FMsgs = @@ -1409,11 +1512,8 @@ ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. -user_roster(SUser, Query, Lang, Admin) -> - UJID = jlib:string_to_jid(SUser), - User = UJID#jid.user, - Server = UJID#jid.server, - US = {UJID#jid.luser, UJID#jid.lserver}, +user_roster(User, Server, Query, Lang, Admin) -> + US = {jlib:nodeprep(User), jlib:nameprep(Server)}, Items1 = mnesia:dirty_index_read(roster, US, #roster.us), Res = user_roster_parse_query(User, Server, Items1, Query, Admin), Items = mnesia:dirty_index_read(roster, US, #roster.us), @@ -1556,7 +1656,7 @@ user_roster_item_parse_query(User, Server, Items, Query) -> nothing. -list_last_activity(Lang, Integral, Period) -> +list_last_activity(Host, Lang, Integral, Period) -> {MegaSecs, Secs, _MicroSecs} = now(), TimeStamp = MegaSecs * 1000000 + Secs, case Period of @@ -1571,7 +1671,7 @@ list_last_activity(Lang, Integral, Period) -> Days = 31 end, case catch mnesia:dirty_select( - last_activity, [{{last_activity, '_', '$1', '_'}, + last_activity, [{{last_activity, {'_', Host}, '$1', '_'}, [{'>', '$1', TS}], [{'trunc', {'/', {'-', TimeStamp, '$1'}, @@ -1677,7 +1777,7 @@ search_running_node(SNode, [Node | Nodes]) -> search_running_node(SNode, Nodes) end. -get_node(Node, [], Query, Lang) -> +get_node(global, Node, [], Query, Lang) -> Res = node_parse_query(Node, Query), [?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++ case Res of @@ -1689,7 +1789,6 @@ get_node(Node, [], Query, Lang) -> [?LI([?ACT("db/", "DB Management")]), ?LI([?ACT("backup/", "Backup Management")]), ?LI([?ACT("ports/", "Listened Ports Management")]), - ?LI([?ACT("modules/", "Modules Management")]), ?LI([?ACT("stats/", "Statistics")]) ]), ?XAE("form", [{"method", "post"}], @@ -1698,7 +1797,13 @@ get_node(Node, [], Query, Lang) -> ?INPUTT("submit", "stop", "Stop")]) ]; -get_node(Node, ["db"], Query, Lang) -> +get_node(Host, Node, [], Query, Lang) -> + [?XC("h1", ?T("Node ") ++ atom_to_list(Node)), + ?XE("ul", + [?LI([?ACT("modules/", "Modules Management")])]) + ]; + +get_node(global, Node, ["db"], Query, Lang) -> case rpc:call(Node, mnesia, system_info, [tables]) of {badrpc, _Reason} -> [?XCT("h1", "RPC call error")]; @@ -1765,7 +1870,7 @@ get_node(Node, ["db"], Query, Lang) -> )])])] end; -get_node(Node, ["backup"], Query, Lang) -> +get_node(global, Node, ["backup"], Query, Lang) -> Res = node_backup_parse_query(Node, Query), [?XC("h1", ?T("Backup Management at ") ++ atom_to_list(Node)), ?XAE("form", [{"method", "post"}], @@ -1810,7 +1915,7 @@ get_node(Node, ["backup"], Query, Lang) -> ]) ])])]; -get_node(Node, ["ports"], Query, Lang) -> +get_node(global, Node, ["ports"], Query, Lang) -> Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]), Res = case catch node_ports_parse_query(Node, Ports, Query) of submitted -> @@ -1832,9 +1937,9 @@ get_node(Node, ["ports"], Query, Lang) -> [node_ports_to_xhtml(NewPorts, Lang)]) ]; -get_node(Node, ["modules"], Query, Lang) -> +get_node(Host, Node, ["modules"], Query, Lang) when is_list(Host) -> Modules = rpc:call(Node, gen_mod, loaded_modules_with_opts, []), - Res = case catch node_modules_parse_query(Node, Modules, Query) of + Res = case catch node_modules_parse_query(Host, Node, Modules, Query) of submitted -> ok; {'EXIT', Reason} -> @@ -1843,7 +1948,8 @@ get_node(Node, ["modules"], Query, Lang) -> _ -> nothing end, - NewModules = lists:sort(rpc:call(Node, gen_mod, loaded_modules_with_opts, [])), + NewModules = lists:sort( + rpc:call(Node, gen_mod, loaded_modules_with_opts, [Host])), [?XC("h1", ?T("Modules at ") ++ atom_to_list(Node))] ++ case Res of ok -> [?CT("submitted"), ?P]; @@ -1854,7 +1960,7 @@ get_node(Node, ["modules"], Query, Lang) -> [node_modules_to_xhtml(NewModules, Lang)]) ]; -get_node(Node, ["stats"], Query, Lang) -> +get_node(global, Node, ["stats"], Query, Lang) -> UpTime = rpc:call(Node, erlang, statistics, [wall_clock]), UpTimeS = io_lib:format("~.3f", [element(1, UpTime)/1000]), CPUTime = rpc:call(Node, erlang, statistics, [runtime]), @@ -1897,7 +2003,7 @@ get_node(Node, ["stats"], Query, Lang) -> ]) ])]; -get_node(Node, NPath, Query, Lang) -> +get_node(Host, Node, NPath, Query, Lang) -> [?XCT("h1", "Not found")]. @@ -2133,7 +2239,7 @@ node_modules_to_xhtml(Modules, Lang) -> )] )]). -node_modules_parse_query(Node, Modules, Query) -> +node_modules_parse_query(Host, Node, Modules, Query) -> lists:foreach( fun({Module, _Opts1}) -> SModule = atom_to_list(Module), @@ -2143,13 +2249,13 @@ node_modules_parse_query(Node, Modules, Query) -> lists:keysearch("opts" ++ SModule, 1, Query), {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, stop_module, [Module]), - rpc:call(Node, gen_mod, start_module, [Module, Opts]), + rpc:call(Node, gen_mod, stop_module, [Host, Module]), + rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]), throw(submitted); _ -> case lists:keysearch("stop" ++ SModule, 1, Query) of {value, _} -> - rpc:call(Node, gen_mod, stop_module, [Module]), + rpc:call(Node, gen_mod, stop_module, [Host, Module]), throw(submitted); _ -> ok @@ -2165,7 +2271,7 @@ node_modules_parse_query(Node, Modules, Query) -> Module = list_to_atom(SModule), {ok, Tokens, _} = erl_scan:string(SOpts ++ "."), {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, start_module, [Module, Opts]), + rpc:call(Node, gen_mod, start_module, [Host, Module, Opts]), throw(submitted); _ -> ok |