\ No newline at end of file
diff --git a/modules-available/backup/config.json b/modules-available/backup/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/backup/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/backup/lang/de/module.json b/modules-available/backup/lang/de/module.json
new file mode 100644
index 00000000..a0dbdf27
--- /dev/null
+++ b/modules-available/backup/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Sichern/Wiederherstellen",
+ "page_title": "Sichern und wiederherstellen"
+}
diff --git a/modules-available/backup/lang/de/templates/_page.json b/modules-available/backup/lang/de/templates/_page.json
new file mode 100644
index 00000000..5e3efea2
--- /dev/null
+++ b/modules-available/backup/lang/de/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Sichern",
+ "lang_backupDescription": "Hier k\u00f6nnen Sie die Konfiguration des Satellitenservers sichern. Dies beinhaltet die Datenbank \u00fcber alle Virtuellen Maschinen, Veranstaltungen, Authentifizerungsmodule, Passw\u00f6rter, Proxies, den konfigurierten VM-Store sowie weitere Konfiguration des MiniLinux.\r\nDie Festplattenabbilder der Virtuellen Maschinen auf dem VM-Store werden hierbei nicht gesichert. Eventuelle Backups des Stores m\u00fcssen separat durchgef\u00fchrt werden.",
+ "lang_backupRestore": "Sichern und Wiederherstellen",
+ "lang_browseForFile": "Durchsuchen",
+ "lang_download": "Herunterladen",
+ "lang_dozmodExplanation": "Die Datenbank des Dozentenmoduls wiederherstellen. Dazu geh\u00f6ren die Metadaten der Virtuellen Maschinen, die Veranstaltungen, etc. Bitte beachten Sie, dass hierzu auf dem konfigurierten VM-Store die passenden VM-Abbilder vorliegen m\u00fcssen, da diese extern gespeichert werden. Wenn sich der Servername oder die -adresse ge\u00e4ndert haben stellen Sie bitte sicher, dass die relativen Pfade innerhalb des Netzlaufwerks gleich geblieben sind. Ansonsten werden die wiederhergestellten VMs nicht verwendbar sein.",
+ "lang_restore": "Hochladen",
+ "lang_restoreDescription": "Hier k\u00f6nnen Sie ein Backup der Konfiguration wieder einspielen. Bitte beachten Sie, dass der Server dabei neu gestartet wird, daher sollten Sie dies m\u00f6glichst durchf\u00fchren, wenn das System nicht genutzt wird, und keine Dozenten Veranstaltungen oder Virtuelle Labore erstellen oder hoch-\/herunterladen. Bitte beachten Sie, dass dabei auch das urspr\u00fcngliche Passwort der Weboberfl\u00e4che wiederhergestellt wird.",
+ "lang_restoreDozmodConfig": "Dozentenmodul-Konfiguration wiederherstellen",
+ "lang_restoreSystemConfig": "Systemkonfiguration wiederherstellen",
+ "lang_selectFile": "Bitte w\u00e4hlen Sie ein Backup-Archiv",
+ "lang_systemExplanation": "Die Grundkonfiguration des Satelliten wiederherstellen: Authentifizierungmethode, Passw\u00f6rter, Proxies, VM-Storage, etc.\r\nACHTUNG: Wenn Sie ein Backup von vor WS15\/16 einspielen (Backup-Format vor Version 10), wird die Systemkonfiguration in jedem Fall wiederhergestellt, auch wenn Sie diesen Haken nicht setzen."
+}
\ No newline at end of file
diff --git a/modules-available/backup/lang/de/templates/restore.json b/modules-available/backup/lang/de/templates/restore.json
new file mode 100644
index 00000000..6b3a7cdd
--- /dev/null
+++ b/modules-available/backup/lang/de/templates/restore.json
@@ -0,0 +1,8 @@
+{
+ "lang_backup": "Sichern...",
+ "lang_reboot": "Systemneustart",
+ "lang_restoreConfig": "Konfiguration wiederherstellen",
+ "lang_restoreFailed": "Wiederherstellung der Konfiguration fehlgeschlagen.",
+ "lang_stopping": "Stoppe",
+ "lang_waitReboot": "Warte auf Reboot."
+}
\ No newline at end of file
diff --git a/modules-available/backup/lang/en/module.json b/modules-available/backup/lang/en/module.json
new file mode 100644
index 00000000..c9379ffd
--- /dev/null
+++ b/modules-available/backup/lang/en/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Here you can backup the complete configuration of this satellite server.",
+ "lang_backupRestore": "Backup and restore",
+ "lang_download": "Download",
+ "lang_file": "File",
+ "lang_reboot": "System reboot",
+ "lang_restore": "Upload",
+ "lang_restoreConfig": "Restore config",
+ "lang_restoreDescription": "Here you can restore a configuration backup. Please note that this will reboot the server, so it is advised to do this while nobody is using the system. Please note that this will also restore the password for the web interface that was active when the configuration backup was created.",
+ "lang_restoreFailed": "Restoring configuration failed.",
+ "lang_stopping": "Stopping",
+ "module_name": "Backup"
+}
\ No newline at end of file
diff --git a/modules-available/backup/lang/en/templates/_page.json b/modules-available/backup/lang/en/templates/_page.json
new file mode 100644
index 00000000..799c6168
--- /dev/null
+++ b/modules-available/backup/lang/en/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Here you can backup the complete configuration of this satellite server. This includes lecture and virtual machine meta data. The HDD images of the virtual machines on the vm store are not included in this backup, because of their size. If desired, the store needs to be backed up manually.",
+ "lang_backupRestore": "Backup and restore",
+ "lang_browseForFile": "Browse",
+ "lang_download": "Download",
+ "lang_dozmodExplanation": "This restores all the virtual machine and lecture meta data created using the \"Dozentenmodul\". Please make sure the VM-storage configured still contains all the VM-Images associated with the virtual machines. If the location of the storage changed, make sure the relative pathes on the share are still the same, otherwise the virtual machines won't be usable.",
+ "lang_restore": "Upload",
+ "lang_restoreDescription": "Here you can restore a configuration backup. Please note that this will reboot the server, so it is advised to do this while nobody is using the system. Please note that this will also restore the password for the web interface that was active when the configuration backup was created.",
+ "lang_restoreDozmodConfig": "Restore Dozentenmodul config",
+ "lang_restoreSystemConfig": "Restore system config",
+ "lang_selectFile": "Please select a backup archive",
+ "lang_systemExplanation": "Restore basic configuration like authentication method, passwords, vm storage location, proxy config, etc. WARNING: If you restore a configuration backup that was made before WS15\/16 (backup format version <10), the system configuration will be restored regardless of this check mark."
+}
\ No newline at end of file
diff --git a/modules-available/backup/lang/en/templates/restore.json b/modules-available/backup/lang/en/templates/restore.json
new file mode 100644
index 00000000..5a5f6f64
--- /dev/null
+++ b/modules-available/backup/lang/en/templates/restore.json
@@ -0,0 +1,8 @@
+{
+ "lang_backup": "Backup...",
+ "lang_reboot": "System reboot",
+ "lang_restoreConfig": "Restore config",
+ "lang_restoreFailed": "Restoring configuration failed.",
+ "lang_stopping": "Stopping",
+ "lang_waitReboot": "Waiting for reboot."
+}
\ No newline at end of file
diff --git a/modules-available/backup/lang/pt/module.json b/modules-available/backup/lang/pt/module.json
new file mode 100644
index 00000000..5077cbfa
--- /dev/null
+++ b/modules-available/backup/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_backup": "Backup",
+ "lang_backupDescription": "Aqui voc\u00ea pode fazer um backup completo da configura\u00e7\u00e3o deste server.",
+ "lang_backupRestore": "Backup e Recupera\u00e7\u00e3o",
+ "lang_download": "Baixar",
+ "lang_file": "Arquivo",
+ "lang_reboot": "Reinicializar Sistema",
+ "lang_restore": "Carregar",
+ "lang_restoreConfig": "Recuperar Configura\u00e7\u00e3o",
+ "lang_restoreDescription": "Aqui voc\u00ea pode recuperar um backup de configura\u00e7\u00e3o. Por favor note que isso ir\u00e1 reinicializar o servidor, portanto \u00e9 recomendado faz\u00ea-lo quando ningu\u00e9m estiver utilizando o sistema. Por favor note que isso tamb\u00e9m ir\u00e1 recuperar a senha da interface web que estava ativa quando a configura\u00e7\u00e3o de backup foi criada.",
+ "lang_restoreFailed": "Recupera\u00e7\u00e3o da configura\u00e7\u00e3o falhou",
+ "lang_stopping": "Parando",
+ "module_name": "Backup"
+}
\ No newline at end of file
diff --git a/modules-available/backup/page.inc.php b/modules-available/backup/page.inc.php
new file mode 100644
index 00000000..4095f875
--- /dev/null
+++ b/modules-available/backup/page.inc.php
@@ -0,0 +1,164 @@
+action = Request::post('action');
+ if ($this->action === 'backup') {
+ $this->backup();
+ } elseif ($this->action === 'restore') {
+ $this->restore();
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ if ($this->action === 'restore') {
+ Render::addTemplate('restore', $this->templateData);
+ } else {
+ Render::addScriptBottom('fileselect');
+ Render::addTemplate('_page');
+ }
+ }
+
+ private function backup()
+ {
+ $task = Taskmanager::submit('BackupRestore', array('mode' => 'backup'));
+ if (!isset($task['id'])) {
+ Message::addError('backup-failed');
+ Util::redirect('?do=Backup');
+ }
+ $task = Taskmanager::waitComplete($task, 30000);
+ if (!Taskmanager::isFinished($task) || !isset($task['data']['backupFile'])) {
+ Taskmanager::addErrorMessage($task);
+ Util::redirect('?do=Backup');
+ }
+ while ((@ob_get_level()) > 0)
+ @ob_end_clean();
+ $fh = @fopen($task['data']['backupFile'], 'rb');
+ if ($fh === false) {
+ Message::addError('error-read', $task['data']['backupFile']);
+ Util::redirect('?do=Backup');
+ }
+ Header('Content-Type: application/octet-stream', true);
+ Header('Content-Disposition: attachment; filename=' . 'satellite-backup_v' . Database::getExpectedSchemaVersion() . '_' . date('Y.m.d-H.i.s') . '.tgz');
+ Header('Content-Length: ' . @filesize($task['data']['backupFile']));
+ while (!feof($fh)) {
+ $data = fread($fh, 16000);
+ if ($data === false) {
+ EventLog::failure('Could not stream system backup to browser - backup corrupted!');
+ die("\r\n\nDOWNLOAD INTERRUPTED!\n");
+ }
+ echo $data;
+ @ob_flush();
+ @flush();
+ }
+ @fclose($fh);
+ @unlink($task['data']['backupFile']);
+ die();
+ }
+
+ private function restore()
+ {
+ if (!isset($_FILES['backupfile'])) {
+ Message::addError('missing-file');
+ Util::redirect('?do=Backup');
+ }
+ if ($_FILES['backupfile']['error'] != UPLOAD_ERR_OK) {
+ Message::addError('upload-failed', Util::uploadErrorString($_FILES['backupfile']['error']));
+ Util::redirect('?do=Backup');
+ }
+ $tempfile = '/tmp/bwlp-' . mt_rand(1, 100000) . '-' . crc32($_SERVER['REMOTE_ADDR']) . '.tgz';
+ if (!move_uploaded_file($_FILES['backupfile']['tmp_name'], $tempfile)) {
+ Message::addError('error-write', $tempfile);
+ Util::redirect('?do=Backup');
+ }
+ // Got uploaded file, now shut down all the daemons etc.
+ $parent = Trigger::stopDaemons(null, $this->templateData);
+ // Unmount store
+ $task = Taskmanager::submit('MountVmStore', array(
+ 'address' => 'null',
+ 'type' => 'images',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['mountid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ EventLog::info('Creating backup, v' . Database::getExpectedSchemaVersion() . ' on ' . Property::getServerIp());
+ // Finally run restore
+ $task = Taskmanager::submit('BackupRestore', array(
+ 'mode' => 'restore',
+ 'backupFile' => $tempfile,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false,
+ 'restoreOpenslx' => Request::post('restore_openslx', 'off') === 'on',
+ 'restoreDozmod' => Request::post('restore_dozmod', 'off') === 'on',
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['restoreid'] = $task['id'];
+ $parent = $task['id'];
+ TaskmanagerCallback::addCallback($task, 'dbRestored');
+ }
+ // Wait a bit
+ $task = Taskmanager::submit('SleepTask', array(
+ 'seconds' => 3,
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id']))
+ $parent = $task['id'];
+ // Reboot
+ $task = Taskmanager::submit('Reboot', array(
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ // Leave this comment so the i18n scanner finds it:
+ // Message::addSuccess('restore-done');
+ if (isset($task['id']))
+ $this->templateData['rebootid'] = $task['id'];
+ }
+ private function stopDaemons($parent)
+ {
+ $task = Taskmanager::submit('SyncdaemonLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['syncid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('DozmodLauncher', array(
+ 'operation' => 'stop',
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['dmsdid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ $task = Taskmanager::submit('LdadpLauncher', array(
+ 'ids' => array(),
+ 'parentTask' => $parent,
+ 'failOnParentFail' => false
+ ));
+ if (isset($task['id'])) {
+ $this->templateData['ldadpid'] = $task['id'];
+ $parent = $task['id'];
+ }
+ return $parent;
+ }
+}
diff --git a/modules-available/backup/templates/_page.html b/modules-available/backup/templates/_page.html
new file mode 100644
index 00000000..47b5a174
--- /dev/null
+++ b/modules-available/backup/templates/_page.html
@@ -0,0 +1,41 @@
+
{{lang_backupRestore}}
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/backup/templates/restore.html b/modules-available/backup/templates/restore.html
new file mode 100644
index 00000000..4494a993
--- /dev/null
+++ b/modules-available/backup/templates/restore.html
@@ -0,0 +1,62 @@
+
+
{{lang_backup}}
+
+
+
{{lang_stopping}} syncdaemon
+
{{lang_stopping}} dmsd
+
{{lang_stopping}} ldadp
+
{{lang_stopping}} vmstore
+
{{lang_restoreConfig}}
+
{{lang_reboot}}
+
+
+ {{lang_restoreFailed}}
+
+
+
+
+
+
+
+
diff --git a/modules-available/baseconfig/config.json b/modules-available/baseconfig/config.json
new file mode 100644
index 00000000..b72e9c23
--- /dev/null
+++ b/modules-available/baseconfig/config.json
@@ -0,0 +1,4 @@
+{
+ "category": "main.settings",
+ "enabled": true
+}
diff --git a/modules-available/baseconfig/lang/de/cat_setting.json b/modules-available/baseconfig/lang/de/cat_setting.json
new file mode 100644
index 00000000..b8cb6935
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/cat_setting.json
@@ -0,0 +1,9 @@
+{
+ "cat_1": "Inaktivit\u00e4t und Abschaltung",
+ "cat_2": "Internetzugriff",
+ "cat_3": "Zeitsynchronisation",
+ "cat_4": "Grundsystem",
+ "cat_5": "Gemeinsames Netzlaufwerk",
+ "cat_6": "Unkategorisiert",
+ "cat_7": "vmchooser"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/setting.json b/modules-available/baseconfig/lang/de/setting.json
new file mode 100644
index 00000000..af1007be
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/setting.json
@@ -0,0 +1,23 @@
+{
+ "SLX_ADDONS": "Zu ladende Addons. Zur Zeit steht nur *vmware* zur Verf\u00fcgung.",
+ "SLX_BENCHMARK_VM": "Tragen Sie hier den exakten Namen einer Veranstaltung, wie sie im *vmchooser* auftaucht ein, um diese VM nach dem Booten automatisch zu starten. Dies ist n\u00fctzlich f\u00fcr Bootzeitmessungen. Feld leer lassen, um Funktion zu deaktivieren.",
+ "SLX_BIOS_CLOCK": "Legt fest, ob und wie die interne Uhr des Rechners im Bezug auf die Systemzeit des \/MiniLinux\/ gesetzt werden soll.\r\n*off* = Die interne Uhr des Rechners wird nicht ver\u00e4ndert.\r\n*local* = Die interne Uhr wird auf die Lokalzeit gesetzt. Bevorzugt wenn z.B. noch eine native Windows-Installation auf dem PC vorhanden ist.\r\n*utc* = Die interne Uhr wird auf die \/Koordinierte Weltzeit\/ gesetzt. Dies ist die g\u00e4ngige Einstellung in einem reinen Linux-Umfeld.",
+ "SLX_COMMON_SHARE_AUTH": "Authentifizierungsmethode f\u00fcr das gemeinsame Netzlaufwerk. *guest* bedeutet, dass keine Authentifizierung notwendig ist, *user* bedeutet, dass die Credentials des angemeldeten Benutzers verwendet werden.",
+ "SLX_COMMON_SHARE_PATH": "Netzwerkpfad des gemeinsamen Netzlaufwerks. Es werden NFS (keine Authentifizierung) und CIFS\/SMB (mit und ohne Authentifizierung) unterst\u00fctzt.",
+ "SLX_DEMO_PASS": "Passwort f\u00fcr den eingebauten *demo*-Account. Leer lassen, um das Einloggen zu verbieten.\r\nDas Passwort wird wie das root-Passwort nur gehasht an den Client \u00fcbertragen.",
+ "SLX_LOGOUT_TIMEOUT": "Zeit \/in Sekunden\/, die eine Benutzersitzung ohne Aktion sein darf, bevor sie beendet wird.Feld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_NET_DOMAIN": "DNS-Dom\u00e4ne, in die sich die Clients eingliedern, sofern der DHCP Server keine solche vorgibt.",
+ "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_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.",
+ "SLX_PROXY_MODE": "Legt fest, ob zum Zugriff aufs Internet ein Proxy-Server ben\u00f6tigt wird.\r\n*off* = keinen Proxy benutzen.\r\n*on* = Proxy immer benutzen.\r\n*auto* = Proxy nur benutzen, wenn sich der Client-PC in einem privaten Adressbereich befindet.",
+ "SLX_PROXY_PORT": "Der Port des zu verwendenden Proxy Servers.",
+ "SLX_PROXY_TYPE": "Art des Proxys: *socks4*, *socks5*, *http-connect* (HTTP Proxy mit Unterst\u00fctzung der CONNECT-Methode), *http-relay* (Klassischer HTTP Proxy)",
+ "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_SHUTDOWN_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner ausschalten, auch wenn noch ein Benutzer aktiv ist.Mehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.",
+ "SLX_SHUTDOWN_TIMEOUT": "Zeit in Sekunden, nach dem ein Rechner abgeschaltet wird, sofern kein Benutzer angemeldet ist.Feld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_VMCHOOSER_FORLOCATION": "Legt das Verhalten fest, wenn es Veranstaltungen gibt, die an einen bestimmten Ort\/Raum gebunden sind.\r\n*IGNORE*: Mit den restlichen, globalen Veranstaltungen alphabetisch sortiert auflisten.\r\n*BUMP*: Die spezifischen Veranstaltungen oben auflisten, die globalen darunter.\r\n*EXCLUSIVE*: Spezifische Veranstaltungen oben auflisten, globale Veranstaltungen zun\u00e4chst ausblenden. Die globalen Veranstaltungen befinden sich unter einem eingeklappten Listenknoten.",
+ "SLX_VMCHOOSER_TAB": "Bestimmt, welcher Karteireiter im vmchooser standardm\u00e4\u00dfig ausgew\u00e4hlt wird.\r\n*0*: Native Linux-Sessions\r\n*1*: Nutzerspezifische Kurse\r\n*2*: Alle Kurse\r\n*AUTO*: Hat der Rechner beschr\u00e4nkte Ressourcen, werden die Linux-Sitzungen angezeigt, sonst alle Kurse\r\n\r\nHat der Benutzer ein persistentes Home-Verzeichnis, wirkt sich diese Einstellung nur beim ersten Anmelden aus. Bei sp\u00e4teren Sitzungen markiert der vmchooser die zuletzt gestartete Sitzung und wechselt zum entsprechenden Karteireiter.",
+ "SLX_VMCHOOSER_TEMPLATES": "Legt fest, wie Veranstaltungen in der Sortierung behandelt werden, welche auf eine VM linken, die eine Vorlage ist.\r\n*IGNORE*: Wie regul\u00e4re Veranstaltungen behandeln\r\n*BUMP*: Weiter oben in der Liste einsortieren"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/templates/_page.json b/modules-available/baseconfig/lang/de/templates/_page.json
new file mode 100644
index 00000000..0f4819b4
--- /dev/null
+++ b/modules-available/baseconfig/lang/de/templates/_page.json
@@ -0,0 +1,15 @@
+{
+ "lang_basicConfiguration": "Basiskonfiguration",
+ "lang_catUser": "Benutzerverwaltung",
+ "lang_clientRelatedConfig": "Die Optionen auf dieser Seite beziehen sich auf das Verhalten der bwLehrpool-Clients.",
+ "lang_close": "Schlie\u00dfen",
+ "lang_create": "Schaffen",
+ "lang_delete": "L\u00f6schen",
+ "lang_help": "Hilfe",
+ "lang_newUser": "Neuer Benutzer",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_reset": "Zur\u00fccksetzen",
+ "lang_save": "Speichern",
+ "lang_userName": "Benutzername",
+ "lang_userPasswd": "Passwort"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/cat_setting.json b/modules-available/baseconfig/lang/en/cat_setting.json
new file mode 100644
index 00000000..7c0ab654
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/cat_setting.json
@@ -0,0 +1,9 @@
+{
+ "cat_1": "Inactivity and Shutdown",
+ "cat_2": "Internet Access",
+ "cat_3": "Time Synchronization",
+ "cat_4": "Basic System",
+ "cat_5": "Common network share",
+ "cat_6": "Uncategorized",
+ "cat_7": "vmchooser"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/module.json b/modules-available/baseconfig/lang/en/module.json
new file mode 100644
index 00000000..9345c27c
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/module.json
@@ -0,0 +1,27 @@
+{
+ "lang_basicConfiguration": "Basic Configuration",
+ "lang_catPartition": "Partition Managment",
+ "lang_catUser": "User Managment",
+ "lang_close": "Close",
+ "lang_confirm": "Would you like to save the settings on [ \/srv\/openslx\/www\/boot\/config ] ?",
+ "lang_create": "Create",
+ "lang_delete": "Delete",
+ "lang_help": "Help",
+ "lang_helpId": "Partition Id",
+ "lang_helpMountPoint": "Must be a directory: \/example\/directory\/",
+ "lang_helpOptions": "Currently, only option 'bootable' is available",
+ "lang_helpSize": "Must be in Gigabytes e.g. 15G",
+ "lang_newPartition": "New Partition",
+ "lang_newUser": "New User",
+ "lang_partitionId": "Id",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_partitionOptions": "Options",
+ "lang_partitionSize": "Size",
+ "lang_reset": "Reset",
+ "lang_resetConfirm": "Do you really wish to reset the variable to their default values?",
+ "lang_resetDefault": "Reset Default",
+ "lang_save": "Save",
+ "lang_userName": "Username",
+ "lang_userPasswd": "Password",
+ "module_name": "Variables"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/setting.json b/modules-available/baseconfig/lang/en/setting.json
new file mode 100644
index 00000000..36ac6e8c
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/setting.json
@@ -0,0 +1,23 @@
+{
+ "SLX_ADDONS": "Addons to load. Currently, only *vmware* is available.",
+ "SLX_BENCHMARK_VM": "If non-empty, this should be the exact display name of an entry in *vmchooser*, so it will be automatically started after bootup. Useful for benchmarking.",
+ "SLX_BIOS_CLOCK": "Specifies whether and how the internal clock of the computer should be set in relation to the system time of the \/MiniLinux\/.\r\n*off* = The internal clock of the computer is not changed.\r\n*local* = The internal clock is set to local time. Preferably if, for example, there is still a native Windows installation available on the PC.\r\n*utc* = The internal clock is set to the \/Coordinated Universal Time\/. This is the most common setup in a pure Linux environment.",
+ "SLX_COMMON_SHARE_AUTH": "Athentication method for the common network share. *guest* means no authentication (public share), *user* means the user's credentials will be used.",
+ "SLX_COMMON_SHARE_PATH": "Path of network share. Supported are NFS (no authentication only) and CIFS\/SMB (with and without authentication).",
+ "SLX_DEMO_PASS": "Password for the *demo* account. Leave empty to disallow logging in as the demo user.\r\nLike the root password, the demo user's password will be sent to the client in its hashed form.",
+ "SLX_LOGOUT_TIMEOUT": "Time \/in seconds\/, in which a user session may remain without action before it is terminated.Leave field blank to disable the function.",
+ "SLX_NET_DOMAIN": "DNS domain in which the client integrate, provided the DHCP server does not specifies such.",
+ "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_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.",
+ "SLX_PROXY_MODE": "Determines whether a proxy server is required to access the Internet.\r\n*off* = do not use a Proxy.\r\n*on* = Always use proxy.\r\n*auto* = Only use proxy when the client PC is in a private address space.",
+ "SLX_PROXY_PORT": "The port to use for the proxy server.",
+ "SLX_PROXY_TYPE": "Type of the proxy.*socks4*, *socks5*, *http-connect* (HTTP proxy with support from the CONNECT method), *http-relay* (Classic HTTP proxy)",
+ "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 passoword in \/$6$...$...\/-format.",
+ "SLX_SHUTDOWN_SCHEDULE": "Fixed time to turn off the computer, even if there is a user active.Several 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.Leave blank to disable the function.",
+ "SLX_VMCHOOSER_FORLOCATION": "Defines how lectures special to the user's location are handled in the vmchooser.\r\n*IGNORE*: Sort them alphabetically among the global lectures.\r\n*BUMP*: Put them atop the global lectures.\r\n*EXCLUSIVE*: Put them atop the global lectures and aditionally collapse the node which contains the global lectures.",
+ "SLX_VMCHOOSER_TAB": "Defines which tab is show by default, if the user doesn't have stored a last used session on his persistent home directory.\r\n*0*: Native Linux sessions\r\n*1*: User specific lectures\r\n*2*: All lectures\r\n*AUTO*: If the computer has low system specs, show the Linux sessions, otherwise, show all lectures",
+ "SLX_VMCHOOSER_TEMPLATES": "Defines how lectures that link to template VMs are treated wrt sorting.\r\n*IGNORE*: Sort among regular lectures\r\n*BUMP*: Move to top of list"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/templates/_page.json b/modules-available/baseconfig/lang/en/templates/_page.json
new file mode 100644
index 00000000..6429a835
--- /dev/null
+++ b/modules-available/baseconfig/lang/en/templates/_page.json
@@ -0,0 +1,7 @@
+{
+ "lang_basicConfiguration": "Basic Configuration",
+ "lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
+ "lang_close": "Close",
+ "lang_reset": "Reset",
+ "lang_save": "Save"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/lang/pt/module.json b/modules-available/baseconfig/lang/pt/module.json
new file mode 100644
index 00000000..0ac0a075
--- /dev/null
+++ b/modules-available/baseconfig/lang/pt/module.json
@@ -0,0 +1,27 @@
+{
+ "lang_basicConfiguration": "Configura\u00e7\u00e3o B\u00e1sica",
+ "lang_catPartition": "Gerenciamento de Parti\u00e7\u00f5es",
+ "lang_catUser": "Gerenciamente de Usu\u00e1rios",
+ "lang_close": "Fechar",
+ "lang_confirm": "Voc\u00ea deseja salvar configura\u00e7\u00f5es em [ \/srv\/openslx\/www\/boot\/default\/config ] ?",
+ "lang_create": "Criar",
+ "lang_delete": "Deletar",
+ "lang_help": "Ajuda",
+ "lang_helpId": "Id da parti\u00e7\u00e3o",
+ "lang_helpMountPoint": "Precisa ser um diret\u00f3rio: \/exemplo\/diret\u00f3rio\/",
+ "lang_helpOptions": "Atualmente, apenas a op\u00e7\u00e3o 'bootable' est\u00e1 dispon\u00edvel",
+ "lang_helpSize": "Precisa estar em Gigabytes, por exemplo 15G",
+ "lang_newPartition": "Nova Parti\u00e7\u00e3o",
+ "lang_newUser": "Novo Usu\u00e1rio",
+ "lang_partitionId": "Id",
+ "lang_partitionMountPoint": "Mount Point",
+ "lang_partitionOptions": "Op\u00e7\u00f5es",
+ "lang_partitionSize": "Tamanho",
+ "lang_reset": "Limpar",
+ "lang_resetConfirm": "Voc\u00ea realmente deseja restaurar as vari\u00e1veis para seus valores padr\u00f5es?",
+ "lang_resetDefault": "Restaurar Padr\u00e3o",
+ "lang_save": "Salvar",
+ "lang_userName": "Nome",
+ "lang_userPasswd": "Senha",
+ "module_name": "Vari\u00e1veis"
+}
\ No newline at end of file
diff --git a/modules-available/baseconfig/page.inc.php b/modules-available/baseconfig/page.inc.php
new file mode 100644
index 00000000..8f914376
--- /dev/null
+++ b/modules-available/baseconfig/page.inc.php
@@ -0,0 +1,129 @@
+qry_extra[] = array(
+ 'name' => 'distroid',
+ 'value' => (int)$_REQUEST['distroid'],
+ 'table' => 'setting_distro',
+ );
+ if (isset($_REQUEST['locationid'])) {
+ $this->qry_extra[] = array(
+ 'name' => 'locationid',
+ 'value' => (int)$_REQUEST['locationid'],
+ 'table' => 'setting_location',
+ );
+ }
+ }
+
+ if (isset($_POST['setting']) && is_array($_POST['setting'])) {
+ if (User::hasPermission('superadmin')) {
+ // Build variables for specific sub-settings
+ $qry_insert = '';
+ $qry_values = '';
+ foreach ($this->qry_extra as $item) {
+ $qry_insert = ', ' . $item['name'];
+ $qry_values = ', :' . $item['name'];
+ }
+ // Load all existing config options to validate input
+ $res = Database::simpleQuery('SELECT setting, validator FROM setting');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $key = $row['setting'];
+ $validator = $row['validator'];
+ $displayValue = (isset($_POST['setting'][$key]) ? $_POST['setting'][$key] : '');
+ // Validate data first!
+ $mangledValue = Validator::validate($validator, $displayValue);
+ if ($mangledValue === false) {
+ Message::addWarning('value-invalid', $key, $displayValue);
+ continue;
+ }
+ // Now put into DB
+ Database::exec("INSERT INTO setting_global (setting, value, displayvalue $qry_insert)
+ VALUES (:key, :value, :displayvalue $qry_values)
+ ON DUPLICATE KEY UPDATE value = :value, displayvalue = :displayvalue",
+ $this->qry_extra + array(
+ 'key' => $key,
+ 'value' => $mangledValue,
+ 'displayvalue' => $displayValue
+ )
+ );
+ }
+ Message::addSuccess('settings-updated');
+ Util::redirect('?do=BaseConfig');
+ }
+ }
+ }
+
+ protected function doRender()
+ {
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ // Build left joins for specific settings
+ $joins = '';
+ foreach ($this->qry_extra as $item) {
+ $joins .= " LEFT JOIN {$item['table']} ";
+ }
+ // List global config option
+ $settings = array();
+ $res = Database::simpleQuery('SELECT cat_setting.catid, setting.setting, setting.defaultvalue, setting.permissions, setting.validator, tbl.displayvalue
+ FROM setting
+ INNER JOIN cat_setting USING (catid)
+ LEFT JOIN setting_global AS tbl USING (setting)
+ ORDER BY cat_setting.sortval ASC, setting.setting ASC');
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['description'] = Util::markup(Page::translate($row['setting'], 'setting'));
+ if (is_null($row['displayvalue'])) $row['displayvalue'] = $row['defaultvalue'];
+ $row['item'] = $this->makeInput($row['validator'], $row['setting'], $row['displayvalue']);
+ $settings[$row['catid']]['settings'][] = $row;
+ if (!isset($settings[$row['catid']]['category_id'])) {
+ $settings[$row['catid']]['category_name'] = Page::translate('cat_' . $row['catid'], 'cat_setting');
+ $settings[$row['catid']]['category_id'] = $row['catid'];
+ }
+ }
+ Render::addTemplate('_page', array(
+ 'categories' => array_values($settings)
+ ));
+ }
+
+ /**
+ * Create html snippet for setting, based on given validator
+ * @param type $validator
+ * @return boolean
+ */
+ private function makeInput($validator, $setting, $current)
+ {
+ $parts = explode(':', $validator, 2);
+ if ($parts[0] === 'list') {
+ $items = explode('|', $parts[1]);
+ $ret = '';
+ }
+ // Password field guessing
+ if (stripos($validator, 'password') !== false) {
+ $type = Property::getPasswordFieldType();
+ } else {
+ $type = 'text';
+ }
+ // Fallback: single line input
+ return '';
+ }
+
+}
diff --git a/modules-available/baseconfig/templates/_page.html b/modules-available/baseconfig/templates/_page.html
new file mode 100644
index 00000000..273ee50c
--- /dev/null
+++ b/modules-available/baseconfig/templates/_page.html
@@ -0,0 +1,188 @@
+
{{lang_basicConfiguration}}
+
{{lang_clientRelatedConfig}}
+
+
+
+
+
+
+
+
+
diff --git a/modules-available/citymanagement/config.json b/modules-available/citymanagement/config.json
new file mode 100644
index 00000000..aff54b3a
--- /dev/null
+++ b/modules-available/citymanagement/config.json
@@ -0,0 +1,4 @@
+{
+ "enabled":"false",
+ "permission":"0"
+}
diff --git a/modules-available/citymanagement/lang/en/module.json b/modules-available/citymanagement/lang/en/module.json
new file mode 100644
index 00000000..ba6c5c44
--- /dev/null
+++ b/modules-available/citymanagement/lang/en/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_cancelConfirm": "Do you really want to remove this city?",
+ "lang_cityInfo": "Here you can create new cities for the website, besides editing or removing the existing ones",
+ "lang_cityPage": "Manage cities",
+ "lang_cityname": "City name",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit": "Edit",
+ "lang_editcity": "Edit City",
+ "lang_operations": "Operations",
+ "lang_remove": "Remove",
+ "lang_save": "Save",
+ "module_name": "Management"
+}
\ No newline at end of file
diff --git a/modules-available/citymanagement/lang/en/templates/citymanagement.json b/modules-available/citymanagement/lang/en/templates/citymanagement.json
new file mode 100644
index 00000000..27bb60c0
--- /dev/null
+++ b/modules-available/citymanagement/lang/en/templates/citymanagement.json
@@ -0,0 +1,13 @@
+{
+ "lang_cancelConfirm": "Do you really want to remove this city?",
+ "lang_cityInfo":"Here you can create new cities for the website, besides editing or removing the existing ones",
+ "lang_cityPage":"Manage cities",
+ "lang_cityname":"City name",
+ "lang_close": "Close",
+ "lang_create": "Create",
+ "lang_edit":"Edit",
+ "lang_editcity":"Edit City",
+ "lang_operations": "Operations",
+ "lang_remove": "Remove",
+ "lang_save": "Save"
+}
diff --git a/modules-available/citymanagement/lang/pt/module.json b/modules-available/citymanagement/lang/pt/module.json
new file mode 100644
index 00000000..c514f437
--- /dev/null
+++ b/modules-available/citymanagement/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_cancelConfirm": "Deseja realmente remover a cidade?",
+ "lang_cityInfo": "Nesta se\u00e7\u00e3o voc\u00ea poder\u00e1 criar cidades para o site, al\u00e9m de editar ou remover cidades existentes",
+ "lang_cityPage": "Gerenciar cidades",
+ "lang_cityname": "Nome da cidade",
+ "lang_close": "Fechar",
+ "lang_create": "Criar",
+ "lang_edit": "Editar",
+ "lang_editcity": "Editar Cidade",
+ "lang_operations": "Opera\u00e7\u00f5es",
+ "lang_remove": "Remover",
+ "lang_save": "Salvar",
+ "module_name": "Gerenciamento"
+}
\ No newline at end of file
diff --git a/modules-available/citymanagement/page.inc.php b/modules-available/citymanagement/page.inc.php
new file mode 100644
index 00000000..acc30bf9
--- /dev/null
+++ b/modules-available/citymanagement/page.inc.php
@@ -0,0 +1,81 @@
+page = $p;
+ else
+ $this->page = 1;
+ switch(Request::post('action')){
+ case "edit":
+ $this->edit(Request::post('cityid'),Request::post('name'));
+ break;
+ case "create":
+ $this->create(Request::post('name'));
+ break;
+ case "delete":
+ $this->delete(Request::post('cityid'));
+ break;
+ }
+
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ }
+
+ protected function doRender()
+ {
+ // load every city
+ $cities = array();
+ $res = Database::simpleQuery("SELECT cityid, name FROM cities ORDER BY cityid DESC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $cities[] = array(
+ 'id' => $row['cityid'],
+ 'name' => $row['name'],
+ );
+ }
+
+ $pag = new Pagination($cities,$this->page);
+
+ Render::addTemplate('page-citymanagement', array(
+ 'cities' => $pag->getItems(),
+ 'pages' => $pag->getPagination()
+ ));
+ }
+
+ private function edit($cityid, $newname){
+ $data = array (
+ 'cityid' => $cityid,
+ 'name' => $newname,
+ );
+ Database::exec ( 'UPDATE cities SET name = :name WHERE cityid = :cityid', $data );
+ Message::addSuccess('update-city');
+ }
+
+ private function create($name){
+ $data = array (
+ 'name' => $name,
+ );
+ Database::exec('INSERT INTO cities(name) VALUES( :name )',$data);
+ Message::addSuccess('add-city');
+ }
+
+ private function delete($cityid){
+ $data = array (
+ 'cityid' => $cityid
+ );
+ Database::exec ( 'DELETE FROM cities WHERE cityid = :cityid', $data );
+ Message::addSuccess('delete-city');
+ }
+}
diff --git a/modules-available/citymanagement/templates/page-citymanagement.html b/modules-available/citymanagement/templates/page-citymanagement.html
new file mode 100644
index 00000000..6d7750cf
--- /dev/null
+++ b/modules-available/citymanagement/templates/page-citymanagement.html
@@ -0,0 +1,77 @@
+
+{{/cities}}
diff --git a/modules-available/dozmod/config.json b/modules-available/dozmod/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/dozmod/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/dozmod/lang/de/templates/images-delete.json b/modules-available/dozmod/lang/de/templates/images-delete.json
new file mode 100644
index 00000000..8c54e4d1
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/images-delete.json
@@ -0,0 +1,12 @@
+{
+ "lang_delButton": "Gew\u00e4hlte Images endg\u00fcltig l\u00f6schen",
+ "lang_delete": "L\u00f6schen",
+ "lang_description": "Diese Liste zeigt Images, 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_hasNewer": "Neuere Version existiert",
+ "lang_heading": "Zu l\u00f6schende Image-Versionen",
+ "lang_image": "VM",
+ "lang_owner": "Besitzer",
+ "lang_size": "Gr\u00f6\u00dfe",
+ "lang_subHeading": "Images, die abgelaufen oder besch\u00e4digt sind",
+ "lang_version": "Version vom"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/mailconfig.json b/modules-available/dozmod/lang/de/templates/mailconfig.json
new file mode 100644
index 00000000..4509c3c7
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/mailconfig.json
@@ -0,0 +1,21 @@
+{
+ "lang_asteriskRequired": "Felder mit (*) sind erforderlich",
+ "lang_host": "Host",
+ "lang_mailConfig": "SMTP-Konfiguration zum Versenden von Mails",
+ "lang_mailConfigHeadline": "EMail-Konfiguration",
+ "lang_mailDescription": "F\u00fcllen Sie die folgenden Felder aus, wenn sie m\u00f6chten, dass Dozenten Benachrichtigungen per Mail bekommen, falls eine von ihnen genutzte oder erstellte VM oder Veranstaltung abl\u00e4uft. Um diese Funktion zu deaktivieren, lassen Sie eines der mit (*) gekennzeichneten Felder leer. Wenn das hier angegebene E-Mail-Konto nur zum Versenden von Mails genutzt wird, sollten Sie einen Auto-Responder einrichten f\u00fcr den Fall, dass ein Dozent auf eine der automatisch generierten Mails antwortet (bzw. eine explizit angegebene Reply-To Adresse ignoriert).",
+ "lang_password": "Passwort",
+ "lang_port": "Port",
+ "lang_replyTo": "Reply-To Adresse (z.B. Helpdesk)",
+ "lang_save": "Konfiguration speichern",
+ "lang_senderAddress": "Absenderadresse",
+ "lang_senderName": "Absender Anzeigename",
+ "lang_ssl": "SSL-Modus",
+ "lang_sslExplicit": "Explizites SSL (\"STARTTLS\")",
+ "lang_sslImplicit": "Implizites SSL",
+ "lang_sslNone": "Kein SSL",
+ "lang_test": "Test-Mail senden",
+ "lang_testConfiguration": "Um die Konfiguration zu testen, geben Sie hier eine Empf\u00e4ngeradresse ein",
+ "lang_testRecipient": "Empf\u00e4nger",
+ "lang_username": "Benutzername (SMTP-Auth)"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/orglist.json b/modules-available/dozmod/lang/de/templates/orglist.json
new file mode 100644
index 00000000..938429f0
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/orglist.json
@@ -0,0 +1,6 @@
+{
+ "lang_canLogin": "Nutzer dieser Einrichtung k\u00f6nnen sich am Satelliten anmelden",
+ "lang_organization": "Einrichtung",
+ "lang_organizationList": "Liste der Einrichtungen",
+ "lang_organizationListHeader": "Nutzungsrechte f\u00fcr den Satelliten festlegen"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/templates/userlist.json b/modules-available/dozmod/lang/de/templates/userlist.json
new file mode 100644
index 00000000..e9e33cc2
--- /dev/null
+++ b/modules-available/dozmod/lang/de/templates/userlist.json
@@ -0,0 +1,12 @@
+{
+ "lang_canLogin": "Dieser Benutzer kann sich am Satelilten anmelden",
+ "lang_email": "EMail",
+ "lang_emailNotifications": "EMail-Benachrichtigungen aktiviert",
+ "lang_lastLogin": "Letzte Anmeldung",
+ "lang_organization": "Organisation",
+ "lang_superUser": "Ist SuperUser (darf alle Veranstaltungen und VMs bearbeiten\/l\u00f6schen)",
+ "lang_user": "Benutzername",
+ "lang_userList": "Benutzerliste",
+ "lang_userListDescription": "Hier k\u00f6nnen Sie individuelle Nutzer zu \"Super-Usern\" machen. Diese haben im Dozentenmodul auf alle Veranstaltungen und VMs Vollzugriff, unabh\u00e4ngig von den gesetzten Berechtigungen. Au\u00dferdem k\u00f6nnen Sie hier Benutzer vom Zugriff mittels des Dozentenmoduls ausschlie\u00dfen.",
+ "lang_userListHeader": "Dem Satelliten bekannte Benutzer"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/module.json b/modules-available/dozmod/lang/en/module.json
new file mode 100644
index 00000000..e42d21ea
--- /dev/null
+++ b/modules-available/dozmod/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Tutor Module"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/images-delete.json b/modules-available/dozmod/lang/en/templates/images-delete.json
new file mode 100644
index 00000000..fcc8c7e7
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/images-delete.json
@@ -0,0 +1,12 @@
+{
+ "lang_delButton": "Permanently delete selected images",
+ "lang_delete": "Delete",
+ "lang_description": "This list shows images that reached their expire date, or where the image file in the file system is damaged or missing. You need to manually confirm the deletion of these files for safety reasons (software bugs, wrong system time, etc.).",
+ "lang_hasNewer": "Newer version exists",
+ "lang_heading": "Images marked for deletion",
+ "lang_image": "VM",
+ "lang_owner": "Owner",
+ "lang_size": "Size",
+ "lang_subHeading": "Expired or damaged images",
+ "lang_version": "Version timestamp"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/mailconfig.json b/modules-available/dozmod/lang/en/templates/mailconfig.json
new file mode 100644
index 00000000..0c0dcd7f
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/mailconfig.json
@@ -0,0 +1,21 @@
+{
+ "lang_asteriskRequired": "Fields marked with (*) are required",
+ "lang_host": "Host",
+ "lang_mailConfig": "SMTP configuration for sending mails",
+ "lang_mailConfigHeadline": "email configuration",
+ "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_password": "Password",
+ "lang_port": "Port",
+ "lang_replyTo": "Reply-To address",
+ "lang_save": "Save configuration",
+ "lang_senderAddress": "Sender address",
+ "lang_senderName": "Sender's display name",
+ "lang_ssl": "SSL mode",
+ "lang_sslExplicit": "Explicit SSL (\"STARTTLS\")",
+ "lang_sslImplicit": "Implicit SSL",
+ "lang_sslNone": "No SSL",
+ "lang_test": "Send test mail",
+ "lang_testConfiguration": "To test the configuration, enter a recipient address here",
+ "lang_testRecipient": "Recipient",
+ "lang_username": "User name (SMTP auth)"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/orglist.json b/modules-available/dozmod/lang/en/templates/orglist.json
new file mode 100644
index 00000000..37b89e02
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/orglist.json
@@ -0,0 +1,6 @@
+{
+ "lang_canLogin": "Members of this organization can login",
+ "lang_organization": "Organization",
+ "lang_organizationList": "List of organizations",
+ "lang_organizationListHeader": "Set access permissions for organizations"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/templates/userlist.json b/modules-available/dozmod/lang/en/templates/userlist.json
new file mode 100644
index 00000000..615f1b14
--- /dev/null
+++ b/modules-available/dozmod/lang/en/templates/userlist.json
@@ -0,0 +1,12 @@
+{
+ "lang_canLogin": "This user can login to this satellite",
+ "lang_email": "E-Mail",
+ "lang_emailNotifications": "E-Mail notifications enabled",
+ "lang_lastLogin": "Last login",
+ "lang_organization": "Organization",
+ "lang_superUser": "Is super user (can edit\/delete all lectures and VMs)",
+ "lang_user": "User name",
+ "lang_userList": "User list",
+ "lang_userListDescription": "Here you can promote \"super users\", which will have all permissions in the \"Dozenzenmodul\". You can also ban users from accessing this server via the \"Dozentenmodul\".",
+ "lang_userListHeader": "Users known to this satellite"
+}
\ No newline at end of file
diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php
new file mode 100644
index 00000000..f98d8952
--- /dev/null
+++ b/modules-available/dozmod/page.inc.php
@@ -0,0 +1,252 @@
+mailHandler();
+ }
+ if ($action === 'delimages') {
+ $result = $this->handleDeleteImages();
+ if (!empty($result)) {
+ Message::addInfo('delete-images', $result);
+ }
+ Util::redirect('?do=DozMod');
+ }
+ }
+
+ protected function doRender()
+ {
+ $this->listDeletePendingImages();
+ // Mail config
+ $conf = Database::queryFirst('SELECT value FROM sat.configuration WHERE parameter = :param', array('param' => 'mailconfig'));
+ if ($conf != null) {
+ $conf = @json_decode($conf['value'], true);
+ if (is_array($conf)) {
+ $conf['set_' . $conf['ssl']] = 'selected="selected"';
+ }
+ }
+ Render::addTemplate('mailconfig', $conf);
+ // User list for making people admin
+ $this->listUsers();
+ $this->listOrganizations();
+ }
+
+ private function listDeletePendingImages()
+ {
+ $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");
+ $NOW = time();
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['latexptime'] > $NOW && $row['latdelstate'] === 'KEEP') {
+ $row['hasNewerClass'] = 'glyphicon-ok green';
+ } else {
+ $row['hasNewerClass'] = 'glyphicon-remove red';
+ }
+ if ($row['deletestate'] === 'WANT_DELETE') {
+ $row['name_extra_class'] = 'slx-strike';
+ }
+ $row['version'] = date('d.m.Y H:i:s', $row['createtime']);
+ $row['filesize'] = Util::readableFileSize($row['filesize']);
+ $rows[] = $row;
+ }
+ if (empty($rows))
+ return;
+ Render::addTemplate('images-delete', array('images' => $rows));
+ }
+
+ private function cleanMailArray()
+ {
+ $keys = array('host', 'port', 'ssl', 'senderAddress', 'replyTo', 'username', 'password', 'serverName');
+ $data = array();
+ foreach ($keys as $key) {
+ $data[$key] = Request::post($key, '');
+ settype($data[$key], 'string');
+ if (is_numeric($data[$key])) {
+ settype($data[$key], 'int');
+ }
+ }
+ return $data;
+ }
+
+ protected function doAjax()
+ {
+ if (!User::hasPermission('superadmin'))
+ return;
+
+ $action = Request::post('action');
+ if ($action === 'mail') {
+ $this->handleTestMail();
+ } elseif ($action === 'setmail' || $action === 'setsu' || $action == 'setlogin') {
+ $this->setUserOption($action);
+ } elseif ($action === 'setorglogin') {
+ $this->setOrgOption($action);
+ } elseif ($action === 'delimages') {
+ die($this->handleDeleteImages());
+ }
+ }
+
+ private 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, 2, $code);
+ if ($code == 999) {
+ $ret .= "\nConnection to DMSD failed.";
+ }
+ return $ret;
+ }
+ }
+ return false;
+ }
+
+ private function handleTestMail()
+ {
+ $do = Request::post('button');
+ if ($do === 'test') {
+ // Prepare array
+ $data = $this->cleanMailArray();
+ Header('Content-Type: text/plain; charset=utf-8');
+ $data['recipient'] = Request::post('recipient', '');
+ if (!preg_match('/.+@.+\..+/', $data['recipient'])) {
+ $result = 'No recipient given!';
+ } else {
+ $result = Download::asStringPost('http://127.0.0.1:9080/do/mailtest', $data, 2, $code);
+ }
+ die($result);
+ }
+ }
+
+ private function mailHandler()
+ {
+ // Check action
+ $do = Request::post('button');
+ if ($do === 'save') {
+ // Prepare array
+ $data = $this->cleanMailArray();
+ $data = json_encode($data);
+ Database::exec('INSERT INTO sat.configuration (parameter, value)'
+ . ' VALUES (:param, :value)'
+ . ' ON DUPLICATE KEY UPDATE value = VALUES(value)', array(
+ 'param' => 'mailconfig',
+ 'value' => $data
+ ));
+ Message::addSuccess('mail-config-saved');
+ }
+ Util::redirect('?do=DozMod');
+ }
+
+ private function listUsers()
+ {
+ $res = Database::simpleQuery('SELECT userid, firstname, lastname, email, lastlogin, user.canlogin, issuperuser, emailnotifications,'
+ . ' organization.displayname AS orgname FROM sat.user'
+ . ' LEFT JOIN sat.organization USING (organizationid)'
+ . ' ORDER BY lastname ASC, firstname ASC');
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['canlogin'] = $this->checked($row['canlogin']);
+ $row['issuperuser'] = $this->checked($row['issuperuser']);
+ $row['emailnotifications'] = $this->checked($row['emailnotifications']);
+ $row['lastlogin'] = date('d.m.Y', $row['lastlogin']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('userlist', array('users' => $rows));
+ }
+
+ private function listOrganizations()
+ {
+ $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'] = $this->checked($row['canlogin']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('orglist', array('organizations' => $rows));
+ }
+
+ private function checked($val)
+ {
+ if ($val)
+ return 'checked="checked"';
+ return '';
+ }
+
+ private function setUserOption($option)
+ {
+ $val = (string) Request::post('value', '-');
+ if ($val !== '1' && $val !== '0')
+ die('Nein');
+ if ($option === 'setmail') {
+ $field = 'emailnotifications';
+ } elseif ($option === 'setsu') {
+ $field = 'issuperuser';
+ } elseif ($option === 'setlogin') {
+ $field = 'canlogin';
+ } else {
+ die('Unknown');
+ }
+ $user = (string) Request::post('userid', '?');
+ $ret = Database::exec("UPDATE sat.user SET $field = :onoff WHERE userid = :userid", array(
+ 'userid' => $user,
+ 'onoff' => $val
+ ));
+ error_log("Setting $field to $val for $user - affected: $ret");
+ if ($ret === false)
+ die('Error');
+ if ($ret == 0)
+ die(1 - $val);
+ die($val);
+ }
+
+ private function setOrgOption($option)
+ {
+ $val = (string) Request::post('value', '-');
+ if ($val !== '1' && $val !== '0')
+ die('Nein');
+ if ($option === 'setorglogin') {
+ $field = 'canlogin';
+ } else {
+ die('Unknown');
+ }
+ $ret = Database::exec("UPDATE sat.organization SET $field = :onoff WHERE organizationid = :organizationid", array(
+ 'organizationid' => (string) Request::post('organizationid', ''),
+ 'onoff' => $val
+ ));
+ if ($ret === false)
+ die('Error');
+ if ($ret === 0)
+ die(1 - $val);
+ die($val);
+ }
+
+}
diff --git a/modules-available/dozmod/templates/images-delete.html b/modules-available/dozmod/templates/images-delete.html
new file mode 100644
index 00000000..c4cbfd34
--- /dev/null
+++ b/modules-available/dozmod/templates/images-delete.html
@@ -0,0 +1,57 @@
+
{{lang_heading}}
+
+
+
+ {{lang_subHeading}}
+
+
+
{{lang_description}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/dozmod/templates/mailconfig.html b/modules-available/dozmod/templates/mailconfig.html
new file mode 100644
index 00000000..b19776c0
--- /dev/null
+++ b/modules-available/dozmod/templates/mailconfig.html
@@ -0,0 +1,91 @@
+
{{lang_mailConfigHeadline}}
+
+
+
+ {{lang_mailConfig}}
+
+
+
{{lang_mailDescription}}
+
[BETA] Diese Funktionalität ist neu. Wir bitten um Nachsicht, falls es Situationen gibt, in denen zu viele
+ oder zu wenige Nachrichten verschickt werden.
+
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/dozmod/templates/orglist.html b/modules-available/dozmod/templates/orglist.html
new file mode 100644
index 00000000..d325cc4d
--- /dev/null
+++ b/modules-available/dozmod/templates/orglist.html
@@ -0,0 +1,51 @@
+
{{lang_organizationList}}
+
+
+
+ {{lang_organizationListHeader}}
+
+
+
+
+
+
+
{{lang_organization}}
+
+
+
+
+ {{#organizations}}
+
+
{{displayname}}
+
+
+ {{/organizations}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/dozmod/templates/userlist.html b/modules-available/dozmod/templates/userlist.html
new file mode 100644
index 00000000..a76eae5e
--- /dev/null
+++ b/modules-available/dozmod/templates/userlist.html
@@ -0,0 +1,62 @@
+
+
+
+
diff --git a/modules-available/internetaccess/config.json b/modules-available/internetaccess/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/internetaccess/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/internetaccess/lang/de/templates/_page.json b/modules-available/internetaccess/lang/de/templates/_page.json
new file mode 100644
index 00000000..61e324a8
--- /dev/null
+++ b/modules-available/internetaccess/lang/de/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_automatic": "Automatisch",
+ "lang_description": "Hier k\u00f6nnen Sie konfigurieren, wie der Satellitenserver auf das Internet zugreifen soll. Dies wird in erster Linie f\u00fcr das Aktualisieren des Systems sowie das Synchronisieren von Virtuellen Maschinen mit dem Zentralserver verwendet. Gegenw\u00e4rtig wird neben Direktzugriff noch SOCKS4\/5 unterst\u00fctzt.",
+ "lang_internetAccess": "Internetzugriff",
+ "lang_manual": "Manuelle Angabe",
+ "lang_manualProxyConfig": "Wenn Sie einen SOCKS-Proxy manuell konfigurieren m\u00f6chten, geben Sie bitte hier die Verbindungsdaten an.",
+ "lang_no": "Keiner",
+ "lang_proxyAddress": "Adresse",
+ "lang_proxyPassword": "Passwort",
+ "lang_proxyPort": "Port",
+ "lang_proxyType": "Proxy Typ",
+ "lang_proxyUsername": "Benutzername",
+ "lang_save": "Speichern"
+}
\ No newline at end of file
diff --git a/modules-available/internetaccess/lang/de/templates/restart.json b/modules-available/internetaccess/lang/de/templates/restart.json
new file mode 100644
index 00000000..899a1d28
--- /dev/null
+++ b/modules-available/internetaccess/lang/de/templates/restart.json
@@ -0,0 +1,5 @@
+{
+ "lang_restartFailed": "Neustart eines oder mehrerer Dienste fehlgeschlagen!",
+ "lang_restarting": "Neustart",
+ "lang_serviceRestart": "Neustarten der Dienste"
+}
\ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/module.json b/modules-available/internetaccess/lang/en/module.json
new file mode 100644
index 00000000..f0c2f72f
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Internet Access"
+}
\ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/templates/_page.json b/modules-available/internetaccess/lang/en/templates/_page.json
new file mode 100644
index 00000000..c02ca4f5
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/templates/_page.json
@@ -0,0 +1,14 @@
+{
+ "lang_automatic": "Auto",
+ "lang_description": "Here you can configure how the satellite server has to access the internet.",
+ "lang_internetAccess": "Internet access",
+ "lang_manual": "Manual",
+ "lang_manualProxyConfig": "If you want to configure a proxy server manually, please supply the credentials here.",
+ "lang_no": "None",
+ "lang_proxyAddress": "Address",
+ "lang_proxyPassword": "Password",
+ "lang_proxyPort": "Port",
+ "lang_proxyType": "Proxy type",
+ "lang_proxyUsername": "User",
+ "lang_save": "Save"
+}
\ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/templates/restart.json b/modules-available/internetaccess/lang/en/templates/restart.json
new file mode 100644
index 00000000..badad460
--- /dev/null
+++ b/modules-available/internetaccess/lang/en/templates/restart.json
@@ -0,0 +1,5 @@
+{
+ "lang_restartFailed": "Restarting one or more services failed!",
+ "lang_restarting": "Restarting",
+ "lang_serviceRestart": "Restart of services"
+}
\ No newline at end of file
diff --git a/modules-available/internetaccess/page.inc.php b/modules-available/internetaccess/page.inc.php
new file mode 100644
index 00000000..b949be26
--- /dev/null
+++ b/modules-available/internetaccess/page.inc.php
@@ -0,0 +1,51 @@
+{{lang_internetAccess}}
+
+
diff --git a/modules-available/internetaccess/templates/restart.html b/modules-available/internetaccess/templates/restart.html
new file mode 100644
index 00000000..effe1feb
--- /dev/null
+++ b/modules-available/internetaccess/templates/restart.html
@@ -0,0 +1,22 @@
+
+
{{lang_serviceRestart}}
+
+
{{lang_restarting}} syncdaemon
+
{{lang_restarting}} dmsd
+
{{lang_restarting}} ldadp
+
+ {{lang_restartFailed}}
+
+
+
+
+
diff --git a/modules-available/locations/config.json b/modules-available/locations/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/locations/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/locations/lang/de/templates/location-subnets.json b/modules-available/locations/lang/de/templates/location-subnets.json
new file mode 100644
index 00000000..6caa1991
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/location-subnets.json
@@ -0,0 +1,18 @@
+{
+ "lang_addNewSubnet": "Neues Subnetz hinzuf\u00fcgen",
+ "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_deleteChildLocations": "Untergeordnete Orte ebenfalls l\u00f6schen",
+ "lang_deleteLocation": "Ort l\u00f6schen",
+ "lang_deleteSubnet": "Bereich l\u00f6schen",
+ "lang_endAddress": "Endadresse",
+ "lang_locationInfo": "Details zu diesem Ort",
+ "lang_locationSettings": "Raum\/Ort bearbeiten",
+ "lang_matchingMachines": "Enthaltene Rechner",
+ "lang_name": "Name",
+ "lang_parentLocation": "\u00dcbergeordneter Ort",
+ "lang_referencingLectures": "Veranstaltungen",
+ "lang_save": "Speichern",
+ "lang_startAddress": "Startadresse",
+ "lang_subnet": "IP-Bereich"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/de/templates/locations.json b/modules-available/locations/lang/de/templates/locations.json
new file mode 100644
index 00000000..3e25ef45
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/locations.json
@@ -0,0 +1,10 @@
+{
+ "lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
+ "lang_edit": "Bearbeiten",
+ "lang_location": "Ort",
+ "lang_locationName": "Name",
+ "lang_locationsMainHeading": "Verwaltung von R\u00e4umen\/Orten",
+ "lang_noParent": "Kein \u00fcbergeordneter Ort",
+ "lang_save": "Speichern",
+ "lang_thisListBySubnet": "Nach Subnetzen auflisten"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/de/templates/subnets.json b/modules-available/locations/lang/de/templates/subnets.json
new file mode 100644
index 00000000..b57f87ce
--- /dev/null
+++ b/modules-available/locations/lang/de/templates/subnets.json
@@ -0,0 +1,7 @@
+{
+ "lang_endAddress": "Ende",
+ "lang_listOfSubnets": "Liste der Subnetze",
+ "lang_location": "Ort",
+ "lang_startAddress": "Start",
+ "lang_thisListByLocation": "Zur Ortsansicht"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/en/module.json b/modules-available/locations/lang/en/module.json
new file mode 100644
index 00000000..b2a837b6
--- /dev/null
+++ b/modules-available/locations/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Room\/Locations"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/location-subnets.json b/modules-available/locations/lang/en/templates/location-subnets.json
new file mode 100644
index 00000000..2ba94384
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/location-subnets.json
@@ -0,0 +1,18 @@
+{
+ "lang_addNewSubnet": "Add new subnet",
+ "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_deleteChildLocations": "Delete child locations aswell",
+ "lang_deleteLocation": "Delete location",
+ "lang_deleteSubnet": "Delete range",
+ "lang_endAddress": "End address",
+ "lang_locationInfo": "Location details",
+ "lang_locationSettings": "Edit this room or location",
+ "lang_matchingMachines": "Matching clients",
+ "lang_name": "Name",
+ "lang_parentLocation": "Parent location",
+ "lang_referencingLectures": "Assigned Lectures",
+ "lang_save": "Save",
+ "lang_startAddress": "Start address",
+ "lang_subnet": "IP range"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/locations.json b/modules-available/locations/lang/en/templates/locations.json
new file mode 100644
index 00000000..db4fd0a7
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/locations.json
@@ -0,0 +1,10 @@
+{
+ "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!",
+ "lang_edit": "Edit",
+ "lang_location": "Ort",
+ "lang_locationName": "Name",
+ "lang_locationsMainHeading": "Manage rooms and locations",
+ "lang_noParent": "No parent",
+ "lang_save": "Save",
+ "lang_thisListBySubnet": "List by subnet"
+}
\ No newline at end of file
diff --git a/modules-available/locations/lang/en/templates/subnets.json b/modules-available/locations/lang/en/templates/subnets.json
new file mode 100644
index 00000000..65da254b
--- /dev/null
+++ b/modules-available/locations/lang/en/templates/subnets.json
@@ -0,0 +1,7 @@
+{
+ "lang_endAddress": "End",
+ "lang_listOfSubnets": "List of subnets",
+ "lang_location": "Location",
+ "lang_startAddress": "Start",
+ "lang_thisListByLocation": "List by location"
+}
\ No newline at end of file
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
new file mode 100644
index 00000000..60af719b
--- /dev/null
+++ b/modules-available/locations/page.inc.php
@@ -0,0 +1,348 @@
+action = Request::post('action');
+ if ($this->action === 'updatelocation') {
+ $this->updateLocation();
+ } elseif ($this->action === 'addlocations') {
+ $this->addLocations();
+ }
+ }
+
+ private function addLocations()
+ {
+ $names = Request::post('newlocation', false);
+ $parents = Request::post('newparent', false);
+ if (!is_array($names) || !is_array($parents)) {
+ Message::addError('empty-field');
+ Util::redirect('?do=Locations');
+ }
+ $locs = Location::getLocations();
+ $count = 0;
+ foreach ($names as $idx => $name) {
+ $name = trim($name);
+ if (empty($name)) continue;
+ $parent = isset($parents[$idx]) ? (int)$parents[$idx] : 0;
+ if ($parent !== 0) {
+ $ok = false;
+ foreach ($locs as $loc) {
+ if ($loc['locationid'] == $parent) {
+ $ok = true;
+ }
+ }
+ if (!$ok) {
+ Message::addWarning('value-invalid', 'parentlocationid', $parent);
+ continue;
+ }
+ }
+ Database::exec("INSERT INTO location (parentlocationid, locationname)"
+ . " VALUES (:parent, :name)", array(
+ 'parent' => $parent,
+ 'name' => $name
+ ));
+ $count++;
+ }
+ Message::addSuccess('added-x-entries', $count);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocation()
+ {
+ $locationId = Request::post('locationid', false, 'integer');
+ $del = Request::post('deletelocation', false, 'integer');
+ if ($locationId === false) {
+ Message::addError('parameter-missing', 'locationid');
+ Util::redirect('?do=Locations');
+ }
+ $location = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location'
+ . ' WHERE locationid = :lid', array('lid' => $locationId));
+ if ($location === false) {
+ Message::addError('value-invalid', 'locationid', $locationId);
+ Util::redirect('?do=Locations');
+ }
+ // Delete location?
+ if ($locationId === $del) {
+ $this->deleteLocation($location);
+ }
+ // Update subnets
+ $this->updateLocationSubnets($location);
+ // Insert subnets
+ $this->addNewLocationSubnets($location); // TODO
+ // Update location!
+ $this->updateLocationData($location);
+ Util::redirect('?do=Locations');
+ }
+
+ private function deleteLocation($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $ids = $locationId;
+ if (Request::post('recursive', false) === 'on') {
+ $rows = Location::queryLocations();
+ $rows = Location::buildTree($rows, $locationId);
+ $rows = Location::extractIds($rows);
+ if (!empty($rows)) {
+ $ids .= ',' . implode(',', $rows);
+ }
+ }
+ $subs = Database::exec("DELETE FROM subnet WHERE locationid IN ($ids)");
+ $locs = Database::exec("DELETE FROM location WHERE locationid IN ($ids)");
+ Database::exec('UPDATE location SET parentlocationid = :newparent WHERE parentlocationid = :oldparent', array(
+ 'newparent' => $location['parentlocationid'],
+ 'oldparent' => $location['locationid']
+ ));
+ Message::addSuccess('location-deleted', $locs, $subs);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocationData($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $newParent = Request::post('parentlocationid', false, 'integer');
+ $newName = Request::post('locationname', false, 'string');
+ if ($newName === false || preg_match('/^\s*$/', $newName)) {
+ if ($newName !== false) {
+ Message::addWarning('value-invalid', 'location name', $newName);
+ }
+ $newName = $location['locationname'];
+ }
+ if ($newParent === false) {
+ $newParent = $location['parentlocationid'];
+ } else if ($newParent !== 0) {
+ $rows = Location::queryLocations();
+ $all = Location::extractIds(Location::buildTree($rows));
+ if (!in_array($newParent, $all) || $newParent === $locationId) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ } else {
+ $rows = Location::extractIds(Location::buildTree($rows, $locationId));
+ if (in_array($newParent, $rows)) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ }
+ }
+ }
+ $ret = Database::exec('UPDATE location SET parentlocationid = :parent, locationname = :name'
+ . ' WHERE locationid = :lid', array(
+ 'lid' => $locationId,
+ 'parent' => $newParent,
+ 'name' => $newName
+ ));
+ if ($ret > 0) {
+ Message::addSuccess('location-updated', $newName);
+ }
+ }
+
+ private function updateLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ // Deletion first
+ $dels = Request::post('deletesubnet', false);
+ if (is_array($dels)) {
+ $count = 0;
+ $stmt = Database::prepare('DELETE FROM subnet WHERE subnetid = :id');
+ foreach ($dels as $key => $value) {
+ if (!is_numeric($key) || $value !== 'on') continue;
+ if ($stmt->execute(array('id' => $key))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-deleted', $count);
+ }
+ }
+ // Now actual updates
+ // TODO: Warn on mismatch/overlap (should lie entirely in parent's subnet, not overlap with others)
+ $starts = Request::post('startaddr', false);
+ $ends = Request::post('endaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('UPDATE subnet SET startaddr = :start, endaddr = :end'
+ . ' WHERE subnetid = :id');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('id' => $key, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-updated', $count);
+ }
+ }
+
+ private function addNewLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $starts = Request::post('newstartaddr', false);
+ $ends = Request::post('newendaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('INSERT INTO subnet SET startaddr = :start, endaddr = :end, locationid = :location');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'new start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'new end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('location' => $locationId, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-created', $count);
+ }
+ }
+
+ /*
+ * Rendering normal pages
+ */
+
+ protected function doRender()
+ {
+ //Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ $getAction = Request::get('action');
+ if (empty($getAction)) {
+ // Until we have a main landing page?
+ Util::redirect('?do=Locations&action=showlocations');
+ }
+ if ($getAction === 'showsubnets') {
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr, locationid FROM subnet");
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $row['locations'] = Location::getLocations($row['locationid']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('subnets', array('list' => $rows));
+ } elseif ($getAction === 'showlocations') {
+ $locs = Location::getLocations();
+ Render::addTemplate('locations', array('list' => $locs));
+ }
+ }
+
+ /*
+ * Ajax
+ */
+
+ protected function doAjax()
+ {
+ User::load();
+ if (!User::isLoggedIn()) {
+ die('Unauthorized');
+ }
+ $action = Request::any('action');
+ if ($action === 'showlocation') {
+ $this->ajaxShowLocation();
+ }
+ }
+
+ private function ajaxShowLocation()
+ {
+ $locationId = Request::any('locationid', 0, 'integer');
+ $loc = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location WHERE locationid = :lid',
+ array('lid' => $locationId));
+ if ($loc === false) {
+ die('Unknown locationid');
+ }
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr FROM subnet WHERE locationid = :lid",
+ array('lid' => $locationId));
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $rows[] = $row;
+ }
+ $data = array(
+ 'locationid' => $loc['locationid'],
+ 'locationname' => $loc['locationname'],
+ 'list' => $rows,
+ 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
+ );
+ // if (moduleEnabled(DOZMOD) {
+ $lectures = Database::queryFirst('SELECT Count(*) AS cnt FROM sat.lecture l '
+ . ' INNER JOIN sat.lecture_x_location ll ON (l.lectureid = ll.lectureid AND ll.locationid = :lid)',
+ array('lid' => $locationId));
+ $data['lectures'] = $lectures['cnt'];
+ // }
+ // Get clients matching this location's subnet(s)
+ $mres = Database::simpleQuery("SELECT lastseen, logintime FROM machine"
+ . " INNER JOIN subnet ON (INET_ATON(machine.clientip) BETWEEN startaddr AND endaddr)"
+ . " WHERE subnet.locationid = :lid OR machine.locationid = :lid", array('lid' => $locationId));
+ $count = $online = $used = 0;
+ $DL = time() - 605;
+ while ($row = $mres->fetch(PDO::FETCH_ASSOC)) {
+ $count++;
+ if ($row['lastseen'] > $DL) {
+ $online++;
+ if ($row['logintime'] != 0) {
+ $used++;
+ }
+ }
+ }
+ $data['machines'] = $count;
+ $data['machines_online'] = $online;
+ $data['machines_used'] = $used;
+ $data['used_percent'] = round(100 * $used / $online);
+ echo Render::parse('location-subnets', $data);
+ }
+
+ /*
+ * Helpers
+ */
+
+ private function rangeToLong($start, $end)
+ {
+ $startLong = ip2long($start);
+ $endLong = ip2long($end);
+ if ($startLong !== false) {
+ $startLong = sprintf("%u", $startLong);
+ }
+ if ($endLong !== false) {
+ $endLong = sprintf("%u", $endLong);
+ }
+ return array($startLong, $endLong);
+ }
+
+}
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
new file mode 100644
index 00000000..76b7442a
--- /dev/null
+++ b/modules-available/locations/templates/location-subnets.html
@@ -0,0 +1,73 @@
+
\ No newline at end of file
diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html
new file mode 100644
index 00000000..76c8f97c
--- /dev/null
+++ b/modules-available/locations/templates/locations.html
@@ -0,0 +1,96 @@
+
diff --git a/modules-available/main/category-icons.json b/modules-available/main/category-icons.json
new file mode 100644
index 00000000..97b76eac
--- /dev/null
+++ b/modules-available/main/category-icons.json
@@ -0,0 +1,7 @@
+{
+ "cities":"tower",
+ "content":"th",
+ "settings":"cog",
+ "status":"tasks",
+ "users":"user"
+}
\ No newline at end of file
diff --git a/modules-available/main/config.json b/modules-available/main/config.json
new file mode 100644
index 00000000..4da67ef8
--- /dev/null
+++ b/modules-available/main/config.json
@@ -0,0 +1,3 @@
+{
+ "enabled":"true"
+}
diff --git a/modules-available/main/lang/de/module.json b/modules-available/main/lang/de/module.json
new file mode 100644
index 00000000..f03e52ad
--- /dev/null
+++ b/modules-available/main/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Dashboard",
+ "page_title": "Startseite"
+}
diff --git a/modules-available/main/lang/de/templates/dialog-generic.json b/modules-available/main/lang/de/templates/dialog-generic.json
new file mode 100644
index 00000000..ff429fdd
--- /dev/null
+++ b/modules-available/main/lang/de/templates/dialog-generic.json
@@ -0,0 +1,3 @@
+{
+ "lang_next": "Weiter"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/main-menu.json b/modules-available/main/lang/de/templates/main-menu.json
new file mode 100644
index 00000000..057bfcf2
--- /dev/null
+++ b/modules-available/main/lang/de/templates/main-menu.json
@@ -0,0 +1,22 @@
+{
+ "lang_configurationBasic": "PXE\/Boot",
+ "lang_configurationVariables": "KonfigurationsVariablen",
+ "lang_dozmod": "Dozentenmodul",
+ "lang_eventLog": "Server Log",
+ "lang_internetAccess": "Internetzugriff",
+ "lang_language": "Sprachen",
+ "lang_localization": "Lokalisierung + Integration",
+ "lang_locations": "R\u00e4ume\/Orte",
+ "lang_login": "Anmelden",
+ "lang_logout": "Abmelden",
+ "lang_needsSetup": "Einrichtung unvollst\u00e4ndig",
+ "lang_news": "vmChooser News",
+ "lang_server": "Server",
+ "lang_serverStatus": "Server Status",
+ "lang_settings": "Einstellungen",
+ "lang_status": "Status",
+ "lang_translations": "\u00dcbersetzungen",
+ "lang_vmLocation": "VM Speicherort",
+ "lang_warning": "Warnung",
+ "lang_webInterface": "Web-Schnittstelle"
+}
diff --git a/modules-available/main/lang/de/templates/messagebox-error.json b/modules-available/main/lang/de/templates/messagebox-error.json
new file mode 100644
index 00000000..c44dc44f
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-error.json
@@ -0,0 +1,3 @@
+[
+
+]
\ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/messagebox-info.json b/modules-available/main/lang/de/templates/messagebox-info.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-info.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/messagebox-success.json b/modules-available/main/lang/de/templates/messagebox-success.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-success.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/messagebox-warning.json b/modules-available/main/lang/de/templates/messagebox-warning.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/main/lang/de/templates/messagebox-warning.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/main/lang/de/templates/page-main-guest.json b/modules-available/main/lang/de/templates/page-main-guest.json
new file mode 100644
index 00000000..876fccac
--- /dev/null
+++ b/modules-available/main/lang/de/templates/page-main-guest.json
@@ -0,0 +1,7 @@
+{
+ "lang_introGuest": "Dies ist das Administrations-Interface der lokalen bwLehrpool-Installation. Bitte authentifizieren Sie sich, um Einstellungen vorzunehmen.",
+ "lang_login": "Anmelden",
+ "lang_noExistingAccount": "Es existiert noch kein Administrator-Zugang f\u00fcr diesen Satelliten-Server.",
+ "lang_register": "Registrieren",
+ "lang_welcome": "Willkommen"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/de/templates/page-main.json b/modules-available/main/lang/de/templates/page-main.json
new file mode 100644
index 00000000..a927e4ce
--- /dev/null
+++ b/modules-available/main/lang/de/templates/page-main.json
@@ -0,0 +1,11 @@
+{
+ "lang_bootMenuWarning": "Das Bootmen\u00fc ist veraltet oder wurde noch nicht generiert.",
+ "lang_configure": "Konfigurieren",
+ "lang_intro": "Dies ist die bwLehrpool Konfigurationsoberfl\u00e4che.",
+ "lang_minilinuxMissing": "Wichtige Dateien der MiniLinux-Installation fehlen.",
+ "lang_numerOfImagesMarkedForDeletion": "Zur L\u00f6schung markierte Abbilder",
+ "lang_systemConfiguration": "Systemkonfiguration",
+ "lang_systemConfigurationNotChosen": "Es wurde noch keine Systemkonfiguration ausgew\u00e4hlt.",
+ "lang_vmLocationNotSet": "Es ist noch kein Speicherort f\u00fcr die Virtuellen Maschinen festgelegt.",
+ "lang_welcome": "Willkommen"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/en/categories.json b/modules-available/main/lang/en/categories.json
new file mode 100644
index 00000000..3d67bcfd
--- /dev/null
+++ b/modules-available/main/lang/en/categories.json
@@ -0,0 +1,6 @@
+{
+ "settings": "Settings",
+ "status": "Status",
+ "content": "Content",
+ "users": "Users"
+}
diff --git a/modules-available/main/lang/en/module.json b/modules-available/main/lang/en/module.json
new file mode 100644
index 00000000..613213d9
--- /dev/null
+++ b/modules-available/main/lang/en/module.json
@@ -0,0 +1,13 @@
+{
+ "lang_intro": "Esta \u00e9 a interface de configura\u00e7\u00e3o do OpenSLX.",
+ "lang_introGuest": "This is the administration interface of the local bwLehrpool intallation. Please authenticate yourself to adjust settings.",
+ "lang_language": "Language",
+ "lang_login": "Login",
+ "lang_logout": "Logout",
+ "lang_needsSetup": "Setup incomplete",
+ "lang_next": "Next",
+ "lang_noExistingAccount": "No account has been created yet. Sign up to become the administrator.",
+ "lang_register": "Register",
+ "lang_warning": "Warning",
+ "lang_welcome": "Welcome"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/dialog-generic.json b/modules-available/main/lang/en/templates/dialog-generic.json
new file mode 100644
index 00000000..c7551ed3
--- /dev/null
+++ b/modules-available/main/lang/en/templates/dialog-generic.json
@@ -0,0 +1,3 @@
+{
+ "lang_next": "Next"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/main-menu.json b/modules-available/main/lang/en/templates/main-menu.json
new file mode 100644
index 00000000..635c7aa8
--- /dev/null
+++ b/modules-available/main/lang/en/templates/main-menu.json
@@ -0,0 +1,28 @@
+{
+ "lang_backup": "Backup\/Restore",
+ "lang_client": "Client",
+ "lang_clientLog": "Client Log",
+ "lang_clientStats": "Client statistics",
+ "lang_configurationBasic": "PXE\/Boot",
+ "lang_configurationVariables": "Configuration Variables",
+ "lang_dozmod": "Tutor module",
+ "lang_eventLog": "Server Log",
+ "lang_internetAccess": "Internet access",
+ "lang_language": "Language",
+ "lang_localization": "Localization",
+ "lang_locations": "Rooms\/Locations",
+ "lang_login": "Login",
+ "lang_logout": "Logout",
+ "lang_needsSetup": "Setup incomplete",
+ "lang_news": "vmChooser news",
+ "lang_server": "Server",
+ "lang_serverStatus": "Server status",
+ "lang_settings": "Settings",
+ "lang_status": "Status",
+ "lang_translations": "Translations",
+ "lang_vmLocation": "VM Location",
+ "lang_warning": "Warning",
+ "lang_webInterface": "Web interface",
+ "lang_loggedInPrefix": "Logged in as",
+ "lang_loggedInSuffix": " "
+}
diff --git a/modules-available/main/lang/en/templates/messagebox-warning.json b/modules-available/main/lang/en/templates/messagebox-warning.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/main/lang/en/templates/messagebox-warning.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/main/lang/en/templates/page-main-guest.json b/modules-available/main/lang/en/templates/page-main-guest.json
new file mode 100644
index 00000000..6526f5bd
--- /dev/null
+++ b/modules-available/main/lang/en/templates/page-main-guest.json
@@ -0,0 +1,7 @@
+{
+ "lang_introGuest": "This is the administration interface of the local bwLehrpool intallation. Please authenticate yourself to adjust settings.",
+ "lang_login": "Login",
+ "lang_noExistingAccount": "No account has been created yet. Sign up to become the administrator.",
+ "lang_register": "Register",
+ "lang_welcome": "Welcome"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/en/templates/page-main.json b/modules-available/main/lang/en/templates/page-main.json
new file mode 100644
index 00000000..8031ac1c
--- /dev/null
+++ b/modules-available/main/lang/en/templates/page-main.json
@@ -0,0 +1,11 @@
+{
+ "lang_bootMenuWarning": "The boot menu is outdated or has not been generated.",
+ "lang_configure": "Configure",
+ "lang_intro": "This is the bwLehrpool configuration interface.",
+ "lang_minilinuxMissing": "Important files from the mini Linux installation are missing.",
+ "lang_numerOfImagesMarkedForDeletion": "Images marked for deletion",
+ "lang_systemConfiguration": "System Configuration",
+ "lang_systemConfigurationNotChosen": "A system configuration has not been chosen yet.",
+ "lang_vmLocationNotSet": "A location for the virtual machine is not set yet.",
+ "lang_welcome": "Welcome"
+}
\ No newline at end of file
diff --git a/modules-available/main/lang/pt/module.json b/modules-available/main/lang/pt/module.json
new file mode 100644
index 00000000..e4e35acd
--- /dev/null
+++ b/modules-available/main/lang/pt/module.json
@@ -0,0 +1,14 @@
+{
+ "lang_intro": "Esta \u00e9 a interface de configura\u00e7\u00e3o do OpenSLX.",
+ "lang_introGuest": "Esta \u00e9 a interface de administra\u00e7\u00e3o da instala\u00e7\u00e3o local do bwLehrpool. Por favor, autentique-se para ajustar op\u00e7\u00f5es.",
+ "lang_language": "L\u00edngua",
+ "lang_login": "Entrar",
+ "lang_logout": "Sair",
+ "lang_needsSetup": "Instala\u00e7\u00e3o incompleta",
+ "lang_next": "Pr\u00f3ximo",
+ "lang_noExistingAccount": "Nenhuma conta foi criada ainda. Registre-se para se tornar administrador.",
+ "lang_register": "Registrar",
+ "lang_translations": "Tradu\u00e7\u00f5es",
+ "lang_warning": "Aten\u00e7\u00e3o",
+ "lang_welcome": "Bem-vindo"
+}
diff --git a/modules-available/main/page.inc.php b/modules-available/main/page.inc.php
new file mode 100644
index 00000000..369d4b54
--- /dev/null
+++ b/modules-available/main/page.inc.php
@@ -0,0 +1,64 @@
+sysconfig = !file_exists(CONFIG_HTTP_DIR . '/default/config.tgz');
+ $this->minilinux = !file_exists(CONFIG_HTTP_DIR . '/default/kernel') || !file_exists(CONFIG_HTTP_DIR . '/default/initramfs-stage31') || !file_exists(CONFIG_HTTP_DIR . '/default/stage32.sqfs');
+ $this->vmstore = !is_array(Property::getVmStoreConfig());
+ $this->ipxe = !preg_match('/^\d+\.\d+\.\d+\.\d+$/', Property::getServerIp());
+ Property::setNeedsSetup(($this->sysconfig || $this->minilinux || $this->vmstore || $this->ipxe) ? 1 : 0);
+ $res = Database::queryFirst("SELECT Count(*) AS cnt FROM sat.imageversion WHERE deletestate = 'SHOULD_DELETE'", array(), true);
+ $this->delPending = isset($res['cnt']) ? $res['cnt'] : 0;
+ }
+ }
+
+ protected function doRender()
+ {
+ // Render::setTitle('abc');
+
+ if (!User::isLoggedIn()) {
+ Render::addTemplate('page-main-guest', array(
+ 'register' => (Database::queryFirst('SELECT userid FROM user LIMIT 1') === false)
+ ));
+ return;
+ }
+ // Logged in here
+
+ // Load news
+ $lines = array();
+ $paginate = new Paginate("SELECT newsid, dateline, title, content FROM news ORDER BY dateline DESC", 10);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if(count($lines) >= 3) break;
+ $lines[] = $row;
+ }
+
+ Render::addTemplate('page-main', array(
+ 'user' => User::getName(),
+ 'sysconfig' => $this->sysconfig,
+ 'minilinux' => $this->minilinux,
+ 'vmstore' => $this->vmstore,
+ 'ipxe' => $this->ipxe,
+ 'delpending' => $this->delPending,
+ 'news' => $lines
+ ));
+ }
+
+ protected function doAjax()
+ {
+ User::isLoggedIn();
+ die('Status: DB running');
+ }
+
+}
diff --git a/modules-available/main/templates/dialog-generic.html b/modules-available/main/templates/dialog-generic.html
new file mode 100644
index 00000000..5face8ce
--- /dev/null
+++ b/modules-available/main/templates/dialog-generic.html
@@ -0,0 +1,13 @@
+
+
+
+
{{title}}
+
+
+ {{{body}}}
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/main/templates/footer.html b/modules-available/main/templates/footer.html
new file mode 100644
index 00000000..8cf71a5c
--- /dev/null
+++ b/modules-available/main/templates/footer.html
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/modules-available/main/templates/main-menu.html b/modules-available/main/templates/main-menu.html
new file mode 100644
index 00000000..65085ee6
--- /dev/null
+++ b/modules-available/main/templates/main-menu.html
@@ -0,0 +1,72 @@
+
diff --git a/modules-available/main/templates/page-minilinux.html b/modules-available/main/templates/page-minilinux.html
new file mode 100644
index 00000000..dc13e6b0
--- /dev/null
+++ b/modules-available/main/templates/page-minilinux.html
@@ -0,0 +1,14 @@
+
+
{{lang_listObtained}}
+
+
+
\ No newline at end of file
diff --git a/modules-available/main/templates/page-news.html b/modules-available/main/templates/page-news.html
new file mode 100644
index 00000000..8e400498
--- /dev/null
+++ b/modules-available/main/templates/page-news.html
@@ -0,0 +1,57 @@
+
+
+ {{lang_editNews}}
+
+
+
{{lang_newsIntro}}
+
+
+
+
+
+
+ {{lang_newsOld}}
+
+
+
+
+
+
+
diff --git a/modules-available/main/templates/page-syslog.html b/modules-available/main/templates/page-syslog.html
new file mode 100644
index 00000000..98e94291
--- /dev/null
+++ b/modules-available/main/templates/page-syslog.html
@@ -0,0 +1,58 @@
+
+
\ No newline at end of file
diff --git a/modules-available/main/templates/tm-callback-trigger.html b/modules-available/main/templates/tm-callback-trigger.html
new file mode 100644
index 00000000..cd03a1fe
--- /dev/null
+++ b/modules-available/main/templates/tm-callback-trigger.html
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/modules-available/minilinux/config.json b/modules-available/minilinux/config.json
new file mode 100644
index 00000000..f2abe27c
--- /dev/null
+++ b/modules-available/minilinux/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings",
+ "enabled":"true"
+}
diff --git a/modules-available/minilinux/lang/de/templates/download.json b/modules-available/minilinux/lang/de/templates/download.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/download.json
@@ -0,0 +1 @@
+{}
diff --git a/modules-available/minilinux/lang/de/templates/filelist.json b/modules-available/minilinux/lang/de/templates/filelist.json
new file mode 100644
index 00000000..214d758a
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/filelist.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Aktuell",
+ "lang_canUpdate1": "Mindestens eine Komponente von",
+ "lang_canUpdate2": "kann aktualisiert werden. F\u00fcr einen reibungslosen Betrieb wird empfohlen, alle Komponenten auf dem aktuellen Stand zu halten.",
+ "lang_configurationPackageNotFound": "Keine Konfigurationspakete gefunden!",
+ "lang_desiredVersion": "Gew\u00fcnschte Version",
+ "lang_filesInVersion": "Dateien zu Version",
+ "lang_outdated": "Veraltet",
+ "lang_redownload": "Erneut herunterladen",
+ "lang_systemUpdated": "Das System ist auf dem aktuellen Stand.",
+ "lang_update": "Aktualisieren"
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/templates/page-minilinux.json b/modules-available/minilinux/lang/de/templates/page-minilinux.json
new file mode 100644
index 00000000..f30f7b76
--- /dev/null
+++ b/modules-available/minilinux/lang/de/templates/page-minilinux.json
@@ -0,0 +1,4 @@
+{
+ "lang_errorGetting": "Fehler beim Herunterladen der Liste!",
+ "lang_listObtained": "Liste wird heruntergeladen..."
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/module.json b/modules-available/minilinux/lang/en/module.json
new file mode 100644
index 00000000..b9262b5a
--- /dev/null
+++ b/modules-available/minilinux/lang/en/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Actual",
+ "lang_canUpdate1": "At least one component of",
+ "lang_canUpdate2": "can be updated. For a smooth operation, it is recommended to keep all components up to date.",
+ "lang_configurationPackageNotFound": "Configuration package not found!",
+ "lang_errorGetting": "Error while downloading list!",
+ "lang_listObtained": "Downloading list...",
+ "lang_outdated": "Outdated",
+ "lang_systemUpdated": "The system is up to date.",
+ "lang_update": "Update",
+ "module_name": "Minilinux"
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/templates/download.json b/modules-available/minilinux/lang/en/templates/download.json
new file mode 100644
index 00000000..2c63c085
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/download.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/modules-available/minilinux/lang/en/templates/filelist.json b/modules-available/minilinux/lang/en/templates/filelist.json
new file mode 100644
index 00000000..89fed42b
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/filelist.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Actual",
+ "lang_canUpdate1": "At least one component of",
+ "lang_canUpdate2": "can be updated. For a smooth operation, it is recommended to keep all components up to date.",
+ "lang_configurationPackageNotFound": "Configuration package not found!",
+ "lang_desiredVersion": "Desired version",
+ "lang_filesInVersion": "Files for version",
+ "lang_outdated": "Outdated",
+ "lang_redownload": "Download again",
+ "lang_systemUpdated": "The system is up to date.",
+ "lang_update": "Update"
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/lang/en/templates/page-minilinux.json b/modules-available/minilinux/lang/en/templates/page-minilinux.json
new file mode 100644
index 00000000..2f59fa57
--- /dev/null
+++ b/modules-available/minilinux/lang/en/templates/page-minilinux.json
@@ -0,0 +1,4 @@
+{
+ "lang_errorGetting": "Error while downloading list!",
+ "lang_listObtained": "Downloading list..."
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/lang/pt/module.json b/modules-available/minilinux/lang/pt/module.json
new file mode 100644
index 00000000..0ce7629d
--- /dev/null
+++ b/modules-available/minilinux/lang/pt/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_actual": "Atual",
+ "lang_canUpdate1": "Pelo menos um componente de",
+ "lang_canUpdate2": "pode ser atualizado. Para um bom funcionamento, recomenda-se manter todos os componentes atualizados.",
+ "lang_configurationPackageNotFound": "Pacote de configura\u00e7\u00e3o n\u00e3o encontrado!",
+ "lang_errorGetting": "Erro ao baixar a lista!",
+ "lang_listObtained": "Carregando lista...",
+ "lang_outdated": "Desatualizado",
+ "lang_systemUpdated": "O sistema est\u00e1 atualizado.",
+ "lang_update": "Atualizar",
+ "module_name": "Minilinux"
+}
\ No newline at end of file
diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php
new file mode 100644
index 00000000..91be456e
--- /dev/null
+++ b/modules-available/minilinux/page.inc.php
@@ -0,0 +1,128 @@
+ '?do=MiniLinux&async=true&action=list'
+ ));
+ Render::addFooter('');
+ }
+
+ protected function doAjax()
+ {
+ $data = Property::getVersionCheckInformation();
+ if (!is_array($data) || !isset($data['systems'])) {
+ echo Render::parse('messagebox-error', array(
+ 'message' => 'Failed to retrieve the list: ' . print_r($data, true)
+ ),'main');
+ return;
+ }
+ $action = Request::any('action');
+ $selectedVersion = (int)Request::any('version', 0);
+ switch ($action) {
+ case 'list':
+ $count = 0;
+ foreach ($data['systems'] as &$system) {
+ // Get latest version, build simple array of all version numbers
+ $versionNumbers = array();
+ $selected = false;
+ foreach ($system['versions'] as $version) {
+ if (!is_numeric($version['version']) || $version['version'] < 1)
+ continue;
+ if ($selectedVersion === 0 && ($selected === false || $selected['version'] < $version['version']))
+ $selected = $version;
+ elseif ($version['version'] == $selectedVersion)
+ $selected = $version;
+ $versionNumbers[(int)$version['version']] = array(
+ 'version' => $version['version']
+ );
+ }
+ if ($selected === false) continue; // No versions for this system!?
+ ksort($versionNumbers);
+ // Mark latest version as selected
+ $versionNumbers[(int)$selected['version']]['selected'] = true;
+ // Add status information to system and its files
+ foreach ($selected['files'] as &$file) {
+ $file['uid'] = 'dlid' . $count++;
+ $local = CONFIG_HTTP_DIR . '/' . $system['id'] . '/' . $file['name'];
+ if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime']) {
+ $file['fileChanged'] = true;
+ $system['systemChanged'] = true;
+ }
+ $taskId = Property::getDownloadTask($file['md5']);
+ if ($taskId !== false) {
+ $task = Taskmanager::status($taskId);
+ if (isset($task['data']['progress'])) {
+ $file['download'] = Render::parse('download', array(
+ 'task' => $taskId,
+ 'name' => $file['name']
+ ));
+ }
+ }
+ }
+ unset($system['versions']);
+ $system['files'] = $selected['files'];
+ $system['version'] = $selected['version'];
+ }
+ $data['versions'] = array_values($versionNumbers);
+ 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 ($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;
+ }
+ }
+
+}
diff --git a/modules-available/minilinux/templates/download.html b/modules-available/minilinux/templates/download.html
new file mode 100644
index 00000000..2e32df5a
--- /dev/null
+++ b/modules-available/minilinux/templates/download.html
@@ -0,0 +1 @@
+
{{name}}
\ No newline at end of file
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
new file mode 100644
index 00000000..ca94f4d0
--- /dev/null
+++ b/modules-available/minilinux/templates/filelist.html
@@ -0,0 +1,77 @@
+ {{#systems}}
+
+
+
\ No newline at end of file
diff --git a/modules-available/news/config.json b/modules-available/news/config.json
new file mode 100644
index 00000000..6c189b63
--- /dev/null
+++ b/modules-available/news/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "enabled":"true"
+}
diff --git a/modules-available/news/lang/de/templates/page-news.json b/modules-available/news/lang/de/templates/page-news.json
new file mode 100644
index 00000000..7801584d
--- /dev/null
+++ b/modules-available/news/lang/de/templates/page-news.json
@@ -0,0 +1,13 @@
+{
+ "lang_activeNews": "Aktive News",
+ "lang_content": "Inhalt",
+ "lang_date": "Datum",
+ "lang_delete": "L\u00f6schen",
+ "lang_editNews": "News bearbeiten",
+ "lang_latestUpdate": "Letzte Aktualisierung",
+ "lang_newsIntro": "Hier haben Sie die M\u00f6glichkeit, die von bwLehrpool-Clients angezeigten News zu editieren.",
+ "lang_newsOld": "Alte News",
+ "lang_save": "Speichern",
+ "lang_show": "Ansehen",
+ "lang_title": "Titel"
+}
\ No newline at end of file
diff --git a/modules-available/news/lang/en/module.json b/modules-available/news/lang/en/module.json
new file mode 100644
index 00000000..5acbe58d
--- /dev/null
+++ b/modules-available/news/lang/en/module.json
@@ -0,0 +1,12 @@
+{
+ "lang_activeNews": "News",
+ "lang_content": "Content",
+ "lang_date": "Date",
+ "lang_delete": "Delete",
+ "lang_latestUpdate": "Last Update",
+ "lang_newsOld": "Old News",
+ "lang_save": "Save",
+ "lang_show": "Show",
+ "lang_title": "Title",
+ "module_name": "News"
+}
\ No newline at end of file
diff --git a/modules-available/news/lang/en/templates/page-news.json b/modules-available/news/lang/en/templates/page-news.json
new file mode 100644
index 00000000..df63658c
--- /dev/null
+++ b/modules-available/news/lang/en/templates/page-news.json
@@ -0,0 +1,12 @@
+{
+ "lang_content": "Content",
+ "lang_date": "Date",
+ "lang_delete": "Delete",
+ "lang_editNews": "Edit news",
+ "lang_latestUpdate": "Latest update",
+ "lang_newsIntro": "Here you have the possibility to edit the news displayed to the bwLehrpool clients.",
+ "lang_newsOld": "Old News",
+ "lang_save": "Save",
+ "lang_show": "Show",
+ "lang_title": "Title"
+}
\ No newline at end of file
diff --git a/modules-available/news/lang/pt/module.json b/modules-available/news/lang/pt/module.json
new file mode 100644
index 00000000..e1997fce
--- /dev/null
+++ b/modules-available/news/lang/pt/module.json
@@ -0,0 +1,13 @@
+{
+ "lang_activeNews": "Novidades",
+ "lang_content": "Conte\u00fado",
+ "lang_date": "Data",
+ "lang_delete": "Excluir",
+ "lang_latestUpdate": "\u00daltima Atualiza\u00e7\u00e3o",
+ "lang_newsIntro": "Aqui voc\u00ea tem a possibilidade de editar as novidades que s\u00e3o mostradas para os clientes do bwLehrpool.",
+ "lang_newsOld": "Antigas",
+ "lang_save": "Salvar",
+ "lang_show": "Mostrar",
+ "lang_title": "T\u00edtulo",
+ "module_name": "Novidades"
+}
\ No newline at end of file
diff --git a/modules-available/news/page.inc.php b/modules-available/news/page.inc.php
new file mode 100644
index 00000000..9bbadc4f
--- /dev/null
+++ b/modules-available/news/page.inc.php
@@ -0,0 +1,167 @@
+newsId = false;
+ $this->newsTitle = false;
+ $this->newsContent = false;
+ $this->newsDate = false;
+ } elseif ($action === 'show') {
+ // show news
+ if (!$this->loadNews(Request::any('newsid'))) {
+ Message::addError('news-empty');
+ }
+ } elseif ($action === 'save') {
+ // save to DB
+ if (!$this->saveNews()) {
+ // re-set the fields we got
+ Request::post('news-title') ? $this->newsTitle = Request::post('news-title') : $this->newsTitle = false;
+ Request::post('news-content') ? $this->newsContent = Request::post('news-content') : $this->newsContent = false;
+ } else {
+ Message::addSuccess('news-save-success');
+ Util::redirect('?do=News');
+ }
+ } elseif ($action === 'delete') {
+ // delete it
+ $this->delNews(Request::post('newsid'));
+ } else {
+ // unknown action, redirect user
+ Message::addError('invalid-action', $action);
+ Util::redirect('?do=News');
+ }
+ }
+
+ /**
+ * Implementation of the abstract doRender function
+ *
+ * Fetch the list of news from the database and paginate it.
+ *
+ */
+ protected function doRender()
+ {
+ // fetch the list of the older news
+ $lines = array();
+ $paginate = new Paginate("SELECT newsid, dateline, title, content FROM news ORDER BY dateline DESC", 10);
+ $res = $paginate->exec();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['date'] = date('d.m.Y H:i', $row['dateline']);
+
+ if ($row['newsid'] == $this->newsId) $row['active'] = "active";
+ $lines[] = $row;
+ }
+ $paginate->render('page-news', array(
+ 'token' => Session::get('token'),
+ 'latestDate' => ($this->newsDate ? date('d.m.Y H:i', $this->newsDate) : '--'),
+ 'latestContent' => $this->newsContent,
+ 'latestTitle' => $this->newsTitle,
+ 'list' => $lines ));
+
+ }
+ /**
+ * Loads the news with the given ID into the form.
+ *
+ * @param int $newsId ID of the news to be shown.
+ * @return boolean true if loading that news worked
+ *
+ */
+ private function loadNews($newsId)
+ {
+ // check to see if we need to request a specific newsid
+ if ($newsId !== false) {
+ $row = Database::queryFirst("SELECT newsid, title, content, dateline FROM news WHERE newsid = :newsid LIMIT 1", array(
+ 'newsid' => $newsId
+ ));
+ } else {
+ $row = Database::queryFirst("SELECT newsid, title, content, dateline FROM news ORDER BY dateline DESC LIMIT 1");
+ }
+
+ // fetch the news to be shown
+ if ($row !== false) {
+ $this->newsId = $row['newsid'];
+ $this->newsTitle = $row['title'];
+ $this->newsContent = $row['content'];
+ $this->newsDate = $row['dateline'];
+ }
+ return $row !== false;
+ }
+
+ /**
+ * Save the given $newsTitle and $newsContent as POST'ed into the database.
+ *
+ */
+ private function saveNews()
+ {
+ // check if news content were set by the user
+ $newsTitle = Request::post('news-title');
+ $newsContent = Request::post('news-content');
+ if ($newsContent !== '' && $newsTitle !== '') {
+ // we got title and content, save it to DB
+ Database::exec("INSERT INTO news (dateline, title, content) VALUES (:dateline, :title, :content)", array(
+ 'dateline' => time(),
+ 'title' => $newsTitle,
+ 'content' => $newsContent
+ ));
+ return true;
+ } else {
+ Message::addError('empty-field');
+ return false;
+ }
+ }
+
+ /**
+ * Delete the news entry with ID $newsId
+ *
+ * @param int $newsId ID of the entry to be deleted.
+ */
+ private function delNews($newsId)
+ {
+ // sanity check: is newsId even numeric?
+ if (!is_numeric($newsId)) {
+ Message::addError('value-invalid', 'newsid', $newsId);
+ } else {
+ // check passed - do delete
+ Database::exec("DELETE FROM news WHERE newsid = :newsid LIMIT 1", array(
+ 'newsid' => $newsId
+ ));
+ Message::addSuccess('news-del-success');
+ }
+ Util::redirect('?do=News');
+ }
+
+}
\ No newline at end of file
diff --git a/modules-available/news/templates/page-news.html b/modules-available/news/templates/page-news.html
new file mode 100644
index 00000000..8e400498
--- /dev/null
+++ b/modules-available/news/templates/page-news.html
@@ -0,0 +1,57 @@
+
+
+ {{lang_editNews}}
+
+
+
{{lang_newsIntro}}
+
+
+
+
+
+
+ {{lang_newsOld}}
+
+
+
+
+
+
+
diff --git a/modules-available/serversetup/config.json b/modules-available/serversetup/config.json
new file mode 100644
index 00000000..f2abe27c
--- /dev/null
+++ b/modules-available/serversetup/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings",
+ "enabled":"true"
+}
diff --git a/modules-available/serversetup/lang/de/templates/ipaddress.json b/modules-available/serversetup/lang/de/templates/ipaddress.json
new file mode 100644
index 00000000..eb3d34d3
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipaddress.json
@@ -0,0 +1,7 @@
+{
+ "lang_active": "Aktiv",
+ "lang_bootAddress": "Boot-Adresse des Servers",
+ "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_chooseIP": "Bitte w\u00e4hlen Sie die IP-Adresse, \u00fcber die der Server von den Clients zum Booten angesprochen werden soll.",
+ "lang_set": "Setzen"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/de/templates/ipxe.json b/modules-available/serversetup/lang/de/templates/ipxe.json
new file mode 100644
index 00000000..edce0b5e
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipxe.json
@@ -0,0 +1,19 @@
+{
+ "lang_bootBehavior": "Standard-Bootverhalten",
+ "lang_bootInfo": "Hier k\u00f6nnen Anpassungen am Erscheinungsbild des Bootmen\u00fcs vorgenommen werden.",
+ "lang_bootMenu": "Bootmen\u00fc",
+ "lang_bootMenuCreate": "Bootmen\u00fc erzeugen",
+ "lang_close": "Schlie\u00dfen",
+ "lang_compile": "Kompilieren",
+ "lang_customEntry": "Eigener Eintrag",
+ "lang_example": "Beispiel",
+ "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_seconds": "Sekunden"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/de/templates/ipxe_update.json b/modules-available/serversetup/lang/de/templates/ipxe_update.json
new file mode 100644
index 00000000..b984de19
--- /dev/null
+++ b/modules-available/serversetup/lang/de/templates/ipxe_update.json
@@ -0,0 +1,4 @@
+{
+ "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 obenstehende Fehlermeldung an das bwLehrpool-Projekt.",
+ "lang_menuGeneration": "Erzeugen des Bootmen\u00fcs"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/module.json b/modules-available/serversetup/lang/en/module.json
new file mode 100644
index 00000000..c7c06098
--- /dev/null
+++ b/modules-available/serversetup/lang/en/module.json
@@ -0,0 +1,34 @@
+{
+ "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_cancel": "Cancel",
+ "lang_chooseIP": "Please select the IP address that the client server will use to boot.",
+ "lang_close": "Close",
+ "lang_compile": "Compile",
+ "lang_compilingIpxe": "Compiling iPXE",
+ "lang_customScript": "Custom Script",
+ "lang_download": "Download",
+ "lang_example": "Example",
+ "lang_extension": "Extension",
+ "lang_ipxeInfo": "Here it is possible to compile and download iPXE for USB using a custom script.",
+ "lang_ipxeWarning": "If this is your first time compiling, it may take 1 to 4 minutes to finish.",
+ "lang_loading": "Loading",
+ "lang_localHDD": "Local HDD",
+ "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 my-entry as well.",
+ "lang_menuDisplayTime": "Menu Display Time",
+ "lang_mountIpxe": "Mount iPXE",
+ "lang_restoreDefault": "Restore Default",
+ "lang_saveScript": "Save Script",
+ "lang_seconds": "Seconds",
+ "lang_set": "Set",
+ "lang_success": "Successfully create file:",
+ "module_name": "iPXE \/ Boot Menu"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/templates/ipaddress.json b/modules-available/serversetup/lang/en/templates/ipaddress.json
new file mode 100644
index 00000000..699aeacc
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipaddress.json
@@ -0,0 +1,7 @@
+{
+ "lang_active": "Active",
+ "lang_bootAddress": "Boot Address of the Server",
+ "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_chooseIP": "Please select the IP address that the client server will use to boot.",
+ "lang_set": "Set"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/en/templates/ipxe.json b/modules-available/serversetup/lang/en/templates/ipxe.json
new file mode 100644
index 00000000..70c17f43
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipxe.json
@@ -0,0 +1,31 @@
+{
+ "lang_bootBehavior": "Default Boot Behavior",
+ "lang_bootInfo": "Here adjustments can be made to the appearance of the boot menu.",
+ "lang_bootMenu": "Boot Menu",
+ "lang_bootMenuCreate": "Create Boot Menu",
+ "lang_cancel": "Cancel",
+ "lang_close": "Close",
+ "lang_compile": "Compile",
+ "lang_compilingIpxe": "Compiling iPXE",
+ "lang_customEntry": "Custom entry",
+ "lang_customScript": "Custom script",
+ "lang_download": "Download",
+ "lang_example": "Example",
+ "lang_extension": "Extension",
+ "lang_ipxeInfo": "Here it is possible to compile iPXE using a custom script.",
+ "lang_ipxeWarning": "If this is your first time compiling, it may take 1 to 4 minutes to finish.",
+ "lang_loading": "Loading",
+ "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_mountIpxe": "Mount iPXE",
+ "lang_restoreDefault": "Restore Default",
+ "lang_saveScript": "Save Script",
+ "lang_seconds": "Seconds",
+ "lang_success": "Successfully create file:"
+}
diff --git a/modules-available/serversetup/lang/en/templates/ipxe_update.json b/modules-available/serversetup/lang/en/templates/ipxe_update.json
new file mode 100644
index 00000000..b33b12dd
--- /dev/null
+++ b/modules-available/serversetup/lang/en/templates/ipxe_update.json
@@ -0,0 +1,4 @@
+{
+ "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 message above to the bwLehrpool project.",
+ "lang_menuGeneration": "Generating boot menu..."
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/pt/module.json b/modules-available/serversetup/lang/pt/module.json
new file mode 100644
index 00000000..e0e5a2b6
--- /dev/null
+++ b/modules-available/serversetup/lang/pt/module.json
@@ -0,0 +1,40 @@
+{
+ "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_cancel": "Cancelar",
+ "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:",
+ "module_name": "iPXE \/ Boot Menu"
+}
\ No newline at end of file
diff --git a/modules-available/serversetup/lang/pt/templates/ipxe-adv.json b/modules-available/serversetup/lang/pt/templates/ipxe-adv.json
new file mode 100644
index 00000000..19d120ad
--- /dev/null
+++ b/modules-available/serversetup/lang/pt/templates/ipxe-adv.json
@@ -0,0 +1,28 @@
+{
+ "lang_bootBehavior": "Comportamento Padr\u00e3o 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_cancel": "Cancelar",
+ "lang_close": "Fechar",
+ "lang_compile": "Compilar",
+ "lang_compilingIpxe": "Compilando iPXE",
+ "lang_customScript": "Script Customizado",
+ "lang_download": "Baixar",
+ "lang_example": "Exemplo",
+ "lang_extension": "Extens\u00e3o",
+ "lang_ipxeInfo": "Aqui \u00e9 poss\u00edvel compilar e baixar o iPXE utilizando um script customiz\u00e1vel.",
+ "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_success": "Arquivo criado com sucesso:"
+}
diff --git a/modules-available/serversetup/page.inc.php b/modules-available/serversetup/page.inc.php
new file mode 100644
index 00000000..289bf3d5
--- /dev/null
+++ b/modules-available/serversetup/page.inc.php
@@ -0,0 +1,194 @@
+currentMenu = Property::getBootMenu();
+
+ if(Request::get('download') !== false){
+ $this->downloadIpxe(Request::get('download'));
+ }
+
+ if(Request::get('defaultIpxe') !== false){
+ $this->defaultIpxe(Request::get('defaultIpxe'));
+ }
+
+ $action = Request::post('action');
+
+ if ($action === false) {
+ $this->currentAddress = Property::getServerIp();
+ $this->getLocalAddresses();
+ }
+
+ if ($action === 'ip') {
+ // New address is to be set
+ $this->getLocalAddresses();
+ $this->updateLocalAddress();
+ }
+
+ if ($action === 'ipxe') {
+ // iPXE stuff changes
+ $this->updatePxeMenu();
+ }
+
+ if($action === 'save-script') {
+ // Save new iPXE script
+ $this->updateIpxeScript();
+ }
+
+ if($action === 'default-script') {
+ // Restore iPXE script to default
+ $this->defaultIpxe();
+ }
+ }
+
+ protected function doRender()
+ {
+ Render::setTitle(Dictionary::translate('lang_serverConfiguration'));
+
+ $taskid = Request::any('taskid');
+ if ($taskid !== false && Taskmanager::isTask($taskid)) {
+ Render::addTemplate('ipxe_update', array('taskid' => $taskid));
+ }
+
+ if (Request::get('advanced', 'false', 'string') === 'false') {
+ Render::addTemplate('ipxe-smp');
+ } else {
+ Render::addTemplate('ipaddress', array(
+ 'ips' => $this->taskStatus['data']['addresses']
+ ));
+ $data = $this->currentMenu;
+ 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';
+ //There is no $this->username and no pxe.embed, why do we need this?
+ //Page won't load with lines below uncommented
+ //$data['username'] = $this->username;
+ //$data['script'] = file_get_contents("/opt/taskmanager/data/pxe.embed");
+ Render::addTemplate('ipxe-adv', $data);
+ }
+ }
+
+ // -----------------------------------------------------------------------------------------------
+
+ private function getLocalAddresses()
+ {
+ $this->taskStatus = Taskmanager::submit('LocalAddressesList', array());
+
+ if ($this->taskStatus === false) {
+ $this->taskStatus['data']['addresses'] = false;
+ return false;
+ }
+
+ if ($this->taskStatus['statusCode'] === TASK_WAITING) { // TODO: Async if just displaying
+ $this->taskStatus = Taskmanager::waitComplete($this->taskStatus['id']);
+ }
+
+ $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;
+ }
+ $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);
+ global $tidIpxe;
+ if (isset($tidIpxe) && $tidIpxe !== false)
+ Util::redirect('?do=ServerSetup&taskid=' . $tidIpxe);
+ } 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('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);
+ $id = Trigger::ipxe();
+ Util::redirect('?do=ServerSetup&taskid=' . $id);
+ }
+
+ private function downloadIpxe($ipxe){
+ $file = '/opt/taskmanager/data/ipxe/src/bin/ipxe.' . $ipxe;
+ if (file_exists($file)) {
+ header('Content-Description: File Transfer');
+ header('Content-Type: application/octet-stream');
+ header('Content-Disposition: attachment; filename='.basename($file));
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate');
+ header('Pragma: public');
+ header('Content-Length: ' . filesize($file));
+ ob_clean();
+ flush();
+ readfile($file);
+ exit();
+ }
+ }
+
+ private function updateIpxeScript(){
+ $newScript = Request::post('custom-script');
+ file_put_contents("/opt/taskmanager/data/pxe.embed",$newScript);
+ Util::redirect('?do=ServerSetup');
+ }
+
+ private function defaultIpxe(){
+ $default = file_get_contents("/opt/taskmanager/data/pxe_default.embed");
+ $default = str_replace("{{ip}}", "http://" . Property::getServerIp(), $default);
+ file_put_contents("/opt/taskmanager/data/pxe.embed",$default);
+ Util::redirect('?do=ServerSetup');
+ }
+}
diff --git a/modules-available/serversetup/templates/ipaddress.html b/modules-available/serversetup/templates/ipaddress.html
new file mode 100644
index 00000000..e4c1fba9
--- /dev/null
+++ b/modules-available/serversetup/templates/ipaddress.html
@@ -0,0 +1,35 @@
+{{lang_ipxeSmp}}
+
+
+ {{lang_bootAddress}}
+
+
+
+ {{lang_chooseIP}}
+
+
+
+
diff --git a/modules-available/serversetup/templates/ipxe-adv.html b/modules-available/serversetup/templates/ipxe-adv.html
new file mode 100644
index 00000000..00e9fd3a
--- /dev/null
+++ b/modules-available/serversetup/templates/ipxe-adv.html
@@ -0,0 +1,149 @@
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html
new file mode 100644
index 00000000..107a34f7
--- /dev/null
+++ b/modules-available/statistics/templates/kvmstate.html
@@ -0,0 +1,47 @@
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-hdds.html b/modules-available/statistics/templates/machine-hdds.html
new file mode 100644
index 00000000..fd6cf1be
--- /dev/null
+++ b/modules-available/statistics/templates/machine-hdds.html
@@ -0,0 +1,67 @@
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html
new file mode 100644
index 00000000..8071416a
--- /dev/null
+++ b/modules-available/statistics/templates/machine-main.html
@@ -0,0 +1,124 @@
+
diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html
new file mode 100644
index 00000000..c4f97543
--- /dev/null
+++ b/modules-available/statistics/templates/machine-notes.html
@@ -0,0 +1,17 @@
+
+
{{lang_notes}}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/machine-usage.html b/modules-available/statistics/templates/machine-usage.html
new file mode 100644
index 00000000..ffaa747b
--- /dev/null
+++ b/modules-available/statistics/templates/machine-usage.html
@@ -0,0 +1,51 @@
+
+
+
+
+ {{lang_usageDetails}}
+
+
+
+
+
+
+
Type
+
When
+
Length
+
+ {{#rows}}
+
+
+
{{from}}
+
{{duration}}
+
+ {{/rows}}
+
+
+
+
+ {{#hasrows2}}
+
+
Type
+
When
+
Length
+
+ {{/hasrows2}}
+ {{#rows2}}
+
+
+
{{from}}
+
{{duration}}
+
+ {{/rows2}}
+
+
+
+
{{{graph}}}
+
+ {{lang_timebarDesc}}
+
+
+
+
+
diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html
new file mode 100644
index 00000000..f4d2ad24
--- /dev/null
+++ b/modules-available/statistics/templates/memory.html
@@ -0,0 +1,47 @@
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/newclients.html b/modules-available/statistics/templates/newclients.html
new file mode 100644
index 00000000..0d9c74df
--- /dev/null
+++ b/modules-available/statistics/templates/newclients.html
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html
new file mode 100644
index 00000000..5f16fd89
--- /dev/null
+++ b/modules-available/statistics/templates/summary.html
@@ -0,0 +1,33 @@
+