From 4c9aba92942c4b9341c46a50aeaa31bea24a8b60 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 9 Jan 2018 17:38:21 +0100 Subject: [exams] implemented permission system --- .../rebootcontrol/lang/de/permissions.json | 5 ++ .../rebootcontrol/lang/en/permissions.json | 5 ++ modules-available/rebootcontrol/page.inc.php | 66 +++++++++++++--- .../rebootcontrol/permissions/permissions.json | 5 ++ .../rebootcontrol/templates/_page.html | 90 +++++++++++----------- 5 files changed, 117 insertions(+), 54 deletions(-) create mode 100644 modules-available/rebootcontrol/lang/de/permissions.json create mode 100644 modules-available/rebootcontrol/lang/en/permissions.json create mode 100644 modules-available/rebootcontrol/permissions/permissions.json diff --git a/modules-available/rebootcontrol/lang/de/permissions.json b/modules-available/rebootcontrol/lang/de/permissions.json new file mode 100644 index 00000000..92eeb37e --- /dev/null +++ b/modules-available/rebootcontrol/lang/de/permissions.json @@ -0,0 +1,5 @@ +{ + "shutdown": "Client herunterfahren.", + "reboot": "Client neustarten.", + "newkeypair": "Neues Schlüsselpaar generieren." +} \ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/en/permissions.json b/modules-available/rebootcontrol/lang/en/permissions.json new file mode 100644 index 00000000..077890fb --- /dev/null +++ b/modules-available/rebootcontrol/lang/en/permissions.json @@ -0,0 +1,5 @@ +{ + "shutdown": "Shutdown Client.", + "reboot": "Reboot Client.", + "newkeypair": "Generate new Keypair." +} \ No newline at end of file diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php index fc3ded8f..fa34a05a 100644 --- a/modules-available/rebootcontrol/page.inc.php +++ b/modules-available/rebootcontrol/page.inc.php @@ -4,6 +4,9 @@ class Page_RebootControl extends Page { private $action = false; + private $allowedShutdownLocs = []; + private $allowedRebootLocs = []; + private $allowedLocs = []; /** * Called before any page rendering happens - early hook to check parameters etc. @@ -17,21 +20,40 @@ class Page_RebootControl extends Page Util::redirect('?do=Main'); // does not return } + $this->allowedShutdownLocs = User::getAllowedLocations("shutdown"); + $this->allowedRebootLocs = User::getAllowedLocations("reboot"); + $this->allowedLocs = array_unique(array_merge($this->allowedShutdownLocs, $this->allowedRebootLocs)); + $this->action = Request::any('action', 'show', 'string'); if ($this->action === 'startReboot' || $this->action === 'startShutdown') { - $clients = Request::post('clients'); - if (!is_array($clients) || empty($clients)) { - Message::addError('no-clients-selected'); - Util::redirect(); - } + $locationId = Request::post('locationId', false, 'int'); if ($locationId === false) { Message::addError('locations.invalid-location-id', $locationId); Util::redirect(); } + $shutdown = $this->action === "startShutdown"; + // Check user permission (if user has no permission, the getAllowed-list will be empty and the check will fail) + if ($shutdown) { + if (!in_array($locationId, $this->allowedShutdownLocs)) { + Message::addError('main.no-permission'); + Util::redirect(); + } + } else { + if (!in_array($locationId, $this->allowedRebootLocs)) { + Message::addError('main.no-permission'); + Util::redirect(); + } + } + + $clients = Request::post('clients'); + if (!is_array($clients) || empty($clients)) { + Message::addError('no-clients-selected'); + Util::redirect(); + } $minutes = Request::post('minutes', 0, 'int'); $list = RebootQueries::getMachinesByUuid($clients); @@ -72,12 +94,34 @@ class Page_RebootControl extends Page //location you want to see, default are "not assigned" clients $requestedLocation = Request::get('location', 0, 'int'); - $data['data'] = RebootQueries::getMachineTable($requestedLocation); - $data['locations'] = Location::getLocations($requestedLocation, 0, true); + // only fill table if user has at least one permission for the location + if (in_array($requestedLocation, $this->allowedLocs)) { + $data['data'] = RebootQueries::getMachineTable($requestedLocation); + $data['allowedToSelect'] = True; + } + $data['locations'] = Location::getLocations($requestedLocation, 0, true); + // Always show public key (it's public, isn't it?) $data['pubKey'] = SSHKey::getPublicKey(); + // disable each location user has no permission for + foreach ($data['locations'] as &$loc) { + if (!in_array($loc["locationid"], $this->allowedLocs)) { + $loc["disabled"] = "disabled"; + } + } + + // Only enable shutdown/reboot-button if user has permission for the location + if (in_array($requestedLocation, $this->allowedShutdownLocs)) { + $data['allowedToShutdown'] = True; + } + if (in_array($requestedLocation, $this->allowedRebootLocs)) { + $data['allowedToReboot'] = True; + } + $data['allowedToGenerateKey'] = User::hasPermission("newkeypair"); + Render::addTemplate('_page', $data); + } } } @@ -86,8 +130,12 @@ class Page_RebootControl extends Page { $this->action = Request::post('action', false, 'string'); if ($this->action === 'generateNewKeypair') { - Property::set("rebootcontrol-private-key", false); - echo SSHKey::getPublicKey(); + if (User::hasPermission("newkeypair")) { + Property::set("rebootcontrol-private-key", false); + echo SSHKey::getPublicKey(); + } else { + echo 'No permission.'; + } } else { echo 'Invalid action.'; } diff --git a/modules-available/rebootcontrol/permissions/permissions.json b/modules-available/rebootcontrol/permissions/permissions.json new file mode 100644 index 00000000..5230c9bd --- /dev/null +++ b/modules-available/rebootcontrol/permissions/permissions.json @@ -0,0 +1,5 @@ +[ + "shutdown", + "reboot", + "newkeypair" +] \ No newline at end of file diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html index 1bef8dd4..9b470943 100644 --- a/modules-available/rebootcontrol/templates/_page.html +++ b/modules-available/rebootcontrol/templates/_page.html @@ -8,15 +8,15 @@
- - - + + +
@@ -25,41 +25,41 @@
- - - - - - - - + + + + + + + + {{#data}} - - - - - - - - + + + + + + + + {{/data}}
{{lang_client}}{{lang_ip}}{{lang_status}}{{lang_session}}{{lang_user}}{{lang_selected}}
{{lang_client}}{{lang_ip}}{{lang_status}}{{lang_session}}{{lang_user}}{{lang_selected}}
- {{hostname}} - {{^hostname}}{{clientip}}{{/hostname}} - {{clientip}} - {{#status}} - {{lang_on}} - {{/status}} - {{^status}} - {{lang_off}} - {{/status}} - {{#status}}{{currentsession}}{{/status}}{{#status}}{{currentuser}}{{/status}} -
- - -
-
+ {{hostname}} + {{^hostname}}{{clientip}}{{/hostname}} + {{clientip}} + {{#status}} + {{lang_on}} + {{/status}} + {{^status}} + {{lang_off}} + {{/status}} + {{#status}}{{currentsession}}{{/status}}{{#status}}{{currentuser}}{{/status}} +
+ + +
+
@@ -79,7 +79,7 @@
@@ -115,11 +115,11 @@ @@ -162,7 +162,7 @@ $('#rebootButton').prop('disabled', false); $('#shutdownButton').prop('disabled', false); } - }); + }); $('.checkboxColumn').click(function(e) { if (e.target === this) { $(this).find('input[type="checkbox"]').click(); -- cgit v1.2.3-55-g7522 From 3601ceb43aaa9f85c8036ee465a99c9aedaff1c3 Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Wed, 10 Jan 2018 16:46:13 +0100 Subject: [locations] fixed root location not getting disabled in the dropdown if the user does not have permisson for location 0 --- modules-available/locations/page.inc.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php index 0cfa5b90..9112e810 100644 --- a/modules-available/locations/page.inc.php +++ b/modules-available/locations/page.inc.php @@ -446,7 +446,6 @@ class Page_Locations extends Page } $addAllowedLocs = User::getAllowedLocations("location.add"); - $addAllowedLocs[] = 0; $addAllowedList = Location::getLocations(0, 0, true); foreach ($addAllowedList as &$loc) { if (!in_array($loc["locationid"], $addAllowedLocs)) { -- cgit v1.2.3-55-g7522 From e4e62c11eb207ed9b155dd4f49dab55f6c0e42a9 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Thu, 11 Jan 2018 15:53:02 +0100 Subject: [rebootcontrol] this time with correct modul-tag in front. Changed buttons so that they don't open the modals to reboot/shutdown if user has no permission. Deleted some redundancy from stylesheet --- modules-available/rebootcontrol/style.css | 5 +---- modules-available/rebootcontrol/templates/_page.html | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules-available/rebootcontrol/style.css b/modules-available/rebootcontrol/style.css index 442cd5de..f10a6157 100644 --- a/modules-available/rebootcontrol/style.css +++ b/modules-available/rebootcontrol/style.css @@ -16,11 +16,8 @@ margin-bottom: 0; } -#rebootButton, #settingsButton, #selectAllButton, #unselectAllButton { +.controlButtons { margin-left: 10px; -} - -#rebootButton, #shutdownButton, #selectAllButton, #unselectAllButton { width: 140px; } diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html index 9b470943..e540cafb 100644 --- a/modules-available/rebootcontrol/templates/_page.html +++ b/modules-available/rebootcontrol/templates/_page.html @@ -15,10 +15,10 @@ {{/locations}} - - - - + + + +
-- cgit v1.2.3-55-g7522 From d09cc1533e858290b3cfa3d4eb3906453e3b2fe9 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 16 Jan 2018 15:25:56 +0100 Subject: [baseconfig_partitions_cdn] implemented permission-system. you can only see site if you have atleast one permission. fixed the save-button (it's working now!). Didn't know what to do with this download-button. --- .../lang/de/permissions.json | 7 +++ .../lang/en/permissions.json | 7 +++ .../lang/en/template-tags.json | 4 +- .../baseconfig_partitions_cdn/page.inc.php | 41 +++++++++++---- .../permissions/permissions.json | 7 +++ .../baseconfig_partitions_cdn/style.css | 4 ++ .../baseconfig_partitions_cdn/templates/_page.html | 60 ++++++++++++++-------- 7 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 modules-available/baseconfig_partitions_cdn/lang/de/permissions.json create mode 100644 modules-available/baseconfig_partitions_cdn/lang/en/permissions.json create mode 100644 modules-available/baseconfig_partitions_cdn/permissions/permissions.json create mode 100644 modules-available/baseconfig_partitions_cdn/style.css diff --git a/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json b/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json new file mode 100644 index 00000000..35c21e09 --- /dev/null +++ b/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json @@ -0,0 +1,7 @@ +{ + "show": "Zeige Partitionen. Wird nicht benötigt, wenn Nutzer eine der anderen Rechte hat.", + "add": "Füge eine neue Partition hinzu.", + "delete": "Lösche eine Partition.", + "edit": "Speichere Änderungen an Partitionen.", + "reset": "Setze Partitionen auf Standardwerte zurück." +} \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json b/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json new file mode 100644 index 00000000..49742618 --- /dev/null +++ b/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json @@ -0,0 +1,7 @@ +{ + "show": "Show Partitions. Not needed if User has any of the other permissions.", + "add": "Add a new partition.", + "delete": "Delete a partition.", + "edit": "Save changes of partitions.", + "reset": "Reset partitions to default." +} \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json b/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json index 04ce6c80..472e5870 100644 --- a/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json +++ b/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json @@ -1,9 +1,9 @@ { "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!", - "lang_confirm": "Would you like to save the settings on [ \/srv\/openslx\/www\/boot\/config ] ?", + "lang_confirm": "Would you like to save the settings on \/srv\/openslx\/www\/boot\/config?", "lang_create": "Create", "lang_discardChanges": "Discard Changes", - "lang_explanationText": "Here you can configure what kind of partitions will be created on the client computers, and where they will be mounted", + "lang_explanationText": "Here you can configure what kind of partitions will be created on the client computers, and where they will be mounted.", "lang_helpId": "Partition Id", "lang_helpMountPoint": "Must be a directory: \/example\/directory\/", "lang_helpOptions": "Currently, only option 'bootable' is available", diff --git a/modules-available/baseconfig_partitions_cdn/page.inc.php b/modules-available/baseconfig_partitions_cdn/page.inc.php index a1d1445f..b61ea448 100644 --- a/modules-available/baseconfig_partitions_cdn/page.inc.php +++ b/modules-available/baseconfig_partitions_cdn/page.inc.php @@ -10,23 +10,39 @@ class Page_BaseConfig_Partitions_CDN extends Page $action = Request::post('action'); if($action == 'new_partition') { - $this->addPartition(); + if (User::hasPermission("partitions.add")) { + $this->addPartition(); + } } if($action == 'reset') { - $this->resetConfig(); + if (User::hasPermission("partitions.reset")) { + $this->resetConfig(); + } } $deletePartition = Request::get('deletePartition'); if($deletePartition !== false) { // TODO: CSRF: Actions that change/update/delete anything should be POST - $this->deletePartition($deletePartition); + if (User::hasPermission("partitions.delete")) { + $this->deletePartition($deletePartition); + } } - $this->updatePartitions(); + if(User::hasPermission("partitions.edit")) { + $this->updatePartitions(); + } } protected function doRender() { - if (!User::hasPermission('baseconfig_local')) { + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); + } + + $hasAnyRight = User::hasPermission("partitions.add") || User::hasPermission("partitions.delete") + || User::hasPermission("partitions.edit") || User::hasPermission("partitions.reset"); + + if (!(User::hasPermission("show") || $hasAnyRight)) { Message::addError('main.no-permission'); Util::redirect('?do=Main'); } @@ -48,7 +64,11 @@ class Page_BaseConfig_Partitions_CDN extends Page Render::addTemplate('_page', array( 'partitions' => $partitions, - 'user' => User::getId() + 'user' => User::getId(), + 'allowedToAdd' => User::hasPermission("partitions.add"), + 'allowedToDelete' => User::hasPermission("partitions.delete"), + 'allowedToEdit' => User::hasPermission("partitions.edit"), + 'allowedToReset' => User::hasPermission("partitions.reset") )); } @@ -92,9 +112,9 @@ class Page_BaseConfig_Partitions_CDN extends Page private function updatePartitions(){ $partitions = array(); foreach($_POST as $key => $value){ - if(substr($key,0,9) == 'partition'){ - $id = substr($key,10,1); - $type = substr($key,12); + + if (substr($key, 0, 9) == 'partition') { + list($key, $id, $type) = explode("-", $key); $partitions[$id][$type] = $value; } } @@ -111,6 +131,8 @@ class Page_BaseConfig_Partitions_CDN extends Page Database::exec('UPDATE setting_partition SET partition_id=:partition_id, size=:size, mount_point=:mount_point, options=:options WHERE id=:id AND user=:user;', $data); } + + if (!empty($partitions)) { Message::addSuccess('partitions-updated'); Util::redirect('?do=BaseConfig_Partitions_CDN'); @@ -129,5 +151,6 @@ class Page_BaseConfig_Partitions_CDN extends Page Database::exec ( "INSERT INTO setting_partition SET partition_id = '40', size = '20G', mount_point = '/cache/export/dnbd3', user = :user", $data ); Database::exec ( "INSERT INTO setting_partition SET partition_id = '41', size = '5G', mount_point = '/home', user = :user", $data ); Database::exec ( "INSERT INTO setting_partition SET partition_id = '82', size = '1G', user = :user", $data ); + Util::redirect('?do=BaseConfig_Partitions_CDN'); } } \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/permissions/permissions.json b/modules-available/baseconfig_partitions_cdn/permissions/permissions.json new file mode 100644 index 00000000..286a975b --- /dev/null +++ b/modules-available/baseconfig_partitions_cdn/permissions/permissions.json @@ -0,0 +1,7 @@ +[ + "show", + "partitions.add", + "partitions.delete", + "partitions.edit", + "partitions.reset" +] \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/style.css b/modules-available/baseconfig_partitions_cdn/style.css new file mode 100644 index 00000000..d55e5e5b --- /dev/null +++ b/modules-available/baseconfig_partitions_cdn/style.css @@ -0,0 +1,4 @@ +.missingInput { + border-color: rgba(255, 0, 0, 0.8); + box-shadow: 0 1px 1px rgba(255, 0, 0, 0.075) inset, 0 0 8px rgba(255, 0, 0, 0.6); +} \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/templates/_page.html b/modules-available/baseconfig_partitions_cdn/templates/_page.html index 71cbb7db..2cb3f2a6 100644 --- a/modules-available/baseconfig_partitions_cdn/templates/_page.html +++ b/modules-available/baseconfig_partitions_cdn/templates/_page.html @@ -21,7 +21,7 @@
- +
@@ -30,16 +30,16 @@ {{/partitions}}
- +
- Download + - +
@@ -60,7 +62,7 @@
- + @@ -78,7 +80,7 @@
-
+ @@ -120,12 +124,28 @@
+ \ No newline at end of file -- cgit v1.2.3-55-g7522 From e8a20be927006348974adc48ce34951f8a314b8c Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 16 Jan 2018 16:55:16 +0100 Subject: [baseconfig_partitions_cdn] updated permission-descriptions --- .../baseconfig_partitions_cdn/lang/de/permissions.json | 8 ++++---- .../baseconfig_partitions_cdn/lang/en/permissions.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json b/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json index 35c21e09..d5805e3d 100644 --- a/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json +++ b/modules-available/baseconfig_partitions_cdn/lang/de/permissions.json @@ -1,7 +1,7 @@ { "show": "Zeige Partitionen. Wird nicht benötigt, wenn Nutzer eine der anderen Rechte hat.", - "add": "Füge eine neue Partition hinzu.", - "delete": "Lösche eine Partition.", - "edit": "Speichere Änderungen an Partitionen.", - "reset": "Setze Partitionen auf Standardwerte zurück." + "partitions.add": "Füge eine neue Partition hinzu.", + "partitions.delete": "Lösche eine Partition.", + "partitions.edit": "Speichere Änderungen an Partitionen.", + "partitions.reset": "Setze Partitionen auf Standardwerte zurück." } \ No newline at end of file diff --git a/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json b/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json index 49742618..f751a839 100644 --- a/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json +++ b/modules-available/baseconfig_partitions_cdn/lang/en/permissions.json @@ -1,7 +1,7 @@ { "show": "Show Partitions. Not needed if User has any of the other permissions.", - "add": "Add a new partition.", - "delete": "Delete a partition.", - "edit": "Save changes of partitions.", - "reset": "Reset partitions to default." + "partitions.add": "Add a new partition.", + "partitions.delete": "Delete a partition.", + "partitions.edit": "Save changes of partitions.", + "partitions.reset": "Reset partitions to default." } \ No newline at end of file -- cgit v1.2.3-55-g7522 From 3ddf56c399746efe3c56194b7be522a92caefb59 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 16 Jan 2018 17:50:22 +0100 Subject: [minilinux] implemented permission-system. you can't see the side without any permission. there are only 2 permissions: show the side and update content (which includes the show permission). --- .../minilinux/lang/de/permissions.json | 4 ++ .../minilinux/lang/en/permissions.json | 4 ++ modules-available/minilinux/page.inc.php | 80 ++++++++++++---------- .../minilinux/permissions/permissions.json | 4 ++ .../minilinux/templates/filelist.html | 6 +- 5 files changed, 59 insertions(+), 39 deletions(-) create mode 100644 modules-available/minilinux/lang/de/permissions.json create mode 100644 modules-available/minilinux/lang/en/permissions.json create mode 100644 modules-available/minilinux/permissions/permissions.json diff --git a/modules-available/minilinux/lang/de/permissions.json b/modules-available/minilinux/lang/de/permissions.json new file mode 100644 index 00000000..372ffc88 --- /dev/null +++ b/modules-available/minilinux/lang/de/permissions.json @@ -0,0 +1,4 @@ +{ + "show": "Zeige Komponenten des Minilinux. Wird nicht benötigt, wenn Nutzer eine der anderen Rechte hat.", + "update": "Aktualisieren von Komponenten des Minilinux." +} \ No newline at end of file diff --git a/modules-available/minilinux/lang/en/permissions.json b/modules-available/minilinux/lang/en/permissions.json new file mode 100644 index 00000000..878388b0 --- /dev/null +++ b/modules-available/minilinux/lang/en/permissions.json @@ -0,0 +1,4 @@ +{ + "show": "Show list of minilinux components. Not needed if User has any of the other permissions.", + "update": "Update minilinux components." +} \ No newline at end of file diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php index 2623500b..df4f14a3 100644 --- a/modules-available/minilinux/page.inc.php +++ b/modules-available/minilinux/page.inc.php @@ -7,7 +7,12 @@ class Page_MiniLinux extends Page { User::load(); - if (!User::hasPermission('superadmin')) { + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); + } + + if (!(User::hasPermission("show") || User::hasPermission("update"))) { Message::addError('main.no-permission'); Util::redirect('?do=Main'); } @@ -81,48 +86,51 @@ class Page_MiniLinux extends Page $system['version'] = $selected['version']; } $data['versions'] = array_values($versionNumbers); + $data['allowedToUpdate'] = User::hasPermission("update"); echo Render::parse('filelist', $data); return; case 'download': - $id = Request::post('id'); - $name = Request::post('name'); - if (!$id || !$name || strpos("$id$name", '/') !== false) { - echo "Invalid download request"; - return; - } - $file = false; - $gpg = 'missing'; - foreach ($data['systems'] as &$system) { - if ($system['id'] !== $id) continue; - foreach ($system['versions'] as &$version) { - if ($version['version'] != $selectedVersion) continue; - foreach ($version['files'] as &$f) { - if ($f['name'] !== $name) continue; - $file = $f; - if (!empty($f['gpg'])) $gpg = $f['gpg']; - break; + if (User::hasPermission("update")) { + $id = Request::post('id'); + $name = Request::post('name'); + if (!$id || !$name || strpos("$id$name", '/') !== false) { + echo "Invalid download request"; + return; + } + $file = false; + $gpg = 'missing'; + foreach ($data['systems'] as &$system) { + if ($system['id'] !== $id) continue; + foreach ($system['versions'] as &$version) { + if ($version['version'] != $selectedVersion) continue; + foreach ($version['files'] as &$f) { + if ($f['name'] !== $name) continue; + $file = $f; + if (!empty($f['gpg'])) $gpg = $f['gpg']; + break; + } } } - } - if ($file === false) { - echo "Nonexistent system/file: $id / $name"; - return; - } - $task = Taskmanager::submit('DownloadFile', array( - 'url' => CONFIG_REMOTE_ML . '/' . $id . '/' . $selectedVersion . '/' . $name, - 'destination' => CONFIG_HTTP_DIR . '/' . $id . '/' . $name, - 'gpg' => $gpg - )); - if (!isset($task['id'])) { - echo 'Error launching download task: ' . $task['statusCode']; + if ($file === false) { + echo "Nonexistent system/file: $id / $name"; + return; + } + $task = Taskmanager::submit('DownloadFile', array( + 'url' => CONFIG_REMOTE_ML . '/' . $id . '/' . $selectedVersion . '/' . $name, + 'destination' => CONFIG_HTTP_DIR . '/' . $id . '/' . $name, + 'gpg' => $gpg + )); + if (!isset($task['id'])) { + echo 'Error launching download task: ' . $task['statusCode']; + return; + } + Property::setDownloadTask($file['md5'], $task['id']); + echo Render::parse('download', array( + 'name' => $name, + 'task' => $task['id'] + )); return; } - Property::setDownloadTask($file['md5'], $task['id']); - echo Render::parse('download', array( - 'name' => $name, - 'task' => $task['id'] - )); - return; } } diff --git a/modules-available/minilinux/permissions/permissions.json b/modules-available/minilinux/permissions/permissions.json new file mode 100644 index 00000000..457d9810 --- /dev/null +++ b/modules-available/minilinux/permissions/permissions.json @@ -0,0 +1,4 @@ +[ + "show", + "update" +] \ No newline at end of file diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html index a1d0aa48..34138c14 100644 --- a/modules-available/minilinux/templates/filelist.html +++ b/modules-available/minilinux/templates/filelist.html @@ -18,7 +18,7 @@

{{lang_canUpdate1}} {{title}} {{lang_canUpdate2}}

-

{{lang_update}}

+ {{/systemChanged}} {{^systemChanged}}

{{lang_systemUpdated}}

@@ -35,8 +35,8 @@ {{#fileChanged}} {{lang_outdated}}{{/fileChanged}}
- {{#fileChanged}}{{lang_update}}{{/fileChanged}} - {{^fileChanged}}{{lang_redownload}}{{/fileChanged}} + {{#fileChanged}} {{/fileChanged}} + {{^fileChanged}} {{/fileChanged}}
{{{download}}} -- cgit v1.2.3-55-g7522 From 734c493dc1e416ee188ad121033b7856e8259816 Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Thu, 18 Jan 2018 17:54:13 +0100 Subject: [statistics] added permissions to view client logs; removed unused query arguments from Paginate::exec (caused an error if query arguments that are actually used in the query are passed to Paginate::exec) --- inc/paginate.inc.php | 2 -- modules-available/syslog/lang/de/permissions.json | 3 +++ modules-available/syslog/lang/en/permissions.json | 3 +++ modules-available/syslog/page.inc.php | 26 +++++++++++++++++++--- .../syslog/permissions/permissions.json | 3 +++ modules-available/syslog/templates/heading.html | 1 + .../syslog/templates/page-syslog.html | 1 - 7 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 modules-available/syslog/lang/de/permissions.json create mode 100644 modules-available/syslog/lang/en/permissions.json create mode 100644 modules-available/syslog/permissions/permissions.json create mode 100644 modules-available/syslog/templates/heading.html diff --git a/inc/paginate.inc.php b/inc/paginate.inc.php index cdb4adf1..b212e252 100644 --- a/inc/paginate.inc.php +++ b/inc/paginate.inc.php @@ -65,8 +65,6 @@ class Paginate $countQuery = preg_replace('/ORDER\s+BY\s.*?(\sASC|\sDESC|$)/is', '', $this->query); $countQuery = preg_replace('/SELECT\s.*?\sFROM\s/is', 'SELECT Count(*) AS rowcount FROM ', $countQuery); $countRes = Database::queryFirst($countQuery, $args); - $args['limit_start'] = $this->currentPage; - $args['limit_count'] = $this->perPage; $query = $this->query . ' LIMIT ' . ($this->currentPage * $this->perPage) . ', ' . $this->perPage; $retval = Database::simpleQuery($query, $args); $this->totalRows = (int)$countRes['rowcount']; diff --git a/modules-available/syslog/lang/de/permissions.json b/modules-available/syslog/lang/de/permissions.json new file mode 100644 index 00000000..0cd05451 --- /dev/null +++ b/modules-available/syslog/lang/de/permissions.json @@ -0,0 +1,3 @@ +{ + "view": "Client Log anschauen." +} \ No newline at end of file diff --git a/modules-available/syslog/lang/en/permissions.json b/modules-available/syslog/lang/en/permissions.json new file mode 100644 index 00000000..497e199e --- /dev/null +++ b/modules-available/syslog/lang/en/permissions.json @@ -0,0 +1,3 @@ +{ + "view": "View client log." +} \ No newline at end of file diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php index c679877a..a34ceb53 100644 --- a/modules-available/syslog/page.inc.php +++ b/modules-available/syslog/page.inc.php @@ -15,6 +15,13 @@ class Page_SysLog extends Page protected function doRender() { + Render::addTemplate("heading"); + + if (!User::hasPermission("view")) { + Message::addError('main.no-permission'); + return; + } + $cutoff = strtotime('-1 month'); $res = Database::simpleQuery("SELECT logtypeid, Count(*) AS counter FROM clientlog WHERE dateline > $cutoff GROUP BY logtypeid ORDER BY counter ASC"); $types = array(); @@ -55,11 +62,24 @@ class Page_SysLog extends Page else $whereClause .= ' AND '; - $whereClause .= "machineuuid='" . preg_replace('/[^0-9a-zA-Z\-]/', '', Request::get('machineuuid', '', 'string')) . "'"; + $whereClause .= "machineuuid='" . preg_replace('/[^0-9a-zA-Z\-]/', '', Request::get('machineuuid', '', 'string')) . "'"; + } + + $allowedLocations = User::getAllowedLocations("view"); + $joinClause = ""; + if (!in_array(0, $allowedLocations)) { + $joinClause = "INNER JOIN machine ON machine.machineuuid = clientlog.machineuuid"; + if (empty($whereClause)) + $whereClause .= ' WHERE '; + else + $whereClause .= ' AND '; + + $whereClause .= 'locationid IN (:allowedLocations)'; } + $lines = array(); - $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50); - $res = $paginate->exec(); + $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientlog.clientip as clientip, description, extra FROM clientlog $joinClause $whereClause ORDER BY logid DESC", 50); + $res = $paginate->exec(array("allowedLocations" => $allowedLocations)); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $row['date'] = Util::prettyTime($row['dateline']); $row['icon'] = $this->eventToIconName($row['logtypeid']); diff --git a/modules-available/syslog/permissions/permissions.json b/modules-available/syslog/permissions/permissions.json new file mode 100644 index 00000000..f04ea714 --- /dev/null +++ b/modules-available/syslog/permissions/permissions.json @@ -0,0 +1,3 @@ +[ + "view" +] \ No newline at end of file diff --git a/modules-available/syslog/templates/heading.html b/modules-available/syslog/templates/heading.html new file mode 100644 index 00000000..d6790a21 --- /dev/null +++ b/modules-available/syslog/templates/heading.html @@ -0,0 +1 @@ +

{{lang_clientLog}}

\ No newline at end of file diff --git a/modules-available/syslog/templates/page-syslog.html b/modules-available/syslog/templates/page-syslog.html index 8b590038..9062dbaa 100644 --- a/modules-available/syslog/templates/page-syslog.html +++ b/modules-available/syslog/templates/page-syslog.html @@ -1,4 +1,3 @@ -

{{lang_clientLog}}

{{lang_proxyConfig}}

-
{{config}}
-
\ No newline at end of file +
+ {{#config}} + {{text1}}{{#text2}}={{/text2}}{{text2}} + {{#extra}} + + {{/extra}} +
+ {{/config}} +
+ -- cgit v1.2.3-55-g7522 From e77086ed9ae09ec569d55fd06814a4a536d2bbea Mon Sep 17 00:00:00 2001 From: Steffen Ritter Date: Thu, 12 Apr 2018 15:11:13 +0200 Subject: [locationinfo] Move save btn to the right (design guidelines) --- .../locationinfo/templates/page-config-panel-default.html | 2 +- .../locationinfo/templates/page-config-panel-summary.html | 2 +- modules-available/locationinfo/templates/page-config-panel-url.html | 2 +- modules-available/locationinfo/templates/page-locations.html | 2 +- modules-available/locationinfo/templates/page-servers.html | 3 +-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules-available/locationinfo/templates/page-config-panel-default.html b/modules-available/locationinfo/templates/page-config-panel-default.html index 41a8fd00..ba493579 100644 --- a/modules-available/locationinfo/templates/page-config-panel-default.html +++ b/modules-available/locationinfo/templates/page-config-panel-default.html @@ -309,8 +309,8 @@ - {{lang_cancel}} + - {{lang_cancel}} + - {{lang_cancel}} + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 3d3c4c8d62f8074935b69b7b002254fd0daf5945 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 25 Apr 2018 16:45:29 +0200 Subject: [locations] Fix machine counting --- modules-available/locations/page.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php index 4d5c6628..5010c9ab 100644 --- a/modules-available/locations/page.inc.php +++ b/modules-available/locations/page.inc.php @@ -404,8 +404,8 @@ class Page_Locations extends Page if (!isset($loc['clientCount'])) { $loc['clientCount'] = 0; $loc['clientLoad'] = '0%'; - $loc['clientCountSum'] += $loc['clientCount']; } + $loc['clientCountSum'] += $loc['clientCount']; foreach ($loc['parents'] as $pid) { if (!in_array($pid, $allowedLocationIds)) continue; -- cgit v1.2.3-55-g7522 From 7d4b41dc7e85b4f5f249c61db11ae27d53144c9c Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 25 Apr 2018 16:46:18 +0200 Subject: [rebootcontrol] Nicer status list; list of all running jobs --- .../rebootcontrol/inc/rebootcontrol.inc.php | 65 ++++++++++++++++++++-- .../rebootcontrol/inc/rebootqueries.inc.php | 4 +- .../rebootcontrol/lang/de/template-tags.json | 6 ++ .../rebootcontrol/lang/en/template-tags.json | 6 ++ modules-available/rebootcontrol/page.inc.php | 48 +++++++++++++--- .../rebootcontrol/templates/_page.html | 44 +++++++-------- .../rebootcontrol/templates/status.html | 26 ++++++--- .../rebootcontrol/templates/task-list.html | 33 +++++++++++ 8 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 modules-available/rebootcontrol/templates/task-list.html diff --git a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php index 789552cd..1024b036 100644 --- a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php +++ b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php @@ -3,28 +3,83 @@ class RebootControl { + const KEY_TASKLIST = 'rebootcontrol.tasklist'; + + const REBOOT = 'REBOOT'; + const KEXEC_REBOOT = 'KEXEC_REBOOT'; + const SHUTDOWN = 'SHUTDOWN'; + /** * @param string[] $uuids List of machineuuids to reboot + * @param bool $kexec whether to trigger kexec-reboot instead of full BIOS cycle * @return false|array task struct for the reboot job */ - public static function reboot($uuids) + public static function reboot($uuids, $kexec = false) { $list = RebootQueries::getMachinesByUuid($uuids); if (empty($list)) return false; - return self::execute($list, false, 0, 0); + return self::execute($list, $kexec ? RebootControl::KEXEC_REBOOT : RebootControl::REBOOT, 0, 0); } - public static function execute($list, $shutdown, $minutes, $locationId) + /** + * @param array $list list of clients containing each keys 'machineuuid' and 'clientip' + * @param string $mode reboot mode: RebootControl::REBOOT ::KEXEC_REBOOT or ::SHUTDOWN + * @param int $minutes delay in minutes for action + * @param int $locationId meta data only: locationId of clients + * @return array|false the task, or false if it could not be started + */ + public static function execute($list, $mode, $minutes, $locationId) { - return Taskmanager::submit("RemoteReboot", array( + $task = Taskmanager::submit("RemoteReboot", array( "clients" => $list, - "shutdown" => $shutdown, + "mode" => $mode, "minutes" => $minutes, "locationId" => $locationId, "sshkey" => SSHKey::getPrivateKey(), "port" => 9922, // Hard-coded, must match mgmt-sshd module )); + if (!Taskmanager::isFailed($task)) { + Property::addToList(RebootControl::KEY_TASKLIST, $locationId . '/' . $task["id"], 60 * 24); + } + return $task; + } + + /** + * @param int[]|null $locations filter by these locations + * @return array list of active tasks for reboots/shutdowns. + */ + public static function getActiveTasks($locations = null) + { + if (is_array($locations) && in_array(0,$locations)) { + $locations = null; + } + $list = Property::getList(RebootControl::KEY_TASKLIST); + $return = []; + foreach ($list as $entry) { + $p = explode('/', $entry, 2); + if (count($p) !== 2) { + Property::removeFromList(RebootControl::KEY_TASKLIST, $entry); + continue; + } + if (is_array($locations) && !in_array($p[0], $locations)) // Ignore + continue; + $id = $p[1]; + $task = Taskmanager::status($id); + if ($task === false) { + Property::removeFromList(RebootControl::KEY_TASKLIST, $entry); + continue; + } + $return[] = [ + 'taskId' => $task['id'], + 'locationId' => $task['data']['locationId'], + 'time' => $task['data']['time'], + 'mode' => $task['data']['mode'], + 'clientCount' => count($task['data']['clients']), + 'status' => $task['statusCode'], + ]; + } + return $return; } } \ No newline at end of file diff --git a/modules-available/rebootcontrol/inc/rebootqueries.inc.php b/modules-available/rebootcontrol/inc/rebootqueries.inc.php index 525a9e58..063b36e4 100644 --- a/modules-available/rebootcontrol/inc/rebootqueries.inc.php +++ b/modules-available/rebootcontrol/inc/rebootqueries.inc.php @@ -44,13 +44,13 @@ class RebootQueries /** * Get machines by list of UUIDs * @param string[] $list list of system UUIDs - * @return array list of machines with machineuuid, clientip, state and locationid + * @return array list of machines with machineuuid, hostname, clientip, state and locationid */ public static function getMachinesByUuid($list) { if (empty($list)) return array(); - $res = Database::simpleQuery("SELECT machineuuid, clientip, state, locationid FROM machine + $res = Database::simpleQuery("SELECT machineuuid, hostname, clientip, state, locationid FROM machine WHERE machineuuid IN (:list)", compact('list')); return $res->fetchAll(PDO::FETCH_ASSOC); } diff --git a/modules-available/rebootcontrol/lang/de/template-tags.json b/modules-available/rebootcontrol/lang/de/template-tags.json index eccc8738..c678ef88 100644 --- a/modules-available/rebootcontrol/lang/de/template-tags.json +++ b/modules-available/rebootcontrol/lang/de/template-tags.json @@ -1,13 +1,17 @@ { + "lang_activeTasks": "Laufende Jobs", "lang_authFail": "Authentifizierung fehlgeschlagen", "lang_client": "Client", + "lang_clientCount": "# Clients", "lang_confirmNewKeypair": "Wirklich neues Schl\u00fcsselpaar erzeugen?", "lang_connecting": "Verbinde...", "lang_error": "Nicht erreichbar", "lang_genNew": "Neues Schl\u00fcsselpaar generieren", "lang_ip": "IP", + "lang_kexecRebootCheck": "Schneller Reboot direkt in bwLehrpool", "lang_location": "Standort", "lang_minutes": " Minuten", + "lang_mode": "Modus", "lang_newKeypairExplanation": "Sie k\u00f6nnen ein neues Schl\u00fcsselpaar erzeugen lassen. In diesem Fall wird das alte Schl\u00fcsselpaar verworfen, sodass alle zum jetzigen Zeitpunkt bereits gestarteten Rechner nicht mehr aus der Ferne bedient werden k\u00f6nnen, bis diese manuell neugestartet wurden.", "lang_off": "Aus", "lang_on": "An", @@ -18,6 +22,7 @@ "lang_rebootButton": "Neustarten", "lang_rebootCheck": "Wollen Sie die ausgew\u00e4hlten Rechner wirklich neustarten?", "lang_rebootControl": "Reboot Control", + "lang_rebootIn": "Neustart in:", "lang_rebooting": "Neustart...", "lang_selectall": "Alle ausw\u00e4hlen", "lang_selected": "Ausgew\u00e4hlt", @@ -29,5 +34,6 @@ "lang_shutdownCheck": "Wollen Sie die ausgew\u00e4hlten Rechner wirklich herunterfahren?", "lang_shutdownIn": "Herunterfahren in: ", "lang_status": "Status", + "lang_time": "Zeit", "lang_unselectall": "Alle abw\u00e4hlen" } \ No newline at end of file diff --git a/modules-available/rebootcontrol/lang/en/template-tags.json b/modules-available/rebootcontrol/lang/en/template-tags.json index c2346044..c64014ff 100644 --- a/modules-available/rebootcontrol/lang/en/template-tags.json +++ b/modules-available/rebootcontrol/lang/en/template-tags.json @@ -1,13 +1,17 @@ { + "lang_activeTasks": "Active tasks", "lang_authFail": "Authentication failed", "lang_client": "Client", + "lang_clientCount": "# clients", "lang_confirmNewKeypair": "Really create new key pair?", "lang_connecting": "Connecting...", "lang_error": "Not available", "lang_genNew": "Generate new keypair", "lang_ip": "IP", + "lang_kexecRebootCheck": "Quick reboot straight to bwLehrpool (kexec)", "lang_location": "Location", "lang_minutes": " Minutes", + "lang_mode": "Mode", "lang_newKeypairExplanation": "You can create a new keypair, which will replace the old one. Please note that after doing so, you cannot poweroff or reboot clients that are already running, since they still use the old key. They have to be rebooted manually first.", "lang_off": "Off", "lang_on": "On", @@ -18,6 +22,7 @@ "lang_rebootButton": "Reboot", "lang_rebootCheck": "Do you really want to reboot the selected clients?", "lang_rebootControl": "Reboot Control", + "lang_rebootIn": "Reboot in:", "lang_rebooting": "Rebooting...", "lang_selectall": "Select all", "lang_selected": "Selected", @@ -29,5 +34,6 @@ "lang_shutdownCheck": "Do you really want to shut down the selected clients?", "lang_shutdownIn": "Shutdown in: ", "lang_status": "Status", + "lang_time": "Time", "lang_unselectall": "Unselect all" } \ No newline at end of file diff --git a/modules-available/rebootcontrol/page.inc.php b/modules-available/rebootcontrol/page.inc.php index 675c94f8..3a438504 100644 --- a/modules-available/rebootcontrol/page.inc.php +++ b/modules-available/rebootcontrol/page.inc.php @@ -27,7 +27,6 @@ class Page_RebootControl extends Page Message::addError('no-clients-selected'); Util::redirect(); } - $minutes = Request::post('minutes', 0, 'int'); $actualClients = RebootQueries::getMachinesByUuid($requestedClients); if (count($actualClients) !== count($requestedClients)) { @@ -56,8 +55,22 @@ class Page_RebootControl extends Page return 0; return $a ? -1 : 1; }); - $task = RebootControl::execute($actualClients, $this->action === 'shutdown', $minutes, $locationId); - Util::redirect("?do=rebootcontrol&taskid=".$task["id"]); + if ($this->action === 'shutdown') { + $mode = 'SHUTDOWN'; + $minutes = Request::post('s-minutes', 0, 'int'); + } elseif (Request::any('quick', false, 'string') === 'on') { + $mode = 'KEXEC_REBOOT'; + $minutes = Request::post('r-minutes', 0, 'int'); + } else { + $mode = 'REBOOT'; + $minutes = Request::post('r-minutes', 0, 'int'); + } + $task = RebootControl::execute($actualClients, $mode, $minutes, $locationId); + if (Taskmanager::isTask($task)) { + Util::redirect("?do=rebootcontrol&taskid=" . $task["id"]); + } else { + Util::redirect("?do=rebootcontrol"); + } } } @@ -71,15 +84,22 @@ class Page_RebootControl extends Page if ($this->action === 'show') { $data = []; - $taskId = Request::get("taskid"); + $task = Request::get("taskid", false, 'string'); + if ($task !== false) { + $task = Taskmanager::status($task); + } - if ($taskId && Taskmanager::isTask($taskId)) { - $task = Taskmanager::status($taskId); - $data['taskId'] = $taskId; + if (Taskmanager::isTask($task)) { + + $data['taskId'] = $task['id']; $data['locationId'] = $task['data']['locationId']; $data['locationName'] = Location::getName($task['data']['locationId']); - $data['clients'] = $task['data']['clients']; + $uuids = array_map(function($entry) { + return $entry['machineuuid']; + }, $task['data']['clients']); + $data['clients'] = RebootQueries::getMachinesByUuid($uuids); Render::addTemplate('status', $data); + } else { //location you want to see, default are "not assigned" clients @@ -103,6 +123,8 @@ class Page_RebootControl extends Page foreach ($data['locations'] as &$loc) { if (!in_array($loc["locationid"], $allowedLocs)) { $loc["disabled"] = "disabled"; + } elseif ($loc["locationid"] == $requestedLocation) { + $data['location'] = $loc['locationname']; } } // Always show public key (it's public, isn't it?) @@ -121,6 +143,16 @@ class Page_RebootControl extends Page Render::addTemplate('_page', $data); } + // Append list of active reboot/shutdown tasks + $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]); + } + } } } diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html index 82f82b02..a124e165 100644 --- a/modules-available/rebootcontrol/templates/_page.html +++ b/modules-available/rebootcontrol/templates/_page.html @@ -1,4 +1,6 @@ -
+

{{location}}

+ +
@@ -45,9 +47,7 @@
- - -
-
- {{lang_generationFailed}} +
+
+ {{lang_generationFailed}} +
{{lang_menuGeneration}}
-- cgit v1.2.3-55-g7522 From 5e786faa6c577abfd48976a6839f61d08b0a6e5e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 24 May 2018 15:35:46 +0200 Subject: Introduce proper constants for taskmanager status --- inc/property.inc.php | 2 +- inc/taskmanager.inc.php | 27 ++++++++++++---------- .../sysconfig/addmodule_custommodule.inc.php | 4 ++-- .../sysconfig/inc/configmodule.inc.php | 2 +- modules-available/sysconfig/inc/configtgz.inc.php | 2 +- modules-available/sysconfig/page.inc.php | 6 ++--- 6 files changed, 23 insertions(+), 20 deletions(-) diff --git a/inc/property.inc.php b/inc/property.inc.php index 56adb823..b69be1f8 100644 --- a/inc/property.inc.php +++ b/inc/property.inc.php @@ -168,7 +168,7 @@ class Property if (!Taskmanager::isFinished($task)) { $task = Taskmanager::waitComplete($task['id'], 5000); } - if ($task['statusCode'] !== TASK_FINISHED || !isset($task['data']['content'])) { + if ($task['statusCode'] !== Taskmanager::TASK_FINISHED || !isset($task['data']['content'])) { return isset($task['data']['error']) ? $task['data']['error'] : 'Timeout'; } $data = json_decode($task['data']['content'], true); diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php index 54821e59..dab950ed 100644 --- a/inc/taskmanager.inc.php +++ b/inc/taskmanager.inc.php @@ -6,6 +6,13 @@ class Taskmanager { + const NO_SUCH_TASK = 'NO_SUCH_TASK'; + const TASK_FINISHED = 'TASK_FINISHED'; + const TASK_ERROR = 'TASK_ERROR'; + const TASK_WAITING = 'TASK_WAITING'; + const NO_SUCH_INSTANCE = 'NO_SUCH_INSTANCE'; + const TASK_PROCESSING = 'TASK_PROCESSING'; + /** * UDP socket used for communication with the task manager * @var resource @@ -49,7 +56,7 @@ class Taskmanager if ($async) return true; $reply = self::readReply($seq); - if ($reply === false || !is_array($reply) || !isset($reply['id']) || (isset($reply['statusCode']) && $reply['statusCode'] === NO_SUCH_TASK)) { + if ($reply === false || !is_array($reply) || !isset($reply['id']) || (isset($reply['statusCode']) && $reply['statusCode'] === Taskmanager::NO_SUCH_TASK)) { self::addErrorMessage($reply); return false; } @@ -82,7 +89,7 @@ class Taskmanager /** * Checks whether the given task id corresponds to a known task in the taskmanager. * Returns true iff the taskmanager is reachable and the status of the task - * is different from NO_SUCH_INSTANCE/_TASK. + * is different from Taskmanager::NO_SUCH_INSTANCE/_TASK. * If you pass an array it is assumed that it was already queried and is evaluated * directly. * @@ -96,8 +103,8 @@ class Taskmanager if (is_string($task)) { $task = self::status($task); } - return isset($task['statusCode']) && $task['statusCode'] !== NO_SUCH_INSTANCE - && $task['statusCode'] !== NO_SUCH_TASK; + return isset($task['statusCode']) && $task['statusCode'] !== Taskmanager::NO_SUCH_INSTANCE + && $task['statusCode'] !== Taskmanager::NO_SUCH_TASK; } /** @@ -110,7 +117,7 @@ class Taskmanager public static function waitComplete($task, $timeout = 2500) { if (is_array($task) && isset($task['id'])) { - if ($task['statusCode'] !== TASK_PROCESSING && $task['statusCode'] !== TASK_WAITING) { + if ($task['statusCode'] !== Taskmanager::TASK_PROCESSING && $task['statusCode'] !== Taskmanager::TASK_WAITING) { self::release($task['id']); return $task; } @@ -124,7 +131,7 @@ class Taskmanager $status = self::status($task); if (!isset($status['statusCode'])) break; - if ($status['statusCode'] !== TASK_PROCESSING && $status['statusCode'] !== TASK_WAITING) { + if ($status['statusCode'] !== Taskmanager::TASK_PROCESSING && $status['statusCode'] !== Taskmanager::TASK_WAITING) { $done = true; break; } @@ -147,7 +154,7 @@ class Taskmanager { if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id'])) return true; - if ($task['statusCode'] !== TASK_WAITING && $task['statusCode'] !== TASK_PROCESSING && $task['statusCode'] !== TASK_FINISHED) + if ($task['statusCode'] !== Taskmanager::TASK_WAITING && $task['statusCode'] !== Taskmanager::TASK_PROCESSING && $task['statusCode'] !== Taskmanager::TASK_FINISHED) return true; return false; } @@ -163,7 +170,7 @@ class Taskmanager { if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id'])) return false; - if ($task['statusCode'] !== TASK_WAITING && $task['statusCode'] !== TASK_PROCESSING) + if ($task['statusCode'] !== Taskmanager::TASK_WAITING && $task['statusCode'] !== Taskmanager::TASK_PROCESSING) return true; return false; } @@ -244,7 +251,3 @@ class Taskmanager } } - -foreach (array('TASK_FINISHED', 'TASK_ERROR', 'TASK_WAITING', 'NO_SUCH_TASK', 'NO_SUCH_INSTANCE', 'TASK_PROCESSING') as $i) { - define($i, $i); -} diff --git a/modules-available/sysconfig/addmodule_custommodule.inc.php b/modules-available/sysconfig/addmodule_custommodule.inc.php index 8c24a071..c234f765 100644 --- a/modules-available/sysconfig/addmodule_custommodule.inc.php +++ b/modules-available/sysconfig/addmodule_custommodule.inc.php @@ -62,7 +62,7 @@ class CustomModule_ProcessUpload extends AddModule_Base unlink($tempfile); $this->tmError(); } - if ($status['statusCode'] != TASK_FINISHED) { + if ($status['statusCode'] != Taskmanager::TASK_FINISHED) { unlink($tempfile); $this->taskError($status); } @@ -128,7 +128,7 @@ class CustomModule_CompressModule extends AddModule_Base if (!isset($status['statusCode'])) { $this->tmError(); } - if ($status['statusCode'] != TASK_FINISHED) { + if ($status['statusCode'] != Taskmanager::TASK_FINISHED) { $this->taskError($status); } // Seems ok, create entry diff --git a/modules-available/sysconfig/inc/configmodule.inc.php b/modules-available/sysconfig/inc/configmodule.inc.php index 2cee37a9..b6db9c4f 100644 --- a/modules-available/sysconfig/inc/configmodule.inc.php +++ b/modules-available/sysconfig/inc/configmodule.inc.php @@ -354,7 +354,7 @@ abstract class ConfigModule // Wait for generation if requested if ($timeoutMs > 0 && isset($ret['id']) && !Taskmanager::isFinished($ret)) $ret = Taskmanager::waitComplete($ret, $timeoutMs); - if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) { + if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === Taskmanager::TASK_FINISHED)) { // Already Finished if (file_exists($this->moduleArchive) && !file_exists($tmpTgz)) $tmpTgz = false; // If generateInternal succeeded and there's no tmpTgz, it means the file didn't have to be updated diff --git a/modules-available/sysconfig/inc/configtgz.inc.php b/modules-available/sysconfig/inc/configtgz.inc.php index 5b459a2e..7b042cdb 100644 --- a/modules-available/sysconfig/inc/configtgz.inc.php +++ b/modules-available/sysconfig/inc/configtgz.inc.php @@ -112,7 +112,7 @@ class ConfigTgz // Wait for completion if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task)) $task = Taskmanager::waitComplete($task, $timeoutMs); - if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === TASK_FINISHED)) { + if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_FINISHED)) { // Success! $this->markUpdated(); return true; diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php index 8d1799af..515d432c 100644 --- a/modules-available/sysconfig/page.inc.php +++ b/modules-available/sysconfig/page.inc.php @@ -425,12 +425,12 @@ class Page_SysConfig extends Page $task = Taskmanager::submit('DeleteFile', array( 'file' => $row['filepath'] )); - if (isset($task['statusCode']) && $task['statusCode'] === TASK_WAITING) { + if (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_WAITING) { $task = Taskmanager::waitComplete($task['id']); } - if (!isset($task['statusCode']) || $task['statusCode'] === TASK_ERROR) { + if (!isset($task['statusCode']) || $task['statusCode'] === Taskmanager::TASK_ERROR) { Message::addWarning('main.task-error', $task['data']['error']); - } elseif ($task['statusCode'] === TASK_FINISHED) { + } elseif ($task['statusCode'] === Taskmanager::TASK_FINISHED) { Message::addSuccess('module-deleted', $row['title']); } Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleid)); -- cgit v1.2.3-55-g7522 From e57eb1a28dff41c54f2c1e0aab6badbbde0bd1df Mon Sep 17 00:00:00 2001 From: Steffen Ritter Date: Wed, 30 May 2018 17:17:03 +0200 Subject: [exams] Fix warning msg when checking timeranges (sec vs ms) --- modules-available/exams/templates/page-add-edit-exam.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html index 11bffed8..dc29a7f5 100644 --- a/modules-available/exams/templates/page-add-edit-exam.html +++ b/modules-available/exams/templates/page-add-edit-exam.html @@ -221,8 +221,8 @@ document.addEventListener("DOMContentLoaded", function () { var to = e.data('to'); if (!from || !to) return; - from = slxMoment(from); - to = slxMoment(to); + from = slxMoment.unix(from); + to = slxMoment.unix(to); if (from.isBefore(sd) || to.isAfter(ed)) { e.css('color', '#999'); e.data('inrange', false) @@ -239,7 +239,7 @@ document.addEventListener("DOMContentLoaded", function () { if (sel.val() === '' || sel.data('inrange')) { $('#lecture-info').text('-'); } else { - $('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment(sel.data('from') * 1000).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment(sel.data('to') * 1000).format('YYYY-MM-DD H:mm') + ')'); + $('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment.unix(sel.data('from')).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment.unix(sel.data('to')).format('YYYY-MM-DD H:mm') + ')'); } }; -- cgit v1.2.3-55-g7522 From 09bfa9c947298ed810b91baeb27e9fae7ea091c7 Mon Sep 17 00:00:00 2001 From: Steffen Ritter Date: Wed, 6 Jun 2018 16:09:22 +0200 Subject: [statistics] Add location column in clientlist --- modules-available/statistics/page.inc.php | 1 + modules-available/statistics/templates/clientlist.html | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index b4d3a42d..abacc8d2 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -679,6 +679,7 @@ class Page_Statistics extends Page $row['modeName'] = $data['modeName']; } } + $row['locationname'] = Location::getName($row['locationid']); $rows[] = $row; } if ($singleMachine !== false && $singleMachine !== 'none') { diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html index b81515ab..18a5d10a 100644 --- a/modules-available/statistics/templates/clientlist.html +++ b/modules-available/statistics/templates/clientlist.html @@ -34,6 +34,11 @@ + + + {{lang_machine}} @@ -43,6 +48,7 @@ {{lang_gbRam}} {{lang_tmpGb}} {{lang_cpuModel}} + {{lang_location}} @@ -97,6 +103,7 @@ {{/nohdd}} {{lang_realCores}}: {{realcores}}
{{cpumodel}}
+ {{locationname}} {{/rows}} -- cgit v1.2.3-55-g7522 From db0b9f1c1c8c2b20a405edeefc5240a6c0bfb1f5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Jun 2018 17:13:06 +0200 Subject: [inc/Dashboard] getter for currently defined sub menus --- inc/dashboard.inc.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/dashboard.inc.php b/inc/dashboard.inc.php index 59d81c2d..d576a8f1 100644 --- a/inc/dashboard.inc.php +++ b/inc/dashboard.inc.php @@ -103,5 +103,10 @@ class Dashboard { self::$subMenu[] = array('url' => $url, 'name' => $name); } + + public static function getSubmenus() + { + return self::$subMenu; + } } \ No newline at end of file -- cgit v1.2.3-55-g7522 From 6c7a5ff06159c6908a47f11adbe30e2349a5fd75 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Jun 2018 17:13:37 +0200 Subject: [inc/Database] Method to return single-column queries as array --- inc/database.inc.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/inc/database.inc.php b/inc/database.inc.php index d5992795..3b2414b5 100644 --- a/inc/database.inc.php +++ b/inc/database.inc.php @@ -76,6 +76,19 @@ class Database return $res->fetchAll(PDO::FETCH_ASSOC); } + /** + * Fetch the first column of the query as a plain list-of-values array. + * + * @return array|bool List of values representing first column of query + */ + public static function queryColumnArray($query, $args = array(), $ignoreError = null) + { + $res = self::simpleQuery($query, $args, $ignoreError); + if ($res === false) + return false; + return $res->fetchAll(PDO::FETCH_COLUMN, 0); + } + /** * Execute the given query and return the number of rows affected. * Mostly useful for UPDATEs or INSERTs -- cgit v1.2.3-55-g7522 From 2e78eec281815d6ba42ff2cf7c3a937abe6d83c5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Jun 2018 17:15:44 +0200 Subject: [serversetup-bwlp] Start rewrite as purely iPXE-based --- modules-available/serversetup-bwlp/api.inc.php | 242 +++++++++ modules-available/serversetup-bwlp/config.json | 5 +- .../serversetup-bwlp/inc/bootentry.inc.php | 195 +++++++ .../serversetup-bwlp/inc/ipxe.inc.php | 560 ++++++++++++++------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 142 ++++++ .../serversetup-bwlp/inc/menuentry.inc.php | 170 +++++++ .../serversetup-bwlp/inc/pxelinux.inc.php | 262 ++++++++++ modules-available/serversetup-bwlp/install.inc.php | 74 +++ modules-available/serversetup-bwlp/page.inc.php | 300 ++++++++++- .../serversetup-bwlp/permissions/permissions.json | 14 +- .../serversetup-bwlp/templates/download.html | 38 ++ .../serversetup-bwlp/templates/heading.html | 4 +- .../templates/ipxe-new-boot-entry.html | 108 ++++ .../serversetup-bwlp/templates/ipxe.html | 117 ----- .../serversetup-bwlp/templates/menu-edit.html | 114 +++++ .../serversetup-bwlp/templates/menu-list.html | 36 ++ 16 files changed, 2049 insertions(+), 332 deletions(-) create mode 100644 modules-available/serversetup-bwlp/api.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/bootentry.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/ipxemenu.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/menuentry.inc.php create mode 100644 modules-available/serversetup-bwlp/inc/pxelinux.inc.php create mode 100644 modules-available/serversetup-bwlp/install.inc.php create mode 100644 modules-available/serversetup-bwlp/templates/download.html create mode 100644 modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe.html create mode 100644 modules-available/serversetup-bwlp/templates/menu-edit.html create mode 100644 modules-available/serversetup-bwlp/templates/menu-list.html diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php new file mode 100644 index 00000000..36f9063c --- /dev/null +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -0,0 +1,242 @@ + 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', +]; + +$serverIp = Property::getServerIp(); + +$ip = $_SERVER['REMOTE_ADDR']; +if (substr($ip, 0, 7) === '::ffff:') { + $ip = substr($ip, 7); +} +$uuid = Request::any('uuid', false, 'string'); +$menu = IPxeMenu::forClient($ip, $uuid); + +// Get platform - EFI or PCBIOS +$platform = strtoupper(Request::any('platform', 'PCBIOS', 'string')); + +// Get preferred localboot method, depending on system model +$localboot = false; +$model = false; +if ($uuid !== false && Module::get('statistics') !== false) { + $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]); + if ($row !== false && !empty($row['systemmodel'])) { + $model = $row['systemmodel']; + } +} +if ($model === false) { + function modfilt($str) + { + if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) + return false; + return trim(preg_replace('/\s+/', ' ', $str)); + } + $manuf = modfilt(Request::any('manuf', false, 'string')); + $product = modfilt(Request::any('product', false, 'string')); + if (!empty($product)) { + $model = $product; + if (!empty($manuf)) { + $model .= " ($manuf)"; + } + } +} +// Query +if ($model !== false) { + $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", + ['model' => $model]); + if ($row !== false) { + $localboot = $row['bootmethod']; + } +} +if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { + $localboot = Property::get('serversetup.localboot', false); + if ($localboot === false) { + if ($platform === 'EFI') { + // It seems most (all) EFI platforms won't enumerate any drives in ipxe. + // No idea if this can be fixed in ipxe code in the future. + $localboot = 'EXIT'; + } else { + $localboot = 'SANBOOT'; + } + } +} +if (isset($BOOT_METHODS[$localboot])) { + // Move preferred method first + $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; + unset($BOOT_METHODS[$localboot]); + $BOOT_METHODS = array_reverse($BOOT_METHODS); +} + +$output = <<getMenuDefinition('target'); + +$output .= <<getItemsCode(); + +/* +:i1 +#console || +echo Welcome to Shell || +shell +goto slx_menu + +:i2 +imgfree || +kernel /boot/default/kernel slxbase=boot/default slxsrv=$serverIp splash BOOTIF=01-\${net\${nic}/mac:hexhyp} || echo Could not download kernel +initrd /boot/default/initramfs-stage31 || echo Could not download initrd +boot -ar || goto fail + +:i3 +chain -ar \${self} || +chain -ar /tftp/undionly.kpxe || goto fail + +:i4 +imgfree || +sanboot --no-describe --drive 0x80 || goto fail + +:i5 +chain -a /tftp/memtest.0 passes=1 onepass || goto membad +prompt Memory OK. Press a key. +goto init + +:i6 +console --left 60 --top 130 --right 67 --bottom 96 --quick --picture bg-load --keep || +echo Welcome to Shell || +shell +goto slx_menu + +:i7 +chain -ar tftp://132.230.4.6/ipxelinux.0 || prompt FAILED PRESS A KEY +goto slx_menu + +:i8 +set x:int32 0 +:again +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep --quick || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep --quick || +inc x +iseq \${x} 20 || goto again +prompt DONE. Press dein Knie. +goto slx_menu + +:i9 +reboot || +prompt Reboot failed. Press a key. +goto slx_menu + +:i10 +poweroff || +prompt Poweroff failed. Press a key. +goto slx_menu + +:membad +iseq \${errno} 0x1 || goto memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory is bad. Press a key. +goto init + +:memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory test aborted. Press a key. +goto init + +*/ + +$output .= << $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } + } + + public abstract function toScript($failLabel); + + public abstract function toArray(); + + public abstract function addFormFields(&$array); + + /* + * + */ + + /** + * Return a BootEntry instance from the serialized data. + * + * @param string $jsonString serialized entry data + * @return BootEntry|null instance representing boot entry, null on error + */ + public static function fromJson($data) + { + if (is_string($data)) { + $data = json_decode($data, true); + } + if (isset($data['script'])) { + return new CustomBootEntry($data); + } + if (isset($data['executable'])) { + return new StandardBootEntry($data); + } + return null; + } + + public static function newStandardBootEntry($initData) + { + if (empty($initData['executable'])) + return null; + return new StandardBootEntry($initData); + } + + public static function newCustomBootEntry($initData) + { + if (empty($initData['script'])) + return null; + return new CustomBootEntry($initData); + } + + /** + * Return a BootEntry instance from database with the given id. + * + * @param string $id + * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success + */ + public static function fromDatabaseId($id) + { + $row = Database::queryFirst("SELECT data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1", ['id' => $id]); + if ($row === false) + return false; + return self::fromJson($row['data']); + } + +} + +class StandardBootEntry extends BootEntry +{ + protected $executable; + protected $initRd; + protected $commandLine; + protected $replace; + protected $autoUnload; + protected $resetConsole; + + public function __construct($data = false) + { + if ($data instanceof PxeSection) { + $this->executable = $data->kernel; + $this->initRd = $data->initrd; + $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; + $this->resetConsole = true; + $this->replace = true; + $this->autoUnload = true; + if (strpos($this->commandLine, ' quiet ') !== false) { + $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; + } + if ($data->ipAppend & 1) { + $this->commandLine .= ' ${ipappend1}'; + } + if ($data->ipAppend & 2) { + $this->commandLine .= ' ${ipappend2}'; + } + if ($data->ipAppend & 4) { + $this->commandLine .= ' SYSUUID=${uuid}'; + } + $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); + } else { + parent::__construct($data); + } + } + + public function toScript($failLabel) + { + $script = ''; + if ($this->resetConsole) { + $script .= "console ||\n"; + } + if (!empty($this->initRd)) { + $script .= "imgfree ||\n"; + if (!is_array($this->initRd)) { + $script .= "initrd {$this->initRd} || goto $failLabel\n"; + } else { + foreach ($this->initRd as $initrd) { + $script .= "initrd $initrd || goto $failLabel\n"; + } + } + } + $script .= "boot "; + if ($this->autoUnload) { + $script .= "-a "; + } + if ($this->replace) { + $script .= "-r "; + } + $script .= "{$this->executable}"; + if (!empty($this->commandLine)) { + $script .= " {$this->commandLine}"; + } + $script .= " || goto $failLabel\n"; + if ($this->resetConsole) { + $script .= "goto start ||\n"; + } + return $script; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace_checked' => $this->replace ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole ? 'checked' : '', + ]; + $array['exec_checked'] = 'checked'; + } + + public function toArray() + { + return [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace' => $this->replace, + 'autoUnload' => $this->autoUnload, + 'resetConsole' => $this->resetConsole, + ]; + } +} + +class CustomBootEntry extends BootEntry +{ + protected $script; + + public function toScript($failLabel) + { + return str_replace('%fail%', $failLabel, $this->script) . "\n"; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'script' => $this->script, + ]; + $array['script_checked'] = 'checked'; + } + + public function toArray() + { + return ['script' => $this->script]; + } +} diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php index c42de80b..d5bbb4b2 100644 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ b/modules-available/serversetup-bwlp/inc/ipxe.inc.php @@ -1,224 +1,398 @@ ['string', 'title'], - 'menu default' => ['true', 'isDefault'], - 'menu hide' => ['true', 'isHidden'], - 'menu disabled' => ['true', 'isDisabled'], - 'menu indent' => ['int', 'indent'], - 'kernel' => ['string', 'kernel'], - 'initrd' => ['string', 'initrd'], - 'append' => ['string', 'append'], - 'ipappend' => ['int', 'ipAppend'], - 'localboot' => ['int', 'localBoot'], - ]; - $globalPropMap = [ - 'timeout' => ['int', 'timeoutMs', 100], - 'totaltimeout' => ['int', 'totalTimeoutMs', 100], - 'menu title' => ['string', 'title'], - 'menu clear' => ['true', 'menuClear'], - 'menu immediate' => ['true', 'immediateHotkeys'], - 'ontimeout' => ['string', 'timeoutLabel'], - ]; - $lines = preg_split('/[\r\n]+/', $input); - $section = null; - $count = count($lines); - for ($li = 0; $li < $count; ++$li) { - $line =& $lines[$li]; - if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + foreach (glob($configPath . '/*', GLOB_NOSORT) as $file) { + if (!is_file($file) || !preg_match('~/[A-F0-9]{1,8}$~', $file)) continue; - $key = trim($out[1]); - $key = strtolower($key); - $key = preg_replace('/\s+/', ' ', $key); - if ($key === 'label') { - if ($section !== null) { - $menu->sections[] = $section; - } - $section = new PxeSection($out[2]); - } elseif ($key === 'menu separator') { - if ($section !== null) { - $menu->sections[] = $section; - $section = null; - } - $menu->sections[] = new PxeSection(null); - } elseif (self::handleKeyword($key, $out[2], $globalPropMap, $menu)) { + $content = file_get_contents($file); + if ($content === false) continue; - } elseif ($section === null) { - continue; - } elseif ($key === 'text' && strtolower($out[2]) === 'help') { - $text = ''; - while (++$li < $count) { - $line =& $lines[$li]; - if (strtolower(trim($line)) === 'endtext') - break; - $text .= $line . "\n"; + $file = basename($file); + $start = hexdec(str_pad($file,8, '0')); + $end = hexdec(str_pad($file,8, 'f')); // TODO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^PREFIX + error_log('From ' . long2ip($start) . ' to ' . long2ip($end)); + $res = Database::simpleQuery("SELECT locationid, startaddr, endaddr FROM subnet + WHERE startaddr >= :start AND endaddr <= :end", compact('start', 'end')); + $locations = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($locations as &$loc) { + if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { + $loc = false; + } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { + continue 2; + } } - $section->helpText = $text; - } elseif (self::handleKeyword($key, $out[2], $sectionPropMap, $section)) { + unset($loc); + $locations[] = $row; + } + $menuId = self::insertMenu($content, 'Imported', false, 0, [], []); + if ($menuId === false) continue; + foreach ($locations as $loc) { + Database::exec('INSERT IGNORE INTO serversetup_menu_x_location (menuid, locationid) + VALUES (:menuid, :locationid)', ['menuid' => $menuId, 'locationid' => $loc['locationid']]); } } - if ($section !== null) { - $menu->sections[] = $section; + } + + public static function importLegacyMenu($force = false) + { + if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) + return false; // Already exists + // Now create the default entry + self::createDefaultEntries(); + $prepend = ['bwlp-default' => false, 'localboot' => false]; + $defaultLabel = 'bwlp-default'; + $menuTitle = 'bwLehrpool Bootauswahl'; + $pxeConfig = ''; + $timeoutSecs = 60; + // Try to import any customization + $oldMenu = Property::getBootMenu(); + if (is_array($oldMenu)) { + // + if (isset($oldMenu['timeout'])) { + $timeoutSecs = (int)$oldMenu['timeout']; + } + if (isset($oldMenu['defaultentry'])) { + if ($oldMenu['defaultentry'] === 'net') { + $defaultLabel = 'bwlp-default'; + } elseif ($oldMenu['defaultentry'] === 'hdd') { + $defaultLabel = 'localboot'; + } elseif ($oldMenu['defaultentry'] === 'custom') { + $defaultLabel = 'custom'; + } + } + if (!empty($oldMenu['custom'])) { + $pxeConfig = $oldMenu['custom']; + } } - return $menu; + $append = [ + '', + 'bwlp-default-dbg' => false, + '', + 'poweroff' => false, + ]; + return self::insertMenu($pxeConfig, $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); } - /** - * Check if keyword is valid and if so, add its interpreted value - * to the given object. The map to look up the keyword has to be passed - * as well as the object to set the value in. Map and object should - * obviously match. - * @param string $key keyword of parsed line - * @param string $val raw value of currently parsed line (empty if not present) - * @param array $map Map in which $key is looked up as key - * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in - * @return bool true if the value was found in the map (and set in the object), false otherwise - */ - private static function handleKeyword($key, $val, $map, $object) + private static function insertMenu($pxeConfig, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) { - if (!isset($map[$key])) + $timeoutMs = []; + $menuEntries = $prepend; + settype($menuEntries, 'array'); + if (!empty($pxeConfig)) { + $pxe = PxeLinux::parsePxeLinux($pxeConfig); + if (!empty($pxe->title)) { + $menuTitle = $pxe->title; + } + if ($pxe->timeoutLabel !== null) { + $defaultLabel = $pxe->timeoutLabel; + } + $timeoutMs[] = $pxe->timeoutMs; + $timeoutMs[] = $pxe->totalTimeoutMs; + foreach ($pxe->sections as $section) { + if ($section->localBoot || preg_match('/chain.c32$/i', $section->kernel)) { + $menuEntries['localboot'] = 'localboot'; + continue; + } + $section->mangle(); + if ($section->label === null) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + if (empty($section->kernel)) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + $entry = self::pxe2BootEntry($section); + if ($entry === null) + continue; + $label = self::cleanLabelFixLocal($section); + if ($defaultLabel === $section->label) { + $defaultLabel = $label; + } + $hotkey = MenuEntry::filterKeyName($section->hotkey); + // Create boot entry + $data = $entry->toArray(); + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => self::sanitizeIpxeString($section->title), + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + if (is_array($append)) { + $menuEntries += $append; + } + if (empty($menuEntries)) return false; - $opt = $map[$key]; - // opt[0] is the type the value should be cast to; special case "true" means - // this is a bool option that will be set as soon as the keyword is present, - // as it doesn't have any parameters - if ($opt[0] === 'true') { - $val = true; + // Make menu + $timeoutMs = array_filter($timeoutMs, 'is_int'); + if (empty($timeoutMs)) { + $timeoutMs = (int)($defaultTimeoutSeconds * 1000); } else { - settype($val, $opt[0]); + $timeoutMs = min($timeoutMs); } - // If opt[2] is present it's a multiplier for the value - if (isset($opt[2])) { - $val *= $opt[2]; + $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); + Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) + VALUES (:timeoutms, :title, NULL, :isdefault)", [ + 'title' => self::sanitizeIpxeString($menuTitle), + 'timeoutms' => $timeoutMs, + 'isdefault' => $isDefault, + ]); + $menuId = Database::lastInsertId(); + if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { + $defaultLabel = array_keys($menuEntries)[0]; } - $object->{$opt[1]} = $val; - return true; + // Link boot entries to menu + $defaultEntryId = null; + $order = 1000; + foreach ($menuEntries as $label => $entry) { + if (is_string($entry)) { + // Gap entry + Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ + 'menuid' => $menuId, + 'entryid' => null, + 'hotkey' => '', + 'title' => self::sanitizeIpxeString($entry), + 'hidden' => 0, + 'sortval' => $order += 100, + ]); + continue; + } + $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); + if ($data === false) + continue; + $data['pass'] = ''; + $data['hidden'] = 0; + if ($entry instanceof PxeSection) { + $data['hidden'] = (int)$entry->isHidden; + // Prefer explicit data from this imported menu over the defaults + $data['title'] = self::sanitizeIpxeString($entry->title); + if (MenuEntry::getKeyCode($entry->hotkey) !== false) { + $data['hotkey'] = $entry->hotkey; + } + if (!empty($entry->passwd)) { + // Most likely it's a hash so we cannot recover; ask people to reset + $data['pass'] ='please_reset'; + } + } + $data['menuid'] = $menuId; + $data['sortval'] = $order += 100; + $res = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); + if ($res !== false && $label === $defaultLabel) { + $defaultEntryId = Database::lastInsertId(); + } + } + // Now we can set default entry + if (!empty($defaultEntryId)) { + Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", + ['menuid' => $menuId, 'entryid' => $defaultEntryId]); + } + // TODO: masterpw? rather pointless.... + //$oldMenu['masterpasswordclear']; + return $menuId; } -} + private static function createDefaultEntries() + { + $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:entryid, :hotkey, :title, 1, :data)'; + Database::exec($query, + [ + 'entryid' => 'bwlp-default', + 'hotkey' => 'B', + 'title' => 'bwLehrpool-Umgebung starten', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'bwlp-default-dbg', + 'hotkey' => '', + 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default loglevel=7 ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'localboot', + 'hotkey' => 'L', + 'title' => 'Lokales System starten', + 'data' => json_encode([ + 'script' => 'goto slx_localboot || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'poweroff', + 'hotkey' => 'P', + 'title' => 'Power off', + 'data' => json_encode([ + 'script' => 'poweroff || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'reboot', + 'hotkey' => 'R', + 'title' => 'Reboot', + 'data' => json_encode([ + 'script' => 'reboot || goto %fail% ||', + ]), + ]); + } -/** - * Class representing a parsed pxelinux menu. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeMenu -{ - /** - * @var string menu title, shown at the top of the menu - */ - public $title; - /** - * @var int initial timeout after which $timeoutLabel would be executed - */ - public $timeoutMs; - /** - * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually - * trigger and launch the $timeoutLabel section - */ - public $totalTimeoutMs; - /** - * @var string label of section which will execute if the timeout expires - */ - public $timeoutLabel; - /** - * @var bool hide menu and just show background after triggering an entry - */ - public $menuClear = false; /** - * @var bool boot the associated entry directly if its corresponding hotkey is presed instead of just highlighting + * Create unique label for a boot entry. It will try to figure out whether + * this is one of our default entries and if not, create a unique label + * representing the menu entry contents. + * Also it patches the entry if it's referencing the local bwlp install + * because side effects. + * + * @param PxeSection $section + * @return string */ - public $immediateHotkeys = false; - /** - * @var PxeSection[] list of sections the menu contains - */ - public $sections = []; -} + private static function cleanLabelFixLocal($section) + { + $myip = Property::getServerIp(); + // Detect our "old" entry types + if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) + && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { + // Kernel and initrd match, examine KCL + if ($section->append === 'slxbase=boot/default vga=current quiet splash') { + // Normal + return 'bwlp-default'; + } elseif ($section->append === 'slxbase=boot/default') { + // Debug output + return 'bwlp-default-dbg'; + } else { + // Transform to relative URL, leave KCL, fall through to generic label gen + $section->kernel = '/boot/default/kernel'; + $section->initrd = ['/boot/default/initramfs-stage31']; + } + } + // Generic -- "smart" hash of kernel, initrd and command line + $str = $section->kernel . ' ' . implode(',', $section->initrd); + $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); + sort($array); + $str .= ' ' . implode(' ', $array); + + return 'i-' . substr(md5($str), 0, 12); + } -/** - * Class representing a parsed pxelinux menu entry. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeSection -{ - /** - * @var string label used internally in PXEMENU definition to address this entry - */ - public $label; - /** - * @var string MENU LABEL of PXEMENU - title of entry displayed to the user - */ - public $title; - /** - * @var int Number of spaces to prefix the title with - */ - public $indent; - /** - * @var string help text to display when the entry is highlighted - */ - public $helpText; - /** - * @var string Kernel to load - */ - public $kernel; - /** - * @var string initrd to load for the kernel - */ - public $initrd; - /** - * @var string command line options to pass to the kernel - */ - public $append; - /** - * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. - */ - public $ipAppend; - /** - * @var string Password protecting the entry. This is most likely in crypted form. - */ - public $passwd; - /** - * @var bool whether this section is marked as default (booted after timeout) - */ - public $isDefault = false; - /** - * @var bool Menu entry is not visible (can only be triggered by timeout) - */ - public $isHidden = false; /** - * @var bool Disable this entry, making it unselectable + * @param PxeSection $section + * @return BootEntry|null The according boot entry, null if it's unparsable */ - public $isDisabled = false; + private static function pxe2BootEntry($section) + { + if (preg_match('/(pxechain.com|pxechn.c32)$/i', $section->kernel)) { + // Chaining -- create script + $args = preg_split('/\s+/', $section->append); + $script = ''; + $file = false; + for ($i = 0; $i < count($args); ++$i) { + $arg = $args[$i]; + if ($arg === '-c') { // PXELINUX config file option + ++$i; + $script .= "set 209:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-p') { // PXELINUX prefix path option + ++$i; + $script .= "set 210:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-t') { // PXELINUX timeout option + ++$i; + $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-o') { // Overriding various DHCP options + ++$i; + if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { + // TODO: 'q' (8byte) unsupported for now + $opt = intval($out[1], 0); + if ($opt > 0 && $opt < 255) { + static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; + $type = $optType[$out[2]]; + $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; + } + } + } elseif ($arg{0} === '-') { + continue; + } elseif ($file === false) { + $file = self::parseFile($arg); + } + } + if ($file !== false) { + $url = parse_url($file); + if (isset($url['host'])) { + $script .= "set next-server {$url['host']} || goto %fail%\n"; + } + if (isset($url['path'])) { + $script .= "set filename {$url['path']} || goto %fail%\n"; + } + $script .= "chain -ar {$file} || goto %fail%\n"; + return new CustomBootEntry(['script' => $script]); + } + return null; + } + // "Normal" entry that should be convertible into a StandardBootEntry + $section->kernel = self::parseFile($section->kernel); + foreach ($section->initrd as &$initrd) { + $initrd = self::parseFile($initrd); + } + return BootEntry::newStandardBootEntry($section); + } + /** - * @var int Value of the LOCALBOOT field + * Parse PXELINUX file notion. Basically, turn + * server::file into tftp://server/file. + * + * @param string $file + * @return string */ - public $localBoot; + private static function parseFile($file) + { + if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { + return 'tftp://' . $out[1] . '/' . $out[2]; + } + return $file; + } - public function __construct($label) { $this->label = $label; } -} + public static function sanitizeIpxeString($string) + { + return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); + } + + public static function makeMd5Pass($plainpass, $salt) + { + if (empty($plainpass)) + return ''; + return md5(md5($plainpass) . '-' . $salt); + } +} diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php new file mode 100644 index 00000000..ed9f0986 --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php @@ -0,0 +1,142 @@ + $menu]); + if (!is_array($menu)) { + $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; + } + } + $this->menuid = (int)$menu['menuid']; + $this->timeoutMs = (int)$menu['timeoutms']; + $this->title = $menu['title']; + $this->defaultEntryId = $menu['defaultentryid']; + $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, + b.data AS bootentry + FROM serversetup_menuentry e + LEFT JOIN serversetup_bootentry b USING (entryid) + WHERE e.menuid = :menuid + ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $this->items[] = new MenuEntry($row); + } + } + + public function getMenuDefinition($targetVar) + { + $str = "menu {$this->title}\n"; + foreach ($this->items as $item) { + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId); + } + if ($this->defaultEntryId === null) { + $defaultLabel = "mx_{$this->menuid}_poweroff"; + } else { + $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; + } + $str .= "choose"; + if ($this->timeoutMs > 0) { + $str .= " --timeout {$this->timeoutMs}"; + } + $str .= " $targetVar || goto $defaultLabel || goto fail\n"; + if ($this->defaultEntryId === null) { + $str .= "goto skip_{$defaultLabel}\n" + . ":{$defaultLabel}\n" + . "poweroff || goto fail\n" + . ":skip_{$defaultLabel}\n"; + } + return $str; + } + + public function getItemsCode() + { + $str = ''; + foreach ($this->items as $item) { + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail'); + $str .= "goto slx_menu\n"; + } + return $str; + } + + /* + * + */ + + public static function forLocation($locationId) + { + $chain = null; + if (Module::isAvailable('location')) { + $chain = Location::getLocationRootChain($locationId); + } + if (!empty($chain)) { + $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, m.defaultentryid, ml.locationid + FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE ml.locationid IN (:chain)", ['chain' => $chain]); + if ($res->rowCount() > 0) { + // Make the location id key, preserving order (closest location is first) + $chain = array_flip($chain); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + // Overwrite the value (numeric ascending values, useless) with menu array of according location + $chain[(int)$row['locationid']] = $row; + } + // Use first one that was found + foreach ($chain as $menu) { + if (is_array($menu)) { + return new IPxeMenu($menu); + } + } + // Should never end up here, but we'd just fall through and use the default + } + } + // We're here, no specific menu, use default + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid + FROM serversetup_menu + ORDER BY isdefault DESC LIMIT 1"); + if ($menu === false) { + return new EmptyIPxeMenu; + } + return new IPxeMenu($menu); + } + + public static function forClient($ip, $uuid) + { + $locationId = 0; + if (Module::isAvailable('location')) { + $locationId = Location::getFromIpAndUuid($ip, $uuid); + } + return self::forLocation($locationId); + } + +} + +class EmptyIPxeMenu extends IPxeMenu +{ + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() + { + $this->title = 'No menu defined'; + $this->menuid = -1; + $this->items[] = new MenuEntry([ + 'title' => 'Please create a menu in Server-Setup first' + ]); + $this->items[] = new MenuEntry([ + 'title' => 'Bitte erstellen Sie zunächst ein Menü' + ]); + } + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php new file mode 100644 index 00000000..9736b7bb --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/menuentry.inc.php @@ -0,0 +1,170 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + $this->hotkey = self::getKeyCode($row['hotkey']); + if (!empty($row['bootentry'])) { + $this->bootEntry = BootEntry::fromJson($row['bootentry']); + } + $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); + } + settype($this->hidden, 'bool'); + settype($this->gap, 'bool'); + settype($this->sortval, 'int'); + settype($this->menuentryid, 'int'); + } + + public function getMenuItemScript($lblPrefix, $requestedDefaultId) + { + $str = 'item '; + if ($this->gap) { + $str .= '--gap '; + } else { + if ($this->hidden) { + if ($this->hotkey === false) + return ''; // Hidden entries without hotkey are illegal + $str .= '--hidden '; + } + if ($this->hotkey !== false) { + $str .= '--key ' . $this->hotkey . ' '; + } + if ($this->menuentryid == $requestedDefaultId) { + $str .= '--default '; + } + $str .= "{$lblPrefix}_{$this->menuentryid} "; + } + $str .= $this->title; + return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; + } + + public function getBootEntryScript($lblPrefix, $failLabel) + { + if ($this->bootEntry === null) + return ''; + $str = ":{$lblPrefix}_{$this->menuentryid}\n"; + if (!empty($this->md5pass)) { + $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" + . "set slx_salt {$this->menuentryid} || goto $failLabel\n" + . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" + . "set slx_pw_fail slx_menu || goto $failLabel\n" + . "goto slx_pass_check || goto $failLabel\n" + . ":{$lblPrefix}_ok\n"; + } + return $str . $this->bootEntry->toScript($failLabel); + } + + /* + * + */ + + private static function getKeyArray() + { + static $data = false; + if ($data === false) { + $data = [ + 'F5' => 0x107e, + 'F6' => 0x127e, + 'F7' => 0x137e, + 'F8' => 0x147e, + 'F9' => 0x157e, + 'F10' => 0x167e, + 'F11' => 0x187e, + 'F12' => 0x197e, + ]; + for ($i = 1; $i <= 26; ++$i) { + $letter = chr(0x40 + $i); + $data['SHIFT_' . $letter] = 0x40 + $i; + if ($letter !== 'C') { + $data['CTRL_' . $letter] = $i; + } + $data[$letter] = 0x60 + $i; + } + for ($i = 0; $i <= 9; ++$i) { + $data[chr(0x30 + $i)] = 0x30 + $i; + } + asort($data, SORT_NUMERIC); + } + return $data; + } + + /** + * Get all the known/supported keys, usable for menu items. + * + * @return string[] list of known key names + */ + public static function getKeyList() + { + return array_keys(self::getKeyArray()); + } + + /** + * Get the key code ipxe expects for the given named + * key. Returns false if the key name is unknown. + * + * @param string $keyName + * @return false|string Key code as hex string, or false if not found + */ + public static function getKeyCode($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return '0x' . dechex($data[$keyName]); + return false; + } + + /** + * @param string $keyName desired key name + * @return string $keyName if it's known, empty string otherwise + */ + public static function filterKeyName($keyName) + { + if (isset($data[$keyName])) + return $keyName; + return ''; + } + +} diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php new file mode 100644 index 00000000..db3dac4b --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php @@ -0,0 +1,262 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'com32' => ['string', 'kernel'], + 'pxe' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'sysappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $val = trim($out[2]); + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($val); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($val) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string|string[] initrd to load for the kernel. + * If mangle() has been called this will be an array, + * otherwise it's a comma separated list. + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + /** + * @var string hotkey to trigger item. Only valid after calling mangle() + */ + public $hotkey; + + public function __construct($label) { $this->label = $label; } + + public function mangle() + { + if (($i = strpos($this->title, '^')) !== false) { + $this->hotkey = strtoupper($this->title{$i+1}); + $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); + } + if (strpos($this->append, 'initrd=') !== false) { + $parts = preg_split('/\s+/', $this->append); + $this->append = ''; + for ($i = 0; $i < count($parts); ++$i) { + if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { + if (!empty($this->initrd)) { + $this->initrd .= ','; + } + $this->initrd .= $out[1]; + } else { + $this->append .= ' ' . $parts[$i]; + } + } + $this->append = trim($this->append); + } + if (is_string($this->initrd)) { + $this->initrd = explode(',', $this->initrd); + } elseif (!is_array($this->initrd)) { + $this->initrd = []; + } + } +} + diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php new file mode 100644 index 00000000..8814bb7c --- /dev/null +++ b/modules-available/serversetup-bwlp/install.inc.php @@ -0,0 +1,74 @@ +handleGetImage(); @@ -44,16 +50,45 @@ class Page_ServerSetup extends Page $this->updatePxeMenu(); } + if ($action === 'savebootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->saveBootEntry(); + } + + if ($action === 'savemenu') { + User::assertPermission('ipxe.menu.edit'); + $this->saveMenu(); + } + if (Request::isPost()) { Util::redirect('?do=serversetup'); } User::assertPermission('access-page'); + + if (User::hasPermission('ipxe.*')) { + Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + } + if (User::hasPermission('edit.address')) { + Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); + } + if (User::hasPermission('download')) { + Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); + } + if (Request::get('show') === false) { + $subs = Dashboard::getSubmenus(); + if (empty($subs)) { + User::assertPermission('download'); + } else { + Util::redirect($subs[0]['url']); + } + } } protected function doRender() { Render::addTemplate("heading"); + $task = Property::get('ipxe-task-id'); if ($task !== false) { $task = Taskmanager::status($task); @@ -65,32 +100,143 @@ class Page_ServerSetup extends Page Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); } - Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + switch (Request::get('show')) { + case 'editbootentry': + User::assertPermission('ipxe.bootentry.edit'); + $this->showEditBootEntry(); + break; + case 'editmenu': + User::assertPermission('ipxe.menu.view'); + $this->showEditMenu(); + break; + case 'download': + User::assertPermission('download'); + $this->showDownload(); + break; + case 'menu': + User::assertPermission('ipxe.menu.view'); + $this->showMenuList(); + break; + default: + Util::redirect('?do=serversetup'); + break; + } + } + + private function showDownload() + { + // TODO: Make nicer, support more variants (taskmanager-plugin) + Render::addTemplate('download'); + } + + private function showMenuList() + { + $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + + // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations + FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) GROUP BY menuid ORDER BY title"); + $table = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['locations'])) { + $locations = []; + $row['allowEdit'] = in_array(0, $allowedEdit); + } else { + $locations = explode(',', $row['locations']); + $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + } + $row['locationCount'] = empty($locations) ? '' : count($locations); + $table[] = $row; + } - Render::addTemplate('ipaddress', array( - 'ips' => $this->taskStatus['data']['addresses'], - 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', - 'editAllowed' => User::hasPermission("edit.address"), - 'perms' => $perms, + Render::addTemplate('menu-list', array( + 'table' => $table, )); - $data = $this->currentMenu; - if (!User::hasPermission('edit.menu')) { - unset($data['masterpasswordclear']); + } + + private function hasMenuPermission($menuid, $permission) + { + $allowedEditLocations = User::getAllowedLocations($permission); + $allowEdit = in_array(0, $allowedEditLocations); + if (!$allowEdit) { + // Get locations + $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location + WHERE menuid = :menuid', compact('menuid')); + if (!empty($locations)) { + $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; + } } - if (!isset($data['defaultentry'])) { - $data['defaultentry'] = 'net'; + return $allowEdit; + } + + private function showEditMenu() + { + $id = Request::get('id', false, 'int'); + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + FROM serversetup_menu WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('invalid-menu-id', $id); + Util::redirect('?do=serversetup&show=menu'); } - if ($data['defaultentry'] === 'net') { - $data['active-net'] = 'checked'; + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + $menu['readonly'] = 'readonly'; + $menu['disabled'] = 'disabled'; + $menu['plainpass'] = ''; } - if ($data['defaultentry'] === 'hdd') { - $data['active-hdd'] = 'checked'; + $menu['timeout'] = round($menu['timeoutms'] / 1000); + $menu['entries'] = Database::queryAll("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + serversetup_menuentry WHERE menuid = :id", compact('id')); + $keyList = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); + $entryList = Database::queryAll("SELECT entryid, title, hotkey FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entries'] as &$entry) { + $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); + $entry['keys'] = $keyList; + foreach ($entry['keys'] as &$key) { + if ($key['key'] === $entry['hotkey']) { + $key['selected'] = 'selected'; // TODO: plainpass only when permissions + } + } + $entry['entrylist'] = $entryList; + foreach ($entry['entrylist'] as &$item) { + if ($item['entryid'] == $entry['entryid']) { + $item['selected'] = 'selected'; + } + if (empty($item['title'])) { + $item['title'] = $item['entryid']; + } + } } - if ($data['defaultentry'] === 'custom') { - $data['active-custom'] = 'checked'; + // TODO: Make assigned locations editable + Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); + Render::addTemplate('menu-edit', $menu); + } + + private function showEditBootEntry() + { + $params = []; + $id = Request::get('id', false, 'string'); + if ($id === false) { + $params['exec_checked'] = 'checked'; + $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + } else { + // Query existing entry + $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1', ['id' => $id]); + if ($row === false) { + Message::addError('invalid-boot-entry', $id); + Util::redirect('?do=serversetup'); + } + $entry = BootEntry::fromJson($row['data']); + if ($entry === null) { + Message::addError('unknown-boot-entry-type', $id); + Util::redirect('?do=serversetup'); + } + $entry->addFormFields($params); + $params['title'] = $row['title']; + $params['oldentryid'] = $params['entryid'] = $row['entryid']; } - $data['perms'] = $perms; - Render::addTemplate('ipxe', $data); + Render::addTemplate('ipxe-new-boot-entry', $params); } // ----------------------------------------------------------------------------------------------- @@ -131,6 +277,76 @@ class Page_ServerSetup extends Page return true; } + private function saveMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', 'TODO'); + return; + } + // TODO: Validate new locations to be saved (and actually save them) + + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', [ + 'menuid' => $id, + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeoutms', 0, 'int') * 1000), + ]); + if (User::hasPermission('ipxe.menu.edit', 0)) { + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + } + + $keepIds = []; + $entries = Request::post('entry', false, 'array'); + foreach ($entries as $key => $entry) { + $params = [ + 'entryid' => $entry['entryid'], // TODO validate + 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), + 'title' => IPxe::sanitizeIpxeString($entry['title']), + 'hidden' => (int)$entry['hidden'], + 'sortval' => (int)$entry['sortval'], + 'plainpass' => $entry['plainpass'], + 'menuid' => $menu['menuid'], + ]; + if (is_numeric($key)) { + $keepIds[] = $key; + $params['menuentryid'] = $key; + $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); + $ret = Database::exec('UPDATE serversetup_menuentry + SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, + plainpass = :plainpass, md5pass = :md5pass + WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); + } else { + $ret = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); + if ($ret && !empty($entry['plainpass'])) { + $key = Database::lastInsertId(); + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $key), + 'key' => $id, + ]); + } + } + if ($ret === false) { + Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); + } + } + Message::addSuccess('menu-saved'); + } + private function updateLocalAddress() { $newAddress = Request::post('ip', 'none'); @@ -184,4 +400,50 @@ class Page_ServerSetup extends Page exit; } + private function saveBootEntry() + { + $oldEntryId = Request::post('entryid', false, 'string'); + $newId = Request::post('newid', false, 'string'); + if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { + Message::addError('main.parameter-empty', 'newid'); + return; + } + $data = Request::post('entry', false); + if (!is_array($data)) { + Message::addError('missing-entry-data'); + return; + } + $type = Request::post('type', false, 'string'); + if ($type === 'exec') { + $entry = BootEntry::newStandardBootEntry($data); + } elseif ($type === 'script') { + $entry = BootEntry::newCustomBootEntry($data); + } else { + Message::addError('unknown-entry-type', $type); + return; + } + if ($entry === null) { + Message::addError('main.empty-field'); + return; + } + $params = [ + 'entryid' => $newId, + 'title' => Request::post('title', '', 'string'), + 'data' => json_encode($entry->toArray()), + ]; + // New or update? + if (empty($oldEntryId)) { + // New entry + Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) + VALUES (:entryid, :title, 0, :data)', $params); + Message::addSuccess('boot-entry-created', $newId); + } else { + // Edit existing entry + $params['oldid'] = $oldEntryId; + Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data + WHERE entryid = :oldid AND builtin = 0', $params); + Message::addSuccess('boot-entry-updated', $newId); + } + } + } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index 44927506..aa2aa001 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -8,7 +8,19 @@ "edit.address": { "location-aware": false }, - "edit.menu": { + "ipxe.bootentry.view": { + "location-aware": false + }, + "ipxe.bootentry.edit": { + "location-aware": false + }, + "ipxe.menu.view": { + "location-aware": false + }, + "ipxe.menu.edit": { + "location-aware": true + }, + "ipxe.localboot.edit": { "location-aware": false } } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html new file mode 100644 index 00000000..6752f7fc --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/download.html @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html index d68360f1..e2aa0bff 100644 --- a/modules-available/serversetup-bwlp/templates/heading.html +++ b/modules-available/serversetup-bwlp/templates/heading.html @@ -1 +1,3 @@ -

{{lang_moduleHeading}}

\ No newline at end of file + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html new file mode 100644 index 00000000..fd9e1d72 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html @@ -0,0 +1,108 @@ +

{{lang_newBootEntryHead}}

+ +
+
+ {{lang_bootEntryData}} +
+
+
+ + + + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+ + +
+
+ +
+ +
+
+
+
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp/templates/ipxe.html deleted file mode 100644 index f4b0b4d3..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe.html +++ /dev/null @@ -1,117 +0,0 @@ -
- - - - -
-
- {{lang_bootMenu}} -
-
-

- {{lang_bootInfo}} -

-
- -
- {{lang_bootBehavior}} -
- - -
-
- - -
-
- - -
-
- -
- {{lang_menuDisplayTime}} -
- - {{lang_seconds}} -
-
- -
- {{lang_masterPassword}} -
- -
- {{lang_masterPasswordHelp}} -
- -
- {{lang_menuCustom}} - -
-
- - -
-
- - - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html new file mode 100644 index 00000000..cf10296e --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -0,0 +1,114 @@ +

{{lang_editMenuHead}}

+ +
+
+ {{title}} + {{^title}} + {{lang_newMenu}} + {{/title}} +
+
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + {{lang_seconds}} +
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + {{#entries}} + + + + + {{#entryid}} + + {{/entryid}} + + + + {{/entries}} + +
{{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_sortOrder}}{{lang_password}}
+
+ + +
+
+ {{#entryid}} + + {{/entryid}} + {{^entryid}} + {{lang_spacer}} + {{/entryid}} + + + + + + + + +
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html new file mode 100644 index 00000000..a862cff2 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-list.html @@ -0,0 +1,36 @@ +

{{lang_listOfMenus}}

+ + + + + + + + + + + + {{#table}} + + + + + + + {{/table}} + +
{{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}
+ {{title}} + + {{locationCount}} + + {{#isdefault}} + + {{/isdefault}} + + {{#allowEdit}} + + + + {{/allowEdit}} +
\ No newline at end of file -- cgit v1.2.3-55-g7522 From 35578a5c327baa803e600700ccaee627d4724339 Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Wed, 20 Jun 2018 20:02:18 +0200 Subject: [serversetup-bwlp] Added drag and drop to the editmenu. Fixed radiobutton alignment and it's now saved/updated properly. --- modules-available/serversetup-bwlp/config.json | 3 +- modules-available/serversetup-bwlp/page.inc.php | 12 +++++- .../serversetup-bwlp/templates/menu-edit.html | 44 +++++++++++++++++----- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/modules-available/serversetup-bwlp/config.json b/modules-available/serversetup-bwlp/config.json index 8ce65a85..ff485760 100644 --- a/modules-available/serversetup-bwlp/config.json +++ b/modules-available/serversetup-bwlp/config.json @@ -1,6 +1,7 @@ { "category": "main.settings-server", "dependencies" : [ - "locations" + "locations", + "js_jqueryui" ] } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 4207bd53..f2c422f3 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -189,6 +189,7 @@ class Page_ServerSetup extends Page serversetup_menuentry WHERE menuid = :id", compact('id')); $keyList = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); $entryList = Database::queryAll("SELECT entryid, title, hotkey FROM serversetup_bootentry ORDER BY title ASC"); + $sortVals = array(); foreach ($menu['entries'] as &$entry) { $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); $entry['keys'] = $keyList; @@ -206,7 +207,12 @@ class Page_ServerSetup extends Page $item['title'] = $item['entryid']; } } + $sortVals[] = $entry['sortval']; } + $arr = $menu['entries']; + $keys = array_keys($arr); + array_multisort( $sortVals, SORT_ASC, $arr, $keys); + $menu['entries'] = $arr; // TODO: Make assigned locations editable Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); Render::addTemplate('menu-edit', $menu); @@ -298,11 +304,12 @@ class Page_ServerSetup extends Page } // TODO: Validate new locations to be saved (and actually save them) - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid WHERE menuid = :menuid', [ 'menuid' => $id, 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), 'timeoutms' => abs(Request::post('timeoutms', 0, 'int') * 1000), + 'defaultentryid' => Request::post('defaultentry', false, 'int'), ]); if (User::hasPermission('ipxe.menu.edit', 0)) { Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); @@ -310,6 +317,7 @@ class Page_ServerSetup extends Page $keepIds = []; $entries = Request::post('entry', false, 'array'); + foreach ($entries as $key => $entry) { $params = [ 'entryid' => $entry['entryid'], // TODO validate @@ -340,9 +348,11 @@ class Page_ServerSetup extends Page ]); } } + if ($ret === false) { Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); } + } Message::addSuccess('menu-saved'); } diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index cf10296e..aa748c3a 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -12,6 +12,7 @@ +
@@ -46,24 +47,30 @@ + - - + {{#entries}} - - + + + + + {{/entryid}} - + - + {{#entries}} - + @@ -120,13 +120,13 @@ \ No newline at end of file -- cgit v1.2.3-55-g7522 From 1229906bc79254bc22c9e08e04a025d8bb1d89bf Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 4 Jul 2018 13:02:31 +0200 Subject: [serversetup-bwlp] Delete menu entries missing from POST data --- modules-available/serversetup-bwlp/page.inc.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 00b658c4..dc53070b 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -343,20 +343,24 @@ class Page_ServerSetup extends Page $ret = Database::exec("INSERT INTO serversetup_menuentry (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); - if ($ret && !empty($entry['plainpass'])) { + if ($ret) { $key = Database::lastInsertId(); - Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ - 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $key), - 'key' => $id, - ]); + $keepIds[] = (int)$key; + if (!empty($entry['plainpass'])) { + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $key), + 'id' => $key, + ]); + } } } if ($ret === false) { Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); } - } + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', + ['menuid' => $menu['menuid'], 'keep' => $keepIds]); Message::addSuccess('menu-saved'); } -- cgit v1.2.3-55-g7522 From a56e3487dd09740996e3648173a550d368bdb10d Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 16 Jul 2018 15:17:35 +0200 Subject: [roomplanner] Fix search feature: load user in ajax call --- modules-available/roomplanner/page.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules-available/roomplanner/page.inc.php b/modules-available/roomplanner/page.inc.php index 023cef73..8b75499b 100644 --- a/modules-available/roomplanner/page.inc.php +++ b/modules-available/roomplanner/page.inc.php @@ -108,6 +108,7 @@ class Page_Roomplanner extends Page if ($this->action === 'getmachines') { + User::load(); $locations = User::getAllowedLocations('edit'); if (empty($locations)) { die('{"machines":[]}'); -- cgit v1.2.3-55-g7522 From 43c02c0eb26a5253663db244346ec1f2a5b62eb2 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 24 Jul 2018 12:41:51 +0200 Subject: [ipxe] added add-menu-button, added delete-menu-buttons, fixed bug that timeoutms didn't save, fixed bug that last edited menu gets the isDefault value, some table styling + language tags --- .../serversetup-bwlp/lang/de/template-tags.json | 6 ++ .../serversetup-bwlp/lang/en/template-tags.json | 6 ++ modules-available/serversetup-bwlp/page.inc.php | 75 ++++++++++++++++++---- .../serversetup-bwlp/permissions/permissions.json | 6 ++ .../serversetup-bwlp/templates/menu-list.html | 54 ++++++++++++++-- 5 files changed, 131 insertions(+), 16 deletions(-) diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index 8d612ab0..14f1c134 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -1,5 +1,6 @@ { "lang_active": "Aktiv", + "lang_addMenu": "Menü hinzufügen", "lang_bootAddress": "Boot-Adresse des Servers", "lang_bootBehavior": "Standard-Bootverhalten", "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", @@ -8,11 +9,15 @@ "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_customEntry": "Eigener Eintrag", + "lang_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Menü löschen wollen?", "lang_downloadImage": "USB-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", "lang_example": "Beispiel", "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", + "lang_isDefault": "Standard", + "lang_listOfMenus": "Menüliste", "lang_localHDD": "Lokale HDD", + "lang_locationCount": "Anzahl Orte", "lang_masterPassword": "Master-Passwort", "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", @@ -21,6 +26,7 @@ "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_menuTitle": "Menü", "lang_moduleHeading": "iPXE \/ Boot Menu", "lang_pxeBuilt": "PXE-Binary gebaut", "lang_seconds": "Sekunden", diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json index 9bb55f93..d70159e2 100644 --- a/modules-available/serversetup-bwlp/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json @@ -1,5 +1,6 @@ { "lang_active": "Active", + "lang_addMenu": "Add Menu", "lang_bootAddress": "Boot Address of the Server", "lang_bootBehavior": "Default Boot Behavior", "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", @@ -8,11 +9,15 @@ "lang_bootMenuCreate": "Create Boot Menu", "lang_chooseIP": "Please select the IP address that the client server will use to boot.", "lang_customEntry": "Custom entry", + "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", "lang_downloadImage": "Download USB Image", "lang_downloadRufus": "Download Rufus", "lang_example": "Example", "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.", + "lang_isDefault": "Default", + "lang_listOfMenus": "Menulist", "lang_localHDD": "Local HDD", + "lang_locationCount": "Number of Locations", "lang_masterPassword": "Master Password", "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", "lang_menuCustom": "Custom Extra Menu", @@ -21,6 +26,7 @@ "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", "lang_menuDisplayTime": "Menu Display Time", "lang_menuGeneration": "Generating boot menu...", + "lang_menuTitle": "Menu", "lang_moduleHeading": "iPXE \/ Boot Menu", "lang_pxeBuilt": "Built PXE binary", "lang_seconds": "Seconds", diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index dc53070b..6df4c49c 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -60,6 +60,11 @@ class Page_ServerSetup extends Page $this->saveMenu(); } + if ($action === 'deleteMenu') { + User::assertPermission('ipxe.menu.delete'); + $this->deleteMenu(); + } + if (Request::isPost()) { Util::redirect('?do=serversetup'); } @@ -132,26 +137,35 @@ class Page_ServerSetup extends Page private function showMenuList() { $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + $allowedDelete = User::getAllowedLocations('ipxe.menu.delete'); // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) GROUP BY menuid ORDER BY title"); - $table = []; + $menuTable = []; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (empty($row['locations'])) { $locations = []; $row['allowEdit'] = in_array(0, $allowedEdit); + $row['allowDelete'] = in_array(0, $allowedDelete); } else { $locations = explode(',', $row['locations']); $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + $row['allowDelete'] = empty(array_diff($locations, $allowedDelete)); } $row['locationCount'] = empty($locations) ? '' : count($locations); - $table[] = $row; + $menuTable[] = $row; + } + + $allowAddMenu = 'disabled'; + if (User::hasPermission('ipxe.menu.add')) { + $allowAddMenu = ''; } Render::addTemplate('menu-list', array( - 'table' => $table, + 'menuTable' => $menuTable, + 'allowAddMenu' => $allowAddMenu )); } @@ -173,8 +187,19 @@ class Page_ServerSetup extends Page private function showEditMenu() { $id = Request::get('id', false, 'int'); - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + // if = edit, else = add new + if ($id != 0) { + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault FROM serversetup_menu WHERE menuid = :id", compact('id')); + } else { + $menu = []; + $menu['menuid'] = 0; + $menu['timeoutms'] = 0; + $menu['title'] = ''; + $menu['defaultentryid'] = null; + $menu['isdefault'] = false; + } + if ($menu === false) { Message::addError('invalid-menu-id', $id); Util::redirect('?do=serversetup&show=menu'); @@ -263,6 +288,22 @@ class Page_ServerSetup extends Page return true; } + private function deleteMenu() + { + $id = Request::post('deleteid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.delete')) { + Message::addError('locations.no-permission-location', 'TODO'); + return; + } + Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); + Message::addSuccess('menu-deleted'); + + } + private function saveMenu() { $id = Request::post('menuid', false, 'int'); @@ -284,15 +325,27 @@ class Page_ServerSetup extends Page } // TODO: Validate new locations to be saved (and actually save them) - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid + if ($id == 0) { + Database::exec("INSERT IGNORE INTO serversetup_menu (title, timeoutms, defaultentryid) VALUES (:title, :timeoutms, :defaultentryid)", [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + 'defaultentryid' => Request::post('defaultentry', null, 'int') + ]); + } else { + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid WHERE menuid = :menuid', [ - 'menuid' => $id, - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeoutms', 0, 'int') * 1000), - 'defaultentryid' => Request::post('defaultentry', null, 'int'), - ]); + 'menuid' => $id, + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + 'defaultentryid' => Request::post('defaultentry', null, 'int'), + ]); + } + + $defmenu = Request::post('defmenu', false, 'boolean'); if (User::hasPermission('ipxe.menu.edit', 0)) { - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + if ($defmenu) { + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + } } $keepIds = []; diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index aa2aa001..e05b9f6c 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -20,6 +20,12 @@ "ipxe.menu.edit": { "location-aware": true }, + "ipxe.menu.add": { + "location-aware": false + }, + "ipxe.menu.delete": { + "location-aware": true + }, "ipxe.localboot.edit": { "location-aware": false } diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html index a862cff2..1f190bb7 100644 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ b/modules-available/serversetup-bwlp/templates/menu-list.html @@ -7,10 +7,11 @@ + - {{#table}} + {{#menuTable}} - - + - {{/table}} + {{/menuTable}} -
{{lang_entryId}} {{lang_title}} {{lang_hotkey}}{{lang_sortOrder}} {{lang_password}}
-
+
+ + +
{{#entryid}} - - @@ -111,4 +116,25 @@ - \ No newline at end of file + + + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 303e32fe9fbbe0897ea53dd8dd41c60abf1f0434 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 28 Jun 2018 12:35:30 +0200 Subject: [serversetup-bwlp] Simplify sorting of menu entries --- modules-available/serversetup-bwlp/page.inc.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index f2c422f3..061abfd5 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -186,10 +186,9 @@ class Page_ServerSetup extends Page } $menu['timeout'] = round($menu['timeoutms'] / 1000); $menu['entries'] = Database::queryAll("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM - serversetup_menuentry WHERE menuid = :id", compact('id')); + serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); $keyList = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); $entryList = Database::queryAll("SELECT entryid, title, hotkey FROM serversetup_bootentry ORDER BY title ASC"); - $sortVals = array(); foreach ($menu['entries'] as &$entry) { $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); $entry['keys'] = $keyList; @@ -207,12 +206,7 @@ class Page_ServerSetup extends Page $item['title'] = $item['entryid']; } } - $sortVals[] = $entry['sortval']; } - $arr = $menu['entries']; - $keys = array_keys($arr); - array_multisort( $sortVals, SORT_ASC, $arr, $keys); - $menu['entries'] = $arr; // TODO: Make assigned locations editable Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); Render::addTemplate('menu-edit', $menu); -- cgit v1.2.3-55-g7522 From e778ad169141aadb3372f5f1778e735c0aa99676 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 28 Jun 2018 12:37:10 +0200 Subject: [serversetup-bwlp] Slightly nicer colors; html cleanup --- modules-available/serversetup-bwlp/templates/menu-edit.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index aa748c3a..4fabd11c 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -55,10 +55,10 @@ {{lang_password}}
{{lang_locationCount}} {{lang_isDefault}} {{lang_edit}}{{lang_delete}}
{{title}} @@ -18,19 +19,62 @@ {{locationCount}} + {{#isdefault}} {{/isdefault}} + {{#allowEdit}} {{/allowEdit}} + {{#allowDelete}} + + {{/allowDelete}} +
\ No newline at end of file + + + + + +
+ + +
+ + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 60b1fc7329725612fb5e6289295c5efd0f31f36d Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 24 Jul 2018 15:36:48 +0200 Subject: [ipxe] Add Bootentry list Bootentry list with add/edit/delete functionality --- .../serversetup-bwlp/lang/de/template-tags.json | 3 + .../serversetup-bwlp/lang/en/template-tags.json | 3 + modules-available/serversetup-bwlp/page.inc.php | 47 +++++++++++++- .../serversetup-bwlp/permissions/permissions.json | 6 ++ .../serversetup-bwlp/templates/bootentry-list.html | 71 ++++++++++++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 modules-available/serversetup-bwlp/templates/bootentry-list.html diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index 14f1c134..bda890c1 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -1,14 +1,17 @@ { "lang_active": "Aktiv", + "lang_addBootentry": "Booteintrag hinzufügen", "lang_addMenu": "Menü hinzufügen", "lang_bootAddress": "Boot-Adresse des Servers", "lang_bootBehavior": "Standard-Bootverhalten", + "lang_bootentryTitle": "Booteintrag", "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", "lang_bootMenu": "Bootmen\u00fc", "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_customEntry": "Eigener Eintrag", + "lang_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Booteintrag löschen wollen?", "lang_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Menü löschen wollen?", "lang_downloadImage": "USB-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json index d70159e2..121ed3e7 100644 --- a/modules-available/serversetup-bwlp/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json @@ -1,14 +1,17 @@ { "lang_active": "Active", + "lang_addBootentry": "Add Bootentry", "lang_addMenu": "Add Menu", "lang_bootAddress": "Boot Address of the Server", "lang_bootBehavior": "Default Boot Behavior", + "lang_bootentryTitle": "Bootentry", "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", "lang_bootMenu": "Boot Menu", "lang_bootMenuCreate": "Create Boot Menu", "lang_chooseIP": "Please select the IP address that the client server will use to boot.", "lang_customEntry": "Custom entry", + "lang_bootentryDeleteConfirm": "Are you sure you want to delete this bootentry?", "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", "lang_downloadImage": "Download USB Image", "lang_downloadRufus": "Download Rufus", diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 6df4c49c..3ef4371f 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -55,6 +55,11 @@ class Page_ServerSetup extends Page $this->saveBootEntry(); } + if ($action === 'deleteBootentry') { + User::assertPermission('ipxe.bootentry.delete'); + $this->deleteBootEntry(); + } + if ($action === 'savemenu') { User::assertPermission('ipxe.menu.edit'); $this->saveMenu(); @@ -73,6 +78,7 @@ class Page_ServerSetup extends Page if (User::hasPermission('ipxe.*')) { Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); } if (User::hasPermission('edit.address')) { Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); @@ -122,6 +128,10 @@ class Page_ServerSetup extends Page User::assertPermission('ipxe.menu.view'); $this->showMenuList(); break; + case 'bootentry': + User::assertPermission('ipxe.bootentry.view'); + $this->showBootentryList(); + break; default: Util::redirect('?do=serversetup'); break; @@ -134,6 +144,29 @@ class Page_ServerSetup extends Page Render::addTemplate('download'); } + private function showBootentryList() + { + $allowEdit = User::hasPermission('ipxe.bootentry.edit'); + $allowDelete = User::hasPermission('ipxe.bootentry.delete'); + $allowAdd = 'disabled'; + if (User::hasPermission('ipxe.bootentry.add')) { + $allowAdd = ''; + } + + $res = Database::simpleQuery("SELECT entryid, hotkey, title FROM serversetup_bootentry"); + $bootentryTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $bootentryTable[] = $row; + } + + Render::addTemplate('bootentry-list', array( + 'bootentryTable' => $bootentryTable, + 'allowAdd' => $allowAdd, + 'allowEdit' => $allowEdit, + 'allowDelete' => $allowDelete + )); + } + private function showMenuList() { $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); @@ -288,11 +321,22 @@ class Page_ServerSetup extends Page return true; } + private function deleteBootEntry() { + $id = Request::post('deleteid', false, 'string'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); + // TODO: Redirect to &show=bootentry + Message::addSuccess('bootentry-deleted'); + } + private function deleteMenu() { $id = Request::post('deleteid', false, 'int'); if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); + Message::addError('main.parameter-missing', 'deleteid'); return; } if (!$this->hasMenuPermission($id, 'ipxe.menu.delete')) { @@ -512,6 +556,7 @@ class Page_ServerSetup extends Page $params['oldid'] = $oldEntryId; Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data WHERE entryid = :oldid AND builtin = 0', $params); + // TODO: Redirect to &show=bootentry Message::addSuccess('boot-entry-updated', $newId); } } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index e05b9f6c..5b97e5c2 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -14,6 +14,12 @@ "ipxe.bootentry.edit": { "location-aware": false }, + "ipxe.bootentry.add": { + "location-aware": false + }, + "ipxe.bootentry.delete": { + "location-aware": false + }, "ipxe.menu.view": { "location-aware": false }, diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html new file mode 100644 index 00000000..f9e881b2 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/bootentry-list.html @@ -0,0 +1,71 @@ + + + + + + + + + + + {{#bootentryTable}} + + + + + + + {{/bootentryTable}} + +
{{lang_bootentryTitle}}Hotkey{{lang_edit}}{{lang_delete}}
+ {{title}} + + {{hotkey}} + + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} +
+ + + +
+ + +
+ + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 870ca20a54a901c56daf691243bd2bb6c5269a26 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 25 Jul 2018 16:10:33 +0200 Subject: [serversetup-bwlp] Fix some menu edit issues - Newly created menu entries couldn't be made default - Saving a new menu was broken --- modules-available/serversetup-bwlp/page.inc.php | 75 ++++++++++++---------- .../serversetup-bwlp/templates/menu-edit.html | 2 +- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 3ef4371f..25a31f06 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -221,7 +221,7 @@ class Page_ServerSetup extends Page { $id = Request::get('id', false, 'int'); // if = edit, else = add new - if ($id != 0) { + if ($id !== 0) { $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault FROM serversetup_menu WHERE menuid = :id", compact('id')); } else { @@ -355,45 +355,41 @@ class Page_ServerSetup extends Page Message::addError('main.parameter-missing', 'menuid'); return; } - $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations + // TODO: Validate new locations to be saved (and actually save them) + $insertParams = [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + ]; + if ($id === 0) { + Database::exec("INSERT INTO serversetup_menu (title, timeoutms) VALUES (:title, :timeoutms)", $insertParams); + $menu['menuid'] = $id = Database::lastInsertId(); + } else { + $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) WHERE menuid = :id", compact('id')); - if ($menu === false) { - Message::addError('no-such-menu', $id); - return; - } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', 'TODO'); - return; + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', 'TODO'); + return; + } + $insertParams['menuid'] = $id; + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', $insertParams); } - // TODO: Validate new locations to be saved (and actually save them) - if ($id == 0) { - Database::exec("INSERT IGNORE INTO serversetup_menu (title, timeoutms, defaultentryid) VALUES (:title, :timeoutms, :defaultentryid)", [ - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), - 'defaultentryid' => Request::post('defaultentry', null, 'int') - ]); - } else { - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms, defaultentryid = :defaultentryid - WHERE menuid = :menuid', [ - 'menuid' => $id, - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), - 'defaultentryid' => Request::post('defaultentry', null, 'int'), - ]); - } - - $defmenu = Request::post('defmenu', false, 'boolean'); - if (User::hasPermission('ipxe.menu.edit', 0)) { - if ($defmenu) { - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); - } + if (User::hasPermission('ipxe.menu.edit', 0) + && Request::post('defmenu', false, 'boolean')) { + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); } $keepIds = []; $entries = Request::post('entry', false, 'array'); + $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); + $defaultEntryId = null; foreach ($entries as $key => $entry) { if (!isset($entry['sortval'])) { @@ -429,6 +425,9 @@ class Page_ServerSetup extends Page ]; } if (is_numeric($key)) { + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $key; + } $keepIds[] = $key; $params['menuentryid'] = $key; $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); @@ -441,12 +440,15 @@ class Page_ServerSetup extends Page (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); if ($ret) { - $key = Database::lastInsertId(); - $keepIds[] = (int)$key; + $newKey = Database::lastInsertId(); + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $newKey; + } + $keepIds[] = (int)$newKey; if (!empty($entry['plainpass'])) { Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ - 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $key), - 'id' => $key, + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), + 'id' => $newKey, ]); } } @@ -458,6 +460,9 @@ class Page_ServerSetup extends Page } Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', ['menuid' => $menu['menuid'], 'keep' => $keepIds]); + // Set default entry + Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', + ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); Message::addSuccess('menu-saved'); } diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index 603c7425..c0a353b4 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -41,7 +41,7 @@
- +
-- cgit v1.2.3-55-g7522 From f37c95a560f0d2bac96e4b0e650d12691181b876 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 7 Aug 2018 16:48:13 +0200 Subject: [syslog] Add user export feature This will export all rows from tables that log user related content, data, events, helping administrators to conform to DSGVO information requests. Closes #3401 --- modules-available/syslog/api.inc.php | 39 ++++++++++++++++++++++ .../syslog/lang/de/template-tags.json | 4 +++ .../syslog/lang/en/template-tags.json | 4 +++ modules-available/syslog/page.inc.php | 2 +- .../syslog/permissions/permissions.json | 3 ++ modules-available/syslog/templates/heading.html | 39 ++++++++++++++++++++-- 6 files changed, 87 insertions(+), 4 deletions(-) diff --git a/modules-available/syslog/api.inc.php b/modules-available/syslog/api.inc.php index 18c42c31..945f9d09 100644 --- a/modules-available/syslog/api.inc.php +++ b/modules-available/syslog/api.inc.php @@ -1,5 +1,44 @@ Database::simpleQuery("SELECT dateline, logtypeid AS typeid, clientip, description FROM clientlog + WHERE description REGEXP :exp + ORDER BY dateline ASC", ['exp' => $exp])]; + if (Module::get('statistics') !== false) { + $srcs[] = ['res' => Database::simpleQuery("SELECT dateline, typeid, clientip, data AS description FROM statistic + WHERE username = :user + ORDER BY dateline ASC", ['user' => $user])]; + } + echo "# Begin log\n"; + for (;;) { + unset($best); + foreach ($srcs as &$src) { + if (!isset($src['row'])) { + $src['row'] = $src['res']->fetch(PDO::FETCH_ASSOC); + } + if ($src['row'] !== false && (!isset($best) || $src['row']['dateline'] < $best['dateline'])) { + $best =& $src['row']; + } + } + if (!isset($best)) + break; + echo date('Y-m-d H:i:s', $best['dateline']), "\t", $best['typeid'], "\t", $best['clientip'], "\t", $best['description'], "\n"; + $best = null; // so we repopulate on next iteration + } + die("# End log\n"); +} + if (empty($_POST['type'])) die('Missing options.'); $type = mb_strtolower($_POST['type']); diff --git a/modules-available/syslog/lang/de/template-tags.json b/modules-available/syslog/lang/de/template-tags.json index b5c6f8c7..c00d619a 100644 --- a/modules-available/syslog/lang/de/template-tags.json +++ b/modules-available/syslog/lang/de/template-tags.json @@ -5,8 +5,12 @@ "lang_clientLog": "Client Log", "lang_details": "Details", "lang_event": "Ereignis", + "lang_export": "Exportieren", + "lang_exportUserDesc": "Mit dieser Funktion k\u00f6nnen Sie alle in der Datenbank vorhandenen Datens\u00e4tze zu einem bestimmten Benutzer exportieren. Bitte geben Sie den Benutzernamen genau so ein, wie ihn der Nutzer beim Login am Client angeben muss.", "lang_filter": "Filter", "lang_not": "not", "lang_settings": "Einstellungen", + "lang_userExport": "Nutzer-Export", + "lang_userLogin": "Benutzer-Login", "lang_when": "Wann" } \ No newline at end of file diff --git a/modules-available/syslog/lang/en/template-tags.json b/modules-available/syslog/lang/en/template-tags.json index 1aae1fe9..24e9aaa1 100644 --- a/modules-available/syslog/lang/en/template-tags.json +++ b/modules-available/syslog/lang/en/template-tags.json @@ -5,8 +5,12 @@ "lang_clientLog": "Client Log", "lang_details": "Details", "lang_event": "Event", + "lang_export": "Export", + "lang_exportUserDesc": "This exports all data from the database relating to the given user login. Please specify the user name exactly the way they would provide it when logging in on a client.", "lang_filter": "Filter", "lang_not": "not", "lang_settings": "Settings", + "lang_userExport": "User export", + "lang_userLogin": "User login", "lang_when": "When" } \ No newline at end of file diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php index 00c55a3f..6c1a0a16 100644 --- a/modules-available/syslog/page.inc.php +++ b/modules-available/syslog/page.inc.php @@ -31,7 +31,7 @@ class Page_SysLog extends Page protected function doRender() { $data = ['anondays' => Property::get(self::PROP_ANON_DAYS, 0)]; - Permission::addGlobalTags($data['perms'], NULL, ['configure-anonymization']); + Permission::addGlobalTags($data['perms'], NULL, ['configure-anonymization', 'export-user-data']); Render::addTemplate("heading", $data); if (!User::hasPermission("view")) { diff --git a/modules-available/syslog/permissions/permissions.json b/modules-available/syslog/permissions/permissions.json index cabf82f9..1f2373d4 100644 --- a/modules-available/syslog/permissions/permissions.json +++ b/modules-available/syslog/permissions/permissions.json @@ -4,5 +4,8 @@ }, "configure-anonymization": { "location-aware": false + }, + "export-user-data": { + "location-aware": false } } \ No newline at end of file diff --git a/modules-available/syslog/templates/heading.html b/modules-available/syslog/templates/heading.html index 2ab1a848..8dd3d440 100644 --- a/modules-available/syslog/templates/heading.html +++ b/modules-available/syslog/templates/heading.html @@ -1,7 +1,13 @@ \ No newline at end of file -- cgit v1.2.3-55-g7522 From cd092274b88599449902f480f35291768be6e99e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 7 Aug 2018 16:54:01 +0200 Subject: [inc/User] Fix access to Page if class is not loaded --- inc/user.inc.php | 11 ++++- lang/pt/flag.png | Bin 1115 -> 0 bytes lang/pt/name.txt | 1 - modules-available/exams/lang/de/template-tags.json | 2 +- modules-available/exams/lang/en/template-tags.json | 2 +- .../coursebackend/coursebackend_hisinone.inc.php | 48 +++++++++++++-------- .../locationinfo/lang/de/template-tags.json | 2 +- .../locationinfo/lang/en/template-tags.json | 2 +- 8 files changed, 43 insertions(+), 25 deletions(-) delete mode 100644 lang/pt/flag.png delete mode 100644 lang/pt/name.txt diff --git a/inc/user.inc.php b/inc/user.inc.php index 2571c61c..20e8cd3d 100644 --- a/inc/user.inc.php +++ b/inc/user.inc.php @@ -34,8 +34,15 @@ class User if ($permission{0} === '.') { $permission = substr($permission, 1); } else { - $module = Page::getModule(); - $permission = $module ? $module->getIdentifier() . "." . $permission : $permission; + if (class_exists('Page')) { + $module = Page::getModule(); + if ($module !== false) { + $module = $module->getIdentifier(); + } + } else { + $module = strtolower(Request::any('do')); + } + $permission = $module ? $module . "." . $permission : $permission; } return PermissionUtil::userHasPermission(self::$user['userid'], $permission, $locationid); } diff --git a/lang/pt/flag.png b/lang/pt/flag.png deleted file mode 100644 index 78c57dea..00000000 Binary files a/lang/pt/flag.png and /dev/null differ diff --git a/lang/pt/name.txt b/lang/pt/name.txt deleted file mode 100644 index 811b10b4..00000000 --- a/lang/pt/name.txt +++ /dev/null @@ -1 +0,0 @@ -Português \ No newline at end of file diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json index 8bf37143..1dd51374 100644 --- a/modules-available/exams/lang/de/template-tags.json +++ b/modules-available/exams/lang/de/template-tags.json @@ -29,7 +29,7 @@ "lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus", "lang_id": "ID", "lang_lectureName": "Veranstaltungsname", - "lang_lectureOutOfRange": "Achtung: Start- bzw. Endzeitpunkt der Veranstaltung liegen au\u00dferhalb des oben angegebenen Zeitraums", + "lang_lectureOutOfRange": "Achtung: Der oben angegebene Zeitraum ist k\u00fcrzer als die Dauer der Veranstaltung", "lang_location": "Raum\/Ort", "lang_locationInfo": "W\u00e4hlen Sie hier die R\u00e4ume und Orte aus, die w\u00e4hrend des unten ausgew\u00e4hlten Zeitraums in den Pr\u00fcfungsmodus versetzt werden. Wenn sie hier keine Auswahl treffen, werden alle R\u00e4ume in den Pr\u00fcfungsmodus versetzt.", "lang_locations": "R\u00e4ume\/Orte", diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json index af87bb01..23266154 100644 --- a/modules-available/exams/lang/en/template-tags.json +++ b/modules-available/exams/lang/en/template-tags.json @@ -29,7 +29,7 @@ "lang_headingMain": "bwLehrpool Exam Mode", "lang_id": "ID", "lang_lectureName": "Lecture name", - "lang_lectureOutOfRange": "Hint: Start or end date of given lecture lies outside of exam period given above", + "lang_lectureOutOfRange": "Hint: The exam period given above is shorter than the duration of the given lecture", "lang_location": "Room\/Location", "lang_locationInfo": "Select the rooms and locations you want to enable the exam mode in. Selecting nothing at all means that all clients will boot into exam mode during the given time period.", "lang_locations": "Rooms\/Locations", diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php index 558f5cd0..3b26e625 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php @@ -274,8 +274,7 @@ class CourseBackend_HisInOne extends CourseBackend foreach ($eventDetails as $event) { foreach (array('/hisdefaulttext', '/hisshorttext', - '/hisshortcomment', - '/hisplanelements/hisplanelement/hisdefaulttext') as $path) { + '/hisshortcomment') as $path) { $name = $this->getArrayPath($event, $path); if (!empty($name) && !empty($name[0])) break; @@ -284,25 +283,38 @@ class CourseBackend_HisInOne extends CourseBackend if ($name === false) { $name = ['???']; } - $unitPlannedDates = $this->getArrayPath($event, - '/hisplanelements/hisplanelement/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); - if ($unitPlannedDates === false) { - $this->error = 'Cannot find ./hisplanelements/hisplanelement/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'; - error_log('Cannot find ./hisplanelements/hisplanelement/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); + $planElements = $this->getArrayPath($event, '/hisplanelements/hisplanelement'); + if ($planElements === false) { + $this->error = 'Cannot find ./hisplanelements/hisplanelement'; + error_log('Cannot find ./hisplanelements/hisplanelement'); error_log(print_r($event, true)); continue; } - foreach ($unitPlannedDates as $plannedDate) { - $eventRoomId = $this->getArrayPath($plannedDate, '/hisroomId')[0]; - $eventDate = $this->getArrayPath($plannedDate, '/hisexecutiondate')[0]; - if (in_array($eventRoomId, $requestedRoomIds) && in_array($eventDate, $currentWeek)) { - $startTime = $this->getArrayPath($plannedDate, '/hisstarttime')[0]; - $endTime = $this->getArrayPath($plannedDate, '/hisendtime')[0]; - $tTables[$eventRoomId][] = array( - 'title' => $name[0], - 'start' => $eventDate . "T" . $startTime, - 'end' => $eventDate . "T" . $endTime - ); + foreach ($planElements as $planElement) { + $unitPlannedDates = $this->getArrayPath($planElement, + '/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); + if ($unitPlannedDates === false) { + $this->error = 'Cannot find ./hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'; + error_log('Cannot find ./hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); + error_log(print_r($planElement, true)); + continue; + } + $localName = $this->getArrayPath($planElement, '/hisdefaulttext'); + if ($localName === false || empty($localName[0])) { + $localName = $name; + } + foreach ($unitPlannedDates as $plannedDate) { + $eventRoomId = $this->getArrayPath($plannedDate, '/hisroomId')[0]; + $eventDate = $this->getArrayPath($plannedDate, '/hisexecutiondate')[0]; + if (in_array($eventRoomId, $requestedRoomIds) && in_array($eventDate, $currentWeek)) { + $startTime = $this->getArrayPath($plannedDate, '/hisstarttime')[0]; + $endTime = $this->getArrayPath($plannedDate, '/hisendtime')[0]; + $tTables[$eventRoomId][] = array( + 'title' => $localName[0], + 'start' => $eventDate . "T" . $startTime, + 'end' => $eventDate . "T" . $endTime + ); + } } } } diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json index bcdf7148..b51c420b 100644 --- a/modules-available/locationinfo/lang/de/template-tags.json +++ b/modules-available/locationinfo/lang/de/template-tags.json @@ -76,7 +76,7 @@ "lang_remoteSchedule": "Abruf Belegungsplan", "lang_room": "Raum", "lang_roomId": "Raum ID", - "lang_roomIdTooltip": "Die Raum ID, die der Server ben\u00f6tigt, um Kalenderdaten abzurufen", + "lang_roomIdTooltip": "Die Raum ID, die der Server ben\u00f6tigt, um Kalenderdaten abzurufen (bei Exchange die Postfachadresse)", "lang_roomupdateTooltip": "Zeit nach der die PCs aktualisiert werden (in Sekunden)", "lang_rotation": "Rotation", "lang_rotation0": "0\u00b0", diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json index 558ddff0..f041dc0a 100644 --- a/modules-available/locationinfo/lang/en/template-tags.json +++ b/modules-available/locationinfo/lang/en/template-tags.json @@ -76,7 +76,7 @@ "lang_remoteSchedule": "Time table retrieval", "lang_room": "Room", "lang_roomId": "Room ID", - "lang_roomIdTooltip": "The ID of the room the server needs, for querying the calendar data", + "lang_roomIdTooltip": "The ID of the room the server needs, for querying the calendar data (when using exchange the room mailbox)", "lang_roomupdateTooltip": "Time the PCs in the room gets updated (in seconds)", "lang_rotation": "Rotation", "lang_rotation0": "0\u00b0", -- cgit v1.2.3-55-g7522 From 4242bf4439ed4c2466e1eac0f0e920d04870e210 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 13 Sep 2018 16:10:09 +0200 Subject: [serversetup-bwlp] Pass initrd= on KCL for EFI mode --- modules-available/serversetup-bwlp/inc/bootentry.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules-available/serversetup-bwlp/inc/bootentry.inc.php b/modules-available/serversetup-bwlp/inc/bootentry.inc.php index f488959b..930f4413 100644 --- a/modules-available/serversetup-bwlp/inc/bootentry.inc.php +++ b/modules-available/serversetup-bwlp/inc/bootentry.inc.php @@ -135,8 +135,9 @@ class StandardBootEntry extends BootEntry $script .= "-r "; } $script .= "{$this->executable}"; + $rdBase = basename($this->initRd); if (!empty($this->commandLine)) { - $script .= " {$this->commandLine}"; + $script .= " initrd=$rdBase {$this->commandLine}"; } $script .= " || goto $failLabel\n"; if ($this->resetConsole) { -- cgit v1.2.3-55-g7522 From 6da78493a0bb010a0fff50ac3cc23e018b15e979 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 19 Sep 2018 11:27:22 +0200 Subject: [serversetup-bwlp] Differentiate between EFI/BIOS Different executable/initrd etc. can be given for a simple boot entry of type "exec", or it can be specified that only one of them is supported. For bootentry type "script" there can still be only one entry, since you can just check the ${platform} variable within the script. --- modules-available/serversetup-bwlp/api.inc.php | 12 +- .../serversetup-bwlp/inc/bootentry.inc.php | 113 ++++++++++++++----- .../serversetup-bwlp/inc/ipxemenu.inc.php | 8 +- .../serversetup-bwlp/inc/menuentry.inc.php | 10 +- modules-available/serversetup-bwlp/page.inc.php | 7 +- .../templates/ipxe-new-boot-entry.html | 122 ++++++++++++++------- 6 files changed, 194 insertions(+), 78 deletions(-) diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php index 36f9063c..52f30440 100644 --- a/modules-available/serversetup-bwlp/api.inc.php +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -1,5 +1,7 @@ 'exit 1', 'COMBOOT' => 'chain /tftp/chain.c32 hd0', @@ -22,12 +24,14 @@ $platform = strtoupper(Request::any('platform', 'PCBIOS', 'string')); $localboot = false; $model = false; if ($uuid !== false && Module::get('statistics') !== false) { + // If we have the machine table, we rather try to look up the system model from there, using the UUID $row = Database::queryFirst('SELECT systemmodel FROM machine WHERE machineuuid = :uuid', ['uuid' => $uuid]); if ($row !== false && !empty($row['systemmodel'])) { $model = $row['systemmodel']; } } if ($model === false) { + // Otherwise use what iPXE sent us function modfilt($str) { if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) @@ -70,6 +74,8 @@ if (isset($BOOT_METHODS[$localboot])) { $BOOT_METHODS = array_reverse($BOOT_METHODS); } +// TODO: Feature check for our own iPXE extensions, stay compatible to stock iPXE + $output = <<getMenuDefinition('target'); +$output .= $menu->getMenuDefinition('target', $platform); $output .= <<getItemsCode(); +$output .= $menu->getItemsCode($platform); + +// TODO: Work out memtest stuff. Needs to be put on server (install/update script) -- PCBIOS only? Chain EFI -> BIOS? /* :i1 diff --git a/modules-available/serversetup-bwlp/inc/bootentry.inc.php b/modules-available/serversetup-bwlp/inc/bootentry.inc.php index 930f4413..010b660c 100644 --- a/modules-available/serversetup-bwlp/inc/bootentry.inc.php +++ b/modules-available/serversetup-bwlp/inc/bootentry.inc.php @@ -14,7 +14,9 @@ abstract class BootEntry } } - public abstract function toScript($failLabel); + public abstract function supportsMode($mode); + + public abstract function toScript($failLabel, $mode); public abstract function toArray(); @@ -46,9 +48,19 @@ abstract class BootEntry public static function newStandardBootEntry($initData) { - if (empty($initData['executable'])) - return null; - return new StandardBootEntry($initData); + $ret = new StandardBootEntry($initData); + $list = []; + if ($ret->arch() !== StandardBootEntry::EFI) { + $list[] = StandardBootEntry::BIOS; + } + if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { + $list[] = StandardBootEntry::EFI; + } + foreach ($list as $mode) { + if (empty($initData['executable'][$mode])) + return null; + } + return $ret; } public static function newCustomBootEntry($initData) @@ -83,6 +95,12 @@ class StandardBootEntry extends BootEntry protected $replace; protected $autoUnload; protected $resetConsole; + protected $arch; // true == available, false == not available + + const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot + const EFI = 'EFI'; // Only valid for EFI boot + const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry + const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) public function __construct($data = false) { @@ -109,38 +127,70 @@ class StandardBootEntry extends BootEntry } else { parent::__construct($data); } + // Convert legacy DB format + foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { + if (!is_array($this->{$key})) { + $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; + } + } + if ($this->arch === null) { + $this->arch = self::AGNOSTIC; + } + } + + public function arch() + { + return $this->arch; + } + + public function supportsMode($mode) + { + if ($mode === $this->arch || $this->arch === self::AGNOSTIC) + return true; + if ($mode === self::BIOS || $mode === self::EFI) { + return $this->arch === self::BOTH; + } + error_log('Unknown iPXE platform: ' . $mode); + return false; } - public function toScript($failLabel) + public function toScript($failLabel, $mode) { + if (!$this->supportsMode($mode)) { + return "prompt Entry doesn't have an executable for mode $mode\n"; + } + if ($this->arch === self::AGNOSTIC) { + $mode = self::BIOS; + } + $script = ''; - if ($this->resetConsole) { + if ($this->resetConsole[$mode]) { $script .= "console ||\n"; } - if (!empty($this->initRd)) { + if (!empty($this->initRd[$mode])) { $script .= "imgfree ||\n"; - if (!is_array($this->initRd)) { - $script .= "initrd {$this->initRd} || goto $failLabel\n"; + if (!is_array($this->initRd[$mode])) { + $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; } else { - foreach ($this->initRd as $initrd) { + foreach ($this->initRd[$mode] as $initrd) { $script .= "initrd $initrd || goto $failLabel\n"; } } } $script .= "boot "; - if ($this->autoUnload) { + if ($this->autoUnload[$mode]) { $script .= "-a "; } - if ($this->replace) { + if ($this->replace[$mode]) { $script .= "-r "; } - $script .= "{$this->executable}"; - $rdBase = basename($this->initRd); - if (!empty($this->commandLine)) { - $script .= " initrd=$rdBase {$this->commandLine}"; + $script .= $this->executable[$mode]; + $rdBase = basename($this->initRd[$mode]); + if (!empty($this->commandLine[$mode])) { + $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; } $script .= " || goto $failLabel\n"; - if ($this->resetConsole) { + if ($this->resetConsole[$mode]) { $script .= "goto start ||\n"; } return $script; @@ -148,14 +198,19 @@ class StandardBootEntry extends BootEntry public function addFormFields(&$array) { - $array['entry'] = [ - 'executable' => $this->executable, - 'initRd' => $this->initRd, - 'commandLine' => $this->commandLine, - 'replace_checked' => $this->replace ? 'checked' : '', - 'autoUnload_checked' => $this->autoUnload ? 'checked' : '', - 'resetConsole_checked' => $this->resetConsole ? 'checked' : '', - ]; + $array[$this->arch . '_selected'] = 'selected'; + foreach ([self::BIOS, self::EFI] as $mode) { + $array['entries'][] = [ + 'is' . $mode => true, + 'mode' => $mode, + 'executable' => $this->executable[$mode], + 'initRd' => $this->initRd[$mode], + 'commandLine' => $this->commandLine[$mode], + 'replace_checked' => $this->replace[$mode] ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', + ]; + } $array['exec_checked'] = 'checked'; } @@ -168,6 +223,7 @@ class StandardBootEntry extends BootEntry 'replace' => $this->replace, 'autoUnload' => $this->autoUnload, 'resetConsole' => $this->resetConsole, + 'arch' => $this->arch, ]; } } @@ -176,7 +232,12 @@ class CustomBootEntry extends BootEntry { protected $script; - public function toScript($failLabel) + public function supportsMode($mode) + { + return true; + } + + public function toScript($failLabel, $mode) { return str_replace('%fail%', $failLabel, $this->script) . "\n"; } diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php index ed9f0986..56041c20 100644 --- a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php +++ b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php @@ -36,11 +36,11 @@ class IPxeMenu } } - public function getMenuDefinition($targetVar) + public function getMenuDefinition($targetVar, $mode) { $str = "menu {$this->title}\n"; foreach ($this->items as $item) { - $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId); + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode); } if ($this->defaultEntryId === null) { $defaultLabel = "mx_{$this->menuid}_poweroff"; @@ -61,11 +61,11 @@ class IPxeMenu return $str; } - public function getItemsCode() + public function getItemsCode($mode) { $str = ''; foreach ($this->items as $item) { - $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail'); + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); $str .= "goto slx_menu\n"; } return $str; diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php index 9d9d4163..03b860e8 100644 --- a/modules-available/serversetup-bwlp/inc/menuentry.inc.php +++ b/modules-available/serversetup-bwlp/inc/menuentry.inc.php @@ -58,8 +58,10 @@ class MenuEntry settype($this->menuentryid, 'int'); } - public function getMenuItemScript($lblPrefix, $requestedDefaultId) + public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode) { + if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) + return ''; $str = 'item '; if ($this->gap) { $str .= '--gap '; @@ -81,9 +83,9 @@ class MenuEntry return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; } - public function getBootEntryScript($lblPrefix, $failLabel) + public function getBootEntryScript($lblPrefix, $failLabel, $mode) { - if ($this->bootEntry === null) + if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) return ''; $str = ":{$lblPrefix}_{$this->menuentryid}\n"; if (!empty($this->md5pass)) { @@ -94,7 +96,7 @@ class MenuEntry . "goto slx_pass_check || goto $failLabel\n" . ":{$lblPrefix}_ok\n"; } - return $str . $this->bootEntry->toScript($failLabel); + return $str . $this->bootEntry->toScript($failLabel, $mode); } /* diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 25a31f06..ba2e5433 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -279,6 +279,7 @@ class Page_ServerSetup extends Page $entry->addFormFields($params); $params['title'] = $row['title']; $params['oldentryid'] = $params['entryid'] = $row['entryid']; + $params['builtin'] = $row['builtin']; } Render::addTemplate('ipxe-new-boot-entry', $params); } @@ -543,7 +544,7 @@ class Page_ServerSetup extends Page } if ($entry === null) { Message::addError('main.empty-field'); - return; + Util::redirect('?do=serversetup&show=bootentry'); } $params = [ 'entryid' => $newId, @@ -560,10 +561,10 @@ class Page_ServerSetup extends Page // Edit existing entry $params['oldid'] = $oldEntryId; Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data - WHERE entryid = :oldid AND builtin = 0', $params); - // TODO: Redirect to &show=bootentry + WHERE entryid = :oldid', $params); Message::addSuccess('boot-entry-updated', $newId); } + Util::redirect('?do=serversetup&show=bootentry'); } } diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html index fd9e1d72..33de1ee4 100644 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html @@ -1,5 +1,11 @@

{{lang_newBootEntryHead}}

+{{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+{{/builtin}} +
{{lang_bootEntryData}} @@ -33,47 +39,69 @@
+
+ + +
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
- - -
+
+ {{#entries}} +
+
+
+

{{mode}}

+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ {{/entries}}
@@ -104,5 +132,21 @@ document.addEventListener('DOMContentLoaded', function () { $('#form-' + $(this).val()).show(); }); $('.type-radio[checked]').click(); + var $as = $('#arch-selector'); + $as.change(function() { + var v = $as.val(); + if (v === 'agnostic') { + v = 'PCBIOS'; + $('.arch-heading').hide(); + } else { + $('.arch-heading').show(); + } + var vs = v.split('-'); + var cols = 12 / vs.length; + $('.mode-class').hide(); + for (var i = 0; i < vs.length; ++i) { + $('#col-' + vs[i]).attr('class', 'mode-class col-md-' + cols).show(); + } + }).change(); }); // --> \ No newline at end of file -- cgit v1.2.3-55-g7522 From 1419f851e2870fd5c13bb6ee30305f19d049d01b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 20 Sep 2018 17:08:41 +0200 Subject: [serversetup-bwlp] Implement chain-to-self for missing params --- modules-available/serversetup-bwlp/api.inc.php | 94 ++++++++++------------ .../templates/ipxe-new-boot-entry.html | 6 ++ 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php index 52f30440..bc81d35d 100644 --- a/modules-available/serversetup-bwlp/api.inc.php +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -1,6 +1,44 @@ $v) { + $query .= $k . '=' . $v . '&'; + } + $query = substr($query, 0, -1); + echo << 'exit 1', @@ -14,11 +52,8 @@ $ip = $_SERVER['REMOTE_ADDR']; if (substr($ip, 0, 7) === '::ffff:') { $ip = substr($ip, 7); } -$uuid = Request::any('uuid', false, 'string'); $menu = IPxeMenu::forClient($ip, $uuid); -// Get platform - EFI or PCBIOS -$platform = strtoupper(Request::any('platform', 'PCBIOS', 'string')); // Get preferred localboot method, depending on system model $localboot = false; @@ -38,8 +73,8 @@ if ($model === false) { return false; return trim(preg_replace('/\s+/', ' ', $str)); } - $manuf = modfilt(Request::any('manuf', false, 'string')); - $product = modfilt(Request::any('product', false, 'string')); + $manuf = modfilt($manuf); + $product = modfilt($product); if (!empty($product)) { $model = $product; if (!empty($manuf)) { @@ -114,17 +149,13 @@ goto fail # start :init -iseq \${nic} \${} && set nic 0 || - -set ipappend1 ip=\${net\${nic}/ip}:{$serverIp}:\${net\${nic}/gateway}:\${net\${nic}/netmask} -set ipappend2 BOOTIF=01-\${net\${nic}/mac:hexhyp} +set ipappend1 ip=\${ip}:{$serverIp}:\${gateway}:\${netmask} +set ipappend2 BOOTIF=01-\${mac:hexhyp} set serverip $serverIp || # Clean up in case we've been chained to imgfree || -ifopen || - imgfetch --name bg-load /tftp/openslx.png || imgfetch --name bg-menu /tftp/pxe-menu.png || @@ -162,41 +193,12 @@ $output .= $menu->getItemsCode($platform); // TODO: Work out memtest stuff. Needs to be put on server (install/update script) -- PCBIOS only? Chain EFI -> BIOS? /* -:i1 -#console || -echo Welcome to Shell || -shell -goto slx_menu - -:i2 -imgfree || -kernel /boot/default/kernel slxbase=boot/default slxsrv=$serverIp splash BOOTIF=01-\${net\${nic}/mac:hexhyp} || echo Could not download kernel -initrd /boot/default/initramfs-stage31 || echo Could not download initrd -boot -ar || goto fail - -:i3 -chain -ar \${self} || -chain -ar /tftp/undionly.kpxe || goto fail - -:i4 -imgfree || -sanboot --no-describe --drive 0x80 || goto fail :i5 chain -a /tftp/memtest.0 passes=1 onepass || goto membad prompt Memory OK. Press a key. goto init -:i6 -console --left 60 --top 130 --right 67 --bottom 96 --quick --picture bg-load --keep || -echo Welcome to Shell || -shell -goto slx_menu - -:i7 -chain -ar tftp://132.230.4.6/ipxelinux.0 || prompt FAILED PRESS A KEY -goto slx_menu - :i8 set x:int32 0 :again @@ -207,16 +209,6 @@ iseq \${x} 20 || goto again prompt DONE. Press dein Knie. goto slx_menu -:i9 -reboot || -prompt Reboot failed. Press a key. -goto slx_menu - -:i10 -poweroff || -prompt Poweroff failed. Press a key. -goto slx_menu - :membad iseq \${errno} 0x1 || goto memaborted params diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html index 33de1ee4..95802f7a 100644 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html @@ -115,6 +115,12 @@
+ {{#builtin}} +
+ {{lang_editBuiltinWarn}} +
+ {{/builtin}} +
@@ -30,8 +30,15 @@ {{/ips}}

- {{lang_bootHint}} + {{lang_recompileHint}}

+
+ + +
\ No newline at end of file -- cgit v1.2.3-55-g7522 From 61bd56dad51354efe37bf0aec80745dd1c09bd4b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 26 Sep 2018 14:49:57 +0200 Subject: [serversetup-bwlp] Detect wether we run our modified iPXE Try not to use special features if stock iPXE is running --- modules-available/serversetup-bwlp/api.inc.php | 54 +++++++++++++++------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 6 +-- .../serversetup-bwlp/inc/menuentry.inc.php | 10 ++-- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php index bc81d35d..4ed316a7 100644 --- a/modules-available/serversetup-bwlp/api.inc.php +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -2,23 +2,40 @@ // Menu mode +$serverIp = Property::getServerIp(); + // Check if required arguments are given; if not, spit out according script and chain to self $uuid = Request::any('uuid', false, 'string'); // Get platform - EFI or PCBIOS $platform = Request::any('platform', false, 'string'); $manuf = Request::any('manuf', false, 'string'); $product = Request::any('product', false, 'string'); +$slxExtensions = Request::any('slx-extensions', false, 'int'); -if ($platform === false || ($uuid === false && $product === false)) { +if ($platform === false || ($uuid === false && $product === false) || $slxExtensions === false) { + error_log(print_r($_SERVER, true)); + sleep(1); $url = parse_url($_SERVER['REQUEST_URI']); - $urlbase = $url['path']; + if (isset($_SERVER['SCRIPT_URI']) && preg_match('#(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) { + $urlbase = $out[1]; + } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_NAME'])) { + $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME']; + } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_ADDR'])) { + $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_ADDR']; + } else { + $urlbase = 'http://' . $serverIp; + } + $urlbase .= $url['path']; if (empty($url['query'])) { $arr = []; } else { parse_str($url['query'], $arr); - $arr = array_map('urlencode', $arr); + foreach ($arr as &$v) { + $v = urlencode($v); + } + unset($v); } - $arr['uuid'] = '${uuid:uristring}'; + $arr['uuid'] = '${uuid}'; $arr['mac'] = '${mac}'; $arr['manuf'] = '${manufacturer:uristring}'; $arr['product'] = '${product:uristring}'; @@ -27,11 +44,16 @@ if ($platform === false || ($uuid === false && $product === false)) { foreach ($arr as $k => $v) { $query .= $k . '=' . $v . '&'; } - $query = substr($query, 0, -1); + //$query = substr($query, 0, -1); echo << 'sanboot --no-describe', ]; -$serverIp = Property::getServerIp(); - $ip = $_SERVER['REMOTE_ADDR']; if (substr($ip, 0, 7) === '::ffff:') { $ip = substr($ip, 7); @@ -109,7 +129,11 @@ if (isset($BOOT_METHODS[$localboot])) { $BOOT_METHODS = array_reverse($BOOT_METHODS); } -// TODO: Feature check for our own iPXE extensions, stay compatible to stock iPXE +if ($slxExtensions) { + $slxConsoleUpdate = '--update'; +} else { + $slxConsoleUpdate = ''; +} $output = <<getMenuDefinition('target', $platform); +$output .= $menu->getMenuDefinition('target', $platform, $slxExtensions); $output .= <<getItemsCode($platform); -// TODO: Work out memtest stuff. Needs to be put on server (install/update script) -- PCBIOS only? Chain EFI -> BIOS? - /* :i5 @@ -202,8 +224,8 @@ goto init :i8 set x:int32 0 :again -console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep --quick || -console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep --quick || +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || inc x iseq \${x} 20 || goto again prompt DONE. Press dein Knie. diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php index 56041c20..6429a2a7 100644 --- a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php +++ b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php @@ -36,11 +36,11 @@ class IPxeMenu } } - public function getMenuDefinition($targetVar, $mode) + public function getMenuDefinition($targetVar, $mode, $slxExtensions) { - $str = "menu {$this->title}\n"; + $str = "menu -- {$this->title}\n"; foreach ($this->items as $item) { - $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode); + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); } if ($this->defaultEntryId === null) { $defaultLabel = "mx_{$this->menuid}_poweroff"; diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php index 03b860e8..d243fd23 100644 --- a/modules-available/serversetup-bwlp/inc/menuentry.inc.php +++ b/modules-available/serversetup-bwlp/inc/menuentry.inc.php @@ -58,7 +58,7 @@ class MenuEntry settype($this->menuentryid, 'int'); } - public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode) + public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) { if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) return ''; @@ -66,7 +66,7 @@ class MenuEntry if ($this->gap) { $str .= '--gap '; } else { - if ($this->hidden) { + if ($this->hidden && $slxExtensions) { if ($this->hotkey === false) return ''; // Hidden entries without hotkey are illegal $str .= '--hidden '; @@ -79,7 +79,11 @@ class MenuEntry } $str .= "{$lblPrefix}_{$this->menuentryid} "; } - $str .= $this->title; + if (empty($this->title)) { + $str .= '${}'; + } else { + $str .= $this->title; + } return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; } -- cgit v1.2.3-55-g7522 From 84e147f238286c1dff78b5f9b5488167293f6d35 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 26 Sep 2018 14:50:32 +0200 Subject: [serversetup-bwlp] Don't allow recompilation if already in progress --- modules-available/serversetup-bwlp/page.inc.php | 56 +++++++++++++--------- .../serversetup-bwlp/templates/ipaddress.html | 4 +- .../serversetup-bwlp/templates/ipxe_update.html | 5 +- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 6b1d20b6..4ebb7530 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -3,11 +3,26 @@ class Page_ServerSetup extends Page { - private $taskStatus; + private $addrListTask; + private $compileTask = null; private $currentAddress; private $currentMenu; private $hasIpSet = false; + private function getCompileTask() + { + if ($this->compileTask !== null) + return $this->compileTask; + $this->compileTask = Property::get('ipxe-task-id'); + if ($this->compileTask !== false) { + $this->compileTask = Taskmanager::status($this->compileTask); + if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { + $this->compileTask = false; + } + } + return $this->compileTask; + } + protected function doPreprocess() { User::load(); @@ -39,7 +54,9 @@ class Page_ServerSetup extends Page if ($action === 'compile') { User::assertPermission("edit.address"); - Trigger::ipxe(); + if ($this->getCompileTask() === false) { + Trigger::ipxe(); + } Util::redirect('?do=serversetup'); } @@ -100,13 +117,7 @@ class Page_ServerSetup extends Page { Render::addTemplate("heading"); - $task = Property::get('ipxe-task-id'); - if ($task !== false) { - $task = Taskmanager::status($task); - if (!Taskmanager::isTask($task) || Taskmanager::isFinished($task)) { - $task = false; - } - } + $task = $this->getCompileTask(); if ($task !== false) { Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); } @@ -291,8 +302,9 @@ class Page_ServerSetup extends Page private function showEditAddress() { Render::addTemplate('ipaddress', array( - 'ips' => $this->taskStatus['data']['addresses'], + 'ips' => $this->addrListTask['data']['addresses'], 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', )); } @@ -300,27 +312,27 @@ class Page_ServerSetup extends Page private function getLocalAddresses() { - $this->taskStatus = Taskmanager::submit('LocalAddressesList', array()); + $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); - if ($this->taskStatus === false) { - $this->taskStatus['data']['addresses'] = false; + if ($this->addrListTask === false) { + $this->addrListTask['data']['addresses'] = false; return false; } - if (!Taskmanager::isFinished($this->taskStatus)) { // TODO: Async if just displaying - $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id'], 4000); + if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying + $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); } - if (Taskmanager::isFailed($this->taskStatus) || !isset($this->taskStatus['data']['addresses'])) { - $this->taskStatus['data']['addresses'] = false; + if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { + $this->addrListTask['data']['addresses'] = false; return false; } $sortIp = array(); - foreach (array_keys($this->taskStatus['data']['addresses']) as $key) { - $item = & $this->taskStatus['data']['addresses'][$key]; + foreach (array_keys($this->addrListTask['data']['addresses']) as $key) { + $item = & $this->addrListTask['data']['addresses'][$key]; if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { - unset($this->taskStatus['data']['addresses'][$key]); + unset($this->addrListTask['data']['addresses'][$key]); continue; } if ($this->currentAddress === $item['ip']) { @@ -330,7 +342,7 @@ class Page_ServerSetup extends Page $sortIp[] = $item['ip']; } unset($item); - array_multisort($sortIp, SORT_STRING, $this->taskStatus['data']['addresses']); + array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); return true; } @@ -483,7 +495,7 @@ class Page_ServerSetup extends Page { $newAddress = Request::post('ip', 'none', 'string'); $valid = false; - foreach ($this->taskStatus['data']['addresses'] as $item) { + foreach ($this->addrListTask['data']['addresses'] as $item) { if ($item['ip'] !== $newAddress) continue; $valid = true; diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp/templates/ipaddress.html index ef8cc914..ea19c417 100644 --- a/modules-available/serversetup-bwlp/templates/ipaddress.html +++ b/modules-available/serversetup-bwlp/templates/ipaddress.html @@ -20,7 +20,7 @@ {{/default}} {{^default}} - @@ -35,7 +35,7 @@
- diff --git a/modules-available/serversetup-bwlp/templates/ipxe_update.html b/modules-available/serversetup-bwlp/templates/ipxe_update.html index c5aafa1c..71611085 100644 --- a/modules-available/serversetup-bwlp/templates/ipxe_update.html +++ b/modules-available/serversetup-bwlp/templates/ipxe_update.html @@ -14,7 +14,7 @@ {{lang_generationFailed}} -
{{lang_menuGeneration}}
+
{{lang_menuGeneration}}
@@ -27,6 +27,9 @@ if (task.data.pxeDone) $('#built-pxe').removeClass('invisible'); if (task.data.usbDone) $('#built-usb').removeClass('invisible'); } + if (task.statusCode === 'TASK_ERROR' || task.statusCode === 'TASK_FINISHED') { + $('#tm-compile-div').find('pre').hide(); + } if (task.statusCode === 'TASK_ERROR') { var $gf = $('#genfailed'); if (task.data && task.data.errors) { -- cgit v1.2.3-55-g7522 From 2f3f7ba4844ca4acff33569f3fa46ccf6301c92f Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Thu, 4 Oct 2018 03:19:44 +0200 Subject: [serversetup-bwlp] bootmenu-edit UI improvements - add cancel button - add checkbox to hide menu entries - add button to remove menu entries - hide input fields not relevant for spacers - add modal to select an entry that shows some information about the selected entry --- modules-available/serversetup-bwlp/page.inc.php | 6 +- .../serversetup-bwlp/templates/menu-edit.html | 269 ++++++++++++++------- 2 files changed, 189 insertions(+), 86 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 4ebb7530..615bac64 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -261,7 +261,11 @@ class Page_ServerSetup extends Page $menu['entries'] = Database::queryAll("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); - $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey FROM serversetup_bootentry ORDER BY title ASC"); + $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entrylist'] as &$bootentry) { + $bootentry['json'] = $bootentry['data']; + $bootentry['data'] = json_decode($bootentry['data']); + } foreach ($menu['entries'] as &$entry) { $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); // TODO: plainpass only when permissions diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index c0a353b4..e695ef20 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -7,7 +7,7 @@
{{title}} {{^title}} - {{lang_newMenu}} + {{lang_newMenu}} {{/title}}
@@ -50,86 +50,151 @@ - - - + + + - - + + + + {{#entries}} - - - - - - + + + + - + + - - {{#entryid}} - - {{/entryid}} - - - + + + + + + + + + + {{/entries}}
{{lang_entryId}}{{lang_entryId}} {{lang_title}}{{lang_hotkey}}{{lang_password}}{{lang_hotkey}}{{lang_password}}
- - -
- - -
-
+ + + +
+ + +
+
+ + - - - - - -
+ + + + + + +
+ + +
+
+ +
-
+
+
+
+ {{lang_cancel}}
+ +
- @@ -141,38 +206,52 @@ -
+ - + + - - + + + +
+ + +
+ + + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 13ce0399b010241ecc136eb35196cffdd503b73f Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Thu, 11 Oct 2018 15:25:20 +0200 Subject: [locationinfo] disable add location when 4 rooms in list in default-panel-config - don't close dropdown selection when room selected - if 4 rooms selected disable the add button - if 4 rooms selected, close the dropdown selection - re enable the button when a room gets removed --- .../templates/page-config-panel-default.html | 25 +++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/modules-available/locationinfo/templates/page-config-panel-default.html b/modules-available/locationinfo/templates/page-config-panel-default.html index ba493579..f7009764 100644 --- a/modules-available/locationinfo/templates/page-config-panel-default.html +++ b/modules-available/locationinfo/templates/page-config-panel-default.html @@ -293,7 +293,7 @@
- {{lang_cancel}} - +
+ {{lang_cancel}} + +
- {{lang_cancel}} - +
+ {{lang_cancel}} + +
- - {{lang_cancel}} - +
+ {{lang_cancel}} + +
\ No newline at end of file -- cgit v1.2.3-55-g7522 From 8882cfc967e2874f57bb70cd55179fed624e0461 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 23 Nov 2018 13:20:19 +0100 Subject: [serversetup-bwlp] Fix adding boot entry of type exec --- modules-available/serversetup-bwlp/page.inc.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 18ad396a..8cd20c75 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -335,6 +335,10 @@ class Page_ServerSetup extends Page if ($id === false) { $params['exec_checked'] = 'checked'; $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + $params['entries'] = [ + ['mode' => 'PCBIOS'], + ['mode' => 'EFI'], + ]; } else { // Query existing entry $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry -- cgit v1.2.3-55-g7522 From 1729bd0ec9137697a7688c5f556163717abfb54b Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Fri, 23 Nov 2018 16:02:17 +0100 Subject: [serversetup-bwlp] Fix new menu entries not using the preset titles --- .../serversetup-bwlp/templates/menu-edit.html | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index 6059333e..2141103f 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -84,7 +84,7 @@ - @@ -363,21 +363,21 @@ }); $('#entry-list').change(function(e) { - var modal = $('#entry-chosser-modal'); + var modal = $('#entry-chooser-modal'); modal.find('.entrydata').hide(); modal.find('#entrydata-' + $(this).val()).show(); }); var currentEntryButton = null; - $('#entry-chosser-modal').on('show.bs.modal', function(e) { + $('#entry-chooser-modal').on('show.bs.modal', function(e) { currentEntryButton = $(e.relatedTarget); var entryId = currentEntryButton.parent().find('.entry-id').val(); $('#entry-list').val(entryId).change(); }); $('#choose-entry').click(function() { - $('#entry-chosser-modal').modal('hide'); + $('#entry-chooser-modal').modal('hide'); var entryId = $('#entry-list').val(); currentEntryButton.parent().find('.entry-id').val(entryId); currentEntryButton.text(entryId || spacerText); @@ -390,6 +390,16 @@ } else { tableRow.find('.no-spacer').show(); } + var $title = tableRow.find('.title'); + var oldval = $title.data('old'); + if (oldval === '#stop#') + return; + if (oldval !== '#new#' && oldval !== $title.val()) { + $title.data('old', '#stop#'); + return; + } + var text = $('#' + entryId + '-name').text(); + $title.val(text).data('old', text); }); }); \ No newline at end of file -- cgit v1.2.3-55-g7522 From baf1cd9fb75b620e80a2eb411ac2b627af292823 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Sat, 1 Dec 2018 15:37:54 +0100 Subject: [dozmod] ldapfilter: Update table scheme, fix minor bugs * Editing filter showed wrong success message * Made all methods static * Fix listing existing filters --- modules-available/dozmod/pages/ldapfilters.inc.php | 32 +++++++++++----------- .../dozmod/templates/ldapfilter-add.html | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/modules-available/dozmod/pages/ldapfilters.inc.php b/modules-available/dozmod/pages/ldapfilters.inc.php index 6b5ce2dc..d0ae41b8 100644 --- a/modules-available/dozmod/pages/ldapfilters.inc.php +++ b/modules-available/dozmod/pages/ldapfilters.inc.php @@ -23,7 +23,8 @@ class SubPage { if (self::$show === false) { // Get all ldapfilters from the sat db. - $ldapfilters = Database::simpleQuery("SELECT * FROM sat.ldapfilter"); + $ldapfilters = Database::queryAll("SELECT filterid, filtername, filterkey, filtervalue FROM sat.presetlecturefilter + WHERE filtertype ='LDAP' ORDER BY filtername ASC"); $data = array( 'ldapfilters' => $ldapfilters, @@ -39,27 +40,23 @@ class SubPage 'filterid' => 0 )); } else { - $ldapfilter = Database::queryFirst("SELECT * FROM sat.ldapfilter WHERE filterid=:id", array( 'id' => $filterid)); - - $data = array( - 'filterid' => $filterid, - 'filtername' => $ldapfilter['filtername'], - 'attribute' => $ldapfilter['attribute'], - 'value' => $ldapfilter['value'] - ); - Render::addTemplate('ldapfilter-add', $data); + $ldapfilter = Database::queryFirst("SELECT filterid, filtername, filterkey, filtervalue FROM sat.presetlecturefilter + WHERE filterid = :id AND filtertype = 'LDAP'", array( 'id' => $filterid)); + // TODO: Show error if not exists + + Render::addTemplate('ldapfilter-add', $ldapfilter); } } } - private function deleteLdapFilter() { + private static function deleteLdapFilter() { User::assertPermission('ldapfilters.save'); $filterid = Request::post('filterid', false, 'int'); if ($filterid === false) { Message::addError('ldap-filter-id-missing'); return; } - $res = Database::exec("DELETE FROM sat.ldapfilter WHERE filterid=:id", array('id' => $filterid)); + $res = Database::exec("DELETE FROM sat.presetlecturefilter WHERE filterid = :id AND filtertype = 'LDAP'", array('id' => $filterid)); if ($res !== 1) { Message::addWarning('ldap-invalid-filter-id', $filterid); } else { @@ -67,7 +64,7 @@ class SubPage } } - private function saveLdapFilter() { + private static function saveLdapFilter() { $filterid = Request::post('filterid', '', 'int'); $filtername = Request::post('filtername', false, 'string'); $filterattribute = Request::post('attribute', false, 'string'); @@ -80,7 +77,8 @@ class SubPage if ($filterid === 0) { // Insert filter in the db. - $res = Database::exec("INSERT INTO sat.ldapfilter (filtername, attribute, value) VALUES (:filtername, :attribute, :value)", array( + $res = Database::exec("INSERT INTO sat.presetlecturefilter (filtertype, filtername, filterkey, filtervalue) + VALUES ('LDAP', :filtername, :attribute, :value)", array( 'filtername' => $filtername, 'attribute' => $filterattribute, 'value' => $filtervalue @@ -94,7 +92,9 @@ class SubPage } else { // Update filter in the db. - $res = Database::exec("UPDATE sat.ldapfilter SET filtername=:filtername, attribute=:attribute, value=:value WHERE filterid=:filterid", array( + $res = Database::exec("UPDATE sat.presetlecturefilter SET + filtername = :filtername, filterkey = :attribute, filtervalue = :value + WHERE filterid = :filterid AND filtertype = 'LDAP'", array( 'filterid' => $filterid, 'filtername' => $filtername, 'attribute' => $filterattribute, @@ -104,7 +104,7 @@ class SubPage if ($res !== 1) { Message::addError('ldap-filter-insert-failed'); } else { - Message::addSuccess('ldap-filter-created'); + Message::addSuccess('ldap-filter-saved'); } } diff --git a/modules-available/dozmod/templates/ldapfilter-add.html b/modules-available/dozmod/templates/ldapfilter-add.html index c74fbcda..f66972d1 100644 --- a/modules-available/dozmod/templates/ldapfilter-add.html +++ b/modules-available/dozmod/templates/ldapfilter-add.html @@ -17,11 +17,11 @@
- +
- +
-- cgit v1.2.3-55-g7522 From acd0f96696dc5c7d581902186f913fb3dff5302d Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 3 Dec 2018 11:55:51 +0100 Subject: [locationinfo] HiS: Check if event has any planned dates --- .../locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php index 3b26e625..22b1d8fb 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php @@ -291,6 +291,8 @@ class CourseBackend_HisInOne extends CourseBackend continue; } foreach ($planElements as $planElement) { + if (empty($planElement['hisplannedDates'])) + continue; $unitPlannedDates = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); if ($unitPlannedDates === false) { -- cgit v1.2.3-55-g7522 From 3104f69bd48bd7241a5ae1077f9f8f8720572bb3 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 3 Dec 2018 14:58:25 +0100 Subject: [dozmod] Networkshares: New DB scheme, error checks --- .../dozmod/pages/networkshares.inc.php | 89 ++++++++++++++++------ .../dozmod/templates/ldapfilters.html | 5 ++ .../dozmod/templates/networkshares-edit.html | 21 +++-- .../dozmod/templates/networkshares.html | 32 ++++++-- 4 files changed, 107 insertions(+), 40 deletions(-) diff --git a/modules-available/dozmod/pages/networkshares.inc.php b/modules-available/dozmod/pages/networkshares.inc.php index d0bbe03a..659321b4 100644 --- a/modules-available/dozmod/pages/networkshares.inc.php +++ b/modules-available/dozmod/pages/networkshares.inc.php @@ -10,32 +10,51 @@ class SubPage if ($action === 'delete') { User::assertPermission('networkshares.save'); $shareid = Request::post('shareid', false, 'int'); - if ($shareid) { + if ($shareid !== false) { $res = Database::exec('DELETE FROM sat.presetnetworkshare WHERE shareid = :shareid', ['shareid' => $shareid]); - if ($res) Message::addSuccess('networkshare-deleted'); + if ($res !== false) { + Message::addSuccess('networkshare-deleted'); + } } } else if ($action === 'save') { User::assertPermission('networkshares.save'); - $shareid = Request::post('shareid', false, 'int'); - $sharename = Request::post('sharename', false, 'string'); + $shareid = Request::post('shareid', 0, 'int'); + $sharename = Request::post('sharename', '', 'string'); $path = Request::post('path', false, 'string'); - $target = Request::post('target', null, 'string'); - $username = Request::post('username', null, 'string'); - $password = Request::post('password', null, 'string'); - if ($sharename && $path) { - if ($shareid) { - Database::exec('UPDATE sat.presetnetworkshare SET sharename = :sharename, path = :path, target = :target, username = :username, password = :password' - .' WHERE shareid = :shareid', compact('shareid', 'sharename', 'path', 'target', 'username', 'password')); + $target = Request::post('target', '', 'string'); + $authType = Request::post('auth', '', 'string'); + $username = Request::post('username', '', 'string'); + $password = Request::post('password', '', 'string'); + if (!in_array($authType, ['LOGIN_USER', 'OTHER_USER'], true)) { + Message::addError('networkshare-invalid-auth-type', $authType); + } elseif (empty($path)) { + Message::addError('networkshare-missing-path'); + } else { + $data = json_encode([ + 'auth' => $authType, + 'path' => $path, + 'displayname' => $sharename, + 'mountpoint' => $target, + 'username' => $username, + 'password' => $password, + ]); + if ($shareid !== 0) { + Database::exec('UPDATE sat.presetnetworkshare SET sharename = :sharename, sharedata = :data' + .' WHERE shareid = :shareid', compact('shareid', 'sharename', 'data')); } else { - Database::exec('INSERT INTO sat.presetnetworkshare (sharename, path, target, username, password, active)' - .' VALUES (:sharename, :path, :target, :username, :password, 0)', compact('sharename', 'path', 'target', 'username', 'password')); + Database::exec('INSERT INTO sat.presetnetworkshare (sharename, sharedata, active)' + .' VALUES (:sharename, :data, 1)', compact('sharename', 'data')); } Message::addSuccess('networkshare-saved'); } - } else if ($action === 'toggleActive') { + } else if ($action === 'activate' || $action === 'deactivate') { User::assertPermission('networkshares.save'); $shareid = Request::post('shareid', false, 'int'); - Database::exec('UPDATE sat.presetnetworkshare SET active = !active WHERE shareid = :shareid', compact('shareid')); + $active = ($action === 'activate' ? 1 : 0); + Database::exec('UPDATE sat.presetnetworkshare SET active = :active WHERE shareid = :shareid', compact('active', 'shareid')); + } + if (Request::isPost()) { + Util::redirect('?do=dozmod§ion=networkshares'); } User::assertPermission('networkshares.view'); } @@ -44,18 +63,44 @@ class SubPage { $show = Request::get('show', 'list', 'string'); if ($show === 'list') { - $res = Database::simpleQuery('SELECT * FROM sat.presetnetworkshare;'); + $res = Database::simpleQuery('SELECT shareid, sharename, sharedata, active + FROM sat.presetnetworkshare ORDER BY sharename ASC'); $rows = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $row['specificUser'] = $row['username'] && $row['password']; - $rows[] = $row; + $dec = json_decode($row['sharedata'], true); + if (!is_array($dec)) { + $dec = []; + } + if ($dec['auth'] === 'LOGIN_USER') { + $row['loginAsUser'] = true; + } + $rows[] = $row + $dec; } - Render::addTemplate('networkshares', ['networkshares' => $rows, 'hasEditPermissions' => User::hasPermission('networkshares.save')]); + Render::addTemplate('networkshares', [ + 'networkshares' => $rows, + 'hasEditPermissions' => User::hasPermission('networkshares.save') + ]); } else if ($show === 'edit') { $shareid = Request::get('shareid', 0, 'int'); - $data = Database::queryFirst('SELECT * FROM sat.presetnetworkshare WHERE shareid = :shareid', ['shareid' => $shareid]); - if ($data['username'] && $data['password']) $data['specificUser'] = 'selected'; - else $data['loggedInUser'] = 'selected'; + if ($shareid === 0) { + $data = []; + } else { + $data = Database::queryFirst('SELECT shareid, sharename, sharedata + FROM sat.presetnetworkshare WHERE shareid = :shareid', ['shareid' => $shareid]); + if ($data === false) { + Message::addError('networkshare-invalid-shareid', $shareid); + Util::redirect('?do=dozmod§ion=networkshares'); + } + $dec = json_decode($data['sharedata'], true); + if (is_array($dec)) { + $data += $dec; + } + if ($data['auth'] === 'LOGIN_USER') { + $data['loggedInUser_selected'] = 'selected'; + } else { + $data['specificUser_selected'] = 'selected'; + } + } Render::addTemplate('networkshares-edit', $data); } } diff --git a/modules-available/dozmod/templates/ldapfilters.html b/modules-available/dozmod/templates/ldapfilters.html index 49ecc222..06be33a5 100644 --- a/modules-available/dozmod/templates/ldapfilters.html +++ b/modules-available/dozmod/templates/ldapfilters.html @@ -1,4 +1,9 @@

{{lang_ldapfilters}}

+ +

+ {{lang_ldapfiltersIntro}} +

+ diff --git a/modules-available/dozmod/templates/networkshares-edit.html b/modules-available/dozmod/templates/networkshares-edit.html index 32aa902f..cc89dbcc 100644 --- a/modules-available/dozmod/templates/networkshares-edit.html +++ b/modules-available/dozmod/templates/networkshares-edit.html @@ -21,7 +21,7 @@
- + +
- +
- +
@@ -20,18 +24,32 @@ - - - + + + {{#hasEditPermissions}} - - - + + {{#hasEditPermission}} {{#exceptions}} - +
{{sharename}} {{path}}{{#target}}{{.}}:{{/target}}{{#specificUser}}{{lang_specificUser}}{{/specificUser}}{{^specificUser}}{{lang_loggedInUser}}{{/specificUser}}{{#specificUser}}{{username}}{{/specificUser}}{{mountpoint}} + {{#loginAsUser}}{{lang_loggedInUser}}{{/loginAsUser}} + {{^loginAsUser}}{{lang_specificUser}}{{/loginAsUser}} + + {{^loginAsUser}}{{username}}{{/loginAsUser}} + +
- + {{#active}} + + + {{/active}} + {{^active}} + + + {{/active}}
-- cgit v1.2.3-55-g7522 From ed899338534f68dd6ba62fd2f1c207858d20aa3b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 3 Dec 2018 15:00:53 +0100 Subject: [dozmod] Update translations --- modules-available/dozmod/lang/de/messages.json | 5 ++++- modules-available/dozmod/lang/de/template-tags.json | 10 +++++++--- modules-available/dozmod/lang/en/messages.json | 8 ++++++-- modules-available/dozmod/lang/en/template-tags.json | 6 +++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/modules-available/dozmod/lang/de/messages.json b/modules-available/dozmod/lang/de/messages.json index 29d40619..f74c3eab 100644 --- a/modules-available/dozmod/lang/de/messages.json +++ b/modules-available/dozmod/lang/de/messages.json @@ -5,13 +5,16 @@ "images-pending-delete-exist": "Zur L\u00f6schung markierte VM-Versionen: {{0}}", "ldap-filter-created": "LDAP Filter wurde erfolgreich erstellt", "ldap-filter-deleted": "LDAP Filter wurde erfolgreich gel\u00f6scht", - "ldap-invalid-filter-id": "Ung\u00fcltige LDAP Filter ID", "ldap-filter-id-missing": "Fehlende LDAP Filter ID", "ldap-filter-insert-failed": "LDAP filter konnte der Datenbank nicht hinzugef\u00fcgt werden", "ldap-filter-save-missing-information": "Es fehlen LDAP Filter Informationen", "ldap-filter-saved": "LDAP Filter wurde erfolgreich gespeichert", + "ldap-invalid-filter-id": "Ung\u00fcltige LDAP Filter ID", "mail-config-saved": "Mail-Konfiguration gespeichert", "networkshare-deleted": "Netzlaufwerk gel\u00f6scht", + "networkshare-invalid-auth-type": "Ung\u00fcltiger Authentifizierungs-Typ: {{0}}", + "networkshare-invalid-shareid": "Nicht-existierender Share: {{0}}", + "networkshare-missing-path": "Fehlende Pfadangabe", "networkshare-saved": "Netzlaufwerk gespeichert", "no-expired-images": "Keine Abgelaufenen VM-Versionen", "nothing-submitted": "Es wurde nichts \u00fcbermittelt", diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json index a7ee9833..99756995 100644 --- a/modules-available/dozmod/lang/de/template-tags.json +++ b/modules-available/dozmod/lang/de/template-tags.json @@ -1,7 +1,7 @@ { "lang_actionTarget": "Aktionsziel", "lang_active": "Aktiv", - "lang_addShare": "Netzlaufwerk hinzufügen", + "lang_addShare": "Netzlaufwerk hinzuf\u00fcgen", "lang_allowLoginByDefault": "Login standardm\u00e4\u00dfig erlauben", "lang_allowLoginDescription": "Wenn diese Option aktiviert ist, k\u00f6nnen sich alle Mitarbeiter der Einrichtung \u00fcber die bwLehrpool-Suite anmelden und VMs\/Veranstaltungen verwalten. Wenn Sie diese Option deaktivieren, m\u00fcssen Sie in der Untersektion \"Benutzer und Berechtigungen\" jeden Benutzer nach dem ersten Loginversuch manuell freischalten.", "lang_asteriskRequired": "Felder mit (*) sind erforderlich", @@ -45,8 +45,9 @@ "lang_ldapFilterDescription": "Dies sind die Filter, die ein Benutzer in der bwLehrpool-Suite Veranstaltungen hinzuf\u00fcgen kann.", "lang_ldapFilterEdit": "LDAP Filter bearbeiten", "lang_ldapFilterName": "Filtername", - "lang_ldapfilters": "LDAP Filter", "lang_ldapFilterValue": "Wert", + "lang_ldapfilters": "LDAP Filter", + "lang_ldapfiltersIntro": "Hier k\u00f6nnen Sie Vorgaben f\u00fcr die Veranstaltungsspezifischen LDAP-Filter machen. LDAP-Filter sind einfache Attributsfilter, die clientseitig Anwendung finden. Die hier definierten Filter werden in der bwLehrpool-Suite als Vorschl\u00e4ge aufgelistet und k\u00f6nnen mittels Checkbox aktiviert werden. Alternativ k\u00f6nnen in der bwLehrpool-Suite auch eigene Filter angelegt werden.", "lang_lecture": "Veranstaltung", "lang_lecturePermissionAdmin": "Administration", "lang_lecturePermissionEdit": "Bearbeiten", @@ -63,6 +64,8 @@ "lang_modified": "Modifiziert", "lang_name": "Name", "lang_networkshares": "Netzlaufwerke", + "lang_networksharesIntro": "Hier k\u00f6nnen Sie vordefinierte Netzlaufwerke anlegen, die den Nutzern der bwLehrpool-Suite zur Auswahl gestellt werden. Es ist den Nutzern der bwLehrpool-Suite weiterhin m\u00f6glich, komplett eigene Netzwerkfreigaben zu definieren. Die Angaben hier sollen lediglich das Hinzuf\u00fcgen h\u00e4ufig genutzter Laufwerke vereinfachen, bzw. das \u00c4ndern eines Netzwerkpfades vereinfachen, da in diesem Fall nur der Zentrale Eintrag hier angepasst werden muss, und nicht mehr wie zuvor jede Veranstaltung einzeln.", + "lang_none": "(Keiner)", "lang_organization": "Einrichtung", "lang_organizationListHeader": "Nutzungsrechte f\u00fcr den Satelliten festlegen", "lang_os": "Betriebssystem", @@ -71,14 +74,15 @@ "lang_path": "Pfad", "lang_placeholders": "Platzhalter", "lang_port": "Port", + "lang_printer": "Drucker", "lang_reallyResetTemplates": "Sind Sie sicher, dass Sie alle Texte l\u00f6schen und auf die Standardwerte zur\u00fccksetzen wollen?", "lang_replaceWithOriginal": "Originaltext in Textbox laden", "lang_replyTo": "Reply-To Adresse", "lang_runtimeConfig": "Laufzeit-Konfiguration", "lang_runtimeConfigLimits": "Beschr\u00e4nkungen", "lang_senderAddress": "Absenderadresse", - "lang_shareDeleteConfirm": "Willst du dieses Netzlaufwerk wirklich l\u00f6schen?", "lang_senderName": "Absender Anzeigename", + "lang_shareDeleteConfirm": "Willst du dieses Netzlaufwerk wirklich l\u00f6schen?", "lang_size": "Gr\u00f6\u00dfe", "lang_spaceWastedDuplication": "Potentiell durch mehrfach vorkommende Bl\u00f6cke belegter Speicherplatz", "lang_specificUser": "Spezifischer Nutzer", diff --git a/modules-available/dozmod/lang/en/messages.json b/modules-available/dozmod/lang/en/messages.json index a76fc67c..d09ff279 100644 --- a/modules-available/dozmod/lang/en/messages.json +++ b/modules-available/dozmod/lang/en/messages.json @@ -5,12 +5,16 @@ "images-pending-delete-exist": "VMs marked for deletion: {{0}}", "ldap-filter-created": "LDAP filter was successfully created", "ldap-filter-deleted": "LDAP filter successfully deleted", - "ldap-invalid-filter-id": "Invalid LDAP filter id", "ldap-filter-id-missing": "LDAP filter id was missing", "ldap-filter-insert-failed": "LDAP filter could not be inserted into the database", - "ldap-filter-save-missing-information": "LDAP filter informations were missing", + "ldap-filter-save-missing-information": "LDAP filter information is missing", + "ldap-filter-saved": "Successfully modified LDAP filter", + "ldap-invalid-filter-id": "Invalid LDAP filter id", "mail-config-saved": "Mail config saved", "networkshare-deleted": "Network share deleted", + "networkshare-invalid-auth-type": "Invalid auth type: {{0}}", + "networkshare-invalid-shareid": "Invalid share id: {{0}}", + "networkshare-missing-path": "Missing network path", "networkshare-saved": "Network share saved", "no-expired-images": "No expired VMs", "nothing-submitted": "There was nothing submitted", diff --git a/modules-available/dozmod/lang/en/template-tags.json b/modules-available/dozmod/lang/en/template-tags.json index 2fd3e91b..ddc89284 100644 --- a/modules-available/dozmod/lang/en/template-tags.json +++ b/modules-available/dozmod/lang/en/template-tags.json @@ -45,8 +45,9 @@ "lang_ldapFilterDescription": "These are the LDAP filters that can be applied to lectures in the bwLehrpool-Suite.", "lang_ldapFilterEdit": "Edit LDAP filter", "lang_ldapFilterName": "Filter name", - "lang_ldapfilters": "LDAP filters", "lang_ldapFilterValue": "Value", + "lang_ldapfilters": "LDAP filters", + "lang_ldapfiltersIntro": "This is the list of predefined lecture filters, based on LDAP attributes. These filters can be applied client side to only show lectures to specific user groups. These predefined filters are shown in the bwLehrpool-Suite when editing a lecture, and can be activated by a simple check box. Users of the bwLehrpool-Suite can either make use of these predefined filters, or add custom ones if desired.", "lang_lecture": "Lecture", "lang_lecturePermissionAdmin": "Administrate", "lang_lecturePermissionEdit": "Edit", @@ -63,6 +64,8 @@ "lang_modified": "modified", "lang_name": "Name", "lang_networkshares": "Network Shares", + "lang_networksharesIntro": "This is the list of predefined network shares. bwLehrpool-Suite users can still add custom network shares to their lectures, however having commonly used network shares as predefined entries should be much more convenient. Another advantage is that changing the path of a network share centrally avoids having to edit a dozen lectures' configuration manually.", + "lang_none": "(none)", "lang_organization": "Organization", "lang_organizationListHeader": "Set access permissions for organizations", "lang_os": "Operating System", @@ -71,6 +74,7 @@ "lang_path": "Path", "lang_placeholders": "Placeholders", "lang_port": "Port", + "lang_printer": "Printer", "lang_reallyResetTemplates": "Are you sure you want to reset all texts to their default values?", "lang_replaceWithOriginal": "load original text into text box", "lang_replyTo": "Reply-To address", -- cgit v1.2.3-55-g7522 From 62cd429deec4f8c3ba5a673a6fbf0b77e754520c Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 3 Dec 2018 15:04:10 +0100 Subject: [dozmod] networkshares: Use {{password_type}} for password field --- modules-available/dozmod/templates/networkshares-edit.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules-available/dozmod/templates/networkshares-edit.html b/modules-available/dozmod/templates/networkshares-edit.html index cc89dbcc..f0b43837 100644 --- a/modules-available/dozmod/templates/networkshares-edit.html +++ b/modules-available/dozmod/templates/networkshares-edit.html @@ -42,11 +42,11 @@
- +
- +
-- cgit v1.2.3-55-g7522 From f57731abf206bf3030a7a95a9019aa398c3dea2e Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Mon, 3 Dec 2018 19:03:24 +0100 Subject: [locationinfo] add new settings for panels (counter, updaterate) - setting: consider only clients in roomplaner, instead of ip range - add option to set update rate in summary panels - directly initialize when mode is "only calendar" - Set counter to "-" if room has ongoing event - fix some spelling / unused code --- modules-available/locationinfo/api.inc.php | 23 +++++-- .../locationinfo/inc/locationinfo.inc.php | 7 +- .../locationinfo/lang/de/template-tags.json | 2 + .../locationinfo/lang/en/template-tags.json | 2 + modules-available/locationinfo/page.inc.php | 18 +++++- .../locationinfo/templates/frontend-default.html | 25 ++++++-- .../locationinfo/templates/frontend-summary.html | 74 ++++++---------------- .../templates/page-config-panel-default.html | 16 +++++ .../templates/page-config-panel-summary.html | 44 +++++++++++++ 9 files changed, 139 insertions(+), 72 deletions(-) diff --git a/modules-available/locationinfo/api.inc.php b/modules-available/locationinfo/api.inc.php index ad71de8b..c33c528e 100644 --- a/modules-available/locationinfo/api.inc.php +++ b/modules-available/locationinfo/api.inc.php @@ -7,7 +7,7 @@ HandleParameters(); /** - * Handles the API paramenters. + * Handles the API parameters. */ function HandleParameters() { @@ -20,7 +20,7 @@ function HandleParameters() } elseif ($get === "machines") { $locationIds = LocationInfo::getLocationsOr404($uuid); $output = array(); - InfoPanel::appendMachineData($output, $locationIds, false); + InfoPanel::appendMachineData($output, $locationIds, true); $output = array_values($output); } elseif ($get === "config") { $type = InfoPanel::getConfig($uuid, $output); @@ -30,7 +30,7 @@ function HandleParameters() } } elseif ($get === "pcstates") { $locationIds = LocationInfo::getLocationsOr404($uuid); - $output = getPcStates($locationIds); + $output = getPcStates($locationIds, $uuid); } elseif ($get === "locationtree") { $locationIds = LocationInfo::getLocationsOr404($uuid); $output = getLocationTree($locationIds); @@ -84,7 +84,7 @@ function getLastChangeTs($paneluuid) * @param int[] $idList list of the location ids. * @return array aggregated PC states */ -function getPcStates($idList) +function getPcStates($idList, $paneluuid) { $pcStates = array(); foreach ($idList as $id) { @@ -99,13 +99,24 @@ function getPcStates($idList) } $locationInfoList = array(); - InfoPanel::appendMachineData($locationInfoList, $idList); + InfoPanel::appendMachineData($locationInfoList, $idList, true); + + $panel = Database::queryFirst('SELECT paneluuid, panelconfig FROM locationinfo_panel WHERE paneluuid = :paneluuid', + compact('paneluuid')); + $config = json_decode($panel['panelconfig'], true); + foreach ($locationInfoList as $locationInfo) { $id = $locationInfo['id']; foreach ($locationInfo['machines'] as $pc) { $key = strtolower($pc['pcState']); if (isset($pcStates[$id][$key])) { - $pcStates[$id][$key]++; + if ($config['roomplaner']) { + if (isset($pc['x']) && isset($pc['y'])) { + $pcStates[$id][$key]++; + } + } else { + $pcStates[$id][$key]++; + } } } } diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php index c51be666..88f96d29 100644 --- a/modules-available/locationinfo/inc/locationinfo.inc.php +++ b/modules-available/locationinfo/inc/locationinfo.inc.php @@ -80,6 +80,7 @@ class LocationInfo 'vertical' => false, 'eco' => false, 'prettytime' => true, + 'roomplaner' => true, 'scaledaysauto' => true, 'daystoshow' => 7, 'rotation' => 0, @@ -93,9 +94,9 @@ class LocationInfo if ($type === 'SUMMARY') { return array( 'language' => defined('LANG') ? LANG : 'en', - 'calupdate' => 30, - 'roomupdate' => 15, - 'configupdate' => 180, + 'roomplaner' => true, + 'eco' => false, + 'panelupdate' => 60, ); } return array(); diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json index 7862297c..e93d83a9 100644 --- a/modules-available/locationinfo/lang/de/template-tags.json +++ b/modules-available/locationinfo/lang/de/template-tags.json @@ -78,6 +78,7 @@ "lang_room": "Raum", "lang_roomId": "Raum ID", "lang_roomIdTooltip": "Die Raum ID, die der Server ben\u00f6tigt, um Kalenderdaten abzurufen (bei Exchange die Postfachadresse)", + "lang_roomplanerTooltip": "Nur PCs berücksichtigen, die im Raumplaner gesetzt wurden", "lang_roomupdateTooltip": "Zeit nach der die PCs aktualisiert werden (in Sekunden)", "lang_rotation": "Rotation", "lang_rotation0": "0\u00b0", @@ -119,6 +120,7 @@ "lang_url": "URL", "lang_urlPanel": "URL-Panel", "lang_urlTooltip": "URL die aufgerufen wird", + "lang_useRoomplaner": "Raumplaner benutzen", "lang_vertical": "Vertikaler Modus", "lang_verticalTooltip": "Legt fest, ob Kalender und Raum \u00fcbereinander angezeigt werden sollen" } \ No newline at end of file diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json index 2a191379..8a6e0724 100644 --- a/modules-available/locationinfo/lang/en/template-tags.json +++ b/modules-available/locationinfo/lang/en/template-tags.json @@ -78,6 +78,7 @@ "lang_room": "Room", "lang_roomId": "Room ID", "lang_roomIdTooltip": "The ID of the room the server needs, for querying the calendar data (when using exchange the room mailbox)", + "lang_roomplanerTooltip": "Only consider PCs which were set in the roomplaner", "lang_roomupdateTooltip": "Time the PCs in the room gets updated (in seconds)", "lang_rotation": "Rotation", "lang_rotation0": "0\u00b0", @@ -119,6 +120,7 @@ "lang_url": "URL", "lang_urlPanel": "URL panel", "lang_urlTooltip": "URL which is shown by the panel", + "lang_useRoomplaner": "Use roomplans", "lang_vertical": "Vertical mode", "lang_verticalTooltip": "Defines whether the room and calendar are shown above each other" } \ No newline at end of file diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php index 3ff80927..26fa6c4e 100644 --- a/modules-available/locationinfo/page.inc.php +++ b/modules-available/locationinfo/page.inc.php @@ -359,6 +359,7 @@ class Page_LocationInfo extends Page 'vertical' => Request::post('vertical', false, 'bool'), 'eco' => Request::post('eco', false, 'bool'), 'prettytime' => Request::post('prettytime', false, 'bool'), + 'roomplaner' => Request::post('roomplaner', false, 'bool'), 'scaledaysauto' => Request::post('scaledaysauto', false, 'bool'), 'daystoshow' => Request::post('daystoshow', 7, 'int'), 'rotation' => Request::post('rotation', 0, 'int'), @@ -388,9 +389,19 @@ class Page_LocationInfo extends Page private function preparePanelConfigSummary() { + // Build json structure + $conf = array( + 'language' => Request::post('language', 'en', 'string'), + 'eco' => Request::post('eco', false, 'bool'), + 'roomplaner' => Request::post('roomplaner', false, 'bool'), + 'panelupdate' => Request::post('panelupdate', 30, 'int') + ); + if ($conf['panelupdate'] < 15) { + $conf['panelupdate'] = 15; + } // Check locations $locationids = self::getLocationIdsFromRequest(true); - return array('locationids' => $locationids); + return array('config' => $conf, 'locationids' => $locationids); } /** @@ -922,6 +933,7 @@ class Page_LocationInfo extends Page 'vertical_checked' => $config['vertical'] ? 'checked' : '', 'eco_checked' => $config['eco'] ? 'checked' : '', 'prettytime_checked' => $config['prettytime'] ? 'checked' : '', + 'roomplaner_checked' => $config['roomplaner'] ? 'checked' : '', 'scaledaysauto_checked' => $config['scaledaysauto'] ? 'checked' : '', 'daystoshow' => $config['daystoshow'], 'rotation' => $config['rotation'], @@ -947,9 +959,11 @@ class Page_LocationInfo extends Page 'uuid' => $id, 'panelname' => $panel['panelname'], 'languages' => $langs, - 'roomupdate' => $config['roomupdate'], + 'panelupdate' => $config['panelupdate'], + 'roomplaner_checked' => $config['roomplaner'] ? 'checked' : '', 'locations' => Location::getLocations(), 'locationids' => $panel['locationids'], + 'eco_checked' => $config['eco'] ? 'checked' : '', )); } } diff --git a/modules-available/locationinfo/templates/frontend-default.html b/modules-available/locationinfo/templates/frontend-default.html index d1ecaae8..7317f8e2 100755 --- a/modules-available/locationinfo/templates/frontend-default.html +++ b/modules-available/locationinfo/templates/frontend-default.html @@ -619,6 +619,11 @@ optional: generateProgressBar(); } + // Manually initialize mode 2, as initRoomLayout isn't called for this mode + if (room.config.mode === 2) { + queryCalendars(); + queryRooms(); + } mainUpdateLoop(); setInterval(mainUpdateLoop, 10000); setInterval(updateHeaders, globalConfig.eco ? 10000 : 1000); @@ -1116,12 +1121,11 @@ optional: * @param room Room */ function SetFreeSeats(room) { - room.$.seatsCounter.text(room.freePcs >= 0 ? room.freePcs : ''); + // if room has no allowed value, set text in the box to - + room.$.seatsCounter.text(room.freePcs >= 0 ? room.freePcs : '-'); room.$.seatsCounter.data('state', JSON.stringify(room.state)); if (room.freePcs > 0 && room.state && room.state.free) { - room.$.seatsBackground.css('background-color', '#250'); - } else if (room.freePcs === -1) { - room.$.seatsBackground.css('background-color', 'red'); + room.$.seatsBackground.css('background-color', '#250'); } else { room.$.seatsBackground.css('background-color', 'red'); } @@ -1148,7 +1152,8 @@ optional: if (!same) newText = t("closed"); } else if (tmp.state === "CalendarEvent") { if (!same) newText = tmp.title; - seats = -1; + // whilst event is running set freePcs to -, hopefully not breaking anything else with this + room.freePcs = "-"; } else if (tmp.state === "Free") { if (!same) newText = t("free"); } else if (tmp.state === "FreeNoEnd") { @@ -1496,8 +1501,14 @@ optional: for (var i = 0; i < update.length; i++) { var $div = $("#pc_" + room.id + "_" + update[i].id); // Pc free - if (update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY") { - freePcs++; + if (room.config.roomplaner === true) { + if ((update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY") && !isNaN(update[i].x) && !isNaN(update[i].y)) { + freePcs++; + } + } else { + if ((update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY")) { + freePcs++; + } } $div.removeClass('BROKEN OFFLINE IDLE OCCUPIED STANDBY'.replace(update[i].pcState, '')).addClass(update[i].pcState); diff --git a/modules-available/locationinfo/templates/frontend-summary.html b/modules-available/locationinfo/templates/frontend-summary.html index 4105dd16..0053d1ff 100644 --- a/modules-available/locationinfo/templates/frontend-summary.html +++ b/modules-available/locationinfo/templates/frontend-summary.html @@ -116,6 +116,7 @@ var startdate; var roomidsString = ""; var config = {{{config}}}; + var lastPanelUpdate = 0; $(document).ready(function () { init(); @@ -139,6 +140,7 @@ SetUpDate(time); generateLayout(config.tree); update(); + setInterval(update, 10000); } function SetUpDate(d) { @@ -165,7 +167,7 @@ */ function generateObject(json, myParent, outermost) { var obj; - if (!json.children || json.children.length == 0) { + if (!json.children || json.children.length === 0) { obj = generateChild(myParent, json.locationid, json.locationname, outermost); } else { obj = generateParent(myParent, json.locationid, json.locationname, outermost); @@ -178,59 +180,24 @@ } /** - * Helper function to generate id string used in query functions - * @param list A string, wicht contains ids or not(for now) - * @param id An ID which should be added to the list + * Main Update loop, this loop runs every 10 seconds */ - function addIdToUpdateList(list, id) { - if (list == "") { - list += id; - } else { - list += ("," + id); - } - return list; - } - - - const ROOMUPDATE_MS = 2*60*1000; - const CALUPDATE_MS = 20*60*1000; - var timeout = null; - function update() { - var calendarUpdateIds = ""; - var rommUpdateIds = ""; - var count = 0; - var nextUpdate = 15000; - var property; - // TODO: Only query a few rooms is not possible with the new api stuff ... - for (property in rooms) { - if (rooms[property].lastCalendarUpdate === null || rooms[property].lastCalendarUpdate + CALUPDATE_MS < MyDate().getTime()) { - // TODO: NOT NECESSARY ANYMORE?! - calendarUpdateIds = addIdToUpdateList(calendarUpdateIds, rooms[property].id); - count++; - rooms[property].lastCalendarUpdate = MyDate().getTime(); - } - if (rooms[property].lastRoomUpdate === null || rooms[property].lastRoomUpdate + ROOMUPDATE_MS < MyDate().getTime()) { - // TODO: NOT NECESSARY ANYMORE?! - rommUpdateIds = addIdToUpdateList(rommUpdateIds, rooms[property].id); - count++; - rooms[property].lastRoomUpdate = MyDate().getTime(); - } - // TODO if (count > 7) break; - } - if (calendarUpdateIds !== "") { - queryCalendars(); - nextUpdate = 1000; - } - if (rommUpdateIds !== "") { + var date = MyDate(); + var now = date.getTime(); + if (lastPanelUpdate + (config.panelupdate * 1000) < now) { + // Set Roomupdate Interval has passed, update. queryRooms(); - nextUpdate = 1000; - } - if (nextUpdate !== 1000) { + queryCalendars(); + lastPanelUpdate = now; + for (var property in rooms) { + rooms[property].lastCalendarUpdate = now; + rooms[property].lastRoomUpdate = now; + } + } else { + // Set Roomupdate Interval has NOT passed, check if panel was changed since last call and reload if true. queryPanelChange(); } - clearTimeout(timeout); - setTimeout(update, nextUpdate); } function cleanDate(d) { @@ -283,7 +250,6 @@ cache: false, timeout: 30000, success: function (result) { - var l = result.length; if (result[0] == null) { console.log("Error: Backend reported null back for RoomUpdate, this might happend if the room isn't" + "configurated."); @@ -331,7 +297,7 @@ function updatePcStates(json) { var l = json.length; for (var i = 0; i < l; i++) { - updateRoomUsage(json[i].id, json[i].idle, json[i].occupied, json[i].offline, json[i].broken, json[i].standby) + updateRoomUsage(json[i].id, json[i].idle, json[i].occupied, json[i].offline, json[i].broken, json[i].standby); } } @@ -428,8 +394,8 @@ /** * computes state of a room, states are: - * closed, FreeNoEnd, Free, ClaendarEvent. - * @param Room Object + * closed, FreeNoEnd, Free, CalendarEvent. + * @param room Object */ function ComputeCurrentState(room) { if (room.lastRoomUpdate === null) { @@ -479,7 +445,7 @@ /** * checks if a room is open * @param room Room object - * @returns bool for open or not + * @returns boolean for open or not */ function IsOpenNow(room) { var now = new MyDate(); diff --git a/modules-available/locationinfo/templates/page-config-panel-default.html b/modules-available/locationinfo/templates/page-config-panel-default.html index f7009764..c2915298 100644 --- a/modules-available/locationinfo/templates/page-config-panel-default.html +++ b/modules-available/locationinfo/templates/page-config-panel-default.html @@ -107,6 +107,22 @@
+ +
+
+
+ +
+
+ +
+
+

+ +

+
+
+
diff --git a/modules-available/locationinfo/templates/page-config-panel-summary.html b/modules-available/locationinfo/templates/page-config-panel-summary.html index 11f0dc65..737eef1f 100644 --- a/modules-available/locationinfo/templates/page-config-panel-summary.html +++ b/modules-available/locationinfo/templates/page-config-panel-summary.html @@ -70,6 +70,22 @@ + +
+
+
+ +
+
+ +
+
+

+ +

+
+
+
-- cgit v1.2.3-55-g7522 From 8e33e94c7ea3dcb62bb8f6aa3492d0c4fbb913dc Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 12 Dec 2018 13:21:53 +0100 Subject: [dozmod] ldapfilters: Fix list (attr/value) --- modules-available/dozmod/templates/ldapfilters.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules-available/dozmod/templates/ldapfilters.html b/modules-available/dozmod/templates/ldapfilters.html index 06be33a5..824ec70b 100644 --- a/modules-available/dozmod/templates/ldapfilters.html +++ b/modules-available/dozmod/templates/ldapfilters.html @@ -20,8 +20,8 @@ {{#ldapfilters}}
{{filtername}}{{attribute}}{{value}}{{filterkey}}{{filtervalue}} -- cgit v1.2.3-55-g7522 From 4c8a7363bf24072bebfa5e80b0257dd6011a8db0 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 2 Jan 2019 14:26:34 +0100 Subject: [locations] Fix invalid array access --- modules-available/locations/inc/location.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php index d43c36a7..8db0c5f3 100644 --- a/modules-available/locations/inc/location.inc.php +++ b/modules-available/locations/inc/location.inc.php @@ -429,7 +429,7 @@ class Location foreach ($self as $entry) { if (!isset($locs[$entry])) continue; - $overlapSelf[]['locationname'] = $locs[$entry['locationid']]['locationname']; + $overlapSelf[]['locationname'] = $locs[$entry]['locationname']; } } if ($overlapOther) { -- cgit v1.2.3-55-g7522 From a27ba66b59fbbdccd1679bcda77a8acb2f41a5c5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 2 Jan 2019 15:01:51 +0100 Subject: [serversetup-bwlp] Add column to override default menuentry of location --- modules-available/serversetup-bwlp/install.inc.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php index 8814bb7c..67d6693f 100644 --- a/modules-available/serversetup-bwlp/install.inc.php +++ b/modules-available/serversetup-bwlp/install.inc.php @@ -40,8 +40,10 @@ $res[] = tableCreate('serversetup_menuentry', " $res[] = tableCreate('serversetup_menu_location', ' `menuid` int(11) NOT NULL, `locationid` int(11) NOT NULL, + `defaultentryid` int(11) DEFAULT NULL, PRIMARY KEY (`menuid`,`locationid`), - UNIQUE `locationid` (`locationid`) + UNIQUE `locationid` (`locationid`), + KEY `defaultentryid` (`defaultentryid`) '); $res[] = tableCreate('serversetup_localboot', " @@ -50,6 +52,15 @@ $res[] = tableCreate('serversetup_localboot', " PRIMARY KEY (`systemmodel`) "); +// Add defaultentry override column +if (!tableHasColumn('serversetup_menu_location', 'defaultentryid')) { + if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL')) { + $res[] = UPDATE_DONE; + } else { + $res[] = UPDATE_FAILED; + } +} + $res[] = tableAddConstraint('serversetup_menu', 'defaultentryid', 'serversetup_menuentry', 'menuentryid', 'ON DELETE SET NULL'); @@ -62,6 +73,9 @@ $res[] = tableAddConstraint('serversetup_menuentry', 'menuid', 'serversetup_menu $res[] = tableAddConstraint('serversetup_menu_location', 'menuid', 'serversetup_menu', 'menuid', 'ON UPDATE CASCADE ON DELETE CASCADE'); +$res[] = tableAddConstraint('serversetup_menu_location', 'defaultentryid', 'serversetup_menuentry', 'menuentryid', + 'ON UPDATE CASCADE ON DELETE SET NULL'); + if (Module::get('location') !== false) { if (!tableExists('location')) { $res[] = UPDATE_RETRY; -- cgit v1.2.3-55-g7522 From bfe34ff61df650b02610767af673644144f015eb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 2 Jan 2019 15:26:04 +0100 Subject: [inc/Util] Improve prettyTime year cutoff logic Closes #3518 --- inc/util.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/util.inc.php b/inc/util.inc.php index 9f6f63db..1a5cbefe 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -495,7 +495,7 @@ SADFACE; settype($ts, 'int'); if ($ts === 0) return '???'; - static $TODAY = false, $ETODAY = false, $YESTERDAY = false, $YEAR = false; + static $TODAY = false, $ETODAY = false, $YESTERDAY = false, $YEARCUTOFF = false; if (!$ETODAY) $ETODAY = strtotime('today 23:59:59'); if ($ts > $ETODAY) // TODO: Do we need strings for future too? return date('d.m.Y H:i', $ts); @@ -505,8 +505,8 @@ SADFACE; if (!$YESTERDAY) $YESTERDAY = strtotime('yesterday 0:00'); if ($ts >= $YESTERDAY) return Dictionary::translate('lang_yesterday') . ' ' . date('H:i', $ts); - if (!$YEAR) $YEAR = strtotime('this year 1/1'); - if ($ts >= $YEAR) + if (!$YEARCUTOFF) $YEARCUTOFF = min(strtotime('-3 month'), strtotime('this year 1/1')); + if ($ts >= $YEARCUTOFF) return date('d.m. H:i', $ts); return date('d.m.Y', $ts); } -- cgit v1.2.3-55-g7522 From 9f2c37c52c64711b86ca53f910a465513df84ea6 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 8 Jan 2019 11:20:36 +0100 Subject: [locationinfo] make colors consistent for summary and default panel - black is now status 'offline' - grey is now status 'broken' --- modules-available/locationinfo/templates/frontend-summary.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules-available/locationinfo/templates/frontend-summary.html b/modules-available/locationinfo/templates/frontend-summary.html index ecb41467..95299e63 100644 --- a/modules-available/locationinfo/templates/frontend-summary.html +++ b/modules-available/locationinfo/templates/frontend-summary.html @@ -82,7 +82,8 @@ } .pc-offline { - background-color: darkgrey; + background-color: black; + color: white; } .pc-standby { @@ -91,8 +92,7 @@ .pc-broken { - background-color: black; - color: white; + background-color: darkgrey; border-radius: 0px 3px 3px 0px; } -- cgit v1.2.3-55-g7522 From ccd6ea5a851c5e3f46c10b06b32f852c36ec4dd1 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 8 Jan 2019 14:50:50 +0100 Subject: [locationinfo] set roomplanner default on false for existing panels - if panel is already existing without roomplanner value in database --- modules-available/locationinfo/inc/infopanel.inc.php | 3 +++ modules-available/locationinfo/page.inc.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules-available/locationinfo/inc/infopanel.inc.php b/modules-available/locationinfo/inc/infopanel.inc.php index 918030f0..fdc253f0 100644 --- a/modules-available/locationinfo/inc/infopanel.inc.php +++ b/modules-available/locationinfo/inc/infopanel.inc.php @@ -37,6 +37,9 @@ class InfoPanel $overrides = $json['overrides']; } unset($json['overrides']); + if (!isset($json['roomplanner'])) { + $config['roomplanner'] = false; + } $config = $json + $config; } } diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php index 13382438..8ea18940 100644 --- a/modules-available/locationinfo/page.inc.php +++ b/modules-available/locationinfo/page.inc.php @@ -902,6 +902,9 @@ class Page_LocationInfo extends Page } $config = json_decode($panel['panelconfig'], true); + if (!isset($config['roomplanner'])) { + $config['roomplanner'] = false; + } } // Permission -- cgit v1.2.3-55-g7522 From 856ff2fa8e9b103ee4033c8ceec3e80af87009bb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 11 Jan 2019 16:48:54 +0100 Subject: [serversetup-bwlp] Decouple location assigning from menu editing --- modules-available/serversetup-bwlp/install.inc.php | 3 +- modules-available/serversetup-bwlp/page.inc.php | 133 ++++++++++++++------- .../serversetup-bwlp/permissions/permissions.json | 3 + .../templates/menu-assign-location.html | 69 +++++++++++ .../serversetup-bwlp/templates/menu-edit.html | 51 ++------ 5 files changed, 169 insertions(+), 90 deletions(-) create mode 100644 modules-available/serversetup-bwlp/templates/menu-assign-location.html diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php index 67d6693f..25579c13 100644 --- a/modules-available/serversetup-bwlp/install.inc.php +++ b/modules-available/serversetup-bwlp/install.inc.php @@ -54,7 +54,8 @@ $res[] = tableCreate('serversetup_localboot', " // Add defaultentry override column if (!tableHasColumn('serversetup_menu_location', 'defaultentryid')) { - if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL')) { + if (Database::exec('ALTER TABLE serversetup_menu_location ADD COLUMN `defaultentryid` int(11) DEFAULT NULL, + ADD KEY `defaultentryid` (`defaultentryid`)') !== false) { $res[] = UPDATE_DONE; } else { $res[] = UPDATE_FAILED; diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index f8a21227..004077dc 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -82,6 +82,12 @@ class Page_ServerSetup extends Page $this->saveMenu(); } + if ($action === 'savelocation') { + // Permcheck in function + $this->saveLocationMenu(); + Util::redirect('?do=locations'); + } + if ($action === 'deleteMenu') { // Permcheck in function $this->deleteMenu(); @@ -158,6 +164,10 @@ class Page_ServerSetup extends Page User::assertPermission('edit.address'); $this->showEditAddress(); break; + case 'assignlocation': + // Permcheck in function + $this->showEditLocation(); + break; default: Util::redirect('?do=serversetup'); break; @@ -294,19 +304,6 @@ class Page_ServerSetup extends Page $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); // TODO: plainpass only when permissions } - // TODO: Make assigned locations editable - - $currentLocations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', array('menuid' => $id)); - $menu['locations'] = Location::getLocations($currentLocations); - - // if user has no permission to edit for this location, disable the location in the select - $allowedEditLocations = User::getAllowedLocations('ipxe.menu.edit'); - foreach ($menu['locations'] as &$loc) { - if (!in_array($loc["locationid"], $allowedEditLocations)) { - $loc["disabled"] = "disabled"; - } - } Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); Render::addTemplate('menu-edit', $menu); @@ -437,23 +434,6 @@ class Page_ServerSetup extends Page return; } - $locationids = Request::post('locations', [], "ARRAY"); - // check if the user is allowed to edit the menu on the affected locations - $allowedEditLocations = User::getAllowedLocations('ipxe.menu.edit'); - $currentLocations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', array('menuid' => $id)); - // permission denied if the user tries to assign or remove a menu to/from locations he has no edit rights for - // or if the user tries to save a menu without locations but does not have the permission for the root location (0) - if (!in_array(0, $allowedEditLocations) - && ( - (!empty(array_diff($locationids, $allowedEditLocations)) && !empty(array_diff($currentLocations, $allowedEditLocations))) - || empty($locationids) - ) - ) { - Message::addError('main.no-permission'); - Util::redirect('?do=serversetup'); - } - $insertParams = [ 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), @@ -462,18 +442,13 @@ class Page_ServerSetup extends Page Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); $menu['menuid'] = $id = Database::lastInsertId(); } else { - $menu = Database::queryFirst("SELECT m.menuid, GROUP_CONCAT(l.locationid) AS locations + $menu = Database::queryFirst("SELECT m.menuid FROM serversetup_menu m - LEFT JOIN serversetup_menu_location l USING (menuid) WHERE menuid = :id", compact('id')); if ($menu === false) { Message::addError('no-such-menu', $id); return; } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', 'TODO'); - return; - } $insertParams['menuid'] = $id; Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms WHERE menuid = :menuid', $insertParams); @@ -562,15 +537,6 @@ class Page_ServerSetup extends Page Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); } - Database::exec('DELETE FROM serversetup_menu_location WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - if (!empty($locationids)) { - Database::exec('DELETE FROM serversetup_menu_location WHERE locationid IN (:locationids)', ['locationids' => $locationids]); - foreach ($locationids as $locationid) { - Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid) VALUES (:menuid, :locationid)', - ['menuid' => $menu['menuid'], 'locationid' => $locationid]); - } - } - Message::addSuccess('menu-saved'); } @@ -653,4 +619,81 @@ class Page_ServerSetup extends Page Util::redirect('?do=serversetup&show=bootentry'); } + private function showEditLocation() + { + $locationId = Request::get('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + // List of menu entries + $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); + $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); + // List of menus + $data = [ + 'locationid' => $locationId, + 'locationName' => $loc['locationname'], + ]; + $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m + LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) + INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) + GROUP BY menuid + ORDER BY m.title ASC', ['locationid' => $locationId]); + $menus = []; + $hasDefault = false; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $eids = explode(',', $row['entries']); + $row['entries'] = []; + foreach ($eids as $eid) { + $row['entries'][] = [ + 'id' => $eid, + 'title' => $menuEntries[$eid], + 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), + ]; + } + if ($row['locationid'] !== null) { + $hasDefault = true; + $row['menu_selected'] = 'checked'; + } + $menus[] = $row; + } + if (!$hasDefault) { + $data['default_selected'] = 'checked'; + } + $data['list'] = $menus; + Render::addTemplate('menu-assign-location', $data); + } + + private function saveLocationMenu() + { + $locationId = Request::post('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + $menuId = Request::post('menuid', false, 'int'); + if ($menuId === 0) { + Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', + ['locationid' => $locationId]); + Message::addSuccess('location-use-default', $loc['locationname']); + return; + } + $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); + if ($defaultEntryId === 0) { + $defaultEntryId = null; + } + Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :defaultentryid) + ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ + 'menuid' => $menuId, + 'locationid' => $locationId, + 'defaultentryid' => $defaultEntryId + ]); + Message::addSuccess('location-menu-assigned', $loc['locationname']); + } + } diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json index aa2aa001..33cc9cea 100644 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ b/modules-available/serversetup-bwlp/permissions/permissions.json @@ -18,6 +18,9 @@ "location-aware": false }, "ipxe.menu.edit": { + "location-aware": false + }, + "ipxe.menu.assign": { "location-aware": true }, "ipxe.localboot.edit": { diff --git a/modules-available/serversetup-bwlp/templates/menu-assign-location.html b/modules-available/serversetup-bwlp/templates/menu-assign-location.html new file mode 100644 index 00000000..077d137e --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/menu-assign-location.html @@ -0,0 +1,69 @@ +

{{lang_assignMenuToLocation}}

+

{{locationName}}

+ +
+ + + + + + + + + + + + + + + + + + + {{#list}} + + + + + + {{/list}} + +
{{lang_menuTitle}}{{lang_menuEntryOverride}}
+
+ + +
+
+ {{lang_useDefaultMenu}} +
+
+ + +
+
+ {{title}} + + +
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html index 2141103f..21c6a30e 100644 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ b/modules-available/serversetup-bwlp/templates/menu-edit.html @@ -36,33 +36,18 @@ -
-
- -
-
- - {{#globalMenuWarning}} - - {{/globalMenuWarning}} -
-
- - - + + + - - - - + + + + @@ -289,28 +274,6 @@ var spacerText = "{{lang_spacer}}"; document.addEventListener("DOMContentLoaded", function() { - var locationSelect = $('#panel-locations'); - locationSelect.multiselect({numberDisplayed: 1}); - var globalMenuWarning = $('#global-menu-warning'); - if (globalMenuWarning.length) { - var saveButton = $('#save-button'); - if (locationSelect.val() !== null) { - saveButton.prop('disabled', false); - globalMenuWarning.hide(); - } else { - saveButton.prop('disabled', true); - globalMenuWarning.show(); - } - locationSelect.change(function () { - if ($(this).val() !== null) { - saveButton.prop('disabled', false); - globalMenuWarning.hide(); - } else { - saveButton.prop('disabled', true); - globalMenuWarning.show(); - } - }); - } function reassignSortValues() { var startValue = 1; -- cgit v1.2.3-55-g7522 From 59f6f89a2b7950e7b5fb4bdb0e2cdaf4998f1e9e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 11 Jan 2019 16:49:58 +0100 Subject: [locations] Show current boot menu per location, add edit button --- .../locations/lang/de/template-tags.json | 1 + .../locations/lang/en/template-tags.json | 1 + modules-available/locations/page.inc.php | 60 ++++++++++++++++------ .../locations/templates/locations.html | 17 +++++- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/modules-available/locations/lang/de/template-tags.json b/modules-available/locations/lang/de/template-tags.json index 43142555..96273ce4 100644 --- a/modules-available/locations/lang/de/template-tags.json +++ b/modules-available/locations/lang/de/template-tags.json @@ -3,6 +3,7 @@ "lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.", "lang_assignSubnetExplanation": "Rechner, die in einen der hier aufgef\u00fchrten Adressbereiche fallen, werden diesem Ort zugeschrieben und erhalten damit z.B. f\u00fcr diesen Raum angepasste Veranstaltungslisten.", "lang_assignedSubnets": "Zugeordnete Subnetze bzw. IP-Bereiche", + "lang_bootMenu": "Bootmen\u00fc", "lang_deleteChildLocations": "Untergeordnete Orte ebenfalls l\u00f6schen", "lang_deleteLocation": "Ort l\u00f6schen", "lang_deleteSubnet": "Bereich l\u00f6schen", diff --git a/modules-available/locations/lang/en/template-tags.json b/modules-available/locations/lang/en/template-tags.json index 41261726..64211e27 100644 --- a/modules-available/locations/lang/en/template-tags.json +++ b/modules-available/locations/lang/en/template-tags.json @@ -3,6 +3,7 @@ "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!", "lang_assignSubnetExplanation": "Client machines which fall into an IP range listed below will be assigned to this location and will see an according lecture list (e.g. they will see lectures that are exclusively assigned to this location).", "lang_assignedSubnets": "Assigned subnets \/ IP ranges", + "lang_bootMenu": "Boot menu", "lang_deleteChildLocations": "Delete child locations aswell", "lang_deleteLocation": "Delete location", "lang_deleteSubnet": "Delete range", diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php index 9beae163..2d8f5ff9 100644 --- a/modules-available/locations/page.inc.php +++ b/modules-available/locations/page.inc.php @@ -375,6 +375,11 @@ class Page_Locations extends Page } else { $locationList[$lid]['havestatistics'] = false; } + if (User::hasPermission('.serversetup.ipxe.menu.assign', $lid)) { + $visibleLocationIds[] = $lid; + } else { + $locationList[$lid]['haveipxe'] = false; + } if (!in_array($lid, $visibleLocationIds)) { unset($locationList[$lid]); } elseif (!in_array($lid, $allowedLocationIds)) { @@ -440,22 +445,7 @@ class Page_Locations extends Page $locationList[$locId] += array('configName' => $conf['title'], 'configClass' => 'slx-bold'); } } - $depth = array(); - foreach ($locationList as &$loc) { - $d = $loc['depth']; - if (!isset($loc['configName'])) { - // Has no explicit config assignment - if ($d === 0) { - $loc['configName'] = $defaultConfig; - } else { - $loc['configName'] = $depth[$d - 1]; - } - $loc['configClass'] = 'gray'; - } - $depth[$d] = $loc['configName']; - unset($depth[$d + 1]); - } - unset($loc); + $this->propagateFields($locationList, $defaultConfig, 'configName', 'configClass'); } // Count overridden config vars if (Module::get('baseconfig') !== false) { @@ -467,6 +457,24 @@ class Page_Locations extends Page $locationList[$lid]['overriddenVars'] = $row['cnt']; } } + // Confusing because the count might be inaccurate within a branch + //$this->propagateFields($locationList, '', 'overriddenVars', 'overriddenClass'); + } + // Show ipxe menu + if (Module::get('serversetup') !== false) { + $res = Database::simpleQuery("SELECT ml.locationid, m.title, ml.defaultentryid FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds')); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $lid = (int)$row['locationid']; + if (isset($locationList[$lid])) { + if ($row['defaultentryid'] !== null) { + $row['title'] .= '(*)'; + } + $locationList[$lid]['customMenu'] = $row['title']; + } + } + $this->propagateFields($locationList, '', 'customMenu', 'customMenuClass'); } $addAllowedLocs = User::getAllowedLocations("location.add"); @@ -484,6 +492,7 @@ class Page_Locations extends Page 'havestatistics' => Module::get('statistics') !== false, 'havebaseconfig' => Module::get('baseconfig') !== false, 'havesysconfig' => Module::get('sysconfig') !== false, + 'haveipxe' => Module::get('serversetup') !== false, 'overlapSelf' => $overlapSelf, 'overlapOther' => $overlapOther, 'haveOverlapSelf' => !empty($overlapSelf), @@ -637,4 +646,23 @@ class Page_Locations extends Page return $result; } + private function propagateFields(&$locationList, $defaultValue, $name, $class) + { + $depth = array(); + foreach ($locationList as &$loc) { + $d = $loc['depth']; + if (!isset($loc[$name])) { + // Has no explicit config assignment + if ($d === 0) { + $loc[$name] = $defaultValue; + } else { + $loc[$name] = $depth[$d - 1]; + } + $loc[$class] = 'gray'; + } + $depth[$d] = $loc[$name]; + unset($depth[$d + 1]); + } + } + } diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html index 67f22744..06d32020 100644 --- a/modules-available/locations/templates/locations.html +++ b/modules-available/locations/templates/locations.html @@ -37,6 +37,9 @@ + {{#list}} @@ -67,7 +70,7 @@ {{clientLoad}} {{/havestatistics}} - + {{/list}} {{#unassignedCount}} @@ -170,7 +183,7 @@ function slxOpenLocation(e, lid) { } return; } - var td = $('').attr('id', 'location-details-' + lid); tr.append(td); $(e).closest('tr').addClass('active slx-bold').after(tr); -- cgit v1.2.3-55-g7522 From 67312e7b378be5fb464f0748bbe949d977a0e894 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 11 Jan 2019 16:50:35 +0100 Subject: [serversetup-bwlp] Implement download page --- modules-available/serversetup-bwlp/page.inc.php | 40 ++++++++++++++++++++-- .../serversetup-bwlp/templates/download.html | 23 ++++++++----- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 004077dc..b9732d2b 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -176,8 +176,44 @@ class Page_ServerSetup extends Page private function showDownload() { - // TODO: Make nicer, support more variants (taskmanager-plugin) - Render::addTemplate('download'); + $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); + usort($list, function ($a, $b) { + return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); + }); + $files = []; + $strings = [ + 'efi' => [Dictionary::translate('dl-efi', true) => 50], + 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51], + 'usb' => [Dictionary::translate('dl-usb', true) => 80], + 'hd' => [Dictionary::translate('dl-hd', true) => 81], + 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82], + 'i386' => [Dictionary::translate('dl-i386', true) => 10], + 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11], + 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60], + 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61], + 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62], + 'snp' => [Dictionary::translate('dl-snp', true) => 63], + ]; + foreach ($list as $file) { + if ($file{0} === '.') + continue; + if (is_file($file)) { + $base = basename($file); + $features = []; + foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { + if (array_key_exists($p, $strings)) { + $features += $strings[$p]; + } + } + asort($features); + $files[] = [ + 'name' => $base, + 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', + 'features' => implode(', ', array_keys($features)), + ]; + } + } + Render::addTemplate('download', ['files' => $files]); } private function showBootentryList() diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html index 6752f7fc..bdb82470 100644 --- a/modules-available/serversetup-bwlp/templates/download.html +++ b/modules-available/serversetup-bwlp/templates/download.html @@ -1,12 +1,17 @@ -
{{lang_entryId}}{{lang_entryId}} {{lang_title}}{{lang_hotkey}}{{lang_password}}{{lang_hotkey}}{{lang_password}}
{{#havesysconfig}}{{lang_sysConfig}}{{/havesysconfig}} + {{#haveipxe}}{{lang_bootMenu}}{{/haveipxe}} +
+ {{#havebaseconfig}}
@@ -87,6 +90,16 @@ {{/havesysconfig}}
+ {{#haveipxe}} +
+ +
+ + {{customMenu}}   + + {{/haveipxe}} +
').attr('colspan', '5').css('padding', '0px 0px 12px'); + var td = $('').attr('colspan', '6').css('padding', '0px 0px 12px'); var tr = $('
{{#files}} -
  • {{name}} ({{features}})
  • + + + + + + {{/files}} - - - - {{lang_usbImgHelpBtn}} - +
    {{name}}{{size}}{{modified}}({{features}})
    +

    + + + {{lang_usbImgHelpBtn}} + +

    +

    + {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} +

    @@ -35,7 +45,7 @@ {{lang_usbImgHelpWindows}}

    - {{lang_downloadRufus}} + {{lang_downloadRufus}}

    -- cgit v1.2.3-55-g7522 From efdd92e2dec0c35f3b3c29cb69c6afe418287f1c Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 18 Jan 2019 16:36:50 +0100 Subject: [inc/Trigger] PHP 5.6 compat --- inc/trigger.inc.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php index 8a130aca..e89a9a17 100644 --- a/inc/trigger.inc.php +++ b/inc/trigger.inc.php @@ -23,12 +23,13 @@ class Trigger $hooks = Hook::load('ipxe-update'); $taskId = false; foreach ($hooks as $hook) { - $ret = (function($taskId) use ($hook) { + $ret = function($taskId) use ($hook) { $ret = include_once($hook->file); if (is_string($ret)) return $ret; return isset($taskId) ? $taskId : false; - })($taskId); + }; + $ret = $ret($taskId); if (is_string($ret)) { $taskId = $ret; } elseif (is_array($ret) && isset($ret['id'])) { -- cgit v1.2.3-55-g7522 From 63aa220d849dca2384773bf755358557a1d711c5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 18 Jan 2019 16:37:17 +0100 Subject: [serversetup-bwlp] Make localboot method configurable --- modules-available/serversetup-bwlp/api.inc.php | 23 ++----- .../serversetup-bwlp/inc/localboot.inc.php | 17 +++++ .../serversetup-bwlp/lang/de/messages.json | 2 + .../serversetup-bwlp/lang/de/module.json | 1 + .../serversetup-bwlp/lang/de/template-tags.json | 6 ++ modules-available/serversetup-bwlp/page.inc.php | 78 ++++++++++++++++++++++ .../serversetup-bwlp/templates/localboot.html | 59 ++++++++++++++++ 7 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 modules-available/serversetup-bwlp/inc/localboot.inc.php create mode 100644 modules-available/serversetup-bwlp/templates/localboot.html diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php index 4ed316a7..d089584e 100644 --- a/modules-available/serversetup-bwlp/api.inc.php +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -13,10 +13,9 @@ $product = Request::any('product', false, 'string'); $slxExtensions = Request::any('slx-extensions', false, 'int'); if ($platform === false || ($uuid === false && $product === false) || $slxExtensions === false) { - error_log(print_r($_SERVER, true)); - sleep(1); + // Redirect to self with added parameters $url = parse_url($_SERVER['REQUEST_URI']); - if (isset($_SERVER['SCRIPT_URI']) && preg_match('#(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) { + if (isset($_SERVER['SCRIPT_URI']) && preg_match('#^(\w+://[^/]+)#', $_SERVER['SCRIPT_URI'], $out)) { $urlbase = $out[1]; } elseif (isset($_SERVER['REQUEST_SCHEME']) && isset($_SERVER['SERVER_NAME'])) { $urlbase = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME']; @@ -62,11 +61,7 @@ HERE; } $platform = strtoupper($platform); -$BOOT_METHODS = [ - 'EXIT' => 'exit 1', - 'COMBOOT' => 'chain /tftp/chain.c32 hd0', - 'SANBOOT' => 'sanboot --no-describe', -]; +$BOOT_METHODS = Localboot::BOOT_METHODS; $ip = $_SERVER['REMOTE_ADDR']; if (substr($ip, 0, 7) === '::ffff:') { @@ -111,15 +106,9 @@ if ($model !== false) { } } if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { - $localboot = Property::get('serversetup.localboot', false); - if ($localboot === false) { - if ($platform === 'EFI') { - // It seems most (all) EFI platforms won't enumerate any drives in ipxe. - // No idea if this can be fixed in ipxe code in the future. - $localboot = 'EXIT'; - } else { - $localboot = 'SANBOOT'; - } + $localboot = Property::get('serversetup.localboot', 'AUTO'); + if (!isset($BOOT_METHODS[$localboot])) { + $localboot = 'AUTO'; } } if (isset($BOOT_METHODS[$localboot])) { diff --git a/modules-available/serversetup-bwlp/inc/localboot.inc.php b/modules-available/serversetup-bwlp/inc/localboot.inc.php new file mode 100644 index 00000000..a91d0547 --- /dev/null +++ b/modules-available/serversetup-bwlp/inc/localboot.inc.php @@ -0,0 +1,17 @@ + 'iseq EFI ${platform} && exit 1 || sanboot --no-describe', + 'EXIT' => 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', + ]; + + + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/lang/de/messages.json b/modules-available/serversetup-bwlp/lang/de/messages.json index 2bcaa391..de48ef0b 100644 --- a/modules-available/serversetup-bwlp/lang/de/messages.json +++ b/modules-available/serversetup-bwlp/lang/de/messages.json @@ -7,6 +7,8 @@ "invalid-boot-entry": "Ung\u00fcltiger Booteintrag: {{0}}", "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", "invalid-menu-id": "Ung\u00fcltige Men\u00fc-ID: {{0}}", + "localboot-invalid-method": "Ung\u00fcltige localboot-Methode: {{0}}", + "localboot-saved": "Einstellungen gespeichert", "location-menu-assigned": "{{0}} wurde ein Men\u00fc zugewiesen", "location-use-default": "{{0}} verwendet jetzt das Standardmen\u00fc", "menu-deleted": "Men\u00fc gel\u00f6scht", diff --git a/modules-available/serversetup-bwlp/lang/de/module.json b/modules-available/serversetup-bwlp/lang/de/module.json index e4c1ff4e..31d563f0 100644 --- a/modules-available/serversetup-bwlp/lang/de/module.json +++ b/modules-available/serversetup-bwlp/lang/de/module.json @@ -14,5 +14,6 @@ "submenu_address": "Server-Adresse", "submenu_bootentry": "Booteintr\u00e4ge verwalten", "submenu_download": "Downloads", + "submenu_localboot": "HDD-Boot", "submenu_menu": "Men\u00fcs verwalten" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index 9d64ebd9..2b68b3fb 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -19,6 +19,7 @@ "lang_bootentryTitle": "Booteintrag", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_commandLine": "Command line", + "lang_count": "Anzahl", "lang_customEntry": "Eigener Eintrag", "lang_downloadBootImage": "Boot-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", @@ -41,6 +42,10 @@ "lang_initRd": "Zu ladendes initramfs", "lang_isDefault": "Standard", "lang_listOfMenus": "Men\u00fcliste", + "lang_localBootDefault": "Standardm\u00e4\u00dfig verwendete Methode, um von Festplatte zu booten", + "lang_localBootExceptions": "Ausnahmen, pro Rechnermodell definierbar", + "lang_localBootHead": "Boot von Festplatte", + "lang_localBootIntro": "Aus dem iPXE Bootmen\u00fc kann auf verschiedene Arten ein Boot von der prim\u00e4ren Festplatte ausgel\u00f6st werden. In den allermeisten F\u00e4llen ist die Einstellung \"AUTO\" ausreichend, bei bestimmten Rechnermodellen kann es allerdings erforderlich sein, eine der alternativen Methoden zu erzwingen. Falls Sie einem solchen Modell begegnen, k\u00f6nnen Sie im unteren Teil dieser Seite eine solche Ausnahme festlegen. In einigen F\u00e4llen l\u00e4sst sich das Problem auch durch ein BIOS-Update auf den entsprechenden Ger\u00e4ten beheben.", "lang_localHDD": "Lokale HDD", "lang_locationCount": "Anzahl Orte", "lang_masterPassword": "Master-Passwort", @@ -60,6 +65,7 @@ "lang_newBootEntryHead": "Neuer Booteintrag", "lang_newMenu": "Neues Men\u00fc", "lang_none": "(keine)", + "lang_override": "\u00dcberschreiben", "lang_pxeBuilt": "PXE-Binary gebaut", "lang_recompileHint": "iPXE-Binaries jetzt neu kompilieren. Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen.", "lang_scriptContent": "Script", diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index a71e56ef..7766050b 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -88,6 +88,11 @@ class Page_ServerSetup extends Page Util::redirect('?do=locations'); } + if ($action === 'savelocalboot') { + User::assertPermission('ipxe.localboot.edit'); + $this->saveLocalboot(); + } + if ($action === 'deleteMenu') { // Permcheck in function $this->deleteMenu(); @@ -114,6 +119,9 @@ class Page_ServerSetup extends Page if (User::hasPermission('download')) { Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); } + if (User::hasPermission('ipxe.localboot.*')) { + Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); + } if (Request::get('show') === false) { $subs = Dashboard::getSubmenus(); if (empty($subs)) { @@ -168,6 +176,10 @@ class Page_ServerSetup extends Page // Permcheck in function $this->showEditLocation(); break; + case 'localboot': + User::assertPermission('ipxe.localboot.*'); + $this->showLocalbootConfig(); + break; default: Util::redirect('?do=serversetup'); break; @@ -218,6 +230,49 @@ class Page_ServerSetup extends Page Render::addTemplate('download', ['files' => $files]); } + private function makeSelectArray($list, $default) + { + $ret = []; + foreach (array_keys($list) as $k) { + $ret[] = [ + 'key' => $k, + 'selected' => ($k === $default ? 'selected' : ''), + ]; + } + return $ret; + } + + private function showLocalbootConfig() + { + // Default setting + $default = Property::get('serversetup.localboot', false); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + $default = 'AUTO'; + } + $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); + // Exceptions + $cutoff = strtotime('-90 days'); + $models = []; + $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( + SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 + WHERE m2.lastseen > :cutoff + GROUP BY systemmodel + ) m + LEFT JOIN serversetup_localboot sl USING (systemmodel) + ORDER BY systemmodel', ['cutoff' => $cutoff]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); + $models[] = $row; + } + // Output + $data = [ + 'default' => $default, + 'options' => $optionList, + 'exceptions' => $models, + ]; + Render::addTemplate('localboot', $data); + } + private function showBootentryList() { $allowEdit = User::hasPermission('ipxe.bootentry.edit'); @@ -734,4 +789,27 @@ class Page_ServerSetup extends Page Message::addSuccess('location-menu-assigned', $loc['locationname']); } + private function saveLocalboot() + { + $default = Request::post('default', 'AUTO', 'string'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + Message::addError('localboot-invalid-method', $default); + return; + } + $overrides = Request::post('override', [], 'array'); + Database::exec('TRUNCATE TABLE serversetup_localboot'); + foreach ($overrides as $model => $mode) { + if (empty($mode)) // No override + continue; + if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { + Message::addWarning('localboot-invalid-method', $mode); + continue; + } + Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) + VALUES (:model, :mode)', compact('model', 'mode')); + } + Message::addSuccess('localboot-saved'); + Util::redirect('?do=serversetup&show=localboot'); + } + } diff --git a/modules-available/serversetup-bwlp/templates/localboot.html b/modules-available/serversetup-bwlp/templates/localboot.html new file mode 100644 index 00000000..7000be37 --- /dev/null +++ b/modules-available/serversetup-bwlp/templates/localboot.html @@ -0,0 +1,59 @@ +

    {{lang_localBootHead}}

    + +

    {{lang_localBootIntro}}

    + +
    + + + + +
    +
    + + +
    + +
    +

    + {{lang_localBootExceptions}} +

    + + + + + + + {{#exceptions}} + + + + + + {{/exceptions}} +
    {{lang_name}}{{lang_count}}{{lang_override}}
    {{systemmodel}}{{cnt}} + +
    + +
    + + +
    + +
    -- cgit v1.2.3-55-g7522 From b0e0eddcfeb0f7d7cdde6caad21e3a0485797890 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 18 Jan 2019 16:49:46 +0100 Subject: [serversetup-bwlp] Link systemmodel to machine list --- modules-available/serversetup-bwlp/templates/localboot.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/serversetup-bwlp/templates/localboot.html b/modules-available/serversetup-bwlp/templates/localboot.html index 7000be37..960f463d 100644 --- a/modules-available/serversetup-bwlp/templates/localboot.html +++ b/modules-available/serversetup-bwlp/templates/localboot.html @@ -31,7 +31,7 @@
    {{systemmodel}}{{systemmodel}} {{cnt}} - + -- cgit v1.2.3-55-g7522 From d42a3b1f1d30d54f89ff4830163b00364f775e83 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 21 Jan 2019 12:19:35 +0100 Subject: [statistics] Improve ID44 filter matching --- modules-available/statistics/inc/filter.inc.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php index 565ea5f0..3ccea2c3 100644 --- a/modules-available/statistics/inc/filter.inc.php +++ b/modules-available/statistics/inc/filter.inc.php @@ -187,20 +187,24 @@ class Id44Filter extends Filter public function whereClause(&$args, &$joins) { global $SIZE_ID44; - $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100); - $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100); + if ($this->operator === '=' || $this->operator === '!=') { + $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100); + $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100); + } else { + $lower = $upper = round($this->argument * 1024); + } - if ($this->operator == '=') { + if ($this->operator === '=') { return " id44mb BETWEEN $lower AND $upper"; - } elseif ($this->operator == '!=') { + } elseif ($this->operator === '!=') { return " id44mb < $lower OR id44mb > $upper"; - } elseif ($this->operator == '<=') { - return " id44mb < $upper"; - } elseif ($this->operator == '>=') { - return " id44mb > $lower"; - } elseif ($this->operator == '<') { + } elseif ($this->operator === '<=') { + return " id44mb <= $upper"; + } elseif ($this->operator === '>=') { + return " id44mb >= $lower"; + } elseif ($this->operator === '<') { return " id44mb < $lower"; - } elseif ($this->operator == '>') { + } elseif ($this->operator === '>') { return " id44mb > $upper"; } else { error_log("unimplemented operator in Id44Filter: $this->operator"); -- cgit v1.2.3-55-g7522 From 54a516de8a49bdcdd2b95ecbfe2a365a860adaf4 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 21 Jan 2019 14:49:54 +0100 Subject: [statistics] Log if client seems to have crashed Log when we reset a client's state in the cron job, as well as if we receive a poweron event even though the state in the DB is still IDLE or OCCUPIED. --- modules-available/statistics/api.inc.php | 17 +++++++++++++++++ modules-available/statistics/hooks/cron.inc.php | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 8f5e9fd0..674cc48d 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -161,6 +161,11 @@ if ($type{0} === '~') { // Check for suspicious hardware changes if ($old !== false) { checkHardwareChange($old, $new); + + // Log potential crash + if ($old['state'] === 'IDLE' || $old['state'] === 'OCCUPIED') { + writeClientLog('machine-mismatch-poweron', 'Client sent poweron event, but previous known state is ' . $old['state']); + } } // Write statistics data @@ -403,6 +408,18 @@ function writeStatisticLog($type, $username, $data) )); } +function writeClientLog($type, $description) +{ + global $ip, $uuid; + Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( + 'type' => $type, + 'client' => $ip, + 'description' => $description, + 'longdesc' => '', + 'uuid' => $uuid, + )); +} + // For backwards compat, we require the . prefix if ($type{0} === '.') { diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index 4df7b0d4..f05762bc 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -23,6 +23,19 @@ function state_cleanup() // Fix online state of machines that crashed $standby = time() - 86400 * 2; // Reset standby machines after two days $on = time() - 610; // Reset others after ~10 minutes + // Query for logging + $res = Database::simpleQuery("SELECT machineuuid, clientip, state FROM machine WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) + VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( + 'type' => 'machine-mismatch-cron', + 'client' => $row['clientip'], + 'description' => 'Client timed out, last known state is ' . $row['state'], + 'longdesc' => '', + 'uuid' => $row['machineuuid'], + )); + } + // Update -- yes this is not atomic. Should be sufficient for simple warnings though. Database::exec("UPDATE machine SET state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); } -- cgit v1.2.3-55-g7522 From ed383e2562c6ecf219620f17f87eba3ddaf8d335 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 22 Jan 2019 12:27:45 +0100 Subject: [locationinfo] some design rework - Change "free" to "open" - h/min display in summary - do not display colors of unused states in summary - change seatcounter to emDash when room has current event --- modules-available/locationinfo/lang/de/template-tags.json | 3 ++- modules-available/locationinfo/lang/en/template-tags.json | 3 ++- .../locationinfo/templates/frontend-default.html | 11 ++++------- .../locationinfo/templates/frontend-summary.html | 9 +++++++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json index 03eb63a5..f9c7fad5 100644 --- a/modules-available/locationinfo/lang/de/template-tags.json +++ b/modules-available/locationinfo/lang/de/template-tags.json @@ -30,7 +30,8 @@ "lang_error": "Fehler", "lang_expertMode": "Expertenmodus", "lang_fourLocsHint": "Hier k\u00f6nnen Sie bis zu vier Orte ausw\u00e4hlen, die in diesem Panel angezeigt werden.", - "lang_free": "Frei", + "lang_free": "Geöffnet", + "lang_for": "für", "lang_general": "Allgemein", "lang_ignoreSslTooltip": "Akzeptiere ung\u00fcltige, abgelaufene oder selbstsignierte SSL-Zertifikate", "lang_insecureSsl": "Unsicheres SSL", diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json index 1b5ab0fd..5bbe3775 100644 --- a/modules-available/locationinfo/lang/en/template-tags.json +++ b/modules-available/locationinfo/lang/en/template-tags.json @@ -30,7 +30,8 @@ "lang_error": "Error", "lang_expertMode": "Expert mode", "lang_fourLocsHint": "You can pick up to four locations that will be shown in this panel.", - "lang_free": "Free", + "lang_free": "Open", + "lang_for": "for", "lang_general": "General", "lang_ignoreSslTooltip": "Accept invalid, expired or self-signed ssl certificates", "lang_insecureSsl": "Insecure SSL", diff --git a/modules-available/locationinfo/templates/frontend-default.html b/modules-available/locationinfo/templates/frontend-default.html index 4dee8ef7..cbb765d0 100755 --- a/modules-available/locationinfo/templates/frontend-default.html +++ b/modules-available/locationinfo/templates/frontend-default.html @@ -352,6 +352,7 @@ optional: {{lang_room}}{{lang_closed}}{{lang_free}} + {{lang_for}}{{lang_shortSun}}{{lang_shortMon}}{{lang_shortTue}} @@ -1151,7 +1152,7 @@ optional: */ function SetFreeSeats(room) { // if room has no allowed value, set text in the box to - - room.$.seatsCounter.text(room.freePcs >= 0 ? room.freePcs : '-'); + room.$.seatsCounter.text(room.freePcs >= 0 ? room.freePcs : '\u2014'); room.$.seatsCounter.data('state', JSON.stringify(room.state)); if (room.freePcs > 0 && room.state && room.state.free) { room.$.seatsBackground.css('background-color', '#250'); @@ -1174,8 +1175,6 @@ optional: var seats = room.freePcs; if (tmp.state === 'closed' || tmp.state === 'CalendarEvent' || tmp.state === 'Free') { newTime = GetTimeDiferenceAsString(tmp.end, MyDate(), globalConfig); - } else if (!same) { - newTime = ''; } if (tmp.state === "closed") { if (!same) newText = t("closed"); @@ -1183,16 +1182,14 @@ optional: if (!same) newText = tmp.title; // whilst event is running set freePcs to -, hopefully not breaking anything else with this room.freePcs = "-"; - } else if (tmp.state === "Free") { - if (!same) newText = t("free"); - } else if (tmp.state === "FreeNoEnd") { + } else if (tmp.state === "Free" || tmp.state === "FreeNoEnd") { if (!same) newText = t("free"); } if (newText !== false) { room.$.currentEvent.text(newText); } if (newTime !== false) { - room.$.currentRemain.text(newTime); + room.$.currentRemain.text(t("for") + " " +newTime); } if (room.lastFreeSeats !== seats || !same) { SetFreeSeats(room); diff --git a/modules-available/locationinfo/templates/frontend-summary.html b/modules-available/locationinfo/templates/frontend-summary.html index 95299e63..ae089da5 100644 --- a/modules-available/locationinfo/templates/frontend-summary.html +++ b/modules-available/locationinfo/templates/frontend-summary.html @@ -63,7 +63,7 @@ } .pc-idle, .pc-occupied, .pc-offline, .pc-broken, .pc-standby { - padding: 2px 1px; + padding: 2px 0px; text-align: center; font-size: 90%; font-weight: 800; @@ -604,7 +604,11 @@ // TODO: Add seconds again with a better update rate. var time_split = time.split(":"); if (time != "") { - $("#div_Time_" + id).text(time_split[0] + ":" + time_split[1]); + if (time_split[0] > 0) { + $("#div_Time_" + id).text(t("for") + " " + time_split[0] + "h " + time_split[1]+"min"); + } else { + $("#div_Time_" + id).text(t("for") + " " + time_split[1]+"min"); + } } else { $("#div_Time_" + id).text(time); } @@ -759,6 +763,7 @@ {{lang_room}}{{lang_closed}}{{lang_free}} + {{lang_for}}{{lang_shortSun}}{{lang_shortMon}}{{lang_shortTue}} -- cgit v1.2.3-55-g7522 From 0a040966751e4a45fc9b9ac6cbaaea100b38ce53 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 22 Jan 2019 16:12:28 +0100 Subject: [inc/Render] new optional argument to change rendered language --- inc/render.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/render.inc.php b/inc/render.inc.php index 4b1d3643..4da0567e 100644 --- a/inc/render.inc.php +++ b/inc/render.inc.php @@ -213,7 +213,7 @@ class Render * @param string $module name of module to load template from; defaults to currently active module * @return string Rendered template */ - public static function parse($template, $params = false, $module = false) + public static function parse($template, $params = false, $module = false, $lang = false) { if ($module === false && class_exists('Page')) { $module = Page::getModule()->getIdentifier(); @@ -228,7 +228,7 @@ class Render } // Now find all language tags in this array if (preg_match_all('/{{\s*(lang_.+?)\s*}}/', $html, $out) > 0) { - $dictionary = Dictionary::getArray($module, 'template-tags'); + $dictionary = Dictionary::getArray($module, 'template-tags', $lang); $fallback = false; foreach ($out[1] as $tag) { if ($fallback === false && empty($dictionary[$tag])) { -- cgit v1.2.3-55-g7522 From e4ff8937a3e0da4c23a97014ade75c15ae456931 Mon Sep 17 00:00:00 2001 From: Christian Hofmaier Date: Tue, 22 Jan 2019 16:13:26 +0100 Subject: [locationinfo] use set config language for panels --- modules-available/locationinfo/page.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php index 8ea18940..7be875d0 100644 --- a/modules-available/locationinfo/page.inc.php +++ b/modules-available/locationinfo/page.inc.php @@ -1002,7 +1002,7 @@ class Page_LocationInfo extends Page 'language' => $config['language'], ); - die(Render::parse('frontend-default', $data)); + die(Render::parse('frontend-default', $data, $module = false, $lang = $config['language'])); } if ($type === 'SUMMARY') { @@ -1014,7 +1014,7 @@ class Page_LocationInfo extends Page 'language' => $config['language'], ); - die(Render::parse('frontend-summary', $data)); + die(Render::parse('frontend-summary', $data, $module = false, $lang = $config['language'])); } http_response_code(500); -- cgit v1.2.3-55-g7522 From d809112f111e302f5a2be4c5b7eaa9b4e6293c44 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 24 Jan 2019 12:04:07 +0100 Subject: [serversetup-bwlp] Fix case of platform var --- modules-available/serversetup-bwlp/api.inc.php | 1 + modules-available/serversetup-bwlp/inc/localboot.inc.php | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php index 6da4c5df..1df0e6e7 100644 --- a/modules-available/serversetup-bwlp/api.inc.php +++ b/modules-available/serversetup-bwlp/api.inc.php @@ -59,6 +59,7 @@ goto retry HERE; exit; } +// ipxe has it lowercase, but we use uppercase $platform = strtoupper($platform); $BOOT_METHODS = Localboot::BOOT_METHODS; diff --git a/modules-available/serversetup-bwlp/inc/localboot.inc.php b/modules-available/serversetup-bwlp/inc/localboot.inc.php index a91d0547..3ab81862 100644 --- a/modules-available/serversetup-bwlp/inc/localboot.inc.php +++ b/modules-available/serversetup-bwlp/inc/localboot.inc.php @@ -6,12 +6,10 @@ class Localboot const PROPERTY_KEY = 'serversetup.localboot'; const BOOT_METHODS = [ - 'AUTO' => 'iseq EFI ${platform} && exit 1 || sanboot --no-describe', + 'AUTO' => 'iseq efi ${platform} && exit 1 || sanboot --no-describe', 'EXIT' => 'exit 1', 'COMBOOT' => 'chain /tftp/chain.c32 hd0', 'SANBOOT' => 'sanboot --no-describe', ]; - - } \ No newline at end of file -- cgit v1.2.3-55-g7522 From c124192e63bd8a48ed1e7caf9542dc52505dfd22 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 24 Jan 2019 12:17:07 +0100 Subject: [statistics] Handling of standby state in statistics, log crashes Or rather, not really crashes, but log whenever a client reports a poweron event without reporting a proper shutdown first. This isn't neccessarily a crash but could also be due to power loss, hard poweroff, or network failures. --- modules-available/statistics/api.inc.php | 102 +++++++++------------ modules-available/statistics/hooks/cron.inc.php | 20 +++- modules-available/statistics/inc/parser.inc.php | 10 +- .../statistics/inc/statistics.inc.php | 17 ++++ modules-available/statistics/page.inc.php | 29 +++++- 5 files changed, 107 insertions(+), 71 deletions(-) diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 674cc48d..813e7d54 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -123,32 +123,20 @@ if ($type{0} === '~') { } } // Maybe log old crashed session - if ($uptime < 120) { + if ($uptime < 150 && $old !== false) { // See if we have a lingering session, create statistic entry if so - if ($old !== false && $old['logintime'] !== 0) { + if ($old['state'] === 'OCCUPIED' && $old['logintime'] !== 0) { $sessionLength = $old['lastseen'] - $old['logintime']; if ($sessionLength > 30 && $sessionLength < 86400*2) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } // Write poweroff period length to statistic table - if ($old !== false && $old['lastseen'] !== 0) { + if ($old['lastseen'] !== 0) { $lastSeen = $old['lastseen']; $offtime = ($NOW - $uptime) - $lastSeen; - if ($offtime > 300 && $offtime < 86400 * 90) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:shutdown, '~offline-length', :uuid, :clientip, '', :length)", array( - 'shutdown' => $lastSeen, - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $offtime - )); + if ($offtime > 90 && $offtime < 86400 * 30) { + Statistics::logMachineState($uuid, $ip, $old['state'] === 'STANDBY' ? Statistics::SUSPEND_LENGTH : Statistics::OFFLINE_LENGTH, $lastSeen, $offtime); } } } @@ -173,36 +161,41 @@ if ($type{0} === '~') { } else if ($type === '~runstate') { // Usage (occupied/free) $sessionLength = 0; + $strUpdateBoottime = ''; if ($old === false) die("Unknown machine.\n"); if ($old['clientip'] !== $ip) { EventLog::warning("[runstate] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); die("Address changed.\n"); } $used = Request::post('used', 0, 'integer'); - if ($old['state'] === 'OFFLINE' && $NOW - $old['lastseen'] > 600) { - $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; - } else { - $strUpdateBoottime = ''; - } - // 1) Log last session length if we didn't see the machine for a while - if ($NOW - $old['lastseen'] > 610 && $old['lastseen'] !== 0) { - // Old session timed out - might be caused by hard reboot - if ($old['logintime'] !== 0) { - if ($old['lastseen'] > $old['logintime']) { - $sessionLength = $old['lastseen'] - $old['logintime']; - } - $old['logintime'] = 0; - } - } - // Figure out what's happening - state changes $params = array( 'uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'], ); + if ($old['state'] === 'OFFLINE') { + // This should never happen -- we expect a poweron event before runstate, which would set the state to IDLE + // So it might be that the poweron event got lost, or that a couple of runstate events got lost, which + // caused our cron.inc.php to time out the client and reset it to OFFLINE + if ($NOW - $old['lastseen'] > 900) { + $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; + } + // 1) Log last session length if we didn't see the machine for a while + if ($NOW - $old['lastseen'] > 900 && $old['lastseen'] !== 0) { + // Old session timed out - might be caused by hard reboot + if ($old['logintime'] !== 0) { + if ($old['lastseen'] > $old['logintime']) { + $sessionLength = $old['lastseen'] - $old['logintime']; + } + } + } + } + // Figure out what's happening - state changes if ($used === 0 && $old['state'] !== 'IDLE') { - // Is not in use, was in use before - $sessionLength = $NOW - $old['logintime']; + if ($old['state'] === 'OCCUPIED' && $sessionLength === 0) { + // Is not in use, was in use before + $sessionLength = $NOW - $old['logintime']; + } $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime . " logintime = 0, currentuser = NULL, state = 'IDLE' " @@ -232,13 +225,7 @@ if ($type{0} === '~') { } // 9) Log last session length if applicable if ($mode === false && $sessionLength > 0 && $sessionLength < 86400*2 && $old['logintime'] !== 0) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } elseif ($type === '~poweroff') { if ($old === false) die("Unknown machine.\n"); @@ -246,16 +233,10 @@ if ($type{0} === '~') { EventLog::warning("[poweroff] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); die("Address changed.\n"); } - if ($mode === false && $old['logintime'] !== 0) { + if ($mode === false && $old['state'] === 'OCCUPIED' && $old['logintime'] !== 0) { $sessionLength = $old['lastseen'] - $old['logintime']; if ($sessionLength > 0 && $sessionLength < 86400*2) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } Database::exec("UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), state = 'OFFLINE' @@ -368,13 +349,7 @@ if ($type{0} === '~') { $lastSeen = $old['lastseen']; $duration = $NOW - $lastSeen; if ($duration > 500 && $duration < 86400 * 14) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:suspend, '~suspend-length', :uuid, :clientip, '', :length)", array( - 'suspend' => $lastSeen, - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $duration - )); + Statistics::logMachineState($uuid, $ip, Statistics::SUSPEND_LENGTH, $lastSeen, $duration); } } } else { @@ -449,9 +424,14 @@ if ($type{0} === '.') { function checkHardwareChange($old, $new) { if ($new['mbram'] !== 0) { - if ($new['mbram'] + 1000 < $old['mbram']) { - $ram1 = round($old['mbram'] / 512) / 2; - $ram2 = round($new['mbram'] / 512) / 2; + if ($new['mbram'] < 6200) { + $ram1 = ceil($old['mbram'] / 512) / 2; + $ram2 = ceil($new['mbram'] / 512) / 2; + } else { + $ram1 = ceil($old['mbram'] / 1024); + $ram2 = ceil($new['mbram'] / 1024); + } + if ($ram1 !== $ram2) { EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM decreased from {$ram1}GB to {$ram2}GB"); } if (!empty($old['cpumodel']) && !empty($new['cpumodel']) && $new['cpumodel'] !== $old['cpumodel']) { diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index f05762bc..0b8e740e 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -21,10 +21,11 @@ function logstats() function state_cleanup() { // Fix online state of machines that crashed - $standby = time() - 86400 * 2; // Reset standby machines after two days + $standby = time() - 86400 * 4; // Reset standby machines after four days $on = time() - 610; // Reset others after ~10 minutes // Query for logging - $res = Database::simpleQuery("SELECT machineuuid, clientip, state FROM machine WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + $res = Database::simpleQuery("SELECT machineuuid, clientip, state, logintime, lastseen FROM machine + WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( @@ -34,9 +35,20 @@ function state_cleanup() 'longdesc' => '', 'uuid' => $row['machineuuid'], )); + if ($row['state'] === 'OCCUPIED') { + $length = $row['lastseen'] - $row['logintime']; + if ($length > 0 && $length < 86400 * 7) { + Statistics::logMachineState($row['machineuuid'], $row['clientip'], Statistics::SESSION_LENGTH, $row['logintime'], $length); + } + } elseif ($row['state'] === 'STANDBY') { + $length = time() - $row['lastseen']; + if ($length > 0 && $length < 86400 * 7) { + Statistics::logMachineState($row['machineuuid'], $row['clientip'], Statistics::SUSPEND_LENGTH, $row['lastseen'], $length); + } + } } - // Update -- yes this is not atomic. Should be sufficient for simple warnings though. - Database::exec("UPDATE machine SET state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + // Update -- yes this is not atomic. Should be sufficient for simple warnings and bookkeeping though. + Database::exec("UPDATE machine SET logintime = 0, state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); } state_cleanup(); diff --git a/modules-available/statistics/inc/parser.inc.php b/modules-available/statistics/inc/parser.inc.php index b179b4a3..0d39079d 100644 --- a/modules-available/statistics/inc/parser.inc.php +++ b/modules-available/statistics/inc/parser.inc.php @@ -104,10 +104,12 @@ class Parser { foreach ($lines as $line) { if (preg_match('/^Disk (\S+):.* (\d+) bytes/i', $line, $out)) { // --- Beginning of MBR disk --- + unset($hdd); if ($out[2] < 10000) // sometimes vmware reports lots of 512byte disks continue; + if (substr($out[1], 0, 8) === '/dev/dm-') // Ignore device mapper + continue; // disk total size and name - unset($hdd); $mbrToMbFactor = 0; // This is != 0 for mbr $sectorToMbFactor = 0; // This is != for gpt $hdd = array( @@ -122,10 +124,12 @@ class Parser { $hdds[] = &$hdd; } elseif (preg_match('/^Disk (\S+):\s+(\d+)\s+sectors,/i', $line, $out)) { // --- Beginning of GPT disk --- + unset($hdd); if ($out[2] < 1000) // sometimes vmware reports lots of 512byte disks continue; + if (substr($out[1], 0, 8) === '/dev/dm-') // Ignore device mapper + continue; // disk total size and name - unset($hdd); $mbrToMbFactor = 0; // This is != 0 for mbr $sectorToMbFactor = 0; // This is != for gpt $hdd = array( @@ -213,7 +217,7 @@ class Parser { $hdd['size'] = round(($hdd['sectors'] * $sectorToMbFactor) / 1024); } $free = $hdd['size'] - $hdd['used']; - if ($free > 5 || ($free / $hdd['size']) > 0.1) { + if ($hdd['size'] > 0 && ($free > 5 || ($free / $hdd['size']) > 0.1)) { $hdd['partitions'][] = array( 'id' => 'free-id-' . $i, 'name' => Dictionary::translate('unused'), diff --git a/modules-available/statistics/inc/statistics.inc.php b/modules-available/statistics/inc/statistics.inc.php index 2500f16f..1f8a081a 100644 --- a/modules-available/statistics/inc/statistics.inc.php +++ b/modules-available/statistics/inc/statistics.inc.php @@ -70,4 +70,21 @@ class Statistics return $list; } + const SESSION_LENGTH = '~session-length'; + const OFFLINE_LENGTH = '~offline-length'; + const SUSPEND_LENGTH = '~suspend-length'; + + public static function logMachineState($uuid, $ip, $type, $start, $length, $username = '') + { + return Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' + . " VALUES (:start, :type, :uuid, :clientip, :username, :length)", array( + 'start' => $start, + 'type' => $type, + 'uuid' => $uuid, + 'clientip' => $ip, + 'username' => $username, + 'length' => $length, + )); + } + } diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index abacc8d2..2d86615e 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -910,16 +910,18 @@ class Page_Statistics extends Page //if ($cutoff < $client['firstseen']) $cutoff = $client['firstseen']; $scale = 100 / ($NOW - $cutoff); $res = Database::simpleQuery('SELECT dateline, typeid, data FROM statistic' - . " WHERE dateline > :cutoff AND typeid IN ('~session-length', '~offline-length') AND machineuuid = :uuid ORDER BY dateline ASC", array( + . " WHERE dateline > :cutoff AND typeid IN (:sessionLength, :offlineLength) AND machineuuid = :uuid ORDER BY dateline ASC", array( 'cutoff' => $cutoff - 86400 * 14, 'uuid' => $uuid, + 'sessionLength' => Statistics::SESSION_LENGTH, + 'offlineLength' => Statistics::OFFLINE_LENGTH, )); $spans['rows'] = array(); $spans['graph'] = ''; $last = false; $first = true; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if (!$client['isclient'] && $row['typeid'] === '~session-length') + if (!$client['isclient'] && $row['typeid'] === Statistics::SESSION_LENGTH) continue; // Don't differentiate between session and idle for non-clients if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) { // Special case: offline before @@ -945,9 +947,12 @@ class Page_Statistics extends Page } $row['from'] = Util::prettyTime($row['dateline']); $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']); - if ($row['typeid'] === '~offline-length') { + if ($row['typeid'] === Statistics::OFFLINE_LENGTH) { $row['glyph'] = 'off'; $color = '#444'; + } elseif ($row['typeid'] === Statistics::SUSPEND_LENGTH) { + $row['glyph'] = 'pause'; + $color = '#686'; } else { $row['glyph'] = 'user'; $color = '#e77'; @@ -967,8 +972,26 @@ class Page_Statistics extends Page } if ($client['state'] === 'OCCUPIED') { $spans['graph'] .= '
     
    '; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['logintime']), + 'duration' => '-', + 'glyph' => 'user', + ]; + $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']); } elseif ($client['state'] === 'OFFLINE') { $spans['graph'] .= '
     
    '; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['lastseen']), + 'duration' => '-', + 'glyph' => 'off', + ]; + } elseif ($client['state'] === 'STANDBY') { + $spans['graph'] .= '
     
    '; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['lastseen']), + 'duration' => '-', + 'glyph' => 'pause', + ]; } $t = explode('-', date('Y-n-j-G', $cutoff)); if ($t[3] >= 8 && $t[3] <= 22) { -- cgit v1.2.3-55-g7522 From ab6171159d5471ea1d4c1be0736ef8db230f5dcc Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 24 Jan 2019 14:00:31 +0100 Subject: [default.css] Make slx-table columns a bit wider --- style/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style/default.css b/style/default.css index 6b0c28f6..6c2beb86 100644 --- a/style/default.css +++ b/style/default.css @@ -76,7 +76,7 @@ body { } .slx-table td { - padding-right: 5px; + padding-right: 7px; padding-bottom: 2px; } -- cgit v1.2.3-55-g7522 From 9ae2c70073490a253ccf2fcb567d57b07811389b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 24 Jan 2019 17:31:38 +0100 Subject: [statistics] Minor refactoring (PHP >= 5.6) --- modules-available/statistics/inc/filter.inc.php | 16 ++++--- modules-available/statistics/page.inc.php | 58 ++++++++++++------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php index 3ccea2c3..46de467b 100644 --- a/modules-available/statistics/inc/filter.inc.php +++ b/modules-available/statistics/inc/filter.inc.php @@ -14,6 +14,13 @@ class Filter public $operator; public $argument; + private static $keyCounter = 0; + + public static function getNewKey($colname) + { + return $colname . '_' . (self::$keyCounter++); + } + public function __construct($column, $operator, $argument = null) { $this->column = trim($column); @@ -24,8 +31,7 @@ class Filter /* returns a where clause and adds needed operators to the passed array */ public function whereClause(&$args, &$joins) { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); $addendum = ''; /* check if we have to do some parsing*/ @@ -226,8 +232,7 @@ class StateFilter extends Filter $map = [ 'on' => ['IDLE', 'OCCUPIED'], 'off' => ['OFFLINE'], 'idle' => ['IDLE'], 'occupied' => ['OCCUPIED'], 'standby' => ['STANDBY'] ]; $neg = $this->operator == '!=' ? 'NOT ' : ''; if (array_key_exists($this->argument, $map)) { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); $args[$key] = $map[$this->argument]; return " machine.state $neg IN ( :$key ) "; } else { @@ -259,8 +264,7 @@ class LocationFilter extends Filter if ($this->argument === 0) { return "machine.locationid IS $neg NULL"; } else { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); if ($recursive) { $args[$key] = array_keys(Location::getRecursiveFlat($this->argument)); } else { diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index 2d86615e..c9a0cac5 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -1,7 +1,6 @@ =', '=', '<', '>']; + const OP_STRCMP = ['!~', '~', '=', '!=']; public static $columns; private $query; @@ -27,123 +26,120 @@ class Page_Statistics extends Page */ private $haveSubpage; - /* PHP sucks, no static, const array definitions... Or am I missing something? */ - public function initConstants() + /** + * Do this here instead of const since we need to check for available modules while building array. + */ + public static function initConstants() { - Page_Statistics::$op_nominal = ['!=', '=']; - Page_Statistics::$op_ordinal = ['!=', '<=', '>=', '=', '<', '>']; - Page_Statistics::$op_stringcmp = ['!~', '~', '=', '!=']; Page_Statistics::$columns = [ 'machineuuid' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true, ], 'macaddr' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true, ], 'firstseen' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'lastseen' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'logintime' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'realcores' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true, ], 'systemmodel' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true, ], 'cpumodel' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true, ], 'hddgb' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => false, 'map_sort' => 'id44mb' ], 'gbram' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'map_sort' => 'mbram', 'column' => false, ], 'kvmstate' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'enum', 'column' => true, 'values' => ['ENABLED', 'DISABLED', 'UNSUPPORTED'] ], 'badsectors' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true ], 'clientip' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true ], 'hostname' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true ], 'subnet' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => false ], 'currentuser' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true ], 'state' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'enum', 'column' => true, 'values' => ['occupied', 'on', 'off', 'idle', 'standby'] ], 'runtime' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'int', 'column' => true ], ]; if (Module::isAvailable('locations')) { Page_Statistics::$columns['location'] = [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'enum', 'column' => false, 'values' => array_keys(Location::getLocationsAssoc()), ]; } - /* TODO ... */ } protected function doPreprocess() { - $this->initConstants(); User::load(); if (!User::isLoggedIn()) { Message::addError('main.no-permission'); @@ -1127,3 +1123,5 @@ class Page_Statistics extends Page ), true); } } + +Page_Statistics::initConstants(); -- cgit v1.2.3-55-g7522 From 54a0a4c9b920bce1dc4e93d2cf05ebbea6502be0 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 28 Jan 2019 13:48:03 +0100 Subject: [inc/Util] readableFileSize: support input in KB/MB/... --- inc/util.inc.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/util.inc.php b/inc/util.inc.php index 1a5cbefe..e459cc46 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -234,9 +234,10 @@ SADFACE; * * @param float|int $bytes numeric value of the filesize to make readable * @param int $decimals number of decimals to show, -1 for automatic + * @param int $shift how many units to skip, i.e. if you pass in KiB or MiB * @return string human readable string representing the given file size */ - public static function readableFileSize($bytes, $decimals = -1) + public static function readableFileSize($bytes, $decimals = -1, $shift = 0) { $bytes = round($bytes); static $sz = array('Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); @@ -249,7 +250,7 @@ SADFACE; $decimals = 2 - floor(strlen((int)$bytes) - 1); } } - return sprintf("%.{$decimals}f", $bytes) . "\xe2\x80\x89" . $sz[$factor]; + return sprintf("%.{$decimals}f", $bytes) . "\xe2\x80\x89" . $sz[$factor + $shift]; } public static function sanitizeFilename($name) -- cgit v1.2.3-55-g7522 From 3b1df888c963d906005ff139892df06a0d1ae2b4 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 28 Jan 2019 13:48:35 +0100 Subject: [statistics] Track and display memory/tmp usage of clients --- modules-available/statistics/api.inc.php | 13 ++++++-- modules-available/statistics/hooks/cron.inc.php | 9 ++++-- modules-available/statistics/install.inc.php | 17 ++++++++++ .../statistics/lang/de/template-tags.json | 4 +++ .../statistics/lang/en/template-tags.json | 4 +++ modules-available/statistics/page.inc.php | 37 ++++++++++++++++------ modules-available/statistics/style.css | 32 +++++++++++++++++++ .../statistics/templates/filterbox.html | 5 ++- .../statistics/templates/machine-main.html | 33 ++++++++++++++++--- 9 files changed, 135 insertions(+), 19 deletions(-) diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 813e7d54..57c4cee2 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -34,7 +34,8 @@ if ($type{0} === '~') { // External mode of operation? $mode = Request::post('mode', false, 'string'); $NOW = time(); - $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); + $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel, live_memfree, live_swapfree, live_tmpfree + FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($old !== false) { settype($old['logintime'], 'integer'); settype($old['lastseen'], 'integer'); @@ -114,6 +115,7 @@ if ($type{0} === '~') { . ' cpumodel = :cpumodel,' . ' systemmodel = :systemmodel,' . ' id44mb = :id44mb,' + . ' live_tmpsize = 0, live_swapsize = 0, live_memsize = 0,' . ' badsectors = :badsectors,' . ' data = :data,' . ' state = :state ' @@ -152,7 +154,10 @@ if ($type{0} === '~') { // Log potential crash if ($old['state'] === 'IDLE' || $old['state'] === 'OCCUPIED') { - writeClientLog('machine-mismatch-poweron', 'Client sent poweron event, but previous known state is ' . $old['state']); + writeClientLog('machine-mismatch-poweron', 'Poweron event, but previous known state is ' . $old['state'] + . '. RAM: ' . Util::readableFileSize($old['live_memfree'], -1, 2) + . ', Swap: ' . Util::readableFileSize($old['live_swapfree'], -1, 2) + . ', ID44: ' . Util::readableFileSize($old['live_memfree'], -1, 2)); } } @@ -190,6 +195,10 @@ if ($type{0} === '~') { } } } + foreach (['memsize', 'tmpsize', 'swapsize', 'memfree', 'tmpfree', 'swapfree'] as $item) { + $strUpdateBoottime .= ' live_' . $item . ' = :_' . $item . ', '; + $params['_' . $item] = ceil(Request::post($item, 0, 'int') / 1024); + } // Figure out what's happening - state changes if ($used === 0 && $old['state'] !== 'IDLE') { if ($old['state'] === 'OCCUPIED' && $sessionLength === 0) { diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index 0b8e740e..f22d0475 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -24,14 +24,17 @@ function state_cleanup() $standby = time() - 86400 * 4; // Reset standby machines after four days $on = time() - 610; // Reset others after ~10 minutes // Query for logging - $res = Database::simpleQuery("SELECT machineuuid, clientip, state, logintime, lastseen FROM machine - WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + $res = Database::simpleQuery("SELECT machineuuid, clientip, state, logintime, lastseen, live_memfree, live_swapfree, live_tmpfree + FROM machine WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( 'type' => 'machine-mismatch-cron', 'client' => $row['clientip'], - 'description' => 'Client timed out, last known state is ' . $row['state'], + 'description' => 'Client timed out, last known state is ' . $row['state'] + . '. RAM: ' . Util::readableFileSize($row['live_memfree'], -1, 2) + . ', Swap: ' . Util::readableFileSize($row['live_swapfree'], -1, 2) + . ', ID44: ' . Util::readableFileSize($row['live_memfree'], -1, 2), 'longdesc' => '', 'uuid' => $row['machineuuid'], )); diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php index 4e2dfcca..84e038a4 100644 --- a/modules-available/statistics/install.inc.php +++ b/modules-available/statistics/install.inc.php @@ -234,5 +234,22 @@ if (!tableHasColumn('machine', 'state')) { $res[] = UPDATE_DONE; } +// 2019-01-25: Add memory/temp stats column +if (!tableHasColumn('machine', 'live_tmpsize')) { + $ret = Database::exec("ALTER TABLE `machine` + ADD COLUMN `live_tmpsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id44mb`, + ADD COLUMN `live_tmpfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_tmpsize`, + ADD COLUMN `live_swapsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_tmpfree`, + ADD COLUMN `live_swapfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_swapsize`, + ADD COLUMN `live_memsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_swapfree`, + ADD COLUMN `live_memfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_memsize`, + ADD INDEX `live_tmpfree` (`live_tmpfree`), + ADD INDEX `live_memfree` (`live_memfree`)"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, 'Adding state column to machine table failed: ' . Database::lastError()); + } + $res[] = UPDATE_DONE; +} + // Create response responseFromArray($res); diff --git a/modules-available/statistics/lang/de/template-tags.json b/modules-available/statistics/lang/de/template-tags.json index 84c4690c..2567eea1 100644 --- a/modules-available/statistics/lang/de/template-tags.json +++ b/modules-available/statistics/lang/de/template-tags.json @@ -14,6 +14,7 @@ "lang_event": "Ereignis", "lang_eventType": "Typ", "lang_firstSeen": "Erste Aktivit\u00e4t", + "lang_free": "frei", "lang_gbRam": "RAM", "lang_hardwareSummary": "Hardware", "lang_hdds": "Festplatten", @@ -40,6 +41,7 @@ "lang_machineStandby": "Im Standby", "lang_machineSummary": "Zusammenfassung", "lang_maximumAbbrev": "Max.", + "lang_memFree": "RAM frei (MB)", "lang_memoryStats": "Arbeitsspeicher", "lang_model": "Modell", "lang_modelCount": "Anzahl", @@ -82,10 +84,12 @@ "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.", "lang_sureReplaceNoUndo": "Wollen Sie die Daten ausgew\u00e4hlten Rechner \u00fcbertragen? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.", + "lang_swapFree": "swap frei (MB)", "lang_tempPart": "Temp. Partition", "lang_tempPartStats": "Tempor\u00e4re Partition", "lang_thoseAreProjectors": "Diese Modellnamen werden als Beamer behandelt, auch wenn die EDID-Informationen des Ger\u00e4tes anderes berichten.", "lang_timebarDesc": "Visuelle Darstellung der letzten Tage. Rote Abschnitte zeigen, wann der Rechner belegt war, gr\u00fcne, wann er nicht verwendet wurde, aber eingeschaltet war. Die leicht abgedunkelten Abschnitte markieren N\u00e4chte (22 bis 8 Uhr).", + "lang_tmpFree": "ID44 frei (MB)", "lang_tmpGb": "Temp-HDD", "lang_total": "Gesamt", "lang_usageDetails": "Nutzungsdetails", diff --git a/modules-available/statistics/lang/en/template-tags.json b/modules-available/statistics/lang/en/template-tags.json index b064ee50..1d9cd4da 100644 --- a/modules-available/statistics/lang/en/template-tags.json +++ b/modules-available/statistics/lang/en/template-tags.json @@ -14,6 +14,7 @@ "lang_event": "Event", "lang_eventType": "Type", "lang_firstSeen": "First seen", + "lang_free": "free", "lang_gbRam": "RAM", "lang_hardwareSummary": "Hardware", "lang_hdds": "Hard disk drives", @@ -40,6 +41,7 @@ "lang_machineStandby": "In standby mode", "lang_machineSummary": "Summary", "lang_maximumAbbrev": "max.", + "lang_memFree": "RAM free (MB)", "lang_memoryStats": "Memory", "lang_model": "Model", "lang_modelCount": "Count", @@ -82,10 +84,12 @@ "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.", "lang_sureReplaceNoUndo": "Are you sure you want to replace the selected machine pairs? This action cannot be undone.", + "lang_swapFree": "swap free (MB)", "lang_tempPart": "Temp. partition", "lang_tempPartStats": "Temporary partition", "lang_thoseAreProjectors": "These model names will always be treated as beamers, even if the device's EDID data says otherwise.", "lang_timebarDesc": "Visual representation of the last few days. Red parts mark periods where the client was occupied, green parts where the client was idle. Dimmed parts mark nights (10pm to 8am).", + "lang_tmpFree": "ID44 free (MB)", "lang_tmpGb": "Temp HDD", "lang_total": "Total", "lang_usageDetails": "Detailed usage", diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index c9a0cac5..a9cde6fb 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -122,8 +122,18 @@ class Page_Statistics extends Page 'column' => true, 'values' => ['occupied', 'on', 'off', 'idle', 'standby'] ], - 'runtime' => [ - 'op' => Page_Statistics::OP_NOMINAL, + 'live_swapfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, + 'type' => 'int', + 'column' => true + ], + 'live_memfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, + 'type' => 'int', + 'column' => true + ], + 'live_tmpfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true ], @@ -764,9 +774,9 @@ class Page_Statistics extends Page $row['currentsession'] = $lecture['displayname']; $row['lectureid'] = $lecture['lectureid']; } + $row['session'] = $row['currentsession']; + return; } - $row['session'] = $row['currentsession']; - return; } $res = Database::simpleQuery('SELECT dateline, username, data FROM statistic' . " WHERE clientip = :ip AND typeid = '.vmchooser-session-name'" @@ -783,14 +793,17 @@ class Page_Statistics extends Page } if ($session !== false) { $row['session'] = $session['data']; - $row['username'] = $session['username']; + if (empty($row['currentuser'])) { + $row['username'] = $session['username']; + } } } private function showMachine($uuid) { - $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state,' - . ' mbram, kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', + $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state, + mbram, live_tmpsize, live_tmpfree, live_swapsize, live_swapfree, live_memsize, live_memfree, + kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($client === false) { Message::addError('unknown-machine', $uuid); @@ -826,6 +839,7 @@ class Page_Statistics extends Page $client['state_' . $client['state']] = true; $client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']); $client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']); + $client['logintime_s'] = date('d.m.Y H:i', $client['logintime']); if ($client['lastboot'] == 0) { $client['lastboot_s'] = '-'; } else { @@ -835,9 +849,14 @@ class Page_Statistics extends Page $client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')'; } } - $client['logintime_s'] = date('d.m.Y H:i', $client['logintime']); - $client['gbram'] = round(round($client['mbram'] / 500) / 2, 1); + $client['gbram'] = round(ceil($client['mbram'] / 512) / 2, 1); $client['gbtmp'] = round($client['id44mb'] / 1024); + foreach (['tmp', 'swap', 'mem'] as $item) { + if ($client['live_' . $item . 'size'] == 0) + continue; + $client['live_' . $item . 'percent'] = round(($client['live_' . $item . 'free'] / $client['live_' . $item . 'size']) * 100, 2); + $client['live_' . $item . 'free_s'] = Util::readableFileSize($client['live_' . $item . 'free'], -1, 2); + } $client['ramclass'] = $this->ramColorClass($client['mbram']); $client['kvmclass'] = $this->kvmColorClass($client['kvmstate']); $client['hddclass'] = $this->hddColorClass($client['gbtmp']); diff --git a/modules-available/statistics/style.css b/modules-available/statistics/style.css index 1496ac87..c48275ba 100644 --- a/modules-available/statistics/style.css +++ b/modules-available/statistics/style.css @@ -8,4 +8,36 @@ border-radius: 25px; font-size: 20px; line-height: 40px; +} + +.meter { + position: relative; + border-radius: 5px; + border: 1px solid #999; + background: linear-gradient(to right, #9e6 0%, #fb8 66%, #f32 100%); + height: 1.25em; + padding: 0; + width: 100%; + overflow: hidden; +} + +.meter .bar { + position: absolute; + top: 0; + height: 100%; + right: 0; + background: #efe; + display: inline-block; + padding: 0; + margin: 0 0 0 auto; +} + +.meter .text { + position: absolute; + right: 5px; + overflow: visible; + font-size: 8pt; + white-space: nowrap; + z-index: 1000; + text-shadow: #fff 1px 1px; } \ No newline at end of file diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html index cd8ec24d..07aa7320 100644 --- a/modules-available/statistics/templates/filterbox.html +++ b/modules-available/statistics/templates/filterbox.html @@ -101,7 +101,10 @@ var slxFilterNames = { currentuser: '{{lang_currentUser}}', subnet: '{{lang_subnet}}', runtime: '{{lang_runtimeHours}}', - hostname: '{{lang_hostname}}' + hostname: '{{lang_hostname}}', + live_swapfree: '{{lang_swapFree}}', + live_memfree: '{{lang_memFree}}', + live_tmpfree: '{{lang_tmpFree}}' }; slxLocations = {{{locations}}}; diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html index f1021482..904c780b 100644 --- a/modules-available/statistics/templates/machine-main.html +++ b/modules-available/statistics/templates/machine-main.html @@ -1,3 +1,4 @@ +{{%FILTERS}}

    {{hostname}} {{#hostname}}–{{/hostname}} {{clientip}} {{#notes}}{{/notes}} @@ -117,9 +118,23 @@

    {{#extram}} @@ -135,7 +150,17 @@ {{/extram}} - + -- cgit v1.2.3-55-g7522 From 8c5c14eb4ad2fcaeb35d746d2183943f6dc52fb1 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 28 Jan 2019 14:33:16 +0100 Subject: [statistics] Cleanup template --- modules-available/statistics/templates/machine-main.html | 1 - 1 file changed, 1 deletion(-) diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html index 904c780b..44f03a99 100644 --- a/modules-available/statistics/templates/machine-main.html +++ b/modules-available/statistics/templates/machine-main.html @@ -1,4 +1,3 @@ -{{%FILTERS}}

    {{hostname}} {{#hostname}}–{{/hostname}} {{clientip}} {{#notes}}{{/notes}} -- cgit v1.2.3-55-g7522 From 1b53e1ac441f547e4524a512eff3d6b25457f95e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 28 Jan 2019 14:38:47 +0100 Subject: [statistics] Fix copypasta --- modules-available/statistics/api.inc.php | 2 +- modules-available/statistics/hooks/cron.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 57c4cee2..280e5abc 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -157,7 +157,7 @@ if ($type{0} === '~') { writeClientLog('machine-mismatch-poweron', 'Poweron event, but previous known state is ' . $old['state'] . '. RAM: ' . Util::readableFileSize($old['live_memfree'], -1, 2) . ', Swap: ' . Util::readableFileSize($old['live_swapfree'], -1, 2) - . ', ID44: ' . Util::readableFileSize($old['live_memfree'], -1, 2)); + . ', ID44: ' . Util::readableFileSize($old['live_tmpfree'], -1, 2)); } } diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index f22d0475..6393b2c6 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -34,7 +34,7 @@ function state_cleanup() 'description' => 'Client timed out, last known state is ' . $row['state'] . '. RAM: ' . Util::readableFileSize($row['live_memfree'], -1, 2) . ', Swap: ' . Util::readableFileSize($row['live_swapfree'], -1, 2) - . ', ID44: ' . Util::readableFileSize($row['live_memfree'], -1, 2), + . ', ID44: ' . Util::readableFileSize($row['live_tmpfree'], -1, 2), 'longdesc' => '', 'uuid' => $row['machineuuid'], )); -- cgit v1.2.3-55-g7522 From 533a2964535bcae48eb30ded2edaa6dd6d2c321b Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 29 Jan 2019 09:20:55 +0100 Subject: [locationinfo] Suppress XML parsing error spam The SimpleXML constructor is quite verbose if you pass it e.g. an HTML error page instead of the expected XML. Suppress those errors, we'll do our own (more concise) logging in the exception handler. --- modules-available/locationinfo/inc/coursebackend.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/locationinfo/inc/coursebackend.inc.php b/modules-available/locationinfo/inc/coursebackend.inc.php index 1da0086a..dcd92f6f 100644 --- a/modules-available/locationinfo/inc/coursebackend.inc.php +++ b/modules-available/locationinfo/inc/coursebackend.inc.php @@ -334,7 +334,7 @@ abstract class CourseBackend { $cleanresponse = preg_replace('/(<\/?)(\w+):([^>]*>)/', '$1$2$3', $response); try { - $xml = new SimpleXMLElement($cleanresponse); + $xml = @new SimpleXMLElement($cleanresponse); // This spams before throwing exception } catch (Exception $e) { $this->error = 'Could not parse reply as XML, got ' . get_class($e) . ': ' . $e->getMessage(); if (CONFIG_DEBUG) { -- cgit v1.2.3-55-g7522 From f538335087e18ad5950eee0ec1c9654fff5addc5 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 29 Jan 2019 09:22:28 +0100 Subject: [locationinfo] HisInOne: Properly handle multiple plannedDates If a course has been running for several semesters, older plannedDate entries tend to have no individualDate entries. Ignore those and don't error_log about it. --- .../locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php index 22b1d8fb..c71623b3 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php @@ -271,6 +271,7 @@ class CourseBackend_HisInOne extends CourseBackend $eventDetails = array_merge($eventDetails, $event); } $name = false; + $now = time(); foreach ($eventDetails as $event) { foreach (array('/hisdefaulttext', '/hisshorttext', @@ -293,6 +294,12 @@ class CourseBackend_HisInOne extends CourseBackend foreach ($planElements as $planElement) { if (empty($planElement['hisplannedDates'])) continue; + $checkDate = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisenddate'); + if (!empty($checkDate) && strtotime($checkDate[0]) + 86400 < $now) + continue; // Course ended + $checkDate = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisstartdate'); + if (!empty($checkDate) && strtotime($checkDate[0]) - 86400 > $now) + continue; // Course didn't start yet $unitPlannedDates = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); if ($unitPlannedDates === false) { -- cgit v1.2.3-55-g7522 From 16c94742e46834336d77ca6825fa681db9c966bb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 30 Jan 2019 13:24:03 +0100 Subject: [systemstatus] Don't show swap warning too early --- modules-available/systemstatus/page.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/systemstatus/page.inc.php b/modules-available/systemstatus/page.inc.php index 958e763f..04423eaf 100644 --- a/modules-available/systemstatus/page.inc.php +++ b/modules-available/systemstatus/page.inc.php @@ -209,7 +209,7 @@ class Page_SystemStatus extends Page $data['swapTotal'] = Util::readableFileSize($info['SwapTotal'] * 1024); $data['swapUsed'] = Util::readableFileSize(($info['SwapTotal'] - $info['SwapFree']) * 1024); $data['swapPercent'] = 100 - round(($info['SwapFree'] / $info['SwapTotal']) * 100); - $data['swapWarning'] = ($data['swapPercent'] > 50 || ($info['SwapTotal'] - $info['SwapFree']) > 200000); + $data['swapWarning'] = ($data['swapPercent'] > 50 || $info['SwapFree'] < 400000); } if (isset($info['CpuIdle']) && isset($info['CpuSystem']) && isset($info['CpuTotal'])) { $data['cpuLoad'] = 100 - round(($info['CpuIdle'] / $info['CpuTotal']) * 100); -- cgit v1.2.3-55-g7522 From 120956761383f8365e95e669a11b344af4764c74 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 30 Jan 2019 13:39:41 +0100 Subject: [inc/Dictionary] Teh evil unvalidated redirects must die! --- inc/dictionary.inc.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inc/dictionary.inc.php b/inc/dictionary.inc.php index fcbfdfb8..935d1f4e 100644 --- a/inc/dictionary.inc.php +++ b/inc/dictionary.inc.php @@ -30,10 +30,15 @@ class Dictionary if ($lang !== false && in_array($lang, self::$languages)) { setcookie('lang', $lang, time() + 60 * 60 * 24 * 30 * 12); $url = Request::get('url'); - if ($url === false && isset($_SERVER['HTTP_REFERER'])) + if ($url === false && isset($_SERVER['HTTP_REFERER'])) { $url = $_SERVER['HTTP_REFERER']; - if ($url === false) - $url = '?do=Main'; + } + $parts = parse_url($url); + if ($url === false || $parts === false || empty($parts['query'])) { + $url = '?do=main'; + } else { + $url = '?' . $parts['query']; + } Util::redirect($url); } -- cgit v1.2.3-55-g7522 From 1ee6bdcda1b285b24829eefc9b9e0ca14b828f77 Mon Sep 17 00:00:00 2001 From: Udo Walter Date: Tue, 12 Feb 2019 11:44:51 +0100 Subject: [dozmod] Add UI to create preset network rules --- modules-available/dozmod/lang/de/messages.json | 6 ++ modules-available/dozmod/lang/de/module.json | 1 + modules-available/dozmod/lang/de/permissions.json | 2 + .../dozmod/lang/de/template-tags.json | 4 + modules-available/dozmod/lang/en/messages.json | 6 ++ modules-available/dozmod/lang/en/module.json | 1 + modules-available/dozmod/lang/en/permissions.json | 2 + .../dozmod/lang/en/template-tags.json | 4 + modules-available/dozmod/page.inc.php | 2 +- .../dozmod/pages/networkrules.inc.php | 98 ++++++++++++++++++++++ .../dozmod/permissions/permissions.json | 6 ++ .../dozmod/templates/networkrules-edit.html | 43 ++++++++++ .../dozmod/templates/networkrules.html | 82 ++++++++++++++++++ 13 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 modules-available/dozmod/pages/networkrules.inc.php create mode 100644 modules-available/dozmod/templates/networkrules-edit.html create mode 100644 modules-available/dozmod/templates/networkrules.html diff --git a/modules-available/dozmod/lang/de/messages.json b/modules-available/dozmod/lang/de/messages.json index 4a4be923..805472d0 100644 --- a/modules-available/dozmod/lang/de/messages.json +++ b/modules-available/dozmod/lang/de/messages.json @@ -11,6 +11,12 @@ "ldap-filter-saved": "LDAP Filter wurde erfolgreich gespeichert", "ldap-invalid-filter-id": "Ung\u00fcltige LDAP Filter ID", "mail-config-saved": "Mail-Konfiguration gespeichert", + "networkrule-deleted": "Netzwerk-Regel gel\u00f6scht", + "networkrule-invalid-direction": "Ung\u00fcltige Richtung: {{0}}", + "networkrule-invalid-ruleid": "Nicht-existierende Regel: {{0}}", + "networkrule-missing-host": "Fehlende Hostangabe", + "networkrule-missing-port": "Fehlende Portangabe", + "networkrule-saved": "Netzwerk-Regel gespeichert", "networkshare-deleted": "Netzlaufwerk gel\u00f6scht", "networkshare-invalid-auth-type": "Ung\u00fcltiger Authentifizierungs-Typ: {{0}}", "networkshare-invalid-shareid": "Nicht-existierender Share: {{0}}", diff --git a/modules-available/dozmod/lang/de/module.json b/modules-available/dozmod/lang/de/module.json index 8902852a..ff4519a7 100644 --- a/modules-available/dozmod/lang/de/module.json +++ b/modules-available/dozmod/lang/de/module.json @@ -5,6 +5,7 @@ "submenu_expiredimages": "Abgelaufene VM-Versionen", "submenu_ldapfilters": "LDAP-Filter", "submenu_mailconfig": "Email-Konfiguration", + "submenu_networkrules": "Netzwerk-Regeln", "submenu_networkshares": "Netzlaufwerke", "submenu_runscripts": "Startskripte", "submenu_runtimeconfig": "Limits und Standardwerte", diff --git a/modules-available/dozmod/lang/de/permissions.json b/modules-available/dozmod/lang/de/permissions.json index a1675148..6475f7ab 100644 --- a/modules-available/dozmod/lang/de/permissions.json +++ b/modules-available/dozmod/lang/de/permissions.json @@ -4,6 +4,8 @@ "ldapfilters.save": "LDAP Filter speichern.", "ldapfilters.view": "LDAP Filter einsehen.", "mailconfig.save": "\u00c4nderungen an der SMTP-Konfiguration zum Versenden von Mails speichern.", + "networkrules.save": "Netzwerk-Regeln einsehen.", + "networkrules.view": "\u00c4nderungen an den Netzwerk-Regeln speichern.", "networkshares.save": "Netzlaufwerke einsehen.", "networkshares.view": "\u00c4nderungen an den Netzlaufwerken speichern.", "runscripts.save": "Startkripte erstellen\/bearbeiten", diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json index 0a719057..320c7592 100644 --- a/modules-available/dozmod/lang/de/template-tags.json +++ b/modules-available/dozmod/lang/de/template-tags.json @@ -1,6 +1,7 @@ { "lang_actionTarget": "Aktionsziel", "lang_active": "Aktiv", + "lang_addRule": "Netzwerk-Regel hinzuf\u00fcgen", "lang_addShare": "Netzlaufwerk hinzuf\u00fcgen", "lang_allowLoginByDefault": "Login standardm\u00e4\u00dfig erlauben", "lang_allowLoginDescription": "Wenn diese Option aktiviert ist, k\u00f6nnen sich alle Mitarbeiter der Einrichtung \u00fcber die bwLehrpool-Suite anmelden und VMs\/Veranstaltungen verwalten. Wenn Sie diese Option deaktivieren, m\u00fcssen Sie in der Untersektion \"Benutzer und Berechtigungen\" jeden Benutzer nach dem ersten Loginversuch manuell freischalten.", @@ -23,7 +24,9 @@ "lang_descriptionPermissionConfig": "Dies sind die Berechtigungen, die ein Benutzer standardm\u00e4\u00dfig f\u00fcr fremde VMs\/Veranstaltungen hat. Sie werden angewandt, wenn der Besitzer keine anderweitigen Berechtigungen w\u00e4hlt.", "lang_descriptionRuntimeLimits": "Hier k\u00f6nnen Sie verschiedene Limits festlegen, z.B. wie lange eine VM nach dem Hochladen g\u00fcltig ist. Nach Ablauf dieses Zeitraums ist der Verantwortliche gezwungen, eine neue Version der VM hochzuladen. Damit k\u00f6nnen Sie das Ansammeln nicht mehr ben\u00f6tigter VMs eind\u00e4mmen. Weiterhin k\u00f6nnen Sie die maximale Anzahl gleichzeitiger Transfers pro Benutzer einschr\u00e4nken.\r\n\r\nVer\u00e4nderte Einstellungen wirken sich nicht auf bereits bestehende VMs aus.", "lang_description_delete_images": "Diese Liste zeigt VMs, die entweder abgelaufen sind, oder deren Datei besch\u00e4digt, verschoben oder gel\u00f6scht wurde. Diese Images sind zur Zeit im Lehrpool nicht verf\u00fcgbar, ihre endg\u00fcltige L\u00f6schung muss aber manuell best\u00e4tigt werden, um gr\u00f6\u00dfere Katastrophen durch Softwarefehler, verstellte Systemuhren etc. zu vermeiden.", + "lang_direction": "Richtung", "lang_dozmodLogHeading": "bwLehrpool-Suite Aktionslog", + "lang_editNetworkrule": "Netzwerk-Regel bearbeiten", "lang_editNetworkshare": "Netzlaufwerk bearbeiten", "lang_editScript": "Startscript bearbeiten", "lang_email": "EMail", @@ -66,6 +69,7 @@ "lang_miscOptions": "Verschiedene Einstellungen", "lang_modified": "Modifiziert", "lang_name": "Name", + "lang_networkrules": "Netzwerk-Regeln", "lang_networkshares": "Netzlaufwerke", "lang_networksharesIntro": "Hier k\u00f6nnen Sie vordefinierte Netzlaufwerke anlegen, die den Nutzern der bwLehrpool-Suite zur Auswahl gestellt werden. Es ist den Nutzern der bwLehrpool-Suite weiterhin m\u00f6glich, komplett eigene Netzwerkfreigaben zu definieren. Die Angaben hier sollen lediglich das Hinzuf\u00fcgen h\u00e4ufig genutzter Laufwerke vereinfachen, bzw. das \u00c4ndern eines Netzwerkpfades vereinfachen, da in diesem Fall nur der Zentrale Eintrag hier angepasst werden muss, und nicht mehr wie zuvor jede Veranstaltung einzeln.", "lang_none": "(Keiner)", diff --git a/modules-available/dozmod/lang/en/messages.json b/modules-available/dozmod/lang/en/messages.json index d09ff279..6d8296ec 100644 --- a/modules-available/dozmod/lang/en/messages.json +++ b/modules-available/dozmod/lang/en/messages.json @@ -11,6 +11,12 @@ "ldap-filter-saved": "Successfully modified LDAP filter", "ldap-invalid-filter-id": "Invalid LDAP filter id", "mail-config-saved": "Mail config saved", + "networkrule-deleted": "Network rule deleted", + "networkrule-invalid-direction": "Invalid direction: {{0}}", + "networkrule-invalid-ruleid": "Invalid rule id: {{0}}", + "networkrule-missing-host": "Missing host", + "networkrule-missing-port": "Missing port", + "networkrule-saved": "Network rule saved", "networkshare-deleted": "Network share deleted", "networkshare-invalid-auth-type": "Invalid auth type: {{0}}", "networkshare-invalid-shareid": "Invalid share id: {{0}}", diff --git a/modules-available/dozmod/lang/en/module.json b/modules-available/dozmod/lang/en/module.json index 4e3969ff..8967493d 100644 --- a/modules-available/dozmod/lang/en/module.json +++ b/modules-available/dozmod/lang/en/module.json @@ -5,6 +5,7 @@ "submenu_expiredimages": "Expired VM versions", "submenu_ldapfilters": "LDAP filters", "submenu_mailconfig": "email configuration", + "submenu_networkrules": "Network Rules", "submenu_networkshares": "Network Shares", "submenu_runtimeconfig": "limits and defaults", "submenu_templates": "templates", diff --git a/modules-available/dozmod/lang/en/permissions.json b/modules-available/dozmod/lang/en/permissions.json index d45e5207..dec3171a 100644 --- a/modules-available/dozmod/lang/en/permissions.json +++ b/modules-available/dozmod/lang/en/permissions.json @@ -4,6 +4,8 @@ "ldapfilters.save": "Save LDAP filter.", "ldapfilters.view": "View LDAP filters. ", "mailconfig.save": "Save SMTP configuration for sending mails.", + "networkrules.save": "View network rules.", + "networkrules.view": "Save network rules.", "networkshares.save": "View network drives.", "networkshares.view": "Save network drives.", "runtimeconfig.save": "Save limits and defaults of a runtime configuration.", diff --git a/modules-available/dozmod/lang/en/template-tags.json b/modules-available/dozmod/lang/en/template-tags.json index ddc89284..3f2ae1fc 100644 --- a/modules-available/dozmod/lang/en/template-tags.json +++ b/modules-available/dozmod/lang/en/template-tags.json @@ -1,6 +1,7 @@ { "lang_actionTarget": "Action target", "lang_active": "Active", + "lang_addRule": "Add Network Rule", "lang_addShare": "Add Network Share", "lang_allowLoginByDefault": "Allow all staff members to login and use the bwLehrpool-Suite", "lang_allowLoginDescription": "If this option is enabled, all members of the organization marked as staff or employee are allowed to login to this server and manage VMs\/courses. Otherwise, new users need to be individually allowed access after their first login attempt by visiting the sub page \"users and permissions\" in this web interface.", @@ -23,7 +24,9 @@ "lang_descriptionPermissionConfig": "These are the default permissions being used for VMs and lectures if the owner does not specify any.", "lang_descriptionRuntimeLimits": "Here you can define some limits, e.g. how long a newly uploaded VM will be valid. This should make sure that you don't end up with a lot of old, unused VMs over time.\r\n\r\nModified settings won't apply for already existing VMs.", "lang_description_delete_images": "This is a list of VMs that either expired, or where the disk image is damaged or missing. These VMs are not available in bwLehrpool currently, but you have to manually confirm the deletion of the disk images for safety reasons (clock skew etc.)", + "lang_direction": "Direction", "lang_dozmodLogHeading": "bwLehrpool-Suite action log", + "lang_editNetworkrule": "Edit Network Rule", "lang_editNetworkshare": "Edit Network Share", "lang_email": "E-Mail", "lang_emailNotifications": "E-Mail notifications enabled", @@ -63,6 +66,7 @@ "lang_miscOptions": "Misc options", "lang_modified": "modified", "lang_name": "Name", + "lang_networkrules": "Network Rules", "lang_networkshares": "Network Shares", "lang_networksharesIntro": "This is the list of predefined network shares. bwLehrpool-Suite users can still add custom network shares to their lectures, however having commonly used network shares as predefined entries should be much more convenient. Another advantage is that changing the path of a network share centrally avoids having to edit a dozen lectures' configuration manually.", "lang_none": "(none)", diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php index 776109cf..b772890f 100644 --- a/modules-available/dozmod/page.inc.php +++ b/modules-available/dozmod/page.inc.php @@ -5,7 +5,7 @@ class Page_DozMod extends Page /** @var bool true if we have a proper subpage */ private $haveSubPage = false; - private $validSections = ['expiredimages', 'mailconfig', 'templates', 'runtimeconfig', 'users', 'actionlog', 'networkshares', 'ldapfilters', 'runscripts']; + private $validSections = ['expiredimages', 'mailconfig', 'templates', 'runtimeconfig', 'users', 'actionlog', 'networkshares', 'ldapfilters', 'runscripts', 'networkrules']; private $section; diff --git a/modules-available/dozmod/pages/networkrules.inc.php b/modules-available/dozmod/pages/networkrules.inc.php new file mode 100644 index 00000000..6011e3ff --- /dev/null +++ b/modules-available/dozmod/pages/networkrules.inc.php @@ -0,0 +1,98 @@ + $ruleid]); + if ($res !== false) { + Message::addSuccess('networkrule-deleted'); + } + } + } else if ($action === 'save') { + User::assertPermission('networkrules.save'); + $ruleid = Request::post('ruleid', 0, 'int'); + $rulename = Request::post('rulename', '', 'string'); + $host = Request::post('host', '', 'string'); + $port = Request::post('port', '', 'string'); + $direction = Request::post('direction', '', 'string'); + + if (!in_array($direction, ['IN', 'OUT'], true)) { + Message::addError('networkrule-invalid-direction', $direction); + } elseif (empty($host)) { + Message::addError('networkrule-missing-host'); + } elseif (empty($port)) { + Message::addError('networkrule-missing-port'); + } else { + $data = json_encode([ + 'host' => $host, + 'port' => $port, + 'direction' => $direction + ]); + if ($ruleid !== 0) { + Database::exec('UPDATE sat.presetnetworkrules SET rulename = :rulename, ruledata = :data' + .' WHERE ruleid = :ruleid', compact('ruleid', 'rulename', 'data')); + } else { + Database::exec('INSERT INTO sat.presetnetworkrules (rulename, ruledata)' + .' VALUES (:rulename, :data)', compact('rulename', 'data')); + } + Message::addSuccess('networkrule-saved'); + } + } + if (Request::isPost()) { + Util::redirect('?do=dozmod§ion=networkrules'); + } + User::assertPermission('networkrules.view'); + } + + public static function doRender() + { + $show = Request::get('show', 'list', 'string'); + if ($show === 'list') { + $res = Database::simpleQuery('SELECT ruleid, rulename, ruledata + FROM sat.presetnetworkrules ORDER BY rulename ASC'); + $rows = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $dec = json_decode($row['ruledata'], true); + if (!is_array($dec)) { + $dec = []; + } + $rows[] = $row + $dec; + } + Render::addTemplate('networkrules', [ + 'networkrules' => $rows, + 'hasEditPermissions' => User::hasPermission('networkrules.save') + ]); + } else if ($show === 'edit') { + $ruleid = Request::get('ruleid', 0, 'int'); + if ($ruleid === 0) { + $data = []; + } else { + $data = Database::queryFirst('SELECT ruleid, rulename, ruledata + FROM sat.presetnetworkrules WHERE ruleid = :ruleid', ['ruleid' => $ruleid]); + if ($data === false) { + Message::addError('networkrule-invalid-ruleid', $ruleid); + Util::redirect('?do=dozmod§ion=networkrules'); + } + $dec = json_decode($data['ruledata'], true); + if (is_array($dec)) { + $data += $dec; + } + if ($data['direction'] === 'IN') { + $data['inSelected'] = 'selected'; + } else { + $data['outSelected'] = 'selected'; + } + } + Render::addTemplate('networkrules-edit', $data); + } + } + +} diff --git a/modules-available/dozmod/permissions/permissions.json b/modules-available/dozmod/permissions/permissions.json index 3f9cd604..c8958089 100644 --- a/modules-available/dozmod/permissions/permissions.json +++ b/modules-available/dozmod/permissions/permissions.json @@ -14,6 +14,12 @@ "mailconfig.save": { "location-aware": false }, + "networkrules.view": { + "location-aware": false + }, + "networkrules.save": { + "location-aware": false + }, "networkshares.view": { "location-aware": false }, diff --git a/modules-available/dozmod/templates/networkrules-edit.html b/modules-available/dozmod/templates/networkrules-edit.html new file mode 100644 index 00000000..c04e2825 --- /dev/null +++ b/modules-available/dozmod/templates/networkrules-edit.html @@ -0,0 +1,43 @@ +

    {{lang_networkrules}}

    + +
    +
    + {{lang_editNetworkrule}} +
    +
    +
    + + + + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + {{lang_cancel}} + + +
    + +
    +
    \ No newline at end of file diff --git a/modules-available/dozmod/templates/networkrules.html b/modules-available/dozmod/templates/networkrules.html new file mode 100644 index 00000000..4344ff4f --- /dev/null +++ b/modules-available/dozmod/templates/networkrules.html @@ -0,0 +1,82 @@ +

    {{lang_networkrules}}

    + +

    + {{lang_networkrulesIntro}} +

    + +
    {{lang_name}}{{lang_systemmodel}} {{lang_count}} {{lang_override}}
    {{lang_ram}} - {{gbram}} GiB - {{#maxram}}({{lang_maximumAbbrev}} {{maxram}}){{/maxram}} - {{ramtype}} +
    + {{gbram}} GiB + {{#maxram}}({{lang_maximumAbbrev}} {{maxram}}){{/maxram}} + {{ramtype}} +
    + {{#live_memsize}} +
    +
    {{live_memfree_s}} {{lang_free}}
    +
    +
    + {{/live_memsize}} + {{#live_swapsize}} +
    +
    {{live_swapfree_s}} {{lang_free}}
    +
    +
    + {{/live_swapsize}}
    {{lang_tempPart}}{{gbtmp}} GiB +
    + {{gbtmp}} GiB +
    + {{#live_tmpsize}} +
    +
    {{live_tmpfree_s}} {{lang_free}}
    +
    +
    + {{/live_tmpsize}} +
    {{lang_64bitSupport}}
    + + + + + + + {{#hasEditPermissions}} + + + {{/hasEditPermissions}} + + + + {{#networkrules}} + + + + + + {{#hasEditPermissions}} + + + {{/hasEditPermissions}} + + {{/networkrules}} + +
    {{lang_name}}{{lang_host}}{{lang_port}}{{lang_direction}}{{lang_edit}}{{lang_delete}}
    {{rulename}}{{host}}{{port}}{{direction}} + + + + + +
    +{{#hasEditPermissions}} + +{{/hasEditPermissions}} + + +
    + + + +
    + + \ No newline at end of file -- cgit v1.2.3-55-g7522 From 2d64b9d8f57f28456eb27c4aed2dde26201b6770 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Feb 2019 14:35:49 +0100 Subject: [serversetup-bwlp] Auto-import of old PXELinux config on bootup Also minor improvements to UI and structuring --- inc/event.inc.php | 11 +++ .../serversetup-bwlp/hooks/bootup.inc.php | 11 +++ .../serversetup-bwlp/inc/bootentry.inc.php | 3 +- .../serversetup-bwlp/inc/ipxe.inc.php | 79 ++++++++++++++++++---- .../serversetup-bwlp/inc/pxelinux.inc.php | 40 +++++++++++ .../serversetup-bwlp/lang/de/messages.json | 12 ++-- .../serversetup-bwlp/lang/de/module.json | 4 +- .../serversetup-bwlp/lang/de/template-tags.json | 23 ++++--- modules-available/serversetup-bwlp/page.inc.php | 39 +++++++---- .../serversetup-bwlp/templates/bootentry-list.html | 12 +++- .../templates/ipxe-new-boot-entry.html | 9 ++- .../serversetup-bwlp/templates/menu-edit.html | 2 +- .../serversetup-bwlp/templates/menu-list.html | 13 +++- 13 files changed, 211 insertions(+), 47 deletions(-) create mode 100644 modules-available/serversetup-bwlp/hooks/bootup.inc.php diff --git a/inc/event.inc.php b/inc/event.inc.php index b1ee3663..4e68ab6d 100644 --- a/inc/event.inc.php +++ b/inc/event.inc.php @@ -24,6 +24,17 @@ class Event Property::clearList('cron.key.status'); Property::clearList('cron.key.blocked'); + // Hooks + foreach (Hook::load('bootup') as $hook) { + // Isolate for local vars + $fun = function() use ($hook) { + include_once($hook->file); + }; + $fun(); + } + + // TODO: Modularize (hooks) + // Tasks: fire away $mountStatus = false; $mountId = Trigger::mount(); diff --git a/modules-available/serversetup-bwlp/hooks/bootup.inc.php b/modules-available/serversetup-bwlp/hooks/bootup.inc.php new file mode 100644 index 00000000..50ac04ae --- /dev/null +++ b/modules-available/serversetup-bwlp/hooks/bootup.inc.php @@ -0,0 +1,11 @@ + 0) { + EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); + } else { + EventLog::info('Imported old PXELinux menu.'); + } +} diff --git a/modules-available/serversetup-bwlp/inc/bootentry.inc.php b/modules-available/serversetup-bwlp/inc/bootentry.inc.php index 010b660c..69adffd3 100644 --- a/modules-available/serversetup-bwlp/inc/bootentry.inc.php +++ b/modules-available/serversetup-bwlp/inc/bootentry.inc.php @@ -95,7 +95,7 @@ class StandardBootEntry extends BootEntry protected $replace; protected $autoUnload; protected $resetConsole; - protected $arch; // true == available, false == not available + protected $arch; // Constants below const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot const EFI = 'EFI'; // Only valid for EFI boot @@ -105,6 +105,7 @@ class StandardBootEntry extends BootEntry public function __construct($data = false) { if ($data instanceof PxeSection) { + // Gets arrayfied below $this->executable = $data->kernel; $this->initRd = $data->initrd; $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php index d5bbb4b2..d34839f0 100644 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ b/modules-available/serversetup-bwlp/inc/ipxe.inc.php @@ -3,8 +3,16 @@ class IPxe { + /** + * Import all IP-Range based pxe menus from the given directory. + * + * @param string $configPath The pxelinux.cfg path where to look for menu files in hexadecimal IP format. + * @return Number of menus imported + */ public static function importPxeMenus($configPath) { + $importCount = 0; + $menus = []; foreach (glob($configPath . '/*', GLOB_NOSORT) as $file) { if (!is_file($file) || !preg_match('~/[A-F0-9]{1,8}$~', $file)) continue; @@ -29,14 +37,53 @@ class IPxe unset($loc); $locations[] = $row; } - $menuId = self::insertMenu($content, 'Imported', false, 0, [], []); + $menu = PxeLinux::parsePxeLinux($content); + $key = $menu->hash(true); + if (isset($menus[$key])) { + $menuId = $menus[$key]; + $defId = null; + // Figure out the default label, get it's label name + foreach ($menu->sections as $section) { + if ($section->isDefault) { + $defId = $section; + } elseif ($defId === null && $section->label === $menu->timeoutLabel) { + $defId = $section; + } + } + if ($defId !== null) { + $defId = self::cleanLabelFixLocal($defId); + // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId + $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) + INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) + WHERE be.entryid = :id AND me.menuid = :menuid', + ['id' => $defId, 'menuid' => $menuId]); + if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { + $defId = null; // Not found, or is already default - don't override if it's the same + } else { + $defId = $me['menuentryid']; + } + } + } else { + $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); + $menus[$key] = $menuId; + $defId = null; + $importCount++; + } if ($menuId === false) continue; foreach ($locations as $loc) { - Database::exec('INSERT IGNORE INTO serversetup_menu_x_location (menuid, locationid) - VALUES (:menuid, :locationid)', ['menuid' => $menuId, 'locationid' => $loc['locationid']]); + if ($loc === false) + continue; + Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :def)', [ + 'menuid' => $menuId, + 'locationid' => $loc['locationid'], + 'def' => $defId, + ]); } } + return $importCount; } public static function importLegacyMenu($force = false) @@ -76,16 +123,25 @@ class IPxe '', 'poweroff' => false, ]; - return self::insertMenu($pxeConfig, $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); + return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); } - private static function insertMenu($pxeConfig, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) + /** + * @param PxeMenu $pxeMenu + * @param string $menuTitle + * @param string|false $defaultLabel + * @param $defaultTimeoutSeconds + * @param $prepend + * @param $append + * @return bool|int + */ + private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) { $timeoutMs = []; $menuEntries = $prepend; settype($menuEntries, 'array'); - if (!empty($pxeConfig)) { - $pxe = PxeLinux::parsePxeLinux($pxeConfig); + if (!empty($pxeMenu)) { + $pxe =& $pxeMenu; if (!empty($pxe->title)) { $menuTitle = $pxe->title; } @@ -95,11 +151,10 @@ class IPxe $timeoutMs[] = $pxe->timeoutMs; $timeoutMs[] = $pxe->totalTimeoutMs; foreach ($pxe->sections as $section) { - if ($section->localBoot || preg_match('/chain.c32$/i', $section->kernel)) { + if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { $menuEntries['localboot'] = 'localboot'; continue; } - $section->mangle(); if ($section->label === null) { if (!$section->isHidden && !empty($section->title)) { $menuEntries[] = $section->title; @@ -221,7 +276,7 @@ class IPxe 'data' => json_encode([ 'executable' => '/boot/default/kernel', 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto ${ipappend1} ${ipappend2}', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', 'replace' => true, 'autoUnload' => true, 'resetConsole' => true, @@ -235,7 +290,7 @@ class IPxe 'data' => json_encode([ 'executable' => '/boot/default/kernel', 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default loglevel=7 ${ipappend1} ${ipappend2}', + 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', 'replace' => true, 'autoUnload' => true, 'resetConsole' => true, @@ -314,7 +369,7 @@ class IPxe */ private static function pxe2BootEntry($section) { - if (preg_match('/(pxechain.com|pxechn.c32)$/i', $section->kernel)) { + if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { // Chaining -- create script $args = preg_split('/\s+/', $section->append); $script = ''; diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php index db3dac4b..1d022fef 100644 --- a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php +++ b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php @@ -86,6 +86,9 @@ class PxeLinux if ($section !== null) { $menu->sections[] = $section; } + foreach ($menu->sections as $section) { + $section->mangle(); + } return $menu; } @@ -131,6 +134,7 @@ class PxeLinux */ class PxeMenu { + /** * @var string menu title, shown at the top of the menu */ @@ -160,6 +164,40 @@ class PxeMenu * @var PxeSection[] list of sections the menu contains */ public $sections = []; + + public function hash($fuzzy) + { + $ctx = hash_init('md5'); + if (!$fuzzy) { + hash_update($ctx, $this->title); + hash_update($ctx, $this->timeoutLabel); + } + hash_update($ctx, $this->timeoutMs); + foreach ($this->sections as $section) { + if ($fuzzy) { + hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); + } else { + hash_update($ctx, $section->label); + hash_update($ctx, $section->title); + hash_update($ctx, $section->indent); + hash_update($ctx, $section->helpText); + hash_update($ctx, $section->isDefault); + hash_update($ctx, $section->hotkey); + } + hash_update($ctx, $section->kernel); + hash_update($ctx, $section->append); + hash_update($ctx, $section->ipAppend); + hash_update($ctx, $section->passwd); + hash_update($ctx, $section->isHidden); + hash_update($ctx, $section->isDisabled); + hash_update($ctx, $section->localBoot); + foreach ($section->initrd as $initrd) { + hash_update($ctx, $initrd); + } + } + return hash_final($ctx, false); + } + } /** @@ -170,6 +208,7 @@ class PxeMenu */ class PxeSection { + /** * @var string label used internally in PXEMENU definition to address this entry */ @@ -258,5 +297,6 @@ class PxeSection $this->initrd = []; } } + } diff --git a/modules-available/serversetup-bwlp/lang/de/messages.json b/modules-available/serversetup-bwlp/lang/de/messages.json index de48ef0b..0772a7e4 100644 --- a/modules-available/serversetup-bwlp/lang/de/messages.json +++ b/modules-available/serversetup-bwlp/lang/de/messages.json @@ -1,10 +1,10 @@ { - "boot-entry-created": "Booteintrag {{0}} erzeugt", - "boot-entry-updated": "Booteintrag {{0}} aktualisiert", - "bootentry-deleted": "Booteintrag gel\u00f6scht", + "boot-entry-created": "Men\u00fceintrag {{0}} erzeugt", + "boot-entry-updated": "Men\u00fceintrag {{0}} aktualisiert", + "bootentry-deleted": "Men\u00fceintrag gel\u00f6scht", "error-saving-entry": "Fehler beim Speichern des Eintrags {{0}}: {{1}}", "image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.", - "invalid-boot-entry": "Ung\u00fcltiger Booteintrag: {{0}}", + "invalid-boot-entry": "Ung\u00fcltiger Men\u00fceintrag: {{0}}", "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", "invalid-menu-id": "Ung\u00fcltige Men\u00fc-ID: {{0}}", "localboot-invalid-method": "Ung\u00fcltige localboot-Methode: {{0}}", @@ -14,8 +14,8 @@ "menu-deleted": "Men\u00fc gel\u00f6scht", "menu-saved": "Men\u00fc wurde gespeichert", "menu-set-default": "Standardmen\u00fc wurde gesetzt", - "missing-bootentry-data": "Fehlende Daten f\u00fcr den Booteintrag", + "missing-bootentry-data": "Fehlende Daten f\u00fcr den Men\u00fceintrag", "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers", "no-such-menu": "Men\u00fc mit ID {{0}} existiert nicht", "unknown-bootentry-type": "Unbekannter Eintrags-Typ: {{0}}" -} \ No newline at end of file +} diff --git a/modules-available/serversetup-bwlp/lang/de/module.json b/modules-available/serversetup-bwlp/lang/de/module.json index 31d563f0..9a8de39c 100644 --- a/modules-available/serversetup-bwlp/lang/de/module.json +++ b/modules-available/serversetup-bwlp/lang/de/module.json @@ -12,8 +12,8 @@ "module_name": "iPXE \/ Boot Menu", "page_title": "PXE- und Boot-Einstellungen", "submenu_address": "Server-Adresse", - "submenu_bootentry": "Booteintr\u00e4ge verwalten", + "submenu_bootentry": "Men\u00fceintr\u00e4ge verwalten", "submenu_download": "Downloads", "submenu_localboot": "HDD-Boot", "submenu_menu": "Men\u00fcs verwalten" -} \ No newline at end of file +} diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json index a242be5e..198a1517 100644 --- a/modules-available/serversetup-bwlp/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json @@ -1,8 +1,9 @@ { "lang_active": "Aktiv", "lang_add": "Hinzuf\u00fcgen", - "lang_addBootentry": "Booteintrag hinzuf\u00fcgen", + "lang_addBootentry": "Men\u00fceintrag hinzuf\u00fcgen", "lang_addMenu": "Men\u00fc hinzuf\u00fcgen", + "lang_additionalInfoLink": "Weitere Informationen", "lang_archAgnostic": "Architekturunabh\u00e4ngig", "lang_archBoth": "BIOS und EFI", "lang_archSelector": "Architekturauswahl", @@ -10,13 +11,15 @@ "lang_biosOnly": "Nur BIOS", "lang_bootAddress": "Boot-Adresse des Servers", "lang_bootBehavior": "Standard-Bootverhalten", - "lang_bootEntryData": "Daten des Booteintrags", + "lang_bootEntryData": "Daten des Men\u00fceintrags", "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", "lang_bootMenu": "Bootmen\u00fc", "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", - "lang_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Booteintrag l\u00f6schen wollen?", - "lang_bootentryTitle": "Booteintrag", + "lang_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Men\u00fceintrag l\u00f6schen wollen?", + "lang_bootentryHead": "Men\u00fceintr\u00e4ge", + "lang_bootentryIntro": "Hier k\u00f6nnen Sie Men\u00fceintr\u00e4ge definieren, die sich sp\u00e4ter einem Men\u00fc zuweisen lassen. Ein Men\u00fceintrag besteht entweder aus einem zu ladenen Kernel\/Image plus optional initrd, oder aus einem iPXE-Script.", + "lang_bootentryTitle": "Men\u00fceintrag", "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_commandLine": "Command line", "lang_count": "Anzahl", @@ -26,7 +29,7 @@ "lang_editBuiltinWarn": "Achtung! Sie bearbeiten einen der vorgegebenen Eintr\u00e4ge! Bei einem Update k\u00f6nnten Ihre \u00c4nderungen wieder \u00fcberschrieben werden", "lang_editMenuHead": "Men\u00fc bearbeiten", "lang_efiOnly": "Nur EFI", - "lang_entryChooserTitle": "Booteintrag ausw\u00e4hlen", + "lang_entryChooserTitle": "Men\u00fceintrag ausw\u00e4hlen", "lang_entryId": "ID", "lang_entryTitle": "Bezeichnung", "lang_example": "Beispiel", @@ -40,6 +43,7 @@ "lang_idFormatHint": "(Max. 16 Zeichen, nur a-z 0-9 - _)", "lang_imageToLoad": "Zu ladendes Image (z.B. Kernel)", "lang_initRd": "Zu ladendes initramfs", + "lang_ipxeWikiUrl": "im iPXE Wiki", "lang_isDefault": "Standard", "lang_listOfMenus": "Men\u00fcliste", "lang_localBootDefault": "Standardm\u00e4\u00dfig verwendete Methode, um von Festplatte zu booten", @@ -49,7 +53,7 @@ "lang_localHDD": "Lokale HDD", "lang_locationCount": "Anzahl Orte", "lang_masterPassword": "Master-Passwort", - "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", + "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Men\u00fceintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.", "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als", @@ -58,16 +62,19 @@ "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", "lang_menuEntryOverride": "Standardeintrag \u00fcberschreiben", "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_menuListIntro": "Hier sehen Sie eine Liste aller vorhandenen Men\u00fcs, deren Zuordnung zu R\u00e4umen sowie die M\u00f6glichkeit, diese zu editieren oder l\u00f6schen. Um ein Men\u00fc einem bestimmten Raum zuzuweisen, besuchen Sie bitte den Men\u00fcpunkt \"R\u00e4ume\/Orte\".", "lang_menuLocations": "Zugewiesene Orte", "lang_menuTimeout": "Timeout", "lang_menuTitle": "Men\u00fc", "lang_moduleHeading": "iPXE \/ Boot Menu", - "lang_newBootEntryHead": "Neuer Booteintrag", + "lang_newBootEntryHead": "Neuer Men\u00fceintrag", "lang_newMenu": "Neues Men\u00fc", "lang_none": "(keine)", "lang_override": "\u00dcberschreiben", "lang_pxeBuilt": "PXE-Binary gebaut", "lang_recompileHint": "iPXE-Binaries jetzt neu kompilieren. Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen.", + "lang_refCount": "Referenzen", + "lang_referencingMenus": "Verkn\u00fcpfte Men\u00fcs", "lang_scriptContent": "Script", "lang_seconds": "Sekunden", "lang_set": "Setzen", @@ -84,4 +91,4 @@ "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)", "lang_useDefaultMenu": "\u00dcbergeordnetes Men\u00fc verwenden", "lang_useDefaultMenuEntry": "(Vorgabe des Men\u00fcs)" -} \ No newline at end of file +} diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php index 6c32cb82..bb69fecf 100644 --- a/modules-available/serversetup-bwlp/page.inc.php +++ b/modules-available/serversetup-bwlp/page.inc.php @@ -32,12 +32,6 @@ class Page_ServerSetup extends Page Util::redirect('?do=Main'); } - if (Request::any('bla') == 'blu') { - IPxe::importLegacyMenu(); - IPxe::importPxeMenus('/srv/openslx/tftp/pxelinux.cfg'); - die('DONE'); - } - if (Request::any('action') === 'getimage') { User::assertPermission("download"); $this->handleGetImage(); @@ -277,7 +271,10 @@ class Page_ServerSetup extends Page { $allowEdit = User::hasPermission('ipxe.bootentry.edit'); - $res = Database::simpleQuery("SELECT entryid, hotkey, title, builtin FROM serversetup_bootentry"); + $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry sm USING (entryid) + GROUP BY be.entryid + ORDER BY be.title ASC"); $bootentryTable = []; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $bootentryTable[] = $row; @@ -295,8 +292,13 @@ class Page_ServerSetup extends Page // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); - $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations - FROM serversetup_menu m LEFT JOIN serversetup_menu_location l USING (menuid) GROUP BY menuid ORDER BY title"); + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, + GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + LEFT JOIN location ll USING (locationid) + GROUP BY menuid + ORDER BY title"); $menuTable = []; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if (empty($row['locations'])) { @@ -351,6 +353,7 @@ class Page_ServerSetup extends Page Message::addError('invalid-menu-id', $id); Util::redirect('?do=serversetup&show=menu'); } + $highlight = Request::get('highlight', false, 'string'); if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { $menu['readonly'] = 'readonly'; $menu['disabled'] = 'disabled'; @@ -361,12 +364,19 @@ class Page_ServerSetup extends Page } $menu['timeout'] = round($menu['timeoutms'] / 1000); - $menu['entries'] = Database::queryAll("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + $menu['entries'] = []; + $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['entryid'] == $highlight) { + $row['highlight'] = 'active'; + } + $menu['entries'][] = $row; + } $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); foreach ($menu['entrylist'] as &$bootentry) { - $bootentry['json'] = $bootentry['data']; + //$bootentry['json'] = $bootentry['data']; $bootentry['data'] = json_decode($bootentry['data'], true); if (array_key_exists('arch', $bootentry['data'])) { $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], @@ -386,7 +396,7 @@ class Page_ServerSetup extends Page $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); } - } else { + } elseif (!array_key_exists('script', $bootentry['data'])) { $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], 'initRd' => $bootentry['data']['initRd'], @@ -428,8 +438,11 @@ class Page_ServerSetup extends Page } $entry->addFormFields($params); $params['title'] = $row['title']; - $params['oldentryid'] = $params['entryid'] = $row['entryid']; + $params['entryid'] = $row['entryid']; $params['builtin'] = $row['builtin']; + $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m + INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) + WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); } Render::addTemplate('ipxe-new-boot-entry', $params); diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html index 929b8c47..0cf005c5 100644 --- a/modules-available/serversetup-bwlp/templates/bootentry-list.html +++ b/modules-available/serversetup-bwlp/templates/bootentry-list.html @@ -1,8 +1,15 @@ +

    {{lang_bootentryHead}}

    + +

    + {{lang_bootentryIntro}} +

    + - + + @@ -16,6 +23,9 @@ + {{#entries}} - +
    {{lang_bootentryTitle}}Hotkey{{lang_hotkey}}{{lang_refCount}} {{lang_edit}} {{lang_delete}}
    {{hotkey}} + {{refs}} + {{#allowEdit}} diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html index fe496029..7e82b5cc 100644 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html @@ -14,7 +14,7 @@
    - +
    @@ -121,6 +121,13 @@
    {{/builtin}} +

    {{lang_referencingMenus}}:

    +
    +
    diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html index 67365a33..545f22a9 100644 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ b/modules-available/serversetup-bwlp/templates/menu-list.html @@ -1,5 +1,9 @@

    {{lang_listOfMenus}}

    +

    + {{lang_menuListIntro}} +

    + @@ -17,7 +21,7 @@ {{title}}
    - {{locationCount}} + {{locationCount}} {{^isdefault}} @@ -37,7 +41,7 @@ {{#allowEdit}} - + {{/allowEdit}} @@ -60,6 +64,8 @@ +
    + @@ -88,4 +94,7 @@ function deleteMenu(menuid) { $("#delete-menu-id").val(menuid); } + document.addEventListener('DOMContentLoaded', function() { + $('[data-toggle="tooltip"]').tooltip(); + }); \ No newline at end of file -- cgit v1.2.3-55-g7522 From c2a6dfb649107ce3ad64162e2bb2c3c7275650fb Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Feb 2019 14:55:12 +0100 Subject: [serversetup*] PXELinux and iPXE side-by-side --- .../serversetup-bwlp-ipxe/api.inc.php | 254 +++++++ .../serversetup-bwlp-ipxe/config.json | 8 + .../serversetup-bwlp-ipxe/hooks/bootup.inc.php | 11 + .../hooks/ipxe-update.inc.php | 10 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-ipxe/inc/bootentry.inc.php | 258 +++++++ .../serversetup-bwlp-ipxe/inc/ipxe.inc.php | 453 +++++++++++ .../serversetup-bwlp-ipxe/inc/ipxemenu.inc.php | 142 ++++ .../serversetup-bwlp-ipxe/inc/localboot.inc.php | 15 + .../serversetup-bwlp-ipxe/inc/menuentry.inc.php | 177 +++++ .../serversetup-bwlp-ipxe/inc/pxelinux.inc.php | 302 ++++++++ .../serversetup-bwlp-ipxe/install.inc.php | 89 +++ .../serversetup-bwlp-ipxe/lang/de/messages.json | 21 + .../serversetup-bwlp-ipxe/lang/de/module.json | 19 + .../serversetup-bwlp-ipxe/lang/de/permissions.json | 12 + .../lang/de/template-tags.json | 94 +++ .../serversetup-bwlp-ipxe/lang/en/messages.json | 5 + .../serversetup-bwlp-ipxe/lang/en/module.json | 3 + .../serversetup-bwlp-ipxe/lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 42 ++ .../serversetup-bwlp-ipxe/lang/pt/messages.json | 3 + .../serversetup-bwlp-ipxe/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-ipxe/page.inc.php | 829 +++++++++++++++++++++ .../permissions/permissions.json | 29 + .../templates/bootentry-list.html | 83 +++ .../serversetup-bwlp-ipxe/templates/download.html | 53 ++ .../serversetup-bwlp-ipxe/templates/heading.html | 3 + .../serversetup-bwlp-ipxe/templates/ipaddress.html | 44 ++ .../templates/ipxe-new-boot-entry.html | 165 ++++ .../templates/ipxe_update.html | 54 ++ .../serversetup-bwlp-ipxe/templates/localboot.html | 59 ++ .../templates/menu-assign-location.html | 69 ++ .../serversetup-bwlp-ipxe/templates/menu-edit.html | 368 +++++++++ .../serversetup-bwlp-ipxe/templates/menu-list.html | 100 +++ .../serversetup-bwlp-pxelinux/config.json | 3 + .../hooks/ipxe-update.inc.php | 9 + .../hooks/main-warning.inc.php | 6 + .../serversetup-bwlp-pxelinux/inc/ipxe.inc.php | 224 ++++++ .../lang/de/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/de/module.json | 4 + .../lang/de/permissions.json | 6 + .../lang/de/template-tags.json | 33 + .../lang/en/messages.json | 5 + .../serversetup-bwlp-pxelinux/lang/en/module.json | 3 + .../lang/en/permissions.json | 6 + .../lang/en/template-tags.json | 33 + .../lang/pt/messages.json | 3 + .../serversetup-bwlp-pxelinux/lang/pt/module.json | 3 + .../lang/pt/template-tags.json | 38 + .../serversetup-bwlp-pxelinux/page.inc.php | 187 +++++ .../permissions/permissions.json | 14 + .../templates/heading.html | 1 + .../templates/ipaddress.html | 37 + .../serversetup-bwlp-pxelinux/templates/ipxe.html | 117 +++ .../templates/ipxe_update.html | 38 + modules-available/serversetup-bwlp/api.inc.php | 254 ------- modules-available/serversetup-bwlp/config.json | 8 - .../serversetup-bwlp/hooks/bootup.inc.php | 11 - .../serversetup-bwlp/hooks/ipxe-update.inc.php | 10 - .../serversetup-bwlp/hooks/main-warning.inc.php | 6 - .../serversetup-bwlp/inc/bootentry.inc.php | 258 ------- .../serversetup-bwlp/inc/ipxe.inc.php | 453 ----------- .../serversetup-bwlp/inc/ipxemenu.inc.php | 142 ---- .../serversetup-bwlp/inc/localboot.inc.php | 15 - .../serversetup-bwlp/inc/menuentry.inc.php | 177 ----- .../serversetup-bwlp/inc/pxelinux.inc.php | 302 -------- modules-available/serversetup-bwlp/install.inc.php | 89 --- .../serversetup-bwlp/lang/de/messages.json | 21 - .../serversetup-bwlp/lang/de/module.json | 19 - .../serversetup-bwlp/lang/de/permissions.json | 12 - .../serversetup-bwlp/lang/de/template-tags.json | 94 --- .../serversetup-bwlp/lang/en/messages.json | 5 - .../serversetup-bwlp/lang/en/module.json | 3 - .../serversetup-bwlp/lang/en/permissions.json | 6 - .../serversetup-bwlp/lang/en/template-tags.json | 42 -- .../serversetup-bwlp/lang/pt/messages.json | 3 - .../serversetup-bwlp/lang/pt/module.json | 3 - .../serversetup-bwlp/lang/pt/template-tags.json | 38 - modules-available/serversetup-bwlp/page.inc.php | 829 --------------------- .../serversetup-bwlp/permissions/permissions.json | 29 - .../serversetup-bwlp/templates/bootentry-list.html | 83 --- .../serversetup-bwlp/templates/download.html | 53 -- .../serversetup-bwlp/templates/heading.html | 3 - .../serversetup-bwlp/templates/ipaddress.html | 44 -- .../templates/ipxe-new-boot-entry.html | 165 ---- .../serversetup-bwlp/templates/ipxe_update.html | 54 -- .../serversetup-bwlp/templates/localboot.html | 59 -- .../templates/menu-assign-location.html | 69 -- .../serversetup-bwlp/templates/menu-edit.html | 368 --------- .../serversetup-bwlp/templates/menu-list.html | 100 --- 91 files changed, 4602 insertions(+), 3827 deletions(-) create mode 100644 modules-available/serversetup-bwlp-ipxe/api.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/config.json create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/bootup.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/install.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-ipxe/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-ipxe/page.inc.php create mode 100644 modules-available/serversetup-bwlp-ipxe/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/download.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/localboot.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html create mode 100644 modules-available/serversetup-bwlp-ipxe/templates/menu-list.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/config.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/hooks/main-warning.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/inc/ipxe.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/page.inc.php create mode 100644 modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/heading.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html create mode 100644 modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/api.inc.php delete mode 100644 modules-available/serversetup-bwlp/config.json delete mode 100644 modules-available/serversetup-bwlp/hooks/bootup.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php delete mode 100644 modules-available/serversetup-bwlp/hooks/main-warning.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/bootentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxe.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/ipxemenu.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/localboot.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/menuentry.inc.php delete mode 100644 modules-available/serversetup-bwlp/inc/pxelinux.inc.php delete mode 100644 modules-available/serversetup-bwlp/install.inc.php delete mode 100644 modules-available/serversetup-bwlp/lang/de/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/de/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/permissions.json delete mode 100644 modules-available/serversetup-bwlp/lang/en/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/messages.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/module.json delete mode 100644 modules-available/serversetup-bwlp/lang/pt/template-tags.json delete mode 100644 modules-available/serversetup-bwlp/page.inc.php delete mode 100644 modules-available/serversetup-bwlp/permissions/permissions.json delete mode 100644 modules-available/serversetup-bwlp/templates/bootentry-list.html delete mode 100644 modules-available/serversetup-bwlp/templates/download.html delete mode 100644 modules-available/serversetup-bwlp/templates/heading.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipaddress.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html delete mode 100644 modules-available/serversetup-bwlp/templates/ipxe_update.html delete mode 100644 modules-available/serversetup-bwlp/templates/localboot.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-assign-location.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-edit.html delete mode 100644 modules-available/serversetup-bwlp/templates/menu-list.html diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php new file mode 100644 index 00000000..1df0e6e7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php @@ -0,0 +1,254 @@ + $v) { + $query .= $k . '=' . $v . '&'; + } + //$query = substr($query, 0, -1); + echo << $uuid]); + if ($row !== false && !empty($row['systemmodel'])) { + $model = $row['systemmodel']; + } +} +if ($model === false) { + // Otherwise use what iPXE sent us + function modfilt($str) + { + if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) + return false; + return trim(preg_replace('/\s+/', ' ', $str)); + } + $manuf = modfilt($manuf); + $product = modfilt($product); + if (!empty($product)) { + $model = $product; + if (!empty($manuf)) { + $model .= " ($manuf)"; + } + } +} +// Query +if ($model !== false) { + $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", + ['model' => $model]); + if ($row !== false) { + $localboot = $row['bootmethod']; + } +} +if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { + $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!isset($BOOT_METHODS[$localboot])) { + $localboot = 'AUTO'; + } +} +if (isset($BOOT_METHODS[$localboot])) { + // Move preferred method first + $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; + unset($BOOT_METHODS[$localboot]); + $BOOT_METHODS = array_reverse($BOOT_METHODS); +} + +if ($slxExtensions) { + $slxConsoleUpdate = '--update'; +} else { + $slxConsoleUpdate = ''; +} + +$output = <<getMenuDefinition('target', $platform, $slxExtensions); + +$output .= <<getItemsCode($platform); + +/* + +:i5 +chain -a /tftp/memtest.0 passes=1 onepass || goto membad +prompt Memory OK. Press a key. +goto init + +:i8 +set x:int32 0 +:again +console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || +console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || +inc x +iseq \${x} 20 || goto again +prompt DONE. Press dein Knie. +goto slx_menu + +:membad +iseq \${errno} 0x1 || goto memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory is bad. Press a key. +goto init + +:memaborted +params +param scrot \${vram} +imgfetch -a http://132.230.8.113/screen.php##params || +prompt Memory test aborted. Press a key. +goto init + +*/ + +$output .= << 0) { + EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); + } else { + EventLog::info('Imported old PXELinux menu.'); + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..583c5a2b --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/ipxe-update.inc.php @@ -0,0 +1,10 @@ + Property::getServerIp() +]; +$task = Taskmanager::submit('CompileIPxeNew', $data); +if (!isset($task['id'])) + return false; +Property::set('ipxe-task-id', $task['id'], 15); +return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php new file mode 100644 index 00000000..a2eba6ff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/hooks/main-warning.inc.php @@ -0,0 +1,6 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + } + } + + public abstract function supportsMode($mode); + + public abstract function toScript($failLabel, $mode); + + public abstract function toArray(); + + public abstract function addFormFields(&$array); + + /* + * + */ + + /** + * Return a BootEntry instance from the serialized data. + * + * @param string $jsonString serialized entry data + * @return BootEntry|null instance representing boot entry, null on error + */ + public static function fromJson($data) + { + if (is_string($data)) { + $data = json_decode($data, true); + } + if (isset($data['script'])) { + return new CustomBootEntry($data); + } + if (isset($data['executable'])) { + return new StandardBootEntry($data); + } + return null; + } + + public static function newStandardBootEntry($initData) + { + $ret = new StandardBootEntry($initData); + $list = []; + if ($ret->arch() !== StandardBootEntry::EFI) { + $list[] = StandardBootEntry::BIOS; + } + if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { + $list[] = StandardBootEntry::EFI; + } + foreach ($list as $mode) { + if (empty($initData['executable'][$mode])) + return null; + } + return $ret; + } + + public static function newCustomBootEntry($initData) + { + if (empty($initData['script'])) + return null; + return new CustomBootEntry($initData); + } + + /** + * Return a BootEntry instance from database with the given id. + * + * @param string $id + * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success + */ + public static function fromDatabaseId($id) + { + $row = Database::queryFirst("SELECT data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1", ['id' => $id]); + if ($row === false) + return false; + return self::fromJson($row['data']); + } + +} + +class StandardBootEntry extends BootEntry +{ + protected $executable; + protected $initRd; + protected $commandLine; + protected $replace; + protected $autoUnload; + protected $resetConsole; + protected $arch; // Constants below + + const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot + const EFI = 'EFI'; // Only valid for EFI boot + const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry + const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) + + public function __construct($data = false) + { + if ($data instanceof PxeSection) { + // Gets arrayfied below + $this->executable = $data->kernel; + $this->initRd = $data->initrd; + $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; + $this->resetConsole = true; + $this->replace = true; + $this->autoUnload = true; + if (strpos($this->commandLine, ' quiet ') !== false) { + $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; + } + if ($data->ipAppend & 1) { + $this->commandLine .= ' ${ipappend1}'; + } + if ($data->ipAppend & 2) { + $this->commandLine .= ' ${ipappend2}'; + } + if ($data->ipAppend & 4) { + $this->commandLine .= ' SYSUUID=${uuid}'; + } + $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); + } else { + parent::__construct($data); + } + // Convert legacy DB format + foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { + if (!is_array($this->{$key})) { + $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; + } + } + if ($this->arch === null) { + $this->arch = self::AGNOSTIC; + } + } + + public function arch() + { + return $this->arch; + } + + public function supportsMode($mode) + { + if ($mode === $this->arch || $this->arch === self::AGNOSTIC) + return true; + if ($mode === self::BIOS || $mode === self::EFI) { + return $this->arch === self::BOTH; + } + error_log('Unknown iPXE platform: ' . $mode); + return false; + } + + public function toScript($failLabel, $mode) + { + if (!$this->supportsMode($mode)) { + return "prompt Entry doesn't have an executable for mode $mode\n"; + } + if ($this->arch === self::AGNOSTIC) { + $mode = self::BIOS; + } + + $script = ''; + if ($this->resetConsole[$mode]) { + $script .= "console ||\n"; + } + if (!empty($this->initRd[$mode])) { + $script .= "imgfree ||\n"; + if (!is_array($this->initRd[$mode])) { + $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; + } else { + foreach ($this->initRd[$mode] as $initrd) { + $script .= "initrd $initrd || goto $failLabel\n"; + } + } + } + $script .= "boot "; + if ($this->autoUnload[$mode]) { + $script .= "-a "; + } + if ($this->replace[$mode]) { + $script .= "-r "; + } + $script .= $this->executable[$mode]; + $rdBase = basename($this->initRd[$mode]); + if (!empty($this->commandLine[$mode])) { + $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; + } + $script .= " || goto $failLabel\n"; + if ($this->resetConsole[$mode]) { + $script .= "goto start ||\n"; + } + return $script; + } + + public function addFormFields(&$array) + { + $array[$this->arch . '_selected'] = 'selected'; + foreach ([self::BIOS, self::EFI] as $mode) { + $array['entries'][] = [ + 'is' . $mode => true, + 'mode' => $mode, + 'executable' => $this->executable[$mode], + 'initRd' => $this->initRd[$mode], + 'commandLine' => $this->commandLine[$mode], + 'replace_checked' => $this->replace[$mode] ? 'checked' : '', + 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', + 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', + ]; + } + $array['exec_checked'] = 'checked'; + } + + public function toArray() + { + return [ + 'executable' => $this->executable, + 'initRd' => $this->initRd, + 'commandLine' => $this->commandLine, + 'replace' => $this->replace, + 'autoUnload' => $this->autoUnload, + 'resetConsole' => $this->resetConsole, + 'arch' => $this->arch, + ]; + } +} + +class CustomBootEntry extends BootEntry +{ + protected $script; + + public function supportsMode($mode) + { + return true; + } + + public function toScript($failLabel, $mode) + { + return str_replace('%fail%', $failLabel, $this->script) . "\n"; + } + + public function addFormFields(&$array) + { + $array['entry'] = [ + 'script' => $this->script, + ]; + $array['script_checked'] = 'checked'; + } + + public function toArray() + { + return ['script' => $this->script]; + } +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php new file mode 100644 index 00000000..d34839f0 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxe.inc.php @@ -0,0 +1,453 @@ += :start AND endaddr <= :end", compact('start', 'end')); + $locations = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($locations as &$loc) { + if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { + $loc = false; + } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { + continue 2; + } + } + unset($loc); + $locations[] = $row; + } + $menu = PxeLinux::parsePxeLinux($content); + $key = $menu->hash(true); + if (isset($menus[$key])) { + $menuId = $menus[$key]; + $defId = null; + // Figure out the default label, get it's label name + foreach ($menu->sections as $section) { + if ($section->isDefault) { + $defId = $section; + } elseif ($defId === null && $section->label === $menu->timeoutLabel) { + $defId = $section; + } + } + if ($defId !== null) { + $defId = self::cleanLabelFixLocal($defId); + // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId + $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) + INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) + WHERE be.entryid = :id AND me.menuid = :menuid', + ['id' => $defId, 'menuid' => $menuId]); + if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { + $defId = null; // Not found, or is already default - don't override if it's the same + } else { + $defId = $me['menuentryid']; + } + } + } else { + $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); + $menus[$key] = $menuId; + $defId = null; + $importCount++; + } + if ($menuId === false) + continue; + foreach ($locations as $loc) { + if ($loc === false) + continue; + Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :def)', [ + 'menuid' => $menuId, + 'locationid' => $loc['locationid'], + 'def' => $defId, + ]); + } + } + return $importCount; + } + + public static function importLegacyMenu($force = false) + { + if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) + return false; // Already exists + // Now create the default entry + self::createDefaultEntries(); + $prepend = ['bwlp-default' => false, 'localboot' => false]; + $defaultLabel = 'bwlp-default'; + $menuTitle = 'bwLehrpool Bootauswahl'; + $pxeConfig = ''; + $timeoutSecs = 60; + // Try to import any customization + $oldMenu = Property::getBootMenu(); + if (is_array($oldMenu)) { + // + if (isset($oldMenu['timeout'])) { + $timeoutSecs = (int)$oldMenu['timeout']; + } + if (isset($oldMenu['defaultentry'])) { + if ($oldMenu['defaultentry'] === 'net') { + $defaultLabel = 'bwlp-default'; + } elseif ($oldMenu['defaultentry'] === 'hdd') { + $defaultLabel = 'localboot'; + } elseif ($oldMenu['defaultentry'] === 'custom') { + $defaultLabel = 'custom'; + } + } + if (!empty($oldMenu['custom'])) { + $pxeConfig = $oldMenu['custom']; + } + } + $append = [ + '', + 'bwlp-default-dbg' => false, + '', + 'poweroff' => false, + ]; + return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); + } + + /** + * @param PxeMenu $pxeMenu + * @param string $menuTitle + * @param string|false $defaultLabel + * @param $defaultTimeoutSeconds + * @param $prepend + * @param $append + * @return bool|int + */ + private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) + { + $timeoutMs = []; + $menuEntries = $prepend; + settype($menuEntries, 'array'); + if (!empty($pxeMenu)) { + $pxe =& $pxeMenu; + if (!empty($pxe->title)) { + $menuTitle = $pxe->title; + } + if ($pxe->timeoutLabel !== null) { + $defaultLabel = $pxe->timeoutLabel; + } + $timeoutMs[] = $pxe->timeoutMs; + $timeoutMs[] = $pxe->totalTimeoutMs; + foreach ($pxe->sections as $section) { + if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { + $menuEntries['localboot'] = 'localboot'; + continue; + } + if ($section->label === null) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + if (empty($section->kernel)) { + if (!$section->isHidden && !empty($section->title)) { + $menuEntries[] = $section->title; + } + continue; + } + $entry = self::pxe2BootEntry($section); + if ($entry === null) + continue; + $label = self::cleanLabelFixLocal($section); + if ($defaultLabel === $section->label) { + $defaultLabel = $label; + } + $hotkey = MenuEntry::filterKeyName($section->hotkey); + // Create boot entry + $data = $entry->toArray(); + Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:label, :hotkey, :title, 0, :data)', [ + 'label' => $label, + 'hotkey' => $hotkey, + 'title' => self::sanitizeIpxeString($section->title), + 'data' => json_encode($data), + ]); + $menuEntries[$label] = $section; + } + } + if (is_array($append)) { + $menuEntries += $append; + } + if (empty($menuEntries)) + return false; + // Make menu + $timeoutMs = array_filter($timeoutMs, 'is_int'); + if (empty($timeoutMs)) { + $timeoutMs = (int)($defaultTimeoutSeconds * 1000); + } else { + $timeoutMs = min($timeoutMs); + } + $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); + Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) + VALUES (:timeoutms, :title, NULL, :isdefault)", [ + 'title' => self::sanitizeIpxeString($menuTitle), + 'timeoutms' => $timeoutMs, + 'isdefault' => $isDefault, + ]); + $menuId = Database::lastInsertId(); + if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { + $defaultLabel = array_keys($menuEntries)[0]; + } + // Link boot entries to menu + $defaultEntryId = null; + $order = 1000; + foreach ($menuEntries as $label => $entry) { + if (is_string($entry)) { + // Gap entry + Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ + 'menuid' => $menuId, + 'entryid' => null, + 'hotkey' => '', + 'title' => self::sanitizeIpxeString($entry), + 'hidden' => 0, + 'sortval' => $order += 100, + ]); + continue; + } + $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); + if ($data === false) + continue; + $data['pass'] = ''; + $data['hidden'] = 0; + if ($entry instanceof PxeSection) { + $data['hidden'] = (int)$entry->isHidden; + // Prefer explicit data from this imported menu over the defaults + $data['title'] = self::sanitizeIpxeString($entry->title); + if (MenuEntry::getKeyCode($entry->hotkey) !== false) { + $data['hotkey'] = $entry->hotkey; + } + if (!empty($entry->passwd)) { + // Most likely it's a hash so we cannot recover; ask people to reset + $data['pass'] ='please_reset'; + } + } + $data['menuid'] = $menuId; + $data['sortval'] = $order += 100; + $res = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); + if ($res !== false && $label === $defaultLabel) { + $defaultEntryId = Database::lastInsertId(); + } + } + // Now we can set default entry + if (!empty($defaultEntryId)) { + Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", + ['menuid' => $menuId, 'entryid' => $defaultEntryId]); + } + // TODO: masterpw? rather pointless.... + //$oldMenu['masterpasswordclear']; + return $menuId; + } + + private static function createDefaultEntries() + { + $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) + VALUES (:entryid, :hotkey, :title, 1, :data)'; + Database::exec($query, + [ + 'entryid' => 'bwlp-default', + 'hotkey' => 'B', + 'title' => 'bwLehrpool-Umgebung starten', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'bwlp-default-dbg', + 'hotkey' => '', + 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', + 'data' => json_encode([ + 'executable' => '/boot/default/kernel', + 'initRd' => '/boot/default/initramfs-stage31', + 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', + 'replace' => true, + 'autoUnload' => true, + 'resetConsole' => true, + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'localboot', + 'hotkey' => 'L', + 'title' => 'Lokales System starten', + 'data' => json_encode([ + 'script' => 'goto slx_localboot || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'poweroff', + 'hotkey' => 'P', + 'title' => 'Power off', + 'data' => json_encode([ + 'script' => 'poweroff || goto %fail% ||', + ]), + ]); + Database::exec($query, + [ + 'entryid' => 'reboot', + 'hotkey' => 'R', + 'title' => 'Reboot', + 'data' => json_encode([ + 'script' => 'reboot || goto %fail% ||', + ]), + ]); + } + + /** + * Create unique label for a boot entry. It will try to figure out whether + * this is one of our default entries and if not, create a unique label + * representing the menu entry contents. + * Also it patches the entry if it's referencing the local bwlp install + * because side effects. + * + * @param PxeSection $section + * @return string + */ + private static function cleanLabelFixLocal($section) + { + $myip = Property::getServerIp(); + // Detect our "old" entry types + if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) + && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { + // Kernel and initrd match, examine KCL + if ($section->append === 'slxbase=boot/default vga=current quiet splash') { + // Normal + return 'bwlp-default'; + } elseif ($section->append === 'slxbase=boot/default') { + // Debug output + return 'bwlp-default-dbg'; + } else { + // Transform to relative URL, leave KCL, fall through to generic label gen + $section->kernel = '/boot/default/kernel'; + $section->initrd = ['/boot/default/initramfs-stage31']; + } + } + // Generic -- "smart" hash of kernel, initrd and command line + $str = $section->kernel . ' ' . implode(',', $section->initrd); + $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); + sort($array); + $str .= ' ' . implode(' ', $array); + + return 'i-' . substr(md5($str), 0, 12); + } + + /** + * @param PxeSection $section + * @return BootEntry|null The according boot entry, null if it's unparsable + */ + private static function pxe2BootEntry($section) + { + if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { + // Chaining -- create script + $args = preg_split('/\s+/', $section->append); + $script = ''; + $file = false; + for ($i = 0; $i < count($args); ++$i) { + $arg = $args[$i]; + if ($arg === '-c') { // PXELINUX config file option + ++$i; + $script .= "set 209:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-p') { // PXELINUX prefix path option + ++$i; + $script .= "set 210:string {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-t') { // PXELINUX timeout option + ++$i; + $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; + } elseif ($arg === '-o') { // Overriding various DHCP options + ++$i; + if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { + // TODO: 'q' (8byte) unsupported for now + $opt = intval($out[1], 0); + if ($opt > 0 && $opt < 255) { + static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; + $type = $optType[$out[2]]; + $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; + } + } + } elseif ($arg{0} === '-') { + continue; + } elseif ($file === false) { + $file = self::parseFile($arg); + } + } + if ($file !== false) { + $url = parse_url($file); + if (isset($url['host'])) { + $script .= "set next-server {$url['host']} || goto %fail%\n"; + } + if (isset($url['path'])) { + $script .= "set filename {$url['path']} || goto %fail%\n"; + } + $script .= "chain -ar {$file} || goto %fail%\n"; + return new CustomBootEntry(['script' => $script]); + } + return null; + } + // "Normal" entry that should be convertible into a StandardBootEntry + $section->kernel = self::parseFile($section->kernel); + foreach ($section->initrd as &$initrd) { + $initrd = self::parseFile($initrd); + } + return BootEntry::newStandardBootEntry($section); + } + + /** + * Parse PXELINUX file notion. Basically, turn + * server::file into tftp://server/file. + * + * @param string $file + * @return string + */ + private static function parseFile($file) + { + if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { + return 'tftp://' . $out[1] . '/' . $out[2]; + } + return $file; + } + + public static function sanitizeIpxeString($string) + { + return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); + } + + public static function makeMd5Pass($plainpass, $salt) + { + if (empty($plainpass)) + return ''; + return md5(md5($plainpass) . '-' . $salt); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php new file mode 100644 index 00000000..5c1a87d5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php @@ -0,0 +1,142 @@ + $menu]); + if (!is_array($menu)) { + $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; + } + } + $this->menuid = (int)$menu['menuid']; + $this->timeoutMs = (int)$menu['timeoutms']; + $this->title = $menu['title']; + $this->defaultEntryId = $menu['defaultentryid']; + $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, + b.data AS bootentry + FROM serversetup_menuentry e + LEFT JOIN serversetup_bootentry b USING (entryid) + WHERE e.menuid = :menuid + ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $this->items[] = new MenuEntry($row); + } + } + + public function getMenuDefinition($targetVar, $mode, $slxExtensions) + { + $str = "menu -- {$this->title}\n"; + foreach ($this->items as $item) { + $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); + } + if ($this->defaultEntryId === null) { + $defaultLabel = "mx_{$this->menuid}_poweroff"; + } else { + $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; + } + $str .= "choose"; + if ($this->timeoutMs > 0) { + $str .= " --timeout {$this->timeoutMs}"; + } + $str .= " $targetVar || goto $defaultLabel || goto fail\n"; + if ($this->defaultEntryId === null) { + $str .= "goto skip_{$defaultLabel}\n" + . ":{$defaultLabel}\n" + . "poweroff || goto fail\n" + . ":skip_{$defaultLabel}\n"; + } + return $str; + } + + public function getItemsCode($mode) + { + $str = ''; + foreach ($this->items as $item) { + $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); + $str .= "goto slx_menu\n"; + } + return $str; + } + + /* + * + */ + + public static function forLocation($locationId) + { + $chain = null; + if (Module::isAvailable('location')) { + $chain = Location::getLocationRootChain($locationId); + } + if (!empty($chain)) { + $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid + FROM serversetup_menu m + INNER JOIN serversetup_menu_location ml USING (menuid) + WHERE ml.locationid IN (:chain)", ['chain' => $chain]); + if ($res->rowCount() > 0) { + // Make the location id key, preserving order (closest location is first) + $chain = array_flip($chain); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + // Overwrite the value (numeric ascending values, useless) with menu array of according location + $chain[(int)$row['locationid']] = $row; + } + // Use first one that was found + foreach ($chain as $menu) { + if (is_array($menu)) { + return new IPxeMenu($menu); + } + } + // Should never end up here, but we'd just fall through and use the default + } + } + // We're here, no specific menu, use default + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid + FROM serversetup_menu + ORDER BY isdefault DESC LIMIT 1"); + if ($menu === false) { + return new EmptyIPxeMenu; + } + return new IPxeMenu($menu); + } + + public static function forClient($ip, $uuid) + { + $locationId = 0; + if (Module::isAvailable('location')) { + $locationId = Location::getFromIpAndUuid($ip, $uuid); + } + return self::forLocation($locationId); + } + +} + +class EmptyIPxeMenu extends IPxeMenu +{ + + /** @noinspection PhpMissingParentConstructorInspection */ + public function __construct() + { + $this->title = 'No menu defined'; + $this->menuid = -1; + $this->items[] = new MenuEntry([ + 'title' => 'Please create a menu in Server-Setup first' + ]); + $this->items[] = new MenuEntry([ + 'title' => 'Bitte erstellen Sie zunächst ein Menü' + ]); + } + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php new file mode 100644 index 00000000..3ab81862 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/localboot.inc.php @@ -0,0 +1,15 @@ + 'iseq efi ${platform} && exit 1 || sanboot --no-describe', + 'EXIT' => 'exit 1', + 'COMBOOT' => 'chain /tftp/chain.c32 hd0', + 'SANBOOT' => 'sanboot --no-describe', + ]; + +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php new file mode 100644 index 00000000..d243fd23 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php @@ -0,0 +1,177 @@ + $value) { + if (property_exists($this, $key)) { + $this->{$key} = $value; + } + } + $this->hotkey = self::getKeyCode($row['hotkey']); + if (!empty($row['bootentry'])) { + $this->bootEntry = BootEntry::fromJson($row['bootentry']); + } + $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); + } + settype($this->hidden, 'bool'); + settype($this->gap, 'bool'); + settype($this->sortval, 'int'); + settype($this->menuentryid, 'int'); + } + + public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) + { + if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) + return ''; + $str = 'item '; + if ($this->gap) { + $str .= '--gap '; + } else { + if ($this->hidden && $slxExtensions) { + if ($this->hotkey === false) + return ''; // Hidden entries without hotkey are illegal + $str .= '--hidden '; + } + if ($this->hotkey !== false) { + $str .= '--key ' . $this->hotkey . ' '; + } + if ($this->menuentryid == $requestedDefaultId) { + $str .= '--default '; + } + $str .= "{$lblPrefix}_{$this->menuentryid} "; + } + if (empty($this->title)) { + $str .= '${}'; + } else { + $str .= $this->title; + } + return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; + } + + public function getBootEntryScript($lblPrefix, $failLabel, $mode) + { + if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) + return ''; + $str = ":{$lblPrefix}_{$this->menuentryid}\n"; + if (!empty($this->md5pass)) { + $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" + . "set slx_salt {$this->menuentryid} || goto $failLabel\n" + . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" + . "set slx_pw_fail slx_menu || goto $failLabel\n" + . "goto slx_pass_check || goto $failLabel\n" + . ":{$lblPrefix}_ok\n"; + } + return $str . $this->bootEntry->toScript($failLabel, $mode); + } + + /* + * + */ + + private static function getKeyArray() + { + static $data = false; + if ($data === false) { + $data = [ + 'F5' => 0x107e, + 'F6' => 0x127e, + 'F7' => 0x137e, + 'F8' => 0x147e, + 'F9' => 0x157e, + 'F10' => 0x167e, + 'F11' => 0x187e, + 'F12' => 0x197e, + ]; + for ($i = 1; $i <= 26; ++$i) { + $letter = chr(0x40 + $i); + $data['SHIFT_' . $letter] = 0x40 + $i; + if ($letter !== 'C') { + $data['CTRL_' . $letter] = $i; + } + $data[$letter] = 0x60 + $i; + } + for ($i = 0; $i <= 9; ++$i) { + $data[chr(0x30 + $i)] = 0x30 + $i; + } + asort($data, SORT_NUMERIC); + } + return $data; + } + + /** + * Get all the known/supported keys, usable for menu items. + * + * @return string[] list of known key names + */ + public static function getKeyList() + { + return array_keys(self::getKeyArray()); + } + + /** + * Get the key code ipxe expects for the given named + * key. Returns false if the key name is unknown. + * + * @param string $keyName + * @return false|string Key code as hex string, or false if not found + */ + public static function getKeyCode($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return '0x' . dechex($data[$keyName]); + return false; + } + + /** + * @param string $keyName desired key name + * @return string $keyName if it's known, empty string otherwise + */ + public static function filterKeyName($keyName) + { + $data = self::getKeyArray(); + if (isset($data[$keyName])) + return $keyName; + return ''; + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php new file mode 100644 index 00000000..1d022fef --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php @@ -0,0 +1,302 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'com32' => ['string', 'kernel'], + 'pxe' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'sysappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $val = trim($out[2]); + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($val); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($val) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + foreach ($menu->sections as $section) { + $section->mangle(); + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; + + public function hash($fuzzy) + { + $ctx = hash_init('md5'); + if (!$fuzzy) { + hash_update($ctx, $this->title); + hash_update($ctx, $this->timeoutLabel); + } + hash_update($ctx, $this->timeoutMs); + foreach ($this->sections as $section) { + if ($fuzzy) { + hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); + } else { + hash_update($ctx, $section->label); + hash_update($ctx, $section->title); + hash_update($ctx, $section->indent); + hash_update($ctx, $section->helpText); + hash_update($ctx, $section->isDefault); + hash_update($ctx, $section->hotkey); + } + hash_update($ctx, $section->kernel); + hash_update($ctx, $section->append); + hash_update($ctx, $section->ipAppend); + hash_update($ctx, $section->passwd); + hash_update($ctx, $section->isHidden); + hash_update($ctx, $section->isDisabled); + hash_update($ctx, $section->localBoot); + foreach ($section->initrd as $initrd) { + hash_update($ctx, $initrd); + } + } + return hash_final($ctx, false); + } + +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string|string[] initrd to load for the kernel. + * If mangle() has been called this will be an array, + * otherwise it's a comma separated list. + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + /** + * @var string hotkey to trigger item. Only valid after calling mangle() + */ + public $hotkey; + + public function __construct($label) { $this->label = $label; } + + public function mangle() + { + if (($i = strpos($this->title, '^')) !== false) { + $this->hotkey = strtoupper($this->title{$i+1}); + $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); + } + if (strpos($this->append, 'initrd=') !== false) { + $parts = preg_split('/\s+/', $this->append); + $this->append = ''; + for ($i = 0; $i < count($parts); ++$i) { + if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { + if (!empty($this->initrd)) { + $this->initrd .= ','; + } + $this->initrd .= $out[1]; + } else { + $this->append .= ' ' . $parts[$i]; + } + } + $this->append = trim($this->append); + } + if (is_string($this->initrd)) { + $this->initrd = explode(',', $this->initrd); + } elseif (!is_array($this->initrd)) { + $this->initrd = []; + } + } + +} + diff --git a/modules-available/serversetup-bwlp-ipxe/install.inc.php b/modules-available/serversetup-bwlp-ipxe/install.inc.php new file mode 100644 index 00000000..25579c13 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/install.inc.php @@ -0,0 +1,89 @@ +compileTask !== null) + return $this->compileTask; + $this->compileTask = Property::get('ipxe-task-id'); + if ($this->compileTask !== false) { + $this->compileTask = Taskmanager::status($this->compileTask); + if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { + $this->compileTask = false; + } + } + return $this->compileTask; + } + + protected function doPreprocess() + { + User::load(); + + if (!User::isLoggedIn()) { + Message::addError('main.no-permission'); + Util::redirect('?do=Main'); + } + + if (Request::any('action') === 'getimage') { + User::assertPermission("download"); + $this->handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'compile') { + User::assertPermission("edit.address"); + if ($this->getCompileTask() === false) { + Trigger::ipxe(); + } + Util::redirect('?do=serversetup'); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'savebootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->saveBootEntry(); + } + + if ($action === 'deleteBootentry') { + User::assertPermission('ipxe.bootentry.edit'); + $this->deleteBootEntry(); + } + + if ($action === 'savemenu') { + User::assertPermission('ipxe.menu.edit'); + $this->saveMenu(); + } + + if ($action === 'savelocation') { + // Permcheck in function + $this->saveLocationMenu(); + Util::redirect('?do=locations'); + } + + if ($action === 'savelocalboot') { + User::assertPermission('ipxe.localboot.edit'); + $this->saveLocalboot(); + } + + if ($action === 'deleteMenu') { + // Permcheck in function + $this->deleteMenu(); + } + + if ($action === 'setDefaultMenu') { + User::assertPermission('ipxe.menu.edit', 0); + $this->setDefaultMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + + if (User::hasPermission('ipxe.*')) { + Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); + Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); + } + if (User::hasPermission('edit.address')) { + Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); + } + if (User::hasPermission('download')) { + Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); + } + if (User::hasPermission('ipxe.localboot.*')) { + Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); + } + if (Request::get('show') === false) { + $subs = Dashboard::getSubmenus(); + if (empty($subs)) { + User::assertPermission('download'); + } else { + Util::redirect($subs[0]['url']); + } + } + } + + protected function doRender() + { + Render::addTemplate("heading"); + + $task = $this->getCompileTask(); + if ($task !== false) { + $files = []; + if ($task['data'] && $task['data']['files']) { + foreach ($task['data']['files'] as $k => $v) { + $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; + } + } + Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); + } + + switch (Request::get('show')) { + case 'editbootentry': + User::assertPermission('ipxe.bootentry.edit'); + $this->showEditBootEntry(); + break; + case 'editmenu': + User::assertPermission('ipxe.menu.view'); + $this->showEditMenu(); + break; + case 'download': + User::assertPermission('download'); + $this->showDownload(); + break; + case 'menu': + User::assertPermission('ipxe.menu.view'); + $this->showMenuList(); + break; + case 'bootentry': + User::assertPermission('ipxe.bootentry.view'); + $this->showBootentryList(); + break; + case 'address': + User::assertPermission('edit.address'); + $this->showEditAddress(); + break; + case 'assignlocation': + // Permcheck in function + $this->showEditLocation(); + break; + case 'localboot': + User::assertPermission('ipxe.localboot.*'); + $this->showLocalbootConfig(); + break; + default: + Util::redirect('?do=serversetup'); + break; + } + } + + private function showDownload() + { + $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); + usort($list, function ($a, $b) { + return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); + }); + $files = []; + $strings = [ + 'efi' => [Dictionary::translate('dl-efi', true) => 50], + 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51], + 'usb' => [Dictionary::translate('dl-usb', true) => 80], + 'hd' => [Dictionary::translate('dl-hd', true) => 81], + 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82], + 'i386' => [Dictionary::translate('dl-i386', true) => 10], + 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11], + 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60], + 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61], + 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62], + 'snp' => [Dictionary::translate('dl-snp', true) => 63], + ]; + foreach ($list as $file) { + if ($file{0} === '.') + continue; + if (is_file($file)) { + $base = basename($file); + $features = []; + foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { + if (array_key_exists($p, $strings)) { + $features += $strings[$p]; + } + } + asort($features); + $files[] = [ + 'name' => $base, + 'size' => Util::readableFileSize(filesize($file)), + 'modified' => Util::prettyTime(filemtime($file)), + 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', + 'features' => implode(', ', array_keys($features)), + ]; + } + } + Render::addTemplate('download', ['files' => $files]); + } + + private function makeSelectArray($list, $default) + { + $ret = []; + foreach (array_keys($list) as $k) { + $ret[] = [ + 'key' => $k, + 'selected' => ($k === $default ? 'selected' : ''), + ]; + } + return $ret; + } + + private function showLocalbootConfig() + { + // Default setting + $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + $default = 'AUTO'; + } + $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); + // Exceptions + $cutoff = strtotime('-90 days'); + $models = []; + $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( + SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 + WHERE m2.lastseen > :cutoff + GROUP BY systemmodel + ) m + LEFT JOIN serversetup_localboot sl USING (systemmodel) + ORDER BY systemmodel', ['cutoff' => $cutoff]); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); + $models[] = $row; + } + // Output + $data = [ + 'default' => $default, + 'options' => $optionList, + 'exceptions' => $models, + ]; + Render::addTemplate('localboot', $data); + } + + private function showBootentryList() + { + $allowEdit = User::hasPermission('ipxe.bootentry.edit'); + + $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be + INNER JOIN serversetup_menuentry sm USING (entryid) + GROUP BY be.entryid + ORDER BY be.title ASC"); + $bootentryTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $bootentryTable[] = $row; + } + + Render::addTemplate('bootentry-list', array( + 'bootentryTable' => $bootentryTable, + 'allowEdit' => $allowEdit, + )); + } + + private function showMenuList() + { + $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); + + // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, + GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames + FROM serversetup_menu m + LEFT JOIN serversetup_menu_location l USING (menuid) + LEFT JOIN location ll USING (locationid) + GROUP BY menuid + ORDER BY title"); + $menuTable = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['locations'])) { + $locations = []; + $row['allowEdit'] = in_array(0, $allowedEdit); + } else { + $locations = explode(',', $row['locations']); + $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); + } + $row['locationCount'] = empty($locations) ? '' : count($locations); + $menuTable[] = $row; + } + + Render::addTemplate('menu-list', array( + 'menuTable' => $menuTable, + 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) + )); + } + + private function hasMenuPermission($menuid, $permission) + { + $allowedEditLocations = User::getAllowedLocations($permission); + $allowEdit = in_array(0, $allowedEditLocations); + if (!$allowEdit) { + // Get locations + $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location + WHERE menuid = :menuid', compact('menuid')); + if (!empty($locations)) { + $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; + } + } + return $allowEdit; + } + + private function showEditMenu() + { + $id = Request::get('id', false, 'int'); + // if = edit, else = add new + if ($id !== 0) { + $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault + FROM serversetup_menu WHERE menuid = :id", compact('id')); + } else { + $menu = []; + $menu['menuid'] = 0; + $menu['timeoutms'] = 0; + $menu['title'] = ''; + $menu['defaultentryid'] = null; + $menu['isdefault'] = false; + } + + if ($menu === false) { + Message::addError('invalid-menu-id', $id); + Util::redirect('?do=serversetup&show=menu'); + } + $highlight = Request::get('highlight', false, 'string'); + if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + $menu['readonly'] = 'readonly'; + $menu['disabled'] = 'disabled'; + $menu['plainpass'] = ''; + } + if (!User::hasPermission('ipxe.menu.edit', 0)) { + $menu['globalMenuWarning'] = true; + } + + $menu['timeout'] = round($menu['timeoutms'] / 1000); + $menu['entries'] = []; + $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM + serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if ($row['entryid'] == $highlight) { + $row['highlight'] = 'active'; + } + $menu['entries'][] = $row; + } + $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); + $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); + foreach ($menu['entrylist'] as &$bootentry) { + //$bootentry['json'] = $bootentry['data']; + $bootentry['data'] = json_decode($bootentry['data'], true); + if (array_key_exists('arch', $bootentry['data'])) { + $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], + 'initRd' => $bootentry['data']['initRd']['PCBIOS'], + 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); + $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], + 'initRd' => $bootentry['data']['initRd']['EFI'], + 'commandLine' => $bootentry['data']['commandLine']['EFI']); + + if ($bootentry['data']['arch'] === 'PCBIOS') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); + unset($bootentry['data']['EFI']); + } else if ($bootentry['data']['arch'] === 'EFI') { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); + unset($bootentry['data']['PCBIOS']); + } else { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); + } + + } elseif (!array_key_exists('script', $bootentry['data'])) { + $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); + $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], + 'initRd' => $bootentry['data']['initRd'], + 'commandLine' => $bootentry['data']['commandLine']); + } + } + foreach ($menu['entries'] as &$entry) { + $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); + // TODO: plainpass only when permissions + } + + Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); + Render::addTemplate('menu-edit', $menu); + } + + private function showEditBootEntry() + { + $params = []; + $id = Request::get('id', false, 'string'); + if ($id === false) { + $params['exec_checked'] = 'checked'; + $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); + $params['entries'] = [ + ['mode' => 'PCBIOS'], + ['mode' => 'EFI'], + ]; + } else { + // Query existing entry + $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry + WHERE entryid = :id LIMIT 1', ['id' => $id]); + if ($row === false) { + Message::addError('invalid-boot-entry', $id); + Util::redirect('?do=serversetup'); + } + $entry = BootEntry::fromJson($row['data']); + if ($entry === null) { + Message::addError('unknown-bootentry-type', $id); + Util::redirect('?do=serversetup'); + } + $entry->addFormFields($params); + $params['title'] = $row['title']; + $params['entryid'] = $row['entryid']; + $params['builtin'] = $row['builtin']; + $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m + INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) + WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); + } + + Render::addTemplate('ipxe-new-boot-entry', $params); + } + + private function showEditAddress() + { + Render::addTemplate('ipaddress', array( + 'ips' => $this->addrListTask['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', + )); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->addrListTask === false) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying + $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); + } + + if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { + $this->addrListTask['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->addrListTask['data']['addresses']) as $key) { + $item = & $this->addrListTask['data']['addresses'][$key]; + if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { + unset($this->addrListTask['data']['addresses'][$key]); + continue; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); + return true; + } + + private function deleteBootEntry() { + $id = Request::post('deleteid', false, 'string'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); + // TODO: Redirect to &show=bootentry + Message::addSuccess('bootentry-deleted'); + } + + private function setDefaultMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); + Message::addSuccess('menu-set-default'); + } + + private function deleteMenu() + { + $id = Request::post('deleteid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'deleteid'); + return; + } + if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { + Message::addError('locations.no-permission-location', $id); + return; + } + Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); + Message::addSuccess('menu-deleted'); + } + + private function saveMenu() + { + $id = Request::post('menuid', false, 'int'); + if ($id === false) { + Message::addError('main.parameter-missing', 'menuid'); + return; + } + + $insertParams = [ + 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), + 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), + ]; + if ($id === 0) { + Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); + $menu['menuid'] = $id = Database::lastInsertId(); + } else { + $menu = Database::queryFirst("SELECT m.menuid + FROM serversetup_menu m + WHERE menuid = :id", compact('id')); + if ($menu === false) { + Message::addError('no-such-menu', $id); + return; + } + $insertParams['menuid'] = $id; + Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms + WHERE menuid = :menuid', $insertParams); + } + + $keepIds = []; + $entries = Request::post('entry', false, 'array'); + $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); + $defaultEntryId = null; + + if ($entries) { + foreach ($entries as $key => $entry) { + if (!isset($entry['sortval'])) { + error_log(print_r($entry, true)); + continue; + } + // Fallback defaults + $entry += [ + 'entryid' => null, + 'title' => '', + 'hidden' => 0, + 'plainpass' => '', + ]; + $params = [ + 'title' => IPxe::sanitizeIpxeString($entry['title']), + 'sortval' => (int)$entry['sortval'], + 'menuid' => $menu['menuid'], + ]; + if (empty($entry['entryid'])) { + // Spacer + $params += [ + 'entryid' => null, + 'hotkey' => '', + 'hidden' => 0, // Doesn't make any sense + 'plainpass' => '', // Doesn't make any sense + ]; + } else { + $params += [ + 'entryid' => $entry['entryid'], // TODO validate? + 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), + 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) + 'plainpass' => $entry['plainpass'], + ]; + } + if (is_numeric($key)) { + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $key; + } + $keepIds[] = $key; + $params['menuentryid'] = $key; + $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); + $ret = Database::exec('UPDATE serversetup_menuentry + SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, + plainpass = :plainpass, md5pass = :md5pass + WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); + } else { + $ret = Database::exec("INSERT INTO serversetup_menuentry + (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) + VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); + if ($ret) { + $newKey = Database::lastInsertId(); + if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key + $defaultEntryId = $newKey; + } + $keepIds[] = (int)$newKey; + if (!empty($entry['plainpass'])) { + Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ + 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), + 'id' => $newKey, + ]); + } + } + } + + if ($ret === false) { + Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); + } + } + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', + ['menuid' => $menu['menuid'], 'keep' => $keepIds]); + // Set default entry + Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', + ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); + } else { + Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); + } + + Message::addSuccess('menu-saved'); + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none', 'string'); + $valid = false; + foreach ($this->addrListTask['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + + private function saveBootEntry() + { + $oldEntryId = Request::post('entryid', false, 'string'); + $newId = Request::post('newid', false, 'string'); + if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { + Message::addError('main.parameter-empty', 'newid'); + return; + } + $data = Request::post('entry', false); + if (!is_array($data)) { + Message::addError('missing-bootentry-data'); + return; + } + $type = Request::post('type', false, 'string'); + if ($type === 'exec') { + $entry = BootEntry::newStandardBootEntry($data); + } elseif ($type === 'script') { + $entry = BootEntry::newCustomBootEntry($data); + } else { + Message::addError('unknown-bootentry-type', $type); + return; + } + if ($entry === null) { + Message::addError('main.empty-field'); + Util::redirect('?do=serversetup&show=bootentry'); + } + $params = [ + 'entryid' => $newId, + 'title' => Request::post('title', '', 'string'), + 'data' => json_encode($entry->toArray()), + ]; + // New or update? + if (empty($oldEntryId)) { + // New entry + Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) + VALUES (:entryid, :title, 0, :data)', $params); + Message::addSuccess('boot-entry-created', $newId); + } else { + // Edit existing entry + $params['oldid'] = $oldEntryId; + Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data + WHERE entryid = :oldid', $params); + Message::addSuccess('boot-entry-updated', $newId); + } + Util::redirect('?do=serversetup&show=bootentry'); + } + + private function showEditLocation() + { + $locationId = Request::get('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + // List of menu entries + $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); + $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); + // List of menus + $data = [ + 'locationid' => $locationId, + 'locationName' => $loc['locationname'], + ]; + $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m + LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) + INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) + GROUP BY menuid + ORDER BY m.title ASC', ['locationid' => $locationId]); + $menus = []; + $hasDefault = false; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $eids = explode(',', $row['entries']); + $row['entries'] = []; + foreach ($eids as $eid) { + $row['entries'][] = [ + 'id' => $eid, + 'title' => $menuEntries[$eid], + 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), + ]; + } + if ($row['locationid'] !== null) { + $hasDefault = true; + $row['menu_selected'] = 'checked'; + } + $menus[] = $row; + } + if (!$hasDefault) { + $data['default_selected'] = 'checked'; + } + $data['list'] = $menus; + Render::addTemplate('menu-assign-location', $data); + } + + private function saveLocationMenu() + { + $locationId = Request::post('locationid', false, 'int'); + $loc = Location::get($locationId); + if ($loc === false) { + Message::addError('locations.invalid-location-id', $locationId); + return; + } + User::assertPermission('ipxe.menu.assign', $locationId); + $menuId = Request::post('menuid', false, 'int'); + if ($menuId === 0) { + Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', + ['locationid' => $locationId]); + Message::addSuccess('location-use-default', $loc['locationname']); + return; + } + $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); + if ($defaultEntryId === 0) { + $defaultEntryId = null; + } + Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) + VALUES (:menuid, :locationid, :defaultentryid) + ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ + 'menuid' => $menuId, + 'locationid' => $locationId, + 'defaultentryid' => $defaultEntryId + ]); + Message::addSuccess('location-menu-assigned', $loc['locationname']); + } + + private function saveLocalboot() + { + $default = Request::post('default', 'AUTO', 'string'); + if (!array_key_exists($default, Localboot::BOOT_METHODS)) { + Message::addError('localboot-invalid-method', $default); + return; + } + Property::set(Localboot::PROPERTY_KEY, $default); + $overrides = Request::post('override', [], 'array'); + Database::exec('TRUNCATE TABLE serversetup_localboot'); + foreach ($overrides as $model => $mode) { + if (empty($mode)) // No override + continue; + if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { + Message::addWarning('localboot-invalid-method', $mode); + continue; + } + Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) + VALUES (:model, :mode)', compact('model', 'mode')); + } + Message::addSuccess('localboot-saved'); + Util::redirect('?do=serversetup&show=localboot'); + } + +} diff --git a/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json new file mode 100644 index 00000000..33cc9cea --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/permissions/permissions.json @@ -0,0 +1,29 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "ipxe.bootentry.view": { + "location-aware": false + }, + "ipxe.bootentry.edit": { + "location-aware": false + }, + "ipxe.menu.view": { + "location-aware": false + }, + "ipxe.menu.edit": { + "location-aware": false + }, + "ipxe.menu.assign": { + "location-aware": true + }, + "ipxe.localboot.edit": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html new file mode 100644 index 00000000..0cf005c5 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/bootentry-list.html @@ -0,0 +1,83 @@ +

    {{lang_bootentryHead}}

    + +

    + {{lang_bootentryIntro}} +

    + + + + + + + + + + + + + {{#bootentryTable}} + + + + + + + + {{/bootentryTable}} + +
    {{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
    + {{title}} + + {{hotkey}} + + {{refs}} + + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowEdit}} + + {{/allowEdit}} +
    +
    + {{#allowEdit}} + + + {{lang_addBootentry}} + + {{/allowEdit}} +
    + + + + + + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/download.html b/modules-available/serversetup-bwlp-ipxe/templates/download.html new file mode 100644 index 00000000..62064b66 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/download.html @@ -0,0 +1,53 @@ +
    +
    + {{lang_downloadBootImage}} +
    +
    + + {{#files}} + + + + + + + {{/files}} +
    {{name}}{{size}}{{modified}}({{features}})
    +

    + + + {{lang_usbImgHelpBtn}} + +

    +

    + {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} +

    +
    +
    + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/heading.html b/modules-available/serversetup-bwlp-ipxe/templates/heading.html new file mode 100644 index 00000000..e2aa0bff --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/heading.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html new file mode 100644 index 00000000..ea19c417 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipaddress.html @@ -0,0 +1,44 @@ +
    +
    + {{lang_bootAddress}} +
    +
    +
    + {{lang_chooseIP}} +
    +
    + + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
    {{ip}} + {{lang_active}} + + +
    +

    + {{lang_recompileHint}} +

    +
    +
    + + +
    +
    +
    \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html new file mode 100644 index 00000000..7e82b5cc --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe-new-boot-entry.html @@ -0,0 +1,165 @@ +

    {{lang_newBootEntryHead}}

    + +{{#builtin}} +
    + {{lang_editBuiltinWarn}} +
    +{{/builtin}} + +
    +
    + {{lang_bootEntryData}} +
    +
    +
    + + + + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    + {{#entries}} +
    +
    +
    +

    {{mode}}

    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + {{/entries}} +
    +
    + +
    +
    + + +
    +
    + + {{#builtin}} +
    + {{lang_editBuiltinWarn}} +
    + {{/builtin}} + +

    {{lang_referencingMenus}}:

    + + +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html new file mode 100644 index 00000000..344d3905 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/ipxe_update.html @@ -0,0 +1,54 @@ +
    +
    {{lang_menuGeneration}}
    +
    +
    + {{#files}} +
    + + {{name}} +
    + {{/files}} +
    +
    +
    + {{lang_generationFailed}} +
    +
    +
    {{lang_menuGeneration}}
    +
    +
    + + diff --git a/modules-available/serversetup-bwlp-ipxe/templates/localboot.html b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html new file mode 100644 index 00000000..3037de2a --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/localboot.html @@ -0,0 +1,59 @@ +

    {{lang_localBootHead}}

    + +

    {{lang_localBootIntro}}

    + +
    + + + + +
    +
    + + +
    + +
    +

    + {{lang_localBootExceptions}} +

    + + + + + + + {{#exceptions}} + + + + + + {{/exceptions}} +
    {{lang_systemmodel}}{{lang_count}}{{lang_override}}
    {{systemmodel}}{{cnt}} + +
    + +
    + + +
    + +
    diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html new file mode 100644 index 00000000..077d137e --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-assign-location.html @@ -0,0 +1,69 @@ +

    {{lang_assignMenuToLocation}}

    +

    {{locationName}}

    + +
    + + + + + + + + + + + + + + + + + + + {{#list}} + + + + + + {{/list}} + +
    {{lang_menuTitle}}{{lang_menuEntryOverride}}
    +
    + + +
    +
    + {{lang_useDefaultMenu}} +
    +
    + + +
    +
    + {{title}} + + +
    + +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html new file mode 100644 index 00000000..1598a2b7 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-edit.html @@ -0,0 +1,368 @@ +

    {{lang_editMenuHead}}

    + + + + +
    +
    + {{title}} + {{^title}} + {{lang_newMenu}} + {{/title}} +
    +
    +
    + + + + +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + {{lang_seconds}} +
    +
    +
    +
    + + + + + + + + + + + + + + + {{#entries}} + + + + + + + + + + + + + + + + + {{/entries}} + +
    {{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
    + + +
    + + +
    +
    + + + + + + + + + +
    + + +
    +
    + +
    +
    +
    + +
    +
    + {{lang_cancel}} + +
    +
    + + +
    +
    + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html new file mode 100644 index 00000000..545f22a9 --- /dev/null +++ b/modules-available/serversetup-bwlp-ipxe/templates/menu-list.html @@ -0,0 +1,100 @@ +

    {{lang_listOfMenus}}

    + +

    + {{lang_menuListIntro}} +

    + + + + + + + + + + + + + {{#menuTable}} + + + + + + + + {{/menuTable}} + +
    {{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
    + {{title}} + + {{locationCount}} + + {{^isdefault}} + {{#showSetDefault}} +
    + + + +
    + {{/showSetDefault}} + {{/isdefault}} + {{#isdefault}} + + {{/isdefault}} +
    + {{#allowEdit}} + + + + {{/allowEdit}} + + {{#allowDelete}} + + {{/allowDelete}} +
    + + +
    + + + +
    + + +
    + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/config.json b/modules-available/serversetup-bwlp-pxelinux/config.json new file mode 100644 index 00000000..36268c6a --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/config.json @@ -0,0 +1,3 @@ +{ + "category": "main.settings-server" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php new file mode 100644 index 00000000..baa7a1bf --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/hooks/ipxe-update.inc.php @@ -0,0 +1,9 @@ + ['string', 'title'], + 'menu default' => ['true', 'isDefault'], + 'menu hide' => ['true', 'isHidden'], + 'menu disabled' => ['true', 'isDisabled'], + 'menu indent' => ['int', 'indent'], + 'kernel' => ['string', 'kernel'], + 'initrd' => ['string', 'initrd'], + 'append' => ['string', 'append'], + 'ipappend' => ['int', 'ipAppend'], + 'localboot' => ['int', 'localBoot'], + ]; + $globalPropMap = [ + 'timeout' => ['int', 'timeoutMs', 100], + 'totaltimeout' => ['int', 'totalTimeoutMs', 100], + 'menu title' => ['string', 'title'], + 'menu clear' => ['true', 'menuClear'], + 'menu immediate' => ['true', 'immediateHotkeys'], + 'ontimeout' => ['string', 'timeoutLabel'], + ]; + $lines = preg_split('/[\r\n]+/', $input); + $section = null; + $count = count($lines); + for ($li = 0; $li < $count; ++$li) { + $line =& $lines[$li]; + if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) + continue; + $key = trim($out[1]); + $key = strtolower($key); + $key = preg_replace('/\s+/', ' ', $key); + if ($key === 'label') { + if ($section !== null) { + $menu->sections[] = $section; + } + $section = new PxeSection($out[2]); + } elseif ($key === 'menu separator') { + if ($section !== null) { + $menu->sections[] = $section; + $section = null; + } + $menu->sections[] = new PxeSection(null); + } elseif (self::handleKeyword($key, $out[2], $globalPropMap, $menu)) { + continue; + } elseif ($section === null) { + continue; + } elseif ($key === 'text' && strtolower($out[2]) === 'help') { + $text = ''; + while (++$li < $count) { + $line =& $lines[$li]; + if (strtolower(trim($line)) === 'endtext') + break; + $text .= $line . "\n"; + } + $section->helpText = $text; + } elseif (self::handleKeyword($key, $out[2], $sectionPropMap, $section)) { + continue; + } + } + if ($section !== null) { + $menu->sections[] = $section; + } + return $menu; + } + + /** + * Check if keyword is valid and if so, add its interpreted value + * to the given object. The map to look up the keyword has to be passed + * as well as the object to set the value in. Map and object should + * obviously match. + * @param string $key keyword of parsed line + * @param string $val raw value of currently parsed line (empty if not present) + * @param array $map Map in which $key is looked up as key + * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in + * @return bool true if the value was found in the map (and set in the object), false otherwise + */ + private static function handleKeyword($key, $val, $map, $object) + { + if (!isset($map[$key])) + return false; + $opt = $map[$key]; + // opt[0] is the type the value should be cast to; special case "true" means + // this is a bool option that will be set as soon as the keyword is present, + // as it doesn't have any parameters + if ($opt[0] === 'true') { + $val = true; + } else { + settype($val, $opt[0]); + } + // If opt[2] is present it's a multiplier for the value + if (isset($opt[2])) { + $val *= $opt[2]; + } + $object->{$opt[1]} = $val; + return true; + } + +} + +/** + * Class representing a parsed pxelinux menu. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeMenu +{ + /** + * @var string menu title, shown at the top of the menu + */ + public $title; + /** + * @var int initial timeout after which $timeoutLabel would be executed + */ + public $timeoutMs; + /** + * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually + * trigger and launch the $timeoutLabel section + */ + public $totalTimeoutMs; + /** + * @var string label of section which will execute if the timeout expires + */ + public $timeoutLabel; + /** + * @var bool hide menu and just show background after triggering an entry + */ + public $menuClear = false; + /** + * @var bool boot the associated entry directly if its corresponding hotkey is presed instead of just highlighting + */ + public $immediateHotkeys = false; + /** + * @var PxeSection[] list of sections the menu contains + */ + public $sections = []; +} + +/** + * Class representing a parsed pxelinux menu entry. Members + * will be set to their annotated type if present or + * be null otherwise, except for present-only boolean + * options, which will default to false. + */ +class PxeSection +{ + /** + * @var string label used internally in PXEMENU definition to address this entry + */ + public $label; + /** + * @var string MENU LABEL of PXEMENU - title of entry displayed to the user + */ + public $title; + /** + * @var int Number of spaces to prefix the title with + */ + public $indent; + /** + * @var string help text to display when the entry is highlighted + */ + public $helpText; + /** + * @var string Kernel to load + */ + public $kernel; + /** + * @var string initrd to load for the kernel + */ + public $initrd; + /** + * @var string command line options to pass to the kernel + */ + public $append; + /** + * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. + */ + public $ipAppend; + /** + * @var string Password protecting the entry. This is most likely in crypted form. + */ + public $passwd; + /** + * @var bool whether this section is marked as default (booted after timeout) + */ + public $isDefault = false; + /** + * @var bool Menu entry is not visible (can only be triggered by timeout) + */ + public $isHidden = false; + /** + * @var bool Disable this entry, making it unselectable + */ + public $isDisabled = false; + /** + * @var int Value of the LOCALBOOT field + */ + public $localBoot; + + public function __construct($label) { $this->label = $label; } +} + diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json new file mode 100644 index 00000000..3e2cc834 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB-Image nicht gefunden. Generieren Sie das Bootmen\u00fc neu.", + "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", + "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json new file mode 100644 index 00000000..da71d558 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/module.json @@ -0,0 +1,4 @@ +{ + "module_name": "iPXE \/ Boot Menu", + "page_title": "PXE- und Boot-Einstellungen" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json new file mode 100644 index 00000000..98baec3c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "Seite sehen.", + "download": "USB-Image herunterladen.", + "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.", + "edit.menu": "Bootmen\u00fc anpassen." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json new file mode 100644 index 00000000..8d612ab0 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/de/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Aktiv", + "lang_bootAddress": "Boot-Adresse des Servers", + "lang_bootBehavior": "Standard-Bootverhalten", + "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", + "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", + "lang_bootMenu": "Bootmen\u00fc", + "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", + "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", + "lang_customEntry": "Eigener Eintrag", + "lang_downloadImage": "USB-Image herunterladen", + "lang_downloadRufus": "Rufus herunterladen", + "lang_example": "Beispiel", + "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", + "lang_localHDD": "Lokale HDD", + "lang_masterPassword": "Master-Passwort", + "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Booteintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", + "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", + "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.", + "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als", + "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", + "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", + "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "PXE-Binary gebaut", + "lang_seconds": "Sekunden", + "lang_set": "Setzen", + "lang_usbBuilt": "USB-Image gebaut", + "lang_usbImage": "USB-Image", + "lang_usbImgHelp": "Mit dem USB-Image k\u00f6nnen Sie einen bootbaren USB-Stick erstellen, \u00fcber den sich bwLehrpool an Rechnern starten l\u00e4sst, die keinen Netzwerkboot unterst\u00fctzen, bzw. f\u00fcr die keine entsprechende DHCP-Konfiguration vorhanden ist. Dies erfordert dann lediglich, dass in der BIOS-Konfiguration des Rechners USB-Boot zugelassen ist. Der Stick dient dabei lediglich als Einstiegspunkt; es ist nach wie vor ein bwLehrpool-Satellitenserver f\u00fcr den eigentlichen Bootvorgang von N\u00f6ten.", + "lang_usbImgHelpLinux": "Nutzen Sie dd, um das Image auf einen USB-Stick zu schreiben. Das Image enth\u00e4lt bereits eine Partitionstabelle, achten Sie daher darauf, dass Sie das Image z.B. nach \/dev\/sdx schreiben, und nicht nach \/dev\/sdx1", + "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json new file mode 100644 index 00000000..d4ba6905 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/messages.json @@ -0,0 +1,5 @@ +{ + "image-not-found": "USB image not found. Try regenerating the boot menu first.", + "invalid-ip": "No interface is configured with the address {{0}}", + "no-ip-addr-set": "Please set the server's primary IP address" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json new file mode 100644 index 00000000..44d1c519 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/permissions.json @@ -0,0 +1,6 @@ +{ + "access-page": "View page.", + "download": "Download USB Image.", + "edit.address": "Choose boot address of the server.", + "edit.menu": "Customize boot menu." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json new file mode 100644 index 00000000..9bb55f93 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/en/template-tags.json @@ -0,0 +1,33 @@ +{ + "lang_active": "Active", + "lang_bootAddress": "Boot Address of the Server", + "lang_bootBehavior": "Default Boot Behavior", + "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", + "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", + "lang_bootMenu": "Boot Menu", + "lang_bootMenuCreate": "Create Boot Menu", + "lang_chooseIP": "Please select the IP address that the client server will use to boot.", + "lang_customEntry": "Custom entry", + "lang_downloadImage": "Download USB Image", + "lang_downloadRufus": "Download Rufus", + "lang_example": "Example", + "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.", + "lang_localHDD": "Local HDD", + "lang_masterPassword": "Master Password", + "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", + "lang_menuCustom": "Custom Extra Menu", + "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.", + "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as", + "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", + "lang_menuDisplayTime": "Menu Display Time", + "lang_menuGeneration": "Generating boot menu...", + "lang_moduleHeading": "iPXE \/ Boot Menu", + "lang_pxeBuilt": "Built PXE binary", + "lang_seconds": "Seconds", + "lang_set": "Set", + "lang_usbBuilt": "Built USB image", + "lang_usbImage": "USB image", + "lang_usbImgHelp": "The USB image can be used to create a bootable USB stick, which enables you to boot bwLehrpool without changing your DHCP settings or enabling network boot in the clients. The only requirement is that you enable USB boot in the client's BIOS. The USB stick is only used for bootstrapping, the actual bwLehrpool system is still loaded via network from your local bwLehrpool server.", + "lang_usbImgHelpLinux": "On Linux you can simply use dd to write the image to a usb stick. The image already contains a partition table, so make sure you write the image to the device itself and not to an already existing partition (e.g. to \/dev\/sdx not \/dev\/sdx1)", + "lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go." +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json new file mode 100644 index 00000000..65745768 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/messages.json @@ -0,0 +1,3 @@ +{ + "invalid-ip": "Nenhuma interface est\u00e1 configurada com o endere\u00e7o {{0}}" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json new file mode 100644 index 00000000..aeea610c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/module.json @@ -0,0 +1,3 @@ +{ + "module_name": "iPXE \/ Boot Menu" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json new file mode 100644 index 00000000..14788767 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/lang/pt/template-tags.json @@ -0,0 +1,38 @@ +{ + "lang_active": "Ativo", + "lang_bootAddress": "Endere\u00e7o Boot do Servidor", + "lang_bootBehavior": "Comportamento Padr\u00e3o de Boot", + "lang_bootHint": "O menu de boot deve ser recriado ap\u00f3s alterar o endere\u00e7o IP. Geralmente isso \u00e9 feito automaticamente, mas o processo tamb\u00e9m pode ser acionado manualmente na se\u00e7\u00e3o do menu de boot.", + "lang_bootInfo": "Aqui ajustes podem ser feitos na apar\u00eancia do menu de boot.", + "lang_bootMenu": "Menu de Boot", + "lang_bootMenuCreate": "Criar Menu de Boot", + "lang_chooseIP": "Por favor, selecione o endere\u00e7o IP que o servidor do cliente utilizar\u00e1 realizar o boot.", + "lang_close": "Fechar", + "lang_compile": "Compilar", + "lang_compileIso": "Compilar .iso", + "lang_compileKkpxe": "Compilar .kkpxe", + "lang_compileUsb": "Compilar .usb", + "lang_compilingIpxe": "Compilando iPXE", + "lang_customScript": "Script Customizado", + "lang_download": "Baixar", + "lang_example": "Exemplo", + "lang_extension": "Extens\u00e3o", + "lang_ipxeAdv": "Gerar iPXE no Modo Avan\u00e7ado", + "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.", + "lang_ipxeSmp": "Gerar iPXE no Modo Simples", + "lang_ipxeSmpInfo": "Aqui voc\u00ea pode escolher gerar o iPXE escolhendo apenas uma das extens\u00f5es abaixo", + "lang_ipxeWarning": "Se esta for a primeira vez compilando, poder\u00e1 levar entre 1 e 4 minutos para que termine.", + "lang_loading": "Carregando", + "lang_localHDD": "HDD Local", + "lang_menuCustom": "Menu Adicional Customizado", + "lang_menuCustomHint1": "Aqui voc\u00ea tem a oportunidade de adicionar seu pr\u00f3prio c\u00f3digo de menu para o menu PXE exibido, por exemplo, para se referir a outro servidor PXE. O formato corresponde ao formato de menu syslinux.", + "lang_menuCustomHint2": "Voc\u00ea pode criar uma ou mais entradas. Se voc\u00ea quiser criar uma entrada que \u00e9 iniciada automaticamente quando o usu\u00e1rio faz uma sele\u00e7\u00e3o, atribua como", + "lang_menuCustomHint3": "e selecione como o comportamento de boot padr\u00e3o tamb\u00e9m my-entry.", + "lang_menuDisplayTime": "Tempo de Exibi\u00e7\u00e3o do Menu", + "lang_mountIpxe": "Montar iPXE", + "lang_restoreDefault": "Restaurar Padr\u00e3o", + "lang_saveScript": "Salvar Script", + "lang_seconds": "Segundos", + "lang_set": "Definir", + "lang_success": "Arquivo criado com sucesso:" +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/page.inc.php b/modules-available/serversetup-bwlp-pxelinux/page.inc.php new file mode 100644 index 00000000..52b3afe4 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/page.inc.php @@ -0,0 +1,187 @@ +handleGetImage(); + } + + $this->currentMenu = Property::getBootMenu(); + + $action = Request::post('action'); + + if ($action === false) { + $this->currentAddress = Property::getServerIp(); + $this->getLocalAddresses(); + } + + if ($action === 'ip') { + User::assertPermission("edit.address"); + // New address is to be set + $this->getLocalAddresses(); + $this->updateLocalAddress(); + } + + if ($action === 'ipxe') { + User::assertPermission("edit.menu"); + // iPXE stuff changes + $this->updatePxeMenu(); + } + + if (Request::isPost()) { + Util::redirect('?do=serversetup'); + } + + User::assertPermission('access-page'); + } + + protected function doRender() + { + Render::addTemplate("heading"); + $task = Property::get('ipxe-task-id'); + if ($task !== false) { + $task = Taskmanager::status($task); + if (!Taskmanager::isTask($task) || Taskmanager::isFinished($task)) { + $task = false; + } + } + if ($task !== false) { + Render::addTemplate('ipxe_update', array('taskid' => $task['id'])); + } + + Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); + + Render::addTemplate('ipaddress', array( + 'ips' => $this->taskStatus['data']['addresses'], + 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', + 'editAllowed' => User::hasPermission("edit.address"), + 'perms' => $perms, + )); + $data = $this->currentMenu; + if (!User::hasPermission('edit.menu')) { + unset($data['masterpasswordclear']); + } + if (!isset($data['defaultentry'])) { + $data['defaultentry'] = 'net'; + } + if ($data['defaultentry'] === 'net') { + $data['active-net'] = 'checked'; + } + if ($data['defaultentry'] === 'hdd') { + $data['active-hdd'] = 'checked'; + } + if ($data['defaultentry'] === 'custom') { + $data['active-custom'] = 'checked'; + } + $data['perms'] = $perms; + Render::addTemplate('ipxe', $data); + } + + // ----------------------------------------------------------------------------------------------- + + private function getLocalAddresses() + { + $this->taskStatus = Taskmanager::submit('LocalAddressesList', array()); + + if ($this->taskStatus === false) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + if (!Taskmanager::isFinished($this->taskStatus)) { // TODO: Async if just displaying + $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id'], 4000); + } + + if (Taskmanager::isFailed($this->taskStatus) || !isset($this->taskStatus['data']['addresses'])) { + $this->taskStatus['data']['addresses'] = false; + return false; + } + + $sortIp = array(); + foreach (array_keys($this->taskStatus['data']['addresses']) as $key) { + $item = & $this->taskStatus['data']['addresses'][$key]; + if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { + unset($this->taskStatus['data']['addresses'][$key]); + continue; + } + if ($this->currentAddress === $item['ip']) { + $item['default'] = true; + $this->hasIpSet = true; + } + $sortIp[] = $item['ip']; + } + unset($item); + array_multisort($sortIp, SORT_STRING, $this->taskStatus['data']['addresses']); + return true; + } + + private function updateLocalAddress() + { + $newAddress = Request::post('ip', 'none'); + $valid = false; + foreach ($this->taskStatus['data']['addresses'] as $item) { + if ($item['ip'] !== $newAddress) + continue; + $valid = true; + break; + } + if ($valid) { + Property::setServerIp($newAddress); + Util::redirect('?do=ServerSetup'); + } else { + Message::addError('invalid-ip', $newAddress); + } + Util::redirect(); + } + + private function updatePxeMenu() + { + $timeout = Request::post('timeout', 10); + if ($timeout === '') + $timeout = 0; + if (!is_numeric($timeout) || $timeout < 0) { + Message::addError('main.value-invalid', 'timeout', $timeout); + } + $this->currentMenu['defaultentry'] = Request::post('defaultentry', 'net'); + $this->currentMenu['timeout'] = $timeout; + $this->currentMenu['custom'] = Request::post('custom', ''); + $this->currentMenu['masterpasswordclear'] = Request::post('masterpassword', ''); + if (empty($this->currentMenu['masterpasswordclear'])) + $this->currentMenu['masterpassword'] = 'invalid'; + else + $this->currentMenu['masterpassword'] = Crypto::hash6($this->currentMenu['masterpasswordclear']); + Property::setBootMenu($this->currentMenu); + Trigger::ipxe(); + Util::redirect('?do=ServerSetup'); + } + + private function handleGetImage() + { + $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; + if (!is_readable($file)) { + Message::addError('image-not-found'); + return; + } + Header('Content-Type: application/octet-stream'); + Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); + readfile($file); + exit; + } + +} diff --git a/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json new file mode 100644 index 00000000..44927506 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/permissions/permissions.json @@ -0,0 +1,14 @@ +{ + "access-page": { + "location-aware": false + }, + "download": { + "location-aware": false + }, + "edit.address": { + "location-aware": false + }, + "edit.menu": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/heading.html b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html new file mode 100644 index 00000000..d68360f1 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/heading.html @@ -0,0 +1 @@ +

    {{lang_moduleHeading}}

    \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html new file mode 100644 index 00000000..8d73dfac --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipaddress.html @@ -0,0 +1,37 @@ +
    +
    + {{lang_bootAddress}} +
    +
    +
    + {{lang_chooseIP}} +
    +
    + + + + {{#ips}} + + + {{#default}} + + {{/default}} + {{^default}} + + {{/default}} + + {{/ips}} +
    {{ip}} + {{lang_active}} + + +
    +

    + {{lang_bootHint}} +

    +
    +
    +
    \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html new file mode 100644 index 00000000..f4b0b4d3 --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe.html @@ -0,0 +1,117 @@ +
    + + + + +
    +
    + {{lang_bootMenu}} +
    +
    +

    + {{lang_bootInfo}} +

    +
    + +
    + {{lang_bootBehavior}} +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + {{lang_menuDisplayTime}} +
    + + {{lang_seconds}} +
    +
    + +
    + {{lang_masterPassword}} +
    + +
    + {{lang_masterPasswordHelp}} +
    + +
    + {{lang_menuCustom}} + +
    +
    + + +
    +
    + + + + \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html new file mode 100644 index 00000000..c5aafa1c --- /dev/null +++ b/modules-available/serversetup-bwlp-pxelinux/templates/ipxe_update.html @@ -0,0 +1,38 @@ +
    +
    {{lang_menuGeneration}}
    +
    + + +
    +
    + {{lang_generationFailed}} +
    +
    +
    {{lang_menuGeneration}}
    +
    +
    + + diff --git a/modules-available/serversetup-bwlp/api.inc.php b/modules-available/serversetup-bwlp/api.inc.php deleted file mode 100644 index 1df0e6e7..00000000 --- a/modules-available/serversetup-bwlp/api.inc.php +++ /dev/null @@ -1,254 +0,0 @@ - $v) { - $query .= $k . '=' . $v . '&'; - } - //$query = substr($query, 0, -1); - echo << $uuid]); - if ($row !== false && !empty($row['systemmodel'])) { - $model = $row['systemmodel']; - } -} -if ($model === false) { - // Otherwise use what iPXE sent us - function modfilt($str) - { - if (empty($str) || preg_match('/product\s+name|be\s+filled|unknown|default\s+string/i', $str)) - return false; - return trim(preg_replace('/\s+/', ' ', $str)); - } - $manuf = modfilt($manuf); - $product = modfilt($product); - if (!empty($product)) { - $model = $product; - if (!empty($manuf)) { - $model .= " ($manuf)"; - } - } -} -// Query -if ($model !== false) { - $row = Database::queryFirst("SELECT bootmethod FROM serversetup_localboot WHERE systemmodel = :model LIMIT 1", - ['model' => $model]); - if ($row !== false) { - $localboot = $row['bootmethod']; - } -} -if ($localboot === false || !isset($BOOT_METHODS[$localboot])) { - $localboot = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!isset($BOOT_METHODS[$localboot])) { - $localboot = 'AUTO'; - } -} -if (isset($BOOT_METHODS[$localboot])) { - // Move preferred method first - $BOOT_METHODS[] = $BOOT_METHODS[$localboot]; - unset($BOOT_METHODS[$localboot]); - $BOOT_METHODS = array_reverse($BOOT_METHODS); -} - -if ($slxExtensions) { - $slxConsoleUpdate = '--update'; -} else { - $slxConsoleUpdate = ''; -} - -$output = <<getMenuDefinition('target', $platform, $slxExtensions); - -$output .= <<getItemsCode($platform); - -/* - -:i5 -chain -a /tftp/memtest.0 passes=1 onepass || goto membad -prompt Memory OK. Press a key. -goto init - -:i8 -set x:int32 0 -:again -console --left 60 --top 130 --right 67 --bottom 96 --picture bg-load --keep || -console --left 55 --top 88 --right 63 --bottom 64 --picture bg-menu --keep || -inc x -iseq \${x} 20 || goto again -prompt DONE. Press dein Knie. -goto slx_menu - -:membad -iseq \${errno} 0x1 || goto memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory is bad. Press a key. -goto init - -:memaborted -params -param scrot \${vram} -imgfetch -a http://132.230.8.113/screen.php##params || -prompt Memory test aborted. Press a key. -goto init - -*/ - -$output .= << 0) { - EventLog::info('Imported old PXELinux menu, with ' . $num . ' additional IP-range based menus.'); - } else { - EventLog::info('Imported old PXELinux menu.'); - } -} diff --git a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php b/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php deleted file mode 100644 index 166e80a8..00000000 --- a/modules-available/serversetup-bwlp/hooks/ipxe-update.inc.php +++ /dev/null @@ -1,10 +0,0 @@ - Property::getServerIp() -]; -$task = Taskmanager::submit('CompileIPxeNew', $data); -if (!isset($task['id'])) -return false; -Property::set('ipxe-task-id', $task['id'], 15); -return $task['id']; \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php b/modules-available/serversetup-bwlp/hooks/main-warning.inc.php deleted file mode 100644 index a2eba6ff..00000000 --- a/modules-available/serversetup-bwlp/hooks/main-warning.inc.php +++ /dev/null @@ -1,6 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - } - } - - public abstract function supportsMode($mode); - - public abstract function toScript($failLabel, $mode); - - public abstract function toArray(); - - public abstract function addFormFields(&$array); - - /* - * - */ - - /** - * Return a BootEntry instance from the serialized data. - * - * @param string $jsonString serialized entry data - * @return BootEntry|null instance representing boot entry, null on error - */ - public static function fromJson($data) - { - if (is_string($data)) { - $data = json_decode($data, true); - } - if (isset($data['script'])) { - return new CustomBootEntry($data); - } - if (isset($data['executable'])) { - return new StandardBootEntry($data); - } - return null; - } - - public static function newStandardBootEntry($initData) - { - $ret = new StandardBootEntry($initData); - $list = []; - if ($ret->arch() !== StandardBootEntry::EFI) { - $list[] = StandardBootEntry::BIOS; - } - if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) { - $list[] = StandardBootEntry::EFI; - } - foreach ($list as $mode) { - if (empty($initData['executable'][$mode])) - return null; - } - return $ret; - } - - public static function newCustomBootEntry($initData) - { - if (empty($initData['script'])) - return null; - return new CustomBootEntry($initData); - } - - /** - * Return a BootEntry instance from database with the given id. - * - * @param string $id - * @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success - */ - public static function fromDatabaseId($id) - { - $row = Database::queryFirst("SELECT data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1", ['id' => $id]); - if ($row === false) - return false; - return self::fromJson($row['data']); - } - -} - -class StandardBootEntry extends BootEntry -{ - protected $executable; - protected $initRd; - protected $commandLine; - protected $replace; - protected $autoUnload; - protected $resetConsole; - protected $arch; // Constants below - - const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot - const EFI = 'EFI'; // Only valid for EFI boot - const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry - const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry) - - public function __construct($data = false) - { - if ($data instanceof PxeSection) { - // Gets arrayfied below - $this->executable = $data->kernel; - $this->initRd = $data->initrd; - $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' '; - $this->resetConsole = true; - $this->replace = true; - $this->autoUnload = true; - if (strpos($this->commandLine, ' quiet ') !== false) { - $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto'; - } - if ($data->ipAppend & 1) { - $this->commandLine .= ' ${ipappend1}'; - } - if ($data->ipAppend & 2) { - $this->commandLine .= ' ${ipappend2}'; - } - if ($data->ipAppend & 4) { - $this->commandLine .= ' SYSUUID=${uuid}'; - } - $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine)); - } else { - parent::__construct($data); - } - // Convert legacy DB format - foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) { - if (!is_array($this->{$key})) { - $this->{$key} = [ 'PCBIOS' => $this->{$key}, 'EFI' => '' ]; - } - } - if ($this->arch === null) { - $this->arch = self::AGNOSTIC; - } - } - - public function arch() - { - return $this->arch; - } - - public function supportsMode($mode) - { - if ($mode === $this->arch || $this->arch === self::AGNOSTIC) - return true; - if ($mode === self::BIOS || $mode === self::EFI) { - return $this->arch === self::BOTH; - } - error_log('Unknown iPXE platform: ' . $mode); - return false; - } - - public function toScript($failLabel, $mode) - { - if (!$this->supportsMode($mode)) { - return "prompt Entry doesn't have an executable for mode $mode\n"; - } - if ($this->arch === self::AGNOSTIC) { - $mode = self::BIOS; - } - - $script = ''; - if ($this->resetConsole[$mode]) { - $script .= "console ||\n"; - } - if (!empty($this->initRd[$mode])) { - $script .= "imgfree ||\n"; - if (!is_array($this->initRd[$mode])) { - $script .= "initrd {$this->initRd[$mode]} || goto $failLabel\n"; - } else { - foreach ($this->initRd[$mode] as $initrd) { - $script .= "initrd $initrd || goto $failLabel\n"; - } - } - } - $script .= "boot "; - if ($this->autoUnload[$mode]) { - $script .= "-a "; - } - if ($this->replace[$mode]) { - $script .= "-r "; - } - $script .= $this->executable[$mode]; - $rdBase = basename($this->initRd[$mode]); - if (!empty($this->commandLine[$mode])) { - $script .= " initrd=$rdBase {$this->commandLine[$mode]}"; - } - $script .= " || goto $failLabel\n"; - if ($this->resetConsole[$mode]) { - $script .= "goto start ||\n"; - } - return $script; - } - - public function addFormFields(&$array) - { - $array[$this->arch . '_selected'] = 'selected'; - foreach ([self::BIOS, self::EFI] as $mode) { - $array['entries'][] = [ - 'is' . $mode => true, - 'mode' => $mode, - 'executable' => $this->executable[$mode], - 'initRd' => $this->initRd[$mode], - 'commandLine' => $this->commandLine[$mode], - 'replace_checked' => $this->replace[$mode] ? 'checked' : '', - 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '', - 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '', - ]; - } - $array['exec_checked'] = 'checked'; - } - - public function toArray() - { - return [ - 'executable' => $this->executable, - 'initRd' => $this->initRd, - 'commandLine' => $this->commandLine, - 'replace' => $this->replace, - 'autoUnload' => $this->autoUnload, - 'resetConsole' => $this->resetConsole, - 'arch' => $this->arch, - ]; - } -} - -class CustomBootEntry extends BootEntry -{ - protected $script; - - public function supportsMode($mode) - { - return true; - } - - public function toScript($failLabel, $mode) - { - return str_replace('%fail%', $failLabel, $this->script) . "\n"; - } - - public function addFormFields(&$array) - { - $array['entry'] = [ - 'script' => $this->script, - ]; - $array['script_checked'] = 'checked'; - } - - public function toArray() - { - return ['script' => $this->script]; - } -} diff --git a/modules-available/serversetup-bwlp/inc/ipxe.inc.php b/modules-available/serversetup-bwlp/inc/ipxe.inc.php deleted file mode 100644 index d34839f0..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxe.inc.php +++ /dev/null @@ -1,453 +0,0 @@ -= :start AND endaddr <= :end", compact('start', 'end')); - $locations = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - foreach ($locations as &$loc) { - if ($row['startaddr'] <= $loc['startaddr'] && $row['endaddr'] >= $loc['endaddr']) { - $loc = false; - } elseif ($row['startaddr'] >= $loc['startaddr'] && $row['endaddr'] <= $loc['endaddr']) { - continue 2; - } - } - unset($loc); - $locations[] = $row; - } - $menu = PxeLinux::parsePxeLinux($content); - $key = $menu->hash(true); - if (isset($menus[$key])) { - $menuId = $menus[$key]; - $defId = null; - // Figure out the default label, get it's label name - foreach ($menu->sections as $section) { - if ($section->isDefault) { - $defId = $section; - } elseif ($defId === null && $section->label === $menu->timeoutLabel) { - $defId = $section; - } - } - if ($defId !== null) { - $defId = self::cleanLabelFixLocal($defId); - // Confirm it actually exists (it should since the menu seems identical) and get menuEntryId - $me = Database::queryFirst('SELECT m.defaultentryid, me.menuentryid FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry me ON (be.entryid = me.entryid) - INNER JOIN serversetup_menu m ON (m.menuid = me.menuid) - WHERE be.entryid = :id AND me.menuid = :menuid', - ['id' => $defId, 'menuid' => $menuId]); - if ($me === false || $me['defaultentryid'] == $me['menuentryid']) { - $defId = null; // Not found, or is already default - don't override if it's the same - } else { - $defId = $me['menuentryid']; - } - } - } else { - $menuId = self::insertMenu($menu, 'Imported', false, 0, [], []); - $menus[$key] = $menuId; - $defId = null; - $importCount++; - } - if ($menuId === false) - continue; - foreach ($locations as $loc) { - if ($loc === false) - continue; - Database::exec('INSERT IGNORE INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :def)', [ - 'menuid' => $menuId, - 'locationid' => $loc['locationid'], - 'def' => $defId, - ]); - } - } - return $importCount; - } - - public static function importLegacyMenu($force = false) - { - if (!$force && false !== Database::queryFirst("SELECT entryid FROM serversetup_bootentry WHERE entryid = 'bwlp-default'")) - return false; // Already exists - // Now create the default entry - self::createDefaultEntries(); - $prepend = ['bwlp-default' => false, 'localboot' => false]; - $defaultLabel = 'bwlp-default'; - $menuTitle = 'bwLehrpool Bootauswahl'; - $pxeConfig = ''; - $timeoutSecs = 60; - // Try to import any customization - $oldMenu = Property::getBootMenu(); - if (is_array($oldMenu)) { - // - if (isset($oldMenu['timeout'])) { - $timeoutSecs = (int)$oldMenu['timeout']; - } - if (isset($oldMenu['defaultentry'])) { - if ($oldMenu['defaultentry'] === 'net') { - $defaultLabel = 'bwlp-default'; - } elseif ($oldMenu['defaultentry'] === 'hdd') { - $defaultLabel = 'localboot'; - } elseif ($oldMenu['defaultentry'] === 'custom') { - $defaultLabel = 'custom'; - } - } - if (!empty($oldMenu['custom'])) { - $pxeConfig = $oldMenu['custom']; - } - } - $append = [ - '', - 'bwlp-default-dbg' => false, - '', - 'poweroff' => false, - ]; - return self::insertMenu(PxeLinux::parsePxeLinux($pxeConfig), $menuTitle, $defaultLabel, $timeoutSecs, $prepend, $append); - } - - /** - * @param PxeMenu $pxeMenu - * @param string $menuTitle - * @param string|false $defaultLabel - * @param $defaultTimeoutSeconds - * @param $prepend - * @param $append - * @return bool|int - */ - private static function insertMenu($pxeMenu, $menuTitle, $defaultLabel, $defaultTimeoutSeconds, $prepend, $append) - { - $timeoutMs = []; - $menuEntries = $prepend; - settype($menuEntries, 'array'); - if (!empty($pxeMenu)) { - $pxe =& $pxeMenu; - if (!empty($pxe->title)) { - $menuTitle = $pxe->title; - } - if ($pxe->timeoutLabel !== null) { - $defaultLabel = $pxe->timeoutLabel; - } - $timeoutMs[] = $pxe->timeoutMs; - $timeoutMs[] = $pxe->totalTimeoutMs; - foreach ($pxe->sections as $section) { - if ($section->localBoot || preg_match('/chain\.c32$/i', $section->kernel)) { - $menuEntries['localboot'] = 'localboot'; - continue; - } - if ($section->label === null) { - if (!$section->isHidden && !empty($section->title)) { - $menuEntries[] = $section->title; - } - continue; - } - if (empty($section->kernel)) { - if (!$section->isHidden && !empty($section->title)) { - $menuEntries[] = $section->title; - } - continue; - } - $entry = self::pxe2BootEntry($section); - if ($entry === null) - continue; - $label = self::cleanLabelFixLocal($section); - if ($defaultLabel === $section->label) { - $defaultLabel = $label; - } - $hotkey = MenuEntry::filterKeyName($section->hotkey); - // Create boot entry - $data = $entry->toArray(); - Database::exec('INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:label, :hotkey, :title, 0, :data)', [ - 'label' => $label, - 'hotkey' => $hotkey, - 'title' => self::sanitizeIpxeString($section->title), - 'data' => json_encode($data), - ]); - $menuEntries[$label] = $section; - } - } - if (is_array($append)) { - $menuEntries += $append; - } - if (empty($menuEntries)) - return false; - // Make menu - $timeoutMs = array_filter($timeoutMs, 'is_int'); - if (empty($timeoutMs)) { - $timeoutMs = (int)($defaultTimeoutSeconds * 1000); - } else { - $timeoutMs = min($timeoutMs); - } - $isDefault = (int)(Database::queryFirst('SELECT menuid FROM serversetup_menu WHERE isdefault = 1') === false); - Database::exec("INSERT INTO serversetup_menu (timeoutms, title, defaultentryid, isdefault) - VALUES (:timeoutms, :title, NULL, :isdefault)", [ - 'title' => self::sanitizeIpxeString($menuTitle), - 'timeoutms' => $timeoutMs, - 'isdefault' => $isDefault, - ]); - $menuId = Database::lastInsertId(); - if (!array_key_exists($defaultLabel, $menuEntries) && $timeoutMs > 0) { - $defaultLabel = array_keys($menuEntries)[0]; - } - // Link boot entries to menu - $defaultEntryId = null; - $order = 1000; - foreach ($menuEntries as $label => $entry) { - if (is_string($entry)) { - // Gap entry - Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, '', '')", [ - 'menuid' => $menuId, - 'entryid' => null, - 'hotkey' => '', - 'title' => self::sanitizeIpxeString($entry), - 'hidden' => 0, - 'sortval' => $order += 100, - ]); - continue; - } - $data = Database::queryFirst("SELECT entryid, hotkey, title FROM serversetup_bootentry WHERE entryid = :entryid", ['entryid' => $label]); - if ($data === false) - continue; - $data['pass'] = ''; - $data['hidden'] = 0; - if ($entry instanceof PxeSection) { - $data['hidden'] = (int)$entry->isHidden; - // Prefer explicit data from this imported menu over the defaults - $data['title'] = self::sanitizeIpxeString($entry->title); - if (MenuEntry::getKeyCode($entry->hotkey) !== false) { - $data['hotkey'] = $entry->hotkey; - } - if (!empty($entry->passwd)) { - // Most likely it's a hash so we cannot recover; ask people to reset - $data['pass'] ='please_reset'; - } - } - $data['menuid'] = $menuId; - $data['sortval'] = $order += 100; - $res = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :pass, :pass)", $data); - if ($res !== false && $label === $defaultLabel) { - $defaultEntryId = Database::lastInsertId(); - } - } - // Now we can set default entry - if (!empty($defaultEntryId)) { - Database::exec("UPDATE serversetup_menu SET defaultentryid = :entryid WHERE menuid = :menuid", - ['menuid' => $menuId, 'entryid' => $defaultEntryId]); - } - // TODO: masterpw? rather pointless.... - //$oldMenu['masterpasswordclear']; - return $menuId; - } - - private static function createDefaultEntries() - { - $query = 'INSERT IGNORE INTO serversetup_bootentry (entryid, hotkey, title, builtin, data) - VALUES (:entryid, :hotkey, :title, 1, :data)'; - Database::exec($query, - [ - 'entryid' => 'bwlp-default', - 'hotkey' => 'B', - 'title' => 'bwLehrpool-Umgebung starten', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default quiet splash loglevel=5 rd.systemd.show_status=auto intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'bwlp-default-dbg', - 'hotkey' => '', - 'title' => 'bwLehrpool-Umgebung starten (nosplash, debug)', - 'data' => json_encode([ - 'executable' => '/boot/default/kernel', - 'initRd' => '/boot/default/initramfs-stage31', - 'commandLine' => 'slxbase=boot/default loglevel=7 intel_iommu=igfx_off ${ipappend1} ${ipappend2}', - 'replace' => true, - 'autoUnload' => true, - 'resetConsole' => true, - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'localboot', - 'hotkey' => 'L', - 'title' => 'Lokales System starten', - 'data' => json_encode([ - 'script' => 'goto slx_localboot || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'poweroff', - 'hotkey' => 'P', - 'title' => 'Power off', - 'data' => json_encode([ - 'script' => 'poweroff || goto %fail% ||', - ]), - ]); - Database::exec($query, - [ - 'entryid' => 'reboot', - 'hotkey' => 'R', - 'title' => 'Reboot', - 'data' => json_encode([ - 'script' => 'reboot || goto %fail% ||', - ]), - ]); - } - - /** - * Create unique label for a boot entry. It will try to figure out whether - * this is one of our default entries and if not, create a unique label - * representing the menu entry contents. - * Also it patches the entry if it's referencing the local bwlp install - * because side effects. - * - * @param PxeSection $section - * @return string - */ - private static function cleanLabelFixLocal($section) - { - $myip = Property::getServerIp(); - // Detect our "old" entry types - if (count($section->initrd) === 1 && preg_match(",$myip/boot/default/kernel\$,", $section->kernel) - && preg_match(",$myip/boot/default/initramfs-stage31\$,", $section->initrd[0])) { - // Kernel and initrd match, examine KCL - if ($section->append === 'slxbase=boot/default vga=current quiet splash') { - // Normal - return 'bwlp-default'; - } elseif ($section->append === 'slxbase=boot/default') { - // Debug output - return 'bwlp-default-dbg'; - } else { - // Transform to relative URL, leave KCL, fall through to generic label gen - $section->kernel = '/boot/default/kernel'; - $section->initrd = ['/boot/default/initramfs-stage31']; - } - } - // Generic -- "smart" hash of kernel, initrd and command line - $str = $section->kernel . ' ' . implode(',', $section->initrd); - $array = preg_split('/\s+/', $section->append, -1, PREG_SPLIT_NO_EMPTY); - sort($array); - $str .= ' ' . implode(' ', $array); - - return 'i-' . substr(md5($str), 0, 12); - } - - /** - * @param PxeSection $section - * @return BootEntry|null The according boot entry, null if it's unparsable - */ - private static function pxe2BootEntry($section) - { - if (preg_match('/(pxechain\.com|pxechn\.c32)$/i', $section->kernel)) { - // Chaining -- create script - $args = preg_split('/\s+/', $section->append); - $script = ''; - $file = false; - for ($i = 0; $i < count($args); ++$i) { - $arg = $args[$i]; - if ($arg === '-c') { // PXELINUX config file option - ++$i; - $script .= "set 209:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-p') { // PXELINUX prefix path option - ++$i; - $script .= "set 210:string {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-t') { // PXELINUX timeout option - ++$i; - $script .= "set 211:int32 {$args[$i]} || goto %fail%\n"; - } elseif ($arg === '-o') { // Overriding various DHCP options - ++$i; - if (preg_match('/^((?:0x)?[a-f0-9]{1,4})\.([bwlsh])=(.*)$/i', $args[$i], $out)) { - // TODO: 'q' (8byte) unsupported for now - $opt = intval($out[1], 0); - if ($opt > 0 && $opt < 255) { - static $optType = ['b' => 'uint8', 'w' => 'uint16', 'l' => 'int32', 's' => 'string', 'h' => 'hex']; - $type = $optType[$out[2]]; - $script .= "set {$opt}:{$type} {$args[$i]} || goto %fail%\n"; - } - } - } elseif ($arg{0} === '-') { - continue; - } elseif ($file === false) { - $file = self::parseFile($arg); - } - } - if ($file !== false) { - $url = parse_url($file); - if (isset($url['host'])) { - $script .= "set next-server {$url['host']} || goto %fail%\n"; - } - if (isset($url['path'])) { - $script .= "set filename {$url['path']} || goto %fail%\n"; - } - $script .= "chain -ar {$file} || goto %fail%\n"; - return new CustomBootEntry(['script' => $script]); - } - return null; - } - // "Normal" entry that should be convertible into a StandardBootEntry - $section->kernel = self::parseFile($section->kernel); - foreach ($section->initrd as &$initrd) { - $initrd = self::parseFile($initrd); - } - return BootEntry::newStandardBootEntry($section); - } - - /** - * Parse PXELINUX file notion. Basically, turn - * server::file into tftp://server/file. - * - * @param string $file - * @return string - */ - private static function parseFile($file) - { - if (preg_match(',^([^:/]+)::(.*)$,', $file, $out)) { - return 'tftp://' . $out[1] . '/' . $out[2]; - } - return $file; - } - - public static function sanitizeIpxeString($string) - { - return str_replace(['&', '|', ';', '$', "\r", "\n"], ['+', '/', ':', 'S', ' ', ' '], $string); - } - - public static function makeMd5Pass($plainpass, $salt) - { - if (empty($plainpass)) - return ''; - return md5(md5($plainpass) . '-' . $salt); - } - -} diff --git a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php deleted file mode 100644 index 5c1a87d5..00000000 --- a/modules-available/serversetup-bwlp/inc/ipxemenu.inc.php +++ /dev/null @@ -1,142 +0,0 @@ - $menu]); - if (!is_array($menu)) { - $menu = ['menuid' => 'foo', 'title' => 'Invalid Menu ID: ' . (int)$menu]; - } - } - $this->menuid = (int)$menu['menuid']; - $this->timeoutMs = (int)$menu['timeoutms']; - $this->title = $menu['title']; - $this->defaultEntryId = $menu['defaultentryid']; - $res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass, - b.data AS bootentry - FROM serversetup_menuentry e - LEFT JOIN serversetup_bootentry b USING (entryid) - WHERE e.menuid = :menuid - ORDER BY e.sortval ASC, e.title ASC", ['menuid' => $menu['menuid']]); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $this->items[] = new MenuEntry($row); - } - } - - public function getMenuDefinition($targetVar, $mode, $slxExtensions) - { - $str = "menu -- {$this->title}\n"; - foreach ($this->items as $item) { - $str .= $item->getMenuItemScript("m_{$this->menuid}", $this->defaultEntryId, $mode, $slxExtensions); - } - if ($this->defaultEntryId === null) { - $defaultLabel = "mx_{$this->menuid}_poweroff"; - } else { - $defaultLabel = "m_{$this->menuid}_{$this->defaultEntryId}"; - } - $str .= "choose"; - if ($this->timeoutMs > 0) { - $str .= " --timeout {$this->timeoutMs}"; - } - $str .= " $targetVar || goto $defaultLabel || goto fail\n"; - if ($this->defaultEntryId === null) { - $str .= "goto skip_{$defaultLabel}\n" - . ":{$defaultLabel}\n" - . "poweroff || goto fail\n" - . ":skip_{$defaultLabel}\n"; - } - return $str; - } - - public function getItemsCode($mode) - { - $str = ''; - foreach ($this->items as $item) { - $str .= $item->getBootEntryScript("m_{$this->menuid}", 'fail', $mode); - $str .= "goto slx_menu\n"; - } - return $str; - } - - /* - * - */ - - public static function forLocation($locationId) - { - $chain = null; - if (Module::isAvailable('location')) { - $chain = Location::getLocationRootChain($locationId); - } - if (!empty($chain)) { - $res = Database::simpleQuery("SELECT m.menuid, m.timeoutms, m.title, IFNULL(ml.defaultentryid, m.defaultentryid) AS defaultentryid, ml.locationid - FROM serversetup_menu m - INNER JOIN serversetup_menu_location ml USING (menuid) - WHERE ml.locationid IN (:chain)", ['chain' => $chain]); - if ($res->rowCount() > 0) { - // Make the location id key, preserving order (closest location is first) - $chain = array_flip($chain); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - // Overwrite the value (numeric ascending values, useless) with menu array of according location - $chain[(int)$row['locationid']] = $row; - } - // Use first one that was found - foreach ($chain as $menu) { - if (is_array($menu)) { - return new IPxeMenu($menu); - } - } - // Should never end up here, but we'd just fall through and use the default - } - } - // We're here, no specific menu, use default - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid - FROM serversetup_menu - ORDER BY isdefault DESC LIMIT 1"); - if ($menu === false) { - return new EmptyIPxeMenu; - } - return new IPxeMenu($menu); - } - - public static function forClient($ip, $uuid) - { - $locationId = 0; - if (Module::isAvailable('location')) { - $locationId = Location::getFromIpAndUuid($ip, $uuid); - } - return self::forLocation($locationId); - } - -} - -class EmptyIPxeMenu extends IPxeMenu -{ - - /** @noinspection PhpMissingParentConstructorInspection */ - public function __construct() - { - $this->title = 'No menu defined'; - $this->menuid = -1; - $this->items[] = new MenuEntry([ - 'title' => 'Please create a menu in Server-Setup first' - ]); - $this->items[] = new MenuEntry([ - 'title' => 'Bitte erstellen Sie zunächst ein Menü' - ]); - } - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/localboot.inc.php b/modules-available/serversetup-bwlp/inc/localboot.inc.php deleted file mode 100644 index 3ab81862..00000000 --- a/modules-available/serversetup-bwlp/inc/localboot.inc.php +++ /dev/null @@ -1,15 +0,0 @@ - 'iseq efi ${platform} && exit 1 || sanboot --no-describe', - 'EXIT' => 'exit 1', - 'COMBOOT' => 'chain /tftp/chain.c32 hd0', - 'SANBOOT' => 'sanboot --no-describe', - ]; - -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/inc/menuentry.inc.php b/modules-available/serversetup-bwlp/inc/menuentry.inc.php deleted file mode 100644 index d243fd23..00000000 --- a/modules-available/serversetup-bwlp/inc/menuentry.inc.php +++ /dev/null @@ -1,177 +0,0 @@ - $value) { - if (property_exists($this, $key)) { - $this->{$key} = $value; - } - } - $this->hotkey = self::getKeyCode($row['hotkey']); - if (!empty($row['bootentry'])) { - $this->bootEntry = BootEntry::fromJson($row['bootentry']); - } - $this->gap = (array_key_exists('entryid', $row) && $row['entryid'] === null); - } - settype($this->hidden, 'bool'); - settype($this->gap, 'bool'); - settype($this->sortval, 'int'); - settype($this->menuentryid, 'int'); - } - - public function getMenuItemScript($lblPrefix, $requestedDefaultId, $mode, $slxExtensions) - { - if ($this->bootEntry !== null && !$this->bootEntry->supportsMode($mode)) - return ''; - $str = 'item '; - if ($this->gap) { - $str .= '--gap '; - } else { - if ($this->hidden && $slxExtensions) { - if ($this->hotkey === false) - return ''; // Hidden entries without hotkey are illegal - $str .= '--hidden '; - } - if ($this->hotkey !== false) { - $str .= '--key ' . $this->hotkey . ' '; - } - if ($this->menuentryid == $requestedDefaultId) { - $str .= '--default '; - } - $str .= "{$lblPrefix}_{$this->menuentryid} "; - } - if (empty($this->title)) { - $str .= '${}'; - } else { - $str .= $this->title; - } - return $str . " || prompt Could not create menu item for {$lblPrefix}_{$this->menuentryid}\n"; - } - - public function getBootEntryScript($lblPrefix, $failLabel, $mode) - { - if ($this->bootEntry === null || !$this->bootEntry->supportsMode($mode)) - return ''; - $str = ":{$lblPrefix}_{$this->menuentryid}\n"; - if (!empty($this->md5pass)) { - $str .= "set slx_hash {$this->md5pass} || goto $failLabel\n" - . "set slx_salt {$this->menuentryid} || goto $failLabel\n" - . "set slx_pw_ok {$lblPrefix}_ok || goto $failLabel\n" - . "set slx_pw_fail slx_menu || goto $failLabel\n" - . "goto slx_pass_check || goto $failLabel\n" - . ":{$lblPrefix}_ok\n"; - } - return $str . $this->bootEntry->toScript($failLabel, $mode); - } - - /* - * - */ - - private static function getKeyArray() - { - static $data = false; - if ($data === false) { - $data = [ - 'F5' => 0x107e, - 'F6' => 0x127e, - 'F7' => 0x137e, - 'F8' => 0x147e, - 'F9' => 0x157e, - 'F10' => 0x167e, - 'F11' => 0x187e, - 'F12' => 0x197e, - ]; - for ($i = 1; $i <= 26; ++$i) { - $letter = chr(0x40 + $i); - $data['SHIFT_' . $letter] = 0x40 + $i; - if ($letter !== 'C') { - $data['CTRL_' . $letter] = $i; - } - $data[$letter] = 0x60 + $i; - } - for ($i = 0; $i <= 9; ++$i) { - $data[chr(0x30 + $i)] = 0x30 + $i; - } - asort($data, SORT_NUMERIC); - } - return $data; - } - - /** - * Get all the known/supported keys, usable for menu items. - * - * @return string[] list of known key names - */ - public static function getKeyList() - { - return array_keys(self::getKeyArray()); - } - - /** - * Get the key code ipxe expects for the given named - * key. Returns false if the key name is unknown. - * - * @param string $keyName - * @return false|string Key code as hex string, or false if not found - */ - public static function getKeyCode($keyName) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return '0x' . dechex($data[$keyName]); - return false; - } - - /** - * @param string $keyName desired key name - * @return string $keyName if it's known, empty string otherwise - */ - public static function filterKeyName($keyName) - { - $data = self::getKeyArray(); - if (isset($data[$keyName])) - return $keyName; - return ''; - } - -} diff --git a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php b/modules-available/serversetup-bwlp/inc/pxelinux.inc.php deleted file mode 100644 index 1d022fef..00000000 --- a/modules-available/serversetup-bwlp/inc/pxelinux.inc.php +++ /dev/null @@ -1,302 +0,0 @@ - ['string', 'title'], - 'menu default' => ['true', 'isDefault'], - 'menu hide' => ['true', 'isHidden'], - 'menu disabled' => ['true', 'isDisabled'], - 'menu indent' => ['int', 'indent'], - 'kernel' => ['string', 'kernel'], - 'com32' => ['string', 'kernel'], - 'pxe' => ['string', 'kernel'], - 'initrd' => ['string', 'initrd'], - 'append' => ['string', 'append'], - 'ipappend' => ['int', 'ipAppend'], - 'sysappend' => ['int', 'ipAppend'], - 'localboot' => ['int', 'localBoot'], - ]; - $globalPropMap = [ - 'timeout' => ['int', 'timeoutMs', 100], - 'totaltimeout' => ['int', 'totalTimeoutMs', 100], - 'menu title' => ['string', 'title'], - 'menu clear' => ['true', 'menuClear'], - 'menu immediate' => ['true', 'immediateHotkeys'], - 'ontimeout' => ['string', 'timeoutLabel'], - ]; - $lines = preg_split('/[\r\n]+/', $input); - $section = null; - $count = count($lines); - for ($li = 0; $li < $count; ++$li) { - $line =& $lines[$li]; - if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out)) - continue; - $val = trim($out[2]); - $key = trim($out[1]); - $key = strtolower($key); - $key = preg_replace('/\s+/', ' ', $key); - if ($key === 'label') { - if ($section !== null) { - $menu->sections[] = $section; - } - $section = new PxeSection($val); - } elseif ($key === 'menu separator') { - if ($section !== null) { - $menu->sections[] = $section; - $section = null; - } - $menu->sections[] = new PxeSection(null); - } elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) { - continue; - } elseif ($section === null) { - continue; - } elseif ($key === 'text' && strtolower($val) === 'help') { - $text = ''; - while (++$li < $count) { - $line =& $lines[$li]; - if (strtolower(trim($line)) === 'endtext') - break; - $text .= $line . "\n"; - } - $section->helpText = $text; - } elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) { - continue; - } - } - if ($section !== null) { - $menu->sections[] = $section; - } - foreach ($menu->sections as $section) { - $section->mangle(); - } - return $menu; - } - - /** - * Check if keyword is valid and if so, add its interpreted value - * to the given object. The map to look up the keyword has to be passed - * as well as the object to set the value in. Map and object should - * obviously match. - * @param string $key keyword of parsed line - * @param string $val raw value of currently parsed line (empty if not present) - * @param array $map Map in which $key is looked up as key - * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in - * @return bool true if the value was found in the map (and set in the object), false otherwise - */ - private static function handleKeyword($key, $val, $map, $object) - { - if (!isset($map[$key])) - return false; - $opt = $map[$key]; - // opt[0] is the type the value should be cast to; special case "true" means - // this is a bool option that will be set as soon as the keyword is present, - // as it doesn't have any parameters - if ($opt[0] === 'true') { - $val = true; - } else { - settype($val, $opt[0]); - } - // If opt[2] is present it's a multiplier for the value - if (isset($opt[2])) { - $val *= $opt[2]; - } - $object->{$opt[1]} = $val; - return true; - } - -} - -/** - * Class representing a parsed pxelinux menu. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeMenu -{ - - /** - * @var string menu title, shown at the top of the menu - */ - public $title; - /** - * @var int initial timeout after which $timeoutLabel would be executed - */ - public $timeoutMs; - /** - * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually - * trigger and launch the $timeoutLabel section - */ - public $totalTimeoutMs; - /** - * @var string label of section which will execute if the timeout expires - */ - public $timeoutLabel; - /** - * @var bool hide menu and just show background after triggering an entry - */ - public $menuClear = false; - /** - * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting - */ - public $immediateHotkeys = false; - /** - * @var PxeSection[] list of sections the menu contains - */ - public $sections = []; - - public function hash($fuzzy) - { - $ctx = hash_init('md5'); - if (!$fuzzy) { - hash_update($ctx, $this->title); - hash_update($ctx, $this->timeoutLabel); - } - hash_update($ctx, $this->timeoutMs); - foreach ($this->sections as $section) { - if ($fuzzy) { - hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title))); - } else { - hash_update($ctx, $section->label); - hash_update($ctx, $section->title); - hash_update($ctx, $section->indent); - hash_update($ctx, $section->helpText); - hash_update($ctx, $section->isDefault); - hash_update($ctx, $section->hotkey); - } - hash_update($ctx, $section->kernel); - hash_update($ctx, $section->append); - hash_update($ctx, $section->ipAppend); - hash_update($ctx, $section->passwd); - hash_update($ctx, $section->isHidden); - hash_update($ctx, $section->isDisabled); - hash_update($ctx, $section->localBoot); - foreach ($section->initrd as $initrd) { - hash_update($ctx, $initrd); - } - } - return hash_final($ctx, false); - } - -} - -/** - * Class representing a parsed pxelinux menu entry. Members - * will be set to their annotated type if present or - * be null otherwise, except for present-only boolean - * options, which will default to false. - */ -class PxeSection -{ - - /** - * @var string label used internally in PXEMENU definition to address this entry - */ - public $label; - /** - * @var string MENU LABEL of PXEMENU - title of entry displayed to the user - */ - public $title; - /** - * @var int Number of spaces to prefix the title with - */ - public $indent; - /** - * @var string help text to display when the entry is highlighted - */ - public $helpText; - /** - * @var string Kernel to load - */ - public $kernel; - /** - * @var string|string[] initrd to load for the kernel. - * If mangle() has been called this will be an array, - * otherwise it's a comma separated list. - */ - public $initrd; - /** - * @var string command line options to pass to the kernel - */ - public $append; - /** - * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2. - */ - public $ipAppend; - /** - * @var string Password protecting the entry. This is most likely in crypted form. - */ - public $passwd; - /** - * @var bool whether this section is marked as default (booted after timeout) - */ - public $isDefault = false; - /** - * @var bool Menu entry is not visible (can only be triggered by timeout) - */ - public $isHidden = false; - /** - * @var bool Disable this entry, making it unselectable - */ - public $isDisabled = false; - /** - * @var int Value of the LOCALBOOT field - */ - public $localBoot; - /** - * @var string hotkey to trigger item. Only valid after calling mangle() - */ - public $hotkey; - - public function __construct($label) { $this->label = $label; } - - public function mangle() - { - if (($i = strpos($this->title, '^')) !== false) { - $this->hotkey = strtoupper($this->title{$i+1}); - $this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1); - } - if (strpos($this->append, 'initrd=') !== false) { - $parts = preg_split('/\s+/', $this->append); - $this->append = ''; - for ($i = 0; $i < count($parts); ++$i) { - if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) { - if (!empty($this->initrd)) { - $this->initrd .= ','; - } - $this->initrd .= $out[1]; - } else { - $this->append .= ' ' . $parts[$i]; - } - } - $this->append = trim($this->append); - } - if (is_string($this->initrd)) { - $this->initrd = explode(',', $this->initrd); - } elseif (!is_array($this->initrd)) { - $this->initrd = []; - } - } - -} - diff --git a/modules-available/serversetup-bwlp/install.inc.php b/modules-available/serversetup-bwlp/install.inc.php deleted file mode 100644 index 25579c13..00000000 --- a/modules-available/serversetup-bwlp/install.inc.php +++ /dev/null @@ -1,89 +0,0 @@ -compileTask !== null) - return $this->compileTask; - $this->compileTask = Property::get('ipxe-task-id'); - if ($this->compileTask !== false) { - $this->compileTask = Taskmanager::status($this->compileTask); - if (!Taskmanager::isTask($this->compileTask) || Taskmanager::isFinished($this->compileTask)) { - $this->compileTask = false; - } - } - return $this->compileTask; - } - - protected function doPreprocess() - { - User::load(); - - if (!User::isLoggedIn()) { - Message::addError('main.no-permission'); - Util::redirect('?do=Main'); - } - - if (Request::any('action') === 'getimage') { - User::assertPermission("download"); - $this->handleGetImage(); - } - - $this->currentMenu = Property::getBootMenu(); - - $action = Request::post('action'); - - if ($action === false) { - $this->currentAddress = Property::getServerIp(); - $this->getLocalAddresses(); - } - - if ($action === 'compile') { - User::assertPermission("edit.address"); - if ($this->getCompileTask() === false) { - Trigger::ipxe(); - } - Util::redirect('?do=serversetup'); - } - - if ($action === 'ip') { - User::assertPermission("edit.address"); - // New address is to be set - $this->getLocalAddresses(); - $this->updateLocalAddress(); - } - - if ($action === 'savebootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->saveBootEntry(); - } - - if ($action === 'deleteBootentry') { - User::assertPermission('ipxe.bootentry.edit'); - $this->deleteBootEntry(); - } - - if ($action === 'savemenu') { - User::assertPermission('ipxe.menu.edit'); - $this->saveMenu(); - } - - if ($action === 'savelocation') { - // Permcheck in function - $this->saveLocationMenu(); - Util::redirect('?do=locations'); - } - - if ($action === 'savelocalboot') { - User::assertPermission('ipxe.localboot.edit'); - $this->saveLocalboot(); - } - - if ($action === 'deleteMenu') { - // Permcheck in function - $this->deleteMenu(); - } - - if ($action === 'setDefaultMenu') { - User::assertPermission('ipxe.menu.edit', 0); - $this->setDefaultMenu(); - } - - if (Request::isPost()) { - Util::redirect('?do=serversetup'); - } - - User::assertPermission('access-page'); - - if (User::hasPermission('ipxe.*')) { - Dashboard::addSubmenu('?do=serversetup&show=menu', Dictionary::translate('submenu_menu', true)); - Dashboard::addSubmenu('?do=serversetup&show=bootentry', Dictionary::translate('submenu_bootentry', true)); - } - if (User::hasPermission('edit.address')) { - Dashboard::addSubmenu('?do=serversetup&show=address', Dictionary::translate('submenu_address', true)); - } - if (User::hasPermission('download')) { - Dashboard::addSubmenu('?do=serversetup&show=download', Dictionary::translate('submenu_download', true)); - } - if (User::hasPermission('ipxe.localboot.*')) { - Dashboard::addSubmenu('?do=serversetup&show=localboot', Dictionary::translate('submenu_localboot', true)); - } - if (Request::get('show') === false) { - $subs = Dashboard::getSubmenus(); - if (empty($subs)) { - User::assertPermission('download'); - } else { - Util::redirect($subs[0]['url']); - } - } - } - - protected function doRender() - { - Render::addTemplate("heading"); - - $task = $this->getCompileTask(); - if ($task !== false) { - $files = []; - if ($task['data'] && $task['data']['files']) { - foreach ($task['data']['files'] as $k => $v) { - $files[] = ['name' => $k, 'namehyphen' => str_replace(['/', '.'], '-', $k)]; - } - } - Render::addTemplate('ipxe_update', array('taskid' => $task['id'], 'files' => $files)); - } - - switch (Request::get('show')) { - case 'editbootentry': - User::assertPermission('ipxe.bootentry.edit'); - $this->showEditBootEntry(); - break; - case 'editmenu': - User::assertPermission('ipxe.menu.view'); - $this->showEditMenu(); - break; - case 'download': - User::assertPermission('download'); - $this->showDownload(); - break; - case 'menu': - User::assertPermission('ipxe.menu.view'); - $this->showMenuList(); - break; - case 'bootentry': - User::assertPermission('ipxe.bootentry.view'); - $this->showBootentryList(); - break; - case 'address': - User::assertPermission('edit.address'); - $this->showEditAddress(); - break; - case 'assignlocation': - // Permcheck in function - $this->showEditLocation(); - break; - case 'localboot': - User::assertPermission('ipxe.localboot.*'); - $this->showLocalbootConfig(); - break; - default: - Util::redirect('?do=serversetup'); - break; - } - } - - private function showDownload() - { - $list = glob('/srv/openslx/www/boot/download/*', GLOB_NOSORT); - usort($list, function ($a, $b) { - return strcmp(substr($a, -4), substr($b, -4)) * 100 + strcmp($a, $b); - }); - $files = []; - $strings = [ - 'efi' => [Dictionary::translate('dl-efi', true) => 50], - 'pcbios' => [Dictionary::translate('dl-pcbios', true) => 51], - 'usb' => [Dictionary::translate('dl-usb', true) => 80], - 'hd' => [Dictionary::translate('dl-hd', true) => 81], - 'lkrn' => [Dictionary::translate('dl-lkrn', true) => 82], - 'i386' => [Dictionary::translate('dl-i386', true) => 10], - 'x86_64' => [Dictionary::translate('dl-x86_64', true) => 11], - 'ecm' => [Dictionary::translate('dl-usbnic', true) => 60], - 'ncm' => [Dictionary::translate('dl-usbnic', true) => 61], - 'ipxe' => [Dictionary::translate('dl-pcinic', true) => 62], - 'snp' => [Dictionary::translate('dl-snp', true) => 63], - ]; - foreach ($list as $file) { - if ($file{0} === '.') - continue; - if (is_file($file)) { - $base = basename($file); - $features = []; - foreach (preg_split('/[\-\.\/]+/', $base, -1, PREG_SPLIT_NO_EMPTY) as $p) { - if (array_key_exists($p, $strings)) { - $features += $strings[$p]; - } - } - asort($features); - $files[] = [ - 'name' => $base, - 'size' => Util::readableFileSize(filesize($file)), - 'modified' => Util::prettyTime(filemtime($file)), - 'class' => substr($base, -4) === '.usb' ? 'slx-bold' : '', - 'features' => implode(', ', array_keys($features)), - ]; - } - } - Render::addTemplate('download', ['files' => $files]); - } - - private function makeSelectArray($list, $default) - { - $ret = []; - foreach (array_keys($list) as $k) { - $ret[] = [ - 'key' => $k, - 'selected' => ($k === $default ? 'selected' : ''), - ]; - } - return $ret; - } - - private function showLocalbootConfig() - { - // Default setting - $default = Property::get(Localboot::PROPERTY_KEY, 'AUTO'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - $default = 'AUTO'; - } - $optionList = $this->makeSelectArray(Localboot::BOOT_METHODS, $default); - // Exceptions - $cutoff = strtotime('-90 days'); - $models = []; - $res = Database::simpleQuery('SELECT m.systemmodel, cnt, sl.bootmethod FROM ( - SELECT m2.systemmodel, Count(*) AS cnt FROM machine m2 - WHERE m2.lastseen > :cutoff - GROUP BY systemmodel - ) m - LEFT JOIN serversetup_localboot sl USING (systemmodel) - ORDER BY systemmodel', ['cutoff' => $cutoff]); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $row['options'] = $this->makeSelectArray(Localboot::BOOT_METHODS, $row['bootmethod']); - $models[] = $row; - } - // Output - $data = [ - 'default' => $default, - 'options' => $optionList, - 'exceptions' => $models, - ]; - Render::addTemplate('localboot', $data); - } - - private function showBootentryList() - { - $allowEdit = User::hasPermission('ipxe.bootentry.edit'); - - $res = Database::simpleQuery("SELECT be.entryid, be.hotkey, be.title, be.builtin, Count(*) AS refs FROM serversetup_bootentry be - INNER JOIN serversetup_menuentry sm USING (entryid) - GROUP BY be.entryid - ORDER BY be.title ASC"); - $bootentryTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $bootentryTable[] = $row; - } - - Render::addTemplate('bootentry-list', array( - 'bootentryTable' => $bootentryTable, - 'allowEdit' => $allowEdit, - )); - } - - private function showMenuList() - { - $allowedEdit = User::getAllowedLocations('ipxe.menu.edit'); - - // TODO Permission::addGlobalTags($perms, null, ['edit.menu', 'edit.address', 'download']); - - $res = Database::simpleQuery("SELECT m.menuid, m.title, m.isdefault, GROUP_CONCAT(l.locationid) AS locations, - GROUP_CONCAT(ll.locationname SEPARATOR ', ') AS locnames - FROM serversetup_menu m - LEFT JOIN serversetup_menu_location l USING (menuid) - LEFT JOIN location ll USING (locationid) - GROUP BY menuid - ORDER BY title"); - $menuTable = []; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if (empty($row['locations'])) { - $locations = []; - $row['allowEdit'] = in_array(0, $allowedEdit); - } else { - $locations = explode(',', $row['locations']); - $row['allowEdit'] = empty(array_diff($locations, $allowedEdit)); - } - $row['locationCount'] = empty($locations) ? '' : count($locations); - $menuTable[] = $row; - } - - Render::addTemplate('menu-list', array( - 'menuTable' => $menuTable, - 'showSetDefault' => User::hasPermission('ipxe.menu.edit', 0) - )); - } - - private function hasMenuPermission($menuid, $permission) - { - $allowedEditLocations = User::getAllowedLocations($permission); - $allowEdit = in_array(0, $allowedEditLocations); - if (!$allowEdit) { - // Get locations - $locations = Database::queryColumnArray('SELECT locationid FROM serversetup_menu_location - WHERE menuid = :menuid', compact('menuid')); - if (!empty($locations)) { - $allowEdit = count(array_diff($locations, $allowedEditLocations)) === 0; - } - } - return $allowEdit; - } - - private function showEditMenu() - { - $id = Request::get('id', false, 'int'); - // if = edit, else = add new - if ($id !== 0) { - $menu = Database::queryFirst("SELECT menuid, timeoutms, title, defaultentryid, isdefault - FROM serversetup_menu WHERE menuid = :id", compact('id')); - } else { - $menu = []; - $menu['menuid'] = 0; - $menu['timeoutms'] = 0; - $menu['title'] = ''; - $menu['defaultentryid'] = null; - $menu['isdefault'] = false; - } - - if ($menu === false) { - Message::addError('invalid-menu-id', $id); - Util::redirect('?do=serversetup&show=menu'); - } - $highlight = Request::get('highlight', false, 'string'); - if ($id !== 0 && !$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - $menu['readonly'] = 'readonly'; - $menu['disabled'] = 'disabled'; - $menu['plainpass'] = ''; - } - if (!User::hasPermission('ipxe.menu.edit', 0)) { - $menu['globalMenuWarning'] = true; - } - - $menu['timeout'] = round($menu['timeoutms'] / 1000); - $menu['entries'] = []; - $res = Database::simpleQuery("SELECT menuentryid, entryid, hotkey, title, hidden, sortval, plainpass FROM - serversetup_menuentry WHERE menuid = :id ORDER BY sortval ASC", compact('id')); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if ($row['entryid'] == $highlight) { - $row['highlight'] = 'active'; - } - $menu['entries'][] = $row; - } - $menu['keys'] = array_map(function ($item) { return ['key' => $item]; }, MenuEntry::getKeyList()); - $menu['entrylist'] = Database::queryAll("SELECT entryid, title, hotkey, data FROM serversetup_bootentry ORDER BY title ASC"); - foreach ($menu['entrylist'] as &$bootentry) { - //$bootentry['json'] = $bootentry['data']; - $bootentry['data'] = json_decode($bootentry['data'], true); - if (array_key_exists('arch', $bootentry['data'])) { - $bootentry['data']['PCBIOS'] = array('executable' => $bootentry['data']['executable']['PCBIOS'], - 'initRd' => $bootentry['data']['initRd']['PCBIOS'], - 'commandLine' => $bootentry['data']['commandLine']['PCBIOS']); - $bootentry['data']['EFI'] = array('executable' => $bootentry['data']['executable']['EFI'], - 'initRd' => $bootentry['data']['initRd']['EFI'], - 'commandLine' => $bootentry['data']['commandLine']['EFI']); - - if ($bootentry['data']['arch'] === 'PCBIOS') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_biosOnly', true); - unset($bootentry['data']['EFI']); - } else if ($bootentry['data']['arch'] === 'EFI') { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_efiOnly', true); - unset($bootentry['data']['PCBIOS']); - } else { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archBoth', true); - } - - } elseif (!array_key_exists('script', $bootentry['data'])) { - $bootentry['data']['arch'] = Dictionary::translateFile('template-tags','lang_archAgnostic', true); - $bootentry['data']['archAgnostic'] = array('executable' => $bootentry['data']['executable'], - 'initRd' => $bootentry['data']['initRd'], - 'commandLine' => $bootentry['data']['commandLine']); - } - } - foreach ($menu['entries'] as &$entry) { - $entry['isdefault'] = ($entry['menuentryid'] == $menu['defaultentryid']); - // TODO: plainpass only when permissions - } - - Permission::addGlobalTags($menu['perms'], 0, ['ipxe.menu.edit']); - Render::addTemplate('menu-edit', $menu); - } - - private function showEditBootEntry() - { - $params = []; - $id = Request::get('id', false, 'string'); - if ($id === false) { - $params['exec_checked'] = 'checked'; - $params['entryid'] = 'u-' . dechex(mt_rand(0x1000, 0xffff)) . '-' . dechex(time()); - $params['entries'] = [ - ['mode' => 'PCBIOS'], - ['mode' => 'EFI'], - ]; - } else { - // Query existing entry - $row = Database::queryFirst('SELECT entryid, title, builtin, data FROM serversetup_bootentry - WHERE entryid = :id LIMIT 1', ['id' => $id]); - if ($row === false) { - Message::addError('invalid-boot-entry', $id); - Util::redirect('?do=serversetup'); - } - $entry = BootEntry::fromJson($row['data']); - if ($entry === null) { - Message::addError('unknown-bootentry-type', $id); - Util::redirect('?do=serversetup'); - } - $entry->addFormFields($params); - $params['title'] = $row['title']; - $params['entryid'] = $row['entryid']; - $params['builtin'] = $row['builtin']; - $params['menus'] = Database::queryAll('SELECT m.menuid, m.title FROM serversetup_menu m - INNER JOIN serversetup_menuentry me ON (me.menuid = m.menuid) - WHERE me.entryid = :entryid', ['entryid' => $row['entryid']]); - } - - Render::addTemplate('ipxe-new-boot-entry', $params); - } - - private function showEditAddress() - { - Render::addTemplate('ipaddress', array( - 'ips' => $this->addrListTask['data']['addresses'], - 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger', - 'disabled' => ($this->getCompileTask() === false) ? '' : 'disabled', - )); - } - - // ----------------------------------------------------------------------------------------------- - - private function getLocalAddresses() - { - $this->addrListTask = Taskmanager::submit('LocalAddressesList', array()); - - if ($this->addrListTask === false) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - if (!Taskmanager::isFinished($this->addrListTask)) { // TODO: Async if just displaying - $this->addrListTask = Taskmanager::waitComplete($this->addrListTask['id'], 4000); - } - - if (Taskmanager::isFailed($this->addrListTask) || !isset($this->addrListTask['data']['addresses'])) { - $this->addrListTask['data']['addresses'] = false; - return false; - } - - $sortIp = array(); - foreach (array_keys($this->addrListTask['data']['addresses']) as $key) { - $item = & $this->addrListTask['data']['addresses'][$key]; - if (!isset($item['ip']) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $item['ip']) || substr($item['ip'], 0, 4) === '127.') { - unset($this->addrListTask['data']['addresses'][$key]); - continue; - } - if ($this->currentAddress === $item['ip']) { - $item['default'] = true; - $this->hasIpSet = true; - } - $sortIp[] = $item['ip']; - } - unset($item); - array_multisort($sortIp, SORT_STRING, $this->addrListTask['data']['addresses']); - return true; - } - - private function deleteBootEntry() { - $id = Request::post('deleteid', false, 'string'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - Database::exec("DELETE FROM serversetup_bootentry WHERE entryid = :entryid", array("entryid" => $id)); - // TODO: Redirect to &show=bootentry - Message::addSuccess('bootentry-deleted'); - } - - private function setDefaultMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - Database::exec('UPDATE serversetup_menu SET isdefault = (menuid = :menuid)', ['menuid' => $id]); - Message::addSuccess('menu-set-default'); - } - - private function deleteMenu() - { - $id = Request::post('deleteid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'deleteid'); - return; - } - if (!$this->hasMenuPermission($id, 'ipxe.menu.edit')) { - Message::addError('locations.no-permission-location', $id); - return; - } - Database::exec("DELETE FROM serversetup_menu WHERE menuid = :menuid", array("menuid" => $id)); - Message::addSuccess('menu-deleted'); - } - - private function saveMenu() - { - $id = Request::post('menuid', false, 'int'); - if ($id === false) { - Message::addError('main.parameter-missing', 'menuid'); - return; - } - - $insertParams = [ - 'title' => IPxe::sanitizeIpxeString(Request::post('title', '', 'string')), - 'timeoutms' => abs(Request::post('timeout', 0, 'int') * 1000), - ]; - if ($id === 0) { - Database::exec("INSERT INTO serversetup_menu (title, timeoutms, isdefault) VALUES (:title, :timeoutms, 0)", $insertParams); - $menu['menuid'] = $id = Database::lastInsertId(); - } else { - $menu = Database::queryFirst("SELECT m.menuid - FROM serversetup_menu m - WHERE menuid = :id", compact('id')); - if ($menu === false) { - Message::addError('no-such-menu', $id); - return; - } - $insertParams['menuid'] = $id; - Database::exec('UPDATE serversetup_menu SET title = :title, timeoutms = :timeoutms - WHERE menuid = :menuid', $insertParams); - } - - $keepIds = []; - $entries = Request::post('entry', false, 'array'); - $wantedDefaultEntryId = Request::post('defaultentry', null, 'string'); - $defaultEntryId = null; - - if ($entries) { - foreach ($entries as $key => $entry) { - if (!isset($entry['sortval'])) { - error_log(print_r($entry, true)); - continue; - } - // Fallback defaults - $entry += [ - 'entryid' => null, - 'title' => '', - 'hidden' => 0, - 'plainpass' => '', - ]; - $params = [ - 'title' => IPxe::sanitizeIpxeString($entry['title']), - 'sortval' => (int)$entry['sortval'], - 'menuid' => $menu['menuid'], - ]; - if (empty($entry['entryid'])) { - // Spacer - $params += [ - 'entryid' => null, - 'hotkey' => '', - 'hidden' => 0, // Doesn't make any sense - 'plainpass' => '', // Doesn't make any sense - ]; - } else { - $params += [ - 'entryid' => $entry['entryid'], // TODO validate? - 'hotkey' => MenuEntry::filterKeyName($entry['hotkey']), - 'hidden' => (int)$entry['hidden'], // TODO (needs hotkey to make sense) - 'plainpass' => $entry['plainpass'], - ]; - } - if (is_numeric($key)) { - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $key; - } - $keepIds[] = $key; - $params['menuentryid'] = $key; - $params['md5pass'] = IPxe::makeMd5Pass($entry['plainpass'], $key); - $ret = Database::exec('UPDATE serversetup_menuentry - SET entryid = :entryid, hotkey = :hotkey, title = :title, hidden = :hidden, sortval = :sortval, - plainpass = :plainpass, md5pass = :md5pass - WHERE menuid = :menuid AND menuentryid = :menuentryid', $params, true); - } else { - $ret = Database::exec("INSERT INTO serversetup_menuentry - (menuid, entryid, hotkey, title, hidden, sortval, plainpass, md5pass) - VALUES (:menuid, :entryid, :hotkey, :title, :hidden, :sortval, :plainpass, '')", $params, true); - if ($ret) { - $newKey = Database::lastInsertId(); - if ((string)$key === $wantedDefaultEntryId) { // Check now that we have generated our key - $defaultEntryId = $newKey; - } - $keepIds[] = (int)$newKey; - if (!empty($entry['plainpass'])) { - Database::exec('UPDATE serversetup_menuentry SET md5pass = :md5pass WHERE menuentryid = :id', [ - 'md5pass' => IPxe::makeMd5Pass($entry['plainpass'], $newKey), - 'id' => $newKey, - ]); - } - } - } - - if ($ret === false) { - Message::addWarning('error-saving-entry', $entry['title'], Database::lastError()); - } - } - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid AND menuentryid NOT IN (:keep)', - ['menuid' => $menu['menuid'], 'keep' => $keepIds]); - // Set default entry - Database::exec('UPDATE serversetup_menu SET defaultentryid = :default WHERE menuid = :menuid', - ['menuid' => $menu['menuid'], 'default' => $defaultEntryId]); - } else { - Database::exec('DELETE FROM serversetup_menuentry WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - Database::exec('UPDATE serversetup_menu SET defaultentryid = NULL WHERE menuid = :menuid', ['menuid' => $menu['menuid']]); - } - - Message::addSuccess('menu-saved'); - } - - private function updateLocalAddress() - { - $newAddress = Request::post('ip', 'none', 'string'); - $valid = false; - foreach ($this->addrListTask['data']['addresses'] as $item) { - if ($item['ip'] !== $newAddress) - continue; - $valid = true; - break; - } - if ($valid) { - Property::setServerIp($newAddress); - Util::redirect('?do=ServerSetup'); - } else { - Message::addError('invalid-ip', $newAddress); - } - Util::redirect(); - } - - private function handleGetImage() - { - $file = "/opt/openslx/ipxe/openslx-bootstick.raw"; - if (!is_readable($file)) { - Message::addError('image-not-found'); - return; - } - Header('Content-Type: application/octet-stream'); - Header('Content-Disposition: attachment; filename="openslx-bootstick-' . Property::getServerIp() . '-raw.img"'); - readfile($file); - exit; - } - - private function saveBootEntry() - { - $oldEntryId = Request::post('entryid', false, 'string'); - $newId = Request::post('newid', false, 'string'); - if (!preg_match('/^[a-z0-9\-_]{1,16}$/', $newId)) { - Message::addError('main.parameter-empty', 'newid'); - return; - } - $data = Request::post('entry', false); - if (!is_array($data)) { - Message::addError('missing-bootentry-data'); - return; - } - $type = Request::post('type', false, 'string'); - if ($type === 'exec') { - $entry = BootEntry::newStandardBootEntry($data); - } elseif ($type === 'script') { - $entry = BootEntry::newCustomBootEntry($data); - } else { - Message::addError('unknown-bootentry-type', $type); - return; - } - if ($entry === null) { - Message::addError('main.empty-field'); - Util::redirect('?do=serversetup&show=bootentry'); - } - $params = [ - 'entryid' => $newId, - 'title' => Request::post('title', '', 'string'), - 'data' => json_encode($entry->toArray()), - ]; - // New or update? - if (empty($oldEntryId)) { - // New entry - Database::exec('INSERT INTO serversetup_bootentry (entryid, title, builtin, data) - VALUES (:entryid, :title, 0, :data)', $params); - Message::addSuccess('boot-entry-created', $newId); - } else { - // Edit existing entry - $params['oldid'] = $oldEntryId; - Database::exec('UPDATE serversetup_bootentry SET entryid = :entryid, title = :title, data = :data - WHERE entryid = :oldid', $params); - Message::addSuccess('boot-entry-updated', $newId); - } - Util::redirect('?do=serversetup&show=bootentry'); - } - - private function showEditLocation() - { - $locationId = Request::get('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - // List of menu entries - $res = Database::simpleQuery('SELECT menuentryid, title FROM serversetup_menuentry'); - $menuEntries = $res->fetchAll(PDO::FETCH_KEY_PAIR); - // List of menus - $data = [ - 'locationid' => $locationId, - 'locationName' => $loc['locationname'], - ]; - $res = Database::simpleQuery('SELECT m.menuid, m.title, ml.locationid, ml.defaultentryid, GROUP_CONCAT(me.menuentryid) AS entries FROM serversetup_menu m - LEFT JOIN serversetup_menu_location ml ON (m.menuid = ml.menuid AND ml.locationid = :locationid) - INNER JOIN serversetup_menuentry me ON (m.menuid = me.menuid AND me.entryid IS NOT NULL) - GROUP BY menuid - ORDER BY m.title ASC', ['locationid' => $locationId]); - $menus = []; - $hasDefault = false; - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $eids = explode(',', $row['entries']); - $row['entries'] = []; - foreach ($eids as $eid) { - $row['entries'][] = [ - 'id' => $eid, - 'title' => $menuEntries[$eid], - 'selected' => ($eid == $row['defaultentryid'] ? 'selected' : ''), - ]; - } - if ($row['locationid'] !== null) { - $hasDefault = true; - $row['menu_selected'] = 'checked'; - } - $menus[] = $row; - } - if (!$hasDefault) { - $data['default_selected'] = 'checked'; - } - $data['list'] = $menus; - Render::addTemplate('menu-assign-location', $data); - } - - private function saveLocationMenu() - { - $locationId = Request::post('locationid', false, 'int'); - $loc = Location::get($locationId); - if ($loc === false) { - Message::addError('locations.invalid-location-id', $locationId); - return; - } - User::assertPermission('ipxe.menu.assign', $locationId); - $menuId = Request::post('menuid', false, 'int'); - if ($menuId === 0) { - Database::exec('DELETE FROM serversetup_menu_location WHERE locationid = :locationid', - ['locationid' => $locationId]); - Message::addSuccess('location-use-default', $loc['locationname']); - return; - } - $defaultEntryId = Request::post('defaultentryid-' . $menuId, 0, 'int'); - if ($defaultEntryId === 0) { - $defaultEntryId = null; - } - Database::exec('INSERT INTO serversetup_menu_location (menuid, locationid, defaultentryid) - VALUES (:menuid, :locationid, :defaultentryid) - ON DUPLICATE KEY UPDATE menuid = :menuid, defaultentryid = :defaultentryid', [ - 'menuid' => $menuId, - 'locationid' => $locationId, - 'defaultentryid' => $defaultEntryId - ]); - Message::addSuccess('location-menu-assigned', $loc['locationname']); - } - - private function saveLocalboot() - { - $default = Request::post('default', 'AUTO', 'string'); - if (!array_key_exists($default, Localboot::BOOT_METHODS)) { - Message::addError('localboot-invalid-method', $default); - return; - } - Property::set(Localboot::PROPERTY_KEY, $default); - $overrides = Request::post('override', [], 'array'); - Database::exec('TRUNCATE TABLE serversetup_localboot'); - foreach ($overrides as $model => $mode) { - if (empty($mode)) // No override - continue; - if (!array_key_exists($mode, Localboot::BOOT_METHODS)) { - Message::addWarning('localboot-invalid-method', $mode); - continue; - } - Database::exec('INSERT INTO serversetup_localboot (systemmodel, bootmethod) - VALUES (:model, :mode)', compact('model', 'mode')); - } - Message::addSuccess('localboot-saved'); - Util::redirect('?do=serversetup&show=localboot'); - } - -} diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json deleted file mode 100644 index 33cc9cea..00000000 --- a/modules-available/serversetup-bwlp/permissions/permissions.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "access-page": { - "location-aware": false - }, - "download": { - "location-aware": false - }, - "edit.address": { - "location-aware": false - }, - "ipxe.bootentry.view": { - "location-aware": false - }, - "ipxe.bootentry.edit": { - "location-aware": false - }, - "ipxe.menu.view": { - "location-aware": false - }, - "ipxe.menu.edit": { - "location-aware": false - }, - "ipxe.menu.assign": { - "location-aware": true - }, - "ipxe.localboot.edit": { - "location-aware": false - } -} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/bootentry-list.html b/modules-available/serversetup-bwlp/templates/bootentry-list.html deleted file mode 100644 index 0cf005c5..00000000 --- a/modules-available/serversetup-bwlp/templates/bootentry-list.html +++ /dev/null @@ -1,83 +0,0 @@ -

    {{lang_bootentryHead}}

    - -

    - {{lang_bootentryIntro}} -

    - - - - - - - - - - - - - {{#bootentryTable}} - - - - - - - - {{/bootentryTable}} - -
    {{lang_bootentryTitle}}{{lang_hotkey}}{{lang_refCount}}{{lang_edit}}{{lang_delete}}
    - {{title}} - - {{hotkey}} - - {{refs}} - - {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowEdit}} - - {{/allowEdit}} -
    -
    - {{#allowEdit}} - - - {{lang_addBootentry}} - - {{/allowEdit}} -
    - - -
    - - -
    - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/download.html b/modules-available/serversetup-bwlp/templates/download.html deleted file mode 100644 index 62064b66..00000000 --- a/modules-available/serversetup-bwlp/templates/download.html +++ /dev/null @@ -1,53 +0,0 @@ -
    -
    - {{lang_downloadBootImage}} -
    -
    - - {{#files}} - - - - - - - {{/files}} -
    {{name}}{{size}}{{modified}}({{features}})
    -

    - - - {{lang_usbImgHelpBtn}} - -

    -

    - {{lang_additionalInfoLink}} {{lang_ipxeWikiUrl}} -

    -
    -
    - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html deleted file mode 100644 index e2aa0bff..00000000 --- a/modules-available/serversetup-bwlp/templates/heading.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp/templates/ipaddress.html deleted file mode 100644 index ea19c417..00000000 --- a/modules-available/serversetup-bwlp/templates/ipaddress.html +++ /dev/null @@ -1,44 +0,0 @@ -
    -
    - {{lang_bootAddress}} -
    -
    -
    - {{lang_chooseIP}} -
    -
    - - - - {{#ips}} - - - {{#default}} - - {{/default}} - {{^default}} - - {{/default}} - - {{/ips}} -
    {{ip}} - {{lang_active}} - - -
    -

    - {{lang_recompileHint}} -

    -
    -
    - - -
    -
    -
    \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html b/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html deleted file mode 100644 index 7e82b5cc..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe-new-boot-entry.html +++ /dev/null @@ -1,165 +0,0 @@ -

    {{lang_newBootEntryHead}}

    - -{{#builtin}} -
    - {{lang_editBuiltinWarn}} -
    -{{/builtin}} - -
    -
    - {{lang_bootEntryData}} -
    -
    -
    - - - - -
    -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    - -
    -
    - {{#entries}} -
    -
    -
    -

    {{mode}}

    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - {{/entries}} -
    -
    - -
    -
    - - -
    -
    - - {{#builtin}} -
    - {{lang_editBuiltinWarn}} -
    - {{/builtin}} - -

    {{lang_referencingMenus}}:

    - - -
    - -
    -
    -
    -
    - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/ipxe_update.html b/modules-available/serversetup-bwlp/templates/ipxe_update.html deleted file mode 100644 index 344d3905..00000000 --- a/modules-available/serversetup-bwlp/templates/ipxe_update.html +++ /dev/null @@ -1,54 +0,0 @@ -
    -
    {{lang_menuGeneration}}
    -
    -
    - {{#files}} -
    - - {{name}} -
    - {{/files}} -
    -
    -
    - {{lang_generationFailed}} -
    -
    -
    {{lang_menuGeneration}}
    -
    -
    - - diff --git a/modules-available/serversetup-bwlp/templates/localboot.html b/modules-available/serversetup-bwlp/templates/localboot.html deleted file mode 100644 index 3037de2a..00000000 --- a/modules-available/serversetup-bwlp/templates/localboot.html +++ /dev/null @@ -1,59 +0,0 @@ -

    {{lang_localBootHead}}

    - -

    {{lang_localBootIntro}}

    - -
    - - - - -
    -
    - - -
    - -
    -

    - {{lang_localBootExceptions}} -

    - - - - - - - {{#exceptions}} - - - - - - {{/exceptions}} -
    {{lang_systemmodel}}{{lang_count}}{{lang_override}}
    {{systemmodel}}{{cnt}} - -
    - -
    - - -
    - -
    diff --git a/modules-available/serversetup-bwlp/templates/menu-assign-location.html b/modules-available/serversetup-bwlp/templates/menu-assign-location.html deleted file mode 100644 index 077d137e..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-assign-location.html +++ /dev/null @@ -1,69 +0,0 @@ -

    {{lang_assignMenuToLocation}}

    -

    {{locationName}}

    - -
    - - - - - - - - - - - - - - - - - - - {{#list}} - - - - - - {{/list}} - -
    {{lang_menuTitle}}{{lang_menuEntryOverride}}
    -
    - - -
    -
    - {{lang_useDefaultMenu}} -
    -
    - - -
    -
    - {{title}} - - -
    - -
    - -
    - -
    - -
    - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-edit.html b/modules-available/serversetup-bwlp/templates/menu-edit.html deleted file mode 100644 index 1598a2b7..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-edit.html +++ /dev/null @@ -1,368 +0,0 @@ -

    {{lang_editMenuHead}}

    - - - - -
    -
    - {{title}} - {{^title}} - {{lang_newMenu}} - {{/title}} -
    -
    -
    - - - - -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - - {{lang_seconds}} -
    -
    -
    -
    - - - - - - - - - - - - - - - {{#entries}} - - - - - - - - - - - - - - - - - {{/entries}} - -
    {{lang_entryId}}{{lang_title}}{{lang_hotkey}}{{lang_password}}
    - - -
    - - -
    -
    - - - - - - - - - -
    - - -
    -
    - -
    -
    -
    - -
    -
    - {{lang_cancel}} - -
    -
    - - -
    -
    - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/modules-available/serversetup-bwlp/templates/menu-list.html b/modules-available/serversetup-bwlp/templates/menu-list.html deleted file mode 100644 index 545f22a9..00000000 --- a/modules-available/serversetup-bwlp/templates/menu-list.html +++ /dev/null @@ -1,100 +0,0 @@ -

    {{lang_listOfMenus}}

    - -

    - {{lang_menuListIntro}} -

    - - - - - - - - - - - - - {{#menuTable}} - - - - - - - - {{/menuTable}} - -
    {{lang_menuTitle}}{{lang_locationCount}}{{lang_isDefault}}{{lang_edit}}{{lang_delete}}
    - {{title}} - - {{locationCount}} - - {{^isdefault}} - {{#showSetDefault}} -
    - - - -
    - {{/showSetDefault}} - {{/isdefault}} - {{#isdefault}} - - {{/isdefault}} -
    - {{#allowEdit}} - - - - {{/allowEdit}} - - {{#allowDelete}} - - {{/allowDelete}} -
    - - -
    - - - -
    - - -
    - - \ No newline at end of file -- cgit v1.2.3-55-g7522 From 1e0a356f3ab77565de59d49a81743652ede9642e Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 15 Feb 2019 15:35:08 +0100 Subject: [serversetup-bwlp-ipxe] ipxe script: Don't load unused png --- modules-available/serversetup-bwlp-ipxe/api.inc.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules-available/serversetup-bwlp-ipxe/api.inc.php b/modules-available/serversetup-bwlp-ipxe/api.inc.php index 1df0e6e7..73461901 100644 --- a/modules-available/serversetup-bwlp-ipxe/api.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/api.inc.php @@ -170,8 +170,6 @@ set serverip $serverIp || # Clean up in case we've been chained to imgfree || -imgfetch --name bg-load /tftp/openslx.png || - imgfetch --name bg-menu /tftp/pxe-menu.png || :start -- cgit v1.2.3-55-g7522 From 7863dcad82b760de0877005a08c5af28d43a6c93 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 15 Feb 2019 17:08:27 +0100 Subject: [dnbd3] Fix recursive client counting --- modules-available/dnbd3/page.inc.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules-available/dnbd3/page.inc.php b/modules-available/dnbd3/page.inc.php index 84007797..03c7e6cd 100644 --- a/modules-available/dnbd3/page.inc.php +++ b/modules-available/dnbd3/page.inc.php @@ -332,16 +332,18 @@ class Page_Dnbd3 extends Page $locCount[0] = array( 'locationname' => '/', 'depth' => 0, - 'recCount' => 0, ); foreach ($locCount as &$loc) { + $loc['clientCount'] = 0; $loc['recCount'] = 0; } $showLocs = false; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { settype($row['locationid'], 'int'); + trigger_error('Bla: ' . $row['locationid'] . ', blu: ' . isset($locCount[$row['locationid']])); $loc =& $locCount[$row['locationid']]; $loc['clientCount'] = $row['cnt']; + trigger_error('Setting ' . $row['locationid'] . ' to ' . $row['cnt'] . ', rec was ' . $loc['recCount']); $loc['recCount'] += $row['cnt']; if ($row['locationid'] !== 0) { $showLocs = true; @@ -350,9 +352,11 @@ class Page_Dnbd3 extends Page if (isset($loc['parents'])) { foreach ($loc['parents'] as $p) { $locCount[$p]['keep'] = true; + trigger_error('[' . $p . '] Adding ' . $row['cnt'] . ' to ' . $locCount[$p]['recCount']); $locCount[$p]['recCount'] += $row['cnt']; } } + $locCount[0]['recCount'] += $row['cnt']; } if ($showLocs) { $stats['loclist'] = array_values(array_filter($locCount, function ($v) { return isset($v['keep']); })); -- cgit v1.2.3-55-g7522 From acae99572ec256c0fce6c021339df918cd55b5b1 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 15 Feb 2019 17:12:12 +0100 Subject: [dnbd3] Remove debug spam --- modules-available/dnbd3/page.inc.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules-available/dnbd3/page.inc.php b/modules-available/dnbd3/page.inc.php index 03c7e6cd..d8dd6cb8 100644 --- a/modules-available/dnbd3/page.inc.php +++ b/modules-available/dnbd3/page.inc.php @@ -340,10 +340,8 @@ class Page_Dnbd3 extends Page $showLocs = false; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { settype($row['locationid'], 'int'); - trigger_error('Bla: ' . $row['locationid'] . ', blu: ' . isset($locCount[$row['locationid']])); $loc =& $locCount[$row['locationid']]; $loc['clientCount'] = $row['cnt']; - trigger_error('Setting ' . $row['locationid'] . ' to ' . $row['cnt'] . ', rec was ' . $loc['recCount']); $loc['recCount'] += $row['cnt']; if ($row['locationid'] !== 0) { $showLocs = true; @@ -352,7 +350,6 @@ class Page_Dnbd3 extends Page if (isset($loc['parents'])) { foreach ($loc['parents'] as $p) { $locCount[$p]['keep'] = true; - trigger_error('[' . $p . '] Adding ' . $row['cnt'] . ' to ' . $locCount[$p]['recCount']); $locCount[$p]['recCount'] += $row['cnt']; } } -- cgit v1.2.3-55-g7522 From 22a2b5c69d18191e3039815210a71a61ff95a283 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 18 Feb 2019 12:07:25 +0100 Subject: [baseconfig_bwlp] Add SLX_SCREEN_SAVER(_GRACE)?_TIMEOUT Control xscreensaver timeout and grace period. --- modules-available/baseconfig_bwlp/baseconfig/settings.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules-available/baseconfig_bwlp/baseconfig/settings.json b/modules-available/baseconfig_bwlp/baseconfig/settings.json index 8145854d..6aab113e 100644 --- a/modules-available/baseconfig_bwlp/baseconfig/settings.json +++ b/modules-available/baseconfig_bwlp/baseconfig/settings.json @@ -121,6 +121,18 @@ "permissions": "2", "validator": "regex:\/^\\d*$\/" }, + "SLX_SCREEN_SAVER_TIMEOUT": { + "catid": "power", + "defaultvalue": "540", + "permissions": "2", + "validator": "regex:\/^\\d*$\/" + }, + "SLX_SCREEN_SAVER_GRACE_TIMEOUT": { + "catid": "power", + "defaultvalue": "15", + "permissions": "2", + "validator": "regex:\/^\\d*$\/" + }, "SLX_SCREEN_STANDBY_TIMEOUT": { "catid": "power", "defaultvalue": "600", -- cgit v1.2.3-55-g7522 From 4a2c2ab41e7c29b1966d02fc40b7c7257b01f8d7 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 18 Feb 2019 15:51:27 +0100 Subject: Update translations --- modules-available/dozmod/lang/de/permissions.json | 12 ++-- .../dozmod/lang/de/template-tags.json | 4 +- modules-available/dozmod/lang/en/messages.json | 2 + modules-available/dozmod/lang/en/module.json | 1 + modules-available/dozmod/lang/en/permissions.json | 10 +-- .../dozmod/lang/en/template-tags.json | 20 ++++++ modules-available/dozmod/page.inc.php | 1 + modules-available/exams/lang/de/template-tags.json | 2 - modules-available/exams/lang/en/template-tags.json | 2 - modules-available/main/lang/de/categories.json | 3 +- modules-available/main/lang/en/categories.json | 3 +- .../serversetup-bwlp-ipxe/lang/de/messages.json | 3 +- .../serversetup-bwlp-ipxe/lang/de/permissions.json | 1 - .../lang/de/template-tags.json | 21 +----- .../serversetup-bwlp-ipxe/lang/en/messages.json | 17 ++++- .../serversetup-bwlp-ipxe/lang/en/module.json | 18 ++++- .../serversetup-bwlp-ipxe/lang/en/permissions.json | 7 +- .../lang/en/template-tags.json | 77 +++++++++++++++------- .../serversetup-bwlp-ipxe/page.inc.php | 2 +- .../sysconfig/lang/de/template-tags.json | 2 + .../sysconfig/lang/en/template-tags.json | 2 + 21 files changed, 142 insertions(+), 68 deletions(-) diff --git a/modules-available/dozmod/lang/de/permissions.json b/modules-available/dozmod/lang/de/permissions.json index 6475f7ab..8e743e5c 100644 --- a/modules-available/dozmod/lang/de/permissions.json +++ b/modules-available/dozmod/lang/de/permissions.json @@ -4,12 +4,12 @@ "ldapfilters.save": "LDAP Filter speichern.", "ldapfilters.view": "LDAP Filter einsehen.", "mailconfig.save": "\u00c4nderungen an der SMTP-Konfiguration zum Versenden von Mails speichern.", - "networkrules.save": "Netzwerk-Regeln einsehen.", - "networkrules.view": "\u00c4nderungen an den Netzwerk-Regeln speichern.", - "networkshares.save": "Netzlaufwerke einsehen.", - "networkshares.view": "\u00c4nderungen an den Netzlaufwerken speichern.", - "runscripts.save": "Startkripte erstellen\/bearbeiten", - "runscripts.view": "Startscripte auflisten", + "networkrules.save": "\u00c4nderungen an den Netzwerk-Regeln speichern.", + "networkrules.view": "Netzwerk-Regeln einsehen.", + "networkshares.save": "\u00c4nderungen an den Netzlaufwerken speichern.", + "networkshares.view": "Netzlaufwerke einsehen.", + "runscripts.save": "Startkripte erstellen\/bearbeiten.", + "runscripts.view": "Startscripte auflisten.", "runtimeconfig.save": "\u00c4nderungen an der Laufzeit-Konfiguration speichern.", "templates.reset": "E-Mail Templates zur\u00fccksetzen.", "templates.save": "E-Mail Templates speichern.", diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json index 320c7592..3e000676 100644 --- a/modules-available/dozmod/lang/de/template-tags.json +++ b/modules-available/dozmod/lang/de/template-tags.json @@ -70,6 +70,7 @@ "lang_modified": "Modifiziert", "lang_name": "Name", "lang_networkrules": "Netzwerk-Regeln", + "lang_networkrulesIntro": "Hier k\u00f6nnen Sie vordefinierte Regelsets f\u00fcr das Firewalling verwalten. Nutzer der bwLehrpool-Suite k\u00f6nnen auf diese Regelsets zur\u00fcckgreifen, um den Netzwerkzugriff ihrer Veranstaltungen einzuschr\u00e4nken.", "lang_networkshares": "Netzlaufwerke", "lang_networksharesIntro": "Hier k\u00f6nnen Sie vordefinierte Netzlaufwerke anlegen, die den Nutzern der bwLehrpool-Suite zur Auswahl gestellt werden. Es ist den Nutzern der bwLehrpool-Suite weiterhin m\u00f6glich, komplett eigene Netzwerkfreigaben zu definieren. Die Angaben hier sollen lediglich das Hinzuf\u00fcgen h\u00e4ufig genutzter Laufwerke vereinfachen, bzw. das \u00c4ndern eines Netzwerkpfades vereinfachen, da in diesem Fall nur der Zentrale Eintrag hier angepasst werden muss, und nicht mehr wie zuvor jede Veranstaltung einzeln.", "lang_none": "(Keiner)", @@ -86,6 +87,7 @@ "lang_reallyResetTemplates": "Sind Sie sicher, dass Sie alle Texte l\u00f6schen und auf die Standardwerte zur\u00fccksetzen wollen?", "lang_replaceWithOriginal": "Originaltext in Textbox laden", "lang_replyTo": "Reply-To Adresse", + "lang_ruleDeleteConfirm": "Soll dieses Regelset wirklich gel\u00f6scht werden?", "lang_runScriptAdd": "Skript hinzuf\u00fcgen", "lang_runScriptDeleteConfirmation": "Skript wirklich l\u00f6schen?", "lang_runtimeConfig": "Laufzeit-Konfiguration", @@ -129,4 +131,4 @@ "lang_usernameplaceholder": "SMTP-Benutzername", "lang_version": "Version vom", "lang_when": "Wann" -} +} \ No newline at end of file diff --git a/modules-available/dozmod/lang/en/messages.json b/modules-available/dozmod/lang/en/messages.json index 6d8296ec..2d813efc 100644 --- a/modules-available/dozmod/lang/en/messages.json +++ b/modules-available/dozmod/lang/en/messages.json @@ -24,6 +24,8 @@ "networkshare-saved": "Network share saved", "no-expired-images": "No expired VMs", "nothing-submitted": "There was nothing submitted", + "runscript-invalid-id": "Invalid script id: {{0}}", + "runscript-saved": "Script has been saved", "runtimelimits-config-saved": "Configuration saved successfully", "templates-saved": "Templates saved successfully", "timeout": "Timeout", diff --git a/modules-available/dozmod/lang/en/module.json b/modules-available/dozmod/lang/en/module.json index 8967493d..5bcee464 100644 --- a/modules-available/dozmod/lang/en/module.json +++ b/modules-available/dozmod/lang/en/module.json @@ -7,6 +7,7 @@ "submenu_mailconfig": "email configuration", "submenu_networkrules": "Network Rules", "submenu_networkshares": "Network Shares", + "submenu_runscripts": "Startup scripts", "submenu_runtimeconfig": "limits and defaults", "submenu_templates": "templates", "submenu_users": "users and permissions" diff --git a/modules-available/dozmod/lang/en/permissions.json b/modules-available/dozmod/lang/en/permissions.json index dec3171a..b0fbb071 100644 --- a/modules-available/dozmod/lang/en/permissions.json +++ b/modules-available/dozmod/lang/en/permissions.json @@ -4,10 +4,12 @@ "ldapfilters.save": "Save LDAP filter.", "ldapfilters.view": "View LDAP filters. ", "mailconfig.save": "Save SMTP configuration for sending mails.", - "networkrules.save": "View network rules.", - "networkrules.view": "Save network rules.", - "networkshares.save": "View network drives.", - "networkshares.view": "Save network drives.", + "networkrules.save": "Save network rules.", + "networkrules.view": "View network rules.", + "networkshares.save": "Save network drives.", + "networkshares.view": "View network drives.", + "runscripts.save": "Save startup scripts.", + "runscripts.view": "View startup scripts.", "runtimeconfig.save": "Save limits and defaults of a runtime configuration.", "templates.reset": "Reset email templates.", "templates.save": "Save email templates.", diff --git a/modules-available/dozmod/lang/en/template-tags.json b/modules-available/dozmod/lang/en/template-tags.json index 3f2ae1fc..c33d872b 100644 --- a/modules-available/dozmod/lang/en/template-tags.json +++ b/modules-available/dozmod/lang/en/template-tags.json @@ -28,6 +28,7 @@ "lang_dozmodLogHeading": "bwLehrpool-Suite action log", "lang_editNetworkrule": "Edit Network Rule", "lang_editNetworkshare": "Edit Network Share", + "lang_editScript": "Edit start script", "lang_email": "E-Mail", "lang_emailNotifications": "E-Mail notifications enabled", "lang_error": "Error", @@ -37,6 +38,7 @@ "lang_hasNewer": "newer version exists", "lang_hash": "Hash", "lang_heading": "Images Marked for Deletion", + "lang_hidden": "Hidden", "lang_host": "Host", "lang_image": "VM", "lang_lastEditor": "Edited by", @@ -63,13 +65,16 @@ "lang_maxLectureVisibility": "Max time lecture end date may lie in the future (days)", "lang_maxLocationsPerLecture": "Max. explicit locations per lecture", "lang_maxTransfers": "Max concurrent transfers per user", + "lang_minimized": "Minimized", "lang_miscOptions": "Misc options", "lang_modified": "modified", "lang_name": "Name", "lang_networkrules": "Network Rules", + "lang_networkrulesIntro": "This is where you can create predefined rulesets for the lecture-based firewalling. bwLehrpool-Suite users can select those rulesets to limit network access of their lectures.", "lang_networkshares": "Network Shares", "lang_networksharesIntro": "This is the list of predefined network shares. bwLehrpool-Suite users can still add custom network shares to their lectures, however having commonly used network shares as predefined entries should be much more convenient. Another advantage is that changing the path of a network share centrally avoids having to edit a dozen lectures' configuration manually.", "lang_none": "(none)", + "lang_normal": "Normal", "lang_organization": "Organization", "lang_organizationListHeader": "Set access permissions for organizations", "lang_os": "Operating System", @@ -82,8 +87,23 @@ "lang_reallyResetTemplates": "Are you sure you want to reset all texts to their default values?", "lang_replaceWithOriginal": "load original text into text box", "lang_replyTo": "Reply-To address", + "lang_ruleDeleteConfirm": "Do you want to delete this ruleset?", + "lang_runScriptAdd": "Add run-script", + "lang_runScriptDeleteConfirmation": "Do you want to delete this run-script?", "lang_runtimeConfig": "Limits and Defaults", "lang_runtimeConfigLimits": "Limitations", + "lang_scriptContent": "Script content", + "lang_scriptExtension": "Script extension", + "lang_scriptExtensionHead": "Extension", + "lang_scriptIsGlobal": "Global script, will execute in every lecture environment", + "lang_scriptIsGlobalHead": "Global", + "lang_scriptIsPredefined": "Predefined script, selectable by bwLehrpool-Suite users for individual lectures", + "lang_scriptPassCredentials": "Pass username and password to this script", + "lang_scriptPassCredentialsHead": "User\/Pass", + "lang_scriptVisibility": "Display mode", + "lang_scriptVisibilityHead": "Display", + "lang_scriptsHead": "Run-scripts for lecture environments", + "lang_scriptsIntro": "Here you can define scripts that will either forcefully run at startup of a lecture, or are selectable by bwLehrpool-Suite users.", "lang_senderAddress": "Sender address", "lang_senderName": "Sender's display name", "lang_shareDeleteConfirm": "Do you really want to delete this network share?", diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php index b772890f..67b791d1 100644 --- a/modules-available/dozmod/page.inc.php +++ b/modules-available/dozmod/page.inc.php @@ -63,6 +63,7 @@ class Page_DozMod extends Page Dictionary::translate('submenu_networkshares', true); Dictionary::translate('submenu_ldapfilters', true); Dictionary::translate('submenu_runscripts', true); + Dictionary::translate('submenu_networkrules', true); */ /* add sub-menus */ diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json index 1dd51374..0fbaf0a1 100644 --- a/modules-available/exams/lang/de/template-tags.json +++ b/modules-available/exams/lang/de/template-tags.json @@ -1,5 +1,4 @@ { - "lang_actions": "Aktionen", "lang_addExam": "Zeitraum hinzuf\u00fcgen", "lang_addingBasedOnLecture": "F\u00fcge neuen Pr\u00fcfungszeitraum basierend auf vorhandener Veranstaltung an", "lang_allExamPeriods": "Alle Pr\u00fcfungszeitr\u00e4ume", @@ -16,7 +15,6 @@ "lang_deleteConfirmation": "Wirklich l\u00f6schen?", "lang_description": "Beschreibung", "lang_duration": "Dauer", - "lang_editExam": "Zeitraum bearbeiten", "lang_end": "Ende", "lang_end_date": "Ende Datum", "lang_end_time": "Uhrzeit", diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json index 23266154..52173740 100644 --- a/modules-available/exams/lang/en/template-tags.json +++ b/modules-available/exams/lang/en/template-tags.json @@ -1,5 +1,4 @@ { - "lang_actions": "Actions", "lang_addExam": "Add exam period", "lang_addingBasedOnLecture": "Adding exam period based on lecture", "lang_allExamPeriods": "All Exam Periods", @@ -16,7 +15,6 @@ "lang_deleteConfirmation": "Are you sure?", "lang_description": "Description", "lang_duration": "Duration", - "lang_editExam": "Edit exam period", "lang_end": "End", "lang_end_date": "End Date", "lang_end_time": "Time", diff --git a/modules-available/main/lang/de/categories.json b/modules-available/main/lang/de/categories.json index 71f149ec..587200ed 100644 --- a/modules-available/main/lang/de/categories.json +++ b/modules-available/main/lang/de/categories.json @@ -3,6 +3,5 @@ "content": "Inhalt", "settings-client": "Einstellungen (Client)", "settings-server": "Einstellungen (Server)", - "status": "Status", - "users": "Benutzer" + "status": "Status" } \ No newline at end of file diff --git a/modules-available/main/lang/en/categories.json b/modules-available/main/lang/en/categories.json index 9dfa0404..f73f0a15 100644 --- a/modules-available/main/lang/en/categories.json +++ b/modules-available/main/lang/en/categories.json @@ -3,6 +3,5 @@ "content": "Content", "settings-client": "Settings (Client)", "settings-server": "Settings (Server)", - "status": "Status", - "users": "Users" + "status": "Status" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json index 0772a7e4..8c5aab54 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/messages.json @@ -16,6 +16,5 @@ "menu-set-default": "Standardmen\u00fc wurde gesetzt", "missing-bootentry-data": "Fehlende Daten f\u00fcr den Men\u00fceintrag", "no-ip-addr-set": "Bitte w\u00e4hlen Sie die prim\u00e4re IP-Adresse des Servers", - "no-such-menu": "Men\u00fc mit ID {{0}} existiert nicht", "unknown-bootentry-type": "Unbekannter Eintrags-Typ: {{0}}" -} +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json b/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json index 5cd02119..9d7e77c6 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/permissions.json @@ -2,7 +2,6 @@ "access-page": "Seite sehen.", "download": "USB-Image herunterladen.", "edit.address": "Boot-Adresse des Servers ausw\u00e4hlen.", - "edit.menu": "Bootmen\u00fc anpassen.", "ipxe.bootentry.edit": "Einen Boot-Eintrag bearbeiten.", "ipxe.bootentry.view": "Liste aller Boot-Eintr\u00e4ge sehen.", "ipxe.localboot.edit": "Ausnahmeliste f\u00fcr Localboot-Modus bearbeiten.", diff --git a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json index 198a1517..525b1562 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/de/template-tags.json @@ -10,12 +10,7 @@ "lang_assignMenuToLocation": "Ort ein Men\u00fc zuweisen", "lang_biosOnly": "Nur BIOS", "lang_bootAddress": "Boot-Adresse des Servers", - "lang_bootBehavior": "Standard-Bootverhalten", "lang_bootEntryData": "Daten des Men\u00fceintrags", - "lang_bootHint": "Das Bootmen\u00fc muss nach einer \u00c4nderung der IP-Adresse neu generiert werden. In der Regel geschieht dies automatisch, der Vorgang kann in der Sektion Bootmen\u00fc allerdings auch manuell ausgel\u00f6st werden.", - "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.", - "lang_bootMenu": "Bootmen\u00fc", - "lang_bootMenuCreate": "Bootmen\u00fc erzeugen", "lang_bootentryDeleteConfirm": "Sind Sie sicher, dass Sie diesen Men\u00fceintrag l\u00f6schen wollen?", "lang_bootentryHead": "Men\u00fceintr\u00e4ge", "lang_bootentryIntro": "Hier k\u00f6nnen Sie Men\u00fceintr\u00e4ge definieren, die sich sp\u00e4ter einem Men\u00fc zuweisen lassen. Ein Men\u00fceintrag besteht entweder aus einem zu ladenen Kernel\/Image plus optional initrd, oder aus einem iPXE-Script.", @@ -23,7 +18,6 @@ "lang_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.", "lang_commandLine": "Command line", "lang_count": "Anzahl", - "lang_customEntry": "Eigener Eintrag", "lang_downloadBootImage": "Boot-Image herunterladen", "lang_downloadRufus": "Rufus herunterladen", "lang_editBuiltinWarn": "Achtung! Sie bearbeiten einen der vorgegebenen Eintr\u00e4ge! Bei einem Update k\u00f6nnten Ihre \u00c4nderungen wieder \u00fcberschrieben werden", @@ -32,13 +26,11 @@ "lang_entryChooserTitle": "Men\u00fceintrag ausw\u00e4hlen", "lang_entryId": "ID", "lang_entryTitle": "Bezeichnung", - "lang_example": "Beispiel", "lang_execAutoUnload": "Nach Ausf\u00fchrung entladen (--autofree)", "lang_execReplace": "Aktuellen iPXE-Stack erstzen (--replace)", "lang_execResetConsole": "Konsole vor Ausf\u00fchrung zur\u00fccksetzen", "lang_forceRecompile": "Jetzt neu compilieren", "lang_generationFailed": "Erzeugen des Bootmen\u00fcs fehlgeschlagen. Der Netzwerkboot von bwLehrpool wird wahrscheinlich nicht funktionieren. Wenn Sie den Fehler nicht selbst beheben k\u00f6nnen, melden Sie bitte die Logausgabe an das bwLehrpool-Projekt.", - "lang_globalMenuWarning": "Dieses Men\u00fc ist keinem Raum zugeordnet", "lang_hotkey": "Hotkey", "lang_idFormatHint": "(Max. 16 Zeichen, nur a-z 0-9 - _)", "lang_imageToLoad": "Zu ladendes Image (z.B. Kernel)", @@ -50,20 +42,11 @@ "lang_localBootExceptions": "Ausnahmen, pro Rechnermodell definierbar", "lang_localBootHead": "Boot von Festplatte", "lang_localBootIntro": "Aus dem iPXE Bootmen\u00fc kann auf verschiedene Arten ein Boot von der prim\u00e4ren Festplatte ausgel\u00f6st werden. In den allermeisten F\u00e4llen ist die Einstellung \"AUTO\" ausreichend, bei bestimmten Rechnermodellen kann es allerdings erforderlich sein, eine der alternativen Methoden zu erzwingen. Falls Sie einem solchen Modell begegnen, k\u00f6nnen Sie im unteren Teil dieser Seite eine solche Ausnahme festlegen. In einigen F\u00e4llen l\u00e4sst sich das Problem auch durch ein BIOS-Update auf den entsprechenden Ger\u00e4ten beheben.", - "lang_localHDD": "Lokale HDD", "lang_locationCount": "Anzahl Orte", - "lang_masterPassword": "Master-Passwort", - "lang_masterPasswordHelp": "Das Master-Passwort wird ben\u00f6tigt, um einen Men\u00fceintrag direkt am Client tempor\u00e4r durch Dr\u00fccken der Tab-Taste zu editieren. Da dies f\u00fcr Manipulation am Client genutzt werden kann, sollte diese Funktion unbedingt mit einem Passwort gesch\u00fctzt werden.", - "lang_menuCustom": "Benutzerdefinierter Men\u00fczusatz", - "lang_menuCustomHint1": "Hier haben Sie die M\u00f6glichkeit, eigenen Men\u00fc-Code zum angezeigten PXE-Men\u00fc hinzuzuf\u00fcgen, um z.B. auf weitere PXE-Server zu verweisen. Das Format entspricht dem syslinux Men\u00fcformat.", - "lang_menuCustomHint2": "Sie k\u00f6nnen ein oder mehrere Eintr\u00e4ge erzeugen. Wenn Sie einen Eintrag erzeugen m\u00f6chten, der automatisch gestartet wird, wenn der Benutzer keine Auswahl t\u00e4tigt, vergeben Sie als", - "lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.", "lang_menuDeleteConfirm": "Sind Sie sicher, dass Sie dieses Men\u00fc l\u00f6schen wollen?", - "lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs", "lang_menuEntryOverride": "Standardeintrag \u00fcberschreiben", "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs", "lang_menuListIntro": "Hier sehen Sie eine Liste aller vorhandenen Men\u00fcs, deren Zuordnung zu R\u00e4umen sowie die M\u00f6glichkeit, diese zu editieren oder l\u00f6schen. Um ein Men\u00fc einem bestimmten Raum zuzuweisen, besuchen Sie bitte den Men\u00fcpunkt \"R\u00e4ume\/Orte\".", - "lang_menuLocations": "Zugewiesene Orte", "lang_menuTimeout": "Timeout", "lang_menuTitle": "Men\u00fc", "lang_moduleHeading": "iPXE \/ Boot Menu", @@ -71,7 +54,6 @@ "lang_newMenu": "Neues Men\u00fc", "lang_none": "(keine)", "lang_override": "\u00dcberschreiben", - "lang_pxeBuilt": "PXE-Binary gebaut", "lang_recompileHint": "iPXE-Binaries jetzt neu kompilieren. Normalerweise wird dieser Vorgang bei \u00c4nderungen automatisch ausgef\u00fchrt. Sollten Bootprobleme auftreten, k\u00f6nnen Sie hier den Vorgang manuell ansto\u00dfen.", "lang_refCount": "Referenzen", "lang_referencingMenus": "Verkn\u00fcpfte Men\u00fcs", @@ -83,7 +65,6 @@ "lang_title": "Titel", "lang_typeExecEntry": "Standardeintrag", "lang_typeScriptEntry": "Benutzerdefiniertes Script", - "lang_usbBuilt": "USB-Image gebaut", "lang_usbImage": "USB-Image", "lang_usbImgHelp": "Mit dem USB-Image k\u00f6nnen Sie einen bootbaren USB-Stick erstellen, \u00fcber den sich bwLehrpool an Rechnern starten l\u00e4sst, die keinen Netzwerkboot unterst\u00fctzen, bzw. f\u00fcr die keine entsprechende DHCP-Konfiguration vorhanden ist. Dies erfordert dann lediglich, dass in der BIOS-Konfiguration des Rechners USB-Boot zugelassen ist. Der Stick dient dabei lediglich als Einstiegspunkt; es ist nach wie vor ein bwLehrpool-Satellitenserver f\u00fcr den eigentlichen Bootvorgang von N\u00f6ten.", "lang_usbImgHelpBtn": "Bootbaren USB-Stick erstellen", @@ -91,4 +72,4 @@ "lang_usbImgHelpWindows": "Unter Windows muss zun\u00e4chst ein Programm besorgt werden, mit dem sich Images direkt auf einen USB-Stick schreiben lassen. Es gibt gleich mehrere kostenlose und quelloffene Programme, eines davon ist Rufus. Rufus wurde mit dem bwLehrpool-Image gestetet. Nach dem Starten des Programms ist lediglich das heruntergeladene Image zu \u00f6ffnen, sowie in der Liste der Laufwerke der richtige USB-Stick auszuw\u00e4hlen (damit Sie nicht versehentlich Daten auf dem falschen Laufwerk \u00fcberschreiben!)", "lang_useDefaultMenu": "\u00dcbergeordnetes Men\u00fc verwenden", "lang_useDefaultMenuEntry": "(Vorgabe des Men\u00fcs)" -} +} \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json index d4ba6905..dcdf4be1 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/messages.json @@ -1,5 +1,20 @@ { + "boot-entry-created": "Created menu item {{0}}", + "boot-entry-updated": "Updated menu item {{0}}", + "bootentry-deleted": "Deleted menu item", + "error-saving-entry": "Error saving item {{0}}: {{1}}", "image-not-found": "USB image not found. Try regenerating the boot menu first.", + "invalid-boot-entry": "Invalid menu item: {{0}}", "invalid-ip": "No interface is configured with the address {{0}}", - "no-ip-addr-set": "Please set the server's primary IP address" + "invalid-menu-id": "Invalid menu id: {{0}}", + "localboot-invalid-method": "Invalid localboot method: {{0}}", + "localboot-saved": "Localboot settings have been saved", + "location-menu-assigned": "Assigned menu to {{0}}", + "location-use-default": "{{0}} is now using the inherited\/default menu", + "menu-deleted": "Menu deleted", + "menu-saved": "Menu saved", + "menu-set-default": "Default menu has been set", + "missing-bootentry-data": "Missing data for menu item", + "no-ip-addr-set": "Please set the server's primary IP address", + "unknown-bootentry-type": "Unknown item type: {{0}}" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/module.json b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json index aeea610c..9e73e865 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/module.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/module.json @@ -1,3 +1,19 @@ { - "module_name": "iPXE \/ Boot Menu" + "dl-efi": "UEFI", + "dl-hd": "HDD partition image", + "dl-i386": "32 bit", + "dl-lkrn": "wrapped in kernel header", + "dl-pcbios": "legacy BIOS", + "dl-pcinic": "with PCI(e) NIC drivers", + "dl-snp": "uses SNP\/NII interface", + "dl-usb": "thumb drive image", + "dl-usbnic": "with USB NIC drivers", + "dl-x86_64": "64 bit", + "module_name": "iPXE \/ Boot Menu", + "page_title": "iPXE and boot settings", + "submenu_address": "Server address", + "submenu_bootentry": "Manage menu items", + "submenu_download": "Downloads", + "submenu_localboot": "HDD boot", + "submenu_menu": "Manage menus" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json b/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json index 44d1c519..53ccec3a 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/permissions.json @@ -2,5 +2,10 @@ "access-page": "View page.", "download": "Download USB Image.", "edit.address": "Choose boot address of the server.", - "edit.menu": "Customize boot menu." + "ipxe.bootentry.edit": "Edit menu items.", + "ipxe.bootentry.view": "View menu items.", + "ipxe.localboot.edit": "Edit local boot settings.", + "ipxe.menu.assign": "Assign menus to locations.", + "ipxe.menu.edit": "Edit menus.", + "ipxe.menu.view": "View menus." } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json index 121ed3e7..ea57c522 100644 --- a/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json +++ b/modules-available/serversetup-bwlp-ipxe/lang/en/template-tags.json @@ -1,42 +1,75 @@ { "lang_active": "Active", + "lang_add": "Add", "lang_addBootentry": "Add Bootentry", "lang_addMenu": "Add Menu", + "lang_additionalInfoLink": "Read more", + "lang_archAgnostic": "Architecture-agnostic", + "lang_archBoth": "BIOS and EFI", + "lang_archSelector": "Select architecture", + "lang_assignMenuToLocation": "Assign menu to location", + "lang_biosOnly": "BIOS only", "lang_bootAddress": "Boot Address of the Server", - "lang_bootBehavior": "Default Boot Behavior", - "lang_bootentryTitle": "Bootentry", - "lang_bootHint": "The Boot menu must be recreated after changing the IP address. Usually this is done automatically, but the process can also be triggered manually in the section of the boot menu.", - "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.", - "lang_bootMenu": "Boot Menu", - "lang_bootMenuCreate": "Create Boot Menu", + "lang_bootEntryData": "Menu entry data", + "lang_bootentryDeleteConfirm": "Are you sure you want to delete this menu item?", + "lang_bootentryHead": "Menu items", + "lang_bootentryIntro": "This is where you can add, edit and remove menu items, which can be added to menus. A menu entry is either a combination of a kernel\/image to load (and an optional initrd), or a custom iPXE-script.", + "lang_bootentryTitle": "Menu item", "lang_chooseIP": "Please select the IP address that the client server will use to boot.", - "lang_customEntry": "Custom entry", - "lang_bootentryDeleteConfirm": "Are you sure you want to delete this bootentry?", - "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", - "lang_downloadImage": "Download USB Image", + "lang_commandLine": "Command line", + "lang_count": "Count", + "lang_downloadBootImage": "Download boot-image", "lang_downloadRufus": "Download Rufus", - "lang_example": "Example", + "lang_editBuiltinWarn": "WARNING! You're editing a predefined item. Future updates might reset your changes!", + "lang_editMenuHead": "Edit menu", + "lang_efiOnly": "EFI only", + "lang_entryChooserTitle": "Select menu item", + "lang_entryId": "ID", + "lang_entryTitle": "Title", + "lang_execAutoUnload": "Unload after execution (--autofree)", + "lang_execReplace": "Replace current iPXE stack (--replace)", + "lang_execResetConsole": "Reset console before execution", + "lang_forceRecompile": "Force recompile", "lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error log below to the bwLehrpool project.", + "lang_hotkey": "Hotkey", + "lang_idFormatHint": "(16 chars max, a-z 0-9 - _)", + "lang_imageToLoad": "Image to load (e.g. kernel)", + "lang_initRd": "Optional initrd\/initramfs to load", + "lang_ipxeWikiUrl": "at the iPXE wiki", "lang_isDefault": "Default", "lang_listOfMenus": "Menulist", - "lang_localHDD": "Local HDD", + "lang_localBootDefault": "Default method to use for booting from disk", + "lang_localBootExceptions": "Exceptions to the local boot method, defined per system model", + "lang_localBootHead": "Boot from local disk", + "lang_localBootIntro": "There are several methods to trigger a local boot from the iPXE environment. In most cases, the \"AUTO\" setting will work, but with some system models it might be necessary to override the default behavior. In some instances, a BIOS update might resolve the issue as well.", "lang_locationCount": "Number of Locations", - "lang_masterPassword": "Master Password", - "lang_masterPasswordHelp": "The master password is required to edit a boot menu entry. This should be set for security reasons.", - "lang_menuCustom": "Custom Extra Menu", - "lang_menuCustomHint1": "Here you have the opportunity to add your own menu code to the displayed PXE menu, eg to refer to other PXE server. The format corresponds to the syslinux menu format.", - "lang_menuCustomHint2": "You can create one or more entries. If you want to create an entry that starts automatically when the user makes a selection, assign as", - "lang_menuCustomHint3": "and select as the default boot behavior custom as well.", - "lang_menuDisplayTime": "Menu Display Time", + "lang_menuDeleteConfirm": "Are you sure you want to delete this menu?", + "lang_menuEntryOverride": "Override default selection", "lang_menuGeneration": "Generating boot menu...", + "lang_menuListIntro": "This is the list of iPXE menus, with information about assigned locations. You can create, edit and delete additional menus.", + "lang_menuTimeout": "Timeout", "lang_menuTitle": "Menu", "lang_moduleHeading": "iPXE \/ Boot Menu", - "lang_pxeBuilt": "Built PXE binary", + "lang_newBootEntryHead": "New menu item", + "lang_newMenu": "New menu", + "lang_none": "(none)", + "lang_override": "Override", + "lang_recompileHint": "Recompile iPXE binaries now. Usually this happens automatically on changes, but if you suspect problems caused by outdated binaries, you can trigger recompilation here.", + "lang_refCount": "References", + "lang_referencingMenus": "Referencing menus", + "lang_scriptContent": "Script content", "lang_seconds": "Seconds", "lang_set": "Set", - "lang_usbBuilt": "Built USB image", + "lang_spacer": "Spacer", + "lang_systemmodel": "System model", + "lang_title": "Title", + "lang_typeExecEntry": "Standardeintrag", + "lang_typeScriptEntry": "Custom script", "lang_usbImage": "USB image", "lang_usbImgHelp": "The USB image can be used to create a bootable USB stick, which enables you to boot bwLehrpool without changing your DHCP settings or enabling network boot in the clients. The only requirement is that you enable USB boot in the client's BIOS. The USB stick is only used for bootstrapping, the actual bwLehrpool system is still loaded via network from your local bwLehrpool server.", + "lang_usbImgHelpBtn": "Create bootable thumb drive", "lang_usbImgHelpLinux": "On Linux you can simply use dd to write the image to a usb stick. The image already contains a partition table, so make sure you write the image to the device itself and not to an already existing partition (e.g. to \/dev\/sdx not \/dev\/sdx1)", - "lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go." + "lang_usbImgHelpWindows": "On Windows you need to use a 3rd party tool that can directly write to usb sticks. There are several free and open source soltions, one of them being Rufus. Rufus has been tested with the bwLehrpool image and is very simple to use. After launching Rufus, just open the downloaded USB image, select the proper USB stick to write to (be careful not to overwrite the wrong drive!), and you're ready to go.", + "lang_useDefaultMenu": "Inherit from parent location", + "lang_useDefaultMenuEntry": "(Menu default)" } \ No newline at end of file diff --git a/modules-available/serversetup-bwlp-ipxe/page.inc.php b/modules-available/serversetup-bwlp-ipxe/page.inc.php index bb69fecf..6f95d754 100644 --- a/modules-available/serversetup-bwlp-ipxe/page.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/page.inc.php @@ -552,7 +552,7 @@ class Page_ServerSetup extends Page FROM serversetup_menu m WHERE menuid = :id", compact('id')); if ($menu === false) { - Message::addError('no-such-menu', $id); + Message::addError('invalid-menu-id', $id); return; } $insertParams['menuid'] = $id; diff --git a/modules-available/sysconfig/lang/de/template-tags.json b/modules-available/sysconfig/lang/de/template-tags.json index 1a09abbd..2a7a02d6 100644 --- a/modules-available/sysconfig/lang/de/template-tags.json +++ b/modules-available/sysconfig/lang/de/template-tags.json @@ -47,6 +47,8 @@ "lang_fixNumeric": "Numerischen Account-Namen muss ein 's' vorangestellt werden", "lang_fixNumericDescription": "Wenn Sie diese Option aktivieren, m\u00fcssen Benutzer, deren Account-Name nur aus Ziffern besteht, diesem ein 's' voranstellen beim Login. Diese Option ist beim alten Login-Manager (KDM) zwingend erforderlich, da sonst der Loginvorgang fehlschl\u00e4gt. Mit dem neuen lightdm-basierten Login-Screen lassen sich numerische Account-Namen jedoch direkt verwenden. Wenn Sie an Ihrer Einrichtung keine numerischen Account-Namen verwenden, hat diese Option keine Auswirkung.", "lang_folderRedirection": "Folder Redirection", + "lang_genUid": "uid-Nummern generieren", + "lang_genUidDescription": "Wenn aktiviert, generiert der Satellitenserver nummerische IDs f\u00fcr die Benutzer, anstatt diese aus dem LDAP\/AD zu extrahieren.", "lang_generateModule": "Modul erzeugen", "lang_handlingNotes": "Hier k\u00f6nnen Sie festlegen, wie Netzwerk-Shares (inkl. des Home-Verzeichnisses) an Virtuelle Maschinen durchgereicht werden. In \u00e4lteren Versionen von bwLehrpool wurden die VMware Shared Folders genutzt, was mit bestimmten file servern Probleme verursachen konnte. Der neue native Modus funktioniert deutlich besser, ist aber bei Windows-G\u00e4sten darauf angewiesen, dass (1) der file server smb\/cifs spricht (z.B. Windows Server, Samba unter Linux) und (2) die openslx.exe im Autostart eingebunden ist (bei den bwLehrpool Vorlagen bereits der Fall).", "lang_helpHomeAttrHead": "Name des Home-Verzeichnis-Attributs", diff --git a/modules-available/sysconfig/lang/en/template-tags.json b/modules-available/sysconfig/lang/en/template-tags.json index e98038a9..fb02cf42 100644 --- a/modules-available/sysconfig/lang/en/template-tags.json +++ b/modules-available/sysconfig/lang/en/template-tags.json @@ -47,6 +47,8 @@ "lang_fixNumeric": "Numeric account names have to be prefixed by 's'", "lang_fixNumericDescription": "If enabled, users with account names that consist entirely of digits have to prefix their user id by 's' when logging in. This is required with the old login manager (KDM) to prevent crashes. The new lightdm-based login manager will accept numeric account names, so you can leave this option disabled. If your organization doesn't have any numeric account names, this option will have no effect.", "lang_folderRedirection": "Folder Redirection", + "lang_genUid": "Generate uid numbers", + "lang_genUidDescription": "When selected, the satellite server will generate numeric IDs for the users, instead of extracting them from AD\/LDAP.", "lang_generateModule": "Generating module", "lang_handlingNotes": "Here you can configure how network shares (like the user's home directory) are mapped inside the VM. Old Versions of bwLehrpool used the VMware Shared Folder technique, which could cause problems with certain file servers. The new \"native mode\" works much better, but on Windows guests, it requires that you (1) use an smb\/cifs file server (Windows Server, Linux with Samba) and (2) have openslx.exe setup to autorun in the VM (this is already configured for bwLehrpool templates).", "lang_helpHomeAttrHead": "Name of the home directory attribute", -- cgit v1.2.3-55-g7522 From 8de5f1ccfef21226329f94b13f656a0aa8153eec Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 19 Feb 2019 11:01:20 +0100 Subject: [baseconfig_bwlp] Update translations --- modules-available/baseconfig_bwlp/lang/de/config-variables.json | 3 +++ modules-available/baseconfig_bwlp/lang/en/config-variables.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/modules-available/baseconfig_bwlp/lang/de/config-variables.json b/modules-available/baseconfig_bwlp/lang/de/config-variables.json index e997b66f..78481da9 100644 --- a/modules-available/baseconfig_bwlp/lang/de/config-variables.json +++ b/modules-available/baseconfig_bwlp/lang/de/config-variables.json @@ -11,6 +11,7 @@ "SLX_NET_SEARCH": "Per Leerzeichen getrennte Liste von Suchdom\u00e4nen, die der Client verwenden soll, sofern der DHCP-Server keine Vorgabe macht.", "SLX_NTP_SERVER": "Adresse des NTP-Zeitservers. Es k\u00f6nnen mehrere Server mit Leerzeichen getrennt angegeben werden.Die Server werden der Reihe nach angefragt, bis ein antwortender Server gefunden wird.", "SLX_PASSTHROUGH_USB_ID": "Geben Sie hier eindeutige IDs von USB-Ger\u00e4ten an, die direkt in die VMs weitergereicht werden sollen. Das erwartete Format ist *vendorID:productID* , als jeweils vierstellige Hexadezimalzahlen, beispielsweise *1234:abcd* .\r\nMehrere IDs k\u00f6nnen als leerzeichengetrennte Liste angegeben werden.", + "SLX_PREFERRED_SOUND_OUTPUT": "Bevorzugte Ausgabemethode f\u00fcr Sound. Standardm\u00e4\u00dfig werden dedizierte Soundkarten bevorzugt, da die Ausgabe \u00fcber HDMI mitunter Probleme bereiten kann, besonders wenn im Betrieb Bildschirme an- oder abgesteckt werden.", "SLX_PRINT_USER_PREFIX": "Pr\u00e4fix, was im Authentifizierungsdialog der PrinterGUI dem Benutzernamen vorangestellt wird.\r\nWenn das Drucksystem auf einem AD-Server l\u00e4uft und der Dom\u00e4nenname vorangestellt werden muss, tragen Sie hier *domain\\* ein. Achten Sie auf die Angabe des Backslashes, er wird nicht automatisch angeh\u00e4ngt. Falls das Drucksystem mit dem reinen Benutzernamen zurecht kommt, k\u00f6nnen Sie das Feld leer lassen.", "SLX_PROXY_BLACKLIST": "Adressen bzw. Adressbereiche, f\u00fcr die der Proxyserver nicht verwendet werden soll (z.B. der Adressbereich der Einrichtung). G\u00fcltige Angaben sind einzelne IP-Adressen, sowie IP-Bereiche in CIDR-Notation (z.B. 1.2.0.0\/16). Mehrere Angaben k\u00f6nnen durch Leerzeichen getrennt werden.", "SLX_PROXY_IP": "Die Adresse des zu verwendenden Proxy Servers.", @@ -21,6 +22,8 @@ "SLX_REBOOT_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner neustarten, auch wenn noch ein Benutzer aktiv ist.\r\nMehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.", "SLX_REMOTE_LOG_SESSIONS": "Legt fest, ob Logins und Logouts der Benutzer an den Satelliten gemeldet werden sollen.\r\n*yes* = Mit Benutzerkennung loggen\r\n*anonymous* = Anonym loggen\r\n*no* = Nicht loggen", "SLX_ROOT_PASS": "Das root-Passwort des Grundsystems. Wird nur f\u00fcr Diagnosezwecke am Client ben\u00f6tigt.\r\nFeld leer lassen, um root-Logins zu verbieten.\r\n\/Hinweis\/: Das Passwort wird im Klartext in der lokalen Datenbank hinterlegt, jedoch immer gehasht an die Clients \u00fcbermittelt (SHA-512 mit Salt). Wenn Sie das Passwort auch im Satelliten nicht im Klartext speichern wollen, k\u00f6nnen Sie hier auch ein vorgehashtes Passwort eintragen (im *$6$....*-Format).", + "SLX_SCREEN_SAVER_GRACE_TIMEOUT": "Wenn sich der Bildschirmschoner nach dem konfigurierten Timeout automatisch aktiviert, kann er f\u00fcr die hier angegebene Zeit (in Sekunden) durch Tastendruck oder Mausbewegen wieder deaktiviert werden, ohne dass das Benutzerkennwort angefordert wird.", + "SLX_SCREEN_SAVER_TIMEOUT": "Zeit in Sekunden, nach der sich bei Nutzerinaktivit\u00e4t der Bildschirmschoner aktiviert. Der Bildschirmschoner sperrt zugleich die Sitzung und fordert zum Entsperren das Nutzerkennwort an.", "SLX_SCREEN_STANDBY_TIMEOUT": "Zeit in Sekunden, nach der der Bildschirm bei Inaktivit\u00e4t des Rechners in den Standby-Modus versetzt wird.", "SLX_SHUTDOWN_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner ausschalten, auch wenn noch ein Benutzer aktiv ist.\r\nMehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.", "SLX_SHUTDOWN_TIMEOUT": "Zeit in Sekunden, nach der ein Rechner abgeschaltet wird, sofern kein Benutzer angemeldet ist.\r\nFeld leer lassen, um die Funktion zu deaktivieren.", diff --git a/modules-available/baseconfig_bwlp/lang/en/config-variables.json b/modules-available/baseconfig_bwlp/lang/en/config-variables.json index e8849382..9fd65053 100644 --- a/modules-available/baseconfig_bwlp/lang/en/config-variables.json +++ b/modules-available/baseconfig_bwlp/lang/en/config-variables.json @@ -11,6 +11,7 @@ "SLX_NET_SEARCH": "Space separated list of DNS search domains to use in case the DHCP server doesn't supply any.", "SLX_NTP_SERVER": "Address of the NTP time server. Multiple servers can be specified separated by spaces.The servers are queried in sequence until a responding server is found.", "SLX_PASSTHROUGH_USB_ID": "Specify IDs of USB devices that should be passed through to the VM directly.\r\nThe expected format is *vendorID:productID* , where each ID is a 4-digit hexadecimal number, e.g. *1234:abcd* \r\nMultiple IDs can be given as a space-separated list.", + "SLX_PREFERRED_SOUND_OUTPUT": "Preferred sound output method.\r\nDefaults to dedicated sound card, since using HDMI can be unreliable, especially if screens get (un)plugged while the (virtual) machine is running.", "SLX_PRINT_USER_PREFIX": "Prefix to add to the user name in the authentication dialog of PrinterGUI.\r\nIf your print server belongs to a Windows domain and requires the domain name prefixed, set this field to *domainname\\*. Note the trailing backslash, it will not be inserted automatically. If your print server just wants the plain user name, this field should be left blank.", "SLX_PROXY_BLACKLIST": "Address or addresses ranges in which the proxy server is not used (for example the address range of the device). Valid entries are individual IP addresses and IP ranges in CIDR notation (for example 1.2.0.0\/16). Multiple selections can be separated by spaces.", "SLX_PROXY_IP": "The address to use for the proxy server.", @@ -21,6 +22,8 @@ "SLX_REBOOT_SCHEDULE": "Fixed time to reboot the computer, even if there is a user active.\r\nSeveral times can be specified, separated by spaces.", "SLX_REMOTE_LOG_SESSIONS": "Determines whether logins and logouts of the users should be reported to the satellite.\r\n*yes* = log with user ID\r\n*anonymous* = anonymous logging\r\n*no* = no logging", "SLX_ROOT_PASS": "The root password of the client system. Only required for diagnostic purposes on the client.Leave field blank to disallow root logins.\r\n\/Hint\/: The password SHA-512-with-salt hashed before it's being sent to the client. It's only stored in clear text on the Satellite Server. If you want to have it hashed on the server too, you can supply a pre-hashed password in \/$6$...$...\/-format.", + "SLX_SCREEN_SAVER_GRACE_TIMEOUT": "If the screen saver activates after the configured timeout, the user can disable it again by just moving the mouse or pressing a key, without entering their password again. This is called the screen saver grace period, which is configurable in seconds.", + "SLX_SCREEN_SAVER_TIMEOUT": "Timeout for screen saver activation. If the user is idle for this long (in seconds), the screen saver will activate and lock the screen, so the user password is required to unlock the screen again.", "SLX_SCREEN_STANDBY_TIMEOUT": "Time in seconds after which the screen will enter power saving mode, if the client is not in use.", "SLX_SHUTDOWN_SCHEDULE": "Fixed time to turn off the computer, even if there is a user active.\r\nSeveral times can be specified, separated by spaces.", "SLX_SHUTDOWN_TIMEOUT": "Time in seconds after which a computer is switched off, if no user is logged on.\r\nLeave blank to disable the function.", -- cgit v1.2.3-55-g7522 From d6da57101c3e4b54d25f65978594bbdec11205f8 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 19 Feb 2019 12:35:03 +0100 Subject: [locationinfo] Fix events not showing HISinOne start and end dates are not reliable - ignore and check individual planned dates. --- .../locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php index c71623b3..2ffb9f41 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php @@ -294,12 +294,17 @@ class CourseBackend_HisInOne extends CourseBackend foreach ($planElements as $planElement) { if (empty($planElement['hisplannedDates'])) continue; + // Do not use -- is set improperly for some courses :-( + /* $checkDate = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisenddate'); if (!empty($checkDate) && strtotime($checkDate[0]) + 86400 < $now) continue; // Course ended $checkDate = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisstartdate'); if (!empty($checkDate) && strtotime($checkDate[0]) - 86400 > $now) continue; // Course didn't start yet + */ + $cancelled = $this->getArrayPath($planElement, '/hiscancelled'); + $cancelled = $cancelled !== false && is_array($cancelled) && ($cancelled[0] > 0 || strtolower($cancelled[0]) === 'true'); $unitPlannedDates = $this->getArrayPath($planElement, '/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate'); if ($unitPlannedDates === false) { @@ -321,7 +326,8 @@ class CourseBackend_HisInOne extends CourseBackend $tTables[$eventRoomId][] = array( 'title' => $localName[0], 'start' => $eventDate . "T" . $startTime, - 'end' => $eventDate . "T" . $endTime + 'end' => $eventDate . "T" . $endTime, + 'cancelled' => $cancelled, ); } } -- cgit v1.2.3-55-g7522 From d8d9fec8d3f19440a5f11eee401ea5b03ea17d77 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 19 Feb 2019 12:35:34 +0100 Subject: [js_weekcalendar] Only show short header if event is < 1 hr --- modules-available/js_weekcalendar/clientscript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules-available/js_weekcalendar/clientscript.js b/modules-available/js_weekcalendar/clientscript.js index 28b9e3cf..67637e65 100755 --- a/modules-available/js_weekcalendar/clientscript.js +++ b/modules-available/js_weekcalendar/clientscript.js @@ -132,7 +132,7 @@ function MyDate() { eventHeader: function(calEvent, calendar) { var options = calendar.weekCalendar('option'); var one_hour = 3600000; - var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() <= (one_hour / options.timeslotsPerHour); + var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() < (one_hour / options.timeslotsPerHour); if (displayTitleWithTime) { return calendar.weekCalendar( 'formatTime', calEvent.start) + -- cgit v1.2.3-55-g7522 From a46ab41355a1ca1a7507358c07045b0ba5ae1f59 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 19 Feb 2019 16:24:22 +0100 Subject: [statistics] Fix RAM change warning to handle increase too --- modules-available/statistics/api.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 280e5abc..d4b8f346 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -441,7 +441,8 @@ function checkHardwareChange($old, $new) $ram2 = ceil($new['mbram'] / 1024); } if ($ram1 !== $ram2) { - EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM decreased from {$ram1}GB to {$ram2}GB"); + $word = $ram1 > $ram2 ? 'decreased' : 'increased'; + EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM $word from {$ram1}GB to {$ram2}GB"); } if (!empty($old['cpumodel']) && !empty($new['cpumodel']) && $new['cpumodel'] !== $old['cpumodel']) { EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): CPU changed from '{$old['cpumodel']}' to '{$new['cpumodel']}'"); -- cgit v1.2.3-55-g7522