summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2019-11-27 17:25:46 +0100
committerSimon Rettberg2019-11-27 17:25:46 +0100
commitca312a6ace43a6fde58d1c09057c7b0bd34f15a2 (patch)
tree3cb4e5ac2b29f7fcc99dfda5df26f11a64caeaf3
parent[js_ip/locations] Mode cidr/ip handling to own module (diff)
downloadslx-admin-ca312a6ace43a6fde58d1c09057c7b0bd34f15a2.tar.gz
slx-admin-ca312a6ace43a6fde58d1c09057c7b0bd34f15a2.tar.xz
slx-admin-ca312a6ace43a6fde58d1c09057c7b0bd34f15a2.zip
[statistics/rebootcontrol] Implement editing subnet
-rw-r--r--inc/permission.inc.php2
-rw-r--r--modules-available/rebootcontrol/inc/rebootcontrol.inc.php6
-rw-r--r--modules-available/rebootcontrol/install.inc.php2
-rw-r--r--modules-available/rebootcontrol/lang/de/messages.json8
-rw-r--r--modules-available/rebootcontrol/lang/de/permissions.json13
-rw-r--r--modules-available/rebootcontrol/lang/de/template-tags.json4
-rw-r--r--modules-available/rebootcontrol/page.inc.php244
-rw-r--r--modules-available/rebootcontrol/pages/jumphost.inc.php167
-rw-r--r--modules-available/rebootcontrol/pages/subnet.inc.php150
-rw-r--r--modules-available/rebootcontrol/pages/task.inc.php101
-rw-r--r--modules-available/rebootcontrol/templates/jumphost-edit.html4
-rw-r--r--modules-available/rebootcontrol/templates/jumphost-list.html3
-rw-r--r--modules-available/rebootcontrol/templates/status-checkconnection.html4
-rw-r--r--modules-available/rebootcontrol/templates/status-reboot.html5
-rw-r--r--modules-available/rebootcontrol/templates/subnet-edit.html64
-rw-r--r--modules-available/rebootcontrol/templates/subnet-list.html58
-rw-r--r--modules-available/rebootcontrol/templates/task-list.html2
-rw-r--r--modules-available/statistics/api.inc.php24
-rw-r--r--modules-available/statistics/page.inc.php2
19 files changed, 629 insertions, 234 deletions
diff --git a/inc/permission.inc.php b/inc/permission.inc.php
index 3a7bdc36..7dd011bb 100644
--- a/inc/permission.inc.php
+++ b/inc/permission.inc.php
@@ -37,7 +37,7 @@ class Permission
continue;
$temp =& $temp[$sub];
}
- $temp = ['disabled' => 'disabled', 'readonly' => 'readonly'];
+ $temp = ['disabled' => 'disabled', 'readonly' => 'readonly', 'hidden' => 'hidden'];
}
if (!$one && !is_null($noneAvailDisabled)) {
$array[$noneAvailDisabled] = [
diff --git a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
index 680f9eff..8a85e3ff 100644
--- a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
+++ b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
@@ -123,13 +123,17 @@ class RebootControl
if ($privkey === false) {
$privkey = SSHKey::getPrivateKey();
}
- return Taskmanager::submit('RemoteExec', [
+ $task = Taskmanager::submit('RemoteExec', [
'clients' => $valid,
'command' => $command,
'timeoutSeconds' => $timeout,
'sshkey' => $privkey,
'port' => 9922, // Fallback if no port given in client struct
]);
+ if (!Taskmanager::isFailed($task)) {
+ Property::addToList(RebootControl::KEY_TASKLIST, '0/' . $task["id"], 60 * 24);
+ }
+ return $task;
}
public static function connectionCheckCallback($task, $hostId)
diff --git a/modules-available/rebootcontrol/install.inc.php b/modules-available/rebootcontrol/install.inc.php
index 0ce5dd23..eb484d3e 100644
--- a/modules-available/rebootcontrol/install.inc.php
+++ b/modules-available/rebootcontrol/install.inc.php
@@ -12,7 +12,7 @@ $output[] = tableCreate('reboot_subnet', "
`lastseen` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`seencount` INT(10) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`subnetid`),
- KEY `range` (`start`, `end`)");
+ UNIQUE KEY `range` (`start`, `end`)");
$output[] = tableCreate('reboot_jumphost', "
`hostid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
diff --git a/modules-available/rebootcontrol/lang/de/messages.json b/modules-available/rebootcontrol/lang/de/messages.json
index 2a7e1299..788e57a2 100644
--- a/modules-available/rebootcontrol/lang/de/messages.json
+++ b/modules-available/rebootcontrol/lang/de/messages.json
@@ -1,4 +1,10 @@
{
+ "invalid-port": "Ung\u00fcltiger Port: {{0}}",
+ "jumphost-saved": "Sprung-Host {{0}} gespeichert",
"no-clients-selected": "Keine Clients ausgew\u00e4hlt",
- "some-machine-not-found": "Einige Clients aus dem POST request wurden nicht gefunden"
+ "no-current-tasks": "Keine aktuellen oder k\u00fcrzlich abgeschlossenen Aufgaben",
+ "no-such-jumphost": "Sprung-Host {{0}} existiert nicht",
+ "no-such-task": "Task {{0}} existiert nicht",
+ "some-machine-not-found": "Einige Clients aus dem POST request wurden nicht gefunden",
+ "unknown-task-type": "Unbekannter Task-Typ"
} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/lang/de/permissions.json b/modules-available/rebootcontrol/lang/de/permissions.json
index 12ec4c83..fdc0b35f 100644
--- a/modules-available/rebootcontrol/lang/de/permissions.json
+++ b/modules-available/rebootcontrol/lang/de/permissions.json
@@ -1,5 +1,12 @@
{
- "action.shutdown": "Client herunterfahren.",
- "action.reboot": "Client neustarten.",
- "newkeypair": "Neues Schlüsselpaar generieren."
+ "action.reboot": "Client neustarten.",
+ "action.shutdown": "Client herunterfahren.",
+ "action.wol": "Client per WOL starten.",
+ "jumphost.assign-subnet": "Einem Sprung-Host ein Subnet zuweisen.",
+ "jumphost.edit": "Einen Sprung-Host bearbeiten.",
+ "jumphost.view": "Liste der Sprung-Hosts sehen.",
+ "newkeypair": "Neues Schl\u00fcsselpaar generieren.",
+ "subnet.edit": "Subnets hinzuf\u00fcgen\/entfernen.",
+ "subnet.flag": "Eigenschaften eines Subnets bearbeiten.",
+ "subnet.view": "Liste der Subnets sehen."
} \ 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 519c2286..365a739b 100644
--- a/modules-available/rebootcontrol/lang/de/template-tags.json
+++ b/modules-available/rebootcontrol/lang/de/template-tags.json
@@ -11,13 +11,13 @@
"lang_editJumpHost": "Sprung-Host bearbeiten",
"lang_error": "Nicht erreichbar",
"lang_genNew": "Neues Schl\u00fcsselpaar generieren",
- "lang_host": "Horst",
+ "lang_host": "Host",
"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_jumpHosts": "Spring-Hosts",
+ "lang_jumpHosts": "Sprung-Hosts",
"lang_keypairConfirmCheck": "Ich bin sicher",
"lang_location": "Standort",
"lang_mode": "Modus",
diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php
index 67996f02..eaa3c2e6 100644
--- a/modules-available/rebootcontrol/page.inc.php
+++ b/modules-available/rebootcontrol/page.inc.php
@@ -4,6 +4,11 @@ class Page_RebootControl extends Page
{
/**
+ * @var bool whether we have a SubPage from the pages/ subdir
+ */
+ private $haveSubpage = false;
+
+ /**
* Called before any page rendering happens - early hook to check parameters etc.
*/
protected function doPreprocess()
@@ -15,17 +20,34 @@ class Page_RebootControl extends Page
Util::redirect('?do=Main'); // does not return
}
- $action = Request::post('action', 'show', 'string');
+ if (User::hasPermission('jumphost.*')) {
+ Dashboard::addSubmenu('?do=rebootcontrol&show=jumphost', Dictionary::translate('jumphosts', true));
+ }
+ if (User::hasPermission('subnet.*')) {
+ Dashboard::addSubmenu('?do=rebootcontrol&show=subnet', Dictionary::translate('subnets', true));
+ }
+
+ $section = Request::any('show', false, 'string');
+ if ($section !== false) {
+ $section = preg_replace('/[^a-z]/', '', $section);
+ if (file_exists('modules/rebootcontrol/pages/' . $section . '.inc.php')) {
+ require_once 'modules/rebootcontrol/pages/' . $section . '.inc.php';
+ $this->haveSubpage = true;
+ SubPage::doPreprocess();
+ } else {
+ Message::addError('main.invalid-action', $section);
+ return;
+ }
+ } else {
+ $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 ($action === 'reboot' || $action === 'shutdown') {
+ $this->execRebootShutdown($action);
+ }
}
+
if (Request::isPost()) {
- Util::redirect('?do=rebootcontrol');
+ Util::redirect('?do=rebootcontrol' . ($section ? '&show=' . $section : ''));
}
}
@@ -76,216 +98,26 @@ class Page_RebootControl extends Page
}
$task = RebootControl::execute($actualClients, $mode, $minutes, $locationId);
if (Taskmanager::isTask($task)) {
- Util::redirect("?do=rebootcontrol&show=task&taskid=" . $task["id"]);
+ Util::redirect("?do=rebootcontrol&show=task&what=task&taskid=" . $task["id"]);
}
return;
}
- 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;
- }
- }
- }
- 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']);
- }
-
/**
* Menu etc. has already been generated, now it's time to generate page content.
*/
protected function doRender()
{
- $show = Request::get('show', 'jumphosts', 'string');
-
// Always show public key (it's public, isn't it?)
$data = ['pubkey' => SSHKey::getPublicKey()];
Permission::addGlobalTags($data['perms'], null, ['newkeypair']);
Render::addTemplate('header', $data);
- if ($show === 'task') {
- $this->showTask();
- } elseif ($show === 'jumphosts') {
- $this->showJumpHosts();
- } elseif ($show === 'jumphost') {
- $this->showJumpHost();
- }
- }
-
- private function showTask()
- {
- $taskid = Request::get("taskid", Request::REQUIRED, 'string');
- $task = Taskmanager::status($taskid);
-
- if (!Taskmanager::isTask($task) || !isset($task['data'])) {
- Message::addError('no-such-task', $taskid);
+ if ($this->haveSubpage) {
+ SubPage::doRender();
return;
}
-
- $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');
- }
- }
-
- 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);
-
- // 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]);
- }
-
- }
-
- 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;
}
protected function doAjax()
@@ -301,13 +133,3 @@ 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/pages/jumphost.inc.php b/modules-available/rebootcontrol/pages/jumphost.inc.php
new file mode 100644
index 00000000..111560ef
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/jumphost.inc.php
@@ -0,0 +1,167 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'save') {
+ self::saveJumpHost();
+ } elseif ($action === 'list') {
+ self::listAction();
+ }
+ }
+
+ /*
+ * POST
+ */
+
+ private static function listAction()
+ {
+ $id = Request::post('checkid', false, 'int');
+ if ($id !== false) {
+ // Check connectivity
+ self::execCheckConnection($id);
+ return;
+ }
+ }
+
+ private static function execCheckConnection($hostid)
+ {
+ $host = self::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&what=task&taskid=' . $task['id']);
+ }
+
+ private static 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;
+ }
+ }
+ }
+ if ($ret > 0) {
+ Message::addSuccess('jumphost-saved', $id);
+ self::execCheckConnection($id);
+ } else {
+ Message::addError('no-such-jumphost', $id);
+ }
+ }
+
+ /*
+ * Render
+ */
+
+ public static function doRender()
+ {
+ $id = Request::get('id', false, 'string');
+ if ($id !== false) {
+ self::showJumpHost($id);
+ } else {
+ self::showJumpHosts();
+ }
+ }
+
+ private static 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);
+ }
+
+ private static function showJumpHost($id)
+ {
+ User::assertPermission('jumphost.edit');
+ 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 = self::getJumpHost($id);
+ }
+ Render::addTemplate('jumphost-edit', $host);
+ }
+
+ public static function doAjax()
+ {
+
+ }
+
+ /*
+ * MISC
+ */
+
+ private static 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;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/pages/subnet.inc.php b/modules-available/rebootcontrol/pages/subnet.inc.php
new file mode 100644
index 00000000..946d2d64
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/subnet.inc.php
@@ -0,0 +1,150 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'add') {
+ self::addSubnet();
+ } elseif ($action === 'edit') {
+ self::editSubnet();
+ }
+ }
+
+ /*
+ * POST
+ */
+
+ private static function addSubnet()
+ {
+ User::assertPermission('subnet.edit');
+ $range = [];
+ foreach (['start', 'end'] as $key) {
+ $range[$key] = Request::post($key, Request::REQUIRED, 'string');
+ $range[$key . '_l'] = ip2long($range[$key]);
+ if ($range[$key . '_l'] === false) {
+ Message::addError('invalid-ip-address', $range[$key]);
+ return;
+ }
+ }
+ if ($range['start_l'] > $range['end_l']) {
+ Message::addError('invalid-range', $range['start'], $range['end']);
+ return;
+ }
+ $ret = Database::exec('INSERT INTO reboot_subnet (start, end, fixed, isdirect)
+ VALUES (:start, :end, 1, 0)', [
+ 'start' => sprintf('%u', $range['start_l']),
+ 'end' => sprintf('%u', $range['end_l']),
+ ], true);
+ if ($ret === false) {
+ Message::addError('subnet-already-exists');
+ } else {
+ Message::addSuccess('subnet-created');
+ Util::redirect('?do=rebootcontrol&show=subnet&what=subnet&id=' . Database::lastInsertId());
+ }
+ }
+
+ private static function editSubnet()
+ {
+ User::assertPermission('subnet.flag');
+ $id = Request::post('id', Request::REQUIRED, 'int');
+ $subnet = Database::queryFirst('SELECT subnetid
+ FROM reboot_subnet WHERE subnetid = :id', ['id' => $id]);
+ if ($subnet === false) {
+ Message::addError('invalid-subnet', $id);
+ return;
+ }
+ $params = [
+ 'id' => $id,
+ 'fixed' => !empty(Request::post('fixed', false, 'string')),
+ 'isdirect' => !empty(Request::post('isdirect', false, 'string')),
+ ];
+ Database::exec('UPDATE reboot_subnet SET fixed = :fixed, isdirect = If(:fixed, :isdirect, isdirect)
+ WHERE subnetid = :id', $params);
+ if (User::hasPermission('jumphost.assign-subnet')) {
+ $hosts = Request::post('jumphost', [], 'array');
+ if (!empty($hosts)) {
+ $hosts = array_keys($hosts);
+ Database::exec('DELETE FROM reboot_jumphost_x_subnet WHERE subnetid = :id AND hostid NOT IN (:hosts)',
+ ['id' => $id, 'hosts' => $hosts]);
+ $hosts = array_map(function($item) use ($id) {
+ return [$item, $id];
+ }, $hosts);
+ Database::exec('INSERT IGNORE INTO reboot_jumphost_x_subnet (hostid, subnetid) VALUES :hosts', ['hosts' => $hosts]);
+ }
+ }
+ Message::addSuccess('subnet-updated');
+ }
+
+ /*
+ * Render
+ */
+
+ public static function doRender()
+ {
+ $what = Request::get('what', 'list', 'string');
+ if ($what === 'list') {
+ self::showSubnets();
+ } elseif ($what === 'subnet') {
+ self::showSubnet();
+ }
+ }
+
+ private static function showSubnets()
+ {
+ User::assertPermission('subnet.*');
+ $nets = [];
+ $res = Database::simpleQuery('SELECT subnetid, start, end, fixed, isdirect,
+ lastdirectcheck, lastseen, seencount, Count(hxs.hostid) AS jumphostcount
+ FROM reboot_subnet
+ LEFT JOIN reboot_jumphost_x_subnet hxs USING (subnetid)
+ GROUP BY subnetid, start, end
+ ORDER BY start ASC, end DESC');
+ $deadline = strtotime('-60 days');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['start_s'] = long2ip($row['start']);
+ $row['end_s'] = long2ip($row['end']);
+ $row['lastseen_s'] = Util::prettyTime($row['lastseen']);
+ if ($row['lastseen'] && $row['lastseen'] < $deadline) {
+ $row['lastseen_class'] = 'text-danger';
+ }
+ $nets[] = $row;
+ }
+ $data = ['subnets' => $nets];
+ Render::addTemplate('subnet-list', $data);
+ Module::isAvailable('js_ip');
+ }
+
+ private static function showSubnet()
+ {
+ User::assertPermission('subnet.*');
+ $id = Request::get('id', Request::REQUIRED, 'int');
+ $subnet = Database::queryFirst('SELECT subnetid, start, end, fixed, isdirect
+ FROM reboot_subnet WHERE subnetid = :id', ['id' => $id]);
+ if ($subnet === false) {
+ Message::addError('invalid-subnet', $id);
+ return;
+ }
+ $subnet['start_s'] = long2ip($subnet['start']);
+ $subnet['end_s'] = long2ip($subnet['end']);
+ $res = Database::simpleQuery('SELECT h.hostid, h.host, h.port, hxs.subnetid FROM reboot_jumphost h
+ LEFT JOIN reboot_jumphost_x_subnet hxs ON (h.hostid = hxs.hostid AND hxs.subnetid = :id)
+ ORDER BY h.host ASC', ['id' => $id]);
+ $jh = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['checked'] = $row['subnetid'] === null ? '' : 'checked';
+ $jh[] = $row;
+ }
+ $subnet['jumpHosts'] = $jh;
+ Permission::addGlobalTags($subnet['perms'], null, ['subnet.flag', 'jumphost.view', 'jumphost.assign-subnet']);
+ Render::addTemplate('subnet-edit', $subnet);
+ }
+
+ public static function doAjax()
+ {
+
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/pages/task.inc.php b/modules-available/rebootcontrol/pages/task.inc.php
new file mode 100644
index 00000000..15449aaf
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/task.inc.php
@@ -0,0 +1,101 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+
+ }
+
+ public static function doRender()
+ {
+ $show = Request::get('what', 'tasklist', 'string');
+ if ($show === 'tasklist') {
+ self::showTaskList();
+ } elseif ($show === 'task') {
+ self::showTask();
+ }
+ }
+
+ private static function showTask()
+ {
+ $taskid = Request::get("taskid", Request::REQUIRED, 'string');
+ $task = Taskmanager::status($taskid);
+
+ if (!Taskmanager::isTask($task) || !isset($task['data'])) {
+ Message::addError('no-such-task', $taskid);
+ return;
+ }
+
+ $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');
+ }
+ }
+
+ private static function showTaskList()
+ {
+ // Append list of active reboot/shutdown tasks
+ $allowedLocs = User::getAllowedLocations("*");
+ $active = RebootControl::getActiveTasks($allowedLocs);
+ if (empty($active)) {
+ Message::addInfo('no-current-tasks');
+ } else {
+ foreach ($active as &$entry) {
+ $entry['locationName'] = Location::getName($entry['locationId']);
+ }
+ unset($entry);
+ Render::addTemplate('task-list', ['list' => $active]);
+ }
+ }
+
+ public static function doAjax()
+ {
+
+ }
+
+}
+
+
+// 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/templates/jumphost-edit.html b/modules-available/rebootcontrol/templates/jumphost-edit.html
index 82629d2e..7a79dc86 100644
--- a/modules-available/rebootcontrol/templates/jumphost-edit.html
+++ b/modules-available/rebootcontrol/templates/jumphost-edit.html
@@ -2,7 +2,7 @@
<form method="post" action="?do=rebootcontrol">
<input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="savejumphost">
+ <input type="hidden" name="show" value="jumphost">
<input type="hidden" name="hostid" value="{{hostid}}">
<div class="list-group">
<div class="list-group-item">
@@ -34,7 +34,7 @@
</div>
<div class="buttonbar text-right">
<button type="reset" class="btn btn-default">{{lang_reset}}</button>
- <button type="submit" class="btn btn-primary">
+ <button type="submit" class="btn btn-primary" name="action" value="save">
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
</button>
diff --git a/modules-available/rebootcontrol/templates/jumphost-list.html b/modules-available/rebootcontrol/templates/jumphost-list.html
index 3fbe4c21..8453d2bb 100644
--- a/modules-available/rebootcontrol/templates/jumphost-list.html
+++ b/modules-available/rebootcontrol/templates/jumphost-list.html
@@ -6,7 +6,8 @@
<form method="post" action="?do=rebootcontrol">
<input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="jumphost">
+ <input type="hidden" name="show" value="jumphost">
+ <input type="hidden" name="action" value="list">
<table class="table">
<thead>
<tr>
diff --git a/modules-available/rebootcontrol/templates/status-checkconnection.html b/modules-available/rebootcontrol/templates/status-checkconnection.html
index c30b42d9..e31d95ea 100644
--- a/modules-available/rebootcontrol/templates/status-checkconnection.html
+++ b/modules-available/rebootcontrol/templates/status-checkconnection.html
@@ -1,9 +1,5 @@
<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>
diff --git a/modules-available/rebootcontrol/templates/status-reboot.html b/modules-available/rebootcontrol/templates/status-reboot.html
index 594faa4c..4be95e81 100644
--- a/modules-available/rebootcontrol/templates/status-reboot.html
+++ b/modules-available/rebootcontrol/templates/status-reboot.html
@@ -1,10 +1,5 @@
<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">
<thead>
diff --git a/modules-available/rebootcontrol/templates/subnet-edit.html b/modules-available/rebootcontrol/templates/subnet-edit.html
new file mode 100644
index 00000000..4c3702ba
--- /dev/null
+++ b/modules-available/rebootcontrol/templates/subnet-edit.html
@@ -0,0 +1,64 @@
+<!-- subnetid, start, end, fixed, isdirect, lastdirectcheck, lastseen, seencount -->
+
+<form method="post" action="?do=rebootcontrol">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="show" value="subnet">
+ <input type="hidden" name="id" value="{{subnetid}}">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_editSubnet}}: <b>{{start_s}} - {{end_s}}</b>
+ </div>
+ <div class="list-group">
+ <div class="list-group-item">
+ <div class="checkbox">
+ <input id="fixed_cb" type="checkbox" name="fixed" {{#fixed}}checked{{/fixed}} {{perms.subnet.flag.disabled}}>
+ <label for="fixed_cb">{{lang_fixSubnetSettings}}</label>
+ </div>
+ <div class="slx-space"></div>
+ <p>{{lang_fixSubnetDesc}}</p>
+ </div>
+ <div class="list-group-item {{^fixed}}collapse{{/fixed}} subnet-option">
+ <div class="checkbox">
+ <input id="direct_cb" type="checkbox" name="isdirect" {{#isdirect}}checked{{/isdirect}} {{perms.subnet.flag.disabled}}>
+ <label for="direct_cb">{{lang_reachableFromServer}}</label>
+ </div>
+ <div class="slx-space"></div>
+ <p>{{lang_reachableFromServerDesc}}</p>
+ </div>
+ <div class="list-group-item {{perms.jumphost.view.hidden}}">
+ <label>{{lang_assignedJumpHosts}}</label>
+ {{#jumpHosts}}
+ <div class="row">
+ <div class="col-md-12">
+ <div class="checkbox">
+ <input id="jhb{{hostid}}" type="checkbox" name="jumphost[{{hostid}}]" {{checked}}
+ {{perms.jumphost.assign-subnet.disabled}}>
+ <label for="jhb{{hostid}}">{{host}}:{{port}}</label>
+ </div>
+ </div>
+ </div>
+ {{/jumpHosts}}
+ </div>
+ </div>
+ <div class="panel-footer text-right">
+ <button type="submit" class="btn btn-primary" name="action" value="edit" {{perms.subnet.flag.disabled}}>
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </div>
+</form>
+<script><!--
+document.addEventListener('DOMContentLoaded', function() {
+ var $overrides = $('.subnet-option');
+ var $cb = $('#fixed_cb');
+ $cb.change(function() {
+ if ($cb.is(':checked')) {
+ $overrides.show();
+ } else {
+ $overrides.hide();
+ }
+ }).change();
+
+});
+//--></script> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/subnet-list.html b/modules-available/rebootcontrol/templates/subnet-list.html
new file mode 100644
index 00000000..e2747316
--- /dev/null
+++ b/modules-available/rebootcontrol/templates/subnet-list.html
@@ -0,0 +1,58 @@
+<!-- subnetid, start, end, fixed, isdirect, lastdirectcheck, lastseen, seencount -->
+
+<h2>{{lang_subnets}}</h2>
+
+<p>{{lang_subnetsDescription}}</p>
+
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_start}}</th>
+ <th>{{lang_end}}</th>
+ <th class="slx-smallcol">{{lang_isFixed}}</th>
+ <th class="slx-smallcol">{{lang_isDirect}}</th>
+ <th class="slx-smallcol">{{lang_jumphostCount}}</th>
+ <th>{{lang_lastseen}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#subnets}}
+ <tr>
+ <td>
+ <a href="?do=rebootcontrol&show=subnet&what=subnet&id={{subnetid}}">{{start_s}}</a>
+ </td>
+ <td>
+ <a href="?do=rebootcontrol&show=subnet&what=subnet&id={{subnetid}}">{{end_s}}</a>
+ </td>
+ <td>{{#fixed}}<span class="glyphicon glyphicon-lock"></span>{{/fixed}}</td>
+ <td>{{#isdirect}}<span class="glyphicon glyphicon-ok"></span>{{/isdirect}}</td>
+ <td>{{jumphostcount}}</td>
+ <td class="{{lastseen_class}}">{{lastseen_s}}</td>
+ </tr>
+ {{/subnets}}
+ </tbody>
+</table>
+
+<form method="post" action="?do=rebootcontrol">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="show" value="subnet">
+ <div class="list-group">
+ <div class="list-group-item cidrmagic">
+ <label>{{lang_addNewSubnet}}</label>
+ <div class="row">
+ <div class="col-md-4">
+ <input class="form-control cidrstart" type="text" name="start" placeholder="1.2.3.4/24">
+ </div>
+ <div class="col-md-4">
+ <input class="form-control cidrend" type="text" name="end">
+ </div>
+ <div class="col-md-4 text-right">
+ <button class="btn btn-primary" name="action" value="add">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_create}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/templates/task-list.html b/modules-available/rebootcontrol/templates/task-list.html
index 87515085..8ae4975f 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;show=task&amp;taskid={{taskId}}">{{mode}}</a>
+ <a href="?do=rebootcontrol&amp;show=task&amp;what=task&amp;taskid={{taskId}}">{{mode}}</a>
</td>
<td>
{{locationName}}
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php
index 1cf98ae4..64925791 100644
--- a/modules-available/statistics/api.inc.php
+++ b/modules-available/statistics/api.inc.php
@@ -158,6 +158,30 @@ if ($type{0} === '~') {
}
}
+ // Inform WOL (rebootcontrol) module about subnet size
+ if (Module::get('rebootcontrol') !== false) {
+ $subnet = Request::post('subnet', false, 'string');
+ if ($subnet !== false && ($subnet = explode('/', $subnet)) !== false && count($subnet) === 2
+ && $subnet[0] === $ip && $subnet[1] >= 8 && $subnet[1] < 32) {
+ $start = ip2long($ip);
+ if ($start !== false) {
+ $maskHost = (int)(pow(2, 32 - $subnet[1]) - 1);
+ $maskNet = ~$maskHost & 0xffffffff;
+ $end = $start | $maskHost;
+ $start &= $maskNet;
+ $netparams = ['start' => sprintf('%u', $start), 'end' => sprintf('%u', $end), 'now' => $NOW];
+ $affected = Database::exec('UPDATE reboot_subnet
+ SET lastseen = :now, seencount = seencount + 1
+ WHERE start = :start AND end = :end', $netparams);
+ if ($affected === 0) {
+ // New entry
+ Database::exec('INSERT INTO reboot_subnet (start, end, fixed, isdirect, lastseen, seencount)
+ VALUES (:start, :end, 0, 0, :now, 1)', $netparams);
+ }
+ }
+ }
+ }
+
// Write statistics data
} else if ($type === '~runstate') {
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index 68649d91..be838fc0 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -129,7 +129,7 @@ class Page_Statistics extends Page
}
$task = RebootControl::execute($allowedMachines, $action, 0, $locactionId);
if (Taskmanager::isTask($task)) {
- Util::redirect("?do=rebootcontrol&taskid=" . $task["id"]);
+ Util::redirect("?do=rebootcontrol&what=task&taskid=" . $task["id"]);
}
}
}