Comparison

plugins/mod_admin_adhoc.lua @ 5514:1091d7c3b4d2

mod_admin_adhoc: Use util.adhoc
author Florian Zeitz <florob@babelmonkeys.de>
date Tue, 23 Apr 2013 14:49:48 +0200
parent 5371:706206e191e8
child 5721:579c51cbc12c
comparison
equal deleted inserted replaced
5513:755f705f126a 5514:1091d7c3b4d2
7 local _G = _G; 7 local _G = _G;
8 8
9 local prosody = _G.prosody; 9 local prosody = _G.prosody;
10 local hosts = prosody.hosts; 10 local hosts = prosody.hosts;
11 local t_concat = table.concat; 11 local t_concat = table.concat;
12
13 local module_host = module:get_host();
12 14
13 local keys = require "util.iterators".keys; 15 local keys = require "util.iterators".keys;
14 local usermanager_user_exists = require "core.usermanager".user_exists; 16 local usermanager_user_exists = require "core.usermanager".user_exists;
15 local usermanager_create_user = require "core.usermanager".create_user; 17 local usermanager_create_user = require "core.usermanager".create_user;
16 local usermanager_delete_user = require "core.usermanager".delete_user; 18 local usermanager_delete_user = require "core.usermanager".delete_user;
23 local timer_add_task = require "util.timer".add_task; 25 local timer_add_task = require "util.timer".add_task;
24 local dataforms_new = require "util.dataforms".new; 26 local dataforms_new = require "util.dataforms".new;
25 local array = require "util.array"; 27 local array = require "util.array";
26 local modulemanager = require "modulemanager"; 28 local modulemanager = require "modulemanager";
27 local core_post_stanza = prosody.core_post_stanza; 29 local core_post_stanza = prosody.core_post_stanza;
30 local adhoc_simple = require "util.adhoc".new_simple_form;
31 local adhoc_initial = require "util.adhoc".new_initial_data_form;
28 32
29 module:depends("adhoc"); 33 module:depends("adhoc");
30 local adhoc_new = module:require "adhoc".new; 34 local adhoc_new = module:require "adhoc".new;
31 35
32 local function generate_error_message(errors) 36 local function generate_error_message(errors)
35 errmsg[#errmsg + 1] = name .. ": " .. err; 39 errmsg[#errmsg + 1] = name .. ": " .. err;
36 end 40 end
37 return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; 41 return { status = "completed", error = { message = t_concat(errmsg, "\n") } };
38 end 42 end
39 43
40 function add_user_command_handler(self, data, state) 44 -- Adding a new user
41 local add_user_layout = dataforms_new{ 45 local add_user_layout = dataforms_new{
42 title = "Adding a User"; 46 title = "Adding a User";
43 instructions = "Fill out this form to add a user."; 47 instructions = "Fill out this form to add a user.";
44 48
45 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 49 { 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" }; 50 { 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" }; 51 { name = "password", type = "text-private", label = "The password for this account" };
48 { name = "password-verify", type = "text-private", label = "Retype password" }; 52 { name = "password-verify", type = "text-private", label = "Retype password" };
49 }; 53 };
50 54
51 if state then 55 local add_user_command_handler = adhoc_simple(add_user_layout, function(fields, err)
52 if data.action == "cancel" then 56 if err then
53 return { status = "canceled" }; 57 return generate_error_message(err);
54 end 58 end
55 local fields, err = add_user_layout:data(data.form); 59 local username, host, resource = jid.split(fields.accountjid);
56 if err then 60 if module_host ~= host then
57 return generate_error_message(err); 61 return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. module_host}};
58 end 62 end
59 local username, host, resource = jid.split(fields.accountjid); 63 if (fields["password"] == fields["password-verify"]) and username and host then
60 if data.to ~= host then 64 if usermanager_user_exists(username, host) then
61 return { status = "completed", error = { message = "Trying to add a user on " .. host .. " but command was sent to " .. data.to}}; 65 return { status = "completed", error = { message = "Account already exists" } };
62 end 66 else
63 if (fields["password"] == fields["password-verify"]) and username and host then 67 if usermanager_create_user(username, fields.password, host) then
64 if usermanager_user_exists(username, host) then 68 module:log("info", "Created new account %s@%s", username, host);
65 return { status = "completed", error = { message = "Account already exists" } }; 69 return { status = "completed", info = "Account successfully created" };
66 else 70 else
67 if usermanager_create_user(username, fields.password, host) then 71 return { status = "completed", error = { message = "Failed to write data to disk" } };
68 module:log("info", "Created new account %s@%s", username, host);
69 return { status = "completed", info = "Account successfully created" };
70 else
71 return { status = "completed", error = { message = "Failed to write data to disk" } };
72 end
73 end 72 end
74 else 73 end
75 module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>"); 74 else
76 return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } }; 75 module:log("debug", "Invalid data, password mismatch or empty username while creating account for %s", fields.accountjid or "<nil>");
77 end 76 return { status = "completed", error = { message = "Invalid data.\nPassword mismatch, or empty username" } };
78 else 77 end
79 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = add_user_layout }, "executing"; 78 end);
80 end 79
81 end 80 -- Changing a user's password
82 81 local change_user_password_layout = dataforms_new{
83 function change_user_password_command_handler(self, data, state) 82 title = "Changing a User Password";
84 local change_user_password_layout = dataforms_new{ 83 instructions = "Fill out this form to change a user's password.";
85 title = "Changing a User Password"; 84
86 instructions = "Fill out this form to change a user's password."; 85 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
87 86 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" };
88 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 87 { name = "password", type = "text-private", required = true, label = "The password for this account" };
89 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for this account" }; 88 };
90 { name = "password", type = "text-private", required = true, label = "The password for this account" }; 89
91 }; 90 local change_user_password_command_handler = adhoc_simple(change_user_password_layout, function(fields, err)
92 91 if err then
93 if state then 92 return generate_error_message(err);
94 if data.action == "cancel" then 93 end
95 return { status = "canceled" }; 94 local username, host, resource = jid.split(fields.accountjid);
96 end 95 if module_host ~= host then
97 local fields, err = change_user_password_layout:data(data.form); 96 return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. module_host}};
98 if err then 97 end
99 return generate_error_message(err); 98 if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then
100 end 99 return { status = "completed", info = "Password successfully changed" };
101 local username, host, resource = jid.split(fields.accountjid); 100 else
102 if data.to ~= host then 101 return { status = "completed", error = { message = "User does not exist" } };
103 return { status = "completed", error = { message = "Trying to change the password of a user on " .. host .. " but command was sent to " .. data.to}}; 102 end
104 end 103 end);
105 if usermanager_user_exists(username, host) and usermanager_set_password(username, fields.password, host) then 104
106 return { status = "completed", info = "Password successfully changed" }; 105 -- Reloading the config
107 else 106 local function config_reload_handler(self, data, state)
108 return { status = "completed", error = { message = "User does not exist" } };
109 end
110 else
111 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = change_user_password_layout }, "executing";
112 end
113 end
114
115 function config_reload_handler(self, data, state)
116 local ok, err = prosody.reload_config(); 107 local ok, err = prosody.reload_config();
117 if ok then 108 if ok then
118 return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" }; 109 return { status = "completed", info = "Configuration reloaded (modules may need to be reloaded for this to have an effect)" };
119 else 110 else
120 return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } }; 111 return { status = "completed", error = { message = "Failed to reload config: " .. tostring(err) } };
121 end 112 end
122 end 113 end
123 114
124 115 -- Deleting a user's account
125 function delete_user_command_handler(self, data, state) 116 local delete_user_layout = dataforms_new{
126 local delete_user_layout = dataforms_new{ 117 title = "Deleting a User";
127 title = "Deleting a User"; 118 instructions = "Fill out this form to delete a user.";
128 instructions = "Fill out this form to delete a user."; 119
129 120 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
130 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 121 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" };
131 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) to delete" }; 122 };
132 }; 123
133 124 local delete_user_command_handler = adhoc_simple(delete_user_layout, function(fields, err)
134 if state then 125 if err then
135 if data.action == "cancel" then 126 return generate_error_message(err);
136 return { status = "canceled" }; 127 end
137 end 128 local failed = {};
138 local fields, err = delete_user_layout:data(data.form); 129 local succeeded = {};
139 if err then 130 for _, aJID in ipairs(fields.accountjids) do
140 return generate_error_message(err); 131 local username, host, resource = jid.split(aJID);
141 end 132 if (host == module_host) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then
142 local failed = {}; 133 module:log("debug", "User %s has been deleted", aJID);
143 local succeeded = {}; 134 succeeded[#succeeded+1] = aJID;
144 for _, aJID in ipairs(fields.accountjids) do 135 else
145 local username, host, resource = jid.split(aJID); 136 module:log("debug", "Tried to delete non-existant user %s", aJID);
146 if (host == data.to) and usermanager_user_exists(username, host) and usermanager_delete_user(username, host) then 137 failed[#failed+1] = aJID;
147 module:log("debug", "User %s has been deleted", aJID); 138 end
148 succeeded[#succeeded+1] = aJID; 139 end
149 else 140 return {status = "completed", info = (#succeeded ~= 0 and
150 module:log("debug", "Tried to delete non-existant user %s", aJID); 141 "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "")..
151 failed[#failed+1] = aJID; 142 (#failed ~= 0 and
152 end 143 "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
153 end 144 end);
154 return {status = "completed", info = (#succeeded ~= 0 and 145
155 "The following accounts were successfully deleted:\n"..t_concat(succeeded, "\n").."\n" or "").. 146 -- Ending a user's session
156 (#failed ~= 0 and 147 local function disconnect_user(match_jid)
157 "The following accounts could not be deleted:\n"..t_concat(failed, "\n") or "") };
158 else
159 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = delete_user_layout }, "executing";
160 end
161 end
162
163 function disconnect_user(match_jid)
164 local node, hostname, givenResource = jid.split(match_jid); 148 local node, hostname, givenResource = jid.split(match_jid);
165 local host = hosts[hostname]; 149 local host = hosts[hostname];
166 local sessions = host.sessions[node] and host.sessions[node].sessions; 150 local sessions = host.sessions[node] and host.sessions[node].sessions;
167 for resource, session in pairs(sessions or {}) do 151 for resource, session in pairs(sessions or {}) do
168 if not givenResource or (resource == givenResource) then 152 if not givenResource or (resource == givenResource) then
171 end 155 end
172 end 156 end
173 return true; 157 return true;
174 end 158 end
175 159
176 function end_user_session_handler(self, data, state) 160 local end_user_session_layout = dataforms_new{
177 local end_user_session_layout = dataforms_new{ 161 title = "Ending a User Session";
178 title = "Ending a User Session"; 162 instructions = "Fill out this form to end a user's session.";
179 instructions = "Fill out this form to end a user's session."; 163
180 164 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
181 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 165 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" };
182 { name = "accountjids", type = "jid-multi", label = "The Jabber ID(s) for which to end sessions" }; 166 };
183 }; 167
184 168 local end_user_session_handler = adhoc_simple(end_user_session_layout, function(fields, err)
185 if state then 169 if err then
186 if data.action == "cancel" then 170 return generate_error_message(err);
187 return { status = "canceled" }; 171 end
188 end 172 local failed = {};
189 173 local succeeded = {};
190 local fields, err = end_user_session_layout:data(data.form); 174 for _, aJID in ipairs(fields.accountjids) do
191 if err then 175 local username, host, resource = jid.split(aJID);
192 return generate_error_message(err); 176 if (host == module_host) and usermanager_user_exists(username, host) and disconnect_user(aJID) then
193 end 177 succeeded[#succeeded+1] = aJID;
194 local failed = {}; 178 else
195 local succeeded = {}; 179 failed[#failed+1] = aJID;
196 for _, aJID in ipairs(fields.accountjids) do 180 end
197 local username, host, resource = jid.split(aJID); 181 end
198 if (host == data.to) and usermanager_user_exists(username, host) and disconnect_user(aJID) then 182 return {status = "completed", info = (#succeeded ~= 0 and
199 succeeded[#succeeded+1] = aJID; 183 "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "")..
184 (#failed ~= 0 and
185 "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") };
186 end);
187
188 -- Getting a user's password
189 local get_user_password_layout = dataforms_new{
190 title = "Getting User's Password";
191 instructions = "Fill out this form to get a user's password.";
192
193 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
194 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" };
195 };
196
197 local get_user_password_result_layout = dataforms_new{
198 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
199 { name = "accountjid", type = "jid-single", label = "JID" };
200 { name = "password", type = "text-single", label = "Password" };
201 };
202
203 local get_user_password_handler = adhoc_simple(get_user_password_layout, function(fields, err)
204 if err then
205 return generate_error_message(err);
206 end
207 local user, host, resource = jid.split(fields.accountjid);
208 local accountjid = "";
209 local password = "";
210 if host ~= module_host then
211 return { status = "completed", error = { message = "Tried to get password for a user on " .. host .. " but command was sent to " .. module_host } };
212 elseif usermanager_user_exists(user, host) then
213 accountjid = fields.accountjid;
214 password = usermanager_get_password(user, host);
215 else
216 return { status = "completed", error = { message = "User does not exist" } };
217 end
218 return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } };
219 end);
220
221 -- Getting a user's roster
222 local get_user_roster_layout = dataforms_new{
223 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
224 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" };
225 };
226
227 local get_user_roster_result_layout = dataforms_new{
228 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
229 { name = "accountjid", type = "jid-single", label = "This is the roster for" };
230 { name = "roster", type = "text-multi", label = "Roster XML" };
231 };
232
233 local get_user_roster_handler = adhoc_simple(get_user_roster_layout, function(fields, err)
234 if err then
235 return generate_error_message(err);
236 end
237
238 local user, host, resource = jid.split(fields.accountjid);
239 if host ~= module_host then
240 return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. module_host } };
241 elseif not usermanager_user_exists(user, host) then
242 return { status = "completed", error = { message = "User does not exist" } };
243 end
244 local roster = rm_load_roster(user, host);
245
246 local query = st.stanza("query", { xmlns = "jabber:iq:roster" });
247 for jid in pairs(roster) do
248 if jid ~= "pending" and jid then
249 query:tag("item", {
250 jid = jid,
251 subscription = roster[jid].subscription,
252 ask = roster[jid].ask,
253 name = roster[jid].name,
254 });
255 for group in pairs(roster[jid].groups) do
256 query:tag("group"):text(group):up();
257 end
258 query:up();
259 end
260 end
261
262 local query_text = tostring(query):gsub("><", ">\n<");
263
264 local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result");
265 result:add_child(query);
266 return { status = "completed", other = result };
267 end);
268
269 -- Getting user statistics
270 local get_user_stats_layout = dataforms_new{
271 title = "Get User Statistics";
272 instructions = "Fill out this form to gather user statistics.";
273
274 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
275 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for statistics" };
276 };
277
278 local get_user_stats_result_layout = dataforms_new{
279 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
280 { name = "ipaddresses", type = "text-multi", label = "IP Addresses" };
281 { name = "rostersize", type = "text-single", label = "Roster size" };
282 { name = "onlineresources", type = "text-multi", label = "Online Resources" };
283 };
284
285 local get_user_stats_handler = adhoc_simple(get_user_stats_layout, function(fields, err)
286 if err then
287 return generate_error_message(err);
288 end
289
290 local user, host, resource = jid.split(fields.accountjid);
291 if host ~= module_host then
292 return { status = "completed", error = { message = "Tried to get stats for a user on " .. host .. " but command was sent to " .. module_host } };
293 elseif not usermanager_user_exists(user, host) then
294 return { status = "completed", error = { message = "User does not exist" } };
295 end
296 local roster = rm_load_roster(user, host);
297 local rostersize = 0;
298 local IPs = "";
299 local resources = "";
300 for jid in pairs(roster) do
301 if jid ~= "pending" and jid then
302 rostersize = rostersize + 1;
303 end
304 end
305 for resource, session in pairs((hosts[host].sessions[user] and hosts[host].sessions[user].sessions) or {}) do
306 resources = resources .. "\n" .. resource;
307 IPs = IPs .. "\n" .. session.ip;
308 end
309 return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
310 onlineresources = resources}} };
311 end);
312
313 -- Getting a list of online users
314 local get_online_users_layout = dataforms_new{
315 title = "Getting List of Online Users";
316 instructions = "How many users should be returned at most?";
317
318 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
319 { name = "max_items", type = "list-single", label = "Maximum number of users",
320 value = { "25", "50", "75", "100", "150", "200", "all" } };
321 { name = "details", type = "boolean", label = "Show details" };
322 };
323
324 local get_online_users_result_layout = dataforms_new{
325 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
326 { name = "onlineuserjids", type = "text-multi", label = "The list of all online users" };
327 };
328
329 local get_online_users_command_handler = adhoc_simple(get_online_users_layout, function(fields, err)
330 if err then
331 return generate_error_message(err);
332 end
333
334 local max_items = nil
335 if fields.max_items ~= "all" then
336 max_items = tonumber(fields.max_items);
337 end
338 local count = 0;
339 local users = {};
340 for username, user in pairs(hosts[module_host].sessions or {}) do
341 if (max_items ~= nil) and (count >= max_items) then
342 break;
343 end
344 users[#users+1] = username.."@"..module_host;
345 count = count + 1;
346 if fields.details then
347 for resource, session in pairs(user.sessions or {}) do
348 local status, priority = "unavailable", tostring(session.priority or "-");
349 if session.presence then
350 status = session.presence:child_with_name("show");
351 if status then
352 status = status:get_text() or "[invalid!]";
353 else
354 status = "available";
355 end
356 end
357 users[#users+1] = " - "..resource..": "..status.."("..priority..")";
358 end
359 end
360 end
361 return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
362 end);
363
364 -- Getting a list of loaded modules
365 local list_modules_result = dataforms_new {
366 title = "List of loaded modules";
367
368 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#list" };
369 { name = "modules", type = "text-multi", label = "The following modules are loaded:" };
370 };
371
372 local function list_modules_handler(self, data, state)
373 local modules = array.collect(keys(hosts[module_host].modules)):sort():concat("\n");
374 return { status = "completed", result = { layout = list_modules_result; values = { modules = modules } } };
375 end
376
377 -- Loading a module
378 local load_module_layout = dataforms_new {
379 title = "Load module";
380 instructions = "Specify the module to be loaded";
381
382 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#load" };
383 { name = "module", type = "text-single", required = true, label = "Module to be loaded:"};
384 };
385
386 local load_module_handler = adhoc_simple(load_module_layout, function(fields, err)
387 if err then
388 return generate_error_message(err);
389 end
390 if modulemanager.is_loaded(module_host, fields.module) then
391 return { status = "completed", info = "Module already loaded" };
392 end
393 local ok, err = modulemanager.load(module_host, fields.module);
394 if ok then
395 return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..module_host..'".' };
396 else
397 return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..module_host..
398 '". Error was: "'..tostring(err or "<unspecified>")..'"' } };
399 end
400 end);
401
402 -- Globally loading a module
403 local globally_load_module_layout = dataforms_new {
404 title = "Globally load module";
405 instructions = "Specify the module to be loaded on all hosts";
406
407 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-load" };
408 { name = "module", type = "text-single", required = true, label = "Module to globally load:"};
409 };
410
411 local globally_load_module_handler = adhoc_simple(globally_load_module_layout, function(fields, err)
412 local ok_list, err_list = {}, {};
413
414 if err then
415 return generate_error_message(err);
416 end
417
418 local ok, err = modulemanager.load(module_host, fields.module);
419 if ok then
420 ok_list[#ok_list + 1] = module_host;
421 else
422 err_list[#err_list + 1] = module_host .. " (Error: " .. tostring(err) .. ")";
423 end
424
425 -- Is this a global module?
426 if modulemanager.is_loaded("*", fields.module) and not modulemanager.is_loaded(module_host, fields.module) then
427 return { status = "completed", info = 'Global module '..fields.module..' loaded.' };
428 end
429
430 -- This is either a shared or "normal" module, load it on all other hosts
431 for host_name, host in pairs(hosts) do
432 if host_name ~= module_host and host.type == "local" then
433 local ok, err = modulemanager.load(host_name, fields.module);
434 if ok then
435 ok_list[#ok_list + 1] = host_name;
200 else 436 else
201 failed[#failed+1] = aJID; 437 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
202 end 438 end
203 end 439 end
204 return {status = "completed", info = (#succeeded ~= 0 and 440 end
205 "The following accounts were successfully disconnected:\n"..t_concat(succeeded, "\n").."\n" or "").. 441
206 (#failed ~= 0 and 442 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully loaded onto the hosts:\n"..t_concat(ok_list, "\n")) or "")
207 "The following accounts could not be disconnected:\n"..t_concat(failed, "\n") or "") }; 443 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
208 else 444 (#err_list > 0 and ("Failed to load the module "..fields.module.." onto the hosts:\n"..t_concat(err_list, "\n")) or "");
209 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = end_user_session_layout }, "executing"; 445 return { status = "completed", info = info };
210 end 446 end);
211 end 447
212 448 -- Reloading modules
213 function get_user_password_handler(self, data, state) 449 local reload_modules_layout = dataforms_new {
214 local get_user_password_layout = dataforms_new{ 450 title = "Reload modules";
215 title = "Getting User's Password"; 451 instructions = "Select the modules to be reloaded";
216 instructions = "Fill out this form to get a user's password."; 452
217 453 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#reload" };
218 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 454 { name = "modules", type = "list-multi", required = true, label = "Modules to be reloaded:"};
219 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the password" }; 455 };
220 }; 456
221 457 local reload_modules_handler = adhoc_initial(reload_modules_layout, function()
222 local get_user_password_result_layout = dataforms_new{ 458 return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
223 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 459 end, function(fields, err)
224 { name = "accountjid", type = "jid-single", label = "JID" }; 460 if err then
225 { name = "password", type = "text-single", label = "Password" }; 461 return generate_error_message(err);
226 }; 462 end
227 463 local ok_list, err_list = {}, {};
228 if state then 464 for _, module in ipairs(fields.modules) do
229 if data.action == "cancel" then 465 local ok, err = modulemanager.reload(module_host, module);
230 return { status = "canceled" }; 466 if ok then
231 end 467 ok_list[#ok_list + 1] = module;
232 local fields, err = get_user_password_layout:data(data.form);
233 if err then
234 return generate_error_message(err);
235 end
236 local user, host, resource = jid.split(fields.accountjid);
237 local accountjid = "";
238 local password = "";
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);
244 else 468 else
245 return { status = "completed", error = { message = "User does not exist" } }; 469 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
246 end 470 end
247 return { status = "completed", result = { layout = get_user_password_result_layout, values = {accountjid = accountjid, password = password} } }; 471 end
248 else 472 local info = (#ok_list > 0 and ("The following modules were successfully reloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
249 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_password_layout }, "executing"; 473 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
250 end 474 (#err_list > 0 and ("Failed to reload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
251 end 475 return { status = "completed", info = info };
252 476 end);
253 function get_user_roster_handler(self, data, state) 477
254 local get_user_roster_layout = dataforms_new{ 478 -- Globally reloading a module
255 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 479 local globally_reload_module_layout = dataforms_new {
256 { name = "accountjid", type = "jid-single", required = true, label = "The Jabber ID for which to retrieve the roster" }; 480 title = "Globally reload module";
257 }; 481 instructions = "Specify the module to reload on all hosts";
258 482
259 local get_user_roster_result_layout = dataforms_new{ 483 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-reload" };
260 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 484 { name = "module", type = "list-single", required = true, label = "Module to globally reload:"};
261 { name = "accountjid", type = "jid-single", label = "This is the roster for" }; 485 };
262 { name = "roster", type = "text-multi", label = "Roster XML" }; 486
263 }; 487 local globally_reload_module_handler = adhoc_initial(globally_reload_module_layout, function()
264 488 local loaded_modules = array(keys(modulemanager.get_modules("*")));
265 if state then 489 for _, host in pairs(hosts) do
266 if data.action == "cancel" then 490 loaded_modules:append(array(keys(host.modules)));
267 return { status = "canceled" }; 491 end
268 end 492 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
269 493 return { module = loaded_modules };
270 local fields, err = get_user_roster_layout:data(data.form); 494 end, function(fields, err)
271 495 local is_global = false;
272 if err then 496
273 return generate_error_message(err); 497 if err then
274 end 498 return generate_error_message(err);
275 499 end
276 local user, host, resource = jid.split(fields.accountjid); 500
277 if host ~= data.to then 501 if modulemanager.is_loaded("*", fields.module) then
278 return { status = "completed", error = { message = "Tried to get roster for a user on " .. host .. " but command was sent to " .. data.to } }; 502 local ok, err = modulemanager.reload("*", fields.module);
279 elseif not usermanager_user_exists(user, host) then 503 if not ok then
280 return { status = "completed", error = { message = "User does not exist" } }; 504 return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
281 end 505 end
282 local roster = rm_load_roster(user, host); 506 is_global = true;
283 507 end
284 local query = st.stanza("query", { xmlns = "jabber:iq:roster" }); 508
285 for jid in pairs(roster) do 509 local ok_list, err_list = {}, {};
286 if jid ~= "pending" and jid then 510 for host_name, host in pairs(hosts) do
287 query:tag("item", { 511 if modulemanager.is_loaded(host_name, fields.module) then
288 jid = jid, 512 local ok, err = modulemanager.reload(host_name, fields.module);
289 subscription = roster[jid].subscription, 513 if ok then
290 ask = roster[jid].ask, 514 ok_list[#ok_list + 1] = host_name;
291 name = roster[jid].name, 515 else
292 }); 516 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
293 for group in pairs(roster[jid].groups) do
294 query:tag("group"):text(group):up();
295 end
296 query:up();
297 end 517 end
298 end 518 end
299 519 end
300 local query_text = tostring(query):gsub("><", ">\n<"); 520
301 521 if #ok_list == 0 and #err_list == 0 then
302 local result = get_user_roster_result_layout:form({ accountjid = user.."@"..host, roster = query_text }, "result"); 522 if is_global then
303 result:add_child(query); 523 return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
304 return { status = "completed", other = result };
305 else
306 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_roster_layout }, "executing";
307 end
308 end
309
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.";
314
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" };
317 };
318
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" };
324 };
325
326 if state then
327 if data.action == "cancel" then
328 return { status = "canceled" };
329 end
330
331 local fields, err = get_user_stats_layout:data(data.form);
332
333 if err then
334 return generate_error_message(err);
335 end
336
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" } };
342 end
343 local roster = rm_load_roster(user, host);
344 local rostersize = 0;
345 local IPs = "";
346 local resources = "";
347 for jid in pairs(roster) do
348 if jid ~= "pending" and jid then
349 rostersize = rostersize + 1;
350 end
351 end
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;
355 end
356 return { status = "completed", result = {layout = get_user_stats_result_layout, values = {ipaddresses = IPs, rostersize = tostring(rostersize),
357 onlineresources = resources}} };
358 else
359 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_user_stats_layout }, "executing";
360 end
361 end
362
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?";
367
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" };
372 };
373
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" };
377 };
378
379 if state then
380 if data.action == "cancel" then
381 return { status = "canceled" };
382 end
383
384 local fields, err = get_online_users_layout:data(data.form);
385
386 if err then
387 return generate_error_message(err);
388 end
389
390 local max_items = nil
391 if fields.max_items ~= "all" then
392 max_items = tonumber(fields.max_items);
393 end
394 local count = 0;
395 local users = {};
396 for username, user in pairs(hosts[data.to].sessions or {}) do
397 if (max_items ~= nil) and (count >= max_items) then
398 break;
399 end
400 users[#users+1] = username.."@"..data.to;
401 count = count + 1;
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");
407 if status then
408 status = status:get_text() or "[invalid!]";
409 else
410 status = "available";
411 end
412 end
413 users[#users+1] = " - "..resource..": "..status.."("..priority..")";
414 end
415 end
416 end
417 return { status = "completed", result = {layout = get_online_users_result_layout, values = {onlineuserjids=t_concat(users, "\n")}} };
418 else
419 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = get_online_users_layout }, "executing";
420 end
421 end
422
423 function list_modules_handler(self, data, state)
424 local result = dataforms_new {
425 title = "List of loaded modules";
426
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:" };
429 };
430
431 local modules = array.collect(keys(hosts[data.to].modules)):sort():concat("\n");
432
433 return { status = "completed", result = { layout = result; values = { modules = modules } } };
434 end
435
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";
440
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:"};
443 };
444 if state then
445 if data.action == "cancel" then
446 return { status = "canceled" };
447 end
448 local fields, err = layout:data(data.form);
449 if err then
450 return generate_error_message(err);
451 end
452 if modulemanager.is_loaded(data.to, fields.module) then
453 return { status = "completed", info = "Module already loaded" };
454 end
455 local ok, err = modulemanager.load(data.to, fields.module);
456 if ok then
457 return { status = "completed", info = 'Module "'..fields.module..'" successfully loaded on host "'..data.to..'".' };
458 else 524 else
459 return { status = "completed", error = { message = 'Failed to load module "'..fields.module..'" on host "'..data.to.. 525 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
460 '". Error was: "'..tostring(err or "<unspecified>")..'"' } }; 526 end
461 end 527 end
462 else 528
463 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing"; 529 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully reloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
464 end 530 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
465 end 531 (#err_list > 0 and ("Failed to reload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
466 532 return { status = "completed", info = info };
467 local function globally_load_module_handler(self, data, state) 533 end);
468 local layout = dataforms_new { 534
469 title = "Globally load module"; 535 local function send_to_online(message, server)
470 instructions = "Specify the module to be loaded on all hosts";
471
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:"};
474 };
475 if state then
476 local ok_list, err_list = {}, {};
477
478 if data.action == "cancel" then
479 return { status = "canceled" };
480 end
481
482 local fields, err = layout:data(data.form);
483 if err then
484 return generate_error_message(err);
485 end
486
487 local ok, err = modulemanager.load(data.to, fields.module);
488 if ok then
489 ok_list[#ok_list + 1] = data.to;
490 else
491 err_list[#err_list + 1] = data.to .. " (Error: " .. tostring(err) .. ")";
492 end
493
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.' };
497 end
498
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);
503 if ok then
504 ok_list[#ok_list + 1] = host_name;
505 else
506 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
507 end
508 end
509 end
510
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 };
515 else
516 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = layout }, "executing";
517 end
518 end
519
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";
524
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:"};
527 };
528 if state then
529 if data.action == "cancel" then
530 return { status = "canceled" };
531 end
532 local fields, err = layout:data(data.form);
533 if err then
534 return generate_error_message(err);
535 end
536 local ok_list, err_list = {}, {};
537 for _, module in ipairs(fields.modules) do
538 local ok, err = modulemanager.reload(data.to, module);
539 if ok then
540 ok_list[#ok_list + 1] = module;
541 else
542 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
543 end
544 end
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 };
549 else
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";
552 end
553 end
554
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";
559
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:"};
562 };
563 if state then
564 if data.action == "cancel" then
565 return { status = "canceled" };
566 end
567
568 local is_global = false;
569 local fields, err = layout:data(data.form);
570 if err then
571 return generate_error_message(err);
572 end
573
574 if modulemanager.is_loaded("*", fields.module) then
575 local ok, err = modulemanager.reload("*", fields.module);
576 if not ok then
577 return { status = "completed", info = 'Global module '..fields.module..' failed to reload: '..err };
578 end
579 is_global = true;
580 end
581
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);
586 if ok then
587 ok_list[#ok_list + 1] = host_name;
588 else
589 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
590 end
591 end
592 end
593
594 if #ok_list == 0 and #err_list == 0 then
595 if is_global then
596 return { status = "completed", info = 'Successfully reloaded global module '..fields.module };
597 else
598 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
599 end
600 end
601
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 };
606 else
607 local loaded_modules = array(keys(modulemanager.get_modules("*")));
608 for _, host in pairs(hosts) do
609 loaded_modules:append(array(keys(host.modules)));
610 end
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";
613 end
614 end
615
616 function send_to_online(message, server)
617 if server then 536 if server then
618 sessions = { [server] = hosts[server] }; 537 sessions = { [server] = hosts[server] };
619 else 538 else
620 sessions = hosts; 539 sessions = hosts;
621 end 540 end
631 end 550 end
632 551
633 return c; 552 return c;
634 end 553 end
635 554
636 function shut_down_service_handler(self, data, state) 555 -- Shutting down the service
637 local shut_down_service_layout = dataforms_new{ 556 local shut_down_service_layout = dataforms_new{
638 title = "Shutting Down the Service"; 557 title = "Shutting Down the Service";
639 instructions = "Fill out this form to shut down the service."; 558 instructions = "Fill out this form to shut down the service.";
640 559
641 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" }; 560 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
642 { name = "delay", type = "list-single", label = "Time delay before shutting down", 561 { name = "delay", type = "list-single", label = "Time delay before shutting down",
643 value = { {label = "30 seconds", value = "30"}, 562 value = { {label = "30 seconds", value = "30"},
644 {label = "60 seconds", value = "60"}, 563 {label = "60 seconds", value = "60"},
645 {label = "90 seconds", value = "90"}, 564 {label = "90 seconds", value = "90"},
646 {label = "2 minutes", value = "120"}, 565 {label = "2 minutes", value = "120"},
647 {label = "3 minutes", value = "180"}, 566 {label = "3 minutes", value = "180"},
648 {label = "4 minutes", value = "240"}, 567 {label = "4 minutes", value = "240"},
649 {label = "5 minutes", value = "300"}, 568 {label = "5 minutes", value = "300"},
650 };
651 }; 569 };
652 { name = "announcement", type = "text-multi", label = "Announcement" };
653 }; 570 };
654 571 { name = "announcement", type = "text-multi", label = "Announcement" };
655 if state then 572 };
656 if data.action == "cancel" then 573
657 return { status = "canceled" }; 574 local shut_down_service_handler = adhoc_simple(shut_down_service_layout, function(fields, err)
658 end 575 if err then
659 576 return generate_error_message(err);
660 local fields, err = shut_down_service_layout:data(data.form); 577 end
661 578
662 if err then 579 if fields.announcement and #fields.announcement > 0 then
663 return generate_error_message(err); 580 local message = st.message({type = "headline"}, fields.announcement):up()
664 end 581 :tag("subject"):text("Server is shutting down");
665 582 send_to_online(message);
666 if fields.announcement and #fields.announcement > 0 then 583 end
667 local message = st.message({type = "headline"}, fields.announcement):up() 584
668 :tag("subject"):text("Server is shutting down"); 585 timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end);
669 send_to_online(message); 586
670 end 587 return { status = "completed", info = "Server is about to shut down" };
671 588 end);
672 timer_add_task(tonumber(fields.delay or "5"), function(time) prosody.shutdown("Shutdown by adhoc command") end); 589
673 590 -- Unloading modules
674 return { status = "completed", info = "Server is about to shut down" }; 591 local unload_modules_layout = dataforms_new {
675 else 592 title = "Unload modules";
676 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = shut_down_service_layout }, "executing"; 593 instructions = "Select the modules to be unloaded";
677 end 594
678 end 595 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" };
679 596 { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"};
680 function unload_modules_handler(self, data, state) 597 };
681 local layout = dataforms_new { 598
682 title = "Unload modules"; 599 local unload_modules_handler = adhoc_initial(unload_modules_layout, function()
683 instructions = "Select the modules to be unloaded"; 600 return { modules = array.collect(keys(hosts[module_host].modules)):sort() };
684 601 end, function(fields, err)
685 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#unload" }; 602 if err then
686 { name = "modules", type = "list-multi", required = true, label = "Modules to be unloaded:"}; 603 return generate_error_message(err);
687 }; 604 end
688 if state then 605 local ok_list, err_list = {}, {};
689 if data.action == "cancel" then 606 for _, module in ipairs(fields.modules) do
690 return { status = "canceled" }; 607 local ok, err = modulemanager.unload(module_host, module);
691 end 608 if ok then
692 local fields, err = layout:data(data.form); 609 ok_list[#ok_list + 1] = module;
693 if err then 610 else
694 return generate_error_message(err); 611 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")";
695 end 612 end
696 local ok_list, err_list = {}, {}; 613 end
697 for _, module in ipairs(fields.modules) do 614 local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..module_host..":\n"..t_concat(ok_list, "\n")) or "")
698 local ok, err = modulemanager.unload(data.to, module); 615 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
616 (#err_list > 0 and ("Failed to unload the following modules on host "..module_host..":\n"..t_concat(err_list, "\n")) or "");
617 return { status = "completed", info = info };
618 end);
619
620 -- Globally unloading a module
621 local globally_unload_module_layout = dataforms_new {
622 title = "Globally unload module";
623 instructions = "Specify a module to unload on all hosts";
624
625 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/modules#global-unload" };
626 { name = "module", type = "list-single", required = true, label = "Module to globally unload:"};
627 };
628
629 local globally_unload_module_handler = adhoc_initial(globally_unload_module_layout, function()
630 local loaded_modules = array(keys(modulemanager.get_modules("*")));
631 for _, host in pairs(hosts) do
632 loaded_modules:append(array(keys(host.modules)));
633 end
634 loaded_modules = array(keys(set.new(loaded_modules):items())):sort();
635 return { module = loaded_modules };
636 end, function(fields, err)
637 local is_global = false;
638 if err then
639 return generate_error_message(err);
640 end
641
642 if modulemanager.is_loaded("*", fields.module) then
643 local ok, err = modulemanager.unload("*", fields.module);
644 if not ok then
645 return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
646 end
647 is_global = true;
648 end
649
650 local ok_list, err_list = {}, {};
651 for host_name, host in pairs(hosts) do
652 if modulemanager.is_loaded(host_name, fields.module) then
653 local ok, err = modulemanager.unload(host_name, fields.module);
699 if ok then 654 if ok then
700 ok_list[#ok_list + 1] = module; 655 ok_list[#ok_list + 1] = host_name;
701 else 656 else
702 err_list[#err_list + 1] = module .. "(Error: " .. tostring(err) .. ")"; 657 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
703 end 658 end
704 end 659 end
705 local info = (#ok_list > 0 and ("The following modules were successfully unloaded on host "..data.to..":\n"..t_concat(ok_list, "\n")) or "") 660 end
706 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") .. 661
707 (#err_list > 0 and ("Failed to unload the following modules on host "..data.to..":\n"..t_concat(err_list, "\n")) or ""); 662 if #ok_list == 0 and #err_list == 0 then
708 return { status = "completed", info = info }; 663 if is_global then
709 else 664 return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
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";
712 end
713 end
714
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";
719
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:"};
722 };
723 if state then
724 if data.action == "cancel" then
725 return { status = "canceled" };
726 end
727
728 local is_global = false;
729 local fields, err = layout:data(data.form);
730 if err then
731 return generate_error_message(err);
732 end
733
734 if modulemanager.is_loaded("*", fields.module) then
735 local ok, err = modulemanager.unload("*", fields.module);
736 if not ok then
737 return { status = "completed", info = 'Global module '..fields.module..' failed to unload: '..err };
738 end
739 is_global = true;
740 end
741
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);
746 if ok then
747 ok_list[#ok_list + 1] = host_name;
748 else
749 err_list[#err_list + 1] = host_name .. " (Error: " .. tostring(err) .. ")";
750 end
751 end
752 end
753
754 if #ok_list == 0 and #err_list == 0 then
755 if is_global then
756 return { status = "completed", info = 'Successfully unloaded global module '..fields.module };
757 else
758 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
759 end
760 end
761
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 };
766 else
767 local loaded_modules = array(keys(modulemanager.get_modules("*")));
768 for _, host in pairs(hosts) do
769 loaded_modules:append(array(keys(host.modules)));
770 end
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";
773 end
774 end
775
776
777 function activate_host_handler(self, data, state)
778 local layout = dataforms_new {
779 title = "Activate host";
780 instructions = "";
781
782 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
783 { name = "host", type = "text-single", required = true, label = "Host:"};
784 };
785 if state then
786 if data.action == "cancel" then
787 return { status = "canceled" };
788 end
789 local fields, err = layout:data(data.form);
790 if err then
791 return generate_error_message(err);
792 end
793 local ok, err = hostmanager_activate(fields.host);
794
795 if ok then
796 return { status = "completed", info = fields.host .. " activated" };
797 else 665 else
798 return { status = "canceled", error = err } 666 return { status = "completed", info = 'Module '..fields.module..' not loaded on any host.' };
799 end 667 end
800 else 668 end
801 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing"; 669
802 end 670 local info = (#ok_list > 0 and ("The module "..fields.module.." was successfully unloaded on the hosts:\n"..t_concat(ok_list, "\n")) or "")
803 end 671 .. ((#ok_list > 0 and #err_list > 0) and "\n" or "") ..
804 672 (#err_list > 0 and ("Failed to unload the module "..fields.module.." on the hosts:\n"..t_concat(err_list, "\n")) or "");
805 function deactivate_host_handler(self, data, state) 673 return { status = "completed", info = info };
806 local layout = dataforms_new { 674 end);
807 title = "Deactivate host"; 675
808 instructions = ""; 676 -- Activating a host
809 677 local activate_host_layout = dataforms_new {
810 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" }; 678 title = "Activate host";
811 { name = "host", type = "text-single", required = true, label = "Host:"}; 679 instructions = "";
812 }; 680
813 if state then 681 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
814 if data.action == "cancel" then 682 { name = "host", type = "text-single", required = true, label = "Host:"};
815 return { status = "canceled" }; 683 };
816 end 684
817 local fields, err = layout:data(data.form); 685 local activate_host_handler = adhoc_simple(activate_host_layout, function(fields, err)
818 if err then 686 if err then
819 return generate_error_message(err); 687 return generate_error_message(err);
820 end 688 end
821 local ok, err = hostmanager_deactivate(fields.host); 689 local ok, err = hostmanager_activate(fields.host);
822 690
823 if ok then 691 if ok then
824 return { status = "completed", info = fields.host .. " deactivated" }; 692 return { status = "completed", info = fields.host .. " activated" };
825 else 693 else
826 return { status = "canceled", error = err } 694 return { status = "canceled", error = err }
827 end 695 end
828 else 696 end);
829 return { status = "executing", actions = {"next", "complete", default = "complete"}, form = { layout = layout } }, "executing"; 697
830 end 698 -- Deactivating a host
831 end 699 local deactivate_host_layout = dataforms_new {
700 title = "Deactivate host";
701 instructions = "";
702
703 { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/hosts#activate" };
704 { name = "host", type = "text-single", required = true, label = "Host:"};
705 };
706
707 local deactivate_host_handler = adhoc_simple(deactivate_host_layout, function(fields, err)
708 if err then
709 return generate_error_message(err);
710 end
711 local ok, err = hostmanager_deactivate(fields.host);
712
713 if ok then
714 return { status = "completed", info = fields.host .. " deactivated" };
715 else
716 return { status = "canceled", error = err }
717 end
718 end);
832 719
833 720
834 local add_user_desc = adhoc_new("Add User", "http://jabber.org/protocol/admin#add-user", add_user_command_handler, "admin"); 721 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"); 722 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"); 723 local config_reload_desc = adhoc_new("Reload configuration", "http://prosody.im/protocol/config#reload", config_reload_handler, "global_admin");