1 -- Copyright (C) 2009-2011 Florian Zeitz
3 -- This file is MIT/X11 licensed. Please see the
4 -- COPYING file in the source package for more information.
9 local prosody = _G.prosody;
10 local hosts = prosody.hosts;
11 local t_concat = table.concat;
13 local keys = require "util.iterators".keys;
14 local usermanager_user_exists = require "core.usermanager".user_exists;
15 local usermanager_create_user = require "core.usermanager".create_user;
16 local usermanager_delete_user = require "core.usermanager".delete_user;
17 local usermanager_get_password = require "core.usermanager".get_password;
18 local usermanager_set_password = require "core.usermanager".set_password;
19 local hostmanager_activate = require "core.hostmanager".activate;
20 local hostmanager_deactivate = require "core.hostmanager".deactivate;
21 local rm_load_roster = require "core.rostermanager".load_roster;
22 local st, jid = require "util.stanza", require "util.jid";
23 local timer_add_task = require "util.timer".add_task;
24 local dataforms_new = require "util.dataforms".new;
25 local array = require "util.array";
26 local modulemanager = require "modulemanager";
27 local core_post_stanza = prosody.core_post_stanza;
29 module:depends("adhoc");
30 local adhoc_new = module:require "adhoc".new;
32 local function generate_error_message(errors)
34 for name, err in pairs(errors) do
35 errmsg[#errmsg + 1] = name .. ": " .. err;
37 return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
40 function add_user_command_handler(self, data, state)
41 local add_user_layout = dataforms_new{
42 title = "Adding a User";
43 instructions = "Fill out this form to add a user.";
45 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
46 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for the account to be added" };
47 { name = "password", type = "text-private", label = "The password for this account" };
48 { name = "password-verify", type = "text-private", label = "Retype password" };
52 if data.action == "cancel" then
53 return { status = "canceled" };
55 local fields, err = add_user_layout:data(data.form);
57 return generate_error_message(err);
59 local username, host, resource = jid.split(fields.accountjid);
60 if data.to ~= host then
61 return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}};
63 if (fields["password"] == fields["password-verify"]) and username and host then
64 if usermanager_user_exists(username, host) then
65 return { status = "completed", error = { message = "Account already exists" } };
67 if usermanager_create_user(username, fields.password, host) then
68 module:log("info", "Created new account %s@%s", username, host);
69 return { status = "completed", info = "Account successfully created" };
71 return { status = "completed", error = { message = "Failed to write data to disk" } };
75 module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
76 return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
79 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = add_user_layout }, "executing";
83 function change_user_password_command_handler(self, data, state)
84 local change_user_password_layout = dataforms_new{
85 title = "Changing a User Password";
86 instructions = "Fill out this form to change a user's password.";
88 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
89 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
90 { name = "password", type = "text-private", required = true, label = "The password for this account" };
94 if data.action == "cancel" then
95 return { status = "canceled" };
97 local fields, err = change_user_password_layout:data(data.form);
99 return generate_error_message(err);
101 local username, host, resource = jid.split(fields.accountjid);
102 if data.to ~= host then
103 return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}};
105 if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
106 return { status = "completed", info = "Password successfully changed" };
108 return { status = "completed", error = { message = "User does not exist" } };
111 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = change_user_password_layout }, "executing";
115 function config_reload_handler(self, data, state)
116 local ok, err = prosody.reload_config();
118 return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" };
120 return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } };
125 function delete_user_command_handler(self, data, state)
126 local delete_user_layout = dataforms_new{
127 title = "Deleting a User";
128 instructions = "Fill out this form to delete a user.";
130 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
131 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
135 if data.action == "cancel" then
136 return { status = "canceled" };
138 local fields, err = delete_user_layout:data(data.form);
140 return generate_error_message(err);
143 local succeeded = {};
144 for _, aJID in ipairs(fields.accountjids) do
145 local username, host, resource = jid.split(aJID);
146 if (host == data.to) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
147 module:log("debug", "User %s has been deleted", aJID);
148 succeeded[#succeeded+1] = aJID;
150 module:log("debug", "Tried to delete non-existant user %s", aJID);
151 failed[#failed+1] = aJID;
154 return {status = "completed", info = (#succeeded ~= 0 and
155 "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
157 "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
159 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = delete_user_layout }, "executing";
163 function disconnect_user(match_jid)
164 local node, hostname, givenResource = jid.split(match_jid);
165 local host = hosts[hostname];
166 local sessions = host.sessions[node] and host.sessions[node].sessions;
167 for resource, session in pairs(sessions or {}) do
168 if not givenResource or (resource == givenResource) then
169 module:log("debug", "Disconnecting %s@%s/%s", node, hostname, resource);
176 function end_user_session_handler(self, data, state)
177 local end_user_session_layout = dataforms_new{
178 title = "Ending a User Session";
179 instructions = "Fill out this form to end a user's session.";
181 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
182 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
186 if data.action == "cancel" then
187 return { status = "canceled" };
190 local fields, err = end_user_session_layout:data(data.form);
192 return generate_error_message(err);
195 local succeeded = {};
196 for _, aJID in ipairs(fields.accountjids) do
197 local username, host, resource = jid.split(aJID);
198 if (host == data.to) and usermanager_user_exists(username, host) and disconnect_user(aJID) then
199 succeeded[#succeeded+1] = aJID;
201 failed[#failed+1] = aJID;
204 return {status = "completed", info = (#succeeded ~= 0 and
205 "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
207 "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
209 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = end_user_session_layout }, "executing";
213 function get_user_password_handler(self, data, state)
214 local get_user_password_layout = dataforms_new{
215 title = "Getting User's Password";
216 instructions = "Fill out this form to get a user's password.";
218 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
219 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
222 local get_user_password_result_layout = dataforms_new{
223 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
224 { name = "accountjid", type = "jid-single", label = "JID" };
225 { name = "password", type = "text-single", label = "Password" };
229 if data.action == "cancel" then
230 return { status = "canceled" };
232 local fields, err = get_user_password_layout:data(data.form);
234 return generate_error_message(err);
236 local user, host, resource = jid.split(fields.accountjid);
237 local accountjid = "";
239 if host ~= data.to then
240 return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. data.to } };
241 elseif usermanager_user_exists(user, host) then
242 accountjid = fields.accountjid;
243 password = usermanager_get_password(user, host);
245 return { status = "completed", error = { message = "User does not exist" } };
247 return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
249 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing";
253 function get_user_roster_handler(self, data, state)
254 local get_user_roster_layout = dataforms_new{
255 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
256 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
259 local get_user_roster_result_layout = dataforms_new{
260 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
261 { name = "accountjid", type = "jid-single", label = "This is the roster for" };
262 { name = "roster", type = "text-multi", label = "Roster XML" };
266 if data.action == "cancel" then
267 return { status = "canceled" };
270 local fields, err = get_user_roster_layout:data(data.form);
273 return generate_error_message(err);
276 local user, host, resource = jid.split(fields.accountjid);
277 if host ~= data.to then
278 return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } };
279 elseif not usermanager_user_exists(user, host) then
280 return { status = "completed", error = { message = "User does not exist" } };
282 local roster = rm_load_roster(user, host);
284 local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
285 for jid in pairs(roster) do
286 if jid ~= "pending" and jid then
289 subscription = roster[jid].subscription,
290 ask = roster[jid].ask,
291 name = roster[jid].name,
293 for group in pairs(roster[jid].groups) do
294 query:tag("group"):text(group):up();
300 local query_text = tostring(query):gsub("><", ">\n<");
302 local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
303 result:add_child(query);
304 return { status = "completed", other = result };
306 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_roster_layout }, "executing";
310 function get_user_stats_handler(self, data, state)
311 local get_user_stats_layout = dataforms_new{
312 title = "Get User Statistics";
313 instructions = "Fill out this form to gather user statistics.";
315 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
316 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
319 local get_user_stats_result_layout = dataforms_new{
320 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
321 { name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
322 { name = "rostersize", type = "text-single", label = "Roster size" };
323 { name = "onlineresources", type = "text-multi", label = "Online Resources" };
327 if data.action == "cancel" then
328 return { status = "canceled" };
331 local fields, err = get_user_stats_layout:data(data.form);
334 return generate_error_message(err);
337 local user, host, resource = jid.split(fields.accountjid);
338 if host ~= data.to then
339 return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. data.to } };
340 elseif not usermanager_user_exists(user, host) then
341 return { status = "completed", error = { message = "User does not exist" } };
343 local roster = rm_load_roster(user, host);
344 local rostersize = 0;
346 local resources = "";
347 for jid in pairs(roster) do
348 if jid ~= "pending" and jid then
349 rostersize = rostersize + 1;
352 for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
353 resources = resources .. "\n" .. resource;
354 IPs = IPs .. "\n" .. session.ip;
356 return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
357 onlineresources = resources}} };
359 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_stats_layout }, "executing";
363 function get_online_users_command_handler(self, data, state)
364 local get_online_users_layout = dataforms_new{
365 title = "Getting List of Online Users";
366 instructions = "How many users should be returned at most?";
368 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
369 { name = "max_items", type = "list-single", label = "Maximum number of users",
370 value = { "25", "50", "75", "100", "150", "200", "all" } };
371 { name = "details", type = "boolean", label = "Show details" };
374 local get_online_users_result_layout = dataforms_new{
375 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
376 { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
380 if data.action == "cancel" then
381 return { status = "canceled" };
384 local fields, err = get_online_users_layout:data(data.form);
387 return generate_error_message(err);
390 local max_items = nil
391 if fields.max_items ~= "all" then
392 max_items = tonumber(fields.max_items);
396 for username, user in pairs(hosts[data.to].sessions or {}) do
397 if (max_items ~= nil) and (count >= max_items) then
400 users[#users+1] = username.."@"..data.to;
402 if fields.details then
403 for resource, session in pairs(user.sessions or {}) do
404 local status, priority = "unavailable", tostring(session.priority or "-");
405 if session.presence then
406 status = session.presence:child_with_name("show");
408 status = status:get_text() or "[invalid!]";
410 status = "available";
413 users[#users+1] = " - "..resource..": "..status.."("..priority..")";
417 return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
419 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_online_users_layout }, "executing";
423 function list_modules_handler(self, data, state)
424 local result = dataforms_new {
425 title = "List of loaded modules";
427 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
428 { name = "modules", type = "text-multi", label = "The following modules are loaded:" };
431 local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n");
433 return { status = "completed", result = { layout = result; values = { modules = modules } } };
436 function load_module_handler(self, data, state)
437 local layout = dataforms_new {
438 title = "Load module";
439 instructions = "Specify the module to be loaded";
441 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
442 { name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
445 if data.action == "cancel" then
446 return { status = "canceled" };
448 local fields, err = layout:data(data.form);
450 return generate_error_message(err);
452 if modulemanager.is_loaded(data.to, fields.module) then
453 return { status = "completed", info = "Module already loaded" };
455 local ok, err = modulemanager.load(data.to, fields.module);
457 return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' };
459 return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to..
460 '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
463 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
467 local function globally_load_module_handler(self, data, state)
468 local layout = dataforms_new {
469 title = "Globally load module";
470 instructions = "Specify the module to be loaded on all hosts";
472 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
473 { name = "module", type = "text-single", required = true, label = "Module to globally load:"};
476 local ok_list, err_list = {}, {};
478 if data.action == "cancel" then
479 return { status = "canceled" };
482 local fields, err = layout:data(data.form);
484 return generate_error_message(err);
487 local ok, err = modulemanager.load(data.to, fields.module);
489 ok_list[#ok_list + 1] = data.to;
491 err_list[#err_list + 1] = data.to .. " (Error: " .. tostring(err) .. ")";
494 -- Is this a global module?
495 if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(data.to, fields.module) then
496 return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
499 -- This is either a shared or "normal" module, load it on all other hosts
500 for host_name, host in pairs(hosts) do
501 if host_name ~= data.to and host.type == "local" then
502 local ok, err = modulemanager.load(host_name, fields.module);
504 ok_list[#ok_list + 1] = host_name;
506 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
511 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
512 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
513 (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
514 return { status = "completed", info = info };
516 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
520 function reload_modules_handler(self, data, state)
521 local layout = dataforms_new {
522 title = "Reload modules";
523 instructions = "Select the modules to be reloaded";
525 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
526 { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
529 if data.action == "cancel" then
530 return { status = "canceled" };
532 local fields, err = layout:data(data.form);
534 return generate_error_message(err);
536 local ok_list, err_list = {}, {};
537 for _, module in ipairs(fields.modules) do
538 local ok, err = modulemanager.reload(data.to, module);
540 ok_list[#ok_list + 1] = module;
542 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
545 local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
546 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
547 (#err_list > 0 and ("Failed to reload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
548 return { status = "completed", info = info };
550 local modules = array.collect(keys(hosts[data.to].modules)):sort();
551 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
555 local function globally_reload_module_handler(self, data, state)
556 local layout = dataforms_new {
557 title = "Globally reload module";
558 instructions = "Specify the module to reload on all hosts";
560 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
561 { name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
564 if data.action == "cancel" then
565 return { status = "canceled" };
568 local is_global = false;
569 local fields, err = layout:data(data.form);
571 return generate_error_message(err);
574 if modulemanager.is_loaded("*", fields.module) then
575 local ok, err = modulemanager.reload("*", fields.module);
577 return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
582 local ok_list, err_list = {}, {};
583 for host_name, host in pairs(hosts) do
584 if modulemanager.is_loaded(host_name, fields.module) then
585 local ok, err = modulemanager.reload(host_name, fields.module);
587 ok_list[#ok_list + 1] = host_name;
589 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
594 if #ok_list == 0 and #err_list == 0 then
596 return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
598 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
602 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
603 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
604 (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
605 return { status = "completed", info = info };
607 local loaded_modules = array(keys(modulemanager.get_modules("*")));
608 for _, host in pairs(hosts) do
609 loaded_modules:append(array(keys(host.modules)));
611 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
612 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
616 function send_to_online(message, server)
618 sessions = { [server] = hosts[server] };
624 for domain, session in pairs(sessions) do
625 for user in pairs(session.sessions or {}) do
627 message.attr.from = domain;
628 message.attr.to = user.."@"..domain;
629 core_post_stanza(session, message);
636 function shut_down_service_handler(self, data, state)
637 local shut_down_service_layout = dataforms_new{
638 title = "Shutting Down the Service";
639 instructions = "Fill out this form to shut down the service.";
641 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
642 { name = "delay", type = "list-single", label = "Time delay before shutting down",
643 value = { {label = "30 seconds", value = "30"},
644 {label = "60 seconds", value = "60"},
645 {label = "90 seconds", value = "90"},
646 {label = "2 minutes", value = "120"},
647 {label = "3 minutes", value = "180"},
648 {label = "4 minutes", value = "240"},
649 {label = "5 minutes", value = "300"},
652 { name = "announcement", type = "text-multi", label = "Announcement" };
656 if data.action == "cancel" then
657 return { status = "canceled" };
660 local fields, err = shut_down_service_layout:data(data.form);
663 return generate_error_message(err);
666 if fields.announcement and #fields.announcement > 0 then
667 local message = st.message({type = "headline"}, fields.announcement):up()
668 :tag("subject"):text("Server is shutting down");
669 send_to_online(message);
672 timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
674 return { status = "completed", info = "Server is about to shut down" };
676 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing";
680 function unload_modules_handler(self, data, state)
681 local layout = dataforms_new {
682 title = "Unload modules";
683 instructions = "Select the modules to be unloaded";
685 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
686 { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
689 if data.action == "cancel" then
690 return { status = "canceled" };
692 local fields, err = layout:data(data.form);
694 return generate_error_message(err);
696 local ok_list, err_list = {}, {};
697 for _, module in ipairs(fields.modules) do
698 local ok, err = modulemanager.unload(data.to, module);
700 ok_list[#ok_list + 1] = module;
702 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
705 local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "")
706 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
707 (#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or "");
708 return { status = "completed", info = info };
710 local modules = array.collect(keys(hosts[data.to].modules)):sort();
711 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout; values = { modules = modules } } }, "executing";
715 local function globally_unload_module_handler(self, data, state)
716 local layout = dataforms_new {
717 title = "Globally unload module";
718 instructions = "Specify a module to unload on all hosts";
720 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
721 { name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
724 if data.action == "cancel" then
725 return { status = "canceled" };
728 local is_global = false;
729 local fields, err = layout:data(data.form);
731 return generate_error_message(err);
734 if modulemanager.is_loaded("*", fields.module) then
735 local ok, err = modulemanager.unload("*", fields.module);
737 return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
742 local ok_list, err_list = {}, {};
743 for host_name, host in pairs(hosts) do
744 if modulemanager.is_loaded(host_name, fields.module) then
745 local ok, err = modulemanager.unload(host_name, fields.module);
747 ok_list[#ok_list + 1] = host_name;
749 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
754 if #ok_list == 0 and #err_list == 0 then
756 return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
758 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
762 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
763 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
764 (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
765 return { status = "completed", info = info };
767 local loaded_modules = array(keys(modulemanager.get_modules("*")));
768 for _, host in pairs(hosts) do
769 loaded_modules:append(array(keys(host.modules)));
771 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
772 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout, values = { module = loaded_modules } } }, "executing";
777 function activate_host_handler(self, data, state)
778 local layout = dataforms_new {
779 title = "Activate host";
782 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
783 { name = "host", type = "text-single", required = true, label = "Host:"};
786 if data.action == "cancel" then
787 return { status = "canceled" };
789 local fields, err = layout:data(data.form);
791 return generate_error_message(err);
793 local ok, err = hostmanager_activate(fields.host);
796 return { status = "completed", info = fields.host .. " activated" };
798 return { status = "canceled", error = err }
801 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
805 function deactivate_host_handler(self, data, state)
806 local layout = dataforms_new {
807 title = "Deactivate host";
810 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
811 { name = "host", type = "text-single", required = true, label = "Host:"};
814 if data.action == "cancel" then
815 return { status = "canceled" };
817 local fields, err = layout:data(data.form);
819 return generate_error_message(err);
821 local ok, err = hostmanager_deactivate(fields.host);
824 return { status = "completed", info = fields.host .. " deactivated" };
826 return { status = "canceled", error = err }
829 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing";
834 local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin");
835 local change_user_password_desc = adhoc_new("Change User Password", "http://jabber.org/protocol/admin#change-user-password", change_user_password_command_handler, "admin");
836 local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin");
837 local delete_user_desc = adhoc_new("Delete User", "http://jabber.org/protocol/admin#delete-user", delete_user_command_handler, "admin");
838 local end_user_session_desc = adhoc_new("End User Session", "http://jabber.org/protocol/admin#end-user-session", end_user_session_handler, "admin");
839 local get_user_password_desc = adhoc_new("Get User Password", "http://jabber.org/protocol/admin#get-user-password", get_user_password_handler, "admin");
840 local get_user_roster_desc = adhoc_new("Get User Roster","http://jabber.org/protocol/admin#get-user-roster", get_user_roster_handler, "admin");
841 local get_user_stats_desc = adhoc_new("Get User Statistics","http://jabber.org/protocol/admin#user-stats", get_user_stats_handler, "admin");
842 local get_online_users_desc = adhoc_new("Get List of Online Users", "http://jabber.org/protocol/admin#get-online-users", get_online_users_command_handler, "admin");
843 local list_modules_desc = adhoc_new("List loaded modules", "http://prosody.im/protocol/modules#list", list_modules_handler, "admin");
844 local load_module_desc = adhoc_new("Load module", "http://prosody.im/protocol/modules#load", load_module_handler, "admin");
845 local globally_load_module_desc = adhoc_new("Globally load module", "http://prosody.im/protocol/modules#global-load", globally_load_module_handler, "global_admin");
846 local reload_modules_desc = adhoc_new("Reload modules", "http://prosody.im/protocol/modules#reload", reload_modules_handler, "admin");
847 local globally_reload_module_desc = adhoc_new("Globally reload module", "http://prosody.im/protocol/modules#global-reload", globally_reload_module_handler, "global_admin");
848 local shut_down_service_desc = adhoc_new("Shut Down Service", "http://jabber.org/protocol/admin#shutdown", shut_down_service_handler, "global_admin");
849 local unload_modules_desc = adhoc_new("Unload modules", "http://prosody.im/protocol/modules#unload", unload_modules_handler, "admin");
850 local globally_unload_module_desc = adhoc_new("Globally unload module", "http://prosody.im/protocol/modules#global-unload", globally_unload_module_handler, "global_admin");
851 local activate_host_desc = adhoc_new("Activate host", "http://prosody.im/protocol/hosts#activate", activate_host_handler, "global_admin");
852 local deactivate_host_desc = adhoc_new("Deactivate host", "http://prosody.im/protocol/hosts#deactivate", deactivate_host_handler, "global_admin");
854 module:provides("adhoc", add_user_desc);
855 module:provides("adhoc", change_user_password_desc);
856 module:provides("adhoc", config_reload_desc);
857 module:provides("adhoc", delete_user_desc);
858 module:provides("adhoc", end_user_session_desc);
859 module:provides("adhoc", get_user_password_desc);
860 module:provides("adhoc", get_user_roster_desc);
861 module:provides("adhoc", get_user_stats_desc);
862 module:provides("adhoc", get_online_users_desc);
863 module:provides("adhoc", list_modules_desc);
864 module:provides("adhoc", load_module_desc);
865 module:provides("adhoc", globally_load_module_desc);
866 module:provides("adhoc", reload_modules_desc);
867 module:provides("adhoc", globally_reload_module_desc);
868 module:provides("adhoc", shut_down_service_desc);
869 module:provides("adhoc", unload_modules_desc);
870 module:provides("adhoc", globally_unload_module_desc);
871 module:provides("adhoc", activate_host_desc);
872 module:provides("adhoc", deactivate_host_desc);