summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2019-11-22 17:14:06 +0100
committerSimon Rettberg2019-11-22 17:14:06 +0100
commit95c8a36dfc7e63f23eae3b411fd1a371da6f774c (patch)
tree3478a4ae1e21c5e315a38488d1af655f93843420
parent[inc/Taskmanagercallback] Remove unused function (diff)
downloadslx-admin-95c8a36dfc7e63f23eae3b411fd1a371da6f774c.tar.gz
slx-admin-95c8a36dfc7e63f23eae3b411fd1a371da6f774c.tar.xz
slx-admin-95c8a36dfc7e63f23eae3b411fd1a371da6f774c.zip
[rebootcontrol] Start adding WOL functionality
-rw-r--r--inc/taskmanagercallback.inc.php11
-rw-r--r--modules-available/rebootcontrol/inc/rebootcontrol.inc.php64
-rw-r--r--modules-available/rebootcontrol/install.inc.php49
-rw-r--r--modules-available/rebootcontrol/lang/de/module.json4
-rw-r--r--modules-available/rebootcontrol/lang/de/template-tags.json37
-rw-r--r--modules-available/rebootcontrol/lang/en/module.json4
-rw-r--r--modules-available/rebootcontrol/lang/en/template-tags.json2
-rw-r--r--modules-available/rebootcontrol/page.inc.php371
-rw-r--r--modules-available/rebootcontrol/permissions/permissions.json21
-rw-r--r--modules-available/rebootcontrol/templates/_page.html184
-rw-r--r--modules-available/rebootcontrol/templates/header.html74
-rw-r--r--modules-available/rebootcontrol/templates/jumphost-edit.html42
-rw-r--r--modules-available/rebootcontrol/templates/jumphost-list.html64
-rw-r--r--modules-available/rebootcontrol/templates/status-checkconnection.html51
-rw-r--r--modules-available/rebootcontrol/templates/status-reboot.html (renamed from modules-available/rebootcontrol/templates/status.html)14
-rw-r--r--modules-available/rebootcontrol/templates/task-list.html2
16 files changed, 620 insertions, 374 deletions
diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php
index 2be9fe73..21bece38 100644
--- a/inc/taskmanagercallback.inc.php
+++ b/inc/taskmanagercallback.inc.php
@@ -201,5 +201,14 @@ class TaskmanagerCallback
$mod->activate(1, false);
MiniLinux::linuxDownloadCallback($task, $args);
}
-
+
+ public static function rbcConnCheck($task, $args)
+ {
+ $mod = Module::get('rebootcontrol');
+ if ($mod === false)
+ return;
+ $mod->activate(1, false);
+ RebootControl::connectionCheckCallback($task, $args);
+ }
+
}
diff --git a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
index ec4b84ed..680f9eff 100644
--- a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
+++ b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
@@ -82,4 +82,68 @@ class RebootControl
return $return;
}
+ /**
+ * Execute given command or script on a list of hosts. The list of hosts is an array of structs containing
+ * each a known machine-uuid and/or hostname, and optionally a port to use, which would otherwise default to 9922,
+ * and optionally a username to use, which would default to root.
+ * The command should be compatible with the remote user's default shell (most likely bash).
+ *
+ * @param array $clients [ { clientip: <host>, machineuuid: <uuid>, port: <port>, username: <username> }, ... ]
+ * @param string $command Command or script to execute on client
+ * @param int $timeout in seconds
+ * @param string|false $privkey SSH private key to use to connect
+ * @return array|false
+ */
+ public static function runScript($clients, $command, $timeout = 5, $privkey = false)
+ {
+ $valid = [];
+ $invalid = [];
+ foreach ($clients as $client) {
+ if (is_string($client)) {
+ $invalid[strtoupper($client)] = []; // Assume machineuuid
+ } elseif (!isset($client['clientip']) && !isset($client['machineuuid'])) {
+ error_log('RebootControl::runScript called with list entry that has neither IP nor UUID');
+ } elseif (!isset($client['clientip'])) {
+ $invalid[$client['machineuuid']] = $client;
+ } else {
+ $valid[] = $client;
+ }
+ }
+ if (!empty($invalid)) {
+ $res = Database::simpleQuery('SELECT machineuuid, clientip FROM machine WHERE machineuuid IN (:uuids)',
+ ['uuids' => array_keys($invalid)]);
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (isset($invalid[$row['machineuuid']])) {
+ $valid[] = $row + $invalid[$row['machineuuid']];
+ } else {
+ $valid[] = $row;
+ }
+ }
+ }
+ if ($privkey === false) {
+ $privkey = SSHKey::getPrivateKey();
+ }
+ return Taskmanager::submit('RemoteExec', [
+ 'clients' => $valid,
+ 'command' => $command,
+ 'timeoutSeconds' => $timeout,
+ 'sshkey' => $privkey,
+ 'port' => 9922, // Fallback if no port given in client struct
+ ]);
+ }
+
+ public static function connectionCheckCallback($task, $hostId)
+ {
+ $reachable = 0;
+ if (isset($task['data']['result'])) {
+ foreach ($task['data']['result'] as $res) {
+ if ($res['exitCode'] == 0) {
+ $reachable = 1;
+ }
+ }
+ }
+ Database::exec('UPDATE reboot_jumphost SET reachable = :reachable WHERE hostid = :id',
+ ['id' => $hostId, 'reachable' => $reachable]);
+ }
+
} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/install.inc.php b/modules-available/rebootcontrol/install.inc.php
new file mode 100644
index 00000000..0ce5dd23
--- /dev/null
+++ b/modules-available/rebootcontrol/install.inc.php
@@ -0,0 +1,49 @@
+<?php
+
+$output = array();
+
+$output[] = tableCreate('reboot_subnet', "
+ `subnetid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `start` INT(10) UNSIGNED NOT NULL,
+ `end` INT(10) UNSIGNED NOT NULL,
+ `fixed` BOOL NOT NULL,
+ `isdirect` BOOL NOT NULL,
+ `lastdirectcheck` INT(10) UNSIGNED NOT NULL DEFAULT '0',
+ `lastseen` INT(10) UNSIGNED NOT NULL DEFAULT '0',
+ `seencount` INT(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`subnetid`),
+ KEY `range` (`start`, `end`)");
+
+$output[] = tableCreate('reboot_jumphost', "
+ `hostid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
+ `host` VARCHAR(100) NOT NULL,
+ `port` SMALLINT(10) UNSIGNED NOT NULL,
+ `username` VARCHAR(30) NOT NULL,
+ `reachable` BOOL NOT NULL,
+ `sshkey` BLOB NOT NULL,
+ `script` BLOB NOT NULL,
+ PRIMARY KEY (`hostid`)");
+
+$output[] = tableCreate('reboot_jumphost_x_subnet', "
+ `hostid` INT(10) UNSIGNED NOT NULL,
+ `subnetid` INT(10) UNSIGNED NOT NULL,
+ PRIMARY KEY (`hostid`, `subnetid`)");
+
+$output[] = tableCreate('reboot_subnet_x_subnet', "
+ `srcid` INT(10) UNSIGNED NOT NULL,
+ `dstid` INT(10) UNSIGNED NOT NULL,
+ `reachable` BOOL NOT NULL,
+ `lastcheck` INT(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`srcid`, `dstid`),
+ KEY `lastcheck` (`lastcheck`)");
+
+$output[] = tableAddConstraint('reboot_jumphost_x_subnet', 'hostid', 'reboot_jumphost', 'hostid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+$output[] = tableAddConstraint('reboot_jumphost_x_subnet', 'subnetid', 'reboot_subnet', 'subnetid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+$output[] = tableAddConstraint('reboot_subnet_x_subnet', 'srcid', 'reboot_subnet', 'subnetid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+$output[] = tableAddConstraint('reboot_subnet_x_subnet', 'dstid', 'reboot_subnet', 'subnetid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+responseFromArray($output); \ No newline at end of file
diff --git a/modules-available/rebootcontrol/lang/de/module.json b/modules-available/rebootcontrol/lang/de/module.json
index 1f325354..47d91e98 100644
--- a/modules-available/rebootcontrol/lang/de/module.json
+++ b/modules-available/rebootcontrol/lang/de/module.json
@@ -1,4 +1,4 @@
{
- "module_name": "Reboot Control",
- "page_title": "Reboot Control"
+ "module_name": "WakeOnLAN",
+ "page_title": "WakeOnLAN"
} \ 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
index c678ef88..519c2286 100644
--- a/modules-available/rebootcontrol/lang/de/template-tags.json
+++ b/modules-available/rebootcontrol/lang/de/template-tags.json
@@ -1,39 +1,42 @@
{
"lang_activeTasks": "Laufende Jobs",
+ "lang_assignedSubnets": "Subnets",
"lang_authFail": "Authentifizierung fehlgeschlagen",
+ "lang_check": "Testen",
+ "lang_checkOutputLabel": "Ausgabe",
+ "lang_checkingJumpHost": "Teste Sprung-Host",
"lang_client": "Client",
"lang_clientCount": "# Clients",
- "lang_confirmNewKeypair": "Wirklich neues Schl\u00fcsselpaar erzeugen?",
"lang_connecting": "Verbinde...",
+ "lang_editJumpHost": "Sprung-Host bearbeiten",
"lang_error": "Nicht erreichbar",
"lang_genNew": "Neues Schl\u00fcsselpaar generieren",
+ "lang_host": "Horst",
+ "lang_hostDeleteConfirm": "Diesen Sprung-Host l\u00f6schen?",
+ "lang_hostNonZeroExit": "Das hinterlegte Script hat einen Exit-Code ungleich 0 zur\u00fcckgeliefert",
+ "lang_hostNotReachable": "Host nicht erreichbar",
+ "lang_hostReachable": "Host erreichbar",
"lang_ip": "IP",
- "lang_kexecRebootCheck": "Schneller Reboot direkt in bwLehrpool",
+ "lang_jumpHosts": "Spring-Hosts",
+ "lang_keypairConfirmCheck": "Ich bin sicher",
"lang_location": "Standort",
- "lang_minutes": " Minuten",
"lang_mode": "Modus",
+ "lang_moduleHeading": "WakeOnLAN",
+ "lang_new": "Neu",
"lang_newKeypairExplanation": "Sie k\u00f6nnen ein neues Schl\u00fcsselpaar erzeugen lassen. In diesem Fall wird das alte Schl\u00fcsselpaar verworfen, sodass alle zum jetzigen Zeitpunkt bereits gestarteten Rechner nicht mehr aus der Ferne bedient werden k\u00f6nnen, bis diese manuell neugestartet wurden.",
- "lang_off": "Aus",
- "lang_on": "An",
"lang_online": "Online",
+ "lang_port": "Port",
+ "lang_privkey": "Geheimer Schl\u00fcssel",
"lang_pubKey": "SSH Public Key:",
- "lang_reboot": "Neustarten",
+ "lang_reachable": "Erreichbar",
"lang_rebootAt": "Neustart um:",
- "lang_rebootButton": "Neustarten",
- "lang_rebootCheck": "Wollen Sie die ausgew\u00e4hlten Rechner wirklich neustarten?",
- "lang_rebootControl": "Reboot Control",
- "lang_rebootIn": "Neustart in:",
"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 die ausgew\u00e4hlten Rechner wirklich herunterfahren?",
- "lang_shutdownIn": "Herunterfahren in: ",
"lang_status": "Status",
"lang_time": "Zeit",
- "lang_unselectall": "Alle abw\u00e4hlen"
+ "lang_wakeScriptHelp": "Dieses Script wird auf dem Sprung-Host ausgef\u00fchrt, um den\/die gew\u00fcnschten Maschinen aufzuwecken. Es wird unter der Standard-Shell des oben angegebenen Benutzers ausgef\u00fchrt. Das Script kann zwei spezielle Platzhalter enthalten, die vor dem Ausf\u00fchren des Scripts vom Satellitenserver ersetzt werden: %MACS% ist eine durch Leerzeichen getrennte Liste von MAC-Adressen, die aufzuwecken sind. Das Tool \"wakeonlan\" unterst\u00fctzt direkt mehrere MAC-Adressen, sodass der Platzhalter %MACS% direkt als Kommandozeilenargument verwendet werden kann. Das Tool \"etherwake\" hingegen kann pro Aufruf immer nur einen Host aufwecken, weshalb eine for-Schleife notwendig ist. Au\u00dferdem wird der Platzhalter %IP% ersetzt, welcher je nach Ziel entweder \"255.255.255.255\" ist, oder bei einem netz\u00fcbergreifenden WOL-Paket die \"directed broadcast address\" des Zielnetzes. Netz\u00fcbergreifende WOL-Pakete werden vom \"etherwake\" nicht unterst\u00fctzt.",
+ "lang_wakeupScript": "Aufweck-Script",
+ "lang_wolReachability": "WOL-Erreichbarkeit"
} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/lang/en/module.json b/modules-available/rebootcontrol/lang/en/module.json
index 1f325354..47d91e98 100644
--- a/modules-available/rebootcontrol/lang/en/module.json
+++ b/modules-available/rebootcontrol/lang/en/module.json
@@ -1,4 +1,4 @@
{
- "module_name": "Reboot Control",
- "page_title": "Reboot Control"
+ "module_name": "WakeOnLAN",
+ "page_title": "WakeOnLAN"
} \ 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
index c64014ff..9c7830aa 100644
--- a/modules-available/rebootcontrol/lang/en/template-tags.json
+++ b/modules-available/rebootcontrol/lang/en/template-tags.json
@@ -9,9 +9,11 @@
"lang_genNew": "Generate new keypair",
"lang_ip": "IP",
"lang_kexecRebootCheck": "Quick reboot straight to bwLehrpool (kexec)",
+ "lang_keypairConfirmCheck": "I'm sure",
"lang_location": "Location",
"lang_minutes": " Minutes",
"lang_mode": "Mode",
+ "lang_moduleHeading": "WakeOnLAN",
"lang_newKeypairExplanation": "You can create a new keypair, which will replace the old one. Please note that after doing so, you cannot poweroff or reboot clients that are already running, since they still use the old key. They have to be rebooted manually first.",
"lang_off": "Off",
"lang_on": "On",
diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php
index 3a438504..67996f02 100644
--- a/modules-available/rebootcontrol/page.inc.php
+++ b/modules-available/rebootcontrol/page.inc.php
@@ -3,8 +3,6 @@
class Page_RebootControl extends Page
{
- private $action = false;
-
/**
* Called before any page rendering happens - early hook to check parameters etc.
*/
@@ -17,62 +15,131 @@ class Page_RebootControl extends Page
Util::redirect('?do=Main'); // does not return
}
- $this->action = Request::any('action', 'show', 'string');
+ $action = Request::post('action', 'show', 'string');
+ if ($action === 'reboot' || $action === 'shutdown') {
+ $this->execRebootShutdown($action);
+ } elseif ($action === 'savejumphost') {
+ $this->saveJumpHost();
+ } elseif ($action === 'jumphost') {
+ $this->postJumpHostDispatch();
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=rebootcontrol');
+ }
+ }
- if ($this->action === 'reboot' || $this->action === 'shutdown') {
+ private function execRebootShutdown($action)
+ {
+ $requestedClients = Request::post('clients', false, 'array');
+ if (!is_array($requestedClients) || empty($requestedClients)) {
+ Message::addError('no-clients-selected');
+ return;
+ }
- $requestedClients = Request::post('clients', false, 'array');
- if (!is_array($requestedClients) || empty($requestedClients)) {
- Message::addError('no-clients-selected');
- Util::redirect();
+ $actualClients = RebootQueries::getMachinesByUuid($requestedClients);
+ if (count($actualClients) !== count($requestedClients)) {
+ // 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');
+ }
+ // Filter ones with no permission
+ foreach (array_keys($actualClients) as $idx) {
+ if (!User::hasPermission('action.' . $action, $actualClients[$idx]['locationid'])) {
+ Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']);
+ unset($actualClients[$idx]);
+ } else {
+ $locationId = $actualClients[$idx]['locationid'];
}
+ }
+ // See if anything is left
+ if (!is_array($actualClients) || empty($actualClients)) {
+ Message::addError('no-clients-selected');
+ return;
+ }
+ usort($actualClients, function($a, $b) {
+ $a = ($a['state'] === 'IDLE' || $a['state'] === 'OCCUPIED');
+ $b = ($b['state'] === 'IDLE' || $b['state'] === 'OCCUPIED');
+ if ($a === $b)
+ return 0;
+ return $a ? -1 : 1;
+ });
+ if ($action === 'shutdown') {
+ $mode = 'SHUTDOWN';
+ $minutes = Request::post('s-minutes', 0, 'int');
+ } elseif (Request::any('quick', false, 'string') === 'on') {
+ $mode = 'KEXEC_REBOOT';
+ $minutes = Request::post('r-minutes', 0, 'int');
+ } else {
+ $mode = 'REBOOT';
+ $minutes = Request::post('r-minutes', 0, 'int');
+ }
+ $task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
+ if (Taskmanager::isTask($task)) {
+ Util::redirect("?do=rebootcontrol&show=task&taskid=" . $task["id"]);
+ }
+ return;
+ }
- $actualClients = RebootQueries::getMachinesByUuid($requestedClients);
- if (count($actualClients) !== count($requestedClients)) {
- // 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');
- }
- // Filter ones with no permission
- foreach (array_keys($actualClients) as $idx) {
- if (!User::hasPermission('action.' . $this->action, $actualClients[$idx]['locationid'])) {
- Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']);
- unset($actualClients[$idx]);
- } else {
- $locationId = $actualClients[$idx]['locationid'];
+ private function saveJumpHost()
+ {
+ User::assertPermission('jumphost.edit');
+ $id = Request::post('hostid', Request::REQUIRED, 'string');
+ $host = Request::post('host', Request::REQUIRED, 'string');
+ $port = Request::post('port', Request::REQUIRED, 'int');
+ if ($port < 1 || $port > 65535) {
+ Message::addError('invalid-port', $port);
+ return;
+ }
+ $username = Request::post('username', Request::REQUIRED, 'string');
+ $sshkey = Request::post('sshkey', Request::REQUIRED, 'string');
+ $script = preg_replace('/\r\n?/', "\n", Request::post('script', Request::REQUIRED, 'string'));
+ if ($id === 'new') {
+ $ret = Database::exec('INSERT INTO reboot_jumphost (host, port, username, sshkey, script, reachable)
+ VALUE (:host, :port, :username, :sshkey, :script, 0)', compact('host', 'port', 'username', 'sshkey', 'script'));
+ $id = Database::lastInsertId();
+ } else {
+ $ret = Database::exec('UPDATE reboot_jumphost SET
+ host = :host, port = :port, username = :username, sshkey = :sshkey, script = :script, reachable = 0
+ WHERE hostid = :id', compact('host', 'port', 'username', 'sshkey', 'script', 'id'));
+ if ($ret === 0) {
+ $ret = Database::queryFirst('SELECT hostid FROM reboot_jumphost WHERE hostid = :id', ['id' => $id]);
+ if ($ret !== false) {
+ $ret = 1;
}
}
- // See if anything is left
- if (!is_array($actualClients) || empty($actualClients)) {
- Message::addError('no-clients-selected');
- Util::redirect();
- }
- usort($actualClients, function($a, $b) {
- $a = ($a['state'] === 'IDLE' || $a['state'] === 'OCCUPIED');
- $b = ($b['state'] === 'IDLE' || $b['state'] === 'OCCUPIED');
- if ($a === $b)
- return 0;
- return $a ? -1 : 1;
- });
- if ($this->action === 'shutdown') {
- $mode = 'SHUTDOWN';
- $minutes = Request::post('s-minutes', 0, 'int');
- } elseif (Request::any('quick', false, 'string') === 'on') {
- $mode = 'KEXEC_REBOOT';
- $minutes = Request::post('r-minutes', 0, 'int');
- } else {
- $mode = 'REBOOT';
- $minutes = Request::post('r-minutes', 0, 'int');
- }
- $task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
- if (Taskmanager::isTask($task)) {
- Util::redirect("?do=rebootcontrol&taskid=" . $task["id"]);
- } else {
- Util::redirect("?do=rebootcontrol");
- }
}
+ if ($ret > 0) {
+ Message::addSuccess('jumphost-saved', $id);
+ $this->execCheckConnection($id);
+ } else {
+ Message::addError('no-such-jumphost', $id);
+ }
+ }
+
+ private function postJumpHostDispatch()
+ {
+ $id = Request::post('checkid', false, 'int');
+ if ($id !== false) {
+ // Check connectivity
+ $this->execCheckConnection($id);
+ return;
+ }
+ }
+ private function execCheckConnection($hostid)
+ {
+ $host = $this->getJumpHost($hostid);
+ $script = str_replace(['%IP%', '%MACS%'], ['255.255.255.255', '00:11:22:33:44:55'], $host['script']);
+ $task = RebootControl::runScript([[
+ 'clientip' => $host['host'],
+ 'port' => $host['port'],
+ 'username' => $host['username'],
+ ]], $script, 5, $host['sshkey']);
+ if (!Taskmanager::isTask($task))
+ return;
+ TaskmanagerCallback::addCallback($task, 'rbcConnCheck', $hostid);
+ Util::redirect('?do=rebootcontrol&show=task&type=checkhost&taskid=' . $task['id']);
}
/**
@@ -81,86 +148,150 @@ class Page_RebootControl extends Page
protected function doRender()
{
- if ($this->action === 'show') {
-
- $data = [];
- $task = Request::get("taskid", false, 'string');
- if ($task !== false) {
- $task = Taskmanager::status($task);
- }
+ $show = Request::get('show', 'jumphosts', 'string');
- if (Taskmanager::isTask($task)) {
+ // Always show public key (it's public, isn't it?)
+ $data = ['pubkey' => SSHKey::getPublicKey()];
+ Permission::addGlobalTags($data['perms'], null, ['newkeypair']);
+ Render::addTemplate('header', $data);
- $data['taskId'] = $task['id'];
- $data['locationId'] = $task['data']['locationId'];
- $data['locationName'] = Location::getName($task['data']['locationId']);
- $uuids = array_map(function($entry) {
- return $entry['machineuuid'];
- }, $task['data']['clients']);
- $data['clients'] = RebootQueries::getMachinesByUuid($uuids);
- Render::addTemplate('status', $data);
-
- } else {
-
- //location you want to see, default are "not assigned" clients
- $requestedLocation = Request::get('location', false, 'int');
- $allowedLocs = User::getAllowedLocations("action.*");
- if (empty($allowedLocs)) {
- User::assertPermission('action.*');
- }
+ if ($show === 'task') {
+ $this->showTask();
+ } elseif ($show === 'jumphosts') {
+ $this->showJumpHosts();
+ } elseif ($show === 'jumphost') {
+ $this->showJumpHost();
+ }
+ }
- if ($requestedLocation === false) {
- if (in_array(0, $allowedLocs)) {
- $requestedLocation = 0;
- } else {
- $requestedLocation = reset($allowedLocs);
- }
- }
+ private function showTask()
+ {
+ $taskid = Request::get("taskid", Request::REQUIRED, 'string');
+ $task = Taskmanager::status($taskid);
- $data['locations'] = Location::getLocations($requestedLocation, 0, true);
+ if (!Taskmanager::isTask($task) || !isset($task['data'])) {
+ Message::addError('no-such-task', $taskid);
+ return;
+ }
- // disable each location user has no permission for
- foreach ($data['locations'] as &$loc) {
- if (!in_array($loc["locationid"], $allowedLocs)) {
- $loc["disabled"] = "disabled";
- } elseif ($loc["locationid"] == $requestedLocation) {
- $data['location'] = $loc['locationname'];
- }
- }
- // Always show public key (it's public, isn't it?)
- $data['pubKey'] = SSHKey::getPublicKey();
+ $td =& $task['data'];
+ $type = Request::get('type', false, 'string');
+ if ($type === false) {
+ // Try to guess
+ if (isset($td['locationId']) || isset($td['clients'])) {
+ $type = 'reboot';
+ } elseif (isset($td['result'])) {
+ $type = 'exec';
+ }
+ }
+ if ($type === 'reboot') {
+ $data = [
+ 'taskId' => $task['id'],
+ 'locationId' => $td['locationId'],
+ 'locationName' => Location::getName($td['locationId']),
+ ];
+ $uuids = array_map(function ($entry) {
+ return $entry['machineuuid'];
+ }, $td['clients']);
+ $data['clients'] = RebootQueries::getMachinesByUuid($uuids);
+ Render::addTemplate('status-reboot', $data);
+ } elseif ($type === 'exec') {
+ $data = [
+ 'taskId' => $task['id'],
+ ];
+ Render::addTemplate('status-exec', $data);
+ } elseif ($type === 'checkhost') {
+ $ip = array_key_first($td['result']);
+ $data = [
+ 'taskId' => $task['id'],
+ 'host' => $ip,
+ ];
+ Render::addTemplate('status-checkconnection', $data);
+ } else {
+ Message::addError('unknown-task-type');
+ }
+ }
- // Only enable shutdown/reboot-button if user has permission for the location
- Permission::addGlobalTags($data['perms'], $requestedLocation, ['newkeypair', 'action.shutdown', 'action.reboot']);
+ private function showJumpHosts()
+ {
+ User::assertPermission('jumphost.*');
+ $hosts = [];
+ $res = Database::simpleQuery('SELECT hostid, host, port, Count(jxs.subnetid) AS subnetCount, reachable
+ FROM reboot_jumphost jh
+ LEFT JOIN reboot_jumphost_x_subnet jxs USING (hostid)
+ GROUP BY hostid
+ ORDER BY hostid');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $hosts[] = $row;
+ }
+ $data = [
+ 'jumpHosts' => $hosts
+ ];
+ Permission::addGlobalTags($data['perms'], null, ['jumphost.edit', 'jumphost.assign-subnet']);
+ Render::addTemplate('jumphost-list', $data);
- Render::addTemplate('header', $data);
+ // Append list of active reboot/shutdown tasks
+ $allowedLocs = User::getAllowedLocations("action.*");
+ $active = RebootControl::getActiveTasks($allowedLocs);
+ if (!empty($active)) {
+ foreach ($active as &$entry) {
+ $entry['locationName'] = Location::getName($entry['locationId']);
+ }
+ unset($entry);
+ Render::addTemplate('task-list', ['list' => $active]);
+ }
- // only fill table if user has at least one permission for the location
- if (!in_array($requestedLocation, $allowedLocs)) {
- Message::addError('locations.no-permission-location', $requestedLocation);
- } else {
- $data['data'] = RebootQueries::getMachineTable($requestedLocation);
- Render::addTemplate('_page', $data);
- }
+ }
- // Append list of active reboot/shutdown tasks
- $active = RebootControl::getActiveTasks($allowedLocs);
- if (!empty($active)) {
- foreach ($active as &$entry) {
- $entry['locationName'] = Location::getName($entry['locationId']);
- }
- unset($entry);
- Render::addTemplate('task-list', ['list' => $active]);
- }
+ private function showJumpHost()
+ {
+ User::assertPermission('jumphost.edit');
+ $id = Request::get('id', Request::REQUIRED, 'string');
+ if ($id === 'new') {
+ $host = ['hostid' => 'new', 'port' => 22, 'script' => "# Assume bash\n"
+ . "MACS='%MACS%'\n"
+ . "IP='%IP'\n"
+ . "EW=false\n"
+ . "WOL=false\n"
+ . "command -v etherwake > /dev/null && ( [ \"\$(id -u)\" = 0 ] || [ -u \"\$(which etherwake)\" ] ) && EW=true\n"
+ . "command -v wakeonlan > /dev/null && WOL=true\n"
+ . "if \$EW && ( ! \$WOL || [ \"\$IP\" = '255.255.255.255' ] ); then\n"
+ . "\tifaces=\"\$(ls -1 /sys/class/net/)\"\n"
+ . "\t[ -z \"\$ifaces\" ] && ifaces=eth0\n"
+ . "\tfor ifc in \$ifaces; do\n"
+ . "\t\t[ \"\$ifc\" = 'lo' ] && continue\n"
+ . "\t\tfor mac in \$MACS; do\n"
+ . "\t\t\tetherwake -i \"\$ifc\" \"\$mac\"\n"
+ . "\t\tdone\n"
+ . "\tdone\n"
+ . "elif \$WOL; then\n"
+ . "\twakeonlan -i \"\$IP\" \$MACS\n"
+ . "else\n"
+ . "\techo 'No suitable WOL tool found' >&2\n"
+ . "\texit 1\n"
+ . "fi\n"];
+ } else {
+ $host = $this->getJumpHost($id);
+ }
+ Render::addTemplate('jumphost-edit', $host);
+ }
- }
+ private function getJumpHost($hostid)
+ {
+ $host = Database::queryFirst('SELECT hostid, host, port, username, sshkey, script
+ FROM reboot_jumphost
+ WHERE hostid = :id', ['id' => $hostid]);
+ if ($host === false) {
+ Message::addError('no-such-jumphost', $hostid);
+ Util::redirect('?do=rebootcontrol');
}
+ return $host;
}
- function doAjax()
+ protected function doAjax()
{
- $this->action = Request::post('action', false, 'string');
- if ($this->action === 'generateNewKeypair') {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'generateNewKeypair') {
User::assertPermission("newkeypair");
Property::set("rebootcontrol-private-key", false);
echo SSHKey::getPublicKey();
@@ -169,6 +300,14 @@ class Page_RebootControl extends Page
}
}
+}
-
+// Remove when we require >= 7.3.0
+if (!function_exists('array_key_first')) {
+ function array_key_first(array $arr) {
+ foreach($arr as $key => $unused) {
+ return $key;
+ }
+ return NULL;
+ }
}
diff --git a/modules-available/rebootcontrol/permissions/permissions.json b/modules-available/rebootcontrol/permissions/permissions.json
index a058ffbf..5983447e 100644
--- a/modules-available/rebootcontrol/permissions/permissions.json
+++ b/modules-available/rebootcontrol/permissions/permissions.json
@@ -2,10 +2,31 @@
"newkeypair": {
"location-aware": false
},
+ "subnet.view": {
+ "location-aware": false
+ },
+ "subnet.edit": {
+ "location-aware": false
+ },
+ "subnet.flag": {
+ "location-aware": false
+ },
+ "jumphost.view": {
+ "location-aware": false
+ },
+ "jumphost.edit": {
+ "location-aware": false
+ },
+ "jumphost.assign-subnet": {
+ "location-aware": false
+ },
"action.reboot": {
"location-aware": true
},
"action.shutdown": {
"location-aware": true
+ },
+ "action.wol": {
+ "location-aware": true
}
} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html
deleted file mode 100644
index a124e165..00000000
--- a/modules-available/rebootcontrol/templates/_page.html
+++ /dev/null
@@ -1,184 +0,0 @@
-<h3>{{location}}</h3>
-
-<form method="post" action="?do=rebootcontrol" class="form-inline">
- <input type="hidden" name="token" value="{{token}}">
- <div class="row">
- <div class="col-md-12">
- <table class="table table-condensed table-hover stupidtable" id="dataTable">
- <thead>
- <tr>
- <th data-sort="string">{{lang_client}}</th>
- <th data-sort="ipv4">{{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}}
- <span class="text-success">{{lang_on}}</span>
- {{/status}}
- {{^status}}
- <span class="text-danger">{{lang_off}}</span>
- {{/status}}
- </td>
- <td>{{#status}}{{currentsession}}{{/status}}</td>
- <td>{{#status}}{{currentuser}}{{/status}}</td>
- <td data-sort-value="0" class="checkboxColumn slx-smallcol">
- <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 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">&times;</span></button>
- <h4 class="modal-title" id="myModalLabel">{{lang_rebootButton}}</h4>
- </div>
- <div class="modal-body">
- <div>{{lang_rebootCheck}}</div>
- <div>{{lang_rebootIn}} <input name="r-minutes" title="{{lang_shutdownIn}}" type="number" value="0" min="0" pattern="\d+"> {{lang_minutes}}</div>
- <div>
- <div class="checkbox checkbox-inline">
- <input name="quick" type="checkbox" value="on" id="rb-quick">
- <label for="rb-quick">{{lang_kexecRebootCheck}}</label>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
- <button type="submit" {{perms.action.reboot.disabled}} name="action" value="reboot" 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="myModalLabel2">
- <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">&times;</span></button>
- <h4 class="modal-title" id="myModalLabel2">{{lang_shutdownButton}}</h4>
- </div>
- <div class="modal-body">
- <div>{{lang_shutdownCheck}}</div>
- {{lang_shutdownIn}} <input name="s-minutes" title="{{lang_shutdownIn}}" type="number" value="0" min="0" pattern="\d+"> {{lang_minutes}}
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
- <button type="submit" {{perms.action.shutdown.disabled}} name="action" value="shutdown" class="btn btn-danger">
- <span class="glyphicon glyphicon-off"></span>
- {{lang_shutdownButton}}
- </button>
- </div>
- </div>
- </div>
- </div>
-</form>
-
-
-<script type="application/javascript">
- var $dataTable;
-
- document.addEventListener("DOMContentLoaded", function() {
-
- $dataTable = $("#dataTable");
- markCheckedRows();
- // Handle change of checkboxes in table
- $('input:checkbox').change(function() {
- var $this = $(this);
- //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 unchecked = $dataTable.find("input:checkbox:not(:checked)").length;
- var checked = $dataTable.find("input:checkbox:checked").length;
- if (unchecked === 0) {
- $('#selectAllButton').hide();
- $('#unselectAllButton').show();
- } else if (checked === 0) {
- $('#selectAllButton').show();
- $('#unselectAllButton').hide();
- }
-
- //if no client is selected, disable the shutdown/reboot button, and enable them if a client is selected
- $('#rebootButton').prop('disabled', checked === 0 || '{{perms.action.reboot.disabled}}' === 'disabled');
- $('#shutdownButton').prop('disabled', checked === 0 || '{{perms.action.shutdown.disabled}}' === 'disabled');
- });
- // Propagate click on column with checkbox to checkbox
- $('.checkboxColumn').click(function(e) {
- if (e.target === this) {
- $(this).find('input:checkbox').click();
- }
- });
- // Arm the (de)select all buttons
- $('#selectAllButton').click(function() { selectAllRows(true); });
- $('#unselectAllButton').click(function() { selectAllRows(false); });
- });
-
- // Check all checkboxes, change selectAll button, make shutdown/reboot button enabled as clients will certainly be selected
- function selectAllRows(selected) {
- var $box = $dataTable.find('input:checkbox');
- if ($box.length === 0) return;
- if (selected) {
- $box = $box.filter(':not(:checked)');
- } else {
- $box = $box.filter(':checked');
- }
- if ($box.length === 0) return;
- $box.prop('checked', !!selected).trigger('change');
- }
-
- // mark all previous checked rows (used when loading site), enable (de)select all if list is not empty
- function markCheckedRows() {
- var $checked = $dataTable.find("input:checkbox:checked");
- markRows($checked.closest("tr"), true);
- var $unchecked = $dataTable.find("input:checkbox:not(:checked)");
- markRows($unchecked.closest("tr"), false);
- if($unchecked.length === 0) {
- $('#selectAllButton').hide();
- $('#unselectAllButton').show();
- }
- if ($unchecked.length > 0 || $checked.length > 0) {
- $('.select-button').prop('disabled', false);
- }
- }
-
- function markRows($rows, marked) {
- if (marked) {
- $rows.addClass('active');
- } else {
- $rows.removeClass('active');
- }
- }
-
-</script> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/header.html b/modules-available/rebootcontrol/templates/header.html
index e171ccd6..4f240b81 100644
--- a/modules-available/rebootcontrol/templates/header.html
+++ b/modules-available/rebootcontrol/templates/header.html
@@ -1,34 +1,9 @@
<div class="page-header">
- <button type="button" id="settingsButton" class="btn btn-default pull-right" data-toggle="modal" data-target="#settingsModal"><span class="glyphicon glyphicon-cog"></span> {{lang_settings}}</button>
- <h1>{{lang_rebootControl}}</h1>
-</div>
-
-<div>
- <label>{{lang_location}}:
- <select id="locationDropdown" class="form-control" onchange="selectLocation()">
- {{#locations}}
- <option value="{{locationid}}" {{disabled}} {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
- {{/locations}}
- </select>
- </label>
- <div class="pull-right">
- <button type="button" id="shutdownButton" class="btn btn-danger action-button" data-toggle="modal" data-target="#shutdownModal" disabled>
- <span class="glyphicon glyphicon-off"></span>
- {{lang_shutdownButton}}
- </button>
- <button type="button" id="rebootButton" class="btn btn-warning action-button" data-toggle="modal" data-target="#rebootModal" disabled>
- <span class="glyphicon glyphicon-repeat"></span>
- {{lang_rebootButton}}
- </button>
- <button type="button" id="selectAllButton" class="btn btn-primary select-button" disabled>
- <span class="glyphicon glyphicon-check"></span>
- {{lang_selectall}}
- </button>
- <button type="button" id="unselectAllButton" class="btn btn-default select-button collapse" disabled>
- <span class="glyphicon glyphicon-unchecked"></span>
- {{lang_unselectall}}
- </button>
- </div>
+ <button type="button" id="settingsButton" class="btn btn-default pull-right" data-toggle="modal" data-target="#settingsModal">
+ <span class="glyphicon glyphicon-cog"></span>
+ {{lang_settings}}
+ </button>
+ <h1>{{lang_moduleHeading}}</h1>
</div>
<div id="settingsModal" class="modal fade" role="dialog">
@@ -41,11 +16,16 @@
</div>
<div class="modal-body">
<p>{{lang_pubKey}}</p>
- <pre id="pubkey">{{pubKey}}</pre>
+ <pre id="pubkey">{{pubkey}}</pre>
<p>{{lang_newKeypairExplanation}}</p>
+ <div class="checkbox">
+ <input {{perms.newkeypair.disabled}} type="checkbox" id="keypair-confirm">
+ <label for="keypair-confirm">{{lang_keypairConfirmCheck}}</label>
+ </div>
</div>
<div class="modal-footer">
- <button {{perms.newkeypair.disabled}} class="btn btn-danger pull-right" onclick="generateNewKeypair()" type="button">
+ <button {{perms.newkeypair.disabled}} class="btn btn-danger pull-right" id="keypair-button"
+ onclick="generateNewKeypair()" type="button">
<span class="glyphicon glyphicon-refresh"></span>
{{lang_genNew}}
</button>
@@ -55,25 +35,33 @@
</div>
<script type="application/javascript">
-
- // Change Location when selected in Dropdown Menu
- function selectLocation() {
- var dropdown = $("#locationDropdown");
- var location = dropdown.val();
- window.location.replace("?do=rebootcontrol&location="+location);
- }
-
- function generateNewKeypair() {
- if (!confirm('{{lang_confirmNewKeypair}}'))
+document.addEventListener('DOMContentLoaded', function() {
+ var $btn = $('#keypair-button');
+ var $chk = $('#keypair-confirm');
+ $chk.prop('checked', false); // Firefox helpfully keeping state on F5
+ $btn.click(function() {
+ if (!$chk.is(':checked')) {
+ var $p = $chk.parent();
+ $p.fadeOut(100, function () {
+ $p.fadeIn(75);
+ });
return;
+ }
+ $btn.prop('disabled', true);
$.ajax({
url: '?do=rebootcontrol',
type: 'POST',
data: { action: "generateNewKeypair", token: TOKEN },
success: function(value) {
$('#pubkey').text(value);
+ },
+ fail: function() {
+ $('#pubkey').text('Error');
+ $btn.prop('disabled', false);
}
});
- }
+ });
+});
+
</script> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/jumphost-edit.html b/modules-available/rebootcontrol/templates/jumphost-edit.html
new file mode 100644
index 00000000..82629d2e
--- /dev/null
+++ b/modules-available/rebootcontrol/templates/jumphost-edit.html
@@ -0,0 +1,42 @@
+<h2>{{lang_editJumpHost}}</h2>
+
+<form method="post" action="?do=rebootcontrol">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="savejumphost">
+ <input type="hidden" name="hostid" value="{{hostid}}">
+ <div class="list-group">
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-md-9 col-sm-7">
+ <label for="host">{{lang_host}}</label>
+ <input required id="host" class="form-control" type="text" name="host" value="{{host}}">
+ </div>
+ <div class="col-md-3 col-sm-5">
+ <label for="port">{{lang_port}}</label>
+ <input required id="port" class="form-control" type="number" name="port" value="{{port}}" min="1" max="65535">
+ </div>
+ </div>
+ </div>
+ <div class="list-group-item">
+ <label for="username">{{lang_username}}</label>
+ <input required id="username" class="form-control" type="text" name="username" value="{{username}}">
+ </div>
+ <div class="list-group-item">
+ <label for="sshkey">{{lang_privkey}}</label>
+ <textarea required id="sshkey" class="form-control" name="sshkey" rows="8">{{sshkey}}</textarea>
+ </div>
+ <div class="list-group-item">
+ <label for="script">{{lang_wakeupScript}}</label>
+ <textarea required id="script" class="form-control" name="script" rows="8">{{script}}</textarea>
+ <div class="slx-smallspace"></div>
+ <p>{{lang_wakeScriptHelp}}</p>
+ </div>
+ </div>
+ <div class="buttonbar text-right">
+ <button type="reset" class="btn btn-default">{{lang_reset}}</button>
+ <button type="submit" class="btn btn-primary">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+</form> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/jumphost-list.html b/modules-available/rebootcontrol/templates/jumphost-list.html
new file mode 100644
index 00000000..3fbe4c21
--- /dev/null
+++ b/modules-available/rebootcontrol/templates/jumphost-list.html
@@ -0,0 +1,64 @@
+<h2>{{lang_settings}}</h2>
+
+<h3>{{lang_wolReachability}}</h3>
+
+<h4>{{lang_jumpHosts}}</h4>
+
+<form method="post" action="?do=rebootcontrol">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="jumphost">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_host}}</th>
+ <th class="slx-smallcol">{{lang_assignedSubnets}}</th>
+ <th class="slx-smallcol">{{lang_reachable}}</th>
+ <th class="slx-smallcol"></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#jumpHosts}}
+ <tr>
+ <td>{{host}}:{{port}}</td>
+ <td class="text-nowrap text-right">
+ {{subnetCount}}
+ <a class="btn btn-xs btn-default {{perms.jumphost.assign-subnet.disabled}}"
+ href="?do=rebootcontrol&amp;show=jumphost-assign&amp;id={{hostid}}">
+ <span class="glyphicon glyphicon-edit"></span>
+ </a>
+ </td>
+ <td class="text-nowrap text-center">
+ {{#reachable}}
+ <span class="glyphicon glyphicon-ok text-success"></span>
+ {{/reachable}}
+ {{^reachable}}
+ <span class="glyphicon glyphicon-remove text-danger"></span>
+ {{/reachable}}
+ <button class="btn btn-xs btn-default btn-check-jumphost" type="submit" name="checkid" value="{{hostid}}"
+ {{perms.jumphost.edit.disabled}}>
+ {{lang_check}}
+ </button>
+ </td>
+ <td class="text-nowrap text-center">
+ <a class="btn btn-xs btn-default {{perms.jumphost.edit.disabled}}"
+ href="?do=rebootcontrol&amp;show=jumphost&amp;id={{hostid}}">
+ <span class="glyphicon glyphicon-edit"></span>
+ </a>
+ <button type="submit" name="deleteid" value="{{hostid}}" class="btn btn-xs btn-danger"
+ data-confirm="#confirm-delete-host" data-title="{{host}}" {{perms.jumphost.edit.disabled}}>
+ <span class="glyphicon glyphicon-trash"></span>
+ </button>
+ </td>
+ </tr>
+ {{/jumpHosts}}
+ </tbody>
+ </table>
+</form>
+<div class="buttonbar text-right">
+ <a class="btn btn-success" href="?do=rebootcontrol&amp;show=jumphost&amp;id=new">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_new}}
+ </a>
+</div>
+
+<div class="hidden" id="confirm-delete-host">{{lang_hostDeleteConfirm}}</div> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/status-checkconnection.html b/modules-available/rebootcontrol/templates/status-checkconnection.html
new file mode 100644
index 00000000..c30b42d9
--- /dev/null
+++ b/modules-available/rebootcontrol/templates/status-checkconnection.html
@@ -0,0 +1,51 @@
+<h3>{{lang_checkingJumpHost}}: {{host}}</h3>
+
+<a href="?do=rebootcontrol" class="btn btn-primary pull-right">
+ <span class="glyphicon glyphicon-arrow-left"></span>
+ {{lang_back}}
+</a>
+<div class="clearfix"></div>
+<div class="collapse alert alert-success" id="result-ok">
+ <span class="glyphicon glyphicon-check"></span>
+ {{lang_hostReachable}}
+</div>
+<div class="collapse alert alert-warning" id="result-error">
+ <span class="glyphicon glyphicon-remove"></span>
+ {{lang_hostNonZeroExit}}
+</div>
+<div class="collapse alert alert-danger" id="result-unreach">
+ <span class="glyphicon glyphicon-remove"></span>
+ {{lang_hostNotReachable}}
+</div>
+
+<div class="collapse" id="log-wrapper">
+ <label for="log-output">{{lang_checkOutputLabel}}</label>
+ <pre id="log-output"></pre>
+</div>
+
+<div data-tm-id="{{taskId}}" data-tm-log="error" data-tm-callback="updateStatus">{{lang_checkingJumpHost}}</div>
+<script type="application/javascript">
+ function updateStatus(task) {
+ if (!task || !task.data || !task.data.result || !task.data.result['{{host}}'])
+ return;
+ var status = task.data.result['{{host}}'];
+ var log = '';
+ if (status.stderr) log += status.stderr + "\n";
+ if (status.stdout) log += status.stdout + "\n";
+ showErrorLog(log);
+ if (task.statusCode === 'TASK_FINISHED' || task.statusCode === 'TASK_ERROR') {
+ if (status.exitCode === 0) {
+ $('#result-ok').show();
+ } else if (status.exitCode > 0) {
+ $('#result-error').show();
+ } else {
+ $('#result-unreach').show();
+ }
+ }
+ }
+ function showErrorLog(log) {
+ if (!log) return;
+ $('#log-output').text(log);
+ $('#log-wrapper').show();
+ }
+</script>
diff --git a/modules-available/rebootcontrol/templates/status.html b/modules-available/rebootcontrol/templates/status-reboot.html
index c05b2fad..594faa4c 100644
--- a/modules-available/rebootcontrol/templates/status.html
+++ b/modules-available/rebootcontrol/templates/status-reboot.html
@@ -1,11 +1,9 @@
-<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>
+<h3>{{lang_location}}: {{locationName}}</h3>
+
+<a href="?do=rebootcontrol" class="btn btn-primary pull-right">
+ <span class="glyphicon glyphicon-arrow-left"></span>
+ {{lang_back}}
+</a>
<div>
<table class="table table-hover stupidtable" id="dataTable">
diff --git a/modules-available/rebootcontrol/templates/task-list.html b/modules-available/rebootcontrol/templates/task-list.html
index 063ba949..87515085 100644
--- a/modules-available/rebootcontrol/templates/task-list.html
+++ b/modules-available/rebootcontrol/templates/task-list.html
@@ -13,7 +13,7 @@
{{#list}}
<tr>
<td>
- <a href="?do=rebootcontrol&amp;taskid={{taskId}}">{{mode}}</a>
+ <a href="?do=rebootcontrol&amp;show=task&amp;taskid={{taskId}}">{{mode}}</a>
</td>
<td>
{{locationName}}