diff options
Diffstat (limited to 'modules-available/rebootcontrol')
9 files changed, 250 insertions, 58 deletions
diff --git a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php index 489b0252..667c8bbd 100644 --- a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php +++ b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php @@ -21,7 +21,7 @@ class RebootControl */ public static function reboot($uuids, $kexec = false) { - $list = RebootQueries::getMachinesByUuid($uuids); + $list = RebootUtils::getMachinesByUuid($uuids); if (empty($list)) return false; return self::execute($list, $kexec ? RebootControl::KEXEC_REBOOT : RebootControl::REBOOT, 0); @@ -501,4 +501,18 @@ class RebootControl $subnet['iclients'] = array_slice($subnet['iclients'], 0, 3); } + public static function prepareExec() + { + User::assertPermission('action.exec'); + $uuids = array_values(Request::post('uuid', Request::REQUIRED, 'array')); + $machines = RebootUtils::getFilteredMachineList($uuids, 'action.exec'); + if ($machines === false) + return; + RebootUtils::sortRunningFirst($machines); + $id = mt_rand(); + Session::set('exec-' . $id, $machines, 60); + Session::save(); + Util::redirect('?do=rebootcontrol&show=exec&what=prepare&id=' . $id); + } + } diff --git a/modules-available/rebootcontrol/inc/rebootqueries.inc.php b/modules-available/rebootcontrol/inc/rebootqueries.inc.php deleted file mode 100644 index c0c479bd..00000000 --- a/modules-available/rebootcontrol/inc/rebootqueries.inc.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -class RebootQueries -{ - - /** - * Get machines by list of UUIDs - * @param string[] $list list of system UUIDs - * @return array list of machines with machineuuid, hostname, clientip, state and locationid - */ - public static function getMachinesByUuid($list, $assoc = false, $columns = ['machineuuid', 'hostname', 'clientip', 'state', 'locationid']) - { - if (empty($list)) - return array(); - if (is_array($columns)) { - $columns = implode(',', $columns); - } - $res = Database::simpleQuery("SELECT $columns FROM machine - WHERE machineuuid IN (:list)", compact('list')); - if (!$assoc) - return $res->fetchAll(PDO::FETCH_ASSOC); - $ret = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $ret[$row['machineuuid']] = $row; - } - return $ret; - } - -}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/inc/rebootutils.inc.php b/modules-available/rebootcontrol/inc/rebootutils.inc.php new file mode 100644 index 00000000..99235e8a --- /dev/null +++ b/modules-available/rebootcontrol/inc/rebootutils.inc.php @@ -0,0 +1,75 @@ +<?php + +class RebootUtils +{ + + /** + * Get machines by list of UUIDs + * @param string[] $list list of system UUIDs + * @return array list of machines with machineuuid, hostname, clientip, state and locationid + */ + public static function getMachinesByUuid($list, $assoc = false, $columns = ['machineuuid', 'hostname', 'clientip', 'state', 'locationid']) + { + if (empty($list)) + return array(); + if (is_array($columns)) { + $columns = implode(',', $columns); + } + $res = Database::simpleQuery("SELECT $columns FROM machine + WHERE machineuuid IN (:list)", compact('list')); + if (!$assoc) + return $res->fetchAll(PDO::FETCH_ASSOC); + $ret = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $ret[$row['machineuuid']] = $row; + } + return $ret; + } + + /** + * Sort list of clients so that machines that are up and running come first. + * Requires the array elements to have key "state" from machine table. + * @param array $clients list of clients + */ + public static function sortRunningFirst(&$clients) + { + usort($clients, 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; + }); + } + + /** + * Query list of clients (by uuid), taking user context into account, by filtering + * by given $permission. + * @param array $requestedClients list of uuids + * @param string $permission name of location-aware permission to check + * @return array|false List of clients the user has access to. + */ + public static function getFilteredMachineList($requestedClients, $permission) + { + $actualClients = RebootUtils::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($permission, $actualClients[$idx]['locationid'])) { + Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']); + unset($actualClients[$idx]); + } + } + // See if anything is left + if (!is_array($actualClients) || empty($actualClients)) { + Message::addError('no-clients-selected'); + return false; + } + return $actualClients; + } + +}
\ No newline at end of file diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php index 764a3d7b..cf87a3b3 100644 --- a/modules-available/rebootcontrol/page.inc.php +++ b/modules-available/rebootcontrol/page.inc.php @@ -71,31 +71,10 @@ class Page_RebootControl extends Page 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.' . $action, $actualClients[$idx]['locationid'])) { - Message::addWarning('locations.no-permission-location', $actualClients[$idx]['locationid']); - unset($actualClients[$idx]); - } - } - // See if anything is left - if (!is_array($actualClients) || empty($actualClients)) { - Message::addError('no-clients-selected'); + $actualClients = RebootUtils::getFilteredMachineList($requestedClients, 'action.' . $action); + if ($actualClients === false) 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; - }); + RebootUtils::sortRunningFirst($actualClients); if ($action === 'shutdown') { $mode = 'SHUTDOWN'; $minutes = Request::post('s-minutes', 0, 'int'); @@ -144,7 +123,7 @@ class Page_RebootControl extends Page $clients = Request::post('clients'); if (is_array($clients)) { // XXX No permission check here, should we consider this as leaking sensitive information? - $machines = RebootQueries::getMachinesByUuid(array_values($clients), false, ['machineuuid', 'state']); + $machines = RebootUtils::getMachinesByUuid(array_values($clients), false, ['machineuuid', 'state']); $ret = []; foreach ($machines as $machine) { switch ($machine['state']) { diff --git a/modules-available/rebootcontrol/pages/exec.inc.php b/modules-available/rebootcontrol/pages/exec.inc.php new file mode 100644 index 00000000..58053072 --- /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 = 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/task.inc.php b/modules-available/rebootcontrol/pages/task.inc.php index e52eb981..691fd9e2 100644 --- a/modules-available/rebootcontrol/pages/task.inc.php +++ b/modules-available/rebootcontrol/pages/task.inc.php @@ -82,9 +82,9 @@ class SubPage // Output if ($type === RebootControl::TASK_REBOOTCTL) { - $job['clients'] = RebootQueries::getMachinesByUuid(ArrayUtil::flattenByKey($job['clients'], 'machineuuid')); + $job['clients'] = RebootUtils::getMachinesByUuid(ArrayUtil::flattenByKey($job['clients'], 'machineuuid')); } elseif ($type === RebootControl::TASK_EXEC) { - $details = RebootQueries::getMachinesByUuid(ArrayUtil::flattenByKey($job['clients'], 'machineuuid'), true); + $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']]; diff --git a/modules-available/rebootcontrol/templates/exec-enter-command.html b/modules-available/rebootcontrol/templates/exec-enter-command.html new file mode 100644 index 00000000..5916e2a8 --- /dev/null +++ b/modules-available/rebootcontrol/templates/exec-enter-command.html @@ -0,0 +1,41 @@ +<h2>{{lang_execRemoteCommand}}</h2> + +<table class="table 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> + </tr> + </thead> + + <tbody> + {{#clients}} + <tr> + <td>{{hostname}}{{^hostname}}{{machineuuid}}{{/hostname}}</td> + <td>{{clientip}}</td> + <td>{{state}}</td> + </tr> + {{/clients}} + </tbody> +</table> + +<h3>{{lang_enterCommand}}</h3> + +<form method="post" action="?do=rebootcontrol" id="list-form"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="show" value="exec"> + <input type="hidden" name="id" value="{{id}}"> + <div> + <label for="script-text">{{lang_scriptOrCommand}}</label> + <textarea id="script-text" class="form-control" name="script" rows="10"></textarea> + </div> + <div class="text-right slx-space"> + <button type="submit" class="btn btn-primary" name="action" value="exec"> + <span class="glyphicon glyphicon-play"></span> + {{lang_remoteExec}} + </button> + </div> +</form>
\ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/status-exec.html b/modules-available/rebootcontrol/templates/status-exec.html new file mode 100644 index 00000000..140de02b --- /dev/null +++ b/modules-available/rebootcontrol/templates/status-exec.html @@ -0,0 +1,55 @@ + +<div data-tm-id="{{id}}" data-tm-log="error" data-tm-callback="updateStatus">{{lang_executingRemotely}}</div> + +<div class="slx-space"></div> + +<div class="row"> + <div class="col-md-5 slx-bold">{{lang_host}}</div> + <div class="col-md-5 slx-bold">{{lang_status}}</div> + <div class="col-md-2 slx-bold text-right">{{lang_exitCode}}</div> +</div> + +{{#clients}} +<div class="slx-space" id="client-{{machineuuid}}"> + <div class="row"> + <div class="col-md-5 slx-bold">{{hostname}}{{^hostname}}{{clientip}}{{/hostname}}</div> + <div class="col-md-5 state"></div> + <div class="col-md-2 text-right exitCode"></div> + </div> + <i>{{lang_stdout}}</i> + <pre class="stdout"></pre> + <i>{{lang_stderr}}</i> + <pre class="stderr"></pre> +</div> +<hr> +{{/clients}} + +<script><!-- + +var ingoreHosts = {}; + +function updateStatus(task) { + if (!task || !task.data || !task.data.result) + return; + for (var host in task.data.result) { + if (ingoreHosts[host] || !task.data.result.hasOwnProperty(host)) + continue; + updateStatusClient(host, task.data.result[host]); + } +} +function updateStatusClient(id, status) { + var $p = $('#client-' + id); + if ($p.length === 0) + return; + $p.find('.state').text(status.state); + $p.find('.stdout').text(status.stdout); + $p.find('.stderr').text(status.stderr); + if (status.state === 'DONE' || status.state === 'ERROR' || status.state === 'TIMEOUT') { + $p.find('.state').addClass((status.state === 'DONE') ? 'text-success' : 'text-danger'); + if (status.exitCode >= 0) { + $p.find('.exitCode').text(status.exitCode).addClass((status.exitCode === 0 ? 'text-success' : 'text-danger')); + } + ingoreHosts[id] = true; + } +} +//--></script>
\ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/status-wol.html b/modules-available/rebootcontrol/templates/status-wol.html index da19b57d..3e83126c 100644 --- a/modules-available/rebootcontrol/templates/status-wol.html +++ b/modules-available/rebootcontrol/templates/status-wol.html @@ -4,7 +4,7 @@ <div class="clearfix slx-space"></div> {{#tasks}} -<div data-tm-id="{{id}}" data-tm-callback="wolCallback">{{lang_aWolJob}}</div> +<div data-tm-id="{{.}}" data-tm-callback="wolCallback">{{lang_aWolJob}}</div> {{/tasks}} {{^tasks}} <div class="alert alert-warning"> |