• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.18
/src/ejabberd_admin.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : ejabberd_admin.erl
3
%%% Author  : Mickael Remond <mremond@process-one.net>
4
%%% Purpose : Administrative functions and commands
5
%%% Created :  7 May 2006 by Mickael Remond <mremond@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%-------------------------------------------------------------------
25

26
-module(ejabberd_admin).
27
-author('mickael.remond@process-one.net').
28

29
-behaviour(gen_server).
30

31
-export([start_link/0,
32
         %% Server
33
         status/0, stop/0, restart/0,
34
         reopen_log/0, rotate_log/0,
35
         set_loglevel/1,
36
         evacuate_kindly/2,
37
         restart_kindly/2,
38
         stop_kindly/2, send_service_message_all_mucs/2,
39
         registered_vhosts/0,
40
         reload_config/0,
41
         dump_config/1,
42
         convert_to_yaml/2,
43
         %% Cluster
44
         join_cluster/1, leave_cluster/1,
45
         join_cluster_here/1,
46
         list_cluster/0, list_cluster_detailed/0,
47
         get_cluster_node_details3/0,
48
         %% Erlang
49
         update_list/0, update/1, update/0,
50
         %% Accounts
51
         register/3, unregister/2,
52
         registered_users/1,
53
         %% Migration jabberd1.4
54
         import_file/1, import_dir/1,
55
         %% Purge DB
56
         delete_expired_messages/0, delete_old_messages/1,
57
         %% Mnesia
58
         get_master/0, set_master/1,
59
         backup_mnesia/1, restore_mnesia/1,
60
         dump_mnesia/1, dump_table/2, load_mnesia/1,
61
         mnesia_info/0, mnesia_table_info/1,
62
         install_fallback_mnesia/1,
63
         dump_to_textfile/1, dump_to_textfile/2,
64
         mnesia_change_nodename/4,
65
         restore/1, % Still used by some modules
66
         clear_cache/0,
67
         gc/0,
68
         get_commands_spec/0,
69
         delete_old_messages_batch/4, delete_old_messages_status/1, delete_old_messages_abort/1,
70
         %% Internal
71
         mnesia_list_tables/0,
72
         mnesia_table_details/1,
73
         mnesia_table_change_storage/2,
74
         mnesia_table_clear/1,
75
         mnesia_table_delete/1,
76
         echo/1, echo3/3]).
77
%% gen_server callbacks
78
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
79
         terminate/2, code_change/3]).
80

81
-export([web_menu_main/2, web_page_main/2,
82
         web_menu_node/3, web_page_node/3]).
83

84
-include_lib("xmpp/include/xmpp.hrl").
85
-include("ejabberd_commands.hrl").
86
-include("ejabberd_http.hrl").
87
-include("ejabberd_web_admin.hrl").
88
-include("logger.hrl").
89
-include("translate.hrl"). %+++ TODO
90

91
-record(state, {}).
92

93
start_link() ->
94
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
11✔
95

96
init([]) ->
97
    process_flag(trap_exit, true),
11✔
98
    ejabberd_commands:register_commands(get_commands_spec()),
11✔
99
    ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
11✔
100
    ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
11✔
101
    ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50),
11✔
102
    ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50),
11✔
103
    {ok, #state{}}.
11✔
104

105
handle_call(Request, From, State) ->
106
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
107
    {noreply, State}.
×
108

109
handle_cast(Msg, State) ->
110
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
111
    {noreply, State}.
×
112

113
handle_info(Info, State) ->
114
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
115
    {noreply, State}.
×
116

117
terminate(_Reason, _State) ->
118
    ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
11✔
119
    ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
11✔
120
    ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50),
11✔
121
    ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50),
11✔
122
    ejabberd_commands:unregister_commands(get_commands_spec()).
11✔
123

124
code_change(_OldVsn, State, _Extra) ->
125
    {ok, State}.
×
126

127
%%%
128
%%% ejabberd commands
129
%%%
130

131
get_commands_spec() ->
132
    [
133
     %% The commands status, stop and restart are implemented also in ejabberd_ctl
134
     %% They are defined here so that other interfaces can use them too
135
     #ejabberd_commands{name = status, tags = [server],
22✔
136
                        desc = "Get status of the ejabberd server",
137
                        module = ?MODULE, function = status,
138
                        result_desc = "Result tuple",
139
                        result_example = {ok, <<"The node ejabberd@localhost is started with status: started"
140
                                                "ejabberd X.X is running in that node">>},
141
                        args = [], result = {res, restuple}},
142
     #ejabberd_commands{name = stop, tags = [server],
143
                        desc = "Stop ejabberd gracefully",
144
                        module = ?MODULE, function = stop,
145
                        args = [], result = {res, rescode}},
146
     #ejabberd_commands{name = halt, tags = [server],
147
                        desc = "Halt ejabberd abruptly with status code 1",
148
                        note = "added in 23.10",
149
                        module = ejabberd, function = halt,
150
                        args = [], result = {res, rescode}},
151
     #ejabberd_commands{name = restart, tags = [server],
152
                        desc = "Restart ejabberd gracefully",
153
                        module = ?MODULE, function = restart,
154
                        args = [], result = {res, rescode}},
155
     #ejabberd_commands{name = reopen_log, tags = [logs],
156
                        desc = "Reopen maybe the log files after being renamed",
157
                        longdesc = "Has no effect on ejabberd main log files, "
158
                        "only on log files generated by some modules.\n"
159
                        "This can be useful when an external tool is "
160
                        "used for log rotation. See "
161
                        "_`../../admin/guide/troubleshooting.md#log-files|Log Files`_.",
162
                        policy = admin,
163
                        module = ?MODULE, function = reopen_log,
164
                        args = [], result = {res, rescode}},
165
     #ejabberd_commands{name = rotate_log, tags = [logs],
166
                        desc = "Rotate maybe log file of some module",
167
                        longdesc = "Has no effect on ejabberd main log files, "
168
                        "only on log files generated by some modules.",
169
                        module = ?MODULE, function = rotate_log,
170
                        args = [], result = {res, rescode}},
171
     #ejabberd_commands{name = evacuate_kindly, tags = [server],
172
                        desc = "Evacuate kindly all users (kick and prevent login)",
173
                        longdesc = "Inform users and rooms, don't allow login, wait, "
174
                        "restart the server, and don't allow new logins.\n"
175
                        "Provide the delay in seconds, and the "
176
                        "announcement quoted, for example: \n"
177
                        "`ejabberdctl evacuate_kindly 60 "
178
                        "\\\"The server will stop in one minute.\\\"`",
179
                        note = "added in 24.12",
180
                        module = ?MODULE, function = evacuate_kindly,
181
                        args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
182
                        args_example = [60, <<"Server will stop now.">>],
183
                        args = [{delay, integer}, {announcement, string}],
184
                        result = {res, rescode}},
185
     #ejabberd_commands{name = restart_kindly, tags = [server, async],
186
                        desc = "Restart kindly the server",
187
                        longdesc = "Inform users and rooms, wait, and restart the server.\n"
188
                        "Provide the delay in seconds, and the "
189
                        "announcement quoted, for example: \n"
190
                        "`ejabberdctl restart_kindly 60 "
191
                        "\\\"The server will stop in one minute.\\\"`",
192
                        note = "added in 25.10",
193
                        module = ?MODULE, function = restart_kindly,
194
                        args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
195
                        args_example = [60, <<"Server will restart now.">>],
196
                        args = [{delay, integer}, {announcement, string}],
197
                        result = {res, rescode}},
198
     #ejabberd_commands{name = stop_kindly, tags = [server, async],
199
                        desc = "Stop kindly the server (informing users)",
200
                        longdesc = "Inform users and rooms, wait, and stop the server.\n"
201
                        "Provide the delay in seconds, and the "
202
                        "announcement quoted, for example: \n"
203
                        "`ejabberdctl stop_kindly 60 "
204
                        "\\\"The server will stop in one minute.\\\"`",
205
                        module = ?MODULE, function = stop_kindly,
206
                        args_desc = ["Seconds to wait", "Announcement to send, with quotes"],
207
                        args_example = [60, <<"Server will stop now.">>],
208
                        args = [{delay, integer}, {announcement, string}],
209
                        result = {res, rescode}},
210
     #ejabberd_commands{name = get_loglevel, tags = [logs],
211
                        desc = "Get the current loglevel",
212
                        module = ejabberd_logger, function = get,
213
                        result_desc = "Tuple with the log level number, its keyword and description",
214
                        result_example = warning,
215
                        args = [],
216
                        result = {levelatom, atom}},
217
     #ejabberd_commands{name = set_loglevel, tags = [logs],
218
                        desc = "Set the loglevel",
219
                        longdesc = "Possible loglevels: `none`, `emergency`, `alert`, `critical`,
220
                                   `error`, `warning`, `notice`, `info`, `debug`.",
221
                        module = ?MODULE, function = set_loglevel,
222
                        args_desc = ["Desired logging level"],
223
                        args_example = ["debug"],
224
                        args = [{loglevel, string}],
225
                        result = {res, rescode}},
226

227
     #ejabberd_commands{name = update_list, tags = [server],
228
                        desc = "List modified modules that can be updated",
229
                        module = ?MODULE, function = update_list,
230
                        args = [],
231
                        result_example = ["mod_configure", "mod_vcard"],
232
                        result = {modules, {list, {module, string}}}},
233
     #ejabberd_commands{name = update, tags = [server],
234
                        desc = "Update the given module",
235
                        longdesc = "To update all the possible modules, use `all`.",
236
                        note = "improved in 24.10",
237
                        module = ?MODULE, function = update,
238
                        args_example = ["all"],
239
                        args = [{module, string}],
240
                        result_example = {ok, <<"Updated modules: mod_configure, mod_vcard">>},
241
                        result = {res, restuple}},
242

243
     #ejabberd_commands{name = register, tags = [accounts],
244
                        desc = "Register a user",
245
                        policy = admin,
246
                        module = ?MODULE, function = register,
247
                        args_desc = ["Username", "Local vhost served by ejabberd", "Password"],
248
                        args_example = [<<"bob">>, <<"example.com">>, <<"SomEPass44">>],
249
                        args = [{user, binary}, {host, binary}, {password, binary}],
250
                        result = {res, restuple}},
251
     #ejabberd_commands{name = unregister, tags = [accounts],
252
                        desc = "Unregister a user",
253
                        longdesc = "This deletes the authentication and all the "
254
                        "data associated to the account (roster, vcard...).",
255
                        policy = admin,
256
                        module = ?MODULE, function = unregister,
257
                        args_desc = ["Username", "Local vhost served by ejabberd"],
258
                        args_example = [<<"bob">>, <<"example.com">>],
259
                        args = [{user, binary}, {host, binary}],
260
                        result = {res, restuple}},
261
     #ejabberd_commands{name = registered_users, tags = [accounts],
262
                        desc = "List all registered users in HOST",
263
                        module = ?MODULE, function = registered_users,
264
                        args_desc = ["Local vhost"],
265
                        args_example = [<<"example.com">>],
266
                        result_desc = "List of registered accounts usernames",
267
                        result_example = [<<"user1">>, <<"user2">>],
268
                        args = [{host, binary}],
269
                        result = {users, {list, {username, string}}}},
270
     #ejabberd_commands{name = registered_vhosts, tags = [server],
271
                        desc = "List all registered vhosts in SERVER",
272
                        module = ?MODULE, function = registered_vhosts,
273
                        result_desc = "List of available vhosts",
274
                        result_example = [<<"example.com">>, <<"anon.example.com">>],
275
                        args = [],
276
                        result = {vhosts, {list, {vhost, string}}}},
277
     #ejabberd_commands{name = reload_config, tags = [config],
278
                        desc = "Reload config file in memory",
279
                        module = ?MODULE, function = reload_config,
280
                        args = [],
281
                        result = {res, rescode}},
282

283
     #ejabberd_commands{name = join_cluster, tags = [cluster],
284
                        desc = "Join our local node into the cluster handled by Node",
285
                        longdesc = "This command returns immediately,
286
                        even before the joining process has
287
                        completed. Consequently, if you are using
288
                        `ejabberdctl` (or some `CTL_ON_` container
289
                        environment variables) to run more commands
290
                        afterwards, you may want to precede them with
291
                        the `started`
292
                        _`../../admin/guide/managing.md#ejabberdctl-commands|ejabberdctl command`_
293
                        to ensure the
294
                        clustering process has completed before
295
                        proceeding. For example: `join_cluster
296
                        ejabberd@main` > `started` > `list_cluster`.",
297
                        note = "improved in 24.06",
298
                        module = ?MODULE, function = join_cluster,
299
                        args_desc = ["Nodename of the node to join"],
300
                        args_example = [<<"ejabberd1@machine7">>],
301
                        args = [{node, binary}],
302
                        result = {res, restuple}},
303
     #ejabberd_commands{name = join_cluster_here, tags = [cluster],
304
                        desc = "Join a remote Node here, into our cluster",
305
                        note = "added in 24.06",
306
                        module = ?MODULE, function = join_cluster_here,
307
                        args_desc = ["Nodename of the node to join here"],
308
                        args_example = [<<"ejabberd1@machine7">>],
309
                        args = [{node, binary}],
310
                        result = {res, restuple}},
311
     #ejabberd_commands{name = leave_cluster, tags = [cluster],
312
                        desc = "Remove and shutdown Node from the running cluster",
313
                        longdesc = "This command can be run from any running "
314
                        "node of the cluster, even the node to be removed. "
315
                        "In the removed node, this command works only when "
316
                        "using ejabberdctl, not _`mod_http_api`_ or other code that "
317
                        "runs inside the same ejabberd node that will leave.",
318
                        module = ?MODULE, function = leave_cluster,
319
                        args_desc = ["Nodename of the node to kick from the cluster"],
320
                        args_example = [<<"ejabberd1@machine8">>],
321
                        args = [{node, binary}],
322
                        result = {res, rescode}},
323

324
     #ejabberd_commands{name = list_cluster, tags = [cluster],
325
                        desc = "List running nodes that are part of this cluster",
326
                        module = ?MODULE, function = list_cluster,
327
                        result_example = [ejabberd1@machine7, ejabberd1@machine8],
328
                        args = [],
329
                        result = {nodes, {list, {node, atom}}}},
330
     #ejabberd_commands{name = list_cluster_detailed, tags = [cluster],
331
                        desc = "List nodes (both running and known) and some stats",
332
                        note = "added in 24.06",
333
                        module = ?MODULE, function = list_cluster_detailed,
334
                        args = [],
335
                        result_example = [{'ejabberd@localhost', "true",
336
                                       "The node ejabberd is started. Status...",
337
                                       7, 348, 60, none}],
338
                        result = {nodes, {list, {node, {tuple, [{name, atom},
339
                                                                {running, string},
340
                                                                {status, string},
341
                                                                {online_users, integer},
342
                                                                {processes, integer},
343
                                                                {uptime_seconds, integer},
344
                                                                {master_node, atom}
345
                                                               ]}}}}},
346

347
     #ejabberd_commands{name = import_file, tags = [mnesia],
348
                        desc = "Import user data from jabberd14 spool file",
349
                        module = ?MODULE, function = import_file,
350
                        args_desc = ["Full path to the jabberd14 spool file"],
351
                        args_example = ["/var/lib/ejabberd/jabberd14.spool"],
352
                        args = [{file, string}], result = {res, restuple}},
353
     #ejabberd_commands{name = import_dir, tags = [mnesia],
354
                        desc = "Import users data from jabberd14 spool dir",
355
                        module = ?MODULE, function = import_dir,
356
                        args_desc = ["Full path to the jabberd14 spool directory"],
357
                        args_example = ["/var/lib/ejabberd/jabberd14/"],
358
                        args = [{file, string}],
359
                        result = {res, restuple}},
360
     #ejabberd_commands{name = import_piefxis, tags = [mnesia],
361
                        desc = "Import users data from a PIEFXIS file (XEP-0227)",
362
                        module = ejabberd_piefxis, function = import_file,
363
                        args_desc = ["Full path to the PIEFXIS file"],
364
                        args_example = ["/var/lib/ejabberd/example.com.xml"],
365
                        args = [{file, binary}], result = {res, rescode}},
366
     #ejabberd_commands{name = export_piefxis, tags = [mnesia],
367
                        desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
368
                        module = ejabberd_piefxis, function = export_server,
369
                        args_desc = ["Full path to a directory"],
370
                        args_example = ["/var/lib/ejabberd/"],
371
                        args = [{dir, binary}], result = {res, rescode}},
372
     #ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
373
                        desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
374
                        module = ejabberd_piefxis, function = export_host,
375
                        args_desc = ["Full path to a directory", "Vhost to export"],
376
                        args_example = ["/var/lib/ejabberd/", "example.com"],
377
                        args = [{dir, binary}, {host, binary}], result = {res, rescode}},
378

379
     #ejabberd_commands{name = delete_mnesia, tags = [mnesia],
380
                        desc = "Delete elements in Mnesia database for a given vhost",
381
                        module = ejd2sql, function = delete,
382
                        args_desc = ["Vhost which content will be deleted in Mnesia database"],
383
                        args_example = ["example.com"],
384
                        args = [{host, string}], result = {res, rescode}},
385
     #ejabberd_commands{name = convert_to_scram, tags = [sql],
386
                        desc = "Convert the passwords of users to SCRAM",
387
                        module = ejabberd_auth, function = convert_to_scram,
388
                        args_desc = ["Vhost which users' passwords will be scrammed"],
389
                        args_example = ["example.com"],
390
                        args = [{host, binary}], result = {res, rescode}},
391
     #ejabberd_commands{name = import_prosody, tags = [mnesia, sql],
392
                        desc = "Import data from Prosody",
393
                        longdesc = "Note: this requires ejabberd to be "
394
                        "compiled with `./configure --enable-lua` "
395
                        "(which installs the `luerl` library).",
396
                        module = prosody2ejabberd, function = from_dir,
397
                        args_desc = ["Full path to the Prosody data directory"],
398
                        args_example = ["/var/lib/prosody/datadump/"],
399
                        args = [{dir, string}], result = {res, rescode}},
400

401
     #ejabberd_commands{name = convert_to_yaml, tags = [config],
402
                        desc = "Convert the input file from Erlang to YAML format",
403
                        module = ?MODULE, function = convert_to_yaml,
404
                        args_desc = ["Full path to the original configuration file", "And full path to final file"],
405
                        args_example = ["/etc/ejabberd/ejabberd.cfg", "/etc/ejabberd/ejabberd.yml"],
406
                        args = [{in, string}, {out, string}],
407
                        result = {res, rescode}},
408
     #ejabberd_commands{name = dump_config, tags = [config],
409
                        desc = "Dump configuration in YAML format as seen by ejabberd",
410
                        module = ?MODULE, function = dump_config,
411
                        args_desc = ["Full path to output file"],
412
                        args_example = ["/tmp/ejabberd.yml"],
413
                        args = [{out, string}],
414
                        result = {res, rescode}},
415

416
     #ejabberd_commands{name = delete_expired_messages, tags = [offline, purge],
417
                        desc = "Delete expired offline messages from database",
418
                        module = ?MODULE, function = delete_expired_messages,
419
                        args = [], result = {res, rescode}},
420
     #ejabberd_commands{name = delete_old_messages, tags = [offline, purge],
421
                        desc = "Delete offline messages older than DAYS",
422
                        module = ?MODULE, function = delete_old_messages,
423
                        args_desc = ["Number of days"],
424
                        args_example = [31],
425
                        args = [{days, integer}], result = {res, rescode}},
426
     #ejabberd_commands{name = delete_old_messages_batch, tags = [offline, purge],
427
                        desc = "Delete offline messages older than DAYS",
428
                        note = "added in 22.05",
429
                        module = ?MODULE, function = delete_old_messages_batch,
430
                        args_desc = ["Name of host where messages should be deleted",
431
                                     "Days to keep messages",
432
                                     "Number of messages to delete per batch",
433
                                     "Desired rate of messages to delete per minute"],
434
                        args_example = [<<"localhost">>, 31, 1000, 10000],
435
                        args = [{host, binary}, {days, integer}, {batch_size, integer}, {rate, integer}],
436
                        result = {res, restuple},
437
                        result_desc = "Result tuple",
438
                        result_example = {ok, <<"Removal of 5000 messages in progress">>}},
439
     #ejabberd_commands{name = delete_old_messages_status, tags = [offline, purge],
440
                        desc = "Status of delete old offline messages operation",
441
                        note = "added in 22.05",
442
                        module = ?MODULE, function = delete_old_messages_status,
443
                        args_desc = ["Name of host where messages should be deleted"],
444
                        args_example = [<<"localhost">>],
445
                        args = [{host, binary}],
446
                        result = {status, string},
447
                        result_desc = "Status test",
448
                        result_example = "Operation in progress, delete 5000 messages"},
449
     #ejabberd_commands{name = abort_delete_old_messages, tags = [offline, purge],
450
                        desc = "Abort currently running delete old offline messages operation",
451
                        note = "added in 22.05",
452
                        module = ?MODULE, function = delete_old_messages_abort,
453
                        args_desc = ["Name of host where operation should be aborted"],
454
                        args_example = [<<"localhost">>],
455
                        args = [{host, binary}],
456
                        result = {status, string},
457
                        result_desc = "Status text",
458
                        result_example = "Operation aborted"},
459

460
     #ejabberd_commands{name = export_db, tags = [db],
461
                        desc = "Export database records for host to files",
462
                        note = "added in 25.XX",
463
                        module = ejabberd_db_serialize, function = export,
464
                        args_desc = ["Name of host that should be exported",
465
                                     "Directory name where exported files should be created"],
466
                        args_example = [<<"localhost">>, <<"/home/ejabberd/export">>],
467
                        args = [{host, binary}, {dir, binary}],
468
                        result = {res, restuple},
469
                        result_desc = "Result tuple",
470
                        result_example = {ok, <<"Export started">>}},
471
     #ejabberd_commands{name = export_db_status, tags = [db],
472
                        desc = "Return current status of export operation",
473
                        note = "added in 22.XX",
474
                        module = ejabberd_db_serialize, function = export_status,
475
                        args_desc = ["Name of host where export is performed"],
476
                        args_example = [<<"localhost">>],
477
                        args = [{host, binary}],
478
                        result = {status, string},
479
                        result_desc = "Current operation status",
480
                        result_example = "Operation in progress: 'Exporting mod_mam', exported 5000 records so far"},
481
     #ejabberd_commands{name = export_db_abort, tags = [db],
482
                        desc = "Abort currently running export peration",
483
                        note = "added in 22.XX",
484
                        module = ejabberd_db_serialize, function = export_abort,
485
                        args_desc = ["Name of host where export is performed"],
486
                        args_example = [<<"localhost">>],
487
                        args = [{host, binary}],
488
                        result = {status, string},
489
                        result_desc = "Operation status",
490
                        result_example = "Operation aborted"},
491

492
     #ejabberd_commands{name = import_db, tags = [db],
493
                        desc = "Import database records for host to files",
494
                        note = "added in 25.XX",
495
                        module = ejabberd_db_serialize, function = import,
496
                        args_desc = ["Name of host that should be imported",
497
                                     "Directory name where imported files should be created"],
498
                        args_example = [<<"localhost">>, <<"/home/ejabberd/export">>],
499
                        args = [{host, binary}, {dir, binary}],
500
                        result = {res, restuple},
501
                        result_desc = "Result tuple",
502
                        result_example = {ok, <<"Import started">>}},
503
     #ejabberd_commands{name = import_db_status, tags = [db],
504
                        desc = "Return current status of import operation",
505
                        note = "added in 22.XX",
506
                        module = ejabberd_db_serialize, function = import_status,
507
                        args_desc = ["Name of host where import is performed"],
508
                        args_example = [<<"localhost">>],
509
                        args = [{host, binary}],
510
                        result = {status, string},
511
                        result_desc = "Current operation status",
512
                        result_example = "Operation in progress: 'Importing mod_mam', imported 5000 records so far"},
513
     #ejabberd_commands{name = import_db_abort, tags = [db],
514
                        desc = "Abort currently running import peration",
515
                        note = "added in 22.XX",
516
                        module = ejabberd_db_serialize, function = import_abort,
517
                        args_desc = ["Name of host where import is performed"],
518
                        args_example = [<<"localhost">>],
519
                        args = [{host, binary}],
520
                        result = {status, string},
521
                        result_desc = "Operation status",
522
                        result_example = "Operation aborted"},
523

524
     #ejabberd_commands{name = export2sql, tags = [mnesia],
525
                        desc = "Export virtual host information from Mnesia tables to SQL file",
526
                        longdesc = "Configure the modules to use SQL, then call this command. "
527
                                   "After correctly exported the database of a vhost, "
528
                                   "you may want to delete from mnesia with "
529
                                   "the _`delete_mnesia`_ API.",
530
                        module = ejd2sql, function = export,
531
                        args_desc = ["Vhost", "Full path to the destination SQL file"],
532
                        args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
533
                        args = [{host, string}, {file, string}],
534
                        result = {res, rescode}},
535
     #ejabberd_commands{name = get_master, tags = [cluster],
536
                        desc = "Get master node of the clustered Mnesia tables",
537
                        note = "added in 24.06",
538
                        longdesc = "If there is no master, returns `none`.",
539
                        module = ?MODULE, function = get_master,
540
                        result = {nodename, atom}},
541
     #ejabberd_commands{name = set_master, tags = [cluster],
542
                        desc = "Set master node of the clustered Mnesia tables",
543
                        longdesc = "If `nodename` is set to `self`, then this "
544
                        "node will be set as its own master.",
545
                        module = ?MODULE, function = set_master,
546
                        args_desc = ["Name of the erlang node that will be considered master of this node"],
547
                        args_example = ["ejabberd@machine7"],
548
                        args = [{nodename, string}], result = {res, restuple}},
549
     #ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
550
                        desc = "Change the erlang node name in a backup file",
551
                        module = ?MODULE, function = mnesia_change_nodename,
552
                        args_desc = ["Name of the old erlang node", "Name of the new node",
553
                                     "Path to old backup file", "Path to the new backup file"],
554
                        args_example = ["ejabberd@machine1", "ejabberd@machine2",
555
                                        "/var/lib/ejabberd/old.backup", "/var/lib/ejabberd/new.backup"],
556
                        args = [{oldnodename, string}, {newnodename, string},
557
                                {oldbackup, string}, {newbackup, string}],
558
                        result = {res, restuple}},
559
     #ejabberd_commands{name = backup, tags = [mnesia],
560
                        desc = "Backup the Mnesia database to a binary file",
561
                        module = ?MODULE, function = backup_mnesia,
562
                        args_desc = ["Full path for the destination backup file"],
563
                        args_example = ["/var/lib/ejabberd/database.backup"],
564
                        args = [{file, string}], result = {res, restuple}},
565
     #ejabberd_commands{name = restore, tags = [mnesia],
566
                        desc = "Restore the Mnesia database from a binary backup file",
567
                        longdesc = "This restores immediately from a "
568
                        "binary backup file the internal Mnesia "
569
                        "database. This will consume a lot of memory if "
570
                        "you have a large database, you may prefer "
571
                        "_`install_fallback`_ API.",
572
                        module = ?MODULE, function = restore_mnesia,
573
                        args_desc = ["Full path to the backup file"],
574
                        args_example = ["/var/lib/ejabberd/database.backup"],
575
                        args = [{file, string}], result = {res, restuple}},
576
     #ejabberd_commands{name = dump, tags = [mnesia],
577
                        desc = "Dump the Mnesia database to a text file",
578
                        module = ?MODULE, function = dump_mnesia,
579
                        args_desc = ["Full path for the text file"],
580
                        args_example = ["/var/lib/ejabberd/database.txt"],
581
                        args = [{file, string}], result = {res, restuple}},
582
     #ejabberd_commands{name = dump_table, tags = [mnesia],
583
                        desc = "Dump a Mnesia table to a text file",
584
                        module = ?MODULE, function = dump_table,
585
                        args_desc = ["Full path for the text file", "Table name"],
586
                        args_example = ["/var/lib/ejabberd/table-muc-registered.txt", "muc_registered"],
587
                        args = [{file, string}, {table, string}], result = {res, restuple}},
588
     #ejabberd_commands{name = load, tags = [mnesia],
589
                        desc = "Restore Mnesia database from a text dump file",
590
                        longdesc = "Restore immediately. This is not "
591
                        "recommended for big databases, as it will "
592
                        "consume much time, memory and processor. In "
593
                        "that case it's preferable to use "
594
                        "_`backup`_ API and "
595
                        "_`install_fallback`_ API.",
596
                        module = ?MODULE, function = load_mnesia,
597
                        args_desc = ["Full path to the text file"],
598
                        args_example = ["/var/lib/ejabberd/database.txt"],
599
                        args = [{file, string}], result = {res, restuple}},
600
     #ejabberd_commands{name = mnesia_info, tags = [mnesia],
601
                        desc = "Dump info on global Mnesia state",
602
                        module = ?MODULE, function = mnesia_info,
603
                        args = [], result = {res, string}},
604
     #ejabberd_commands{name = mnesia_table_info, tags = [mnesia],
605
                        desc = "Dump info on Mnesia table state",
606
                        module = ?MODULE, function = mnesia_table_info,
607
                        args_desc = ["Mnesia table name"],
608
                        args_example = ["roster"],
609
                        args = [{table, string}], result = {res, string}},
610
     #ejabberd_commands{name = install_fallback, tags = [mnesia],
611
                        desc = "Install Mnesia database from a binary backup file",
612
                        longdesc = "The binary backup file is "
613
                        "installed as fallback: it will be used to "
614
                        "restore the database at the next ejabberd "
615
                        "start. This means that, after running this "
616
                        "command, you have to restart ejabberd. This "
617
                        "command requires less memory than "
618
                        "_`restore`_ API.",
619
                        module = ?MODULE, function = install_fallback_mnesia,
620
                        args_desc = ["Full path to the fallback file"],
621
                        args_example = ["/var/lib/ejabberd/database.fallback"],
622
                        args = [{file, string}], result = {res, restuple}},
623
     #ejabberd_commands{name = clear_cache, tags = [server],
624
                        desc = "Clear database cache on all nodes",
625
                        module = ?MODULE, function = clear_cache,
626
                        args = [], result = {res, rescode}},
627
     #ejabberd_commands{name = gc, tags = [server],
628
                        desc = "Force full garbage collection",
629
                        note = "added in 20.01",
630
                        module = ?MODULE, function = gc,
631
                        args = [], result = {res, rescode}},
632
     #ejabberd_commands{name = man, tags = [documentation],
633
                        desc = "Generate Unix manpage for current ejabberd version",
634
                        note = "added in 20.01",
635
                        module = ejabberd_doc, function = man,
636
                        args = [], result = {res, restuple}},
637

638
     #ejabberd_commands{name = webadmin_host_user_queue, tags = [offline, internal],
639
                        desc = "Generate WebAdmin offline queue HTML",
640
                        module = mod_offline, function = webadmin_host_user_queue,
641
                        args = [{user, binary}, {host, binary}, {query, any}, {lang, binary}],
642
                        result = {res, any}},
643

644
     #ejabberd_commands{name = webadmin_host_last_activity, tags = [internal],
645
                        desc = "Generate WebAdmin Last Activity HTML",
646
                        module = ejabberd_web_admin, function = webadmin_host_last_activity,
647
                        args = [{host, binary}, {query, any}, {lang, binary}],
648
                        result = {res, any}},
649
     #ejabberd_commands{name = webadmin_host_srg, tags = [internal],
650
                        desc = "Generate WebAdmin Shared Roster Group HTML",
651
                        module = mod_shared_roster, function = webadmin_host_srg,
652
                        args = [{host, binary}, {query, any}, {lang, binary}],
653
                        result = {res, any}},
654
     #ejabberd_commands{name = webadmin_host_srg_group, tags = [internal],
655
                        desc = "Generate WebAdmin Shared Roster Group HTML for a group",
656
                        module = mod_shared_roster, function = webadmin_host_srg_group,
657
                        args = [{host, binary}, {group, binary}, {query, any}, {lang, binary}],
658
                        result = {res, any}},
659

660
     #ejabberd_commands{name = webadmin_node_contrib, tags = [internal],
661
                        desc = "Generate WebAdmin ejabberd-contrib HTML",
662
                        module = ext_mod, function = webadmin_node_contrib,
663
                        args = [{node, atom}, {query, any}, {lang, binary}],
664
                        result = {res, any}},
665
     #ejabberd_commands{name = webadmin_node_db, tags = [internal],
666
                        desc = "Generate WebAdmin Mnesia database HTML",
667
                        module = ejabberd_web_admin, function = webadmin_node_db,
668
                        args = [{node, atom}, {query, any}, {lang, binary}],
669
                        result = {res, any}},
670
     #ejabberd_commands{name = webadmin_node_db_table, tags = [internal],
671
                        desc = "Generate WebAdmin Mnesia database HTML for a table",
672
                        module = ejabberd_web_admin, function = webadmin_node_db_table,
673
                        args = [{node, atom}, {table, binary}, {lang, binary}],
674
                        result = {res, any}},
675
     #ejabberd_commands{name = webadmin_node_db_table_page, tags = [internal],
676
                        desc = "Generate WebAdmin Mnesia database HTML for a table content",
677
                        module = ejabberd_web_admin, function = webadmin_node_db_table_page,
678
                        args = [{node, atom}, {table, binary}, {page, integer}],
679
                        result = {res, any}},
680

681
     #ejabberd_commands{name = mnesia_list_tables, tags = [mnesia],
682
                        desc = "List of Mnesia tables",
683
                        note = "added in 25.03",
684
                        module = ?MODULE, function = mnesia_list_tables,
685
                        result = {tables, {list, {table, {tuple, [{name, atom},
686
                                                                {storage_type, binary},
687
                                                                {elements, integer},
688
                                                                {memory_kb, integer},
689
                                                                {memory_mb, integer}
690
                                                               ]}}}}},
691
     #ejabberd_commands{name = mnesia_table_details, tags = [internal, mnesia],
692
                        desc = "Get details of a Mnesia table",
693
                        module = ?MODULE, function = mnesia_table_details,
694
                        args = [{table, binary}],
695
                        result = {details, {list, {detail, {tuple, [{name, atom},
696
                                                                {value, binary}
697
                                                               ]}}}}},
698

699
     #ejabberd_commands{name = mnesia_table_change_storage, tags = [mnesia],
700
                        desc = "Change storage type of a Mnesia table",
701
                        note = "added in 25.03",
702
                        longdesc = "Storage type can be: `ram_copies`, `disc_copies`, `disc_only_copies`, `remote_copy`.",
703
                        module = ?MODULE, function = mnesia_table_change_storage,
704
                        args = [{table, binary}, {storage_type, binary}],
705
                        result = {res, restuple}},
706
     #ejabberd_commands{name = mnesia_table_clear, tags = [internal, mnesia],
707
                        desc = "Delete all content in a Mnesia table",
708
                        module = ?MODULE, function = mnesia_table_clear,
709
                        args = [{table, binary}],
710
                        result = {res, restuple}},
711
     #ejabberd_commands{name = mnesia_table_destroy, tags = [internal, mnesia],
712
                        desc = "Destroy a Mnesia table",
713
                        module = ?MODULE, function = mnesia_table_destroy,
714
                        args = [{table, binary}],
715
                        result = {res, restuple}},
716
     #ejabberd_commands{name = echo, tags = [internal],
717
                        desc = "Return the same sentence that was provided",
718
                        module = ?MODULE, function = echo,
719
                        args_desc = ["Sentence to echoe"],
720
                        args_example = [<<"Test Sentence">>],
721
                        args = [{sentence, binary}],
722
                        result = {sentence, string},
723
                        result_example = "Test Sentence"},
724
     #ejabberd_commands{name = echo3, tags = [internal],
725
                        desc = "Return the same sentence that was provided",
726
                        module = ?MODULE, function = echo3,
727
                        args_desc = ["First argument", "Second argument", "Sentence to echoe"],
728
                        args_example = [<<"example.com">>, <<"Group1">>, <<"Test Sentence">>],
729
                        args = [{first, binary}, {second, binary}, {sentence, binary}],
730
                        result = {sentence, string},
731
                        result_example = "Test Sentence"}
732
    ].
733

734
%%%
735
%%% Server management
736
%%%
737

738
status() ->
739
    {InternalStatus, ProvidedStatus} = init:get_status(),
×
740
    String1 = io_lib:format("The node ~p is ~p. Status: ~p",
×
741
                            [node(), InternalStatus, ProvidedStatus]),
742
    {Is_running, String2} =
×
743
        case lists:keysearch(ejabberd, 1, application:which_applications()) of
744
            false ->
745
                {ejabberd_not_running, "ejabberd is not running in that node."};
×
746
            {value, {_, _, Version}} ->
747
                {ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
×
748
        end,
749
    {Is_running, String1 ++ "\n" ++String2}.
×
750

751
stop() ->
752
    _ = supervisor:terminate_child(ejabberd_sup, ejabberd_sm),
×
753
    timer:sleep(1000),
×
754
    init:stop().
×
755

756
restart() ->
757
    _ = supervisor:terminate_child(ejabberd_sup, ejabberd_sm),
×
758
    timer:sleep(1000),
×
759
    init:restart().
×
760

761
reopen_log() ->
762
    ejabberd_hooks:run(reopen_log_hook, []).
×
763

764
rotate_log() ->
765
    ejabberd_hooks:run(rotate_log_hook, []).
×
766

767
set_loglevel(LogLevel) ->
768
    try binary_to_existing_atom(iolist_to_binary(LogLevel), latin1) of
×
769
        Level ->
770
            case lists:member(Level, ejabberd_logger:loglevels()) of
×
771
                true ->
772
                    ejabberd_logger:set(Level);
×
773
                false ->
774
                    {error, "Invalid log level"}
×
775
            end
776
    catch _:_ ->
777
            {error, "Invalid log level"}
×
778
    end.
779

780
%%%
781
%%% Stop Kindly
782
%%%
783

784
evacuate_kindly(DelaySeconds, AnnouncementTextString) ->
785
    perform_kindly(DelaySeconds, AnnouncementTextString, evacuate).
×
786

787
restart_kindly(DelaySeconds, AnnouncementTextString) ->
788
    perform_kindly(DelaySeconds, AnnouncementTextString, restart).
×
789

790
stop_kindly(DelaySeconds, AnnouncementTextString) ->
791
    perform_kindly(DelaySeconds, AnnouncementTextString, stop).
×
792

793
perform_kindly(DelaySeconds, AnnouncementTextString, Action) ->
794
    Subject = str:format("Server stop in ~p seconds!", [DelaySeconds]),
×
795
    WaitingDesc = str:format("Waiting ~p seconds", [DelaySeconds]),
×
796
    AnnouncementText = list_to_binary(AnnouncementTextString),
×
797
    PreSteps =
×
798
        [{"Stopping ejabberd port listeners", ejabberd_listener, stop_listeners, []},
799
         {"Sending announcement to connected users",
800
          mod_announce,
801
          send_announcement_to_all,
802
          [ejabberd_config:get_myname(), Subject, AnnouncementText]},
803
         {"Sending service message to MUC rooms",
804
          ejabberd_admin,
805
          send_service_message_all_mucs,
806
          [Subject, AnnouncementText]},
807
         {WaitingDesc, timer, sleep, [DelaySeconds * 1000]}
808
        ],
809
    SpecificSteps =
×
810
        case Action of
811
            evacuate ->
812
                [{"Stopping ejabberd", application, stop, [ejabberd]},
×
813
                 {"Starting ejabberd", application, start, [ejabberd]},
814
                 {"Stopping ejabberd port listeners", ejabberd_listener, stop_listeners, []}];
815
            restart ->
816
                [{"Restarting Erlang node", init, restart, []}];
×
817
            stop ->
818
                [
UNCOV
819
                 {"Stopping Erlang node", init, stop, []}]
×
820
        end,
821
    Steps = PreSteps ++ SpecificSteps,
×
822
    NumberLast = length(Steps),
×
823
    TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
×
824
    lists:foldl(fun({Desc, Mod, Func, Args}, NumberThis) ->
×
825
                   SecondsDiff =
×
826
                       calendar:datetime_to_gregorian_seconds({date(), time()}) - TimestampStart,
827
                   io:format("~s[~p/~p ~ps]~s ~ts...~s ",
×
828
                             [?CLEAD ++ ?CINFO, NumberThis, NumberLast, SecondsDiff,
829
                              ?CMID ++ ?CINFO, Desc, ?CCLEAN]),
830
                   Result = (catch apply(Mod, Func, Args)),
×
831
                   io:format("~p~n", [Result]),
×
832
                   NumberThis + 1
×
833
                end,
834
                1,
835
                Steps),
836
    ok.
×
837

838
send_service_message_all_mucs(Subject, AnnouncementText) ->
839
    Message = str:format("~s~n~s", [Subject, AnnouncementText]),
×
840
    lists:foreach(
×
841
      fun(ServerHost) ->
842
              MUCHosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc),
×
843
              lists:foreach(
×
844
                fun(MUCHost) ->
845
                        mod_muc:broadcast_service_message(ServerHost, MUCHost, Message)
×
846
                end, MUCHosts)
847
      end,
848
      ejabberd_option:hosts()).
849

850
%%%
851
%%% ejabberd_update
852
%%%
853

854
update_list() ->
855
    {ok, _Dir, UpdatedBeams, _Script, _LowLevelScript, _Check} =
×
856
        ejabberd_update:update_info(),
857
    [atom_to_list(Beam) || Beam <- UpdatedBeams].
×
858

859
update("all") ->
860
    ResList = [{ModStr, update_module(ModStr)} || ModStr <- update_list()],
×
861
    String = case string:join([Mod || {Mod, {ok, _}} <- ResList], ", ") of
×
862
                 [] ->
863
                     "No modules updated";
×
864
                 ModulesString ->
865
                     "Updated modules: " ++ ModulesString
×
866
             end,
867
    {ok, String};
×
868
update(ModStr) ->
869
    update_module(ModStr).
×
870

871
update_module(ModuleNameBin) when is_binary(ModuleNameBin) ->
872
    update_module(binary_to_list(ModuleNameBin));
×
873
update_module(ModuleNameString) ->
874
    ModuleName = list_to_atom(ModuleNameString),
×
875
    case ejabberd_update:update([ModuleName]) of
×
876
        {ok, []} ->
877
            {ok, "Not updated: "++ModuleNameString};
×
878
        {ok, [ModuleName]} ->
879
            {ok, "Updated: "++ModuleNameString};
×
880
        {error, Reason} -> {error, Reason}
×
881
    end.
882

883
update() ->
884
    io:format("Compiling ejabberd...~n", []),
×
885
    os:cmd("make"),
×
886
    Mods = ejabberd_admin:update_list(),
×
887
    io:format("Updating modules: ~p~n", [Mods]),
×
888
    ejabberd_admin:update("all"),
×
889
    Mods2 = Mods -- ejabberd_admin:update_list(),
×
890
    io:format("Updated modules: ~p~n", [Mods2]),
×
891
    ok.
×
892

893
%%%
894
%%% Account management
895
%%%
896

897
register(User, Host, Password) ->
UNCOV
898
    case is_my_host(Host) of
8✔
899
        true ->
UNCOV
900
            case ejabberd_auth:try_register(User, Host, Password) of
8✔
901
                ok ->
UNCOV
902
                    {ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
8✔
903
                {error, exists} ->
904
                    Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
×
905
                    {error, conflict, 10090, Msg};
×
906
                {error, Reason} ->
907
                    String = io_lib:format("Can't register user ~s@~s at node ~p: ~s",
×
908
                                           [User, Host, node(),
909
                                            mod_register:format_error(Reason)]),
910
                    {error, cannot_register, 10001, String}
×
911
            end;
912
        false ->
913
            {error, cannot_register, 10001, "Unknown virtual host"}
×
914
    end.
915

916
unregister(User, Host) ->
UNCOV
917
    case is_my_host(Host) of
8✔
918
        true ->
UNCOV
919
            ejabberd_auth:remove_user(User, Host),
8✔
UNCOV
920
            {ok, ""};
8✔
921
        false ->
922
            {error, "Unknown virtual host"}
×
923
    end.
924

925
registered_users(Host) ->
UNCOV
926
    case is_my_host(Host) of
8✔
927
        true ->
UNCOV
928
            Users = ejabberd_auth:get_users(Host),
8✔
UNCOV
929
            SUsers = lists:sort(Users),
8✔
UNCOV
930
            lists:map(fun({U, _S}) -> U end, SUsers);
8✔
931
        false ->
932
            {error, "Unknown virtual host"}
×
933
    end.
934

935
registered_vhosts() ->
936
    ejabberd_option:hosts().
×
937

938
reload_config() ->
939
    case ejabberd_config:reload() of
×
940
        ok -> ok;
×
941
        Err ->
942
            Reason = ejabberd_config:format_error(Err),
×
943
            {error, Reason}
×
944
    end.
945

946
dump_config(Path) ->
947
    case ejabberd_config:dump(Path) of
×
948
        ok -> ok;
×
949
        Err ->
950
            Reason = ejabberd_config:format_error(Err),
×
951
            {error, Reason}
×
952
    end.
953

954
convert_to_yaml(In, Out) ->
955
    case ejabberd_config:convert_to_yaml(In, Out) of
×
956
        ok -> {ok, ""};
×
957
        Err ->
958
            Reason = ejabberd_config:format_error(Err),
×
959
            {error, Reason}
×
960
    end.
961

962
%%%
963
%%% Cluster management
964
%%%
965

966
join_cluster(NodeBin) when is_binary(NodeBin) ->
967
    join_cluster(list_to_atom(binary_to_list(NodeBin)));
×
968
join_cluster(Node) when is_atom(Node) ->
969
    IsNodes = lists:member(Node, ejabberd_cluster:get_nodes()),
×
970
    IsKnownNodes = lists:member(Node, ejabberd_cluster:get_known_nodes()),
×
971
    Ping = net_adm:ping(Node),
×
972
    join_cluster(Node, IsNodes, IsKnownNodes, Ping).
×
973

974
join_cluster(_Node, true, _IsKnownNodes, _Ping) ->
975
    {error, "This node already joined that running node."};
×
976
join_cluster(_Node, _IsNodes, true, _Ping) ->
977
    {error, "This node already joined that known node."};
×
978
join_cluster(_Node, _IsNodes, _IsKnownNodes, pang) ->
979
    {error, "This node cannot reach that node."};
×
980
join_cluster(Node, false, false, pong) ->
981
    case timer:apply_after(1000, ejabberd_cluster, join, [Node]) of
×
982
        {ok, _} ->
983
            {ok, "Trying to join that cluster, wait a few seconds and check the list of nodes."};
×
984
        Error ->
985
            {error, io_lib:format("Can't join that cluster: ~p", [Error])}
×
986
    end.
987

988
join_cluster_here(NodeBin) ->
989
    Node = list_to_atom(binary_to_list(NodeBin)),
×
990
    IsNodes = lists:member(Node, ejabberd_cluster:get_nodes()),
×
991
    IsKnownNodes = lists:member(Node, ejabberd_cluster:get_known_nodes()),
×
992
    Ping = net_adm:ping(Node),
×
993
    join_cluster_here(Node, IsNodes, IsKnownNodes, Ping).
×
994

995
join_cluster_here(_Node, true, _IsKnownNodes, _Ping) ->
996
    {error, "This node already joined that running node."};
×
997
join_cluster_here(_Node, _IsNodes, true, _Ping) ->
998
    {error, "This node already joined that known node."};
×
999
join_cluster_here(_Node, _IsNodes, _IsKnownNodes, pang) ->
1000
    {error, "This node cannot reach that node."};
×
1001
join_cluster_here(Node, false, false, pong) ->
1002
    case ejabberd_cluster:call(Node, ejabberd_admin, join_cluster, [misc:atom_to_binary(node())]) of
×
1003
        {ok, _} ->
1004
            {ok, "Trying to join node to this cluster, wait a few seconds and check the list of nodes."};
×
1005
        Error ->
1006
            {error, io_lib:format("Can't join node to this cluster: ~p", [Error])}
×
1007
    end.
1008

1009
leave_cluster(NodeBin) when is_binary(NodeBin) ->
1010
    leave_cluster(list_to_atom(binary_to_list(NodeBin)));
×
1011
leave_cluster(Node) ->
1012
    ejabberd_cluster:leave(Node).
×
1013

1014
list_cluster() ->
1015
    ejabberd_cluster:get_nodes().
×
1016

1017
list_cluster_detailed() ->
1018
    KnownNodes = ejabberd_cluster:get_known_nodes(),
×
1019
    RunningNodes = ejabberd_cluster:get_nodes(),
×
1020
    [get_cluster_node_details(Node, RunningNodes) || Node <- KnownNodes].
×
1021

1022
get_cluster_node_details(Node, RunningNodes) ->
1023
    get_cluster_node_details2(Node, lists:member(Node, RunningNodes)).
×
1024

1025
get_cluster_node_details2(Node, false) ->
1026
    {Node, "false", "", -1, -1, -1, unknown};
×
1027
get_cluster_node_details2(Node, true) ->
1028
    try ejabberd_cluster:call(Node, ejabberd_admin, get_cluster_node_details3, []) of
×
1029
        Result -> Result
×
1030
    catch
1031
        E:R ->
1032
            Status = io_lib:format("~p: ~p", [E, R]),
×
1033
            {Node, "true", Status, -1, -1, -1, unknown}
×
1034
    end.
1035

1036
get_cluster_node_details3() ->
1037
    {ok, StatusString} = status(),
×
1038
    UptimeSeconds = mod_admin_extra:stats(<<"uptimeseconds">>),
×
1039
    Processes = mod_admin_extra:stats(<<"processes">>),
×
1040
    OnlineUsers = mod_admin_extra:stats(<<"onlineusersnode">>),
×
1041
    GetMaster = get_master(),
×
1042
    {node(), "true", StatusString, OnlineUsers, Processes, UptimeSeconds, GetMaster}.
×
1043

1044
%%%
1045
%%% Migration management
1046
%%%
1047

1048
import_file(Path) ->
1049
    case jd2ejd:import_file(Path) of
×
1050
        ok ->
1051
            {ok, ""};
×
1052
        {error, Reason} ->
1053
            String = io_lib:format("Can't import jabberd14 spool file ~p at node ~p: ~p",
×
1054
                                   [filename:absname(Path), node(), Reason]),
1055
            {cannot_import_file, String}
×
1056
    end.
1057

1058
import_dir(Path) ->
1059
    case jd2ejd:import_dir(Path) of
×
1060
        ok ->
1061
            {ok, ""};
×
1062
        {error, Reason} ->
1063
            String = io_lib:format("Can't import jabberd14 spool dir ~p at node ~p: ~p",
×
1064
                                   [filename:absname(Path), node(), Reason]),
1065
            {cannot_import_dir, String}
×
1066
    end.
1067

1068
%%%
1069
%%% Purge DB
1070
%%%
1071

1072
delete_expired_messages() ->
1073
    lists:foreach(
×
1074
      fun(Host) ->
1075
              {atomic, ok} = mod_offline:remove_expired_messages(Host)
×
1076
      end, ejabberd_option:hosts()).
1077

1078
delete_old_messages(Days) ->
1079
    lists:foreach(
×
1080
      fun(Host) ->
1081
              {atomic, _} = mod_offline:remove_old_messages(Days, Host)
×
1082
      end, ejabberd_option:hosts()).
1083

1084
delete_old_messages_batch(Server, Days, BatchSize, Rate) ->
1085
    LServer = jid:nameprep(Server),
×
1086
    Mod = gen_mod:db_mod(LServer, mod_offline),
×
1087
    case ejabberd_batch:register_task({spool, LServer}, 0, Rate, {LServer, Days, BatchSize, none},
×
1088
                                      fun({L, Da, B, IS} = S) ->
1089
                                          case {erlang:function_exported(Mod, remove_old_messages_batch, 3),
×
1090
                                                erlang:function_exported(Mod, remove_old_messages_batch, 4)} of
1091
                                              {true, _} ->
1092
                                                  case Mod:remove_old_messages_batch(L, Da, B) of
×
1093
                                                      {ok, Count} ->
1094
                                                          {ok, S, Count, undefined};
×
1095
                                                      {error, _} = E ->
1096
                                                          E
×
1097
                                                  end;
1098
                                              {_, true} ->
1099
                                                  case Mod:remove_old_messages_batch(L, Da, B, IS) of
×
1100
                                                      {ok, IS2, Count, undefined} ->
1101
                                                          {ok, {L, Da, B, IS2}, Count};
×
1102
                                                      {error, _} = E ->
1103
                                                          E
×
1104
                                                  end;
1105
                                              _ ->
1106
                                                  {error, not_implemented_for_backend}
×
1107
                                          end
1108
                                      end) of
1109
        ok ->
1110
            {ok, ""};
×
1111
        {error, in_progress} ->
1112
            {error, "Operation in progress"}
×
1113
    end.
1114

1115
delete_old_messages_status(Server) ->
1116
    LServer = jid:nameprep(Server),
×
1117
    Msg = case ejabberd_batch:task_status({spool, LServer}) of
×
1118
              not_started ->
1119
                  "Operation not started";
×
1120
              {failed, Steps, Error} ->
1121
                  io_lib:format("Operation failed after deleting ~p messages with error ~p",
×
1122
                                [Steps, misc:format_val(Error)]);
1123
              {aborted, Steps, _} ->
1124
                  io_lib:format("Operation was aborted after deleting ~p messages",
×
1125
                                [Steps]);
1126
              {working, Steps, _} ->
1127
                  io_lib:format("Operation in progress, deleted ~p messages",
×
1128
                                [Steps]);
1129
              {completed, Steps, _} ->
1130
                  io_lib:format("Operation was completed after deleting ~p messages",
×
1131
                                [Steps])
1132
          end,
1133
    lists:flatten(Msg).
×
1134

1135
delete_old_messages_abort(Server) ->
1136
    LServer = jid:nameprep(Server),
×
1137
    case ejabberd_batch:abort_task({spool, LServer}) of
×
1138
        aborted -> "Operation aborted";
×
1139
        not_started -> "No task running"
×
1140
    end.
1141

1142
%%%
1143
%%% Mnesia management
1144
%%%
1145

1146
get_master() ->
1147
    case mnesia:table_info(session, master_nodes) of
×
1148
    [] -> none;
×
1149
    [Node] -> Node
×
1150
    end.
1151

1152
set_master("self") ->
1153
    set_master(node());
×
1154
set_master(NodeString) when is_list(NodeString) ->
1155
    set_master(list_to_atom(NodeString));
×
1156
set_master(Node) when is_atom(Node) ->
1157
    case mnesia:set_master_nodes([Node]) of
×
1158
        ok ->
1159
            {ok, "ok"};
×
1160
        {error, Reason} ->
1161
            String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
×
1162
                                   [Node, node(), Reason]),
1163
            {error, String}
×
1164
    end.
1165

1166
backup_mnesia(Path) ->
1167
    case mnesia:backup(Path) of
×
1168
        ok ->
1169
            {ok, ""};
×
1170
        {error, Reason} ->
1171
            String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
×
1172
                                   [filename:absname(Path), node(), Reason]),
1173
            {cannot_backup, String}
×
1174
    end.
1175

1176
restore_mnesia(Path) ->
1177
    case ejabberd_admin:restore(Path) of
×
1178
        {atomic, _} ->
1179
            {ok, ""};
×
1180
        {aborted,{no_exists,Table}} ->
1181
            String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
×
1182
                                   [filename:absname(Path), node(), Table]),
1183
            {table_not_exists, String};
×
1184
        {aborted,enoent} ->
1185
            String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
×
1186
                                   [filename:absname(Path), node()]),
1187
            {file_not_found, String}
×
1188
    end.
1189

1190
%% Mnesia database restore
1191
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
1192
%% mod_configure/adhoc
1193
restore(Path) ->
1194
    mnesia:restore(Path, [{keep_tables,keep_tables()},
×
1195
                          {default_op, skip_tables}]).
1196

1197
%% This function return a list of tables that should be kept from a previous
1198
%% version backup.
1199
%% Obsolete tables or tables created by module who are no longer used are not
1200
%% restored and are ignored.
1201
keep_tables() ->
1202
    lists:flatten([acl, passwd, config,
×
1203
                   keep_modules_tables()]).
1204

1205
%% Returns the list of modules tables in use, according to the list of actually
1206
%% loaded modules
1207
keep_modules_tables() ->
1208
    lists:map(fun(Module) -> module_tables(Module) end,
×
1209
              gen_mod:loaded_modules(ejabberd_config:get_myname())).
1210

1211
%% TODO: This mapping should probably be moved to a callback function in each
1212
%% module.
1213
%% Mapping between modules and their tables
1214
module_tables(mod_announce) -> [motd, motd_users];
×
1215
module_tables(mod_last) -> [last_activity];
×
1216
module_tables(mod_muc) -> [muc_room, muc_registered];
×
1217
module_tables(mod_offline) -> [offline_msg];
×
1218
module_tables(mod_privacy) -> [privacy];
×
1219
module_tables(mod_private) -> [private_storage];
×
1220
module_tables(mod_pubsub) -> [pubsub_node];
×
1221
module_tables(mod_roster) -> [roster];
×
1222
module_tables(mod_shared_roster) -> [sr_group, sr_user];
×
1223
module_tables(mod_vcard) -> [vcard, vcard_search];
×
1224
module_tables(_Other) -> [].
×
1225

1226
get_local_tables() ->
1227
    Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
×
1228
    Tabs = lists:filter(
×
1229
             fun(T) ->
1230
                     case mnesia:table_info(T, storage_type) of
×
1231
                         disc_copies -> true;
×
1232
                         disc_only_copies -> true;
×
1233
                         _ -> false
×
1234
                     end
1235
             end, Tabs1),
1236
    Tabs.
×
1237

1238
dump_mnesia(Path) ->
1239
    Tabs = get_local_tables(),
×
1240
    dump_tables(Path, Tabs).
×
1241

1242
dump_table(Path, STable) ->
1243
    Table = list_to_atom(STable),
×
1244
    dump_tables(Path, [Table]).
×
1245

1246
dump_tables(Path, Tables) ->
1247
    case dump_to_textfile(Path, Tables) of
×
1248
        ok ->
1249
            {ok, ""};
×
1250
        {error, Reason} ->
1251
            String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
×
1252
                                   [filename:absname(Path), node(), Reason]),
1253
            {cannot_dump, String}
×
1254
    end.
1255

1256
dump_to_textfile(File) ->
1257
    Tabs = get_local_tables(),
×
1258
    dump_to_textfile(File, Tabs).
×
1259

1260
dump_to_textfile(File, Tabs) ->
1261
    dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
×
1262
dump_to_textfile(yes, Tabs, {ok, F}) ->
1263
    Defs = lists:map(
×
1264
             fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
×
1265
                            {attributes, mnesia:table_info(T, attributes)}]}
1266
             end,
1267
             Tabs),
1268
    io:format(F, "~p.~n", [{tables, Defs}]),
×
1269
    lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
×
1270
    file:close(F);
×
1271
dump_to_textfile(_, _, {ok, F}) ->
1272
    file:close(F),
×
1273
    {error, mnesia_not_running};
×
1274
dump_to_textfile(_, _, {error, Reason}) ->
1275
    {error, Reason}.
×
1276

1277
dump_tab(F, T) ->
1278
    W = mnesia:table_info(T, wild_pattern),
×
1279
    {atomic,All} = mnesia:transaction(
×
1280
                     fun() -> mnesia:match_object(T, W, read) end),
×
1281
    lists:foreach(
×
1282
      fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
×
1283

1284
load_mnesia(Path) ->
1285
    case mnesia:load_textfile(Path) of
×
1286
        {atomic, ok} ->
1287
            {ok, ""};
×
1288
        {error, Reason} ->
1289
            String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
×
1290
                                   [filename:absname(Path), node(), Reason]),
1291
            {cannot_load, String}
×
1292
    end.
1293

1294
mnesia_info() ->
1295
    lists:flatten(io_lib:format("~p", [mnesia:system_info(all)])).
×
1296

1297
mnesia_table_info(Table) ->
1298
    ATable = list_to_atom(Table),
×
1299
    lists:flatten(io_lib:format("~p", [mnesia:table_info(ATable, all)])).
×
1300

1301
install_fallback_mnesia(Path) ->
1302
    case mnesia:install_fallback(Path) of
×
1303
        ok ->
1304
            {ok, ""};
×
1305
        {error, Reason} ->
1306
            String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
×
1307
                                   [filename:absname(Path), node(), Reason]),
1308
            {cannot_fallback, String}
×
1309
    end.
1310

1311
mnesia_change_nodename(FromString, ToString, Source, Target) ->
1312
    From = list_to_atom(FromString),
×
1313
    To = list_to_atom(ToString),
×
1314
    Switch =
×
1315
        fun
1316
            (Node) when Node == From ->
1317
                io:format("     - Replacing nodename: '~p' with: '~p'~n", [From, To]),
×
1318
                To;
×
1319
            (Node) when Node == To ->
1320
                %% throw({error, already_exists});
1321
                io:format("     - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]),
×
1322
                Node;
×
1323
            (Node) ->
1324
                io:format("     - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]),
×
1325
                Node
×
1326
        end,
1327
    Convert =
×
1328
        fun
1329
            ({schema, db_nodes, Nodes}, Acc) ->
1330
                io:format(" +++ db_nodes ~p~n", [Nodes]),
×
1331
                {[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
×
1332
            ({schema, version, Version}, Acc) ->
1333
                io:format(" +++ version: ~p~n", [Version]),
×
1334
                {[{schema, version, Version}], Acc};
×
1335
            ({schema, cookie, Cookie}, Acc) ->
1336
                io:format(" +++ cookie: ~p~n", [Cookie]),
×
1337
                {[{schema, cookie, Cookie}], Acc};
×
1338
            ({schema, Tab, CreateList}, Acc) ->
1339
                io:format("~n * Checking table: '~p'~n", [Tab]),
×
1340
                Keys = [ram_copies, disc_copies, disc_only_copies],
×
1341
                OptSwitch =
×
1342
                    fun({Key, Val}) ->
1343
                            case lists:member(Key, Keys) of
×
1344
                                true ->
1345
                                    io:format("   + Checking key: '~p'~n", [Key]),
×
1346
                                    {Key, lists:map(Switch, Val)};
×
1347
                                false-> {Key, Val}
×
1348
                            end
1349
                    end,
1350
                Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
×
1351
                Res;
×
1352
            (Other, Acc) ->
1353
                {[Other], Acc}
×
1354
        end,
1355
    mnesia:traverse_backup(Source, Target, Convert, switched).
×
1356

1357
clear_cache() ->
1358
    Nodes = ejabberd_cluster:get_nodes(),
×
1359
    lists:foreach(fun(T) -> ets_cache:clear(T, Nodes) end, ets_cache:all()).
×
1360

1361
gc() ->
1362
    lists:foreach(fun erlang:garbage_collect/1, processes()).
×
1363

1364
-spec is_my_host(binary()) -> boolean().
1365
is_my_host(Host) ->
UNCOV
1366
    try ejabberd_router:is_my_host(Host)
24✔
1367
    catch _:{invalid_domain, _} -> false
×
1368
    end.
1369

1370
%%%
1371
%%% Internal
1372
%%%
1373

1374
%% @format-begin
1375

1376
mnesia_table_change_storage(STable, SType) ->
1377
    Table = binary_to_existing_atom(STable, latin1),
×
1378
    Type =
×
1379
        case SType of
1380
            <<"remote_copy">> ->
1381
                remote_copy;
×
1382
            <<"ram_copies">> ->
1383
                ram_copies;
×
1384
            <<"disc_copies">> ->
1385
                disc_copies;
×
1386
            <<"disc_only_copies">> ->
1387
                disc_only_copies;
×
1388
            _ ->
1389
                false
×
1390
        end,
1391
    Node = node(),
×
1392
    Result =
×
1393
        case Type of
1394
            false ->
1395
                "Nothing to do";
×
1396
            remote_copy ->
1397
                mnesia:del_table_copy(Table, Node),
×
1398
                "Deleted table copy";
×
1399
            _ ->
1400
                case mnesia:add_table_copy(Table, Node, Type) of
×
1401
                    {aborted, _} ->
1402
                        mnesia:change_table_copy_type(Table, Node, Type),
×
1403
                        "Changed table copy type";
×
1404
                    _ ->
1405
                        "Added table copy"
×
1406
                end
1407
        end,
1408
    {ok, Result}.
×
1409

1410
mnesia_table_clear(STable) ->
1411
    Table = binary_to_existing_atom(STable, latin1),
×
1412
    mnesia:clear_table(Table).
×
1413

1414
mnesia_table_delete(STable) ->
1415
    Table = binary_to_existing_atom(STable, latin1),
×
1416
    mnesia:delete_table(Table).
×
1417

1418
mnesia_table_details(STable) ->
1419
    Table = binary_to_existing_atom(STable, latin1),
×
1420
    [{Name, iolist_to_binary(str:format("~p", [Value]))}
×
1421
     || {Name, Value} <- mnesia:table_info(Table, all)].
×
1422

1423
mnesia_list_tables() ->
1424
    STables =
×
1425
        lists:sort(
1426
            mnesia:system_info(tables)),
1427
    lists:map(fun(Table) ->
×
1428
                 TInfo = mnesia:table_info(Table, all),
×
1429
                 {value, {storage_type, Type}} = lists:keysearch(storage_type, 1, TInfo),
×
1430
                 {value, {size, Size}} = lists:keysearch(size, 1, TInfo),
×
1431
                 {value, {memory, Memory}} = lists:keysearch(memory, 1, TInfo),
×
1432
                 MemoryB = Memory * erlang:system_info(wordsize),
×
1433
                 MemoryKB = MemoryB div 1024,
×
1434
                 MemoryMB = MemoryKB div 1024,
×
1435
                 {Table, storage_type_bin(Type), Size, MemoryKB, MemoryMB}
×
1436
              end,
1437
              STables).
1438

1439
storage_type_bin(ram_copies) ->
1440
    <<"RAM copy">>;
×
1441
storage_type_bin(disc_copies) ->
1442
    <<"RAM and disc copy">>;
×
1443
storage_type_bin(disc_only_copies) ->
1444
    <<"Disc only copy">>;
×
1445
storage_type_bin(unknown) ->
1446
    <<"Remote copy">>.
×
1447

1448
echo(Sentence) ->
UNCOV
1449
    Sentence.
104✔
1450

1451
echo3(_, _, Sentence) ->
1452
    Sentence.
×
1453

1454
%%%
1455
%%% Web Admin: Main
1456
%%%
1457

1458
web_menu_main(Acc, _Lang) ->
UNCOV
1459
    Acc ++ [{<<"purge">>, <<"Purge">>}, {<<"stanza">>, <<"Stanza">>}].
48✔
1460

1461
web_page_main(_, #request{path = [<<"purge">>]} = R) ->
1462
    Types =
×
1463
        [{<<"#erlang">>, <<"Erlang">>},
1464
         {<<"#users">>, <<"Users">>},
1465
         {<<"#offline">>, <<"Offline">>},
1466
         {<<"#mam">>, <<"MAM">>},
1467
         {<<"#pubsub">>, <<"PubSub">>},
1468
         {<<"#push">>, <<"Push">>}],
1469
    Head = [?XC(<<"h1">>, <<"Purge">>)],
×
1470
    Set = [?XE(<<"ul">>, [?LI([?AC(MIU, MIN)]) || {MIU, MIN} <- Types]),
×
1471
           ?X(<<"hr">>),
1472
           ?XAC(<<"h2">>, [{<<"id">>, <<"erlang">>}], <<"Erlang">>),
1473
           ?XE(<<"blockquote">>,
1474
               [ejabberd_web_admin:make_command(clear_cache, R),
1475
                ejabberd_web_admin:make_command(gc, R)]),
1476
           ?X(<<"hr">>),
1477
           ?XAC(<<"h2">>, [{<<"id">>, <<"users">>}], <<"Users">>),
1478
           ?XE(<<"blockquote">>, [ejabberd_web_admin:make_command(delete_old_users, R)]),
1479
           ?X(<<"hr">>),
1480
           ?XAC(<<"h2">>, [{<<"id">>, <<"offline">>}], <<"Offline">>),
1481
           ?XE(<<"blockquote">>,
1482
               [ejabberd_web_admin:make_command(delete_expired_messages, R),
1483
                ejabberd_web_admin:make_command(delete_old_messages, R),
1484
                ejabberd_web_admin:make_command(delete_old_messages_batch, R),
1485
                ejabberd_web_admin:make_command(delete_old_messages_status, R)]),
1486
           ?X(<<"hr">>),
1487
           ?XAC(<<"h2">>, [{<<"id">>, <<"mam">>}], <<"MAM">>),
1488
           ?XE(<<"blockquote">>,
1489
               [ejabberd_web_admin:make_command(delete_old_mam_messages, R),
1490
                ejabberd_web_admin:make_command(delete_old_mam_messages_batch, R),
1491
                ejabberd_web_admin:make_command(delete_old_mam_messages_status, R)]),
1492
           ?X(<<"hr">>),
1493
           ?XAC(<<"h2">>, [{<<"id">>, <<"pubsub">>}], <<"PubSub">>),
1494
           ?XE(<<"blockquote">>,
1495
               [ejabberd_web_admin:make_command(delete_expired_pubsub_items, R),
1496
                ejabberd_web_admin:make_command(delete_old_pubsub_items, R)]),
1497
           ?X(<<"hr">>),
1498
           ?XAC(<<"h2">>, [{<<"id">>, <<"push">>}], <<"Push">>),
1499
           ?XE(<<"blockquote">>, [ejabberd_web_admin:make_command(delete_old_push_sessions, R)])],
1500
    {stop, Head ++ Set};
×
1501
web_page_main(_, #request{path = [<<"stanza">>]} = R) ->
1502
    Head = [?XC(<<"h1">>, <<"Stanza">>)],
×
1503
    Set = [ejabberd_web_admin:make_command(send_message, R),
×
1504
           ejabberd_web_admin:make_command(send_stanza, R),
1505
           ejabberd_web_admin:make_command(send_stanza_c2s, R)],
1506
    {stop, Head ++ Set};
×
1507
web_page_main(Acc, _) ->
1508
    Acc.
×
1509

1510
%%%
1511
%%% Web Admin: Node
1512
%%%
1513

1514
web_menu_node(Acc, _Node, _Lang) ->
1515
    Acc
1516
    ++ [{<<"cluster">>, <<"Clustering">>},
×
1517
        {<<"update">>, <<"Code Update">>},
1518
        {<<"config-file">>, <<"Configuration File">>},
1519
        {<<"logs">>, <<"Logs">>},
1520
        {<<"stop">>, <<"Stop Node">>}].
1521

1522
get_nodes_hint() ->
1523
    maybe
×
1524
        {ok, Names} ?= net_adm:names(),
×
1525
        NodeNames = lists:join(", ", [Name || {Name, _Port} <- Names]),
×
1526
        io_lib:format("Hint: Erlang nodes found in this machine that may be running ejabberd: ~s",
×
1527
                      [NodeNames])
1528
    else
1529
        {error, address} ->
1530
            io_lib:format("Hint: When EPMD is running, I can show here the Erlang nodes running in this machine.",
×
1531
                          [])
1532
    end.
1533

1534
web_page_node(_, Node, #request{path = [<<"cluster">>]} = R) ->
1535
    Head = ?H1GLraw(<<"Clustering">>, <<"admin/guide/clustering/">>, <<"Clustering">>),
×
1536
    Hint = list_to_binary(get_nodes_hint()),
×
1537
    Set1 =
×
1538
        [ejabberd_cluster:call(Node,
1539
                               ejabberd_web_admin,
1540
                               make_command,
1541
                               [join_cluster_here, R, [], []]),
1542
         ?XE(<<"blockquote">>, [?C(Hint)]),
1543
         ejabberd_cluster:call(Node,
1544
                               ejabberd_web_admin,
1545
                               make_command,
1546
                               [join_cluster, R, [], [{style, danger}]]),
1547
         ?XE(<<"blockquote">>, [?C(Hint)]),
1548
         ejabberd_cluster:call(Node,
1549
                               ejabberd_web_admin,
1550
                               make_command,
1551
                               [leave_cluster, R, [], [{style, danger}]])],
1552
    Set2 =
×
1553
        [ejabberd_cluster:call(Node,
1554
                               ejabberd_web_admin,
1555
                               make_command,
1556
                               [set_master, R, [], [{style, danger}]])],
1557
    timer:sleep(100), % leaving a cluster takes a while, let's delay the get commands
×
1558
    Get1 =
×
1559
        [ejabberd_cluster:call(Node,
1560
                               ejabberd_web_admin,
1561
                               make_command,
1562
                               [list_cluster_detailed,
1563
                                R,
1564
                                [],
1565
                                [{result_links, [{name, node, 3, <<"">>}]}]])],
1566
    Get2 =
×
1567
        [ejabberd_cluster:call(Node,
1568
                               ejabberd_web_admin,
1569
                               make_command,
1570
                               [get_master,
1571
                                R,
1572
                                [],
1573
                                [{result_named, true},
1574
                                 {result_links, [{nodename, node, 3, <<"">>}]}]])],
1575
    {stop, Head ++ Get1 ++ Set1 ++ Get2 ++ Set2};
×
1576
web_page_node(_, Node, #request{path = [<<"update">>]} = R) ->
1577
    Head = [?XC(<<"h1">>, <<"Code Update">>)],
×
1578
    Set = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [update, R])],
×
1579
    Get = [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [update_list, R])],
×
1580
    {stop, Head ++ Get ++ Set};
×
1581
web_page_node(_, Node, #request{path = [<<"config-file">>]} = R) ->
1582
    Res = ?H1GLraw(<<"Configuration File">>,
×
1583
                   <<"admin/configuration/file-format/">>,
1584
                   <<"File Format">>)
1585
          ++ [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [convert_to_yaml, R]),
1586
              ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [dump_config, R]),
1587
              ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [reload_config, R])],
1588
    {stop, Res};
×
1589
web_page_node(_, Node, #request{path = [<<"stop">>]} = R) ->
1590
    Res = [?XC(<<"h1">>, <<"Stop This Node">>),
×
1591
           ejabberd_cluster:call(Node,
1592
                                 ejabberd_web_admin,
1593
                                 make_command,
1594
                                 [restart, R, [], [{style, danger}]]),
1595
           ejabberd_cluster:call(Node,
1596
                                 ejabberd_web_admin,
1597
                                 make_command,
1598
                                 [stop_kindly, R, [], [{style, danger}]]),
1599
           ejabberd_cluster:call(Node,
1600
                                 ejabberd_web_admin,
1601
                                 make_command,
1602
                                 [stop, R, [], [{style, danger}]]),
1603
           ejabberd_cluster:call(Node,
1604
                                 ejabberd_web_admin,
1605
                                 make_command,
1606
                                 [halt, R, [], [{style, danger}]])],
1607
    {stop, Res};
×
1608
web_page_node(_, Node, #request{path = [<<"logs">>]} = R) ->
1609
    Res = ?H1GLraw(<<"Logs">>, <<"admin/configuration/basic/#logging">>, <<"Logging">>)
×
1610
          ++ [ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [set_loglevel, R]),
1611
              ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [get_loglevel, R]),
1612
              ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [reopen_log, R]),
1613
              ejabberd_cluster:call(Node, ejabberd_web_admin, make_command, [rotate_log, R])],
1614
    {stop, Res};
×
1615
web_page_node(Acc, _, _) ->
1616
    Acc.
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc