diff options
author | Simon Rettberg | 2017-04-12 12:30:51 +0200 |
---|---|---|
committer | Simon Rettberg | 2017-04-12 12:30:51 +0200 |
commit | 5a922e7d360f7aa2ea3c4a84a5610940f06cd037 (patch) | |
tree | d4a78c213380c58219a1d545bb2606a7e9bab1ca /modules-available | |
parent | [statistics_reporting] Send backlogged reports in cronjob (diff) | |
download | slx-admin-5a922e7d360f7aa2ea3c4a84a5610940f06cd037.tar.gz slx-admin-5a922e7d360f7aa2ea3c4a84a5610940f06cd037.tar.xz slx-admin-5a922e7d360f7aa2ea3c4a84a5610940f06cd037.zip |
[rebootcontrol] New module for shutting down and rebooting clients
Squashed commit of the following:
commit 00a9af598b949dec748f2b6ddb42f124f2352451
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Wed Apr 12 12:28:18 2017 +0200
[rebootcontrol] Enforce POST for keypair regen triggering
commit fb5da4d50c364ec53086b7da7d9f9f094ac0b87e
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Wed Apr 12 12:00:59 2017 +0200
[rebootcontrol] Resolve lecture uuids in client list
commit 0fc97f2c7b4a085efcd86262afe351784d4a6743
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Wed Apr 12 11:43:17 2017 +0200
[rebootcontrol] Support REBOOT_AT state
commit 8383db423d5ef86da49d0ed1ae719254bcd65d79
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Tue Apr 11 15:15:20 2017 +0200
[rebootcontrol] Add REBOOT_AT translation
commit d19b59015f383f04369fb89ac67abae4872c3d19
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Mon Apr 10 15:58:55 2017 +0200
[rebootcontrol] Remove redundant location queries; improve pages a bit
Rows are now marked using the designated active class,
a client can be selected clicking anywhere in the last column.
commit 674053e18b9e800f113471c19050ab468f73f233
Merge: 2210fbb 3dedcae
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Mon Apr 10 11:32:55 2017 +0200
Merge branch 'master' into reboot-control
commit 2210fbb4947643988adef716594d79cf58482b87
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Sat Apr 8 11:22:44 2017 +0200
[rebootcontrol] Adapt to changed task structure; handle clients by UUID
commit 0ca01982028e08aa99e896d47c669ec77ec7dada
Author: Simon Rettberg <rettberg@informatik.uni-freiburg.de>
Date: Fri Apr 7 15:45:36 2017 +0200
[rebootcontrol] Fix config.tgz hook
commit 0bb4ae9291452112895ec3f3703bc868ee8d80d2
Author: Simon Rettberg <spam@aol.com>
Date: Tue Mar 14 19:54:53 2017 +0100
[rebootcontrol] Fix race condition when multiple config.tgz are generated
Since the same file got created and deleted in a rapid succession, this
could lead to the RecompressArchive task failing with a "file not found"
error.
commit 74e735de601961620fd65500c3f2375af16d61fa
Author: Udo Walter <you@example.com>
Date: Mon Mar 13 12:32:36 2017 +0100
[reboot-control] added status column
commit 06d52b3c106d9db37b877297531da0f36452b81b
Author: Udo Walter <you@example.com>
Date: Thu Mar 9 13:15:05 2017 +0100
[reboot-control] added config-tgz hook to include the ssh key
commit 83abeda07ab995b7439011829fb98caf94b5c63e
Author: Christian Hofmaier <you@example.com>
Date: Mon Mar 6 16:58:14 2017 +0100
[reboot-control] made sortable by checkbox column
commit 207c78e4431689eb3e2944f6a3657522806abf89
Merge: 712a9a5 deaa6b2
Author: Udo Walter <you@example.com>
Date: Mon Mar 6 15:41:37 2017 +0100
Merge remote-tracking branch 'origin/master' into reboot-control
commit 712a9a5f2d51e3a866e31df94722919279ed7fc2
Author: Udo Walter <you@example.com>
Date: Mon Mar 6 15:36:59 2017 +0100
[reboot-control] added possibility to generate a new ssh public private keypair
commit d40de6044d4d63b4c25a1a950c4a126c6becc1ca
Author: Christian Hofmaier <you@example.com>
Date: Thu Mar 2 15:02:12 2017 +0100
[reboot-control] disabled possibility to reboot/shutdown if no clients selected
commit d008eb6adb486857b898609e1e89adf4473be50c
Merge: 5259dd5 7b198f9
Author: Udo Walter <you@example.com>
Date: Thu Mar 2 14:42:28 2017 +0100
Merge remote-tracking branch 'origin/master' into reboot-control
commit 5259dd5957a328fe4b12c9cf0e47f7c90c9d8bed
Merge: 3c81c7e 1ed086a
Author: Udo Walter <you@example.com>
Date: Thu Mar 2 14:30:43 2017 +0100
Merge branch 'reboot-control' of git.openslx.org:openslx-ng/slx-admin into reboot-control
commit 3c81c7eb35b878c01e1d4b70000dab6c76559012
Author: Christian Hofmaier <you@example.com>
Date: Thu Mar 2 13:49:09 2017 +0100
[reboot-control] bugfix @ locationid=none
commit 1925d0469626ee7cf4dd7baa9c05274c1d243c7c
Author: Christian Hofmaier <you@example.com>
Date: Thu Mar 2 13:40:11 2017 +0100
[reboot-control] if no clients are selected, redirect back, not to status page
commit 0cd8ec5787baac16f4169b11cf9ce46733baa124
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 27 18:11:09 2017 +0100
[reboot-control] second (last) part javascript revision
commit 1133a5c8b488c8381762e727dbeab31a1860e1eb
Author: Christian Hofmaier <you@example.com>
Date: Thu Feb 16 16:10:29 2017 +0100
[reboot-control] first small part javascript revision
commit ace5ab7e45b232a54299d9a3c41c60fa839dfbc1
Author: Udo Walter <you@example.com>
Date: Tue Feb 14 15:44:20 2017 +0100
[reboot-control] pass ssh private key to the reboot task
commit 5df5cb3b8ae35d56eefe94e8557aa13cb1178391
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 14 14:36:26 2017 +0100
[reboot-control] added comments
commit ff3cbfbb8100071bc748ce15e4c37b1d028cbb0d
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 14 14:14:01 2017 +0100
[reboot-control] added api
commit afce01b6f629b20e195a6e6ee4cd581d7ef8c079
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:54:45 2017 +0100
[reboot-control] fixed warning
commit ff7c072d1462ed23de8441ebdb06d6435606a2b1
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:27:56 2017 +0100
[reboot-control] small css change
commit 55195c89b7846c25d246badf96863bd24a9b8d2d
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:23:51 2017 +0100
[reboot-control] deleted slider, using textbox now for shutdown-timer
commit 23864e7e658cd0e061875c5c353497c92ab77bc9
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 17:17:39 2017 +0100
[reboot-control] fixed typo. (extremely) small design change.
commit 821501e290abd89c8ce78e764a997655779a71b1
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 16:54:55 2017 +0100
[reboot-control] added glyphicons to the buttons
commit be5f54a29cde1b2de7577b022ec97032104d8437
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:52:09 2017 +0100
[reboot-control] fixed bugs caused by renaming the module
commit 89bb0068d2a232c9a2dc4ebf23e84f626db6e1a9
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:21:52 2017 +0100
[reboot-control] fixed class name
commit 60d080b30e02cf42395e1fe1b4ca7df11d4e3ff1
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:11:15 2017 +0100
[reboot-control] renamed reboot_control to rebootcontrol
commit dfc9a48705501c73c611674b0c7f2bfa3178a38c
Author: Udo Walter <you@example.com>
Date: Wed Feb 8 17:28:14 2017 +0100
[reboot-control] added status page and reorganized status update
commit f7850d060dd32682ddc6795b2836ba45e9e6fe44
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 7 15:58:48 2017 +0100
[reboot-control] variable language consistency
commit 24757e1e2395ae70b8e06f9c72a6b3b1050895b3
Author: Udo Walter <you@example.com>
Date: Fri Feb 3 17:52:53 2017 +0100
[reboot-control] improved (un)select all button
commit f611bd2b594f0e0ffe8f81dfe0260758f86419ec
Author: Christian Hofmaier <you@example.com>
Date: Fri Feb 3 17:27:02 2017 +0100
[reboot-control] removed warning
commit 1e58de43049dd6e2fa0ce0d32df0b1ed2e90cd63
Author: Christian Hofmaier <you@example.com>
Date: Fri Feb 3 16:56:53 2017 +0100
[reboot-control] added stylesheet, Show hand-cursor for sortable columns, rows are now marked when chosen
commit 6661568437cb36328b346730a3e0d5dc57e85cfc
Author: Udo Walter <you@example.com>
Date: Tue Jan 31 16:06:53 2017 +0100
[reboot-control] added option to shutdown clients in future (server-side)
commit 4a0aa0bc53508206ff473aa9589c5160db3f85d5
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 31 15:13:58 2017 +0100
[reboot-control] added option to shutdown clients in future
commit 0411d3f2a19d639229dba6a58bf5dbaccac607b3
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 31 13:06:28 2017 +0100
[reboot-control] added shutdown option/button
commit 9155820fd6ebceae70d98e23479358229457868d
Author: Udo Walter <you@example.com>
Date: Fri Jan 27 13:06:06 2017 +0100
[reboot-control] added status translation
commit 2715df67d51f07a7f80cb02759b1726fd8b0ddd3
Author: Christian Hofmaier <you@example.com>
Date: Fri Jan 27 13:01:18 2017 +0100
[reboot-control] added table sorting (based on stupidtable plugin)
commit 959fb45b039db04af759efe96d6c9b8eafda6227
Author: Udo Walter <you@example.com>
Date: Fri Jan 27 11:58:09 2017 +0100
[reboot-control] added reboot functionality
commit b54f970b8e229a976ac9938626bce1b065f741a8
Author: Christian Hofmaier <you@example.com>
Date: Fri Jan 27 11:28:07 2017 +0100
[reboot-control] reboot buttons, style changes
commit 4a5a933a91fad7a936790d57c5eea42d0f53f614
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 24 16:20:29 2017 +0100
[reboot-control] warningfix
commit f9a6212eca2bbb9902827c8824d853f042ffa4f1
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 24 16:19:30 2017 +0100
[reboot-control] user interface
commit 59e425ac93dfa181891cac92a435ef34972e90a7
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 17 16:05:12 2017 +0100
[reboot-control] created branch and empty files to work with
commit 1ed086a17b248016882383a99c85356ee33342b2
Author: Christian Hofmaier <you@example.com>
Date: Thu Mar 2 13:49:09 2017 +0100
[reboot-control] bugfix @ locationid=none
commit ee36e147133d8b1a8ae3fe406677f3483bd1db02
Author: Christian Hofmaier <you@example.com>
Date: Thu Mar 2 13:40:11 2017 +0100
[reboot-control] if no clients are selected, redirect back, not to status page
commit 3c6d7946fd4e9e10649d8d41ab89bc90f83112ff
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 27 18:11:09 2017 +0100
[reboot-control] second (last) part javascript revision
commit 550aa8a776cb1a328693f1fee5722f10c1ff07b8
Author: Christian Hofmaier <you@example.com>
Date: Thu Feb 16 16:10:29 2017 +0100
[reboot-control] first small part javascript revision
commit 67f851c63fd937ffca2f8a1e6174982591cded2a
Merge: e1701e0 a7b1a42
Author: Udo Walter <you@example.com>
Date: Tue Feb 14 15:46:05 2017 +0100
Merge remote-tracking branch 'origin/reboot-control' into reboot-control
commit e1701e01065131777f8643b015b32832f21e864c
Author: Udo Walter <you@example.com>
Date: Tue Feb 14 15:44:20 2017 +0100
[reboot-control] pass ssh private key to the reboot task
commit a7b1a42a00742b995a7ef6a8d28fa661966ec5be
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 14 14:36:26 2017 +0100
[reboot-control] added comments
commit 7af992b2fcee946b04a1fdea4e7cc28b70436bf8
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 14 14:14:01 2017 +0100
[reboot-control] added api
commit 57beae14c82cd1a39a6b266963c78d81aa50d494
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:54:45 2017 +0100
[reboot-control] fixed warning
commit 1a4842c22d7f8e85033f793bd801e2d82ec351c4
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:27:56 2017 +0100
[reboot-control] small css change
commit da57d7491ad17a6d64be0c4624472ffa80efb43a
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 18:23:51 2017 +0100
[reboot-control] deleted slider, using textbox now for shutdown-timer
commit a1b16068d4f56223587084897b7c6ca5a4c54805
Author: Christian Hofmaier <you@example.com>
Date: Mon Feb 13 17:17:39 2017 +0100
[reboot-control] fixed typo. (extremely) small design change.
commit a84f2f50eb4e0d6853020212f162780e23f0a696
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 16:54:55 2017 +0100
[reboot-control] added glyphicons to the buttons
commit 06f52dfc9518ffda317cea2dab5cf16dbe66524c
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:52:09 2017 +0100
[reboot-control] fixed bugs caused by renaming the module
commit e5edd1b1d4291ac403587e938200c24b3bcebb22
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:21:52 2017 +0100
[reboot-control] fixed class name
commit 1c2b3afc26dec0d9fefb7692b4670e80cd16362a
Author: Udo Walter <you@example.com>
Date: Thu Feb 9 15:11:15 2017 +0100
[reboot-control] renamed reboot_control to rebootcontrol
commit f2e8624ecf9858276f63be10c279633fd45bf9d9
Author: Udo Walter <you@example.com>
Date: Wed Feb 8 17:28:14 2017 +0100
[reboot-control] added status page and reorganized status update
commit 0d918fead580748f5a3cb1db6ab754e34c844192
Author: Christian Hofmaier <you@example.com>
Date: Tue Feb 7 15:58:48 2017 +0100
[reboot-control] variable language consistency
commit 54fe25485bd1f09d332f50b3d0645b166e8012d7
Author: Udo Walter <you@example.com>
Date: Fri Feb 3 17:52:53 2017 +0100
[reboot-control] improved (un)select all button
commit 852261f194eb235e68f7256d3d526f5ae3936e13
Author: Christian Hofmaier <you@example.com>
Date: Fri Feb 3 17:27:02 2017 +0100
[reboot-control] removed warning
commit 63bbeb4f684eeb8e36a462cda76b3534f96a99e6
Author: Christian Hofmaier <you@example.com>
Date: Fri Feb 3 16:56:53 2017 +0100
[reboot-control] added stylesheet, Show hand-cursor for sortable columns, rows are now marked when chosen
commit d42b680196e727a0a67e5aaa954ba3a723c300f7
Author: Udo Walter <you@example.com>
Date: Tue Jan 31 16:06:53 2017 +0100
[reboot-control] added option to shutdown clients in future (server-side)
commit 49f8d204439d091970911d1528f9d3b839442d27
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 31 15:13:58 2017 +0100
[reboot-control] added option to shutdown clients in future
commit 666b61b9d5786ffedc01dc4a20285dd607b443ba
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 31 13:06:28 2017 +0100
[reboot-control] added shutdown option/button
commit d866d5e36fe7f5ef6f6fd7a9bd661b471a8b040d
Author: Udo Walter <you@example.com>
Date: Fri Jan 27 13:06:06 2017 +0100
[reboot-control] added status translation
commit d56d8361c4113fc9ccaf575b372ea4c7ec0d4123
Author: Christian Hofmaier <you@example.com>
Date: Fri Jan 27 13:01:18 2017 +0100
[reboot-control] added table sorting (based on stupidtable plugin)
commit bac9bcbd84f3fc6d6b593e5ae4113b23f538ff6c
Author: Udo Walter <you@example.com>
Date: Fri Jan 27 11:58:09 2017 +0100
[reboot-control] added reboot functionality
commit adfc00ebb64c8001927f5d60459c71b5da1b2dca
Author: Christian Hofmaier <you@example.com>
Date: Fri Jan 27 11:28:07 2017 +0100
[reboot-control] reboot buttons, style changes
commit 3eac6c54c9999f810f17c05c460d086ce30fd6d8
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 24 16:20:29 2017 +0100
[reboot-control] warningfix
commit 00f90f2d27593a18108e89daad48a99d5c150dea
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 24 16:19:30 2017 +0100
[reboot-control] user interface
commit b576fc14ca48f47793c2cea2a701ae22cd3110a6
Author: Christian Hofmaier <you@example.com>
Date: Tue Jan 17 16:05:12 2017 +0100
[reboot-control] created branch and empty files to work with
Diffstat (limited to 'modules-available')
17 files changed, 734 insertions, 0 deletions
diff --git a/modules-available/rebootcontrol/api.inc.php b/modules-available/rebootcontrol/api.inc.php new file mode 100644 index 00000000..77687f8e --- /dev/null +++ b/modules-available/rebootcontrol/api.inc.php @@ -0,0 +1,36 @@ +<?php +/* + Needed POST-Parameters: + 'token' -- for authentification + 'action' -- which action should be performed (shutdown or reboot) + 'clients' -- which are to reboot/shutdown (json encoded array!) + 'timer' -- (optional) when to perform action in minutes (default value is 0) +*/ + +$ips = json_decode(Request::post('clients')); +$minutes = Request::post('timer', 0, 'int'); + +$clients = array(); +foreach ($ips as $client) { + $clients[] = array("ip" => $client); +} + +if (Request::post('token') == Property::get("rebootcontrol_APIPOSTKEY")) { + if (Request::isPost()) { + if (Request::post('action') == 'shutdown') { + $shutdown = true; + $task = Taskmanager::submit("RemoteReboot", array("clients" => $clients, "shutdown" => $shutdown, "minutes" => $minutes)); + echo $task["id"]; + } else if (Request::post('action') == 'reboot') { + $shutdown = false; + $task = Taskmanager::submit("RemoteReboot", array("clients" => $clients, "shutdown" => $shutdown, "minutes" => $minutes)); + echo $task["id"]; + } else { + echo "Only action=shutdown and action=reboot available."; + } + } else { + echo "Only POST Method available."; + } +} else { + echo "Not authorized"; +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/clientscript.js b/modules-available/rebootcontrol/clientscript.js new file mode 100644 index 00000000..d3ecbe48 --- /dev/null +++ b/modules-available/rebootcontrol/clientscript.js @@ -0,0 +1,22 @@ +document.addEventListener("DOMContentLoaded", function() { + var table = $("table"); + table.stupidtable({ + "ipsort":function(a,b){ + var aa = a.split("."); + var bb = b.split("."); + + var resulta = aa[0]*0x1000000 + aa[1]*0x10000 + aa[2]*0x100 + aa[3]*1; + var resultb = bb[0]*0x1000000 + bb[1]*0x10000 + bb[2]*0x100 + bb[3]*1; + + return resulta-resultb; + } + }); + + table.on("aftertablesort", function (event, data) { + var th = $(this).find("th"); + th.find(".arrow").remove(); + var dir = $.fn.stupidtable.dir; + var arrow = data.direction === dir.ASC ? "down" : "up"; + th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>'); + }); +});
\ No newline at end of file diff --git a/modules-available/rebootcontrol/config.json b/modules-available/rebootcontrol/config.json new file mode 100644 index 00000000..2cc05822 --- /dev/null +++ b/modules-available/rebootcontrol/config.json @@ -0,0 +1,4 @@ +{ + "category":"main.content", + "dependencies": [ "locations", "js_stupidtable" ] +} diff --git a/modules-available/rebootcontrol/hooks/config-tgz.inc.php b/modules-available/rebootcontrol/hooks/config-tgz.inc.php new file mode 100644 index 00000000..0b706960 --- /dev/null +++ b/modules-available/rebootcontrol/hooks/config-tgz.inc.php @@ -0,0 +1,18 @@ +<?php + +$pubkey = SSHKey::getPublicKey(); +$tmpfile = '/tmp/bwlp-' . md5($pubkey) . '.tar'; +if (!is_file($tmpfile) || !is_readable($tmpfile) || filemtime($tmpfile) + 86400 < time()) { + if (file_exists($tmpfile)) { + unlink($tmpfile); + } + try { + $a = new PharData($tmpfile); + $a->addFromString("/root/.ssh/authorized_keys.d/rebootcontrol", $pubkey); + $file = $tmpfile; + } catch (Exception $e) { + EventLog::failure('Could not include ssh key for reboot-control in config.tgz', (string)$e); + } +} elseif (is_file($tmpfile) && is_readable($tmpfile)) { + $file = $tmpfile; +} diff --git a/modules-available/rebootcontrol/inc/rebootqueries.inc.php b/modules-available/rebootcontrol/inc/rebootqueries.inc.php new file mode 100644 index 00000000..df3c13d8 --- /dev/null +++ b/modules-available/rebootcontrol/inc/rebootqueries.inc.php @@ -0,0 +1,44 @@ +<?php + +class RebootQueries +{ + + // Get Client+IP+CurrentVM+CurrentUser+Location to fill the table + public static function getMachineTable($locationId) { + if ($locationId === 0) { + $where = 'machine.locationid IS NULL'; + } else { + $where = 'machine.locationid = :locationid'; + } + $leftJoin = ''; + $sessionField = 'machine.currentsession'; + if (Module::get('dozmod') !== false) { + // SELECT lectureid, displayname FROM sat.lecture WHERE lectureid = :lectureid + $leftJoin = 'LEFT JOIN sat.lecture ON (lecture.lectureid = machine.currentsession)'; + $sessionField = 'IFNULL(lecture.displayname, machine.currentsession) AS currentsession'; + } + $res = Database::simpleQuery(" + SELECT machine.machineuuid, machine.hostname, machine.clientip, + IF(machine.lastboot = 0 OR UNIX_TIMESTAMP() - machine.lastseen >= 600, 0, 1) AS status, + $sessionField, machine.currentuser, machine.locationid + FROM machine + $leftJoin + WHERE " . $where, array('locationid' => $locationId)); + return $res->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Get machines by list of UUIDs + * @param string[] $list list of system UUIDs + * @return array list of machines with machineuuid, clientip and locationid + */ + public static function getMachinesByUuid($list) + { + if (empty($list)) + return array(); + $qs = '?' . str_repeat(',?', count($list) - 1); + $res = Database::simpleQuery("SELECT machineuuid, clientip, locationid FROM machine WHERE machineuuid IN ($qs)", $list); + return $res->fetchAll(PDO::FETCH_ASSOC); + } + +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/inc/sshkey.inc.php b/modules-available/rebootcontrol/inc/sshkey.inc.php new file mode 100644 index 00000000..b4e36d25 --- /dev/null +++ b/modules-available/rebootcontrol/inc/sshkey.inc.php @@ -0,0 +1,40 @@ +<?php + +class SSHKey +{ + + public static function getPrivateKey() { + $privKey = Property::get("rebootcontrol-private-key"); + if (!$privKey) { + $rsaKey = openssl_pkey_new(array( + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA)); + openssl_pkey_export( openssl_pkey_get_private($rsaKey), $privKey); + Property::set("rebootcontrol-private-key", $privKey); + } + return $privKey; + } + + public static function getPublicKey() { + $pkImport = openssl_pkey_get_private(self::getPrivateKey()); + return self::sshEncodePublicKey($pkImport); + } + + private static function sshEncodePublicKey($privKey) { + $keyInfo = openssl_pkey_get_details($privKey); + $buffer = pack("N", 7) . "ssh-rsa" . + self::sshEncodeBuffer($keyInfo['rsa']['e']) . + self::sshEncodeBuffer($keyInfo['rsa']['n']); + return "ssh-rsa " . base64_encode($buffer); + } + + private static function sshEncodeBuffer($buffer) { + $len = strlen($buffer); + if (ord($buffer[0]) & 0x80) { + $len++; + $buffer = "\x00" . $buffer; + } + return pack("Na*", $len, $buffer); + } + +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/de/messages.json b/modules-available/rebootcontrol/lang/de/messages.json new file mode 100644 index 00000000..2a7e1299 --- /dev/null +++ b/modules-available/rebootcontrol/lang/de/messages.json @@ -0,0 +1,4 @@ +{ + "no-clients-selected": "Keine Clients ausgew\u00e4hlt", + "some-machine-not-found": "Einige Clients aus dem POST request wurden nicht gefunden" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/de/module.json b/modules-available/rebootcontrol/lang/de/module.json new file mode 100644 index 00000000..03196610 --- /dev/null +++ b/modules-available/rebootcontrol/lang/de/module.json @@ -0,0 +1,5 @@ +{ + "module_name": "Reboot Control", + "notAssigned": "Nicht zugewiesen", + "page_title": "Reboot Control" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/de/template-tags.json b/modules-available/rebootcontrol/lang/de/template-tags.json new file mode 100644 index 00000000..2a04e746 --- /dev/null +++ b/modules-available/rebootcontrol/lang/de/template-tags.json @@ -0,0 +1,31 @@ +{ + "lang_authFail": "Athentifizierung fehlgeschlagen", + "lang_client": "Client", + "lang_connecting": "Verbinde...", + "lang_error": "Nicht erreichbar", + "lang_genNew": "Neues Schl\u00fcsselpaar generieren", + "lang_ip": "IP", + "lang_location": "Standort", + "lang_minutes": " Minuten", + "lang_off": "Aus", + "lang_on": "An", + "lang_online": "Online", + "lang_pubKey": "SSH Public Key:", + "lang_reboot": "Neustarten", + "lang_rebootAt": "Neustart um:", + "lang_rebootButton": "Neustarten", + "lang_rebootCheck": "Wollen Sie wirklich die ausgew\u00e4hlten Rechner neustarten?", + "lang_rebooting": "Neustart...", + "lang_selectall": "Alle ausw\u00e4hlen", + "lang_selected": "Ausgew\u00e4hlt", + "lang_session": "Sitzung", + "lang_settings": "Einstellungen", + "lang_shutdown": "Herunterfahren", + "lang_shutdownAt": "Herunterfahren um: ", + "lang_shutdownButton": "Herunterfahren", + "lang_shutdownCheck": "Wollen Sie wirklich die ausgew\u00e4hlten Rechner herunterfahren?", + "lang_shutdownIn": "Herunterfahren in: ", + "lang_status": "Status", + "lang_unselectall": "Alle abw\u00e4hlen", + "lang_user": "Nutzer" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/en/messages.json b/modules-available/rebootcontrol/lang/en/messages.json new file mode 100644 index 00000000..50bdd7fe --- /dev/null +++ b/modules-available/rebootcontrol/lang/en/messages.json @@ -0,0 +1,4 @@ +{ + "no-clients-selected": "No clients selected", + "some-machine-not-found": "Some machines from your POST request don't exist" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/en/module.json b/modules-available/rebootcontrol/lang/en/module.json new file mode 100644 index 00000000..129140dd --- /dev/null +++ b/modules-available/rebootcontrol/lang/en/module.json @@ -0,0 +1,5 @@ +{ + "module_name": "Reboot Control", + "notAssigned": "Not assigned", + "page_title": "Reboot Control" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/en/template-tags.json b/modules-available/rebootcontrol/lang/en/template-tags.json new file mode 100644 index 00000000..ca44171a --- /dev/null +++ b/modules-available/rebootcontrol/lang/en/template-tags.json @@ -0,0 +1,31 @@ +{ + "lang_authFail": "Authentication failed", + "lang_client": "Client", + "lang_connecting": "Connecting...", + "lang_error": "Not available", + "lang_genNew": "Generate new keypair", + "lang_ip": "IP", + "lang_location": "Location", + "lang_minutes": " Minutes", + "lang_off": "Off", + "lang_on": "On", + "lang_online": "Online", + "lang_pubKey": "SSH Public Key:", + "lang_reboot": "Reboot", + "lang_rebootAt": "Reboot at:", + "lang_rebootButton": "Reboot", + "lang_rebootCheck": "Do you really want to reboot the selected clients?", + "lang_rebooting": "Rebooting...", + "lang_selectall": "Select all", + "lang_selected": "Selected", + "lang_session": "Session", + "lang_settings": "Settings", + "lang_shutdown": "Shut Down", + "lang_shutdownAt": "Shutdown at: ", + "lang_shutdownButton": "Shutdown", + "lang_shutdownCheck": "Do you really want to shut down the selected clients?", + "lang_shutdownIn": "Shutdown in: ", + "lang_status": "Status", + "lang_unselectall": "Unselect all", + "lang_user": "User" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/pt/template-tags.json b/modules-available/rebootcontrol/lang/pt/template-tags.json new file mode 100644 index 00000000..89fa4d96 --- /dev/null +++ b/modules-available/rebootcontrol/lang/pt/template-tags.json @@ -0,0 +1,30 @@ +{ + "lang_client": "Client", + "lang_ip": "IP", + "lang_session": "Session", + "lang_user": "User", + "lang_location": "Location", + "lang_locations": "Locations", + "lang_selectall": "Select all", + "lang_unselectall": "Unselect all", + "lang_status": "Status", + "lang_rebootButton": "Reboot", + "lang_rebootCheck": "Do you really want to reboot the selected clients?", + "lang_shutdownButton": "Shut Down", + "lang_shutdownCheck": "Do you really want to shut down the selected clients?", + "lang_cancel": "Cancel", + "lang_reboot": "Reboot", + "lang_connecting": "Connecting...", + "lang_rebooting": "Rebooting...", + "lang_online": "Online", + "lang_error": "Not available", + "lang_shutdown": "Shut Down", + "lang_shutdownIn": "Shutdown in: ", + "lang_shutdownAt": "Shutdown at: ", + "lang_minutes": " Minutes", + "lang_back": "Back", + "lang_pubKey": "SSH Public Key:", + "lang_settings": "Settings", + "lang_genNew": "Generate new keypair", + "lang_selected": "Selected" +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php new file mode 100644 index 00000000..d7083528 --- /dev/null +++ b/modules-available/rebootcontrol/page.inc.php @@ -0,0 +1,106 @@ +<?php + +class Page_RebootControl extends Page +{ + + private $action = false; + + /** + * Called before any page rendering happens - early hook to check parameters etc. + */ + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); // does not return + } + + $this->action = Request::any('action', 'show', 'string'); + + + if ($this->action === 'startReboot' || $this->action === 'startShutdown') { + $clients = Request::post('clients'); + if (!is_array($clients) || empty($clients)) { + Message::addError('no-clients-selected'); + Util::redirect(); + } + $locationId = Request::post('locationId', false, 'int'); + if ($locationId === false) { + Message::addError('locations.invalid-location-id', $locationId); + Util::redirect(); + } + $shutdown = $this->action === "startShutdown"; + $minutes = Request::post('minutes', 0, 'int'); + $privKey = SSHKey::getPrivateKey(); + + $list = RebootQueries::getMachinesByUuid($clients); + if (count($list) !== count($clients)) { + // We could go ahead an see which ones were not found in DB but this should not happen anyways unless the + // user manipulated the request + Message::addWarning('some-machine-not-found'); + } + // TODO: Iterate over list and check if a locationid is not in permissions + // TODO: we could also check if the locationid is equal or a sublocation of the $locationId from above + // (this would be more of a sanity check though, or does the UI allow selecting machines from different locations) + + $task = Taskmanager::submit("RemoteReboot", array( + "clients" => $list, + "shutdown" => $shutdown, + "minutes" => $minutes, + "locationId" => $locationId, + "sshkey" => $privKey, + "port" => 22, // TODO: Get from ssh config + )); + + Util::redirect("?do=rebootcontrol&taskid=".$task["id"]); + } + + } + + /** + * Menu etc. has already been generated, now it's time to generate page content. + */ + + protected function doRender() + { + if ($this->action === 'show') { + + $taskId = Request::get("taskid"); + + if ($taskId && Taskmanager::isTask($taskId)) { + $task = Taskmanager::status($taskId); + $data['taskId'] = $taskId; + $data['locationId'] = $task['data']['locationId']; + $data['locationName'] = Location::getName($task['data']['locationId']); + $data['clients'] = $task['data']['clients']; + Render::addTemplate('status', $data); + } else { + //location you want to see, default are "not assigned" clients + $requestedLocation = Request::get('location', 0, 'int'); + + $data['data'] = RebootQueries::getMachineTable($requestedLocation); + $data['locations'] = Location::getLocations($requestedLocation, 0, true); + + $data['pubKey'] = SSHKey::getPublicKey(); + + Render::addTemplate('_page', $data); + } + } + } + + function doAjax() + { + $this->action = Request::post('action', false, 'string'); + if ($this->action === 'generateNewKeypair') { + Property::set("rebootcontrol-private-key", false); + echo SSHKey::getPublicKey(); + } else { + echo 'Invalid action.'; + } + } + + + +} diff --git a/modules-available/rebootcontrol/style.css b/modules-available/rebootcontrol/style.css new file mode 100644 index 00000000..442cd5de --- /dev/null +++ b/modules-available/rebootcontrol/style.css @@ -0,0 +1,47 @@ +.rebootTimerForm { + margin-top: 20px; +} + +.statusColumn { + text-align: center; +} + +.table > tbody > tr > td { + vertical-align: middle; + height: 50px; +} + +.checkbox { + margin-top: 0; + margin-bottom: 0; +} + +#rebootButton, #settingsButton, #selectAllButton, #unselectAllButton { + margin-left: 10px; +} + +#rebootButton, #shutdownButton, #selectAllButton, #unselectAllButton { + width: 140px; +} + +#dataTable { + margin-top: 20px; +} + + +#shutdownTimer { + text-align: center; +} +#pubKeyTitle { + display: inline-block; + margin-top: 7px; + margin-bottom: 20px; +} + +pre { + white-space: pre-wrap; +} + +th[data-sort] { + cursor: pointer; +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html new file mode 100644 index 00000000..690316df --- /dev/null +++ b/modules-available/rebootcontrol/templates/_page.html @@ -0,0 +1,245 @@ +<form id="tableDataForm" method="post" action="?do=rebootcontrol" class="form-inline"> + <input type="hidden" name="token" value="{{token}}"> + <div class="row"> + <div class="col-md-12"> + <label>{{lang_location}}: + <select id="locationDropdown" name="locationId" class="form-control" onchange="selectLocation()"> + {{#locations}} + <option value="{{locationid}}" {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option> + {{/locations}} + </select> + </label> + <button type="button" id="settingsButton" class="btn btn-default pull-right" data-toggle="modal" data-target="#settingsModal"><span class="glyphicon glyphicon-cog"></span></button> + <button type="button" id="selectAllButton" class="btn btn-primary pull-right" onclick="selectAllRows()"><span class="glyphicon glyphicon-check"></span> {{lang_selectall}}</button> + <button type="button" id="unselectAllButton" class="btn btn-default pull-right" onclick="unselectAllRows()" style="display: none;"><span class="glyphicon glyphicon-unchecked"></span> {{lang_unselectall}}</button> + <button type="button" id="rebootButton" class="btn btn-warning pull-right" data-toggle="modal" data-target="#rebootModal" disabled><span class="glyphicon glyphicon-repeat"></span> {{lang_rebootButton}}</button> + <button type="button" id="shutdownButton" class="btn btn-danger pull-right" data-toggle="modal" data-target="#shutdownModal" disabled><span class="glyphicon glyphicon-off"></span> {{lang_shutdownButton}}</button> + </div> + </div> + <div class="row"> + <div class="col-md-12"> + <table class="table table-condensed table-hover" id="dataTable"> + <thead> + <tr> + <th data-sort="string">{{lang_client}}</th> + <th data-sort="ipsort">{{lang_ip}}</th> + <th data-sort="string">{{lang_status}}</th> + <th data-sort="string">{{lang_session}}</th> + <th data-sort="string">{{lang_user}}</th> + <th data-sort="int" data-sort-default="desc">{{lang_selected}}</th> + </tr> + </thead> + + <tbody> + {{#data}} + <tr> + <td> + {{hostname}} + {{^hostname}}{{clientip}}{{/hostname}} + </td> + <td>{{clientip}}</td> + <td class="statusColumn"> + {{#status}} + {{lang_on}} + {{/status}} + {{^status}} + {{lang_off}} + {{/status}} + </td> + <td>{{#status}}{{currentsession}}{{/status}}</td> + <td>{{#status}}{{currentuser}}{{/status}}</td> + <td data-sort-value="0" class="checkboxColumn"> + <div class="checkbox"> + <input id="m-{{machineuuid}}" type="checkbox" name="clients[]" value='{{machineuuid}}'> + <label for="m-{{machineuuid}}"></label> + </div> + </td> + </tr> + {{/data}} + </tbody> + </table> + </div> + </div> + + + <!-- Modals --> + + <div id="settingsModal" class="modal fade" role="dialog"> + <div class="modal-dialog"> + + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal">×</button> + <h4 class="modal-title"><b>{{lang_settings}}</b></h4> + </div> + <div class="modal-body"> + <span id="pubKeyTitle">{{lang_pubKey}}</span> + <button class="btn btn-s btn-warning pull-right" onclick="generateNewKeypair()" type="button">{{lang_genNew}}</button> + <pre id="pubKey">{{pubKey}}</pre> + </div> + <div class="modal-footer"> + </div> + </div> + </div> + </div> + + <div class ="modal fade" id="rebootModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel">{{lang_rebootButton}}</h4> + </div> + <div class="modal-body"> + {{lang_rebootCheck}} + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" name="action" value="startReboot" class="btn btn-warning"><span class="glyphicon glyphicon-repeat"></span> {{lang_reboot}}</button> + </div> + </div> + </div> + </div> + + <div class ="modal fade" id="shutdownModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="myModalLabel2">{{lang_shutdownButton}}</h4> + </div> + <div class="modal-body"> + {{lang_shutdownCheck}} + {{lang_shutdownIn}} <input id="shutdownTimer" name="minutes" title="{{lang_shutdownIn}}" type="number" value="0" min="0" onkeypress="return isNumberKey(event)"> {{lang_minutes}} + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" name="action" value="startShutdown" class="btn btn-danger"><span class="glyphicon glyphicon-off"></span> {{lang_shutdownButton}}</button> + </div> + </div> + </div> + </div> +</form> + + +<script type="application/javascript"> + document.addEventListener("DOMContentLoaded", function() { + markCheckedRows(); + + $('input:checkbox').change( + function(){ + //give each checkbox the function to mark the row (in green) + if ($(this).is(':checked')) { + markRows($(this).closest("tr"), true); + $(this).closest("td").data("sort-value", 1); + } else { + markRows($(this).closest("tr"), false); + $(this).closest("td").data("sort-value", 0); + } + + //if all are checked, change the selectAll-Button to unselectAll. if one is not checked, change unselectAll to selectAll + var dataTable = $("#dataTable"); + var unchecked = dataTable.find("input[type=checkbox]:not(:checked)").length; + if (unchecked == 0) { + $('#selectAllButton').hide(); + $('#unselectAllButton').show(); + } else if (unchecked == 1) { + $('#selectAllButton').show(); + $('#unselectAllButton').hide(); + } + + //if no client is selected, disable the shutdown/reboot button, and enable them if a client is selected + var checked = dataTable.find("input[type=checkbox]:checked").length; + if (checked == 0) { + $('#rebootButton').prop('disabled', true); + $('#shutdownButton').prop('disabled', true); + } else { + $('#rebootButton').prop('disabled', false); + $('#shutdownButton').prop('disabled', false); + } + }); + $('.checkboxColumn').click(function(e) { + if (e.target === this) { + $(this).find('input[type="checkbox"]').click(); + } + }); + }); + + // Change Location when selected in Dropdown Menu + function selectLocation() { + var dropdown = $("#locationDropdown"); + var location = dropdown.val(); + window.location.replace("?do=rebootcontrol&location="+location); + } + + // Check all checkboxes, change selectAll button, make shutdown/reboot button enabled as clients will certainly be selected + function selectAllRows() { + var checked = $("tr input:checkbox:checked"); + + //change button + $('#selectAllButton').hide(); + $('#unselectAllButton').show(); + + //check rows and mark them + $('input[type="checkbox"]', '#dataTable').prop('checked', true); + markRows($("tr:not(:first)"), true); + $(".checkboxColumn").data("sort-value", 1); + + //enable shutdown/reboot button + $('#rebootButton').prop('disabled', false); + $('#shutdownButton').prop('disabled', false); + } + + // Uncheck all checkboxes, change unselectAll Button, make shutdown/reboot button disabled as clients will certainly be not selected + function unselectAllRows() { + //change button + $('#selectAllButton').show(); + $('#unselectAllButton').hide(); + + //uncheck rows and unmark them + $('input[type="checkbox"]', '#dataTable').prop('checked', false); + markRows($("tr"), false); + $(".checkboxColumn").data("sort-value", 0); + + //disable shutdown/reboot button + $('#rebootButton').prop('disabled', true); + $('#shutdownButton').prop('disabled', true); + } + + // mark all previous checked rows (used when loading site) + function markCheckedRows() { + var checked = $("tr input:checkbox:checked"); + markRows(checked.closest("tr"), true); + var unchecked = $("#dataTable").find("input[type=checkbox]:not(:checked)").length; + if(unchecked == 0) { + $('#selectAllButton').hide(); + $('#unselectAllButton').show(); + } + } + + // only allow numbers to get typed into the "shutdown in X Minutes" box. + function isNumberKey(evt){ + var charCode = (evt.which) ? evt.which : event.keyCode; + return !(charCode > 31 && (charCode < 48 || charCode > 57)); + } + + function markRows($rows, marked) { + if (marked) { + $rows.addClass('active'); + } else { + $rows.removeClass('active'); + } + } + + function generateNewKeypair() { + $.ajax({ + url: '?do=rebootcontrol', + type: 'POST', + data: { action: "generateNewKeypair", token: TOKEN }, + success: function(value) { + $('#pubKey').text(value); + } + }); + } + +</script>
\ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/status.html b/modules-available/rebootcontrol/templates/status.html new file mode 100644 index 00000000..35bbe42f --- /dev/null +++ b/modules-available/rebootcontrol/templates/status.html @@ -0,0 +1,62 @@ +<div> + <form class="form-inline"> + <b>{{lang_location}}: {{locationName}}</b> + <input type="hidden" name="do" value="rebootcontrol"> + <input type="hidden" name="location" value="{{locationId}}"> + <button type="submit" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-arrow-left"></span> {{lang_back}}</button> + </form> +</div> + +<div data-tm-id="{{taskId}}" data-tm-log="error" data-tm-callback="updateStatus"></div> + +<div> + <table class="table table-hover" id="dataTable"> + <thead> + <tr> + <th data-sort="string">{{lang_client}}</th> + <th data-sort="ipsort">{{lang_ip}}</th> + <th data-sort="string"> + {{lang_status}} + </th> + </tr> + </thead> + + <tbody> + {{#clients}} + <tr> + <td>{{machineuuid}}</td> + <td>{{clientip}}</td> + <td id="status-{{machineuuid}}"></td> + </tr> + {{/clients}} + </tbody> + </table> +</div> + +<script type="application/javascript"> + statusStrings = { + "CONNECTING" : "{{lang_connecting}}", + "REBOOTING" : "{{lang_rebooting}}", + "REBOOT_AT" : "{{lang_rebootAt}}", + "ONLINE" : "{{lang_online}}", + "ERROR" : "{{lang_error}}", + "SHUTDOWN" : "{{lang_shutdown}}", + "SHUTDOWN_AT" : "{{lang_shutdownAt}}", + "AUTH_FAIL" : "{{lang_authFail}}" + }; + + function updateStatus(task) { + if (!task || !task.data || !task.data.clientStatus) + return; + var clientStatus = task.data.clientStatus; + for (var uuid in clientStatus) { + if (clientStatus.hasOwnProperty(uuid)) { + var shutdownTime = ' '; + if (clientStatus[uuid] === 'SHUTDOWN_AT' || clientStatus[uuid] === 'REBOOT_AT') { + shutdownTime += task.data.time; + } + $("#status-" + uuid).text(statusStrings[clientStatus[uuid]] + shutdownTime); + } + } + } +</script> |