diff options
Diffstat (limited to 'modules-available/rebootcontrol/pages')
-rw-r--r-- | modules-available/rebootcontrol/pages/exec.inc.php | 57 | ||||
-rw-r--r-- | modules-available/rebootcontrol/pages/jumphost.inc.php | 208 | ||||
-rw-r--r-- | modules-available/rebootcontrol/pages/subnet.inc.php | 156 | ||||
-rw-r--r-- | modules-available/rebootcontrol/pages/task.inc.php | 150 |
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; + } +} |