summaryrefslogtreecommitdiffstats
path: root/modules-available/dozmod
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/dozmod')
-rw-r--r--modules-available/dozmod/api.inc.php107
-rw-r--r--modules-available/dozmod/lang/de/messages.json13
-rw-r--r--modules-available/dozmod/lang/de/permissions.json2
-rw-r--r--modules-available/dozmod/lang/de/template-tags.json20
-rw-r--r--modules-available/dozmod/lang/en/messages.json1
-rw-r--r--modules-available/dozmod/lang/en/module.json10
-rw-r--r--modules-available/dozmod/lang/en/permissions.json4
-rw-r--r--modules-available/dozmod/lang/en/template-tags.json20
-rw-r--r--modules-available/dozmod/page.inc.php67
-rw-r--r--modules-available/dozmod/pages/actionlog.inc.php124
-rw-r--r--modules-available/dozmod/pages/expiredimages.inc.php134
-rw-r--r--modules-available/dozmod/pages/mailconfig.inc.php2
-rw-r--r--modules-available/dozmod/pages/networkrules.inc.php2
-rw-r--r--modules-available/dozmod/pages/networkshares.inc.php2
-rw-r--r--modules-available/dozmod/pages/runscripts.inc.php4
-rw-r--r--modules-available/dozmod/pages/runtimeconfig.inc.php26
-rw-r--r--modules-available/dozmod/pages/special.inc.php85
-rw-r--r--modules-available/dozmod/pages/templates.inc.php2
-rw-r--r--modules-available/dozmod/pages/users.inc.php8
-rw-r--r--modules-available/dozmod/permissions/permissions.json6
-rw-r--r--modules-available/dozmod/templates/actionlog-lecture.html8
-rw-r--r--modules-available/dozmod/templates/blockstats.html5
-rw-r--r--modules-available/dozmod/templates/images-delete.html172
-rw-r--r--modules-available/dozmod/templates/images-orphaned.html25
-rw-r--r--modules-available/dozmod/templates/runtimeconfig.html16
25 files changed, 550 insertions, 315 deletions
diff --git a/modules-available/dozmod/api.inc.php b/modules-available/dozmod/api.inc.php
index d9f7354c..b5030cc5 100644
--- a/modules-available/dozmod/api.inc.php
+++ b/modules-available/dozmod/api.inc.php
@@ -10,6 +10,8 @@
**/
+use JetBrains\PhpStorm\NoReturn;
+
if (!Module::isAvailable('locations')) {
die('require locations module');
}
@@ -17,27 +19,30 @@ if (!Module::isAvailable('locations')) {
define('LIST_URL', CONFIG_DOZMOD_URL . '/vmchooser/list');
define('VMX_URL', CONFIG_DOZMOD_URL . '/vmchooser/lecture');
-$availableRessources = ['list', 'vmx', 'netrules', 'runscript', 'metadata', 'netshares'];
+$availableRessources = ['list', 'netrules', 'metadata', 'imagemeta'];
/* BEGIN: A simple caching mechanism ---------------------------- */
-function cache_hash($obj)
+function cache_hash($obj): string
{
return md5(serialize($obj));
}
-function cache_key_to_filename($key)
+function cache_key_to_filename(string $key): string
{
return "/tmp/bwlp-slxadmin-cache-$key";
}
-function cache_put($key, $value)
+function cache_put(string $key, string $value): void
{
$filename = cache_key_to_filename($key);
- file_put_contents($filename, $value);
+ // Try to avoid another client concurrently accessing the cache seeing an empty file
+ $tmp = $filename . '-' . mt_rand();
+ file_put_contents($tmp, $value);
+ rename($tmp, $filename);
}
-function cache_has($key)
+function cache_has(string $key): bool
{
$filename = cache_key_to_filename($key);
$mtime = @filemtime($filename);
@@ -46,21 +51,18 @@ function cache_has($key)
return false; // cache miss
}
$now = time();
- if ($now < $mtime || $now - $mtime > CONFIG_DOZMOD_EXPIRE) {
- return false;
- } else {
- return true;
- }
+ return $now >= $mtime && $now - $mtime <= CONFIG_DOZMOD_EXPIRE;
}
-function cache_get($key)
+function cache_get(string $key): string
{
$filename = cache_key_to_filename($key);
return file_get_contents($filename);
}
/* good for large binary files */
-function cache_get_passthru($key)
+#[NoReturn]
+function cache_get_passthru(string $key): void
{
$filename = cache_key_to_filename($key);
$fp = fopen($filename, "r");
@@ -68,7 +70,7 @@ function cache_get_passthru($key)
fpassthru($fp);
exit;
}
- error_log('Cannot passthrough cache file ' . $filename);
+ error_log('DMSD-cache: Cannot passthrough cache file ' . $filename);
}
/* END: Cache ---------------------------------------------------- */
@@ -76,7 +78,7 @@ function cache_get_passthru($key)
/* this script requires 2 (3 with implicit client ip) parameters
*
-* resource = vmx,...
+* resource = list,metadata,...
* lecture_uuid = client can choose
**/
@@ -85,11 +87,16 @@ function cache_get_passthru($key)
* Takes raw lecture list xml, returns array of uuids.
*
* @param string $responseXML XML from dozmod server
- * @return array list of UUIDs, false on error
+ * @return array list of UUIDs
*/
-function xmlToLectureIds($responseXML)
+function xmlToLectureIds(string $responseXML): array
{
- $xml = new SimpleXMLElement($responseXML);
+ try {
+ $xml = new SimpleXMLElement($responseXML);
+ } catch (Exception $e) {
+ EventLog::warning('Error parsing XML response data from DMSD: ' . $e->getMessage(), $responseXML);
+ return [];
+ }
if (!isset($xml->eintrag))
return [];
@@ -102,7 +109,8 @@ function xmlToLectureIds($responseXML)
return $uuids;
}
-function sendExamModeMismatch()
+#[NoReturn]
+function sendExamModeMismatch(): void
{
Header('Content-Type: text/xml; charset=utf-8');
echo
@@ -112,8 +120,8 @@ function sendExamModeMismatch()
<image_name param="null"/>
<priority param="100"/>
<creator param="Ernie Esslingen"/>
- <short_description param="Klausurmodus geändert, bitte PC neustarten"/>
- <long_description param="Der Klausurmodus wurde ein- oder ausgeschaltet, bitte starten Sie den PC neu"/>
+ <short_description param="Prüfungsmodus geändert, bitte PC neustarten"/>
+ <long_description param="Der Prüfungsmodus wurde ein- oder ausgeschaltet, bitte starten Sie den PC neu"/>
<uuid param="exam-mode-warning"/>
<virtualmachine param="exam-mode-warning"/>
<os param="debian8"/>
@@ -142,7 +150,7 @@ BLA;
}
/** Caching wrapper around _getLecturesForLocations() */
-function getListForLocations($locationIds, $raw)
+function getListForLocations(array $locationIds, bool $raw)
{
/* if in any of the locations there is an exam active, consider the client
to be in "exam-mode" and only offer him exams (no lectures) */
@@ -179,7 +187,12 @@ function getListForLocations($locationIds, $raw)
if ($examMode) {
$url .= '&exams';
}
+ $t = microtime(true);
$value = Download::asString($url, 60, $code);
+ $t = microtime(true) - $t;
+ if ($t > 5) {
+ error_log("DMSD-cache: Download of lecture list took $t ($code)");
+ }
if ($value === false || $code < 200 || $code > 299)
return false;
cache_put($rawKey, $value);
@@ -191,30 +204,37 @@ function getListForLocations($locationIds, $raw)
return $list;
}
-function getLectureUuidsForLocations($locationIds)
+function getLectureUuidsForLocations(array $locationIds)
{
return getListForLocations($locationIds, false);
}
-function outputLectureXmlForLocation($locationIds)
+function outputLectureXmlForLocation(array $locationIds)
{
return getListForLocations($locationIds, true);
}
-function _getVmData($lecture_uuid, $subResource = false)
+function _getVmData(string $lecture_uuid, string $subResource = null)
{
$url = VMX_URL . '/' . $lecture_uuid;
- if ($subResource !== false) {
+ if ($subResource !== null) {
$url .= '/' . $subResource;
}
+ $t = microtime(true);
$response = Download::asString($url, 60, $code);
- if ($code < 200 || $code > 299)
+ $t = microtime(true) - $t;
+ if ($t > 5) {
+ error_log("DMSD-cache: Download of $subResource took $t ($code)");
+ }
+ if ($code < 200 || $code > 299) {
+ error_log("DMSD-cache: Return code $code, payload len " . strlen($response));
return (int)$code;
+ }
return $response;
}
/** Caching wrapper around _getVmData() **/
-function outputResource($lecture_uuid, $resource)
+function outputResource(string $lecture_uuid, string $resource): void
{
if ($resource === 'metadata') {
// HACK: config.tgz is compressed, don't use gzip output handler
@@ -235,7 +255,7 @@ function outputResource($lecture_uuid, $resource)
} else {
$value = _getVmData($lecture_uuid, $resource);
if ($value === false)
- return false;
+ return;
if (is_int($value)) {
http_response_code($value);
exit;
@@ -243,16 +263,16 @@ function outputResource($lecture_uuid, $resource)
cache_put($key, $value);
die($value);
}
- return false;
}
+#[NoReturn]
function fatalDozmodUnreachable()
{
Header('HTTP/1.1 504 Gateway Timeout');
die('DMSD currently not available');
}
-function readLectureParam($locationIds)
+function readLectureParam(array $locationIds): string
{
$lecture = Request::get('lecture', false, 'string');
if ($lecture === false) {
@@ -272,12 +292,26 @@ function readLectureParam($locationIds)
}
+// in this context the lecture param is an image id (container),
+// just read and check if valid.
+// TODO do we need to check if this is allowed?
+function readImageParam(): string
+{
+ $image = Request::get('lecture', false, 'string');
+
+ if ($image === false) {
+ Header('HTTP/1.1 400 Bad Request');
+ die('Missing IMAGE UUID');
+ }
+ return $image;
+}
+
// -----------------------------------------------------------------------------//
/* request data, don't trust */
$resource = Request::get('resource', false, 'string');
if ($resource === false) {
- Util::traceError("you have to specify the 'resource' parameter");
+ ErrorHandler::traceError("you have to specify the 'resource' parameter");
}
if (!in_array($resource, $availableRessources)) {
@@ -298,12 +332,11 @@ $location_ids = Location::getLocationRootChain($location_ids);
if ($resource === 'list') {
outputLectureXmlForLocation($location_ids);
// Won't return on success...
- fatalDozmodUnreachable();
+} elseif ($resource === 'imagemeta') {
+ $image = readImageParam();
+ outputResource($image, $resource);
} else {
$lecture = readLectureParam($location_ids);
outputResource($lecture, $resource);
- fatalDozmodUnreachable();
}
-
-Header('HTTP/1.1 400 Bad Request');
-die("I don't know how to give you that resource");
+fatalDozmodUnreachable();
diff --git a/modules-available/dozmod/lang/de/messages.json b/modules-available/dozmod/lang/de/messages.json
index a2d6a0ae..c904b0c8 100644
--- a/modules-available/dozmod/lang/de/messages.json
+++ b/modules-available/dozmod/lang/de/messages.json
@@ -1,29 +1,28 @@
{
"all-templates-reset": "Alle Templates wurden zur\u00fcckgesetzt",
- "delete-images": "L\u00f6schung: {{0}}",
- "dozmod-error": "Fehler bei der Kommunikation mit dem bwLehrpool-Suite server: {{0}}",
+ "dozmod-error": "Fehler bei der Kommunikation mit dem bwLehrpool-Suite Server: {{0}}",
"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-filter-id-missing": "Fehlende LDAP Filter ID",
- "ldap-filter-insert-failed": "LDAP filter konnte der Datenbank nicht hinzugef\u00fcgt werden",
+ "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",
- "networkrule-deleted": "Netzwerk-Regel gel\u00f6scht",
+ "networkrule-deleted": "Netzwerkregel gel\u00f6scht",
"networkrule-empty-set": "Leeres Regelset; nicht gespeichert",
"networkrule-invalid-direction": "Ung\u00fcltige Richtung: {{0}}",
"networkrule-invalid-host": "Ung\u00fcltiger Host: {{0}}; Zeile ignoriert",
"networkrule-invalid-port": "Ung\u00fcltiger Port: {{0}}; Zeile ignoriert",
"networkrule-invalid-ruleid": "Nicht-existierende Regel: {{0}}",
- "networkrule-saved": "Netzwerk-Regel gespeichert",
+ "networkrule-saved": "Netzwerkregel 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",
+ "no-expired-images": "Keine abgelaufenen VM-Versionen",
"nothing-submitted": "Es wurde nichts \u00fcbermittelt",
"runscript-deleted": "Skript gel\u00f6scht",
"runscript-invalid-id": "Ung\u00fcltige Skript-ID: {{0}}",
@@ -33,4 +32,4 @@
"timeout": "Zeit\u00fcberschreitung",
"unknown-targetid": "Target {{0}} nicht bekannt",
"unknown-userid": "Unbekannter Nutzer, {{0}}"
-}
+} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/permissions.json b/modules-available/dozmod/lang/de/permissions.json
index 8e743e5c..6159362d 100644
--- a/modules-available/dozmod/lang/de/permissions.json
+++ b/modules-available/dozmod/lang/de/permissions.json
@@ -8,6 +8,8 @@
"networkrules.view": "Netzwerk-Regeln einsehen.",
"networkshares.save": "\u00c4nderungen an den Netzlaufwerken speichern.",
"networkshares.view": "Netzlaufwerke einsehen.",
+ "orphaned.delete": "Verwaiste Dateien vom VM-Store l\u00f6schen.",
+ "orphaned.scan": "Nach Verwaisten Dateien auf VM-Store suchen.",
"runscripts.save": "Startkripte erstellen\/bearbeiten.",
"runscripts.view": "Startscripte auflisten.",
"runtimeconfig.save": "\u00c4nderungen an der Laufzeit-Konfiguration speichern.",
diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json
index 5bc2f2b1..338e8e42 100644
--- a/modules-available/dozmod/lang/de/template-tags.json
+++ b/modules-available/dozmod/lang/de/template-tags.json
@@ -5,22 +5,26 @@
"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_allowStudentDownload": "Studenten den Download lizenzfreier VMs erlauben",
+ "lang_allowStudentDownloadDescription": "Wenn diese Option aktiviert ist, k\u00f6nnen Studenten alle VMs herunterladen, bei denen beim Upload der Haken \"enth\u00e4lt lizenzpflichtige Software\" nicht gesetzt wurde. Es liegt hier in der Verantwortung der VM-Ersteller, diesen Haken nur unter entsprechenden Umst\u00e4nden zu entfernen.",
"lang_asteriskRequired": "Felder mit (*) sind erforderlich",
"lang_authMethod": "Authentifizierung",
"lang_blockCount": "Anzahl Bl\u00f6cke",
"lang_bwlehrpoolsuite": "bwLehrpool-Suite",
"lang_canLoginOrganization": "Nutzer dieser Einrichtung k\u00f6nnen sich am Satelliten anmelden",
"lang_canLoginUser": "Nutzer kann sich am Satelliten anmelden",
+ "lang_confirmDeleteOrphanedFiles": "Sind Sie sicher, dass Sie alle aufgelisteten Dateien unwiderruflich vom VM-Store l\u00f6schen wollen?",
"lang_createTime": "Erstellt",
"lang_currentFilter": "Aktueller Filter",
- "lang_defaultImagePermissionAdmin": "Administrieren",
+ "lang_defaultImagePermissionAdmin": "Administration",
"lang_defaultImagePermissionDownload": "Download",
"lang_defaultImagePermissionEdit": "Bearbeiten",
- "lang_defaultImagePermissionLink": "Veranstaltung Verkn\u00fcpfen",
+ "lang_defaultImagePermissionLink": "Veranstaltung verkn\u00fcpfen",
"lang_defaultImagePermissions": "F\u00fcr VMs",
"lang_defaultLecturePermissions": "F\u00fcr Veranstaltungen",
"lang_defaultPermissions": "Standardberechtigungen",
"lang_delButton": "Gew\u00e4hlte VMs endg\u00fcltig l\u00f6schen",
+ "lang_deleteExpiredHeading": "Zu l\u00f6schende VM-Versionen",
"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.",
@@ -33,14 +37,15 @@
"lang_emailNotifications": "EMail-Benachrichtigungen aktiviert",
"lang_error": "Fehler",
"lang_event": "Ereignis",
+ "lang_fileName": "Dateiname",
"lang_fileSize": "Dateigr\u00f6\u00dfe",
"lang_followingPlaceholdersUnused": "Folgende Platzhalter m\u00fcssen im Template verwendet werden",
"lang_hasNewer": "Neuere Version existiert",
"lang_hash": "Hash",
- "lang_heading": "Zu l\u00f6schende VM-Versionen",
"lang_hidden": "Versteckt",
"lang_host": "Host",
"lang_image": "VM",
+ "lang_lastBoot": "Letzter Start",
"lang_lastEditor": "Zuletzt bearbeitet von",
"lang_lastLogin": "Letzte Anmeldung",
"lang_latestVersion": "Neuste Version",
@@ -65,6 +70,7 @@
"lang_maxLectureVisibility": "Sp\u00e4testes Enddatum einer Veranstaltung (Tage in der Zukunft)",
"lang_maxLocationsPerLecture": "Max. explizite Orte pro Veranstaltung",
"lang_maxTransfers": "Maximale Zahl gleichzeitiger Up-\/Downloads pro Benutzer",
+ "lang_maxVmHddSizeGb": "Maximale VM-Gr\u00f6\u00dfe (GiB, 0 = kein Limit)",
"lang_minimized": "Minimiert",
"lang_miscOptions": "Verschiedene Einstellungen",
"lang_modified": "Modifiziert",
@@ -75,8 +81,12 @@
"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_normal": "Normal",
+ "lang_numberBoots": "Anzahl Starts",
"lang_organization": "Einrichtung",
"lang_organizationListHeader": "Nutzungsrechte f\u00fcr den Satelliten festlegen",
+ "lang_orphanDeleteButton": "Dateien l\u00f6schen",
+ "lang_orphanedFilesDesc": "Hier aufgelistete Dateien geh\u00f6ren zu keiner aus der Datenbank bekannten VM. Sollten Sie sich sicher sein, dass diese Dateien nicht anderweitig verwendet werden, oder h\u00e4ndisch auf dem VM-Store abgelegt wurden, k\u00f6nnen Sie diese Dateien l\u00f6schen, um Speicherplatz zu gewinnen. Wenn Sie sich nicht sicher sind, untersuchen Sie die Situation auf dem VM-Store h\u00e4ndisch.",
+ "lang_orphanedFilesHeading": "Verwaiste Images und Dateien auf dem VM-Store",
"lang_os": "Betriebssystem",
"lang_owner": "Besitzer",
"lang_passwordCleartextHint": "Bitte beachten Sie, dass ein hier explizit angegebenes Passwort im Klartext an das Poolsystem \u00fcbergeben wird. Sie sollten also nur dedizierte Funktionsaccounts nutzen, die keinen Zugriff auf weitere Systeme erm\u00f6glichen.",
@@ -93,6 +103,7 @@
"lang_runScriptDeleteConfirmation": "Skript wirklich l\u00f6schen?",
"lang_runtimeConfig": "Laufzeit-Konfiguration",
"lang_runtimeConfigLimits": "Beschr\u00e4nkungen",
+ "lang_scanButton": "VM-Store durchsuchen",
"lang_scriptContent": "Skriptinhalt",
"lang_scriptExtension": "Dateinamenerweiterung",
"lang_scriptExtensionHead": "Erweiterung",
@@ -108,7 +119,7 @@
"lang_senderAddress": "Absenderadresse",
"lang_senderName": "Absender Anzeigename",
"lang_serverSideCopy": "Serverseitiges Kopieren",
- "lang_serverSideCopyDescription": "Wenn aktiviert, werden bei VM-Uploads solche Bl\u00f6cke, die bereits in anderen VM-Abbildern vorhanden sind, nicht erneut vom Client hochgeladen, sondern durch den Satelliten-Server vom verwendeten Fileserver gelesen und kopiert. Abh\u00e4ngig von der Netzwerkinfrastruktur und Hardwareausstattung des Fileservers kann dies den Uploadvorgang merklich beschleunigen. Da diese Funktion allerdings zus\u00e4tzliche I\/O-Last auf dem Fileserver erzeugt, ist ihre Verwendung u.U. nicht erw\u00fcnscht.",
+ "lang_serverSideCopyDescription": "Wenn aktiviert, werden bei VM-Uploads solche Bl\u00f6cke, die bereits in anderen VM-Abbildern vorhanden sind, nicht erneut vom Client hochgeladen, sondern durch den Satellitenserver vom verwendeten Fileserver gelesen und kopiert. Abh\u00e4ngig von der Netzwerkinfrastruktur und Hardwareausstattung des Fileservers kann dies den Uploadvorgang merklich beschleunigen. Da diese Funktion allerdings zus\u00e4tzliche I\/O-Last auf dem Fileserver erzeugt, ist ihre Verwendung u.U. nicht erw\u00fcnscht.",
"lang_shareDeleteConfirm": "Wollen Sie dieses Netzlaufwerk wirklich l\u00f6schen?",
"lang_size": "Gr\u00f6\u00dfe",
"lang_spaceWastedDuplication": "Potentiell durch mehrfach vorkommende Bl\u00f6cke belegter Speicherplatz",
@@ -121,6 +132,7 @@
"lang_sslExplicit": "Explizites SSL (\"STARTTLS\")",
"lang_sslImplicit": "Implizites SSL",
"lang_sslNone": "Kein SSL",
+ "lang_status": "Status",
"lang_superUser": "Ist SuperUser (darf alle Veranstaltungen und VMs bearbeiten\/l\u00f6schen)",
"lang_system": "System",
"lang_target": "Ziel",
diff --git a/modules-available/dozmod/lang/en/messages.json b/modules-available/dozmod/lang/en/messages.json
index 84677402..1b46339b 100644
--- a/modules-available/dozmod/lang/en/messages.json
+++ b/modules-available/dozmod/lang/en/messages.json
@@ -1,6 +1,5 @@
{
"all-templates-reset": "All templates have been reset",
- "delete-images": "Delete: {{0}}",
"dozmod-error": "Error communicating with the bwLehrpool-Suite server: {{0}}",
"images-pending-delete-exist": "VMs marked for deletion: {{0}}",
"ldap-filter-created": "LDAP filter was successfully created",
diff --git a/modules-available/dozmod/lang/en/module.json b/modules-available/dozmod/lang/en/module.json
index 5bcee464..81996b10 100644
--- a/modules-available/dozmod/lang/en/module.json
+++ b/modules-available/dozmod/lang/en/module.json
@@ -1,14 +1,14 @@
{
"module_name": "bwLehrpool-Suite",
"page_title": "Manage the bwLehrpool-Suite",
- "submenu_actionlog": "action log",
+ "submenu_actionlog": "Action log",
"submenu_expiredimages": "Expired VM versions",
"submenu_ldapfilters": "LDAP filters",
- "submenu_mailconfig": "email configuration",
+ "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"
+ "submenu_runtimeconfig": "Limits and defaults",
+ "submenu_templates": "Templates",
+ "submenu_users": "Users and permissions"
} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/permissions.json b/modules-available/dozmod/lang/en/permissions.json
index b0fbb071..479b150b 100644
--- a/modules-available/dozmod/lang/en/permissions.json
+++ b/modules-available/dozmod/lang/en/permissions.json
@@ -8,6 +8,8 @@
"networkrules.view": "View network rules.",
"networkshares.save": "Save network drives.",
"networkshares.view": "View network drives.",
+ "orphaned.delete": "Delete orphaned files from VM store.",
+ "orphaned.scan": "Scan for orphaned files on VM store.",
"runscripts.save": "Save startup scripts.",
"runscripts.view": "View startup scripts.",
"runtimeconfig.save": "Save limits and defaults of a runtime configuration.",
@@ -15,7 +17,7 @@
"templates.save": "Save email templates.",
"users.setlogin": "Enable\/Disable Login.",
"users.setmail": "Enable\/Disable Email Notification.",
- "users.setorglogin": "Enalbe\/Disable Login for Users from certain organisations.",
+ "users.setorglogin": "Enable\/Disable Login for Users from certain organisations.",
"users.setsu": "Set User to superuser.",
"users.view": "View user list."
} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/template-tags.json b/modules-available/dozmod/lang/en/template-tags.json
index af64d000..b741e03d 100644
--- a/modules-available/dozmod/lang/en/template-tags.json
+++ b/modules-available/dozmod/lang/en/template-tags.json
@@ -5,12 +5,15 @@
"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.",
+ "lang_allowStudentDownload": "Allow students to download license-free VMs",
+ "lang_allowStudentDownloadDescription": "If enabled, students can download all VMs that don't have the \"contains software subject to licensing\" checkbox enabled (to be set when uploading a new VM). The author of a VM is responsible for only removing that checkbox for applicable VMs.",
"lang_asteriskRequired": "Fields marked with (*) are required",
"lang_authMethod": "Authentication",
"lang_blockCount": "Block count",
"lang_bwlehrpoolsuite": "bwLehrpool-Suite",
"lang_canLoginOrganization": "Users from this organization can login",
"lang_canLoginUser": "This user can login",
+ "lang_confirmDeleteOrphanedFiles": "Are you sure you want to permanently delete the files listed below?",
"lang_createTime": "Created",
"lang_currentFilter": "Current filter",
"lang_defaultImagePermissionAdmin": "Administrate",
@@ -21,6 +24,7 @@
"lang_defaultLecturePermissions": "For lectures",
"lang_defaultPermissions": "Default permissions",
"lang_delButton": "Permanently delete selected images",
+ "lang_deleteExpiredHeading": "Images marked for deletion",
"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.)",
@@ -33,17 +37,18 @@
"lang_emailNotifications": "E-Mail notifications enabled",
"lang_error": "Error",
"lang_event": "Event",
+ "lang_fileName": "File name",
"lang_fileSize": "File size",
"lang_followingPlaceholdersUnused": "The following placeholders are not being used",
"lang_hasNewer": "newer version exists",
"lang_hash": "Hash",
- "lang_heading": "Images Marked for Deletion",
"lang_hidden": "Hidden",
"lang_host": "Host",
"lang_image": "VM",
+ "lang_lastBoot": "Last started",
"lang_lastEditor": "Edited by",
"lang_lastLogin": "Last login",
- "lang_latestVersion": "latest version",
+ "lang_latestVersion": "Latest version",
"lang_ldapFilterAdd": "Add LDAP filter",
"lang_ldapFilterAttribute": "Attribute",
"lang_ldapFilterDeleteConfirmation": "Do you really want to delete this LDAP filter.",
@@ -62,9 +67,10 @@
"lang_mailDescription": "Fill in the following fields if you want to notify tutors\/professors\/lecturers about expiring VMs and lectures. If you leave one of the required fields blank, the feature will be disabled.",
"lang_mailTemplates": "E-Mail Templates",
"lang_maxImageValidity": "New VM validity (days)",
- "lang_maxLectureVisibility": "Max time lecture end date may lie in the future (days)",
+ "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_maxTransfers": "Max. concurrent transfers per user",
+ "lang_maxVmHddSizeGb": "Max. VM size (GiB, 0 = unlimited)",
"lang_minimized": "Minimized",
"lang_miscOptions": "Misc options",
"lang_modified": "modified",
@@ -75,8 +81,12 @@
"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_numberBoots": "Use count",
"lang_organization": "Organization",
"lang_organizationListHeader": "Set access permissions for organizations",
+ "lang_orphanDeleteButton": "Delete files",
+ "lang_orphanedFilesDesc": "Files listed here could not be matched to any VM from the database. If you're sure that these files aren't used for anything else (e.g. shared VM store), you can delete these files to free up space. If you're not sure, leave them alone or manually examine the situation on the file system.",
+ "lang_orphanedFilesHeading": "Orphaned files on VM store",
"lang_os": "Operating System",
"lang_owner": "Owner",
"lang_passwordCleartextHint": "Please not that explicitly provided credentials will be passed in clear text. You should only use dedicated accounts that don't give access to anything else than the desired network share(s).",
@@ -93,6 +103,7 @@
"lang_runScriptDeleteConfirmation": "Do you want to delete this run-script?",
"lang_runtimeConfig": "Limits and Defaults",
"lang_runtimeConfigLimits": "Limitations",
+ "lang_scanButton": "Scan VM store",
"lang_scriptContent": "Script content",
"lang_scriptExtension": "Script extension",
"lang_scriptExtensionHead": "Extension",
@@ -121,6 +132,7 @@
"lang_sslExplicit": "Explicit SSL (\"STARTTLS\")",
"lang_sslImplicit": "Implicit SSL",
"lang_sslNone": "No SSL",
+ "lang_status": "Status",
"lang_superUser": "Is super user (can edit\/delete all lectures and VMs)",
"lang_system": "System",
"lang_target": "Target",
diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php
index 67b791d1..4a43d881 100644
--- a/modules-available/dozmod/page.inc.php
+++ b/modules-available/dozmod/page.inc.php
@@ -5,7 +5,8 @@ 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', 'networkrules'];
+ private $validSections = ['expiredimages', 'mailconfig', 'templates', 'runtimeconfig', 'users', 'actionlog',
+ 'networkshares', 'ldapfilters', 'runscripts', 'networkrules', 'special'];
private $section;
@@ -15,15 +16,13 @@ class Page_DozMod extends Page
return;
/* different pages for different sections */
$this->section = Request::any('section', false, 'string');
- if ($this->section === 'blockstats') // HACK HACK
- return;
if ($this->section === false) {
foreach ($this->validSections as $this->section) {
if (User::hasPermission($this->section . '.*'))
break;
}
} elseif (!in_array($this->section, $this->validSections)) {
- Util::traceError('Invalid section: ' . $this->section);
+ ErrorHandler::traceError('Invalid section: ' . $this->section);
}
// Check permissions
User::assertPermission($this->section . '.*');
@@ -68,8 +67,8 @@ class Page_DozMod extends Page
/* add sub-menus */
foreach ($this->validSections as $section) {
- if (User::hasPermission($section . '.*')) {
- Dashboard::addSubmenu('?do=dozmod&section=' . $section, Dictionary::translate('submenu_' . $section, true));
+ if ($section !== 'special' && User::hasPermission($section . '.*')) {
+ Dashboard::addSubmenu('?do=dozmod&section=' . $section, Dictionary::translate('submenu_' . $section));
}
}
}
@@ -79,31 +78,10 @@ class Page_DozMod extends Page
/* different pages for different sections */
if ($this->haveSubPage !== false) {
SubPage::doRender();
- return;
- }
-
- if ($this->section === 'blockstats') {
- $this->showBlockStats();
}
}
- private function showBlockStats()
- {
- $res = Database::simpleQuery("SELECT blocksha1, blocksize, Count(*) AS blockcount FROM sat.imageblock"
- . " GROUP BY blocksha1, blocksize HAVING blockcount > 1 ORDER BY blockcount DESC, blocksha1 ASC");
- $data = array('hashes' => array());
- $spaceWasted = 0;
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $row['hash_hex'] = bin2hex($row['blocksha1']);
- $row['blocksize_s'] = Util::readableFileSize($row['blocksize']);
- $data['hashes'][] = $row;
- $spaceWasted += $row['blocksize'] * ($row['blockcount'] - 1);
- }
- $data['spacewasted'] = Util::readableFileSize($spaceWasted);
- Render::addTemplate('blockstats', $data);
- }
-
protected function doAjax()
{
User::load();
@@ -111,43 +89,8 @@ class Page_DozMod extends Page
if ($this->haveSubPage !== false) {
SubPage::doAjax();
- return;
}
- $action = Request::post('action');
-
- if ($action === 'getblockinfo') {
- $this->ajaxGetBlockInfo();
- }
- }
-
- private function ajaxGetBlockInfo()
- {
- $hash = Request::any('hash', false, 'string');
- $size = Request::any('size', false, 'string');
- if ($hash === false || $size === false) {
- die('Missing parameter');
- }
- if (!is_numeric($size) || strlen($hash) !== 40 || !preg_match('/^[a-f0-9]+$/i', $hash)) {
- die('Malformed parameter');
- }
- $res = Database::simpleQuery("SELECT i.displayname, v.createtime, v.filesize, Count(*) AS blockcount FROM sat.imageblock ib"
- . " INNER JOIN sat.imageversion v USING (imageversionid)"
- . " INNER JOIN sat.imagebase i USING (imagebaseid)"
- . " WHERE ib.blocksha1 = :hash AND ib.blocksize = :size"
- . " GROUP BY ib.imageversionid"
- . " ORDER BY i.displayname ASC, v.createtime ASC",
- array('hash' => hex2bin($hash), 'size' => $size), true);
- if ($res === false) {
- die('Database error: ' . Database::lastError());
- }
- $data = array('rows' => array());
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $row['createtime_s'] = date('d.m.Y H:i', $row['createtime']);
- $row['filesize_s'] = Util::readableFileSize($row['filesize']);
- $data['rows'][] = $row;
- }
- die(Render::parse('blockstats-details', $data));
}
}
diff --git a/modules-available/dozmod/pages/actionlog.inc.php b/modules-available/dozmod/pages/actionlog.inc.php
index a014ddf7..182198c2 100644
--- a/modules-available/dozmod/pages/actionlog.inc.php
+++ b/modules-available/dozmod/pages/actionlog.inc.php
@@ -11,7 +11,7 @@ class SubPage
User::assertPermission("actionlog.view");
self::$action = Request::get('action', '', 'string');
if (self::$action !== '' && self::$action !== 'showtarget' && self::$action !== 'showuser') {
- Util::traceError('Invalid action for actionlog: "' . self::$action . '"');
+ ErrorHandler::traceError('Invalid action for actionlog: "' . self::$action . '"');
}
self::$uuid = Request::get('uuid', '', 'string');
}
@@ -20,15 +20,15 @@ class SubPage
{
Render::addTemplate('actionlog-header');
if (self::$action === '') {
- self::generateLog("SELECT al.dateline, al.targetid, al.description,"
- . " img.displayname AS imgname, tu.firstname AS tfirstname, tu.lastname AS tlastname, l.displayname AS lecturename,"
- . " al.userid AS uuserid, usr.firstname AS ufirstname, usr.lastname AS ulastname"
- . " FROM sat.actionlog al"
- . " LEFT JOIN sat.imagebase img ON (img.imagebaseid = targetid)"
- . " LEFT JOIN sat.user usr ON (usr.userid = al.userid)"
- . " LEFT JOIN sat.user tu ON (tu.userid = al.targetid)"
- . " LEFT JOIN sat.lecture l ON (l.lectureid = targetid)"
- . " ORDER BY al.dateline DESC LIMIT 500", array(), true, true);
+ self::generateLog("SELECT al.dateline, al.targetid, al.description,
+ img.displayname AS imgname, tu.firstname AS tfirstname, tu.lastname AS tlastname, l.displayname AS lecturename,
+ al.userid AS uuserid, usr.firstname AS ufirstname, usr.lastname AS ulastname
+ FROM sat.actionlog al
+ LEFT JOIN sat.imagebase img ON (img.imagebaseid = targetid)
+ LEFT JOIN sat.user usr ON (usr.userid = al.userid)
+ LEFT JOIN sat.user tu ON (tu.userid = al.targetid)
+ LEFT JOIN sat.lecture l ON (l.lectureid = targetid)
+ ORDER BY al.dateline DESC LIMIT 500", array(), true, true);
} elseif (self::$action === 'showuser') {
self::listUser();
} else {
@@ -39,11 +39,11 @@ class SubPage
private static function listUser()
{
// Query user
- $user = Database::queryFirst('SELECT userid, firstname, lastname, email, lastlogin,'
- . ' organization.displayname AS orgname FROM sat.user'
- . ' LEFT JOIN sat.organization USING (organizationid)'
- . ' WHERE userid = :uuid'
- . ' LIMIT 1', array('uuid' => self::$uuid));
+ $user = Database::queryFirst('SELECT userid, firstname, lastname, email, lastlogin,
+ organization.displayname AS orgname FROM sat.user
+ LEFT JOIN sat.organization USING (organizationid)
+ WHERE userid = :uuid
+ LIMIT 1', array('uuid' => self::$uuid));
if ($user === false) {
Message::addError('unknown-userid', self::$uuid);
Util::redirect('?do=dozmod&section=actionlog');
@@ -52,14 +52,14 @@ class SubPage
$user['lastlogin_s'] = date('d.m.Y H:i', $user['lastlogin']);
Render::addTemplate('actionlog-user', $user);
// Finally add the actionlog
- self::generateLog("SELECT al.dateline, al.targetid, al.description,"
- . " img.displayname AS imgname, usr.firstname AS tfirstname, usr.lastname AS tlastname, l.displayname AS lecturename"
- . " FROM sat.actionlog al"
- . " LEFT JOIN sat.imagebase img ON (img.imagebaseid = targetid)"
- . " LEFT JOIN sat.user usr ON (usr.userid = targetid)"
- . " LEFT JOIN sat.lecture l ON (l.lectureid = targetid)"
- . " WHERE al.userid = :uuid"
- . " ORDER BY al.dateline DESC LIMIT 500", array('uuid' => self::$uuid), false, true);
+ self::generateLog("SELECT al.dateline, al.targetid, al.description,
+ img.displayname AS imgname, usr.firstname AS tfirstname, usr.lastname AS tlastname, l.displayname AS lecturename
+ FROM sat.actionlog al
+ LEFT JOIN sat.imagebase img ON (img.imagebaseid = targetid)
+ LEFT JOIN sat.user usr ON (usr.userid = targetid)
+ LEFT JOIN sat.lecture l ON (l.lectureid = targetid)
+ WHERE al.userid = :uuid
+ ORDER BY al.dateline DESC LIMIT 500", array('uuid' => self::$uuid), false, true);
}
private static function listTarget()
@@ -72,54 +72,68 @@ class SubPage
}
// Finally add the actionlog
- self::generateLog("SELECT al.dateline, al.userid AS uuserid, al.description,"
- . " usr.firstname AS ufirstname, usr.lastname AS ulastname"
- . " FROM sat.actionlog al"
- . " LEFT JOIN sat.user usr ON (usr.userid = al.userid)"
- . " WHERE al.targetid = :uuid"
- . " ORDER BY al.dateline DESC LIMIT 500", array('uuid' => self::$uuid), true, false);
+ self::generateLog("SELECT al.dateline, al.userid AS uuserid, al.description,
+ usr.firstname AS ufirstname, usr.lastname AS ulastname
+ FROM sat.actionlog al
+ LEFT JOIN sat.user usr ON (usr.userid = al.userid)
+ WHERE al.targetid = :uuid
+ ORDER BY al.dateline DESC LIMIT 500", array('uuid' => self::$uuid), true, false);
}
- private static function addImageHeader()
+ private static function mangleHtml($desc)
{
- $image = Database::queryFirst('SELECT o.userid AS ouserid, o.firstname AS ofirstname, o.lastname AS olastname,'
- . ' u.userid AS uuserid, u.firstname AS ufirstname, u.lastname AS ulastname,'
- . ' img.displayname, img.description, img.createtime, img.updatetime,'
- . ' os.displayname AS osname'
- . ' FROM sat.imagebase img'
- . ' LEFT JOIN sat.user o ON (img.ownerid = o.userid)'
- . ' LEFT JOIN sat.user u ON (img.updaterid = u.userid)'
- . ' LEFT JOIN sat.operatingsystem os ON (img.osid = os.osid)'
- . ' WHERE img.imagebaseid = :uuid'
- . ' LIMIT 1', array('uuid' => self::$uuid));
+ if (substr($desc, 0, 5) === '<html') {
+ $desc = strip_tags($desc,
+ '<strong><b><i><u><ul><li><font><span><p><div><hr><h1><h2><h3><h4><h5><h6>');
+ $desc = preg_replace('/\b(on\w+|style)[\s\r\n]*=[\s\r\n]*(\'.*?\'|".*?"|[^\'"]\S*)/si', '', $desc);
+ } else {
+ $desc = nl2br(htmlspecialchars($desc));
+ }
+ return $desc;
+ }
+
+ private static function addImageHeader(): bool
+ {
+ $image = Database::queryFirst('SELECT o.userid AS ouserid, o.firstname AS ofirstname, o.lastname AS olastname,
+ u.userid AS uuserid, u.firstname AS ufirstname, u.lastname AS ulastname,
+ img.displayname, img.description, img.createtime, img.updatetime,
+ os.displayname AS osname
+ FROM sat.imagebase img
+ LEFT JOIN sat.user o ON (img.ownerid = o.userid)
+ LEFT JOIN sat.user u ON (img.updaterid = u.userid)
+ LEFT JOIN sat.operatingsystem os ON (img.osid = os.osid)
+ WHERE img.imagebaseid = :uuid
+ LIMIT 1', array('uuid' => self::$uuid));
if ($image !== false) {
// Mangle date and render
$image['createtime_s'] = date('d.m.Y H:i', $image['createtime']);
$image['updatetime_s'] = date('d.m.Y H:i', $image['updatetime']);
- $image['descriptionHtml'] = nl2br(htmlspecialchars($image['description']));
+ $image['descriptionHtml'] = self::mangleHtml($image['description']);
Render::addTemplate('actionlog-image', $image);
}
return $image !== false;
}
- private static function addLectureHeader()
+ private static function addLectureHeader(): bool
{
- $lecture = Database::queryFirst('SELECT o.userid AS ouserid, o.firstname AS ofirstname, o.lastname AS olastname,'
- . ' u.userid AS uuserid, u.firstname AS ufirstname, u.lastname AS ulastname,'
- . ' l.displayname, l.description, l.createtime, l.updatetime,'
- . ' img.displayname AS imgname, img.imagebaseid'
- . ' FROM sat.lecture l'
- . ' LEFT JOIN sat.user o ON (l.ownerid = o.userid)'
- . ' LEFT JOIN sat.user u ON (l.updaterid = u.userid)'
- . ' LEFT JOIN sat.imageversion ver ON (ver.imageversionid = l.imageversionid)'
- . ' LEFT JOIN sat.imagebase img ON (img.imagebaseid = ver.imagebaseid)'
- . ' WHERE l.lectureid = :uuid'
- . ' LIMIT 1', array('uuid' => self::$uuid));
+ $lecture = Database::queryFirst('SELECT o.userid AS ouserid, o.firstname AS ofirstname, o.lastname AS olastname,
+ u.userid AS uuserid, u.firstname AS ufirstname, u.lastname AS ulastname,
+ l.displayname, l.description, l.createtime, l.updatetime, l.usecount, l.lastused,
+ img.displayname AS imgname, img.imagebaseid
+ FROM sat.lecture l
+ LEFT JOIN sat.user o ON (l.ownerid = o.userid)
+ LEFT JOIN sat.user u ON (l.updaterid = u.userid)
+ LEFT JOIN sat.imageversion ver ON (ver.imageversionid = l.imageversionid)
+ LEFT JOIN sat.imagebase img ON (img.imagebaseid = ver.imagebaseid)
+ WHERE l.lectureid = :uuid
+ LIMIT 1', array('uuid' => self::$uuid));
if ($lecture !== false) {
// Mangle date and render
$lecture['createtime_s'] = date('d.m.Y H:i', $lecture['createtime']);
$lecture['updatetime_s'] = date('d.m.Y H:i', $lecture['updatetime']);
- $lecture['descriptionHtml'] = nl2br(htmlspecialchars($lecture['description']));
+ $lecture['lastused_s'] = date('d.m.Y H:i', $lecture['lastused']);
+
+ $lecture['descriptionHtml'] = self::mangleHtml($lecture['description']);
Render::addTemplate('actionlog-lecture', $lecture);
}
return $lecture !== false;
@@ -130,7 +144,7 @@ class SubPage
// query action log
$res = Database::simpleQuery($query, $params);
$events = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$row['dateline_s'] = date('d.m.Y H:i', $row['dateline']);
if (isset($row['imgname'])) {
$row['targeturl'] = '?do=dozmod&section=actionlog&action=showtarget&uuid=' . $row['targetid'];
diff --git a/modules-available/dozmod/pages/expiredimages.inc.php b/modules-available/dozmod/pages/expiredimages.inc.php
index 2b5a2274..ab563273 100644
--- a/modules-available/dozmod/pages/expiredimages.inc.php
+++ b/modules-available/dozmod/pages/expiredimages.inc.php
@@ -5,57 +5,24 @@ class SubPage
public static function doPreprocess()
{
- $action = Request::post('action', false, 'string');
- if ($action === 'delimages') {
- if (User::hasPermission("expiredimages.delete")) {
- $result = self::handleDeleteImages();
- if (!empty($result)) {
- Message::addInfo('delete-images', $result);
- }
- Util::redirect('?do=DozMod');
- }
- }
- }
-
- private static function handleDeleteImages()
- {
- $images = Request::post('images', false);
- if (is_array($images)) {
- foreach ($images as $image => $val) {
- if (strtolower($val) !== 'on')
- continue;
- Database::exec("UPDATE sat.imageversion SET deletestate = 'WANT_DELETE'"
- . " WHERE deletestate = 'SHOULD_DELETE' AND imageversionid = :imageversionid", array(
- 'imageversionid' => $image
- ));
- }
- if (!empty($images)) {
- $ret = Download::asStringPost('http://127.0.0.1:9080/do/delete-images', false, 10, $code);
- if ($code == 999) {
- $ret .= "\nConnection to DMSD failed.";
- }
- return $ret;
- }
- }
- return false;
}
- private static function loadExpiredImages()
+ private static function loadExpiredImages(): array
{
- $res = Database::simpleQuery("SELECT b.displayname,"
- . " own.firstname, own.lastname, own.email,"
- . " v.imageversionid, v.createtime, v.filesize, v.deletestate,"
- . " lat.expiretime AS latexptime, lat.deletestate AS latdelstate"
- . " FROM sat.imageversion v"
- . " INNER JOIN sat.imagebase b ON (b.imagebaseid = v.imagebaseid)"
- . " INNER JOIN sat.user own ON (b.ownerid = own.userid)"
- . " LEFT JOIN sat.imageversion lat ON (b.latestversionid = lat.imageversionid)"
- . " WHERE v.deletestate <> 'KEEP'"
- . " ORDER BY b.displayname ASC, v.createtime ASC");
+ $res = Database::simpleQuery("SELECT b.displayname,
+ own.firstname, own.lastname, own.userid,
+ v.imageversionid, v.createtime, v.filesize, v.deletestate,
+ lat.expiretime AS latexptime, lat.deletestate AS latdelstate
+ FROM sat.imageversion v
+ INNER JOIN sat.imagebase b ON (b.imagebaseid = v.imagebaseid)
+ INNER JOIN sat.user own ON (b.ownerid = own.userid)
+ LEFT JOIN sat.imageversion lat ON (b.latestversionid = lat.imageversionid)
+ WHERE v.deletestate <> 'KEEP'
+ ORDER BY b.displayname ASC, v.createtime ASC");
$NOW = time();
$rows = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if ($row['latexptime'] > $NOW && $row['latdelstate'] === 'KEEP') {
$row['hasNewerClass'] = 'glyphicon-ok green';
$row['checked'] = 'checked';
@@ -76,22 +43,81 @@ class SubPage
public static function doRender()
{
$expiredImages = self::loadExpiredImages();
-
- if (empty($expiredImages)) {
- Message::addSuccess('no-expired-images');
- } else {
- Render::addTemplate('images-delete', array('images' => $expiredImages, 'allowedDelete' => User::hasPermission("expiredimages.delete")));
- }
+ $data = ['images' => $expiredImages];
+ Permission::addGlobalTags($data['perm'], null, ['expiredimages.delete', 'orphaned.scan']);
+ Render::addTemplate('images-delete', $data);
}
public static function doAjax()
{
$action = Request::post('action');
if ($action === 'delimages') {
- User::assertPermission("expiredimages.delete");
- die(self::handleDeleteImages());
+ self::handleDeleteImages();
+ } elseif ($action === 'orphaned') {
+ self::handleOrphaned();
+ } else {
+ echo 'Huh?';
+ }
+ }
+
+ private static function handleDeleteImages()
+ {
+ User::assertPermission("expiredimages.delete");
+ $images = Request::post('images', false);
+ $result = false;
+ if (is_array($images)) {
+ foreach ($images as $image => $val) {
+ if (strtolower($val) !== 'on')
+ continue;
+ Database::exec("UPDATE sat.imageversion SET deletestate = 'WANT_DELETE'"
+ . " WHERE deletestate = 'SHOULD_DELETE' AND imageversionid = :imageversionid", array(
+ 'imageversionid' => $image
+ ));
+ }
+ if (!empty($images)) {
+ $result = Download::asStringPost('http://127.0.0.1:9080/do/delete-images', false, 10, $code);
+ if ($code == 999) {
+ $result .= "\nConnection to DMSD failed.";
+ }
+ }
+ }
+ if (!empty($result)) {
+ echo $result;
+ }
+ }
+
+ private static function handleOrphaned()
+ {
+ if (Request::post('delete', 0, 'int') !== 0) {
+ User::assertPermission("orphaned.delete");
+ $action = 'delete';
+ } else {
+ User::assertPermission("orphaned.scan");
+ $action = 'scan';
+ }
+ // Talk to dmsd
+ $result = Download::asStringPost('http://127.0.0.1:9080/do/scan-orphaned-files', ['action' => $action],
+ 10, $code);
+ if ($code == 999) {
+ $result = '<div class="alert alert-warning">'
+ . $result . ' - Connection to DMSD failed.</div>';
+ } else {
+ $json = json_decode($result, true);
+ if (is_array($json)) {
+ $result = [];
+ $showDelete = false;
+ foreach ($json as $k => $v) {
+ $result[] = ['file' => $k, 'status' => $v];
+ if ($v === 'EXISTS') {
+ $showDelete = true;
+ }
+ }
+ $data = ['files' => $result, 'show_delete' => $showDelete];
+ Permission::addGlobalTags($data['perm'], null, ['orphaned.delete']);
+ $result = Render::parse('images-orphaned', $data);
+ }
}
- die('Huh?');
+ echo $result;
}
}
diff --git a/modules-available/dozmod/pages/mailconfig.inc.php b/modules-available/dozmod/pages/mailconfig.inc.php
index 08205f2e..aa03a4d3 100644
--- a/modules-available/dozmod/pages/mailconfig.inc.php
+++ b/modules-available/dozmod/pages/mailconfig.inc.php
@@ -34,7 +34,7 @@ class SubPage
Util::redirect('?do=DozMod&section=mailconfig');
}
- private static function cleanMailArray()
+ private static function cleanMailArray(): array
{
$keys = array('host', 'port', 'ssl', 'senderAddress', 'replyTo', 'username', 'password', 'serverName');
$data = array();
diff --git a/modules-available/dozmod/pages/networkrules.inc.php b/modules-available/dozmod/pages/networkrules.inc.php
index 710e90a9..218b7b06 100644
--- a/modules-available/dozmod/pages/networkrules.inc.php
+++ b/modules-available/dozmod/pages/networkrules.inc.php
@@ -74,7 +74,7 @@ class SubPage
$res = Database::simpleQuery('SELECT ruleid, rulename, ruledata
FROM sat.presetnetworkrule ORDER BY rulename ASC');
$rows = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$rows[] = $row;
}
Render::addTemplate('networkrules', [
diff --git a/modules-available/dozmod/pages/networkshares.inc.php b/modules-available/dozmod/pages/networkshares.inc.php
index 659321b4..852a8c67 100644
--- a/modules-available/dozmod/pages/networkshares.inc.php
+++ b/modules-available/dozmod/pages/networkshares.inc.php
@@ -66,7 +66,7 @@ class SubPage
$res = Database::simpleQuery('SELECT shareid, sharename, sharedata, active
FROM sat.presetnetworkshare ORDER BY sharename ASC');
$rows = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$dec = json_decode($row['sharedata'], true);
if (!is_array($dec)) {
$dec = [];
diff --git a/modules-available/dozmod/pages/runscripts.inc.php b/modules-available/dozmod/pages/runscripts.inc.php
index 9e6062d4..5665ba83 100644
--- a/modules-available/dozmod/pages/runscripts.inc.php
+++ b/modules-available/dozmod/pages/runscripts.inc.php
@@ -98,7 +98,7 @@ class SubPage
FROM sat.presetrunscript
ORDER BY scriptname ASC');
$rows = [];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if ($row['visibility'] == 0) {
$row['visibility'] = 'eye-close';
} elseif ($row['visibility'] == 1) {
@@ -139,7 +139,7 @@ class SubPage
$res = Database::simpleQuery('SELECT o.osid, o.displayname, pxo.osid AS isvalid FROM sat.operatingsystem o
LEFT JOIN sat.presetrunscript_x_operatingsystem pxo ON (o.osid = pxo.osid AND pxo.runscriptid = :runscriptid)
ORDER BY o.displayname ASC', ['runscriptid' => $id]);
- while ($osrow = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $osrow) {
$row['oslist'][] = [
'osid' => $osrow['osid'],
'displayname' => $osrow['displayname'],
diff --git a/modules-available/dozmod/pages/runtimeconfig.inc.php b/modules-available/dozmod/pages/runtimeconfig.inc.php
index ab8500f2..f5790e82 100644
--- a/modules-available/dozmod/pages/runtimeconfig.inc.php
+++ b/modules-available/dozmod/pages/runtimeconfig.inc.php
@@ -31,12 +31,13 @@ class SubPage
'maxTransfers' => array('min' => 1, 'max' => 10),
],
'bool' => [
- 'allowLoginByDefault' => array('default' => false)
+ 'allowLoginByDefault' => array('default' => false),
+ 'allowStudentDownload' => array('default' => false),
],
];
foreach ($params as $type => $list) {
foreach ($list as $field => $limits) {
- $default = isset($limits['default']) ? $limits['default'] : false;
+ $default = $limits['default'] ?? false;
$value = Request::post($field, $default, $type);
if (isset($limits['min']) && $value < $limits['min']) {
$value = $limits['min'];
@@ -52,6 +53,8 @@ class SubPage
if (!in_array($data['serverSideCopy'], ['OFF', 'ON', 'AUTO', 'USER'])) {
$data['serverSideCopy'] = 'OFF';
}
+ // VM size limit
+ $data['vmSizeLimit'] = ceil(max(0, Request::post('vmSizeLimit', '0', 'float') * 1024 * 1024 * 1024));
/* ensure types */
settype($data['defaultLecturePermissions']['edit'], 'boolean');
@@ -61,6 +64,7 @@ class SubPage
settype($data['defaultImagePermissions']['link'], 'boolean');
settype($data['defaultImagePermissions']['download'], 'boolean');
+ // Write to DB - java server app wil reload this periodically
$data = json_encode($data);
Database::exec('INSERT INTO sat.configuration (parameter, value)'
. ' VALUES (:param, :value)'
@@ -81,31 +85,35 @@ class SubPage
$runtimeConf = json_decode($runtimeConf['value'], true);
/* convert some value to corresponding "selected" texts */
- if ($runtimeConf['defaultLecturePermissions']['edit']) {
+ if ($runtimeConf['defaultLecturePermissions']['edit'] ?? false) {
$runtimeConf['defaultLecturePermissions']['edit'] = 'checked';
}
- if ($runtimeConf['defaultLecturePermissions']['admin']) {
+ if ($runtimeConf['defaultLecturePermissions']['admin'] ?? false) {
$runtimeConf['defaultLecturePermissions']['admin'] = 'checked';
}
- if ($runtimeConf['defaultImagePermissions']['edit']) {
+ if ($runtimeConf['defaultImagePermissions']['edit'] ?? false) {
$runtimeConf['defaultImagePermissions']['edit'] = 'checked';
}
- if ($runtimeConf['defaultImagePermissions']['admin']) {
+ if ($runtimeConf['defaultImagePermissions']['admin'] ?? false) {
$runtimeConf['defaultImagePermissions']['admin'] = 'checked';
}
- if ($runtimeConf['defaultImagePermissions']['link']) {
+ if ($runtimeConf['defaultImagePermissions']['link'] ?? false) {
$runtimeConf['defaultImagePermissions']['link'] = 'checked';
}
- if ($runtimeConf['defaultImagePermissions']['download']) {
+ if ($runtimeConf['defaultImagePermissions']['download'] ?? false) {
$runtimeConf['defaultImagePermissions']['download'] = 'checked';
}
- if ($runtimeConf['allowLoginByDefault']) {
+ if ($runtimeConf['allowLoginByDefault'] ?? false) {
$runtimeConf['allowLoginByDefault'] = 'checked';
}
+ if ($runtimeConf['allowStudentDownload'] ?? false) {
+ $runtimeConf['allowStudentDownload'] = 'checked';
+ }
if (isset($runtimeConf['serverSideCopy'])) {
$runtimeConf[$runtimeConf['serverSideCopy'] . '_selected'] = 'selected';
}
+ $runtimeConf['vmSizeLimit'] = ceil(($runtimeConf['vmSizeLimit'] ?? 0) / (1024 * 1024 * 1024));
}
$runtimeConf['allowedSave'] = User::hasPermission("runtimeconfig.save");
Render::addTemplate('runtimeconfig', $runtimeConf);
diff --git a/modules-available/dozmod/pages/special.inc.php b/modules-available/dozmod/pages/special.inc.php
new file mode 100644
index 00000000..d6ac53d6
--- /dev/null
+++ b/modules-available/dozmod/pages/special.inc.php
@@ -0,0 +1,85 @@
+<?php
+
+class SubPage
+{
+
+
+ public static function doPreprocess()
+ {
+
+ }
+
+ public static function doRender()
+ {
+ $res = Database::simpleQuery("SELECT blocksha1, blocksize, Count(*) AS blockcount FROM sat.imageblock"
+ . " GROUP BY blocksha1, blocksize HAVING blockcount > 1 ORDER BY blockcount DESC, blocksha1 ASC");
+ $data = array('hashes' => array());
+ $spaceWasted = 0;
+ foreach ($res as $row) {
+ $row['hash_hex'] = bin2hex($row['blocksha1']);
+ $row['blocksize_s'] = Util::readableFileSize($row['blocksize']);
+ $data['hashes'][] = $row;
+ $spaceWasted += $row['blocksize'] * ($row['blockcount'] - 1);
+ }
+ $data['spacewasted'] = Util::readableFileSize($spaceWasted);
+ Render::addTemplate('blockstats', $data);
+ }
+
+ public static function doAjax()
+ {
+ $action = Request::any('action');
+
+ if ($action === 'getblockinfo') {
+ self::ajaxGetBlockInfo();
+ } elseif ($action === 'dmsd-status') {
+ self::ajaxDmsdStatus();
+ }
+ }
+
+ private static function ajaxGetBlockInfo()
+ {
+ $hash = Request::any('hash', false, 'string');
+ $size = Request::any('size', false, 'string');
+ if ($hash === false || $size === false) {
+ die('Missing parameter');
+ }
+ if (!is_numeric($size) || strlen($hash) !== 40 || !preg_match('/^[a-f0-9]+$/i', $hash)) {
+ die('Malformed parameter');
+ }
+ $res = Database::simpleQuery("SELECT i.displayname, v.createtime, v.filesize, Count(*) AS blockcount FROM sat.imageblock ib"
+ . " INNER JOIN sat.imageversion v USING (imageversionid)"
+ . " INNER JOIN sat.imagebase i USING (imagebaseid)"
+ . " WHERE ib.blocksha1 = :hash AND ib.blocksize = :size"
+ . " GROUP BY ib.imageversionid"
+ . " ORDER BY i.displayname ASC, v.createtime ASC",
+ array('hash' => hex2bin($hash), 'size' => $size), true);
+ if ($res === false) {
+ die('Database error: ' . Database::lastError());
+ }
+ $data = array('rows' => array());
+ foreach ($res as $row) {
+ $row['createtime_s'] = date('d.m.Y H:i', $row['createtime']);
+ $row['filesize_s'] = Util::readableFileSize($row['filesize']);
+ $data['rows'][] = $row;
+ }
+ die(Render::parse('blockstats-details', $data));
+ }
+
+ private static function ajaxDmsdStatus()
+ {
+ $ret = Download::asStringPost('http://127.0.0.1:9080/status/fileserver', false, 2, $code);
+ $args = array();
+ if ($code != 200) {
+ $args['error'] = true;
+ } else {
+ $data = @json_decode($ret, true);
+ if (is_array($data)) {
+ $args['uploads'] = $data['activeUploads'];
+ $args['downloads'] = $data['activeDownloads'];
+ }
+ }
+ Header('Content-Type: application/json');
+ echo json_encode($args);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/dozmod/pages/templates.inc.php b/modules-available/dozmod/pages/templates.inc.php
index b857115f..b916e14c 100644
--- a/modules-available/dozmod/pages/templates.inc.php
+++ b/modules-available/dozmod/pages/templates.inc.php
@@ -69,7 +69,7 @@ class SubPage
]);
}
- private static function forcmp($string)
+ private static function forcmp(string $string): string
{
return trim(str_replace("\r\n", "\n", $string));
}
diff --git a/modules-available/dozmod/pages/users.inc.php b/modules-available/dozmod/pages/users.inc.php
index 50f0f763..fe00a71b 100644
--- a/modules-available/dozmod/pages/users.inc.php
+++ b/modules-available/dozmod/pages/users.inc.php
@@ -42,7 +42,7 @@ class SubPage
. ' LEFT JOIN sat.organization USING (organizationid)'
. ' ORDER BY lastname ASC, firstname ASC');
$rows = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
settype($row['lastlogin'], 'int');
$row['canlogin'] = self::checked($row['canlogin']);
$row['issuperuser'] = self::checked($row['issuperuser']);
@@ -63,14 +63,14 @@ class SubPage
$res = Database::simpleQuery('SELECT organizationid, displayname, canlogin FROM sat.organization'
. ' ORDER BY displayname ASC');
$rows = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $row['canlogin'] = self::checked($row['canlogin']);
+ foreach ($res as $row) {
+ $row['canlogin'] = self::checked((bool)$row['canlogin']);
$rows[] = $row;
}
Render::addTemplate('orglist', array('organizations' => $rows));
}
- private static function checked($val)
+ private static function checked(bool $val): string
{
if ($val)
return 'checked="checked"';
diff --git a/modules-available/dozmod/permissions/permissions.json b/modules-available/dozmod/permissions/permissions.json
index c8958089..12d0acd3 100644
--- a/modules-available/dozmod/permissions/permissions.json
+++ b/modules-available/dozmod/permissions/permissions.json
@@ -2,6 +2,12 @@
"expiredimages.delete": {
"location-aware": false
},
+ "orphaned.scan": {
+ "location-aware": false
+ },
+ "orphaned.delete": {
+ "location-aware": false
+ },
"actionlog.view": {
"location-aware": false
},
diff --git a/modules-available/dozmod/templates/actionlog-lecture.html b/modules-available/dozmod/templates/actionlog-lecture.html
index 4fb2b4d0..6b8701b7 100644
--- a/modules-available/dozmod/templates/actionlog-lecture.html
+++ b/modules-available/dozmod/templates/actionlog-lecture.html
@@ -25,6 +25,14 @@
<td><a href="?do=dozmod&amp;section=actionlog&amp;action=showtarget&amp;uuid={{imagebaseid}}">{{imgname}}</a></td>
</tr>
<tr>
+ <th class="text-nowrap">{{lang_lastBoot}}</th>
+ <td>{{lastused_s}}</td>
+ </tr>
+ <tr>
+ <th class="text-nowrap">{{lang_numberBoots}}</th>
+ <td>{{usecount}}</td>
+ </tr>
+ <tr>
<td colspan="2">{{{descriptionHtml}}}</td>
</tr>
</table> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/blockstats.html b/modules-available/dozmod/templates/blockstats.html
index cba3b476..b71d219c 100644
--- a/modules-available/dozmod/templates/blockstats.html
+++ b/modules-available/dozmod/templates/blockstats.html
@@ -30,8 +30,9 @@
<script type="application/javascript"><!--
function slxLoadBlocks(hash, size) {
$('#block-details .modal-header').text(hash + '/' + size);
- $('#block-details .modal-body').html('<div class="slx-rotation"><span class="glyphicon glyphicon-refresh"></span></div>');
+ $('#block-details .modal-body')
+ .html('<div class="slx-rotation"><span class="glyphicon glyphicon-refresh"></span></div>')
+ .load('?do=dozmod&section=special', { token: TOKEN, action: 'getblockinfo', hash: hash, size: size });
$('#block-details').modal('show');
- $('#block-details .modal-body').load('?do=dozmod&section=blockstats', { token: TOKEN, action: 'getblockinfo', hash: hash, size: size });
}
//--></script> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/images-delete.html b/modules-available/dozmod/templates/images-delete.html
index 78690426..5bbebdc3 100644
--- a/modules-available/dozmod/templates/images-delete.html
+++ b/modules-available/dozmod/templates/images-delete.html
@@ -2,96 +2,142 @@
<div class="panel panel-default">
<div class="panel-heading">
- {{lang_heading}}
+ {{lang_deleteExpiredHeading}}
</div>
<div class="panel-body">
<p>{{lang_description_delete_images}}</p>
<div class="table-responsive">
- <form id="delform" method="post" action="?do=DozMod" onsubmit="return slxPostdel()">
+ <form id="delform" method="post" action="?do=dozmod">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="section" value="expiredimages">
<input type="hidden" name="action" value="delimages">
<table class="table table-stripped table-condensed stupidtable">
<thead>
- <tr>
- <th data-sort="string">{{lang_image}}</th>
- <th data-sort="int">{{lang_version}}</th>
- <th data-sort="string">{{lang_owner}}</th>
- <th><span class="glyphicon glyphicon-upload" title="{{lang_hasNewer}}"></span></th>
- <th data-sort="int">{{lang_size}}</th>
- <th>
- <div class="checkbox">
- <input id="del-all" type="checkbox" onclick="slxChangeAll()">
- <label for="del-all"></label>
- <span class="glyphicon glyphicon-trash" title="{{lang_delete}}"></span>
- </div>
- </th>
- </tr>
+ <tr>
+ <th data-sort="string">{{lang_image}}</th>
+ <th data-sort="int">{{lang_version}}</th>
+ <th data-sort="string">{{lang_owner}}</th>
+ <th><span class="glyphicon glyphicon-upload" title="{{lang_hasNewer}}"></span></th>
+ <th data-sort="int">{{lang_size}}</th>
+ <th>
+ <div class="checkbox">
+ <input id="del-all" type="checkbox">
+ <label for="del-all"></label>
+ <span class="glyphicon glyphicon-trash" title="{{lang_delete}}"></span>
+ </div>
+ </th>
+ </tr>
</thead>
<tbody>
- {{#images}}
- <tr>
- <td class="text-left text-nowrap {{name_extra_class}}">{{displayname}}<br><span class="small">{{imageversionid}}</span></td>
- <td class="text-left text-nowrap" data-sort-value="{{createtime}}" >{{version}}</td>
- <td class="text-left text-nowrap"><a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a></td>
- <td class="text-left text-nowrap"><span class="glyphicon {{hasNewerClass}}"></span></td>
- <td class="text-left text-nowrap" data-sort-value="{{rawfilesize}}">{{filesize}}</td>
- <td>
- <div class="checkbox">
- <input type="checkbox" id="images[{{imageversionid}}]" class="del-check" name="images[{{imageversionid}}]" {{checked}}>
- <label for="images[{{imageversionid}}]"></label>
- </div>
- </td>
- </tr>
- {{/images}}
+ {{#images}}
+ <tr>
+ <td class="text-left text-nowrap {{name_extra_class}}">{{displayname}}<br><span class="small">{{imageversionid}}</span>
+ </td>
+ <td class="text-left text-nowrap" data-sort-value="{{createtime}}">{{version}}</td>
+ <td class="text-left text-nowrap"><a href="?do=dozmod&amp;section=actionlog&amp;action=showuser&amp;uuid={{userid}}">{{lastname}}, {{firstname}}</a></td>
+ <td class="text-left text-nowrap"><span class="glyphicon {{hasNewerClass}}"></span></td>
+ <td class="text-left text-nowrap" data-sort-value="{{rawfilesize}}">{{filesize}}</td>
+ <td>
+ <div class="checkbox">
+ <input type="checkbox" id="images[{{imageversionid}}]" class="del-check"
+ name="images[{{imageversionid}}]" {{checked}}>
+ <label for="images[{{imageversionid}}]"></label>
+ </div>
+ </td>
+ </tr>
+ {{/images}}
</tbody>
</table>
- <button {{^allowedDelete}}disabled{{/allowedDelete}} style="margin-left: 20px" id="delbtn" class="btn btn-danger pull-right" type="submit" name="button" value="save"><span class="glyphicon glyphicon-trash"></span> {{lang_delButton}}</button>
+ <button {{perm.expiredimages.delete.disabled}} id="expired-delete"
+ class="btn btn-danger pull-right" type="submit">
+ <span class="glyphicon glyphicon-trash"></span>
+ {{lang_delButton}}
+ </button>
</form>
<pre style="display:none" id="deloutput"></pre>
</div>
</div>
</div>
-<script type="text/javascript"><!--
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_orphanedFilesHeading}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_orphanedFilesDesc}}</p>
+ <div id="orphan-ajax">
+ <button {{perm.orphaned.scan.disabled}} id="orphan-scan"
+ class="btn btn-default pull-right" type="button">
+ <span class="glyphicon glyphicon-search"></span>
+ {{lang_scanButton}}
+ </button>
+ </div>
+ </div>
+</div>
-function slxPostdel() {
- var f = $('#delform');
- $('#delbtn').prop('disabled', true);
- $.post('?do=DozMod', f.serialize()).done(function (data) {
- $('#deloutput').text(data).css('display', '');
- }).fail(function () {
- $('#deloutput').text('ERROR').css('display', '');
- });
- return false;
-}
+<div class="hidden" id="confirm-orphan-delete">
+ {{lang_confirmDeleteOrphanedFiles}}
+</div>
-function slxChangeAll()
-{
- if ($('#del-all').is(':checked')) {
- $('.del-check').prop('checked', true);
- } else {
- $('.del-check').prop('checked', false);
- }
-}
+<script type="text/javascript"><!--
-function slxChangeSingle()
-{
- var ons = 0;
- var offs = 0;
- $('.del-check').each(function(idx, elem) {
- if (elem.checked) {
- ons++;
+document.addEventListener("DOMContentLoaded", function () {
+ $('#del-all').click(function () {
+ if ($(this).is(':checked')) {
+ $('.del-check').prop('checked', true);
} else {
- offs++;
+ $('.del-check').prop('checked', false);
}
});
- $('#del-all').prop('checked', offs === 0).prop('indeterminate', ons > 0 && offs > 0);
-}
-
-document.addEventListener("DOMContentLoaded", function() {
+ var slxChangeSingle = function () {
+ var ons = 0;
+ var offs = 0;
+ $('.del-check').each(function (idx, elem) {
+ if (elem.checked) {
+ ons++;
+ } else {
+ offs++;
+ }
+ });
+ // TODO indeterminate doesn't work with styled checkbox
+ $('#del-all').prop('checked', offs === 0).prop('indeterminate', ons > 0 && offs > 0);
+ };
$('.del-check').click(slxChangeSingle);
slxChangeSingle();
+ // Handler for delete expired images button
+ var delform = $('#delform');
+ delform.submit(function (e) {
+ e.preventDefault();
+ $('#expired-delete').prop('disabled', true);
+ $.post('?do=dozmod', delform.serialize()).done(function (data) {
+ $('#deloutput').text(data).css('display', '');
+ }).fail(function () {
+ $('#deloutput').text('ERROR').css('display', '');
+ });
+ });
+ // Handler for scanning/deleting orphaned files
+ var slxOrphans = function (del) {
+ $.post('?do=dozmod', {
+ token: TOKEN, section: 'expiredimages', action: 'orphaned', delete: del
+ }).done(function (data) {
+ $('#orphan-ajax').html(data).find('#orphan-delete')
+ .click(slxModalConfirmHandler)
+ .click(function () {
+ slxOrphans(1);
+ });
+ }).fail(function () {
+ $('#orphan-ajax').text('ERROR');
+ });
+ };
+ // Handler for scanning for orphaned files
+ $('#orphan-scan').click(function () {
+ $('#orphan-scan').prop('disabled', true);
+ slxOrphans(0);
+ });
+ window.addEventListener('unload', function () {
+ // Do something here that forces browsers to throw away any JS state on nav.back
+ $('input[type=checkbox]').prop('checked', false);
+ });
}, false);
//--> </script>
diff --git a/modules-available/dozmod/templates/images-orphaned.html b/modules-available/dozmod/templates/images-orphaned.html
new file mode 100644
index 00000000..d1b74a42
--- /dev/null
+++ b/modules-available/dozmod/templates/images-orphaned.html
@@ -0,0 +1,25 @@
+<table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_fileName}}</th>
+ <th>{{lang_status}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#files}}
+ <tr>
+ <td>{{file}}</td>
+ <td>{{status}}</td>
+ </tr>
+ {{/files}}
+ </tbody>
+</table>
+
+{{#show_delete}}
+<button {{perm.orphaned.delete.disabled}} id="orphan-delete"
+ class="btn btn-danger pull-right" type="button"
+ data-confirm="#confirm-orphan-delete">
+ <span class="glyphicon glyphicon-search"></span>
+ {{lang_orphanDeleteButton}}
+</button>
+{{/show_delete}} \ No newline at end of file
diff --git a/modules-available/dozmod/templates/runtimeconfig.html b/modules-available/dozmod/templates/runtimeconfig.html
index 44fb4106..1540042d 100644
--- a/modules-available/dozmod/templates/runtimeconfig.html
+++ b/modules-available/dozmod/templates/runtimeconfig.html
@@ -19,7 +19,7 @@
<div class="checkbox">
<input type="checkbox" name="defaultLecturePermissions[admin]" value="1" {{defaultLecturePermissions.admin}} id ="lecture_admin" class="form-control">
- <label for"lecture_admin">
+ <label for="lecture_admin">
{{lang_lecturePermissionAdmin}}
</label>
</div>
@@ -94,6 +94,12 @@
<input name="maxTransfers" class="form-control" type="number" value="{{maxTransfers}}" min="1" max="10" pattern="^\d+$">
</td>
</tr>
+ <tr class="input-group">
+ <td class="input-group-addon">{{lang_maxVmHddSizeGb}}</td>
+ <td>
+ <input name="vmSizeLimit" class="form-control" type="number" value="{{vmSizeLimit}}" min="0" max="65535" pattern="\d+">
+ </td>
+ </tr>
</table>
</fieldset>
<br>
@@ -107,6 +113,14 @@
<p><i>{{lang_allowLoginDescription}}</i></p>
</div>
<br>
+ <div class="checkbox">
+ <input type="checkbox" name="allowStudentDownload" value="1" {{allowStudentDownload}} id="allowStudentDownload" class="form-control">
+ <label for="allowStudentDownload">
+ {{lang_allowStudentDownload}}
+ </label>
+ <p><i>{{lang_allowStudentDownloadDescription}}</i></p>
+ </div>
+ <br>
<div>
<label for="serverSideCopy">
{{lang_serverSideCopy}}