+2007-08-23 Alexey Shchepin <alexey@process-one.net>
+
+ * src/web/ejabberd_web_admin.erl: Added hooks to allow plugins to
+ add their pages without modifying ejabberd_web_admin.erl (thanks
+ to Badlop)
+ * src/web/ejabberd_web_admin.hrl: Macro definitions moved here
+ * src/mod_shared_roster.erl: Updated
+ * src/mod_offline.erl: Likewise
+ * src/mod_offline_odbc.erl: Likewise
+
2007-08-22 Alexey Shchepin <alexey@process-one.net>
* src/jlib.erl: Use http_base_64:decode if available
pop_offline_messages/3,
remove_expired_messages/0,
remove_old_messages/1,
- remove_user/2]).
+ remove_user/2,
+ webadmin_page/3,
+ webadmin_user/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
+-include("web/ejabberd_http.hrl").
+-include("web/ejabberd_web_admin.hrl").
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
+ ejabberd_hooks:add(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
+ ejabberd_hooks:add(webadmin_user, Host,
+ ?MODULE, webadmin_user, 50),
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [MaxOfflineMsgs])).
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
+ ejabberd_hooks:delete(webadmin_user, Host,
+ ?MODULE, webadmin_user, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
{wait, Proc}.
To,
From, Err)
end, Msgs).
+
+
+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.
+
+user_queue(User, Server, Query, Lang) ->
+ US = {jlib:nodeprep(User), jlib:nameprep(Server)},
+ Res = user_queue_parse_query(US, Query),
+ Msgs = lists:keysort(#offline_msg.timestamp,
+ mnesia:dirty_read({offline_msg, US})),
+ FMsgs =
+ lists:map(
+ fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
+ packet = {xmlelement, Name, Attrs, Els}} = Msg) ->
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
+ {{Year, Month, Day}, {Hour, Minute, Second}} =
+ calendar:now_to_local_time(TimeStamp),
+ Time = lists:flatten(
+ io_lib:format(
+ "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [Year, Month, Day, Hour, Minute, Second])),
+ SFrom = jlib:jid_to_string(From),
+ STo = jlib:jid_to_string(To),
+ Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
+ Packet = {xmlelement, Name, Attrs2, Els},
+ FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
+ ?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)])]
+ )
+ end, Msgs),
+ [?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
+ [us_to_list(US)]))] ++
+ case Res of
+ ok -> [?CT("Submitted"), ?P];
+ nothing -> []
+ end ++
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
+ [?XE("table",
+ [?XE("thead",
+ [?XE("tr",
+ [?X("td"),
+ ?XCT("td", "Time"),
+ ?XCT("td", "From"),
+ ?XCT("td", "To"),
+ ?XCT("td", "Packet")
+ ])]),
+ ?XE("tbody",
+ if
+ FMsgs == [] ->
+ [?XE("tr",
+ [?XAC("td", [{"colspan", "4"}], " ")]
+ )];
+ true ->
+ FMsgs
+ end
+ )]),
+ ?BR,
+ ?INPUTT("submit", "delete", "Delete Selected")
+ ])].
+
+user_queue_parse_query(US, Query) ->
+ case lists:keysearch("delete", 1, Query) of
+ {value, _} ->
+ Msgs = lists:keysort(#offline_msg.timestamp,
+ mnesia:dirty_read({offline_msg, US})),
+ F = fun() ->
+ lists:foreach(
+ fun(Msg) ->
+ ID = jlib:encode_base64(
+ binary_to_list(term_to_binary(Msg))),
+ case lists:member({"selected", ID}, Query) of
+ true ->
+ mnesia:delete_object(Msg);
+ false ->
+ ok
+ end
+ end, Msgs)
+ end,
+ mnesia:transaction(F),
+ ok;
+ false ->
+ nothing
+ end.
+
+us_to_list({User, Server}) ->
+ jlib:jid_to_string({User, Server, ""}).
+
+webadmin_user(Acc, User, Server, Lang) ->
+ US = {jlib:nodeprep(User), jlib:nameprep(Server)},
+ QueueLen = length(mnesia:dirty_read({offline_msg, US})),
+ FQueueLen = [?AC("queue/",
+ integer_to_list(QueueLen))],
+ Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
-%%% Id : $Id$
%%%----------------------------------------------------------------------
+
-module(mod_offline_odbc).
-author('alexey@sevcom.net').
stop/1,
store_packet/3,
pop_offline_messages/3,
- remove_user/2]).
+ remove_user/2,
+ webadmin_page/3,
+ webadmin_user/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
+-include("web/ejabberd_http.hrl").
+-include("web/ejabberd_web_admin.hrl").
-record(offline_msg, {user, timestamp, expire, from, to, packet}).
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
+ ejabberd_hooks:add(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
+ ejabberd_hooks:add(webadmin_user, Host,
+ ?MODULE, webadmin_user, 50),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, init, [Host])).
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
+ ejabberd_hooks:delete(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
+ ejabberd_hooks:delete(webadmin_user, Host,
+ ?MODULE, webadmin_user, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
ok.
find_x_expire(TimeStamp, [El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EXPIRE ->
- case xml:get_tag_attr_s("seconds", El) of
- Val ->
- case catch list_to_integer(Val) of
- {'EXIT', _} ->
- never;
- Int when Int > 0 ->
- {MegaSecs, Secs, MicroSecs} = TimeStamp,
- S = MegaSecs * 1000000 + Secs + Int,
- MegaSecs1 = S div 1000000,
- Secs1 = S rem 1000000,
- {MegaSecs1, Secs1, MicroSecs};
- _ ->
- never
- end;
+ Val = xml:get_tag_attr_s("seconds", El),
+ case catch list_to_integer(Val) of
+ {'EXIT', _} ->
+ never;
+ Int when Int > 0 ->
+ {MegaSecs, Secs, MicroSecs} = TimeStamp,
+ S = MegaSecs * 1000000 + Secs + Int,
+ MegaSecs1 = S div 1000000,
+ Secs1 = S rem 1000000,
+ {MegaSecs1, Secs1, MicroSecs};
_ ->
never
end;
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username).
+
+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.
+
+user_queue(User, Server, Query, Lang) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ Username = ejabberd_odbc:escape(LUser),
+ US = {LUser, LServer},
+ Res = user_queue_parse_query(Username, LServer, Query),
+ Msgs = case catch ejabberd_odbc:sql_query(
+ LServer,
+ ["select username, xml from spool"
+ " where username='", Username, "'"
+ " order by seq;"]) of
+ {selected, ["username", "xml"], Rs} ->
+ lists:flatmap(
+ fun({_, XML}) ->
+ case xml_stream:parse_element(XML) of
+ {error, _Reason} ->
+ [];
+ El ->
+ [El]
+ end
+ end, Rs);
+ _ ->
+ []
+ end,
+ FMsgs =
+ lists:map(
+ fun({xmlelement, Name, Attrs, Els} = Msg) ->
+ ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
+ Packet = Msg,
+ FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
+ ?XE("tr",
+ [?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
+ ?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
+ )
+ end, Msgs),
+ [?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
+ [us_to_list(US)]))] ++
+ case Res of
+ ok -> [?CT("Submitted"), ?P];
+ nothing -> []
+ end ++
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
+ [?XE("table",
+ [?XE("thead",
+ [?XE("tr",
+ [?X("td"),
+ ?XCT("td", "Packet")
+ ])]),
+ ?XE("tbody",
+ if
+ FMsgs == [] ->
+ [?XE("tr",
+ [?XAC("td", [{"colspan", "4"}], " ")]
+ )];
+ true ->
+ FMsgs
+ end
+ )]),
+ ?BR,
+ ?INPUTT("submit", "delete", "Delete Selected")
+ ])].
+
+user_queue_parse_query(Username, LServer, Query) ->
+ case lists:keysearch("delete", 1, Query) of
+ {value, _} ->
+ Msgs = case catch ejabberd_odbc:sql_query(
+ LServer,
+ ["select xml, seq from spool"
+ " where username='", Username, "'"
+ " order by seq;"]) of
+ {selected, ["xml", "seq"], Rs} ->
+ lists:flatmap(
+ fun({XML, Seq}) ->
+ case xml_stream:parse_element(XML) of
+ {error, _Reason} ->
+ [];
+ El ->
+ [{El, Seq}]
+ end
+ end, Rs);
+ _ ->
+ []
+ end,
+ F = fun() ->
+ lists:foreach(
+ fun({Msg, Seq}) ->
+ ID = jlib:encode_base64(
+ binary_to_list(term_to_binary(Msg))),
+ case lists:member({"selected", ID}, Query) of
+ true ->
+ SSeq = ejabberd_odbc:escape(Seq),
+ catch ejabberd_odbc:sql_query(
+ LServer,
+ ["delete from spool"
+ " where username='", Username, "'"
+ " and seq='", SSeq, "';"]);
+ false ->
+ ok
+ end
+ end, Msgs)
+ end,
+ mnesia:transaction(F),
+ ok;
+ false ->
+ nothing
+ end.
+
+us_to_list({User, Server}) ->
+ jlib:jid_to_string({User, Server, ""}).
+
+webadmin_user(Acc, User, Server, Lang) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ Username = ejabberd_odbc:escape(LUser),
+ QueueLen = case catch ejabberd_odbc:sql_query(
+ LServer,
+ ["select count(*) from spool"
+ " where username='", Username, "';"]) of
+ {selected, [_], [{SCount}]} ->
+ SCount;
+ _ ->
+ 0
+ end,
+ FQueueLen = [?AC("queue/", QueueLen)],
+ Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen.
-behaviour(gen_mod).
-export([start/2, stop/1,
+ webadmin_menu/2, webadmin_page/3,
get_user_roster/2,
get_subscription_lists/3,
get_jid_info/4,
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
+-include("web/ejabberd_http.hrl").
+-include("web/ejabberd_web_admin.hrl").
-record(sr_group, {group_host, opts}).
-record(sr_user, {us, group_host}).
{type, bag},
{attributes, record_info(fields, sr_user)}]),
mnesia:add_table_index(sr_user, group_host),
+ ejabberd_hooks:add(webadmin_menu_host, Host,
+ ?MODULE, webadmin_menu, 70),
+ ejabberd_hooks:add(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
ejabberd_hooks:add(roster_get, Host,
?MODULE, get_user_roster, 70),
ejabberd_hooks:add(roster_in_subscription, Host,
% ?MODULE, remove_user, 50),
stop(Host) ->
+ ejabberd_hooks:delete(webadmin_menu_host, Host,
+ ?MODULE, webadmin_menu, 70),
+ ejabberd_hooks:delete(webadmin_page_host, Host,
+ ?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(roster_get, Host,
?MODULE, get_user_roster, 70),
ejabberd_hooks:delete(roster_in_subscription, Host,
mnesia:delete_object(R)
end,
mnesia:transaction(F).
+
+
+%%---------------------
+%% Web Admin
+%%---------------------
+
+webadmin_menu(Acc, _Host) ->
+ [{"shared-roster", "Shared Roster"} | Acc].
+
+webadmin_page(_, Host,
+ #request{us = US,
+ path = ["shared-roster"],
+ q = Query,
+ lang = Lang} = Request) ->
+ Res = list_shared_roster_groups(Host, Query, Lang),
+ {stop, 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};
+
+webadmin_page(Acc, _, _) -> Acc.
+
+list_shared_roster_groups(Host, Query, Lang) ->
+ Res = list_sr_groups_parse_query(Host, Query),
+ SRGroups = mod_shared_roster:list_groups(Host),
+ FGroups =
+ ?XAE("table", [],
+ [?XE("tbody",
+ lists:map(
+ fun(Group) ->
+ ?XE("tr",
+ [?XE("td", [?INPUT("checkbox", "selected",
+ Group)]),
+ ?XE("td", [?AC(Group ++ "/", Group)])
+ ]
+ )
+ end, lists:sort(SRGroups)) ++
+ [?XE("tr",
+ [?X("td"),
+ ?XE("td", [?INPUT("text", "namenew", "")]),
+ ?XE("td", [?INPUTT("submit", "addnew", "Add New")])
+ ]
+ )]
+ )]),
+ [?XC("h1", ?T("Shared Roster Groups"))] ++
+ case Res of
+ ok -> [?CT("Submitted"), ?P];
+ error -> [?CT("Bad format"), ?P];
+ nothing -> []
+ end ++
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
+ [FGroups,
+ ?BR,
+ ?INPUTT("submit", "delete", "Delete Selected")
+ ])
+ ].
+
+list_sr_groups_parse_query(Host, Query) ->
+ case lists:keysearch("addnew", 1, Query) of
+ {value, _} ->
+ list_sr_groups_parse_addnew(Host, Query);
+ _ ->
+ case lists:keysearch("delete", 1, Query) of
+ {value, _} ->
+ list_sr_groups_parse_delete(Host, Query);
+ _ ->
+ nothing
+ end
+ end.
+
+list_sr_groups_parse_addnew(Host, Query) ->
+ case lists:keysearch("namenew", 1, Query) of
+ {value, {_, Group}} when Group /= "" ->
+ mod_shared_roster:create_group(Host, Group),
+ ok;
+ _ ->
+ error
+ end.
+
+list_sr_groups_parse_delete(Host, Query) ->
+ SRGroups = mod_shared_roster:list_groups(Host),
+ lists:foreach(
+ fun(Group) ->
+ case lists:member({"selected", Group}, Query) of
+ true ->
+ mod_shared_roster:delete_group(Host, Group);
+ _ ->
+ ok
+ end
+ end, SRGroups),
+ ok.
+
+
+shared_roster_group(Host, Group, Query, Lang) ->
+ Res = shared_roster_group_parse_query(Host, Group, Query),
+ GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
+ Name = get_opt(GroupOpts, name, ""),
+ Description = get_opt(GroupOpts, description, ""),
+ AllUsers = get_opt(GroupOpts, all_users, false),
+ Disabled = false,
+ DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
+ Members = mod_shared_roster:get_group_explicit_users(Host, Group),
+ FMembers =
+ if
+ AllUsers ->
+ "@all@\n";
+ true ->
+ []
+ end ++ [[us_to_list(Member), $\n] || Member <- Members],
+ FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
+ FGroup =
+ ?XAE("table", [],
+ [?XE("tbody",
+ [?XE("tr",
+ [?XCT("td", "Name:"),
+ ?XE("td", [?INPUT("text", "name", Name)])
+ ]
+ ),
+ ?XE("tr",
+ [?XCT("td", "Description:"),
+ ?XE("td", [?XAC("textarea", [{"name", "description"},
+ {"rows", "3"},
+ {"cols", "20"}],
+ Description)])
+ ]
+ ),
+ ?XE("tr",
+ [?XCT("td", "Members:"),
+ ?XE("td", [?XAC("textarea", [{"name", "members"},
+ {"rows", "3"},
+ {"cols", "20"}],
+ FMembers)])
+ ]
+ ),
+ ?XE("tr",
+ [?XCT("td", "Displayed Groups:"),
+ ?XE("td", [?XAC("textarea", [{"name", "dispgroups"},
+ {"rows", "3"},
+ {"cols", "20"}],
+ FDisplayedGroups)])
+ ]
+ )]
+ )]),
+ [?XC("h1", ?T("Shared Roster Groups"))] ++
+ [?XC("h2", ?T("Group ") ++ Group)] ++
+ case Res of
+ ok -> [?CT("Submitted"), ?P];
+ error -> [?CT("Bad format"), ?P];
+ nothing -> []
+ end ++
+ [?XAE("form", [{"action", ""}, {"method", "post"}],
+ [FGroup,
+ ?BR,
+ ?INPUTT("submit", "submit", "Submit")
+ ])
+ ].
+
+shared_roster_group_parse_query(Host, Group, Query) ->
+ case lists:keysearch("submit", 1, Query) of
+ {value, _} ->
+ {value, {_, Name}} = lists:keysearch("name", 1, Query),
+ {value, {_, Description}} = lists:keysearch("description", 1, Query),
+ {value, {_, SMembers}} = lists:keysearch("members", 1, Query),
+ {value, {_, SDispGroups}} = lists:keysearch("dispgroups", 1, Query),
+ NameOpt =
+ if
+ Name == "" -> [];
+ true -> [{name, Name}]
+ end,
+ DescriptionOpt =
+ if
+ Description == "" -> [];
+ true -> [{description, Description}]
+ end,
+ DispGroups = string:tokens(SDispGroups, "\r\n"),
+ DispGroupsOpt =
+ if
+ DispGroups == [] -> [];
+ true -> [{displayed_groups, DispGroups}]
+ end,
+
+ OldMembers = mod_shared_roster:get_group_explicit_users(
+ Host, Group),
+ SJIDs = string:tokens(SMembers, ", \r\n"),
+ NewMembers =
+ lists:foldl(
+ fun(_SJID, error) -> error;
+ (SJID, USs) ->
+ case SJID of
+ "@all@" ->
+ USs;
+ _ ->
+ case jlib:string_to_jid(SJID) of
+ JID when is_record(JID, jid) ->
+ [{JID#jid.luser, JID#jid.lserver} | USs];
+ error ->
+ error
+ end
+ end
+ end, [], SJIDs),
+ AllUsersOpt =
+ case lists:member("@all@", SJIDs) of
+ true -> [{all_users, true}];
+ false -> []
+ end,
+
+ mod_shared_roster:set_group_opts(
+ Host, Group,
+ NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt),
+
+ if
+ NewMembers == error -> error;
+ true ->
+ AddedMembers = NewMembers -- OldMembers,
+ RemovedMembers = OldMembers -- NewMembers,
+ lists:foreach(
+ fun(US) ->
+ mod_shared_roster:remove_user_from_group(
+ Host, US, Group)
+ end, RemovedMembers),
+ lists:foreach(
+ fun(US) ->
+ mod_shared_roster:add_user_to_group(
+ Host, US, Group)
+ end, AddedMembers),
+ ok
+ end;
+ _ ->
+ nothing
+ end.
+
+get_opt(Opts, Opt, Default) ->
+ case lists:keysearch(Opt, 1, Opts) of
+ {value, {_, Val}} ->
+ Val;
+ false ->
+ Default
+ end.
+
+us_to_list({User, Server}) ->
+ jlib:jid_to_string({User, Server, ""}).
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Administration web interface
%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@sevcom.net>
-%%% Id : $Id$
%%%----------------------------------------------------------------------
%%% Copyright (c) 2004-2006 Alexey Shchepin
%%% Copyright (c) 2004-2006 Process One
%% External exports
-export([process/2,
- %% XXX bard: unexported, since it is only called from process/2 now
- %% process_admin/2,
list_users/4,
- list_users_in_diapason/4]).
+ list_users_in_diapason/4,
+ pretty_print_xml/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-
--define(X(Name), {xmlelement, Name, [], []}).
--define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
--define(XE(Name, Els), {xmlelement, Name, [], Els}).
--define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
--define(C(Text), {xmlcdata, Text}).
--define(XC(Name, Text), ?XE(Name, [?C(Text)])).
--define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
-
--define(T(Text), translate:translate(Lang, Text)).
--define(CT(Text), ?C(?T(Text))).
--define(XCT(Name, Text), ?XC(Name, ?T(Text))).
--define(XACT(Name, Attrs, Text), ?XAC(Name, Attrs, ?T(Text))).
-
-
--define(LI(Els), ?XE("li", Els)).
--define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
--define(AC(URL, Text), ?A(URL, [?C(Text)])).
--define(ACT(URL, Text), ?AC(URL, ?T(Text))).
--define(P, ?X("p")).
--define(BR, ?X("br")).
--define(INPUT(Type, Name, Value),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value}])).
--define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
--define(INPUTS(Type, Name, Value, Size),
- ?XA("input", [{"type", Type},
- {"name", Name},
- {"value", Value},
- {"size", Size}])).
--define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
+-include("ejabberd_web_admin.hrl").
process(["server", SHost | RPath], #request{auth = Auth,
end.
make_xhtml(Els, global, Lang) ->
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], []),
+ MenuItems2 = [?LI([?ACT("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", Lang},
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")])
- ]
+ ] ++ MenuItems2
)]),
?XAE("div",
[{"id", "content"}],
make_xhtml(Els, Host, Lang) ->
Base = "/admin/server/" ++ Host ++ "/",
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host]),
+ MenuItems2 = [?LI([?ACT(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
{200, [html],
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
{"xml:lang", Lang},
?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(Host)) of
- true ->
- [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
- false ->
- []
- end
+ ] ++ MenuItems2
)]),
?XAE("div",
[{"id", "content"}],
path = [],
q = Query,
lang = Lang} = Request) ->
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_main, [], []),
+ MenuItems2 = [?LI([?ACT("/admin/"++MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
make_xhtml([?XCT("h1", "Administration"),
?XE("ul",
[?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "),
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")])
- ]
+ ] ++ MenuItems2
)
], global, Lang);
q = Query,
lang = Lang} = Request) ->
Base = "/admin/server/" ++ Host ++ "/",
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], [Host]),
+ MenuItems2 = [?LI([?ACT(Base ++ MI_uri ++ "/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
make_xhtml([?XCT("h1", "Administration"),
?XE("ul",
[?LI([?ACT(Base ++ "acls/", "Access Control Lists"), ?C(" "),
?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(Host)) of
- true ->
- [?LI([?ACT(Base ++ "shared-roster/", "Shared Roster")])];
- false ->
- []
- end
+ ] ++ MenuItems2
)
], Host, Lang);
Res = user_info(U, Host, Query, Lang),
make_xhtml(Res, Host, Lang);
-process_admin(Host,
- #request{us = US,
- path = ["user", U, "queue"],
- q = Query,
- lang = Lang} = Request) ->
- Res = user_queue(U, Host, Query, Lang),
- make_xhtml(Res, Host, Lang);
-
process_admin(Host,
#request{us = US,
path = ["user", U, "roster"],
make_xhtml(Res, Host, Lang)
end;
-process_admin(Host,
- #request{us = US,
- path = ["shared-roster"],
- q = Query,
- lang = Lang} = Request) ->
- Res = list_shared_roster_groups(Host, Query, Lang),
- make_xhtml(Res, Host, Lang);
-
-process_admin(Host,
- #request{us = US,
- path = ["shared-roster", Group],
- q = Query,
- lang = Lang} = Request) ->
- Res = shared_roster_group(Host, Group, Query, Lang),
- make_xhtml(Res, Host, Lang);
-
-process_admin(Host,
- #request{lang = Lang}) ->
- setelement(1, make_xhtml([?XC("h1", "Not Found")], Host, Lang), 404).
+process_admin(Host, #request{lang = Lang} = Request) ->
+ {Hook, Opts} = case Host of
+ global -> {webadmin_page_main, [Request]};
+ Host -> {webadmin_page_host, [Host, Request]}
+ end,
+ case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
+ [] -> setelement(1, make_xhtml([?XC("h1", "Not Found")], Host, Lang), 404);
+ Res -> make_xhtml(Res, Host, Lang)
+ end.
)]
)]).
--define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
-
acl_spec_to_text({user, U}) ->
{user, U};
end, SUsers).
user_info(User, Server, Query, Lang) ->
- US = {jlib:nodeprep(User), jlib:nameprep(Server)},
+ LServer = jlib:nameprep(Server),
+ US = {jlib:nodeprep(User), LServer},
Res = user_parse_query(User, Server, Query),
Resources = ejabberd_sm:get_user_resources(User, Server),
FResources =
Password = ejabberd_auth:get_password_s(User, Server),
FPassword = [?INPUT("password", "password", Password), ?C(" "),
?INPUTT("submit", "chpassword", "Change Password")],
- QueueLen = length(mnesia:dirty_read({offline_msg, US})),
- FQueueLen = [?AC("queue/",
- integer_to_list(QueueLen))],
+ UserItems = ejabberd_hooks:run_fold(webadmin_user, LServer, [],
+ [User, Server, Lang]),
[?XC("h1", ?T("User ") ++ us_to_list(US))] ++
case Res of
ok -> [?CT("Submitted"), ?P];
[?XAE("form", [{"action", ""}, {"method", "post"}],
[?XCT("h3", "Connected Resources:")] ++ FResources ++
[?XCT("h3", "Password:")] ++ FPassword ++
- [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++
[?XE("h3", [?ACT("roster/", "Roster")])] ++
- [?BR, ?INPUTT("submit", "removeuser", "Remove User")])].
+ UserItems ++
+ [?P, ?INPUTT("submit", "removeuser", "Remove User")])].
user_parse_query(User, Server, Query) ->
end.
-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 =
- lists:map(
- fun({offline_msg, _US, TimeStamp, _Expire, From, To,
- {xmlelement, Name, Attrs, Els}} = Msg) ->
- ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
- {{Year, Month, Day}, {Hour, Minute, Second}} =
- calendar:now_to_local_time(TimeStamp),
- Time = lists:flatten(
- io_lib:format(
- "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
- [Year, Month, Day, Hour, Minute, Second])),
- SFrom = jlib:jid_to_string(From),
- STo = jlib:jid_to_string(To),
- Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
- Packet = jlib:remove_attr(
- "jeai-id", {xmlelement, Name, Attrs2, Els}),
- FPacket = pretty_print(Packet),
- ?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)])]
- )
- end, Msgs),
- [?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
- [us_to_list(US)]))] ++
- case Res of
- ok -> [?CT("Submitted"), ?P];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [?XE("table",
- [?XE("thead",
- [?XE("tr",
- [?X("td"),
- ?XCT("td", "Time"),
- ?XCT("td", "From"),
- ?XCT("td", "To"),
- ?XCT("td", "Packet")
- ])]),
- ?XE("tbody",
- if
- FMsgs == [] ->
- [?XE("tr",
- [?XAC("td", [{"colspan", "4"}], " ")]
- )];
- true ->
- FMsgs
- end
- )]),
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])].
-
-user_queue_parse_query(US, Query) ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- Msgs = lists:keysort(3, mnesia:dirty_read({offline_msg, US})),
- F = fun() ->
- lists:foreach(
- fun(Msg) ->
- ID = jlib:encode_base64(
- binary_to_list(term_to_binary(Msg))),
- case lists:member({"selected", ID}, Query) of
- true ->
- mnesia:delete_object(Msg);
- false ->
- ok
- end
- end, Msgs)
- end,
- mnesia:transaction(F),
- ok;
- false ->
- nothing
- end.
-
-
-record(roster, {usj,
us,
get_node(global, Node, [], Query, Lang) ->
Res = node_parse_query(Node, Query),
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_node, [], [Node]),
+ MenuItems2 = [?LI([?ACT(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
[?XC("h1", ?T("Node ") ++ atom_to_list(Node))] ++
case Res of
ok -> [?CT("Submitted"), ?P];
?LI([?ACT("ports/", "Listened Ports")]),
?LI([?ACT("stats/", "Statistics")]),
?LI([?ACT("update/", "Update")])
- ]),
+ ] ++ MenuItems2),
?XAE("form", [{"action", ""}, {"method", "post"}],
[?INPUTT("submit", "restart", "Restart"),
?C(" "),
];
get_node(Host, Node, [], Query, Lang) ->
+ MenuItems1 = ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, [], [Host, Node]),
+ MenuItems2 = [?LI([?ACT(MI_uri++"/", MI_name)]) || {MI_uri, MI_name} <- MenuItems1],
[?XC("h1", ?T("Node ") ++ atom_to_list(Node)),
?XE("ul",
- [?LI([?ACT("modules/", "Modules")])])
+ [?LI([?ACT("modules/", "Modules")])] ++ MenuItems2)
];
get_node(global, Node, ["db"], Query, Lang) ->
];
get_node(Host, Node, NPath, Query, Lang) ->
- [?XCT("h1", "Not Found")].
+ {Hook, Opts} = case Host of
+ global -> {webadmin_page_node, [Node, NPath, Query]};
+ Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query]}
+ end,
+ case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of
+ [] -> [?XC("h1", "Not Found")];
+ Res -> Res
+ end.
+
node_parse_query(Node, Query) ->
end.
-pretty_print(El) ->
- lists:flatten(pretty_print(El, "")).
+pretty_print_xml(El) ->
+ lists:flatten(pretty_print_xml(El, "")).
-pretty_print({xmlcdata, CData}, Prefix) ->
+pretty_print_xml({xmlcdata, CData}, Prefix) ->
[Prefix, CData, $\n];
-pretty_print({xmlelement, Name, Attrs, Els}, Prefix) ->
+pretty_print_xml({xmlelement, Name, Attrs, Els}, Prefix) ->
[Prefix, $<, Name,
case Attrs of
[] ->
true ->
[$>, $\n,
lists:map(fun(E) ->
- pretty_print(E, [Prefix, " "])
+ pretty_print_xml(E, [Prefix, " "])
end, Els),
Prefix, $<, $/, Name, $>, $\n
]
end].
-list_shared_roster_groups(Host, Query, Lang) ->
- Res = list_sr_groups_parse_query(Host, Query),
- SRGroups = mod_shared_roster:list_groups(Host),
- FGroups =
- ?XAE("table", [],
- [?XE("tbody",
- lists:map(
- fun(Group) ->
- ?XE("tr",
- [?XE("td", [?INPUT("checkbox", "selected",
- Group)]),
- ?XE("td", [?AC(Group ++ "/", Group)])
- ]
- )
- end, lists:sort(SRGroups)) ++
- [?XE("tr",
- [?X("td"),
- ?XE("td", [?INPUT("text", "namenew", "")]),
- ?XE("td", [?INPUTT("submit", "addnew", "Add New")])
- ]
- )]
- )]),
- [?XC("h1", ?T("Shared Roster Groups"))] ++
- case Res of
- ok -> [?CT("Submitted"), ?P];
- error -> [?CT("Bad format"), ?P];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [FGroups,
- ?BR,
- ?INPUTT("submit", "delete", "Delete Selected")
- ])
- ].
-
-list_sr_groups_parse_query(Host, Query) ->
- case lists:keysearch("addnew", 1, Query) of
- {value, _} ->
- list_sr_groups_parse_addnew(Host, Query);
- _ ->
- case lists:keysearch("delete", 1, Query) of
- {value, _} ->
- list_sr_groups_parse_delete(Host, Query);
- _ ->
- nothing
- end
- end.
-
-list_sr_groups_parse_addnew(Host, Query) ->
- case lists:keysearch("namenew", 1, Query) of
- {value, {_, Group}} when Group /= "" ->
- mod_shared_roster:create_group(Host, Group),
- ok;
- _ ->
- error
- end.
-
-list_sr_groups_parse_delete(Host, Query) ->
- SRGroups = mod_shared_roster:list_groups(Host),
- lists:foreach(
- fun(Group) ->
- case lists:member({"selected", Group}, Query) of
- true ->
- mod_shared_roster:delete_group(Host, Group);
- _ ->
- ok
- end
- end, SRGroups),
- ok.
-
-
-shared_roster_group(Host, Group, Query, Lang) ->
- Res = shared_roster_group_parse_query(Host, Group, Query),
- GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
- Name = get_opt(GroupOpts, name, ""),
- Description = get_opt(GroupOpts, description, ""),
- AllUsers = get_opt(GroupOpts, all_users, false),
- Disabled = false,
- DisplayedGroups = get_opt(GroupOpts, displayed_groups, []),
- Members = mod_shared_roster:get_group_explicit_users(Host, Group),
- FMembers =
- if
- AllUsers ->
- "@all@\n";
- true ->
- []
- end ++ [[us_to_list(Member), $\n] || Member <- Members],
- FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups],
- FGroup =
- ?XAE("table", [],
- [?XE("tbody",
- [?XE("tr",
- [?XCT("td", "Name:"),
- ?XE("td", [?INPUT("text", "name", Name)])
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Description:"),
- ?XE("td", [?XAC("textarea", [{"name", "description"},
- {"rows", "3"},
- {"cols", "20"}],
- Description)])
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Members:"),
- ?XE("td", [?XAC("textarea", [{"name", "members"},
- {"rows", "3"},
- {"cols", "20"}],
- FMembers)])
- ]
- ),
- ?XE("tr",
- [?XCT("td", "Displayed Groups:"),
- ?XE("td", [?XAC("textarea", [{"name", "dispgroups"},
- {"rows", "3"},
- {"cols", "20"}],
- FDisplayedGroups)])
- ]
- )]
- )]),
- [?XC("h1", ?T("Shared Roster Groups"))] ++
- [?XC("h2", ?T("Group ") ++ Group)] ++
- case Res of
- ok -> [?CT("Submitted"), ?P];
- error -> [?CT("Bad format"), ?P];
- nothing -> []
- end ++
- [?XAE("form", [{"action", ""}, {"method", "post"}],
- [FGroup,
- ?BR,
- ?INPUTT("submit", "submit", "Submit")
- ])
- ].
-
-shared_roster_group_parse_query(Host, Group, Query) ->
- case lists:keysearch("submit", 1, Query) of
- {value, _} ->
- {value, {_, Name}} = lists:keysearch("name", 1, Query),
- {value, {_, Description}} = lists:keysearch("description", 1, Query),
- {value, {_, SMembers}} = lists:keysearch("members", 1, Query),
- {value, {_, SDispGroups}} = lists:keysearch("dispgroups", 1, Query),
- NameOpt =
- if
- Name == "" -> [];
- true -> [{name, Name}]
- end,
- DescriptionOpt =
- if
- Description == "" -> [];
- true -> [{description, Description}]
- end,
- DispGroups = string:tokens(SDispGroups, "\r\n"),
- DispGroupsOpt =
- if
- DispGroups == [] -> [];
- true -> [{displayed_groups, DispGroups}]
- end,
-
- OldMembers = mod_shared_roster:get_group_explicit_users(
- Host, Group),
- SJIDs = string:tokens(SMembers, ", \r\n"),
- NewMembers =
- lists:foldl(
- fun(_SJID, error) -> error;
- (SJID, USs) ->
- case SJID of
- "@all@" ->
- USs;
- _ ->
- case jlib:string_to_jid(SJID) of
- JID when is_record(JID, jid) ->
- [{JID#jid.luser, JID#jid.lserver} | USs];
- error ->
- error
- end
- end
- end, [], SJIDs),
- AllUsersOpt =
- case lists:member("@all@", SJIDs) of
- true -> [{all_users, true}];
- false -> []
- end,
-
- mod_shared_roster:set_group_opts(
- Host, Group,
- NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt),
-
- if
- NewMembers == error -> error;
- true ->
- AddedMembers = NewMembers -- OldMembers,
- RemovedMembers = OldMembers -- NewMembers,
- lists:foreach(
- fun(US) ->
- mod_shared_roster:remove_user_from_group(
- Host, US, Group)
- end, RemovedMembers),
- lists:foreach(
- fun(US) ->
- mod_shared_roster:add_user_to_group(
- Host, US, Group)
- end, AddedMembers),
- ok
- end;
- _ ->
- nothing
- end.
-
-
-get_opt(Opts, Opt, Default) ->
- case lists:keysearch(Opt, 1, Opts) of
- {value, {_, Val}} ->
- Val;
- false ->
- Default
- end.
-
-
url_func({user_diapason, From, To}) ->
integer_to_list(From) ++ "-" ++ integer_to_list(To) ++ "/";
url_func({users_queue, Prefix, User, Server}) ->
--- /dev/null
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_web_admin.hrl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose :
+%%% Created : 22 Aug 2007 by Alexey Shchepin <alexey@process-one.net>
+%%%----------------------------------------------------------------------
+
+-define(X(Name), {xmlelement, Name, [], []}).
+-define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
+-define(XE(Name, Els), {xmlelement, Name, [], Els}).
+-define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+-define(C(Text), {xmlcdata, Text}).
+-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
+-define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(T(Text), translate:translate(Lang, Text)).
+-define(CT(Text), ?C(?T(Text))).
+-define(XCT(Name, Text), ?XC(Name, ?T(Text))).
+-define(XACT(Name, Attrs, Text), ?XAC(Name, Attrs, ?T(Text))).
+
+-define(LI(Els), ?XE("li", Els)).
+-define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(AC(URL, Text), ?A(URL, [?C(Text)])).
+-define(ACT(URL, Text), ?AC(URL, ?T(Text))).
+-define(P, ?X("p")).
+-define(BR, ?X("br")).
+-define(INPUT(Type, Name, Value),
+ ?XA("input", [{"type", Type},
+ {"name", Name},
+ {"value", Value}])).
+-define(INPUTT(Type, Name, Value), ?INPUT(Type, Name, ?T(Value))).
+-define(INPUTS(Type, Name, Value, Size),
+ ?XA("input", [{"type", Type},
+ {"name", Name},
+ {"value", Value},
+ {"size", Size}])).
+-define(INPUTST(Type, Name, Value, Size), ?INPUT(Type, Name, ?T(Value), Size)).
+-define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).