summaryrefslogtreecommitdiffstats
path: root/modules-available/statistics
diff options
context:
space:
mode:
authorSimon Rettberg2019-03-22 11:52:46 +0100
committerSimon Rettberg2019-03-22 11:52:46 +0100
commite2da7046e75cc54859a2ec67495934e7d2964f79 (patch)
tree51dd1a93aefe0f9f9226d3b020f29c529ce810ce /modules-available/statistics
parent[sysconfig] Remember last modified date, show as tooltip (diff)
downloadslx-admin-e2da7046e75cc54859a2ec67495934e7d2964f79.tar.gz
slx-admin-e2da7046e75cc54859a2ec67495934e7d2964f79.tar.xz
slx-admin-e2da7046e75cc54859a2ec67495934e7d2964f79.zip
[statistics] Add reboot/shutdown to list and details view
Diffstat (limited to 'modules-available/statistics')
-rw-r--r--modules-available/statistics/lang/de/template-tags.json5
-rw-r--r--modules-available/statistics/lang/en/template-tags.json5
-rw-r--r--modules-available/statistics/page.inc.php84
-rw-r--r--modules-available/statistics/templates/clientlist.html115
-rw-r--r--modules-available/statistics/templates/machine-main.html75
5 files changed, 235 insertions, 49 deletions
diff --git a/modules-available/statistics/lang/de/template-tags.json b/modules-available/statistics/lang/de/template-tags.json
index 8eb367e8..51d8c4be 100644
--- a/modules-available/statistics/lang/de/template-tags.json
+++ b/modules-available/statistics/lang/de/template-tags.json
@@ -73,6 +73,9 @@
"lang_ramSlots": "Speicher-Slots",
"lang_realCores": "Kerne",
"lang_reallocatedSectors": "Defekte Sektoren",
+ "lang_reboot": "Neustart",
+ "lang_rebootConfirm": "Ausgew\u00e4hlte Rechner wirklich neustarten?",
+ "lang_rebootKexecCheck": "Schneller Reboot direkt in bwLehrpool (kexec)",
"lang_replace": "Ersetzen",
"lang_replaceInstructions": "Hier k\u00f6nnen Sie Metadaten automatisch \u00fcbertragen, wenn in einem Raum die Rechner ausgetauscht wurden. Dies setzt voraus, dass alle neuen Rechner die gleiche IP Adresse erhalten haben wie der Rechner, der zuvor am entsprechenden Platz stand, und die neuen Rechner alle einmal gestartet wurden. In der Liste unten sehen Sie alle Rechnerpaare, auf die folgendes zutrifft: 1) Die IP-Adressen sind identisch 2) Der letzte Boot des einen Rechners liegt vor dem ersten Boot des anderen Rechners. W\u00e4hlen Sie alle Rechnerpaare aus, f\u00fcr die eine Ersetzung stattfinden soll. Bei der Ersetzung werden alle Logeintr\u00e4ge, Sitzungslogs, Position im Raumplan und evtl. spezielle Betriebsmodi vom alten Rechner auf den neuen \u00dcbertragen.",
"lang_replaceMachinesHeading": "Rechner ersetzen",
@@ -86,6 +89,8 @@
"lang_serialNo": "Serien-Nr",
"lang_showList": "Liste",
"lang_showVisualization": "Visualisierung",
+ "lang_shutdown": "Herunterfahren",
+ "lang_shutdownConfirm": "Ausgew\u00e4hlte Rechner wirklich herunterfahren?",
"lang_sockets": "Sockel",
"lang_subnet": "Subnetz",
"lang_sureDeletePermanent": "M\u00f6chten Sie diese(n) Rechner wirklich unwiderruflich aus der Datenbank entfernen?\r\n\r\nWichtig: L\u00f6schen verhindert nicht, dass ein Rechner nach erneutem Starten von bwLehrpool wieder in die Datenbank aufgenommen wird.",
diff --git a/modules-available/statistics/lang/en/template-tags.json b/modules-available/statistics/lang/en/template-tags.json
index ea74ce52..4fb57e72 100644
--- a/modules-available/statistics/lang/en/template-tags.json
+++ b/modules-available/statistics/lang/en/template-tags.json
@@ -73,6 +73,9 @@
"lang_ramSlots": "Memory slots",
"lang_realCores": "Cores",
"lang_reallocatedSectors": "Bad sectors",
+ "lang_reboot": "Reboot",
+ "lang_rebootConfirm": "Reboot selected machines?",
+ "lang_rebootKexecCheck": "Quick reboot to bwLehrpool (kexec)",
"lang_replace": "Replace",
"lang_replaceInstructions": "If some PCs\/clients have been physically replaced, you can re-assign log entries, session data, position information etc. from the old machine to the new one. This requires that the new machine gets assigned the same IP address as the old one and, if the room planner is used -- that it is placed in the same spot as the old one. The list below shows all machine pairs where 1) the last boot of one machine lies before the first boot of the other one 2) both machines had the same IP address last time they booted. The replacement action will reassign all log events, room plan location and special run mode from the old machine to the new machine.",
"lang_replaceMachinesHeading": "Replace machines",
@@ -86,6 +89,8 @@
"lang_serialNo": "Serial no",
"lang_showList": "List",
"lang_showVisualization": "Visualization",
+ "lang_shutdown": "Shutdown",
+ "lang_shutdownConfirm": "Shutdown selected machines?",
"lang_sockets": "Sockets",
"lang_subnet": "Subnet",
"lang_sureDeletePermanent": "Are your sure you want to delete the selected machine(s) from the database? This cannot be undone.\r\n\r\nNote: Deleting machines from the database does not prevent booting up bwLehrpool again, which would recreate their respective database entries.",
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index 302aea29..b49115da 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -200,6 +200,10 @@ class Page_Statistics extends Page
} elseif ($action === 'delmachines') {
$this->deleteMachines();
Util::redirect('?do=statistics', true);
+ } elseif ($action === 'rebootmachines') {
+ $this->rebootControl(true);
+ } elseif ($action === 'shutdownmachines') {
+ $this->rebootControl(false);
}
}
@@ -211,6 +215,61 @@ class Page_Statistics extends Page
}
}
+ /**
+ * @param bool $reboot true = reboot, false = shutdown
+ */
+ private function rebootControl($reboot)
+ {
+ if (!Module::isAvailable('rebootcontrol'))
+ return;
+ $ids = Request::post('uuid', [], 'array');
+ $ids = array_values($ids);
+ if (empty($ids)) {
+ Message::addError('main.parameter-empty', 'uuid');
+ return;
+ }
+ $allowedLocations = User::getAllowedLocations(".rebootcontrol.action." . ($reboot ? 'reboot' : 'shutdown'));
+ if (empty($allowedLocations)) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=statistics');
+ }
+ $res = Database::simpleQuery('SELECT machineuuid, clientip, locationid FROM machine WHERE machineuuid IN (:ids)', compact('ids'));
+ $ids = array_flip($ids);
+ $allowedMachines = [];
+ $seenLocations = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ unset($ids[$row['machineuuid']]);
+ settype($row['locationid'], 'int');
+ if (in_array($row['locationid'], $allowedLocations)) {
+ $allowedMachines[] = $row;
+ } elseif (!isset($seenLocations[$row['locationid']])) {
+ Message::addError('locations.no-permission-location', $row['locationid']);
+ }
+ $seenLocations[$row['locationid']] = true;
+ }
+ if (!empty($ids)) {
+ Message::addWarning('unknown-machine', implode(', ', array_keys($ids)));
+ }
+ if (!empty($allowedMachines)) {
+ if (count($seenLocations) === 1) {
+ $locactionId = (int)array_keys($seenLocations)[0];
+ } else {
+ $locactionId = 0;
+ }
+ if ($reboot && Request::post('kexec', false)) {
+ $action = RebootControl::KEXEC_REBOOT;
+ } elseif ($reboot) {
+ $action = RebootControl::REBOOT;
+ } else {
+ $action = RebootControl::SHUTDOWN;
+ }
+ $task = RebootControl::execute($allowedMachines, $action, 0, $locactionId);
+ if (Taskmanager::isTask($task)) {
+ Util::redirect("?do=rebootcontrol&taskid=" . $task["id"]);
+ }
+ }
+ }
+
private function deleteMachines()
{
$ids = Request::post('uuid', [], 'array');
@@ -643,7 +702,14 @@ class Page_Statistics extends Page
. " $join WHERE $where $sort", $args);
$rows = array();
$singleMachine = 'none';
+ // TODO: Cannot disable checkbox for those where user has no permission, since we got multiple actions now
+ // We should pass these lists to the output and add some JS magic
+ // Either disable the delete/reboot/... buttons as soon as at least one "forbidden" client is selected (potentially annoying)
+ // or add a notice to the confirmation dialog of the according action (nicer but a little more work)
$deleteAllowedLocations = User::getAllowedLocations("machine.delete");
+ $rebootAllowedLocations = User::getAllowedLocations('.rebootcontrol.action.reboot');
+ $shutdownAllowedLocations = User::getAllowedLocations('.rebootcontrol.action.reboot');
+ // Only make client clickable if user is allowed to view details page
$detailsAllowedLocations = User::getAllowedLocations("machine.view-details");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
if ($singleMachine === 'none') {
@@ -651,8 +717,6 @@ class Page_Statistics extends Page
} else {
$singleMachine = false;
}
- // TODO: This only makes sense as long as there is only one action to perform on selected clients; reboot/shutdown is planned
- $row['delete_disabled'] = in_array($row['locationid'], $deleteAllowedLocations) ? '' : 'disabled';
$row['link_details'] = in_array($row['locationid'], $detailsAllowedLocations);
//$row['firstseen'] = Util::prettyTime($row['firstseen']);
$row['lastseen_int'] = $row['lastseen'];
@@ -704,7 +768,11 @@ class Page_Statistics extends Page
'columns' => json_encode(Page_Statistics::$columns),
'showList' => 1,
'show' => 'list',
- 'redirect' => $_SERVER['QUERY_STRING']
+ 'redirect' => $_SERVER['QUERY_STRING'],
+ 'rebootcontrol' => (Module::get('rebootcontrol') !== false),
+ 'canReboot' => !empty($rebootAllowedLocations),
+ 'canShutdown' => !empty($shutdownAllowedLocations),
+ 'canDelete' => !empty($deleteAllowedLocations),
);
Render::addTemplate('clientlist', $data);
}
@@ -825,6 +893,16 @@ class Page_Statistics extends Page
$client += $data;
}
}
+ // Rebootcontrol
+ if (Module::get('rebootcontrol') !== false) {
+ if (User::hasPermission('.rebootcontrol.action.reboot', (int)$client['locationid'])) {
+ $client['canReboot'] = true;
+ }
+ if (User::hasPermission('.rebootcontrol.action.shutdown', (int)$client['locationid'])) {
+ $client['canShutdown'] = true;
+ }
+ $client['rebootcontrol'] = $client['canReboot'] || $client['canShutdown'];
+ }
if (!isset($client['isclient'])) {
$client['isclient'] = true;
}
diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html
index 18a5d10a..6d7c7f36 100644
--- a/modules-available/statistics/templates/clientlist.html
+++ b/modules-available/statistics/templates/clientlist.html
@@ -56,7 +56,7 @@
<tr>
<td data-sort-value="{{hostname}}" class="text-nowrap">
<div class="checkbox checkbox-inline">
- <input type="checkbox" name="uuid[]" value="{{machineuuid}}" class="deleteCheckboxes" {{delete_disabled}}>
+ <input type="checkbox" name="uuid[]" value="{{machineuuid}}" class="machine-checkbox">
<label></label>
</div>
{{#hasnotes}}
@@ -113,10 +113,26 @@
<span class="glyphicon glyphicon-refresh"></span>
{{lang_reset}}
</button>
- <button id="deleteButton" type="button" class="btn btn-danger" onclick="$('#del-confirm').modal()">
+ {{#rebootcontrol}}
+ {{#canShutdown}}
+ <button type="button" class="btn btn-danger btn-machine-action" data-toggle="modal" data-target="#shutdown-confirm">
+ <span class="glyphicon glyphicon-off"></span>
+ {{lang_shutdown}}
+ </button>
+ {{/canShutdown}}
+ {{#canReboot}}
+ <button type="button" class="btn btn-warning btn-machine-action" data-toggle="modal" data-target="#reboot-confirm">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_reboot}}
+ </button>
+ {{/canReboot}}
+ {{/rebootcontrol}}
+ {{#canDelete}}
+ <button type="button" class="btn btn-danger btn-machine-action" data-toggle="modal" data-target="#del-confirm">
<span class="glyphicon glyphicon-trash"></span>
{{lang_delete}}
</button>
+ {{/canDelete}}
</div>
<div class="modal fade" id="del-confirm" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
@@ -138,70 +154,77 @@
</div>
</div>
</div>
+ <div class="modal fade" id="reboot-confirm" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_reboot}}</b>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_rebootConfirm}}</p>
+ <div class="checkbox">
+ <input type="checkbox" name="kexec" value="1" id="kexec-input">
+ <label for="kexec-input">{{lang_rebootKexecCheck}}</label>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="action" value="rebootmachines">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_reboot}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal fade" id="shutdown-confirm" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_shutdown}}</b>
+ </div>
+ <div class="modal-body">
+ {{lang_shutdownConfirm}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="action" value="shutdownmachines">
+ <span class="glyphicon glyphicon-off"></span>
+ {{lang_shutdown}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
</form>
<script type="application/javascript"><!--
-selectedMachineCounter = 0;
+var selectedMachineCounter = 0;
document.addEventListener("DOMContentLoaded", function () {
- ['gbram', 'hddgb', 'realcores', 'kvmstate', 'lastseen', 'clientip'].forEach(function (v) {
- var $sortBtn = $('#sortButton-' + v);
- var order = 'up'; /* default */
- if ($('#sortColumn').val() == v) {
- $sortBtn.addClass('btn-success');
- order = $('#sortDirection').val() == 'ASC' ? 'up' : 'down';
- }
- $sortBtn.html('<span class="glyphicon glyphicon-arrow-' + order + '"></span>');
- $sortBtn.attr('onclick', 'toggleButton(\'' + v + '\');');
- });
-
- $('[data-toggle="tooltip"]').tooltip({
- container: 'body',
- trigger : 'hover'
- });
-
- $("#deleteButton").prop("disabled", true);
- $(".deleteCheckboxes").change(function() {
+ var $buttons = $('.btn-machine-action');
+ $buttons.prop("disabled", true);
+ $(".machine-checkbox").change(function() {
if ($(this).is(':checked')) {
selectedMachineCounter++;
if (selectedMachineCounter === 1) {
- $("#deleteButton").prop("disabled", false);
+ $buttons.prop("disabled", false);
}
} else {
selectedMachineCounter--;
if (selectedMachineCounter === 0) {
- $("#deleteButton").prop("disabled", true);
+ $buttons.prop("disabled", true);
}
}
});
$("button[type=reset]").click(function() {
selectedMachineCounter = 0;
- $("#deleteButton").prop("disabled", true);
+ $buttons.prop("disabled", true);
});
-
});
-function toggleButton(v) {
- var $sortBtn = $('#sortButton-' + v);
- var $col = $('#sortColumn');
- var $dir = $('#sortDirection');
- if ($col.val() == v) {
- /* toggle direction */
- var newDir = $dir.val() == 'ASC' ? 'DESC' : 'ASC';
- $dir.val(newDir);
- /* update button */
- var order = newDir == 'ASC' ? 'up' : 'down';
- $sortBtn.html('<span class="glyphicon glyphicon-arrow-' + order + '"></span>');
- } else {
- /* remove "btn-success" from current sorting */
- $('#sortButton-'+v).removeClass('btn-success');
- $sortBtn.addClass('btn-success');
- $col.val(v);
- $dir = 'ASC';
- }
- $queryForm.submit();
-}
-
//--></script>
diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html
index e8e67065..5adfe5aa 100644
--- a/modules-available/statistics/templates/machine-main.html
+++ b/modules-available/statistics/templates/machine-main.html
@@ -101,6 +101,81 @@
</td>
</tr>
{{/hasroomplan}}
+ {{#rebootcontrol}}
+ <tr>
+ <td class="text-nowrap">
+ {{lang_reboot}}/{{lang_shutdown}}
+ </td>
+ <td>
+ <form method="post" action="?do=statistics">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="uuid" value="{{machineuuid}}">
+ {{#canShutdown}}
+ <button type="button" class="btn btn-sm btn-danger btn-machine-action" data-toggle="modal"
+ data-target="#shutdown-confirm">
+ <span class="glyphicon glyphicon-off"></span>
+ {{lang_shutdown}}
+ </button>
+ {{/canShutdown}}
+ {{#canReboot}}
+ <button type="button" class="btn btn-sm btn-warning btn-machine-action" data-toggle="modal"
+ data-target="#reboot-confirm">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_reboot}}
+ </button>
+ {{/canReboot}}
+ <div class="modal fade" id="reboot-confirm" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_reboot}}</b>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_rebootConfirm}}</p>
+ <div class="checkbox">
+ <input type="checkbox" name="kexec" value="1" id="kexec-input">
+ <label for="kexec-input">{{lang_rebootKexecCheck}}</label>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default"
+ data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="action"
+ value="rebootmachines">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_reboot}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal fade" id="shutdown-confirm" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_shutdown}}</b>
+ </div>
+ <div class="modal-body">
+ {{lang_shutdownConfirm}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default"
+ data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="action"
+ value="shutdownmachines">
+ <span class="glyphicon glyphicon-off"></span>
+ {{lang_shutdown}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+ </td>
+ </tr>
+ {{/rebootcontrol}}
</table>
</div>
</div>