* 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

SVN Revision: 884
This commit is contained in:
Alexey Shchepin 2007-08-23 00:51:54 +00:00
parent 106cf7f963
commit 727a70c2cb
6 changed files with 632 additions and 416 deletions

View File

@ -1,3 +1,13 @@
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

View File

@ -18,10 +18,14 @@
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}).
@ -42,6 +46,10 @@ start(Host, Opts) ->
?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])).
@ -106,6 +114,10 @@ stop(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),
{wait, Proc}.
@ -420,3 +432,107 @@ discard_warn_sender(Msgs) ->
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.

View File

@ -3,8 +3,8 @@
%%% 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').
@ -15,10 +15,14 @@
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}).
@ -34,6 +38,10 @@ start(Host, _Opts) ->
?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])).
@ -98,6 +106,10 @@ stop(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.
@ -180,20 +192,16 @@ find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
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;
@ -238,3 +246,139 @@ remove_user(User, Server) ->
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.

View File

@ -12,6 +12,7 @@
-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,
@ -32,6 +33,8 @@
-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}).
@ -45,6 +48,10 @@ start(Host, _Opts) ->
{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,
@ -61,6 +68,10 @@ start(Host, _Opts) ->
% ?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,
@ -352,3 +363,249 @@ remove_user_from_group(Host, US, Group) ->
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, ""}).

View File

@ -3,7 +3,6 @@
%%% 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
@ -15,46 +14,14 @@
%% 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,
@ -126,6 +93,8 @@ get_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},
@ -153,7 +122,7 @@ make_xhtml(Els, global, Lang) ->
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")])
]
] ++ MenuItems2
)]),
?XAE("div",
[{"id", "content"}],
@ -172,6 +141,8 @@ make_xhtml(Els, global, Lang) ->
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},
@ -205,14 +176,7 @@ make_xhtml(Els, Host, 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"}],
@ -607,6 +571,8 @@ process_admin(global,
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(" "),
@ -616,7 +582,7 @@ process_admin(global,
?LI([?ACT("/admin/vhosts/", "Virtual Hosts")]),
?LI([?ACT("/admin/nodes/", "Nodes")]),
?LI([?ACT("/admin/stats/", "Statistics")])
]
] ++ MenuItems2
)
], global, Lang);
@ -626,6 +592,8 @@ process_admin(Host,
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(" "),
@ -637,14 +605,7 @@ process_admin(Host,
?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);
@ -996,14 +957,6 @@ process_admin(Host,
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"],
@ -1033,25 +986,15 @@ process_admin(Host,
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.
@ -1076,8 +1019,6 @@ acls_to_xhtml(ACLs) ->
)]
)]).
-define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
acl_spec_to_text({user, U}) ->
{user, U};
@ -1489,7 +1430,8 @@ list_online_users(Host, _Lang) ->
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 =
@ -1516,9 +1458,8 @@ user_info(User, Server, Query, Lang) ->
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];
@ -1528,9 +1469,9 @@ user_info(User, Server, Query, Lang) ->
[?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) ->
@ -1556,89 +1497,6 @@ 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,
@ -1922,6 +1780,8 @@ search_running_node(SNode, [Node | Nodes]) ->
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];
@ -1934,7 +1794,7 @@ get_node(global, Node, [], Query, Lang) ->
?LI([?ACT("ports/", "Listened Ports")]),
?LI([?ACT("stats/", "Statistics")]),
?LI([?ACT("update/", "Update")])
]),
] ++ MenuItems2),
?XAE("form", [{"action", ""}, {"method", "post"}],
[?INPUTT("submit", "restart", "Restart"),
?C(" "),
@ -1942,9 +1802,11 @@ get_node(global, Node, [], Query, Lang) ->
];
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) ->
@ -2176,7 +2038,15 @@ get_node(global, Node, ["update"], 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) ->
@ -2464,12 +2334,12 @@ node_update_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
[] ->
@ -2499,7 +2369,7 @@ pretty_print({xmlelement, Name, Attrs, Els}, Prefix) ->
true ->
[$>, $\n,
lists:map(fun(E) ->
pretty_print(E, [Prefix, " "])
pretty_print_xml(E, [Prefix, " "])
end, Els),
Prefix, $<, $/, Name, $>, $\n
]
@ -2507,225 +2377,6 @@ pretty_print({xmlelement, Name, Attrs, Els}, Prefix) ->
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}) ->

View File

@ -0,0 +1,38 @@
%%%----------------------------------------------------------------------
%%% 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)])).