summaryrefslogtreecommitdiffstats
path: root/modules-available/rebootcontrol/pages
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/rebootcontrol/pages')
-rw-r--r--modules-available/rebootcontrol/pages/exec.inc.php57
-rw-r--r--modules-available/rebootcontrol/pages/jumphost.inc.php208
-rw-r--r--modules-available/rebootcontrol/pages/subnet.inc.php156
-rw-r--r--modules-available/rebootcontrol/pages/task.inc.php150
4 files changed, 571 insertions, 0 deletions
diff --git a/modules-available/rebootcontrol/pages/exec.inc.php b/modules-available/rebootcontrol/pages/exec.inc.php
new file mode 100644
index 00000000..0c40c313
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/exec.inc.php
@@ -0,0 +1,57 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'exec') {
+ self::execExec();
+ }
+ }
+
+ private static function execExec()
+ {
+ $id = Request::post('id', Request::REQUIRED, 'int');
+ $machines = Session::get('exec-' . $id);
+ if (!is_array($machines)) {
+ Message::addError('unknown-exec-job', $id);
+ return;
+ }
+ $script = preg_replace('/\r\n?/', "\n", Request::post('script', Request::REQUIRED, 'string'));
+ $task = RebootControl::runScript($machines, $script);
+ if (Taskmanager::isTask($task)) {
+ Util::redirect("?do=rebootcontrol&show=task&what=task&taskid=" . $task["id"]);
+ }
+ }
+
+ /*
+ * Render
+ */
+
+ public static function doRender()
+ {
+ $what = Request::get('what', 'list', 'string');
+ if ($what === 'prepare') {
+ self::showPrepare();
+ }
+ }
+
+ private static function showPrepare()
+ {
+ $id = Request::get('id', Request::REQUIRED, 'int');
+ $machines = Session::get('exec-' . $id);
+ if (!is_array($machines)) {
+ Message::addError('unknown-exec-job', $id);
+ return;
+ }
+ Render::addTemplate('exec-enter-command', ['clients' => $machines, 'id' => $id]);
+ }
+
+ public static function doAjax()
+ {
+
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/rebootcontrol/pages/jumphost.inc.php b/modules-available/rebootcontrol/pages/jumphost.inc.php
new file mode 100644
index 00000000..7d1877d2
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/jumphost.inc.php
@@ -0,0 +1,208 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'save') {
+ self::saveJumpHost();
+ } elseif ($action === 'assign') {
+ self::saveSubnetAssignment();
+ } elseif ($action === 'list') {
+ self::listAction();
+ }
+ }
+
+ /*
+ * POST
+ */
+
+ private static function listAction()
+ {
+ $id = Request::post('checkid', false, 'int');
+ if ($id !== false) {
+ // Check connectivity
+ User::assertPermission('jumphost.edit');
+ self::execCheckConnection($id);
+ return;
+ }
+ }
+
+ private static function execCheckConnection($hostid)
+ {
+ $host = self::getJumpHost($hostid);
+ $task = RebootControl::wakeViaJumpHost($host, '255.255.255.255', [['macaddr' => '00:11:22:33:44:55']]);
+ if (!Taskmanager::isTask($task))
+ return;
+ 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', $host);
+ self::execCheckConnection($id);
+ } else {
+ Message::addError('no-such-jumphost', $id);
+ }
+ }
+
+ private static function saveSubnetAssignment()
+ {
+ User::assertPermission('jumphost.edit');
+ $id = Request::post('hostid', Request::REQUIRED, 'string');
+ $host = self::getJumpHost($id);
+ $nets = Request::post('subnet', [], 'array');
+ if (empty($nets)) {
+ Database::exec('DELETE FROM reboot_jumphost_x_subnet WHERE hostid = :id', ['id' => $id]);
+ } else {
+ $nets = array_keys($nets);
+ Database::exec('DELETE FROM reboot_jumphost_x_subnet WHERE hostid = :id AND subnetid NOT IN (:nets)',
+ ['id' => $id, 'nets' => $nets]);
+ $nets = array_map(function($item) use ($id) {
+ return [$id, $item];
+ }, $nets);
+ Database::exec('INSERT IGNORE INTO reboot_jumphost_x_subnet (hostid, subnetid) VALUES :nets', ['nets' => $nets]);
+ }
+ Message::addSuccess('jumphost-saved', $host['host']);
+ }
+
+ /*
+ * Render
+ */
+
+ public static function doRender()
+ {
+ $what = Request::get('what', 'list', 'string');
+ if ($what === 'edit') {
+ self::showJumpHost();
+ } elseif ($what === 'assign') {
+ self::showAssignSubnets();
+ } 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()
+ {
+ 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 = self::getJumpHost($id);
+ }
+ Render::addTemplate('jumphost-edit', $host);
+ }
+
+ private static function showAssignSubnets()
+ {
+ User::assertPermission('jumphost.assign-subnet');
+ $id = Request::get('id', Request::REQUIRED, 'int');
+ $host = self::getJumpHost($id);
+ $res = Database::simpleQuery('SELECT s.subnetid, s.start, s.end, jxs.hostid FROM reboot_subnet s
+ LEFT JOIN reboot_jumphost_x_subnet jxs ON (s.subnetid = jxs.subnetid AND jxs.hostid = :id)
+ ORDER BY start ASC',
+ ['id' => $id]);
+ $list = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['cidr'] = IpUtil::rangeToCidr($row['start'], $row['end']);
+ if ($row['hostid'] !== null) {
+ $row['checked'] = 'checked';
+ }
+ $list[] = $row;
+ }
+ $host['list'] = $list;
+ Render::addTemplate('jumphost-subnets', $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..45151954
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/subnet.inc.php
@@ -0,0 +1,156 @@
+<?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');
+ $cidr = Request::post('cidr', Request::REQUIRED, 'string');
+ $range = IpUtil::parseCidr($cidr);
+ if ($range === false) {
+ Message::addError('invalid-cidr', $cidr);
+ return;
+ }
+ $ret = Database::exec('INSERT INTO reboot_subnet (start, end, fixed, isdirect)
+ VALUES (:start, :end, 1, 0)', [
+ 'start' => $range['start'],
+ 'end' => $range['end'],
+ ], 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)) {
+ Database::exec('DELETE FROM reboot_jumphost_x_subnet WHERE subnetid = :id AND', ['id' => $id]);
+ } else {
+ $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,
+ nextdirectcheck, lastseen, seencount, Count(hxs.hostid) AS jumphostcount, Count(sxs.srcid) AS sourcecount
+ FROM reboot_subnet s
+ LEFT JOIN reboot_jumphost_x_subnet hxs USING (subnetid)
+ LEFT JOIN reboot_subnet_x_subnet sxs ON (s.subnetid = sxs.dstid AND sxs.reachable <> 0)
+ GROUP BY subnetid, start, end
+ ORDER BY start ASC, end DESC');
+ $deadline = strtotime('-60 days');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['cidr'] = IpUtil::rangeToCidr($row['start'], $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);
+ }
+
+ 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['cidr'] = IpUtil::rangeToCidr($subnet['start'], $subnet['end']);
+ $subnet['start_s'] = long2ip($subnet['start']);
+ $subnet['end_s'] = long2ip($subnet['end']);
+ // Get list of jump hosts
+ $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]);
+ // Mark those assigned to the current subnet
+ $jh = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['checked'] = $row['subnetid'] === null ? '' : 'checked';
+ $jh[] = $row;
+ }
+ $subnet['jumpHosts'] = $jh;
+ // Get list of all subnets that can broadcast into this one
+ $res = Database::simpleQuery('SELECT s.start, s.end FROM reboot_subnet s
+ INNER JOIN reboot_subnet_x_subnet sxs ON (s.subnetid = sxs.srcid AND sxs.dstid = :id AND sxs.reachable = 1)
+ ORDER BY s.start ASC', ['id' => $id]);
+ $sn = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $sn[] = ['cidr' => IpUtil::rangeToCidr($row['start'], $row['end'])];
+ }
+ $subnet['sourceNets'] = $sn;
+ Permission::addGlobalTags($subnet['perms'], null, ['subnet.flag', 'jumphost.view', 'jumphost.assign-subnet']);
+ Render::addTemplate('subnet-edit', $subnet);
+ }
+
+ public static function doAjax()
+ {
+
+ }
+
+}
diff --git a/modules-available/rebootcontrol/pages/task.inc.php b/modules-available/rebootcontrol/pages/task.inc.php
new file mode 100644
index 00000000..691fd9e2
--- /dev/null
+++ b/modules-available/rebootcontrol/pages/task.inc.php
@@ -0,0 +1,150 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+
+ }
+
+ public static function doRender()
+ {
+ $xxx = Request::get('tasks');
+ if (is_array($xxx)) {
+ $data = array_map(function($item) { return ['id' => $item]; }, $xxx);
+ Render::addTemplate('status-wol', ['tasks' => $data]);
+ return;
+ }
+ $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');
+ $type = Request::get('type', false, 'string');
+ if ($type === 'checkhost') {
+ // Override
+ $task = Taskmanager::status($taskid);
+ if (!Taskmanager::isTask($task) || !isset($task['data'])) {
+ Message::addError('no-such-task', $taskid);
+ return;
+ }
+ $td =& $task['data'];
+ $ip = array_key_first($td['result']);
+ $data = [
+ 'taskId' => $task['id'],
+ 'host' => $ip,
+ ];
+ Render::addTemplate('status-checkconnection', $data);
+ return;
+ }
+ if ($type !== false) {
+ Message::addError('unknown-task-type');
+ }
+
+ $job = RebootControl::getActiveTasks(null, $taskid);
+ if ($job === false) {
+ Message::addError('no-such-task', $taskid);
+ return;
+ }
+ if (isset($job['type'])) {
+ $type = $job['type'];
+ }
+ if ($type === RebootControl::TASK_EXEC) {
+ $template = $perm = 'exec';
+ } elseif ($type === RebootControl::TASK_REBOOTCTL) {
+ $template = 'reboot';
+ if ($job['action'] === RebootControl::SHUTDOWN) {
+ $perm = 'shutdown';
+ } else {
+ $perm = 'reboot';
+ }
+ } elseif ($type == RebootControl::TASK_WOL) {
+ $template = $perm = 'wol';
+ } else {
+ Message::addError('unknown-task-type', $type);
+ return;
+ }
+ if (!empty($job['locations'])) {
+ $allowedLocs = User::getAllowedLocations("action.$perm");
+ if (!in_array(0, $allowedLocs) && array_diff($job['locations'], $allowedLocs) !== []) {
+ Message::addError('main.no-permission');
+ return;
+ }
+ self::expandLocationIds($job['locations']);
+ }
+
+ // Output
+ if ($type === RebootControl::TASK_REBOOTCTL) {
+ $job['clients'] = RebootUtils::getMachinesByUuid(ArrayUtil::flattenByKey($job['clients'], 'machineuuid'));
+ } elseif ($type === RebootControl::TASK_EXEC) {
+ $details = RebootUtils::getMachinesByUuid(ArrayUtil::flattenByKey($job['clients'], 'machineuuid'), true);
+ foreach ($job['clients'] as &$client) {
+ if (isset($client['machineuuid']) && isset($details[$client['machineuuid']])) {
+ $client += $details[$client['machineuuid']];
+ }
+ }
+ } elseif ($type === RebootControl::TASK_WOL) {
+ // Nothing (yet)
+ } else {
+ Util::traceError('oopsie');
+ }
+ Render::addTemplate('status-' . $template, $job);
+ }
+
+ private static function showTaskList()
+ {
+ Render::addTemplate('task-header');
+ // Append list of active reboot/shutdown tasks
+ $allowedLocs = User::getAllowedLocations("action.*");
+ $active = RebootControl::getActiveTasks($allowedLocs);
+ if (empty($active)) {
+ Message::addInfo('no-current-tasks');
+ } else {
+ foreach ($active as &$entry) {
+ self::expandLocationIds($entry['locations']);
+ if (isset($entry['clients'])) {
+ $entry['clients'] = count($entry['clients']);
+ }
+ }
+ unset($entry);
+ Render::addTemplate('task-list', ['list' => $active]);
+ }
+ }
+
+ private static function expandLocationIds(&$lids)
+ {
+ foreach ($lids as &$locid) {
+ if ($locid === 0) {
+ $name = '-';
+ } else {
+ $name = Location::getName($locid);
+ }
+ $locid = ['id' => $locid, 'name' => $name];
+ }
+ $lids = array_values($lids);
+ }
+
+ 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;
+ }
+}