25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-19 15:32:08 +02:00

WebAdmin: Move content to commands; new pages; hook changes; new commands

Also:
- Added support to view user subpages in the menu
- Webadmin hooks now get the full request
- New commands added to be used in webadmin pages
This commit is contained in:
Badlop 2024-06-13 00:15:42 +02:00
parent 2b1d4ff98d
commit 5a34020d23
11 changed files with 2709 additions and 1889 deletions

View File

@ -32,11 +32,16 @@
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% WebAdmin
-export([webadmin_menu_node/3, webadmin_page_node/3]).
-include("logger.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-define(CALL_TIMEOUT, timer:minutes(10)).
@ -108,6 +113,8 @@ init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, register_certfiles, 40),
ejabberd_hooks:add(ejabberd_started, ?MODULE, ejabberd_started, 110),
ejabberd_hooks:add(config_reloaded, ?MODULE, ejabberd_started, 110),
ejabberd_hooks:add(webadmin_menu_node, ?MODULE, webadmin_menu_node, 110),
ejabberd_hooks:add(webadmin_page_node, ?MODULE, webadmin_page_node, 110),
ejabberd_commands:register_commands(get_commands_spec()),
register_certfiles(),
{ok, #state{}}.
@ -153,6 +160,8 @@ terminate(_Reason, _State) ->
ejabberd_hooks:delete(config_reloaded, ?MODULE, register_certfiles, 40),
ejabberd_hooks:delete(ejabberd_started, ?MODULE, ejabberd_started, 110),
ejabberd_hooks:delete(config_reloaded, ?MODULE, ejabberd_started, 110),
ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, webadmin_menu_node, 110),
ejabberd_hooks:delete(webadmin_page_node, ?MODULE, webadmin_page_node, 110),
ejabberd_commands:unregister_commands(get_commands_spec()).
code_change(_OldVsn, State, _Extra) ->
@ -547,6 +556,21 @@ list_certificates() ->
{Domain, Path, sets:is_element(E, Used)}
end, Known)).
%%%===================================================================
%%% WebAdmin
%%%===================================================================
webadmin_menu_node(Acc, _Node, _Lang) ->
Acc ++ [{<<"acme">>, <<"ACME">>}].
webadmin_page_node(_, Node, #request{path = [<<"acme">>]} = R) ->
Head = ?H1GLraw(<<"ACME Certificates">>, <<"admin/configuration/basic/#acme">>, <<"ACME">>),
Set = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [request_certificate, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [revoke_certificate, R])],
Get = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [list_certificates, R])],
{stop, Head ++ Get ++ Set};
webadmin_page_node(Acc, _, _) -> Acc.
%%%===================================================================
%%% Other stuff
%%%===================================================================

View File

@ -39,7 +39,9 @@
dump_config/1,
convert_to_yaml/2,
%% Cluster
join_cluster/1, leave_cluster/1, list_cluster/0,
join_cluster/1, leave_cluster/1,
list_cluster/0, list_cluster_detailed/0,
get_cluster_node_details3/0,
%% Erlang
update_list/0, update/1, update/0,
%% Accounts
@ -50,7 +52,7 @@
%% Purge DB
delete_expired_messages/0, delete_old_messages/1,
%% Mnesia
set_master/1,
get_master/0, set_master/1,
backup_mnesia/1, restore_mnesia/1,
dump_mnesia/1, dump_table/2, load_mnesia/1,
mnesia_info/0, mnesia_table_info/1,
@ -61,13 +63,27 @@
clear_cache/0,
gc/0,
get_commands_spec/0,
delete_old_messages_batch/4, delete_old_messages_status/1, delete_old_messages_abort/1]).
delete_old_messages_batch/4, delete_old_messages_status/1, delete_old_messages_abort/1,
%% Internal
mnesia_list_tables/0,
mnesia_table_details/1,
mnesia_table_change_storage/2,
mnesia_table_clear/1,
mnesia_table_delete/1,
echo/1, echo3/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("logger.hrl").
-export([web_menu_main/2, web_page_main/2,
web_menu_node/3, web_page_node/3]).
-include_lib("xmpp/include/xmpp.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("logger.hrl").
-include("translate.hrl"). %+++ TODO
-record(state, {}).
@ -77,6 +93,10 @@ start_link() ->
init([]) ->
process_flag(trap_exit, true),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50),
ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50),
{ok, #state{}}.
handle_call(Request, From, State) ->
@ -92,6 +112,10 @@ handle_info(Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50),
ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50),
ejabberd_commands:unregister_commands(get_commands_spec()).
code_change(_OldVsn, State, _Extra) ->
@ -179,8 +203,9 @@ get_commands_spec() ->
desc = "Update the given module",
longdesc = "To update all the possible modules, use `all`.",
module = ?MODULE, function = update,
args_example = ["mod_vcard"],
args_example = ["all"],
args = [{module, string}],
result_example = {ok, <<"Updated modules: mod_configure, mod_vcard">>},
result = {res, restuple}},
#ejabberd_commands{name = register, tags = [accounts],
@ -225,14 +250,12 @@ get_commands_spec() ->
#ejabberd_commands{name = join_cluster, tags = [cluster],
desc = "Join this node into the cluster handled by Node",
longdesc = "This command works only with ejabberdctl, "
"not mod_http_api or other code that runs inside the "
"same ejabberd node that will be joined.",
note = "improved in 24.xx",
module = ?MODULE, function = join_cluster,
args_desc = ["Nodename of the node to join"],
args_example = [<<"ejabberd1@machine7">>],
args = [{node, binary}],
result = {res, rescode}},
result = {res, restuple}},
#ejabberd_commands{name = leave_cluster, tags = [cluster],
desc = "Remove and shutdown Node from the running cluster",
longdesc = "This command can be run from any running "
@ -247,11 +270,27 @@ get_commands_spec() ->
result = {res, rescode}},
#ejabberd_commands{name = list_cluster, tags = [cluster],
desc = "List nodes that are part of the cluster handled by Node",
desc = "List running nodes that are part of this cluster",
module = ?MODULE, function = list_cluster,
result_example = [ejabberd1@machine7, ejabberd1@machine8],
args = [],
result = {nodes, {list, {node, atom}}}},
#ejabberd_commands{name = list_cluster_detailed, tags = [cluster],
desc = "List nodes (both running and known) and some stats",
note = "added in 24.xx",
module = ?MODULE, function = list_cluster_detailed,
args = [],
result_example = [{'ejabberd@localhost', "true",
"The node ejabberd is started. Status...",
7, 348, 60, none}],
result = {nodes, {list, {node, {tuple, [{name, atom},
{running, string},
{status, string},
{online_users, integer},
{processes, integer},
{uptime_seconds, integer},
{master_node, atom}
]}}}}},
#ejabberd_commands{name = import_file, tags = [mnesia],
desc = "Import user data from jabberd14 spool file",
@ -377,6 +416,12 @@ get_commands_spec() ->
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
args = [{host, string}, {file, string}],
result = {res, rescode}},
#ejabberd_commands{name = get_master, tags = [cluster],
desc = "Get master node of the clustered Mnesia tables",
note = "added in 24.xx",
longdesc = "If there is no master, returns `none`.",
module = ?MODULE, function = get_master,
result = {nodename, atom}},
#ejabberd_commands{name = set_master, tags = [cluster],
desc = "Set master node of the clustered Mnesia tables",
longdesc = "If `nodename` is set to `self`, then this "
@ -472,9 +517,100 @@ get_commands_spec() ->
desc = "Generate Unix manpage for current ejabberd version",
note = "added in 20.01",
module = ejabberd_doc, function = man,
args = [], result = {res, restuple}}
].
args = [], result = {res, restuple}},
#ejabberd_commands{name = webadmin_host_user_queue, tags = [internal],
desc = "Generate WebAdmin offline queue HTML",
module = mod_offline, function = webadmin_host_user_queue,
args = [{user, binary}, {host, binary}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_host_last_activity, tags = [internal],
desc = "Generate WebAdmin Last Activity HTML",
module = ejabberd_web_admin, function = webadmin_host_last_activity,
args = [{host, binary}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_host_srg, tags = [internal],
desc = "Generate WebAdmin Shared Roster Group HTML",
module = mod_shared_roster, function = webadmin_host_srg,
args = [{host, binary}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_host_srg_group, tags = [internal],
desc = "Generate WebAdmin Shared Roster Group HTML for a group",
module = mod_shared_roster, function = webadmin_host_srg_group,
args = [{host, binary}, {group, binary}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_node_contrib, tags = [internal],
desc = "Generate WebAdmin ejabberd-contrib HTML",
module = ext_mod, function = webadmin_node_contrib,
args = [{node, atom}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_node_db, tags = [internal],
desc = "Generate WebAdmin Mnesia database HTML",
module = ejabberd_web_admin, function = webadmin_node_db,
args = [{node, atom}, {query, any}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_node_db_table, tags = [internal],
desc = "Generate WebAdmin Mnesia database HTML for a table",
module = ejabberd_web_admin, function = webadmin_node_db_table,
args = [{node, atom}, {table, binary}, {lang, binary}],
result = {res, any}},
#ejabberd_commands{name = webadmin_node_db_table_page, tags = [internal],
desc = "Generate WebAdmin Mnesia database HTML for a table content",
module = ejabberd_web_admin, function = webadmin_node_db_table_page,
args = [{node, atom}, {table, binary}, {page, integer}],
result = {res, any}},
#ejabberd_commands{name = mnesia_list_tables, tags = [internal, mnesia],
desc = "List of Mnesia tables",
module = ?MODULE, function = mnesia_list_tables,
result = {tables, {list, {table, {tuple, [{name, atom},
{storage_type, binary},
{elements, integer},
{memory_kb, integer},
{memory_mb, integer}
]}}}}},
#ejabberd_commands{name = mnesia_table_details, tags = [internal, mnesia],
desc = "Get details of a Mnesia table",
module = ?MODULE, function = mnesia_table_details,
args = [{table, binary}],
result = {details, {list, {detail, {tuple, [{name, atom},
{value, binary}
]}}}}},
#ejabberd_commands{name = mnesia_table_change_storage, tags = [internal, mnesia],
desc = "Change storage type of a Mnesia table to: ram_copies, disc_copies, or disc_only_copies.",
module = ?MODULE, function = mnesia_table_change_storage,
args = [{table, binary}, {storage_type, binary}],
result = {res, restuple}},
#ejabberd_commands{name = mnesia_table_clear, tags = [internal, mnesia],
desc = "Delete all content in a Mnesia table",
module = ?MODULE, function = mnesia_table_clear,
args = [{table, binary}],
result = {res, restuple}},
#ejabberd_commands{name = mnesia_table_destroy, tags = [internal, mnesia],
desc = "Destroy a Mnesia table",
module = ?MODULE, function = mnesia_table_destroy,
args = [{table, binary}],
result = {res, restuple}},
#ejabberd_commands{name = echo, tags = [internal],
desc = "Return the same sentence that was provided",
module = ?MODULE, function = echo,
args_desc = ["Sentence to echoe"],
args_example = [<<"Test Sentence">>],
args = [{sentence, binary}],
result = {sentence, string},
result_example = "Test Sentence"},
#ejabberd_commands{name = echo3, tags = [internal],
desc = "Return the same sentence that was provided",
module = ?MODULE, function = echo3,
args_desc = ["First argument", "Second argument", "Sentence to echoe"],
args_example = [<<"example.com">>, <<"Group1">>, <<"Test Sentence">>],
args = [{first, binary}, {second, binary}, {sentence, binary}],
result = {sentence, string},
result_example = "Test Sentence"}
].
%%%
%%% Server management
@ -491,7 +627,7 @@ status() ->
{value, {_, _, Version}} ->
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
end,
{Is_running, String1 ++ String2}.
{Is_running, String1 ++ " " ++String2}.
stop() ->
_ = supervisor:terminate_child(ejabberd_sup, ejabberd_sm),
@ -583,8 +719,14 @@ update_list() ->
[atom_to_list(Beam) || Beam <- UpdatedBeams].
update("all") ->
[update_module(ModStr) || ModStr <- update_list()],
{ok, []};
ResList = [{ModStr, update_module(ModStr)} || ModStr <- update_list()],
String = case string:join([Mod || {Mod, {ok, _}} <- ResList], ", ") of
[] ->
"No modules updated";
ModulesString ->
"Updated modules: " ++ ModulesString
end,
{ok, String};
update(ModStr) ->
update_module(ModStr).
@ -593,7 +735,10 @@ update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
update_module(ModuleNameString) ->
ModuleName = list_to_atom(ModuleNameString),
case ejabberd_update:update([ModuleName]) of
{ok, _Res} -> {ok, []};
{ok, []} ->
{ok, "Not updated: "++ModuleNameString};
{ok, [{ModuleName, _}]} ->
{ok, "Updated: "++ModuleNameString};
{error, Reason} -> {error, Reason}
end.
@ -681,7 +826,25 @@ convert_to_yaml(In, Out) ->
%%%
join_cluster(NodeBin) ->
ejabberd_cluster:join(list_to_atom(binary_to_list(NodeBin))).
Node = list_to_atom(binary_to_list(NodeBin)),
IsNodes = lists:member(Node, ejabberd_cluster:get_nodes()),
IsKnownNodes = lists:member(Node, ejabberd_cluster:get_known_nodes()),
Ping = net_adm:ping(Node),
join_cluster(Node, IsNodes, IsKnownNodes, Ping).
join_cluster(_Node, true, _IsKnownNodes, _Ping) ->
{error, "This node already joined that running node."};
join_cluster(_Node, _IsNodes, true, _Ping) ->
{error, "This node already joined that known node."};
join_cluster(_Node, _IsNodes, _IsKnownNodes, pang) ->
{error, "This node cannot reach that node."};
join_cluster(Node, false, false, pong) ->
case timer:apply_after(1000, ejabberd_cluster, join, [Node]) of
{ok, _} ->
{ok, "Trying to join the cluster, wait a few seconds and check the list of nodes."};
Error ->
{error, io_lib:format("Can't join cluster: ~p", [Error])}
end.
leave_cluster(NodeBin) ->
ejabberd_cluster:leave(list_to_atom(binary_to_list(NodeBin))).
@ -689,6 +852,33 @@ leave_cluster(NodeBin) ->
list_cluster() ->
ejabberd_cluster:get_nodes().
list_cluster_detailed() ->
KnownNodes = ejabberd_cluster:get_known_nodes(),
RunningNodes = ejabberd_cluster:get_nodes(),
[get_cluster_node_details(Node, RunningNodes) || Node <- KnownNodes].
get_cluster_node_details(Node, RunningNodes) ->
get_cluster_node_details2(Node, lists:member(Node, RunningNodes)).
get_cluster_node_details2(Node, false) ->
{Node, "false", "", -1, -1, -1, "unknown"};
get_cluster_node_details2(Node, true) ->
try ejabberd_cluster:call(Node, ejabberd_admin, get_cluster_node_details3, []) of
Result -> Result
catch
E:R ->
Status = io_lib:format("~p: ~p", [E, R]),
{Node, "true", Status, -1, -1, -1, "unknown"}
end.
get_cluster_node_details3() ->
{ok, StatusString} = status(),
UptimeSeconds = mod_admin_extra:stats(<<"uptimeseconds">>),
Processes = mod_admin_extra:stats(<<"processes">>),
OnlineUsers = mod_admin_extra:stats(<<"onlineusersnode">>),
GetMaster = get_master(),
{node(), "true", StatusString, OnlineUsers, Processes, UptimeSeconds, GetMaster}.
%%%
%%% Migration management
%%%
@ -791,6 +981,12 @@ delete_old_messages_abort(Server) ->
%%% Mnesia management
%%%
get_master() ->
case mnesia:table_info(session, master_nodes) of
[] -> none;
[Node] -> Node
end.
set_master("self") ->
set_master(node());
set_master(NodeString) when is_list(NodeString) ->
@ -798,7 +994,7 @@ set_master(NodeString) when is_list(NodeString) ->
set_master(Node) when is_atom(Node) ->
case mnesia:set_master_nodes([Node]) of
ok ->
{ok, ""};
{ok, "ok"};
{error, Reason} ->
String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
[Node, node(), Reason]),
@ -1008,3 +1204,222 @@ is_my_host(Host) ->
try ejabberd_router:is_my_host(Host)
catch _:{invalid_domain, _} -> false
end.
%%%
%%% Internal
%%%
%% @format-begin
%% mnesia:del_table_copy(Table, Node);
%% mnesia:change_table_copy_type(Table, Node, Type);
mnesia_table_change_storage(STable, SType) ->
Table = binary_to_existing_atom(STable, latin1),
Type =
case SType of
<<"ram_copies">> ->
ram_copies;
<<"disc_copies">> ->
disc_copies;
<<"disc_only_copies">> ->
disc_only_copies;
_ ->
false
end,
mnesia:add_table_copy(Table, node(), Type).
mnesia_table_clear(STable) ->
Table = binary_to_existing_atom(STable, latin1),
mnesia:clear_table(Table).
mnesia_table_delete(STable) ->
Table = binary_to_existing_atom(STable, latin1),
mnesia:delete_table(Table).
mnesia_table_details(STable) ->
Table = binary_to_existing_atom(STable, latin1),
[{Name, iolist_to_binary(str:format("~p", [Value]))}
|| {Name, Value} <- mnesia:table_info(Table, all)].
mnesia_list_tables() ->
STables =
lists:sort(
mnesia:system_info(tables)),
lists:map(fun(Table) ->
TInfo = mnesia:table_info(Table, all),
{value, {storage_type, Type}} = lists:keysearch(storage_type, 1, TInfo),
{value, {size, Size}} = lists:keysearch(size, 1, TInfo),
{value, {memory, Memory}} = lists:keysearch(memory, 1, TInfo),
MemoryB = Memory * erlang:system_info(wordsize),
MemoryKB = MemoryB div 1024,
MemoryMB = MemoryKB div 1024,
{Table, storage_type_bin(Type), Size, MemoryKB, MemoryMB}
end,
STables).
storage_type_bin(ram_copies) ->
<<"RAM copy">>;
storage_type_bin(disc_copies) ->
<<"RAM and disc copy">>;
storage_type_bin(disc_only_copies) ->
<<"Disc only copy">>;
storage_type_bin(unknown) ->
<<"Remote copy">>.
echo(Sentence) ->
Sentence.
echo3(_, _, Sentence) ->
Sentence.
%%%
%%% Web Admin: Main
%%%
web_menu_main(Acc, _Lang) ->
Acc ++ [{<<"purge">>, <<"Purge">>}, {<<"stanza">>, <<"Stanza">>}].
web_page_main(_, #request{path = [<<"purge">>]} = R) ->
Types =
[{<<"#erlang">>, <<"Erlang">>},
{<<"#users">>, <<"Users">>},
{<<"#offline">>, <<"Offline">>},
{<<"#mam">>, <<"MAM">>},
{<<"#pubsub">>, <<"PubSub">>},
{<<"#push">>, <<"Push">>}],
Head = [?XC(<<"h1">>, <<"Purge">>)],
Set = [?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- Types]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"erlang">>}], <<"Erlang">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(clear_cache, R),
ejabberd_web_admin:make_command(gc, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"users">>}], <<"Users">>),
?XE(<<"blockquote">>, [ejabberd_web_admin:make_command(delete_old_users, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"offline">>}], <<"Offline">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(delete_expired_messages, R),
ejabberd_web_admin:make_command(delete_old_messages, R),
ejabberd_web_admin:make_command(delete_old_messages_batch, R),
ejabberd_web_admin:make_command(delete_old_messages_status, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"mam">>}], <<"MAM">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(delete_old_mam_messages, R),
ejabberd_web_admin:make_command(delete_old_mam_messages_batch, R),
ejabberd_web_admin:make_command(delete_old_mam_messages_status, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"pubsub">>}], <<"PubSub">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(delete_expired_pubsub_items, R),
ejabberd_web_admin:make_command(delete_old_pubsub_items, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"push">>}], <<"Push">>),
?XE(<<"blockquote">>, [ejabberd_web_admin:make_command(delete_old_push_sessions, R)])],
{stop, Head ++ Set};
web_page_main(_, #request{path = [<<"stanza">>]} = R) ->
Head = [?XC(<<"h1">>, <<"Stanza">>)],
Set = [ejabberd_web_admin:make_command(send_message, R),
ejabberd_web_admin:make_command(send_stanza, R),
ejabberd_web_admin:make_command(send_stanza_c2s, R)],
{stop, Head ++ Set};
web_page_main(Acc, _) ->
Acc.
%%%
%%% Web Admin: Node
%%%
web_menu_node(Acc, _Node, _Lang) ->
Acc
++ [{<<"cluster">>, <<"Clustering">>},
{<<"update">>, <<"Code Update">>},
{<<"config-file">>, <<"Configuration File">>},
{<<"logs">>, <<"Logs">>},
{<<"stop">>, <<"Stop Node">>}].
web_page_node(_, Node, #request{path = [<<"cluster">>]} = R) ->
{ok, Names} = net_adm:names(),
NodeNames = lists:join(", ", [Name || {Name, _Port} <- Names]),
Hint =
list_to_binary(io_lib:format("Hint: Erlang nodes found in this machine that may be running ejabberd: ~s",
[NodeNames])),
Head = ?H1GLraw(<<"Clustering">>, <<"admin/guide/clustering/">>, <<"Clustering">>),
Set1 =
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[join_cluster, R, [], [{style, danger}]]),
?XE(<<"blockquote">>, [?C(Hint)]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[leave_cluster, R, [], [{style, danger}]])],
Set2 =
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[set_master, R, [], [{style, danger}]])],
timer:sleep(100), % leaving a cluster takes a while, let's delay the get commands
Get1 =
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[list_cluster_detailed,
R,
[],
[{result_links, [{name, node, 3, <<"">>}]}]])],
Get2 =
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[get_master,
R,
[],
[{result_named, true},
{result_links, [{nodename, node, 3, <<"">>}]}]])],
{stop, Head ++ Get1 ++ Set1 ++ Get2 ++ Set2};
web_page_node(_, Node, #request{path = [<<"update">>]} = R) ->
Head = [?XC(<<"h1">>, <<"Code Update">>)],
Set = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [update, R])],
Get = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [update_list, R])],
{stop, Head ++ Get ++ Set};
web_page_node(_, Node, #request{path = [<<"config-file">>]} = R) ->
Res = ?H1GLraw(<<"Configuration File">>,
<<"admin/configuration/file-format/">>,
<<"File Format">>)
++ [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [convert_to_yaml, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [dump_config, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [reload_config, R])],
{stop, Res};
web_page_node(_, Node, #request{path = [<<"stop">>]} = R) ->
Res = [?XC(<<"h1">>, <<"Stop This Node">>),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[restart, R, [], [{style, danger}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[stop_kindly, R, [], [{style, danger}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[stop, R, [], [{style, danger}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[halt, R, [], [{style, danger}]])],
{stop, Res};
web_page_node(_, Node, #request{path = [<<"logs">>]} = R) ->
Res = ?H1GLraw(<<"Logs">>, <<"admin/configuration/basic/#logging">>, <<"Logging">>)
++ [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [set_loglevel, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [get_loglevel, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [reopen_log, R]),
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [rotate_log, R])],
{stop, Res};
web_page_node(Acc, _, _) ->
Acc.

View File

@ -54,6 +54,8 @@
oauth_add_client_implicit/3,
oauth_remove_client/1]).
-export([web_menu_main/2, web_page_main/2]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("ejabberd_http.hrl").
@ -230,6 +232,8 @@ init([]) ->
application:set_env(oauth2, expiry_time, Expire div 1000),
application:start(oauth2),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
erlang:send_after(expire(), self(), clean),
{ok, ok}.
@ -255,6 +259,8 @@ handle_info(Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50).
code_change(_OldVsn, State, _Extra) -> {ok, State}.
@ -794,3 +800,30 @@ logo() ->
{error, _} ->
<<>>
end.
%%%
%%% WebAdmin
%%%
%% @format-begin
web_menu_main(Acc, _Lang) ->
Acc ++ [{<<"oauth">>, <<"OAuth">>}].
web_page_main(_, #request{path = [<<"oauth">>]} = R) ->
Head = ?H1GLraw(<<"OAuth">>, <<"developer/ejabberd-api/oauth/">>, <<"OAuth">>),
Set = [?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"token">>}], <<"Token">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(oauth_list_tokens, R),
ejabberd_web_admin:make_command(oauth_issue_token, R),
ejabberd_web_admin:make_command(oauth_revoke_token, R)]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"client">>}], <<"Client">>),
?XE(<<"blockquote">>,
[ejabberd_web_admin:make_command(oauth_add_client_implicit, R),
ejabberd_web_admin:make_command(oauth_add_client_password, R),
ejabberd_web_admin:make_command(oauth_remove_client, R)])],
{stop, Head ++ Set};
web_page_main(Acc, _) ->
Acc.

File diff suppressed because it is too large Load Diff

View File

@ -37,13 +37,14 @@
config_dir/0, get_commands_spec/0]).
-export([modules_configs/0, module_ebin_dir/1]).
-export([compile_erlang_file/2, compile_elixir_file/2]).
-export([web_menu_node/3, web_page_node/5, get_page/3]).
-export([web_menu_node/3, web_page_node/3, webadmin_node_contrib/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("logger.hrl").
-include("translate.hrl").
@ -924,26 +925,156 @@ parse_details(Body) ->
)
).
web_menu_node(Acc, _Node, Lang) ->
Acc ++ [{<<"contrib">>, translate:translate(Lang, ?T("Contrib Modules"))}].
%% @format-begin
web_page_node(_, Node, [<<"contrib">>], Query, Lang) ->
Res = rpc:call(Node, ?MODULE, get_page, [Node, Query, Lang]),
{stop, Res};
web_page_node(Acc, _, _, _, _) ->
web_menu_node(Acc, _Node, _Lang) ->
Acc
++ [{<<"contrib">>, <<"Contrib Modules (Detailed)">>},
{<<"contrib-api">>, <<"Contrib Modules (API)">>}].
web_page_node(_,
Node,
#request{path = [<<"contrib">>],
q = Query,
lang = Lang} =
R) ->
Title =
?H1GL(<<"Contrib Modules (Detailed)">>,
<<"../../developer/extending-ejabberd/modules/#ejabberd-contrib">>,
<<"ejabberd-contrib">>),
Res = [ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[webadmin_node_contrib,
R,
[{<<"node">>, Node}, {<<"query">>, Query}, {<<"lang">>, Lang}],
[]])],
{stop, Title ++ Res};
web_page_node(_, Node, #request{path = [<<"contrib-api">> | RPath]} = R) ->
Title =
?H1GL(<<"Contrib Modules (API)">>,
<<"../../developer/extending-ejabberd/modules/#ejabberd-contrib">>,
<<"ejabberd-contrib">>),
_TableInstalled = make_table_installed(Node, R, RPath),
_TableAvailable = make_table_available(Node, R, RPath),
TableInstalled = make_table_installed(Node, R, RPath),
TableAvailable = make_table_available(Node, R, RPath),
Res = [?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"specs">>}], <<"Specs">>),
?XE(<<"blockquote">>,
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[modules_update_specs, R])]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"installed">>}], <<"Installed">>),
?XE(<<"blockquote">>,
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[modules_installed, R, [], [{only, presentation}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_uninstall, R, [], [{only, presentation}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_upgrade, R, [], [{only, presentation}]]),
TableInstalled]),
?X(<<"hr">>),
?XAC(<<"h2">>, [{<<"id">>, <<"available">>}], <<"Available">>),
?XE(<<"blockquote">>,
[ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[modules_available, R, [], [{only, presentation}]]),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_install, R, [], [{only, presentation}]]),
TableAvailable,
ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [module_check, R])])],
{stop, Title ++ Res};
web_page_node(Acc, _, _) ->
Acc.
get_page(Node, Query, Lang) ->
make_table_installed(Node, R, RPath) ->
Columns = [<<"Name">>, <<"Summary">>, <<"">>, <<"">>],
ModulesInstalled =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command_raw_value,
[modules_installed, R, []]),
Rows =
lists:map(fun({Name, Summary}) ->
NameBin = misc:atom_to_binary(Name),
Upgrade =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_upgrade,
R,
[{<<"module">>, NameBin}],
[{only, button}, {input_name_append, [NameBin]}]]),
Uninstall =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_uninstall,
R,
[{<<"module">>, NameBin}],
[{only, button},
{style, danger},
{input_name_append, [NameBin]}]]),
{?C(NameBin), ?C(list_to_binary(Summary)), Upgrade, Uninstall}
end,
ModulesInstalled),
ejabberd_web_admin:make_table(200, RPath, Columns, Rows).
make_table_available(Node, R, RPath) ->
Columns = [<<"Name">>, <<"Summary">>, <<"">>],
ModulesAll =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command_raw_value,
[modules_available, R, []]),
ModulesInstalled =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command_raw_value,
[modules_installed, R, []]),
ModulesNotInstalled =
lists:filter(fun({Mod, _}) -> not lists:keymember(Mod, 1, ModulesInstalled) end,
ModulesAll),
Rows =
lists:map(fun({Name, Summary}) ->
NameBin = misc:atom_to_binary(Name),
Install =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[module_install,
R,
[{<<"module">>, NameBin}],
[{only, button}, {input_name_append, [NameBin]}]]),
{?C(NameBin), ?C(list_to_binary(Summary)), Install}
end,
ModulesNotInstalled),
ejabberd_web_admin:make_table(200, RPath, Columns, Rows).
webadmin_node_contrib(Node, Query, Lang) ->
QueryRes = list_modules_parse_query(Query),
Title = ?H1GL(translate:translate(Lang, ?T("Contrib Modules")),
<<"../../developer/extending-ejabberd/modules/#ejabberd-contrib">>,
<<"ejabberd-contrib">>),
Contents = get_content(Node, Query, Lang),
Result = case QueryRes of
ok -> [?XREST(?T("Submitted"))];
nothing -> []
end,
Title ++ Result ++ Contents.
Result =
case QueryRes of
ok ->
[?XREST(?T("Submitted"))];
nothing ->
[]
end,
Result ++ Contents.
%% @format-end
get_module_home(Module, Attrs) ->
case get_module_information(home, Attrs) of

View File

@ -59,7 +59,7 @@
% Roster
add_rosteritem/7, delete_rosteritem/4,
get_roster/2, push_roster/3,
get_roster/2, get_roster_count/2, push_roster/3,
push_roster_all/1, push_alltoall/2,
push_roster_item/5, build_roster_item/3,
@ -67,8 +67,10 @@
private_get/4, private_set/3,
% Shared roster
srg_create/5,
srg_create/5, srg_add/2,
srg_delete/2, srg_list/1, srg_get_info/2,
srg_set_info/4,
srg_get_displayed/2, srg_add_displayed/3, srg_del_displayed/3,
srg_get_members/2, srg_user_add/4, srg_user_del/4,
% Send message
@ -80,9 +82,17 @@
% Stats
stats/1, stats/2
]).
-export([web_menu_main/2, web_page_main/2,
web_menu_host/3, web_page_host/3,
web_menu_hostuser/4, web_page_hostuser/4,
web_menu_hostnode/4, web_page_hostnode/4,
web_menu_node/3, web_page_node/3]).
-import(ejabberd_web_admin, [make_command/4, make_table/2]).
-include("ejabberd_commands.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("mod_roster.hrl").
-include("mod_privacy.hrl").
-include("ejabberd_sm.hrl").
@ -94,7 +104,17 @@
%%%
start(_Host, _Opts) ->
ejabberd_commands:register_commands(?MODULE, get_commands_spec()).
ejabberd_commands:register_commands(?MODULE, get_commands_spec()),
{ok, [{hook, webadmin_menu_main, web_menu_main, 50, global},
{hook, webadmin_page_main, web_page_main, 50, global},
{hook, webadmin_menu_host, web_menu_host, 50},
{hook, webadmin_page_host, web_page_host, 50},
{hook, webadmin_menu_hostuser, web_menu_hostuser, 50},
{hook, webadmin_page_hostuser, web_page_hostuser, 50},
{hook, webadmin_menu_hostnode, web_menu_hostnode, 50},
{hook, webadmin_page_hostnode, web_page_hostnode, 50},
{hook, webadmin_menu_node, web_menu_node, 50, global},
{hook, webadmin_page_node, web_page_node, 50, global}]}.
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
@ -670,6 +690,16 @@ get_commands_spec() ->
{pending, string},
{groups, {list, {group, string}}}
]}}}}},
#ejabberd_commands{name = get_roster_count, tags = [roster],
desc = "Get number of contacts in a local user roster",
note = "added in 24.xx",
policy = user,
module = ?MODULE, function = get_roster_count,
args = [],
args_rename = [{server, host}],
result_example = 5,
result_desc = "Number",
result = {value, integer}},
#ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user",
longdesc = "The text file must contain an erlang term: a list "
@ -777,6 +807,14 @@ get_commands_spec() ->
args_desc = ["Group identifier", "Group server name", "Group name",
"Group description", "List of groups to display"],
result = {res, rescode}},
#ejabberd_commands{name = srg_add, tags = [shared_roster_group],
desc = "Add/Create a Shared Roster Group (without details)",
module = ?MODULE, function = srg_add,
note = "added in 24.xx",
args = [{group, binary}, {host, binary}],
args_example = [<<"group3">>, <<"myserver.com">>],
args_desc = ["Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
desc = "Delete a Shared Roster Group",
module = ?MODULE, function = srg_delete,
@ -802,6 +840,48 @@ get_commands_spec() ->
result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
result_desc = "List of group information, as key and value",
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
#ejabberd_commands{name = srg_set_info, tags = [shared_roster_group],
desc = "Set info of a Shared Roster Group",
module = ?MODULE, function = srg_set_info,
note = "added in 24.xx",
args = [{group, binary}, {host, binary}, {key, binary}, {value, binary}],
args_example = [<<"group3">>, <<"myserver.com">>, <<"label">>, <<"Family">>],
args_desc = ["Group identifier", "Group server name",
"Information key: label, description",
"Information value"],
result = {res, rescode}},
#ejabberd_commands{name = srg_get_displayed, tags = [shared_roster_group],
desc = "Get displayed groups of a Shared Roster Group",
module = ?MODULE, function = srg_get_displayed,
note = "added in 24.xx",
args = [{group, binary}, {host, binary}],
args_example = [<<"group3">>, <<"myserver.com">>],
args_desc = ["Group identifier", "Group server name"],
result_example = [<<"group1">>, <<"group2">>],
result_desc = "List of groups to display",
result = {display, {list, {group, binary}}}},
#ejabberd_commands{name = srg_add_displayed, tags = [shared_roster_group],
desc = "Add a group to displayed_groups of a Shared Roster Group",
module = ?MODULE, function = srg_add_displayed,
note = "added in 24.xx",
args = [{group, binary}, {host, binary},
{add, binary}],
args_example = [<<"group3">>, <<"myserver.com">>, <<"group1">>],
args_desc = ["Group identifier", "Group server name",
"Group to add to displayed_groups"],
result = {res, rescode}},
#ejabberd_commands{name = srg_del_displayed, tags = [shared_roster_group],
desc = "Delete a group from displayed_groups of a Shared Roster Group",
module = ?MODULE, function = srg_del_displayed,
note = "added in 24.xx",
args = [{group, binary}, {host, binary},
{del, binary}],
args_example = [<<"group3">>, <<"myserver.com">>, <<"group1">>],
args_desc = ["Group identifier", "Group server name",
"Group to delete from displayed_groups"],
result = {res, rescode}},
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
desc = "Get members of a Shared Roster Group",
module = ?MODULE, function = srg_get_members,
@ -836,6 +916,18 @@ get_commands_spec() ->
result_example = 5,
result_desc = "Number",
result = {value, integer}},
#ejabberd_commands{name = get_offline_messages,
tags = [internal, offline],
desc = "Get the offline messages",
policy = user,
module = mod_offline, function = get_offline_messages,
args = [],
result = {queue, {list, {messages, {tuple, [{time, string},
{from, string},
{to, string},
{packet, string}
]}}}}},
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
longdesc = "When sending a groupchat message to a MUC room, "
@ -1586,6 +1678,15 @@ make_roster_xmlrpc(Roster) ->
end,
Roster).
get_roster_count(User, Server) ->
case jid:make(User, Server) of
error ->
throw({error, "Invalid 'user'/'server'"});
#jid{luser = U, lserver = S} ->
Items = ejabberd_hooks:run_fold(roster_get, S, [], [{U, S}]),
length(Items)
end.
%%-----------------------------
%% Push Roster from file
%%-----------------------------
@ -1748,12 +1849,29 @@ srg_create(Group, Host, Label, Description, Display) when is_binary(Display) ->
srg_create(Group, Host, Label, Description, DisplayList);
srg_create(Group, Host, Label, Description, DisplayList) ->
{_DispGroups, WrongDispGroups} = filter_groups_existence(Host, DisplayList),
case (WrongDispGroups -- [Group]) /= [] of
true ->
{wrong_displayed_groups, WrongDispGroups};
false ->
srg_create2(Group, Host, Label, Description, DisplayList)
end.
srg_create2(Group, Host, Label, Description, DisplayList) ->
Opts = [{label, Label},
{displayed_groups, DisplayList},
{description, Description}],
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
ok.
srg_add(Group, Host) ->
Opts = [{label, <<"">>},
{description, <<"">>},
{displayed_groups, []}
],
{atomic, _} = mod_shared_roster:create_group(Host, Group, Opts),
ok.
srg_delete(Group, Host) ->
{atomic, _} = mod_shared_roster:delete_group(Host, Group),
ok.
@ -1769,9 +1887,109 @@ srg_get_info(Group, Host) ->
[{misc:atom_to_binary(Title), to_list(Value)} || {Title, Value} <- Opts].
to_list([]) -> [];
to_list([H|T]) -> [to_list(H)|to_list(T)];
to_list([H|_]=List) when is_binary(H) -> lists:join(", ", [to_list(E) || E <- List]);
to_list(E) when is_atom(E) -> atom_to_list(E);
to_list(E) -> binary_to_list(E).
to_list(E) when is_binary(E) -> binary_to_list(E).
%% @format-begin
srg_set_info(Group, Host, Key, Value) ->
Opts =
case mod_shared_roster:get_group_opts(Host, Group) of
Os when is_list(Os) ->
Os;
error ->
[]
end,
Opts2 = srg_set_info(Key, Value, Opts),
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
{atomic, ok} ->
ok;
Problem ->
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
error
end.
srg_set_info(<<"description">>, Value, Opts) ->
[{description, Value} | proplists:delete(description, Opts)];
srg_set_info(<<"label">>, Value, Opts) ->
[{label, Value} | proplists:delete(label, Opts)];
srg_set_info(<<"all_users">>, <<"true">>, Opts) ->
[{all_users, true} | proplists:delete(all_users, Opts)];
srg_set_info(<<"online_users">>, <<"true">>, Opts) ->
[{online_users, true} | proplists:delete(online_users, Opts)];
srg_set_info(<<"all_users">>, _, Opts) ->
proplists:delete(all_users, Opts);
srg_set_info(<<"online_users">>, _, Opts) ->
proplists:delete(online_users, Opts);
srg_set_info(Key, _Value, Opts) ->
?ERROR_MSG("Unknown Key in srg_set_info: ~p", [Key]),
Opts.
srg_get_displayed(Group, Host) ->
Opts =
case mod_shared_roster:get_group_opts(Host, Group) of
Os when is_list(Os) ->
Os;
error ->
[]
end,
proplists:get_value(displayed_groups, Opts).
srg_add_displayed(Group, Host, NewGroup) ->
Opts =
case mod_shared_roster:get_group_opts(Host, Group) of
Os when is_list(Os) ->
Os;
error ->
[]
end,
{DispGroups, WrongDispGroups} = filter_groups_existence(Host, [NewGroup]),
case WrongDispGroups /= [] of
true ->
{wrong_displayed_groups, WrongDispGroups};
false ->
DisplayedOld = proplists:get_value(displayed_groups, Opts, []),
Opts2 =
[{displayed_groups, lists:flatten(DisplayedOld, DispGroups)}
| proplists:delete(displayed_groups, Opts)],
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
{atomic, ok} ->
ok;
Problem ->
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
error
end
end.
srg_del_displayed(Group, Host, OldGroup) ->
Opts =
case mod_shared_roster:get_group_opts(Host, Group) of
Os when is_list(Os) ->
Os;
error ->
[]
end,
DisplayedOld = proplists:get_value(displayed_groups, Opts, []),
{DispGroups, OldDispGroups} = lists:partition(fun(G) -> G /= OldGroup end, DisplayedOld),
case OldDispGroups == [] of
true ->
{inexistent_displayed_groups, OldGroup};
false ->
Opts2 = [{displayed_groups, DispGroups} | proplists:delete(displayed_groups, Opts)],
case mod_shared_roster:set_group_opts(Host, Group, Opts2) of
{atomic, ok} ->
ok;
Problem ->
?INFO_MSG("Problem: ~n ~p", [Problem]), %+++
error
end
end.
filter_groups_existence(Host, Groups) ->
lists:partition(fun(Group) -> error /= mod_shared_roster:get_group_opts(Host, Group) end,
Groups).
%% @format-end
srg_get_members(Group, Host) ->
Members = mod_shared_roster:get_group_explicit_users(Host,Group),
@ -1915,6 +2133,256 @@ num_prio(Priority) when is_integer(Priority) ->
num_prio(_) ->
-1.
%%%
%%% Web Admin
%%%
%% @format-begin
%%% Main
web_menu_main(Acc, _Lang) ->
Acc ++ [{<<"stats">>, <<"Statistics">>}].
web_page_main(_, #request{path = [<<"stats">>]} = R) ->
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
++ [make_command(stats_host, R, [], [{only, presentation}]),
make_command(incoming_s2s_number, R, [], [{only, presentation}]),
make_command(outgoing_s2s_number, R, [], [{only, presentation}]),
make_table([<<"stat name">>, {<<"stat value">>, right}],
[{?C(<<"Registered Users:">>),
make_command(stats,
R,
[{<<"name">>, <<"registeredusers">>}],
[{only, value}])},
{?C(<<"Online Users:">>),
make_command(stats,
R,
[{<<"name">>, <<"onlineusers">>}],
[{only, value}])},
{?C(<<"S2S Connections Incoming:">>),
make_command(incoming_s2s_number, R, [], [{only, value}])},
{?C(<<"S2S Connections Outgoing:">>),
make_command(outgoing_s2s_number, R, [], [{only, value}])}])],
{stop, Res};
web_page_main(Acc, _) ->
Acc.
%%% Host
web_menu_host(Acc, _Host, _Lang) ->
Acc ++ [{<<"purge">>, <<"Purge">>}, {<<"stats">>, <<"Statistics">>}].
web_page_host(_, Host, #request{path = [<<"purge">>]} = R) ->
Head = [?XC(<<"h1">>, <<"Purge">>)],
Set = [ejabberd_web_admin:make_command(delete_old_users_vhost,
R,
[{<<"host">>, Host}],
[])],
{stop, Head ++ Set};
web_page_host(_, Host, #request{path = [<<"stats">>]} = R) ->
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
++ [make_command(stats_host, R, [], [{only, presentation}]),
make_table([<<"stat name">>, {<<"stat value">>, right}],
[{?C(<<"Registered Users:">>),
make_command(stats_host,
R,
[{<<"host">>, Host}, {<<"name">>, <<"registeredusers">>}],
[{only, value},
{result_links, [{stat, arg_host, 3, <<"users">>}]}])},
{?C(<<"Online Users:">>),
make_command(stats_host,
R,
[{<<"host">>, Host}, {<<"name">>, <<"onlineusers">>}],
[{only, value},
{result_links,
[{stat, arg_host, 3, <<"online-users">>}]}])}])],
{stop, Res};
web_page_host(Acc, _, _) ->
Acc.
%%% HostUser
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
Acc
++ [{<<"auth">>, <<"Authentication">>},
{<<"mam">>, <<"MAM">>},
{<<"privacy">>, <<"Privacy Lists">>},
{<<"private">>, <<"Private XML Storage">>},
{<<"session">>, <<"Sessions">>},
{<<"vcard">>, <<"vCard">>}].
web_page_hostuser(_, Host, User, #request{path = [<<"auth">>]} = R) ->
Ban = make_command(ban_account,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}]),
Unban = make_command(unban_account, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
Res = ?H1GLraw(<<"Authentication">>,
<<"admin/configuration/authentication/">>,
<<"Authentication">>)
++ [make_command(register, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(check_account, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
?X(<<"hr">>),
make_command(check_password, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(check_password_hash, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(change_password,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}]),
?X(<<"hr">>),
make_command(get_ban_details, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
Ban,
Unban,
?X(<<"hr">>),
make_command(unregister,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"mam">>]} = R) ->
Res = ?H1GL(<<"MAM">>, <<"modules/#mod_mam">>, <<"mod_mam">>)
++ [make_command(remove_mam_for_user,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}]),
make_command(remove_mam_for_user_with_peer,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"privacy">>]} = R) ->
Res = ?H1GL(<<"Privacy Lists">>, <<"modules/#mod_privacy">>, <<"mod_privacy">>)
++ [make_command(privacy_set, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"private">>]} = R) ->
Res = ?H1GL(<<"Private XML Storage">>, <<"modules/#mod_private">>, <<"mod_private">>)
++ [make_command(private_set, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(private_get, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"session">>]} = R) ->
Head = [?XC(<<"h1">>, <<"Sessions">>), ?BR],
Set = [make_command(resource_num, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(set_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(kick_user, R, [{<<"user">>, User}, {<<"host">>, Host}], [{style, danger}]),
make_command(kick_session,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{style, danger}])],
timer:sleep(100), % kicking sessions takes a while, let's delay the get commands
Get = [make_command(user_sessions_info,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{result_links, [{node, node, 5, <<>>}]}]),
make_command(user_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(get_presence, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(num_resources, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
{stop, Head ++ Get ++ Set};
web_page_hostuser(_, Host, User, #request{path = [<<"vcard">>]} = R) ->
Head = ?H1GL(<<"vCard">>, <<"modules/#mod_vcard">>, <<"mod_vcard">>),
Set = [make_command(set_nickname, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(set_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(set_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(set_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
timer:sleep(100), % setting vcard takes a while, let's delay the get commands
FieldNames = [<<"VERSION">>, <<"FN">>, <<"NICKNAME">>, <<"BDAY">>],
FieldNames2 =
[{<<"N">>, <<"FAMILY">>},
{<<"N">>, <<"GIVEN">>},
{<<"N">>, <<"MIDDLE">>},
{<<"ADR">>, <<"CTRY">>},
{<<"ADR">>, <<"LOCALITY">>},
{<<"EMAIL">>, <<"USERID">>}],
Get = [make_command(get_vcard, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
?XE(<<"blockquote">>,
[make_table([<<"name">>, <<"value">>],
[{?C(FieldName),
make_command(get_vcard,
R,
[{<<"user">>, User},
{<<"host">>, Host},
{<<"name">>, FieldName}],
[{only, value}])}
|| FieldName <- FieldNames])]),
make_command(get_vcard2, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
?XE(<<"blockquote">>,
[make_table([<<"name">>, <<"subname">>, <<"value">>],
[{?C(FieldName),
?C(FieldSubName),
make_command(get_vcard2,
R,
[{<<"user">>, User},
{<<"host">>, Host},
{<<"name">>, FieldName},
{<<"subname">>, FieldSubName}],
[{only, value}])}
|| {FieldName, FieldSubName} <- FieldNames2])]),
make_command(get_vcard2_multi, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
{stop, Head ++ Get ++ Set};
web_page_hostuser(Acc, _, _, _) ->
Acc.
%%% HostNode
web_menu_hostnode(Acc, _Host, _Username, _Lang) ->
Acc ++ [{<<"modules">>, <<"Modules">>}].
web_page_hostnode(_, Host, Node, #request{path = [<<"modules">>]} = R) ->
Res = ?H1GLraw(<<"Modules">>, <<"admin/configuration/modules/">>, <<"Modules Options">>)
++ [ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[restart_module, R, [{<<"host">>, Host}], []])],
{stop, Res};
web_page_hostnode(Acc, _Host, _Node, _Request) ->
Acc.
%%% Node
web_menu_node(Acc, _Node, _Lang) ->
Acc ++ [{<<"stats">>, <<"Statistics">>}].
web_page_node(_, Node, #request{path = [<<"stats">>]} = R) ->
UpSecs =
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[stats, R, [{<<"name">>, <<"uptimeseconds">>}], [{only, value}]]),
UpDaysBin = integer_to_binary(binary_to_integer(fxml:get_tag_cdata(UpSecs)) div 24000),
UpDays =
#xmlel{name = <<"code">>,
attrs = [],
children = [{xmlcdata, UpDaysBin}]},
Res = ?H1GL(<<"Statistics">>, <<"modules/#mod_stats">>, <<"mod_stats">>)
++ [make_command(stats, R, [], [{only, presentation}]),
make_table([<<"stat name">>, {<<"stat value">>, right}],
[{?C(<<"Online Users in this node:">>),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[stats,
R,
[{<<"name">>, <<"onlineusersnode">>}],
[{only, value}]])},
{?C(<<"Uptime Seconds:">>), UpSecs},
{?C(<<"Uptime Seconds (rounded to days):">>), UpDays},
{?C(<<"Processes:">>),
ejabberd_cluster:call(Node,
ejabberd_web_admin,
make_command,
[stats,
R,
[{<<"name">>, <<"processes">>}],
[{only, value}]])}])],
{stop, Res};
web_page_node(Acc, _, _) ->
Acc.
%% @format-end
%%%
%%% Document
%%%
mod_options(_) -> [].
mod_doc() ->

View File

@ -34,7 +34,7 @@
process_iq/1,
get_mix_roster_items/2,
webadmin_user/4,
webadmin_page/3]).
webadmin_menu_hostuser/4, webadmin_page_hostuser/4]).
-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
@ -69,7 +69,8 @@ start(Host, Opts) ->
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:add(roster_get, Host, ?MODULE, get_mix_roster_items, 50),
ejabberd_hooks:add(webadmin_user, Host, ?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_menu_hostuser, Host, ?MODULE, webadmin_menu_hostuser, 50),
ejabberd_hooks:add(webadmin_page_hostuser, Host, ?MODULE, webadmin_page_hostuser, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0, ?MODULE, process_iq),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_2, ?MODULE, process_iq);
Err ->
@ -82,7 +83,8 @@ stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:delete(roster_get, Host, ?MODULE, get_mix_roster_items, 50),
ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_menu_hostuser, Host, ?MODULE, webadmin_menu_hostuser, 50),
ejabberd_hooks:delete(webadmin_page_hostuser, Host, ?MODULE, webadmin_page_hostuser, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_2).
@ -469,7 +471,7 @@ delete_cache(Mod, JID, Channel) ->
%%%===================================================================
%%% Webadmin interface
%%%===================================================================
webadmin_user(Acc, User, Server, Lang) ->
webadmin_user(Acc, User, Server, #request{lang = Lang}) ->
QueueLen = case get_channels({jid:nodeprep(User), jid:nameprep(Server), <<>>}) of
{ok, Channels} -> length(Channels);
error -> -1
@ -482,12 +484,13 @@ webadmin_user(Acc, User, Server, Lang) ->
?C(<<" | ">>),
FQueueView].
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"mix_channels">>],
lang = Lang} = _Request) ->
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
Acc ++ [{<<"mix_channels">>, <<"MIX Channels">>}].
webadmin_page_hostuser(_, Host, U, #request{path = [<<"mix_channels">>], lang = Lang}) ->
Res = web_mix_channels(U, Host, Lang),
{stop, Res};
webadmin_page(Acc, _, _) -> Acc.
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
web_mix_channels(User, Server, Lang) ->
LUser = jid:nodeprep(User),

View File

@ -34,20 +34,24 @@
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
rooms_empty_list/1, rooms_empty_destroy/1,
rooms_empty_list/1, rooms_empty_destroy/1, rooms_empty_destroy_restuple/1,
get_user_rooms/2, get_user_subscriptions/2, get_room_occupants/2,
get_room_occupants_number/2, send_direct_invitation/5,
change_room_option/4, get_room_options/2,
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, subscribe_room_many/3,
unsubscribe_room/2, get_subscribers/2,
get_room_serverhost/1,
web_page_host/3,
web_menu_main/2, web_page_main/2,
web_menu_host/3, web_page_host/3,
web_menu_hostuser/4, web_page_hostuser/4,
webadmin_muc/2,
mod_opt_type/1, mod_options/1,
get_commands_spec/0, find_hosts/1, room_diagnostics/2,
get_room_pid/2, get_room_history/2]).
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("mod_muc.hrl").
@ -66,7 +70,10 @@ start(_Host, _Opts) ->
{ok, [{hook, webadmin_menu_main, web_menu_main, 50, global},
{hook, webadmin_page_main, web_page_main, 50, global},
{hook, webadmin_menu_host, web_menu_host, 50},
{hook, webadmin_page_host, web_page_host, 50}]}.
{hook, webadmin_page_host, web_page_host, 50},
{hook, webadmin_menu_hostuser, web_menu_hostuser, 50},
{hook, webadmin_page_hostuser, web_page_hostuser, 50}
]}.
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
@ -235,6 +242,19 @@ get_commands_spec() ->
args = [{service, binary}],
args_rename = [{host, service}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
desc = "Destroy the rooms that have no messages in archive",
longdesc = "The MUC service argument can be `global` to get all hosts.",
module = ?MODULE, function = rooms_empty_destroy_restuple,
version = 2,
note = "modified in 24.xx",
args_desc = ["MUC service, or `global` for all"],
args_example = ["conference.example.com"],
result_desc = "List of empty rooms that have been destroyed",
result_example = {ok, <<"Destroyed rooms: 2">>},
args = [{service, binary}],
args_rename = [{host, service}],
result = {res, restuple}},
#ejabberd_commands{name = get_user_rooms, tags = [muc],
desc = "Get the list of rooms where this user is occupant",
@ -478,7 +498,13 @@ get_commands_spec() ->
result = {history, {list,
{entry, {tuple,
[{timestamp, string},
{message, string}]}}}}}
{message, string}]}}}}},
#ejabberd_commands{name = webadmin_muc, tags = [internal],
desc = "Generate WebAdmin MUC Rooms HTML",
module = ?MODULE, function = webadmin_muc,
args = [{request, any}, {lang, binary}],
result = {res, any}}
].
@ -580,6 +606,8 @@ get_user_subscriptions(User, Server) ->
%% Web Admin
%%----------------------------
%% @format-begin
%%---------------
%% Web Admin Menu
@ -589,112 +617,404 @@ web_menu_main(Acc, Lang) ->
web_menu_host(Acc, _Host, Lang) ->
Acc ++ [{<<"muc">>, translate:translate(Lang, ?T("Multi-User Chat"))}].
%%---------------
%% Web Admin Page
-define(TDTD(L, N),
?XE(<<"tr">>, [?XCT(<<"td">>, L),
?XC(<<"td">>, integer_to_binary(N))
])).
?XE(<<"tr">>, [?XCT(<<"td">>, L), ?XC(<<"td">>, integer_to_binary(N))])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
OnlineRoomsNumber = lists:foldl(
fun(Host, Acc) ->
Acc + mod_muc:count_online_rooms(Host)
end, 0, find_hosts(global)),
web_page_main(_, #request{path = [<<"muc">>], lang = Lang} = R) ->
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
Res = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>) ++
[?XCT(<<"h3">>, ?T("Statistics")),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(?T("Total rooms"), OnlineRoomsNumber)
])
]),
?XE(<<"ul">>, [?LI([?ACT(<<"rooms/">>, ?T("List of rooms"))])])
],
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Res = [make_command(webadmin_muc, R, [{<<"request">>, R}, {<<"lang">>, Lang}], [])],
{stop, Title ++ Res};
web_page_main(Acc, _) ->
Acc.
web_page_host(_, Host, #request{path = [<<"muc">> | RPath], lang = Lang} = R) ->
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
Service = find_service(Host),
Level = length(RPath),
Res = webadmin_muc_host(Host, Service, RPath, R, Lang, Level, PageTitle),
{stop, Res};
web_page_host(Acc, _, _) ->
Acc.
web_page_main(_, #request{path=[<<"muc">>, <<"rooms">>], q = Q, lang = Lang} = _Request) ->
Sort_query = get_sort_query(Q),
Res = make_rooms_page(global, Lang, Sort_query),
{stop, Res};
%%---------------
%% WebAdmin MUC Host Page
web_page_main(Acc, _) -> Acc.
webadmin_muc_host(Host,
Service,
[<<"create-room">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Create Room">>, RPath}),
Set = [make_command(create_room, R, [{<<"service">>, Service}, {<<"host">>, Host}], []),
make_command(create_room_with_opts,
R,
[{<<"service">>, Service}, {<<"host">>, Host}],
[])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"nick-register">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({service_section, Level, Service, <<"Nick Register">>, RPath}),
Set = [make_command(muc_register_nick, R, [{<<"service">>, Service}], []),
make_command(muc_unregister_nick, R, [{<<"service">>, Service}], [])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms-empty">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms Empty">>, RPath}),
Set = [make_command(rooms_empty_list,
R,
[{<<"service">>, Service}],
[{table_options, {2, RPath}},
{result_links, [{room, room, 3 + Level, <<"">>}]}]),
make_command(rooms_empty_destroy, R, [{<<"service">>, Service}], [])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms-unused">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({service_section, Level, Service, <<"Rooms Unused">>, RPath}),
Set = [make_command(rooms_unused_list,
R,
[{<<"service">>, Service}],
[{result_links, [{room, room, 3 + Level, <<"">>}]}]),
make_command(rooms_unused_destroy, R, [{<<"service">>, Service}], [])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms-regex">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({service_section, Level, Service, <<"Rooms by Regex">>, RPath}),
Set = [make_command(muc_online_rooms_by_regex,
R,
[{<<"service">>, Service}],
[{result_links, [{jid, room, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"affiliations">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Affiliations">>, Name, R, RPath}),
Set = [make_command(set_room_affiliation,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[])],
Get = [make_command(get_room_affiliations,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}}])],
Title ++ Breadcrumb ++ Get ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"history">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"History">>, Name, R, RPath}),
Get = [make_command(get_room_history,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{table_options, {10, RPath}},
{result_links, [{message, paragraph, 1, <<"">>}]}])],
Title ++ Breadcrumb ++ Get;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"invite">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Invite">>, Name, R, RPath}),
Set = [make_command(send_direct_invitation,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"occupants">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Occupants">>, Name, R, RPath}),
Get = [make_command(get_room_occupants,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}},
{result_links, [{jid, user, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Get;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"options">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Options">>, Name, R, RPath}),
Set = [make_command(change_room_option,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[])],
Get = [make_command(get_room_options,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[])],
Title ++ Breadcrumb ++ Get ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"subscribers">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title =
?H1GLraw(PageTitle,
<<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
<<"MUC/Sub Extension">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Subscribers">>, Name, R, RPath}),
Set = [make_command(subscribe_room,
R,
[{<<"room">>, jid:encode({Name, Service, <<"">>})}],
[]),
make_command(unsubscribe_room,
R,
[{<<"room">>, jid:encode({Name, Service, <<"">>})}],
[{style, danger}])],
Get = [make_command(get_subscribers,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{table_options, {20, RPath}},
{result_links, [{jid, user, 3 + Level, <<"">>}]}])],
Title ++ Breadcrumb ++ Get ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name, <<"destroy">> | RPath],
R,
_Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb =
make_breadcrumb({room_section, Level, Service, <<"Destroy">>, Name, R, RPath}),
Set = [make_command(destroy_room,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{style, danger}])],
Title ++ Breadcrumb ++ Set;
webadmin_muc_host(_Host,
Service,
[<<"rooms">>, <<"room">>, Name | _RPath],
_R,
Lang,
Level,
PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb = make_breadcrumb({room, Level, Service, Name}),
MenuItems =
[{<<"affiliations/">>, <<"Affiliations">>},
{<<"history/">>, <<"History">>},
{<<"invite/">>, <<"Invite">>},
{<<"occupants/">>, <<"Occupants">>},
{<<"options/">>, <<"Options">>},
{<<"subscribers/">>, <<"Subscribers">>},
{<<"destroy/">>, <<"Destroy">>}],
Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
Title ++ Breadcrumb ++ Get;
webadmin_muc_host(_Host, Service, [<<"rooms">> | RPath], R, _Lang, Level, PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb = make_breadcrumb({service_section, Level, Service, <<"Rooms">>, RPath}),
Columns = [<<"jid">>, <<"occupants">>],
Rows =
lists:map(fun(NameService) ->
#jid{user = Name} = jid:decode(NameService),
{make_command(echo,
R,
[{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
[{only, raw_and_value},
{result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
make_command(get_room_occupants_number,
R,
[{<<"name">>, Name}, {<<"service">>, Service}],
[{only, raw_and_value}])}
end,
make_command_raw_value(muc_online_rooms, R, [{<<"service">>, Service}])),
Get = [make_command(muc_online_rooms, R, [], [{only, presentation}]),
make_command(get_room_occupants_number, R, [], [{only, presentation}]),
make_table(20, RPath, Columns, Rows)],
Title ++ Breadcrumb ++ Get;
webadmin_muc_host(_Host, Service, [], _R, Lang, _Level, PageTitle) ->
Title = ?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>),
Breadcrumb = make_breadcrumb({service, Service}),
MenuItems =
[{<<"create-room/">>, <<"Create Room">>},
{<<"rooms/">>, <<"Rooms">>},
{<<"rooms-regex/">>, <<"Rooms by Regex">>},
{<<"rooms-empty/">>, <<"Rooms Empty">>},
{<<"rooms-unused/">>, <<"Rooms Unused">>},
{<<"nick-register/">>, <<"Nick Register">>}],
Get = [?XE(<<"ul">>, [?LI([?ACT(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
Title ++ Breadcrumb ++ Get;
webadmin_muc_host(_Host, _Service, _RPath, _R, _Lang, _Level, _PageTitle) ->
[].
web_page_host(_, Host,
#request{path = [<<"muc">>],
q = Q,
lang = Lang} = _Request) ->
Sort_query = get_sort_query(Q),
Res = make_rooms_page(Host, Lang, Sort_query),
{stop, Res};
web_page_host(Acc, _, _) -> Acc.
make_breadcrumb({service, Service}) ->
make_breadcrumb([Service]);
make_breadcrumb({service_section, Level, Service, Section, RPath}) ->
make_breadcrumb([{Level, Service}, separator, Section | RPath]);
make_breadcrumb({room, Level, Service, Name}) ->
make_breadcrumb([{Level, Service},
separator,
{Level - 1, <<"Rooms">>},
separator,
jid:encode({Name, Service, <<"">>})]);
make_breadcrumb({room_section, Level, Service, Section, Name, R, RPath}) ->
make_breadcrumb([{Level, Service},
separator,
{Level - 1, <<"Rooms">>},
separator,
make_command(echo,
R,
[{<<"sentence">>, jid:encode({Name, Service, <<"">>})}],
[{only, value},
{result_links, [{sentence, room, 3 + Level, <<"">>}]}]),
separator,
Section
| RPath]);
make_breadcrumb(Elements) ->
lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
Xmlel;
(<<"sort">>) ->
?C(<<" +">>);
(<<"page">>) ->
?C(<<" #">>);
(separator) ->
?C(<<" > ">>);
(Bin) when is_binary(Bin) ->
?C(Bin);
({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
?AC(binary:copy(<<"../">>, Level), Bin)
end,
Elements).
%%---------------
%%
%% Returns: {normal | reverse, Integer}
get_sort_query(Q) ->
case catch get_sort_query2(Q) of
{ok, Res} -> Res;
_ -> {normal, 1}
{ok, Res} ->
Res;
_ ->
{normal, 1}
end.
get_sort_query2(Q) ->
{value, {_, Binary}} = lists:keysearch(<<"sort">>, 1, Q),
Integer = list_to_integer(string:strip(binary_to_list(Binary), right, $/)),
case Integer >= 0 of
true -> {ok, {normal, Integer}};
false -> {ok, {reverse, abs(Integer)}}
true ->
{ok, {normal, Integer}};
false ->
{ok, {reverse, abs(Integer)}}
end.
make_rooms_page(Host, Lang, {Sort_direction, Sort_column}) ->
webadmin_muc(#request{q = Q} = R, Lang) ->
{Sort_direction, Sort_column} = get_sort_query(Q),
Host = global,
Service = find_service(Host),
Rooms_names = get_online_rooms(Service),
Rooms_infos = build_info_rooms(Rooms_names),
Rooms_sorted = sort_rooms(Sort_direction, Sort_column, Rooms_infos),
Rooms_prepared = prepare_rooms_infos(Rooms_sorted),
TList = lists:map(
fun(Room) ->
?XE(<<"tr">>, [?XC(<<"td">>, E) || E <- Room])
end, Rooms_prepared),
Titles = [?T("Jabber ID"),
?T("# participants"),
?T("Last message"),
?T("Public"),
?T("Persistent"),
?T("Logging"),
?T("Just created"),
?T("Room title"),
?T("Node")],
TList =
lists:map(fun([RoomJid | Room]) ->
JidLink =
make_command(echo,
R,
[{<<"sentence">>, RoomJid}],
[{only, value},
{result_links, [{sentence, room, 1, <<"">>}]}]),
?XE(<<"tr">>, [?XE(<<"td">>, [JidLink]) | [?XC(<<"td">>, E) || E <- Room]])
end,
Rooms_prepared),
Titles =
[?T("Jabber ID"),
?T("# participants"),
?T("Last message"),
?T("Public"),
?T("Persistent"),
?T("Logging"),
?T("Just created"),
?T("Room title"),
?T("Node")],
{Titles_TR, _} =
lists:mapfoldl(
fun(Title, Num_column) ->
NCS = integer_to_binary(Num_column),
TD = ?XE(<<"td">>, [?CT(Title),
?C(<<" ">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column+1}
end,
1,
Titles),
PageTitle = translate:translate(Lang, ?T("Multi-User Chat")),
?H1GL(PageTitle, <<"modules/#mod_muc">>, <<"mod_muc">>) ++
lists:mapfoldl(fun(Title, Num_column) ->
NCS = integer_to_binary(Num_column),
TD = ?XE(<<"td">>,
[?CT(Title),
?C(<<" ">>),
?AC(<<"?sort=", NCS/binary>>, <<"<">>),
?C(<<" ">>),
?AC(<<"?sort=-", NCS/binary>>, <<">">>)]),
{TD, Num_column + 1}
end,
1,
Titles),
[?XCT(<<"h2">>, ?T("Chatrooms")),
?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>, Titles_TR)]
),
?XE(<<"tbody">>, TList)
]
)
].
[?XE(<<"thead">>, [?XE(<<"tr">>, Titles_TR)]), ?XE(<<"tbody">>, TList)])].
sort_rooms(Direction, Column, Rooms) ->
Rooms2 = lists:keysort(Column, Rooms),
case Direction of
normal -> Rooms2;
reverse -> lists:reverse(Rooms2)
normal ->
Rooms2;
reverse ->
lists:reverse(Rooms2)
end.
build_info_rooms(Rooms) ->
@ -712,16 +1032,16 @@ build_info_room({Name, Host, _ServerHost, Pid}) ->
Num_participants = maps:size(S#state.users),
Node = node(Pid),
History = (S#state.history)#lqueue.queue,
History = S#state.history#lqueue.queue,
Ts_last_message =
case p1_queue:is_empty(History) of
true ->
<<"A long time ago">>;
false ->
Last_message1 = get_queue_last(History),
{_, _, _, Ts_last, _} = Last_message1,
xmpp_util:encode_timestamp(Ts_last)
end,
case p1_queue:is_empty(History) of
true ->
<<"A long time ago">>;
false ->
Last_message1 = get_queue_last(History),
{_, _, _, Ts_last, _} = Last_message1,
xmpp_util:encode_timestamp(Ts_last)
end,
{<<Name/binary, "@", Host/binary>>,
Num_participants,
@ -739,6 +1059,7 @@ get_queue_last(Queue) ->
prepare_rooms_infos(Rooms) ->
[prepare_room_info(Room) || Room <- Rooms].
prepare_room_info(Room_info) ->
{NameHost,
Num_participants,
@ -748,7 +1069,8 @@ prepare_room_info(Room_info) ->
Logging,
Just_created,
Title,
Node} = Room_info,
Node} =
Room_info,
[NameHost,
integer_to_binary(Num_participants),
Ts_last_message,
@ -763,10 +1085,61 @@ justcreated_to_binary(J) when is_integer(J) ->
JNow = misc:usec_to_now(J),
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(JNow),
str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second]);
[Year, Month, Day, Hour, Minute, Second]);
justcreated_to_binary(J) when is_atom(J) ->
misc:atom_to_binary(J).
%%--------------------
%% Web Admin Host User
web_menu_hostuser(Acc, _Host, _Username, _Lang) ->
Acc
++ [{<<"muc-rooms">>, <<"MUC Rooms Online">>},
{<<"muc-affiliations">>, <<"MUC Rooms Affiliations">>},
{<<"muc-sub">>, <<"MUC Rooms Subscriptions">>},
{<<"muc-register">>, <<"MUC Service Registration">>}].
web_page_hostuser(_, Host, User, #request{path = [<<"muc-rooms">> | RPath]} = R) ->
Level = 5 + length(RPath),
Res = ?H1GL(<<"MUC Rooms Online">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
++ [make_command(get_user_rooms,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{table_options, {2, RPath}},
{result_links, [{room, room, Level, <<"">>}]}])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"muc-affiliations">>]} = R) ->
Jid = jid:encode(
jid:make(User, Host)),
Res = ?H1GL(<<"MUC Rooms Affiliations">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
++ [make_command(set_room_affiliation, R, [{<<"jid">>, Jid}], []),
make_command(get_room_affiliation, R, [{<<"jid">>, Jid}], [])],
{stop, Res};
web_page_hostuser(_, Host, User, #request{path = [<<"muc-sub">> | RPath]} = R) ->
Title =
?H1GLraw(<<"MUC Rooms Subscriptions">>,
<<"developer/xmpp-clients-bots/extensions/muc-sub/">>,
<<"MUC/Sub">>),
Level = 5 + length(RPath),
Set = [make_command(subscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], []),
make_command(unsubscribe_room, R, [{<<"user">>, User}, {<<"host">>, Host}], [])],
Get = [make_command(get_user_subscriptions,
R,
[{<<"user">>, User}, {<<"host">>, Host}],
[{table_options, {20, RPath}},
{result_links, [{roomjid, room, Level, <<"">>}]}])],
{stop, Title ++ Get ++ Set};
web_page_hostuser(_, Host, User, #request{path = [<<"muc-register">>]} = R) ->
Jid = jid:encode(
jid:make(User, Host)),
Res = ?H1GL(<<"MUC Service Registration">>, <<"modules/#mod_muc">>, <<"mod_muc">>)
++ [make_command(muc_register_nick, R, [{<<"jid">>, Jid}], []),
make_command(muc_unregister_nick, R, [{<<"jid">>, Jid}], [])],
{stop, Res};
web_page_hostuser(Acc, _, _, _) ->
Acc.
%% @format-end
%%----------------------------
%% Create/Delete Room
%%----------------------------
@ -898,6 +1271,10 @@ rooms_empty_list(Service) ->
rooms_empty_destroy(Service) ->
rooms_report(empty, destroy, Service, 0).
rooms_empty_destroy_restuple(Service) ->
DestroyedRooms = rooms_report(empty, destroy, Service, 0),
NumberBin = integer_to_binary(length(DestroyedRooms)),
{ok, <<"Destroyed rooms: ", NumberBin/binary>>}.
rooms_report(Method, Action, Service, Days) ->
{NA, NP, RP} = muc_unused(Method, Action, Service, Days),
@ -1413,7 +1790,8 @@ get_room_history(Name, Service) ->
History = p1_queue:to_list((StateData#state.history)#lqueue.queue),
lists:map(
fun({_Nick, Packet, _HaveSubject, TimeStamp, _Size}) ->
{xmpp_util:encode_timestamp(TimeStamp), fxml:element_to_binary(xmpp:encode(Packet))}
{xmpp_util:encode_timestamp(TimeStamp),
ejabberd_web_admin:pretty_print_xml(xmpp:encode(Packet))}
end, History);
_ ->
throw({error, "Unable to fetch room state."})

View File

@ -60,13 +60,17 @@
find_x_expire/2,
c2s_handle_info/2,
c2s_copy_session/2,
webadmin_page/3,
get_offline_messages/2,
webadmin_menu_hostuser/4,
webadmin_page_hostuser/4,
webadmin_user/4,
webadmin_user_parse_query/5,
c2s_handle_bind2_inline/1]).
-export([mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]).
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
-deprecated({get_queue_length,2}).
-include("logger.hrl").
@ -133,7 +137,8 @@ start(Host, Opts) ->
{hook, c2s_handle_info, c2s_handle_info, 50},
{hook, c2s_copy_session, c2s_copy_session, 50},
{hook, c2s_handle_bind2_inline, c2s_handle_bind2_inline, 50},
{hook, webadmin_page_host, webadmin_page, 50},
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
{hook, webadmin_user, webadmin_user, 50},
{hook, webadmin_user_parse_query, webadmin_user_parse_query, 50},
{iq_handler, ejabberd_sm, ?NS_FLEX_OFFLINE, handle_offline_query}]}.
@ -730,12 +735,39 @@ discard_warn_sender(Packet, Reason) ->
ok
end.
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"queue">>],
q = Query, lang = Lang} =
_Request) ->
Res = user_queue(U, Host, Query, Lang), {stop, Res};
webadmin_page(Acc, _, _) -> Acc.
%%%
%%% Commands
%%%
get_offline_messages(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
HdrsAll = case Mod:read_message_headers(LUser, LServer) of
error -> [];
L -> L
end,
format_user_queue(HdrsAll).
%%%
%%% WebAdmin
%%%
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
Acc ++ [{<<"queue">>, <<"Offline Queue">>}].
webadmin_page_hostuser(_, Host, U,
#request{us = _US, path = [<<"queue">> | RPath],
lang = Lang} = R) ->
US = {U, Host},
PageTitle = str:translate_and_format(Lang, ?T("~ts's Offline Messages Queue"), [us_to_list(US)]),
Head = ?H1GL(PageTitle, <<"modules/#mod_offline">>, <<"mod_offline">>),
Res = make_command(get_offline_messages, R, [{<<"user">>, U},
{<<"host">>, Host}],
[{table_options, {10, RPath}},
{result_links, [{packet, paragraph, 1, <<"">>}]}]),
{stop, Head ++ [Res]};
webadmin_page_hostuser(Acc, _, _, _) -> Acc.
get_offline_els(LUser, LServer) ->
[Packet || {_Seq, Packet} <- read_messages(LUser, LServer)].
@ -939,8 +971,7 @@ count_mam_messages(LUser, LServer, ReadMsgs) ->
format_user_queue(Hdrs) ->
lists:map(
fun({Seq, From, To, TS, El}) ->
ID = integer_to_binary(Seq),
fun({_Seq, From, To, TS, El}) ->
FPacket = ejabberd_web_admin:pretty_print_xml(El),
SFrom = jid:encode(From),
STo = jid:encode(To),
@ -956,14 +987,7 @@ format_user_queue(Hdrs) ->
{_, _, _} = Now ->
format_time(Now)
end,
?XE(<<"tr">>,
[?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
[?INPUT(<<"checkbox">>, <<"selected">>, ID)]),
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time),
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom),
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo),
?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
[?XC(<<"pre">>, FPacket)])])
{Time, SFrom, STo, FPacket}
end, Hdrs).
format_time(Now) ->
@ -971,111 +995,18 @@ format_time(Now) ->
str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second]).
user_queue(User, Server, Query, Lang) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
Mod = gen_mod:db_mod(LServer, ?MODULE),
user_queue_parse_query(LUser, LServer, Query),
HdrsAll = case Mod:read_message_headers(LUser, LServer) of
error -> [];
L -> L
end,
Hdrs = get_messages_subset(User, Server, HdrsAll),
FMsgs = format_user_queue(Hdrs),
PageTitle = str:translate_and_format(Lang, ?T("~ts's Offline Messages Queue"), [us_to_list(US)]),
(?H1GL(PageTitle, <<"modules/#mod_offline">>, <<"mod_offline">>))
++ [?XREST(?T("Submitted"))] ++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>,
[?X(<<"td">>), ?XCT(<<"td">>, ?T("Time")),
?XCT(<<"td">>, ?T("From")),
?XCT(<<"td">>, ?T("To")),
?XCT(<<"td">>, ?T("Packet"))])]),
?XE(<<"tbody">>,
if FMsgs == [] ->
[?XE(<<"tr">>,
[?XAC(<<"td">>, [{<<"colspan">>, <<"4">>}],
<<" ">>)])];
true -> FMsgs
end)]),
?BR,
?INPUTTD(<<"submit">>, <<"delete">>,
?T("Delete Selected"))])].
user_queue_parse_query(LUser, LServer, Query) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case lists:keysearch(<<"delete">>, 1, Query) of
{value, _} ->
case user_queue_parse_query(LUser, LServer, Query, Mod, false) of
true ->
flush_cache(Mod, LUser, LServer);
false ->
ok
end;
_ ->
ok
end.
user_queue_parse_query(LUser, LServer, Query, Mod, Acc) ->
case lists:keytake(<<"selected">>, 1, Query) of
{value, {_, Seq}, Query2} ->
NewAcc = case catch binary_to_integer(Seq) of
I when is_integer(I), I>=0 ->
Mod:remove_message(LUser, LServer, I),
true;
_ ->
Acc
end,
user_queue_parse_query(LUser, LServer, Query2, Mod, NewAcc);
false ->
Acc
end.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
get_queue_length(LUser, LServer) ->
count_offline_messages(LUser, LServer).
get_messages_subset(User, Host, MsgsAll) ->
MaxOfflineMsgs = case get_max_user_messages(User, Host) of
Number when is_integer(Number) -> Number;
_ -> 100
end,
Length = length(MsgsAll),
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
webadmin_user(Acc, User, Server, R) ->
Acc ++ [make_command(get_offline_count, R, [{<<"user">>, User}, {<<"host">>, Server}], [])].
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 ->
MsgsAll;
get_messages_subset2(Max, Length, MsgsAll) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
Msgs2),
NoJID = jid:make(<<"...">>, <<"...">>),
Seq = <<"0">>,
IntermediateMsg = #xmlel{name = <<"...">>, attrs = [],
children = []},
MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
QueueLen = count_offline_messages(jid:nodeprep(User),
jid:nameprep(Server)),
FQueueLen = ?C(integer_to_binary(QueueLen)),
FQueueView = ?AC(<<"queue/">>,
?T("View Queue")),
Acc ++
[?XCT(<<"h3">>, ?T("Offline Messages:")),
FQueueLen,
?C(<<" | ">>),
FQueueView,
?C(<<" | ">>),
?INPUTTD(<<"submit">>, <<"removealloffline">>,
?T("Remove All Offline Messages"))].
%%%
%%%
%%%
-spec delete_all_msgs(binary(), binary()) -> {atomic, any()}.
delete_all_msgs(User, Server) ->

View File

@ -46,13 +46,16 @@
import_start/2, import_stop/2, is_subscribed/2,
c2s_self_presence/1, in_subscription/2,
out_subscription/1, set_items/3, remove_user/2,
get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
get_jid_info/4, encode_item/1, get_versioning_feature/2,
roster_version/2, mod_doc/0,
mod_opt_type/1, mod_options/1, set_roster/1, del_roster/3,
process_rosteritems/5,
depends/2, set_item_and_notify_clients/3]).
-export([webadmin_page_hostuser/4, webadmin_menu_hostuser/4, webadmin_user/4]).
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/4]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
-include("mod_roster.hrl").
@ -98,7 +101,8 @@ start(Host, Opts) ->
{hook, remove_user, remove_user, 50},
{hook, c2s_self_presence, c2s_self_presence, 50},
{hook, c2s_post_auth_features, get_versioning_feature, 50},
{hook, webadmin_page_host, webadmin_page, 50},
{hook, webadmin_menu_hostuser, webadmin_menu_hostuser, 50},
{hook, webadmin_page_hostuser, webadmin_page_hostuser, 50},
{hook, webadmin_user, webadmin_user, 50},
{iq_handler, ejabberd_sm, ?NS_ROSTER, process_iq}]}.
@ -1016,205 +1020,84 @@ process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"roster">>],
q = Query, lang = Lang} =
_Request) ->
Res = user_roster(U, Host, Query, Lang), {stop, Res};
webadmin_page(Acc, _, _) -> Acc.
%%% @format-begin
user_roster(User, Server, Query, Lang) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
Items1 = get_roster(LUser, LServer),
Res = user_roster_parse_query(User, Server, Items1,
Query),
Items = get_roster(LUser, LServer),
SItems = lists:sort(Items),
FItems = case SItems of
[] -> [?CT(?T("None"))];
_ ->
[?XE(<<"table">>,
[?XE(<<"thead">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Jabber ID")),
?XCT(<<"td">>, ?T("Nickname")),
?XCT(<<"td">>, ?T("Subscription")),
?XCT(<<"td">>, ?T("Pending")),
?XCT(<<"td">>, ?T("Groups"))])]),
?XE(<<"tbody">>,
(lists:map(fun (R) ->
Groups = lists:flatmap(fun
(Group) ->
[?C(Group),
?BR]
end,
R#roster.groups),
Pending =
ask_to_pending(R#roster.ask),
TDJID =
build_contact_jid_td(R#roster.jid),
?XE(<<"tr">>,
[TDJID,
?XAC(<<"td">>,
[{<<"class">>,
<<"valign">>}],
(R#roster.name)),
?XAC(<<"td">>,
[{<<"class">>,
<<"valign">>}],
(iolist_to_binary(atom_to_list(R#roster.subscription)))),
?XAC(<<"td">>,
[{<<"class">>,
<<"valign">>}],
(iolist_to_binary(atom_to_list(Pending)))),
?XAE(<<"td">>,
[{<<"class">>,
<<"valign">>}],
Groups),
if Pending == in ->
?XAE(<<"td">>,
[{<<"class">>,
<<"valign">>}],
[?INPUTT(<<"submit">>,
<<"validate",
(ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
?T("Validate"))]);
true -> ?X(<<"td">>)
end,
?XAE(<<"td">>,
[{<<"class">>,
<<"valign">>}],
[?INPUTTD(<<"submit">>,
<<"remove",
(ejabberd_web_admin:term_to_id(R#roster.jid))/binary>>,
?T("Remove"))])])
end,
SItems)))])]
end,
PageTitle = str:translate_and_format(Lang, ?T("Roster of ~ts"), [us_to_list(US)]),
(?H1GL(PageTitle, <<"modules/#mod_roster">>, <<"mod_roster">>))
++
case Res of
ok -> [?XREST(?T("Submitted"))];
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
( [?P, ?INPUT(<<"text">>, <<"newjid">>, <<"">>),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"addjid">>,
?T("Add Jabber ID"))]
++ FItems))].
webadmin_menu_hostuser(Acc, _Host, _Username, _Lang) ->
Acc ++ [{<<"roster">>, <<"Roster">>}].
build_contact_jid_td(RosterJID) ->
ContactJID = jid:make(RosterJID),
JIDURI = case {ContactJID#jid.luser,
ContactJID#jid.lserver}
of
{<<"">>, _} -> <<"">>;
{CUser, CServer} ->
case lists:member(CServer, ejabberd_option:hosts()) of
false -> <<"">>;
true ->
<<"../../../../../server/", CServer/binary, "/user/",
CUser/binary, "/">>
end
end,
case JIDURI of
<<>> ->
?XAC(<<"td">>, [{<<"class">>, <<"valign">>}],
(jid:encode(RosterJID)));
URI when is_binary(URI) ->
?XAE(<<"td">>, [{<<"class">>, <<"valign">>}],
[?AC(JIDURI, (jid:encode(RosterJID)))])
end.
webadmin_page_hostuser(_, Host, Username, #request{path = [<<"roster">> | RPath]} = R) ->
Head = ?H1GL(<<"Roster">>, <<"modules/#mod_roster">>, <<"mod_roster">>),
%% Execute twice: first to perform the action, the second to get new roster
_ = make_webadmin_roster_table(Host, Username, R, RPath),
RV2 = make_webadmin_roster_table(Host, Username, R, RPath),
Set = [make_command(add_rosteritem,
R,
[{<<"localuser">>, Username}, {<<"localhost">>, Host}],
[]),
make_command(push_roster, R, [{<<"user">>, Username}, {<<"host">>, Host}], [])],
Get = [make_command(get_roster, R, [], [{only, presentation}]),
make_command(delete_rosteritem, R, [], [{only, presentation}]),
RV2],
{stop, Head ++ Get ++ Set};
webadmin_page_hostuser(Acc, _, _, _) ->
Acc.
user_roster_parse_query(User, Server, Items, Query) ->
case lists:keysearch(<<"addjid">>, 1, Query) of
{value, _} ->
case lists:keysearch(<<"newjid">>, 1, Query) of
{value, {_, SJID}} ->
try jid:decode(SJID) of
JID ->
user_roster_subscribe_jid(User, Server, JID), ok
catch _:{bad_jid, _} ->
error
end;
false -> error
end;
false ->
case catch user_roster_item_parse_query(User, Server,
Items, Query)
of
submitted -> ok;
{'EXIT', _Reason} -> error;
_ -> nothing
end
end.
make_webadmin_roster_table(Host, Username, R, RPath) ->
Contacts =
case make_command_raw_value(get_roster, R, [{<<"user">>, Username}, {<<"host">>, Host}])
of
Cs when is_list(Cs) ->
Cs;
_ ->
[]
end,
Level = 5 + length(RPath),
Columns =
[<<"jid">>, <<"nick">>, <<"subscription">>, <<"pending">>, <<"groups">>, <<"">>],
Rows =
lists:map(fun({Jid, Nick, Subscriptions, Pending, Groups}) ->
{JidSplit, ProblematicBin} =
try jid:decode(Jid) of
#jid{} = J ->
{jid:split(J), <<"">>}
catch
_:{bad_jid, _} ->
?INFO_MSG("Error parsing contact of ~s@~s that is invalid JID: ~s",
[Username, Host, Jid]),
{{<<"000--error-parsing-jid">>, <<"localhost">>, <<"">>},
<<", Error parsing JID: ", Jid/binary>>}
end,
{make_command(echo,
R,
[{<<"sentence">>, jid:encode(JidSplit)}],
[{only, raw_and_value},
{result_links, [{sentence, user, Level, <<"">>}]}]),
?C(<<Nick/binary, ProblematicBin/binary>>),
?C(Subscriptions),
?C(Pending),
?C(Groups),
make_command(delete_rosteritem,
R,
[{<<"localuser">>, Username},
{<<"localhost">>, Host},
{<<"user">>, element(1, JidSplit)},
{<<"host">>, element(2, JidSplit)}],
[{only, button},
{style, danger},
{input_name_append,
[Username,
Host,
element(1, JidSplit),
element(2, JidSplit)]}])}
end,
lists:keysort(1, Contacts)),
Table = make_table(20, RPath, Columns, Rows),
?XE(<<"blockquote">>, [Table]).
user_roster_subscribe_jid(User, Server, JID) ->
UJID = jid:make(User, Server),
Presence = #presence{from = UJID, to = JID, type = subscribe},
out_subscription(Presence),
ejabberd_router:route(Presence).
user_roster_item_parse_query(User, Server, Items,
Query) ->
lists:foreach(fun (R) ->
JID = R#roster.jid,
case lists:keysearch(<<"validate",
(ejabberd_web_admin:term_to_id(JID))/binary>>,
1, Query)
of
{value, _} ->
JID1 = jid:make(JID),
UJID = jid:make(User, Server),
Pres = #presence{from = UJID, to = JID1,
type = subscribed},
out_subscription(Pres),
ejabberd_router:route(Pres),
throw(submitted);
false ->
case lists:keysearch(<<"remove",
(ejabberd_web_admin:term_to_id(JID))/binary>>,
1, Query)
of
{value, _} ->
UJID = jid:make(User, Server),
RosterItem = #roster_item{
jid = jid:make(JID),
subscription = remove},
process_iq_set(
#iq{type = set,
from = UJID,
to = UJID,
id = p1_rand:get_string(),
sub_els = [#roster_query{
items = [RosterItem]}]}),
throw(submitted);
false -> ok
end
end
end,
Items),
nothing.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
webadmin_user(Acc, User, Server, Lang) ->
QueueLen = length(get_roster(jid:nodeprep(User), jid:nameprep(Server))),
FQueueLen = ?C(integer_to_binary(QueueLen)),
FQueueView = ?AC(<<"roster/">>, ?T("View Roster")),
Acc ++
[?XCT(<<"h3">>, ?T("Roster:")),
FQueueLen,
?C(<<" | ">>),
FQueueView].
webadmin_user(Acc, User, Server, R) ->
Acc
++ [make_command(get_roster_count, R, [{<<"user">>, User}, {<<"host">>, Server}], [])].
%%% @format-end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec has_duplicated_groups([binary()]) -> boolean().

View File

@ -41,6 +41,8 @@
is_user_in_group/3, add_user_to_group/3, opts_to_binary/1,
remove_user_from_group/3, mod_opt_type/1, mod_options/1, mod_doc/0, depends/2]).
-import(ejabberd_web_admin, [make_command/4, make_command_raw_value/3, make_table/2, make_table/4]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
@ -861,286 +863,402 @@ unset_presence(User, Server, Resource, Status) ->
end.
%%---------------------
%% Web Admin
%% Web Admin: Page Frontend
%%---------------------
%% @format-begin
webadmin_menu(Acc, _Host, Lang) ->
[{<<"shared-roster">>, translate:translate(Lang, ?T("Shared Roster Groups"))}
| Acc].
[{<<"shared-roster">>, translate:translate(Lang, ?T("Shared Roster Groups"))} | 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.
webadmin_page(_,
Host,
#request{us = _US,
path = [<<"shared-roster">> | RPath],
lang = Lang} =
R) ->
PageTitle = translate:translate(Lang, ?T("Shared Roster Groups")),
Head = ?H1GL(PageTitle, <<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>),
Level = length(RPath),
Res = case check_group_exists(Host, RPath) of
true ->
webadmin_page_backend(Host, RPath, R, Lang, Level);
false ->
[?XREST(<<"Group does not exist.">>)]
end,
{stop, Head ++ Res};
webadmin_page(Acc, _, _) ->
Acc.
list_shared_roster_groups(Host, Query, Lang) ->
Res = list_sr_groups_parse_query(Host, Query),
SRGroups = list_groups(Host),
FGroups = (?XAE(<<"table">>, [],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?X(<<"td">>),
?XE(<<"td">>, [?CT(?T("Name:"))])
])]++
(lists:map(fun (Group) ->
?XE(<<"tr">>,
[?XE(<<"td">>,
[?INPUT(<<"checkbox">>,
<<"selected">>,
Group)]),
?XE(<<"td">>,
[?AC(<<Group/binary, "/">>,
Group)])])
end,
lists:sort(SRGroups))
++
[?XE(<<"tr">>,
[?X(<<"td">>),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"namenew">>,
<<"">>),
?C(<<" ">>),
?INPUTT(<<"submit">>, <<"addnew">>,
?T("Add New"))])])]))])),
(?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))),
<<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>))
++
case Res of
ok -> [?XREST(?T("Submitted"))];
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[FGroups, ?BR,
?INPUTTD(<<"submit">>, <<"delete">>,
?T("Delete Selected"))])].
check_group_exists(Host, [<<"group">>, Id | _]) ->
case get_group_opts(Host, Id) of
error ->
false;
_ ->
true
end;
check_group_exists(_, _) ->
true.
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.
%%---------------------
%% Web Admin: Page Backend
%%---------------------
list_sr_groups_parse_addnew(Host, Query) ->
case lists:keysearch(<<"namenew">>, 1, Query) of
{value, {_, Group}} when Group /= <<"">> ->
create_group(Host, Group),
ok;
_ ->
error
end.
webadmin_page_backend(Host, [<<"group">>, Id, <<"info">> | RPath], R, _Lang, Level) ->
Breadcrumb =
make_breadcrumb({group_section,
Level,
<<"Groups of ", Host/binary>>,
Id,
<<"Information">>,
RPath}),
SetLabel =
make_command(srg_set_info,
R,
[{<<"host">>, Host}, {<<"group">>, Id}, {<<"key">>, <<"label">>}],
[{only, without_presentation}, {input_name_append, [Id, Host, <<"label">>]}]),
SetDescription =
make_command(srg_set_info,
R,
[{<<"host">>, Host}, {<<"group">>, Id}, {<<"key">>, <<"description">>}],
[{only, without_presentation},
{input_name_append, [Id, Host, <<"description">>]}]),
SetAll =
make_command(srg_set_info,
R,
[{<<"host">>, Host},
{<<"group">>, Id},
{<<"key">>, <<"all_users">>},
{<<"value">>, <<"true">>}],
[{only, button},
{input_name_append, [Id, Host, <<"all_users">>, <<"true">>]}]),
UnsetAll =
make_command(srg_set_info,
R,
[{<<"host">>, Host},
{<<"group">>, Id},
{<<"key">>, <<"all_users">>},
{<<"value">>, <<"false">>}],
[{only, button},
{input_name_append, [Id, Host, <<"all_users">>, <<"false">>]}]),
SetOnline =
make_command(srg_set_info,
R,
[{<<"host">>, Host},
{<<"group">>, Id},
{<<"key">>, <<"online_users">>},
{<<"value">>, <<"true">>}],
[{only, button},
{input_name_append, [Id, Host, <<"online_users">>, <<"true">>]}]),
UnsetOnline =
make_command(srg_set_info,
R,
[{<<"host">>, Host},
{<<"group">>, Id},
{<<"key">>, <<"online_users">>},
{<<"value">>, <<"false">>}],
[{only, button},
{input_name_append, [Id, Host, <<"online_users">>, <<"false">>]}]),
GetInfo =
make_command_raw_value(srg_get_info, R, [{<<"group">>, Id}, {<<"host">>, Host}]),
AllElement =
case proplists:get_value(<<"all_users">>, GetInfo, not_found) of
"true" ->
{?C("Unset @all@: "), UnsetAll};
_ ->
{?C("Set @all@: "), SetAll}
end,
OnlineElement =
case proplists:get_value(<<"online_users">>, GetInfo, not_found) of
"true" ->
{?C("Unset @online@: "), UnsetOnline};
_ ->
{?C("Set @online@: "), SetOnline}
end,
Types =
[{?C("Set label: "), SetLabel},
{?C("Set description: "), SetDescription},
AllElement,
OnlineElement],
Get = [?BR,
make_command(srg_get_info, R, [{<<"host">>, Host}, {<<"group">>, Id}], []),
make_command(srg_set_info, R, [], [{only, presentation}]),
make_table(20, [], [{<<"">>, right}, <<"">>], Types)],
Breadcrumb ++ Get;
webadmin_page_backend(Host,
[<<"group">>, Id, <<"displayed">> | RPath],
R,
_Lang,
Level) ->
Breadcrumb =
make_breadcrumb({group_section,
Level,
<<"Groups of ", Host/binary>>,
Id,
<<"Displayed Groups">>,
RPath}),
AddDisplayed =
make_command(srg_add_displayed, R, [{<<"host">>, Host}, {<<"group">>, Id}], []),
_ = make_webadmin_displayed_table(Host, Id, R),
DisplayedTable = make_webadmin_displayed_table(Host, Id, R),
Get = [?BR,
make_command(srg_get_displayed, R, [], [{only, presentation}]),
make_command(srg_del_displayed, R, [], [{only, presentation}]),
?XE(<<"blockquote">>, [DisplayedTable]),
AddDisplayed],
Breadcrumb ++ Get;
webadmin_page_backend(Host, [<<"group">>, Id, <<"members">> | RPath], R, _Lang, Level) ->
Breadcrumb =
make_breadcrumb({group_section,
Level,
<<"Groups of ", Host/binary>>,
Id,
<<"Members">>,
RPath}),
UserAdd = make_command(srg_user_add, R, [{<<"grouphost">>, Host}, {<<"group">>, Id}], []),
_ = make_webadmin_members_table(Host, Id, R),
MembersTable = make_webadmin_members_table(Host, Id, R),
Get = [make_command(srg_get_members, R, [], [{only, presentation}]),
make_command(srg_user_del, R, [], [{only, presentation}]),
?XE(<<"blockquote">>, [MembersTable]),
UserAdd],
Breadcrumb ++ Get;
webadmin_page_backend(Host, [<<"group">>, Id, <<"delete">> | RPath], R, _Lang, Level) ->
Breadcrumb =
make_breadcrumb({group_section,
Level,
<<"Groups of ", Host/binary>>,
Id,
<<"Delete">>,
RPath}),
Get = [make_command(srg_delete,
R,
[{<<"host">>, Host}, {<<"group">>, Id}],
[{style, danger}])],
Breadcrumb ++ Get;
webadmin_page_backend(Host, [<<"group">>, Id | _RPath], _R, _Lang, Level) ->
Breadcrumb = make_breadcrumb({group, Level, <<"Groups of ", Host/binary>>, Id}),
MenuItems =
[{<<"info/">>, <<"Information">>},
{<<"members/">>, <<"Members">>},
{<<"displayed/">>, <<"Displayed Groups">>},
{<<"delete/">>, <<"Delete">>}],
Get = [?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- MenuItems])],
Breadcrumb ++ Get;
webadmin_page_backend(Host, RPath, R, _Lang, Level) ->
Breadcrumb = make_breadcrumb({groups, <<"Groups of ", Host/binary>>}),
_ = make_webadmin_srg_table(Host, R, 3 + Level, RPath),
Set = [make_command(srg_add, R, [{<<"host">>, Host}], []),
make_command(srg_create, R, [{<<"host">>, Host}], [])],
RV2 = make_webadmin_srg_table(Host, R, 3 + Level, RPath),
Get = [make_command(srg_list, R, [{<<"host">>, Host}], [{only, presentation}]),
make_command(srg_get_info, R, [{<<"host">>, Host}], [{only, presentation}]),
make_command(srg_delete, R, [{<<"host">>, Host}], [{only, presentation}]),
?XE(<<"blockquote">>, [RV2])],
Breadcrumb ++ Get ++ Set.
list_sr_groups_parse_delete(Host, Query) ->
SRGroups = list_groups(Host),
lists:foreach(fun (Group) ->
case lists:member({<<"selected">>, Group}, Query) of
true -> delete_group(Host, Group);
_ -> ok
end
end,
SRGroups),
ok.
%%---------------------
%% Web Admin: Table Generation
%%---------------------
shared_roster_group(Host, Group, Query, Lang) ->
Res = shared_roster_group_parse_query(Host, Group,
Query),
GroupOpts = get_group_opts(Host, Group),
Label = get_opt(GroupOpts, label, <<"">>), %%++
Description = get_opt(GroupOpts, description, <<"">>),
AllUsers = get_opt(GroupOpts, all_users, false),
OnlineUsers = get_opt(GroupOpts, online_users, false),
DisplayedGroups = get_opt(GroupOpts, displayed_groups,
[]),
Members = get_group_explicit_users(Host,
Group),
FMembers = iolist_to_binary(
[if AllUsers -> <<"@all@\n">>;
true -> <<"">>
end,
if OnlineUsers -> <<"@online@\n">>;
true -> <<"">>
end,
[[us_to_list(Member), $\n] || Member <- Members]]),
FDisplayedGroups = [<<DG/binary, $\n>> || DG <- DisplayedGroups],
DescNL = length(ejabberd_regexp:split(Description,
<<"\n">>)),
FGroup = (?XAE(<<"table">>,
[{<<"class">>, <<"withtextareas">>}],
[?XE(<<"tbody">>,
[?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Name:")),
?XE(<<"td">>, [?C(Group)]),
?XE(<<"td">>, [?C(<<"">>)])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Label:")),
?XE(<<"td">>,
[?INPUT(<<"text">>, <<"label">>, Label)]),
?XE(<<"td">>, [?CT(?T("Name in the rosters where this group will be displayed"))])]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Description:")),
?XE(<<"td">>,
[?TEXTAREA(<<"description">>,
integer_to_binary(lists:max([3,
DescNL])),
<<"20">>, Description)]),
?XE(<<"td">>, [?CT(?T("Only admins can see this"))])
]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Members:")),
?XE(<<"td">>,
[?TEXTAREA(<<"members">>,
integer_to_binary(lists:max([3,
length(Members)+3])),
<<"20">>, FMembers)]),
?XE(<<"td">>, [?C(<<"JIDs, @all@, @online@">>)])
]),
?XE(<<"tr">>,
[?XCT(<<"td">>, ?T("Displayed:")),
?XE(<<"td">>,
[?TEXTAREA(<<"dispgroups">>,
integer_to_binary(lists:max([3, length(FDisplayedGroups)])),
<<"20">>,
list_to_binary(FDisplayedGroups))]),
?XE(<<"td">>, [?CT(?T("Groups that will be displayed to the members"))])
])])])),
(?H1GL((translate:translate(Lang, ?T("Shared Roster Groups"))),
<<"modules/#mod_shared_roster">>, <<"mod_shared_roster">>))
++
[?XC(<<"h2">>, translate:translate(Lang, ?T("Group")))] ++
case Res of
ok -> [?XREST(?T("Submitted"))];
{error_elements, NonAddedList1, NG1} ->
make_error_el(Lang,
?T("Members not added (inexistent vhost!): "),
[jid:encode({U,S,<<>>}) || {U,S} <- NonAddedList1])
++ make_error_el(Lang, ?T("'Displayed groups' not added (they do not exist!): "), NG1);
error -> [?XREST(?T("Bad format"))];
nothing -> []
end
++
[?XAE(<<"form">>,
[{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
[FGroup, ?BR,
?INPUTT(<<"submit">>, <<"submit">>, ?T("Submit"))])].
make_webadmin_srg_table(Host, R, Level, RPath) ->
Groups =
case make_command_raw_value(srg_list, R, [{<<"host">>, Host}]) of
Gs when is_list(Gs) ->
Gs;
_ ->
[]
end,
Columns =
[<<"id">>,
<<"label">>,
<<"description">>,
<<"all">>,
<<"online">>,
{<<"members">>, right},
{<<"displayed">>, right},
<<"">>],
Rows =
[{make_command(echo3,
R,
[{<<"first">>, Id}, {<<"second">>, Host}, {<<"sentence">>, Id}],
[{only, value}, {result_links, [{sentence, shared_roster, Level, <<"">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
iolist_to_binary(proplists:get_value(<<"label">>,
make_command_raw_value(srg_get_info,
R,
[{<<"group">>,
Id},
{<<"host">>,
Host}]),
""))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"info">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
iolist_to_binary(proplists:get_value(<<"description">>,
make_command_raw_value(srg_get_info,
R,
[{<<"group">>,
Id},
{<<"host">>,
Host}]),
""))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"info">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
iolist_to_binary(proplists:get_value(<<"all_users">>,
make_command_raw_value(srg_get_info,
R,
[{<<"group">>,
Id},
{<<"host">>,
Host}]),
""))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"info">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
iolist_to_binary(proplists:get_value(<<"online_users">>,
make_command_raw_value(srg_get_info,
R,
[{<<"group">>,
Id},
{<<"host">>,
Host}]),
""))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"info">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
integer_to_binary(length(make_command_raw_value(srg_get_members,
R,
[{<<"group">>, Id},
{<<"host">>, Host}])))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"members">>}]}]),
make_command(echo3,
R,
[{<<"first">>, Id},
{<<"second">>, Host},
{<<"sentence">>,
integer_to_binary(length(make_command_raw_value(srg_get_displayed,
R,
[{<<"group">>, Id},
{<<"host">>, Host}])))}],
[{only, value},
{result_links, [{sentence, shared_roster, Level, <<"displayed">>}]}]),
make_command(srg_delete,
R,
[{<<"group">>, Id}, {<<"host">>, Host}],
[{only, button}, {style, danger}, {input_name_append, [Id, Host]}])}
|| Id <- Groups],
make_table(20, RPath, Columns, Rows).
make_error_el(_, _, []) ->
[];
make_error_el(Lang, Message, BinList) ->
NG2 = str:join(BinList, <<", ">>),
NG3 = translate:translate(Lang, Message),
NG4 = str:concat(NG3, NG2),
[?XRES(NG4)].
make_webadmin_members_table(Host, Id, R) ->
Members =
case make_command_raw_value(srg_get_members, R, [{<<"host">>, Host}, {<<"group">>, Id}])
of
Ms when is_list(Ms) ->
Ms;
_ ->
[]
end,
make_table([<<"member">>, <<"">>],
[{make_command(echo,
R,
[{<<"sentence">>, Jid}],
[{only, value}, {result_links, [{sentence, user, 6, <<"">>}]}]),
make_command(srg_user_del,
R,
[{<<"user">>,
element(1,
jid:split(
jid:decode(Jid)))},
{<<"host">>,
element(2,
jid:split(
jid:decode(Jid)))},
{<<"group">>, Id},
{<<"grouphost">>, Host}],
[{only, button},
{style, danger},
{input_name_append,
[element(1,
jid:split(
jid:decode(Jid))),
element(2,
jid:split(
jid:decode(Jid))),
Id,
Host]}])}
|| Jid <- Members]).
shared_roster_group_parse_query(Host, Group, Query) ->
case lists:keysearch(<<"submit">>, 1, Query) of
{value, _} ->
{value, {_, Label}} = lists:keysearch(<<"label">>, 1,
Query), %++
{value, {_, Description}} =
lists:keysearch(<<"description">>, 1, Query),
{value, {_, SMembers}} = lists:keysearch(<<"members">>,
1, Query),
{value, {_, SDispGroups}} =
lists:keysearch(<<"dispgroups">>, 1, Query),
LabelOpt = if Label == <<"">> -> [];
true -> [{label, Label}] %++
end,
DescriptionOpt = if Description == <<"">> -> [];
true -> [{description, Description}]
end,
DispGroups1 = str:tokens(SDispGroups, <<"\r\n">>),
{DispGroups, WrongDispGroups} = filter_groups_existence(Host, DispGroups1),
DispGroupsOpt = if DispGroups == [] -> [];
true -> [{displayed_groups, DispGroups}]
end,
OldMembers = get_group_explicit_users(Host,
Group),
SJIDs = str:tokens(SMembers, <<", \r\n">>),
NewMembers = lists:foldl(fun (_SJID, error) -> error;
(SJID, USs) ->
case SJID of
<<"@all@">> -> USs;
<<"@online@">> -> USs;
_ ->
try jid:decode(SJID) of
JID ->
[{JID#jid.luser,
JID#jid.lserver}
| USs]
catch _:{bad_jid, _} ->
error
end
end
end,
[], SJIDs),
AllUsersOpt = case lists:member(<<"@all@">>, SJIDs) of
true -> [{all_users, true}];
false -> []
end,
OnlineUsersOpt = case lists:member(<<"@online@">>,
SJIDs)
of
true -> [{online_users, true}];
false -> []
end,
CurrentDisplayedGroups = get_displayed_groups(Group, Host),
AddedDisplayedGroups = DispGroups -- CurrentDisplayedGroups,
RemovedDisplayedGroups = CurrentDisplayedGroups -- DispGroups,
displayed_groups_update(OldMembers, RemovedDisplayedGroups, remove),
displayed_groups_update(OldMembers, AddedDisplayedGroups, both),
set_group_opts(Host, Group,
LabelOpt ++
DispGroupsOpt ++
DescriptionOpt ++
AllUsersOpt ++ OnlineUsersOpt),
if NewMembers == error -> error;
true ->
AddedMembers = NewMembers -- OldMembers,
RemovedMembers = OldMembers -- NewMembers,
lists:foreach(
fun(US) ->
remove_user_from_group(Host,
US,
Group)
end,
RemovedMembers),
NonAddedMembers = lists:filter(
fun(US) ->
error == add_user_to_group(Host, US,
Group)
end,
AddedMembers),
case (NonAddedMembers /= []) or (WrongDispGroups /= []) of
true -> {error_elements, NonAddedMembers, WrongDispGroups};
false -> ok
end
end;
_ -> nothing
end.
make_webadmin_displayed_table(Host, Id, R) ->
Displayed =
case make_command_raw_value(srg_get_displayed, R, [{<<"host">>, Host}, {<<"group">>, Id}])
of
Ms when is_list(Ms) ->
Ms;
_ ->
[]
end,
make_table([<<"group">>, <<"">>],
[{make_command(echo3,
R,
[{<<"first">>, ThisId},
{<<"second">>, Host},
{<<"sentence">>, ThisId}],
[{only, value},
{result_links, [{sentence, shared_roster, 6, <<"">>}]}]),
make_command(srg_del_displayed,
R,
[{<<"group">>, Id}, {<<"host">>, Host}, {<<"del">>, ThisId}],
[{only, button},
{style, danger},
{input_name_append, [Id, Host, ThisId]}])}
|| ThisId <- Displayed]).
get_opt(Opts, Opt, Default) ->
case lists:keysearch(Opt, 1, Opts) of
{value, {_, Val}} -> Val;
false -> Default
end.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
make_breadcrumb({groups, Service}) ->
make_breadcrumb([Service]);
make_breadcrumb({group, Level, Service, Name}) ->
make_breadcrumb([{Level, Service}, separator, Name]);
make_breadcrumb({group_section, Level, Service, Name, Section, RPath}) ->
make_breadcrumb([{Level, Service}, separator, {Level - 2, Name}, separator, Section
| RPath]);
make_breadcrumb(Elements) ->
lists:map(fun ({xmlel, _, _, _} = Xmlel) ->
Xmlel;
(<<"sort">>) ->
?C(<<" +">>);
(<<"page">>) ->
?C(<<" #">>);
(separator) ->
?C(<<" > ">>);
(Bin) when is_binary(Bin) ->
?C(Bin);
({Level, Bin}) when is_integer(Level) and is_binary(Bin) ->
?AC(binary:copy(<<"../">>, Level), Bin)
end,
Elements).
%% @format-end
split_grouphost(Host, Group) ->
case str:tokens(Group, <<"@">>) of
@ -1148,17 +1266,6 @@ split_grouphost(Host, Group) ->
[_] -> {Host, Group}
end.
filter_groups_existence(Host, Groups) ->
lists:partition(
fun(Group) -> error /= get_group_opts(Host, Group) end,
Groups).
displayed_groups_update(Members, DisplayedGroups, Subscription) ->
lists:foreach(
fun({U, S}) ->
push_displayed_to_user(U, S, S, Subscription, DisplayedGroups)
end, Members).
opts_to_binary(Opts) ->
lists:map(
fun({label, Label}) ->