summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorJannik Schönartz2017-12-14 13:03:44 +0100
committerJannik Schönartz2017-12-14 13:03:44 +0100
commit5d5c2f27bee5d4fbd3747555efbf2ac9f337805b (patch)
treec65898e1b3d6f0f46366a280bbbaf4c6ccbc477c /modules-available
parent[usb-lock-off] Design changes to fit the design_guidelines. TODO: lang_discar... (diff)
parent[sysconfig] Update translations (diff)
downloadslx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.tar.gz
slx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.tar.xz
slx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.zip
Merge branch 'master' into usb-lock-off
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/backup/hooks/main-warning.inc.php8
-rw-r--r--modules-available/backup/lang/de/messages.json2
-rw-r--r--modules-available/backup/lang/de/template-tags.json2
-rw-r--r--modules-available/backup/lang/en/messages.json2
-rw-r--r--modules-available/backup/lang/en/template-tags.json4
-rw-r--r--modules-available/backup/page.inc.php11
-rw-r--r--modules-available/backup/templates/_page.html19
-rw-r--r--modules-available/baseconfig/api.inc.php2
-rw-r--r--modules-available/baseconfig/lang/de/module.json2
-rw-r--r--modules-available/baseconfig/lang/de/template-tags.json1
-rw-r--r--modules-available/baseconfig/lang/en/module.json2
-rw-r--r--modules-available/baseconfig/lang/en/template-tags.json1
-rw-r--r--modules-available/baseconfig/page.inc.php2
-rw-r--r--modules-available/baseconfig/templates/_page.html28
-rw-r--r--modules-available/baseconfig_bwlp/baseconfig/settings.json41
-rw-r--r--modules-available/baseconfig_bwlp/lang/de/config-variables.json13
-rw-r--r--modules-available/baseconfig_bwlp/lang/en/config-variables.json13
-rw-r--r--modules-available/baseconfig_partitions_cdn/lang/de/template-tags.json2
-rw-r--r--modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json2
-rw-r--r--modules-available/baseconfig_partitions_cdn/templates/_page.html74
-rw-r--r--modules-available/dnbd3/baseconfig/getconfig.inc.php9
-rw-r--r--modules-available/dnbd3/hooks/main-warning.inc.php2
-rw-r--r--modules-available/dnbd3/inc/dnbd3.inc.php11
-rw-r--r--modules-available/dnbd3/inc/dnbd3rpc.inc.php10
-rw-r--r--modules-available/dnbd3/inc/dnbd3util.inc.php12
-rw-r--r--modules-available/dnbd3/install.inc.php41
-rw-r--r--modules-available/dnbd3/lang/de/template-tags.json19
-rw-r--r--modules-available/dnbd3/lang/en/messages.json8
-rw-r--r--modules-available/dnbd3/lang/en/template-tags.json60
-rw-r--r--modules-available/dnbd3/page.inc.php80
-rw-r--r--modules-available/dnbd3/templates/fragment-server-settings.html14
-rw-r--r--modules-available/dnbd3/templates/page-proxy-altservers.html36
-rw-r--r--modules-available/dnbd3/templates/page-proxy-clients.html (renamed from modules-available/dnbd3/templates/page-clientlist.html)0
-rw-r--r--modules-available/dnbd3/templates/page-proxy-config.html4
-rw-r--r--modules-available/dnbd3/templates/page-proxy-header.html (renamed from modules-available/dnbd3/templates/page-header-servername.html)0
-rw-r--r--modules-available/dnbd3/templates/page-proxy-loclist.html (renamed from modules-available/dnbd3/templates/page-client-loclist.html)0
-rw-r--r--modules-available/dnbd3/templates/page-proxy-stats.html9
-rw-r--r--modules-available/dnbd3/templates/page-server-locations.html2
-rw-r--r--modules-available/dnbd3/templates/page-serverlist.html30
-rw-r--r--modules-available/dozmod/api.inc.php8
-rw-r--r--modules-available/dozmod/inc/pagedozmodusers.inc.php15
-rw-r--r--modules-available/dozmod/inc/pagemailtemplates.inc.php16
-rw-r--r--modules-available/dozmod/lang/de/template-tags.json8
-rw-r--r--modules-available/dozmod/lang/en/template-tags.json18
-rw-r--r--modules-available/dozmod/page.inc.php50
-rw-r--r--modules-available/dozmod/permissions/permissions.json12
-rw-r--r--modules-available/dozmod/style.css14
-rw-r--r--modules-available/dozmod/templates/actionlog-log.html84
-rw-r--r--modules-available/dozmod/templates/images-delete.html34
-rw-r--r--modules-available/dozmod/templates/mailconfig.html41
-rw-r--r--modules-available/dozmod/templates/orglist.html18
-rw-r--r--modules-available/dozmod/templates/runtimeconfig.html60
-rw-r--r--modules-available/dozmod/templates/templates.html170
-rw-r--r--modules-available/dozmod/templates/userlist.html48
-rw-r--r--modules-available/eventlog/lang/de/template-tags.json2
-rw-r--r--modules-available/eventlog/lang/en/module.json2
-rw-r--r--modules-available/eventlog/lang/en/template-tags.json2
-rw-r--r--modules-available/eventlog/templates/_page.html13
-rw-r--r--modules-available/exams/baseconfig/getconfig.inc.php8
-rw-r--r--modules-available/exams/config.json2
-rw-r--r--modules-available/exams/lang/de/template-tags.json3
-rw-r--r--modules-available/exams/lang/en/module.json2
-rw-r--r--modules-available/exams/lang/en/template-tags.json13
-rw-r--r--modules-available/exams/page.inc.php4
-rw-r--r--modules-available/exams/style.css8
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html219
-rw-r--r--modules-available/exams/templates/page-exams-vis.html13
-rw-r--r--modules-available/exams/templates/page-exams.html113
-rw-r--r--modules-available/exams/templates/page-upcoming-lectures.html99
-rw-r--r--modules-available/internetaccess/lang/en/template-tags.json2
-rw-r--r--modules-available/internetaccess/page.inc.php47
-rw-r--r--modules-available/internetaccess/permissions/permissions.json3
-rw-r--r--modules-available/internetaccess/style.css3
-rw-r--r--modules-available/internetaccess/templates/_page.html64
-rw-r--r--modules-available/js_stupidtable/clientscript.js181
-rw-r--r--modules-available/js_stupidtable/style.css3
-rw-r--r--modules-available/locationinfo/api.inc.php61
-rw-r--r--modules-available/locationinfo/frontend/frontendscript.js83
-rw-r--r--modules-available/locationinfo/inc/coursebackend.inc.php74
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php3
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php17
-rwxr-xr-xmodules-available/locationinfo/inc/coursebackend/exchange.todo (renamed from modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php)0
-rw-r--r--modules-available/locationinfo/inc/infopanel.inc.php43
-rw-r--r--modules-available/locationinfo/inc/locationinfo.inc.php86
-rw-r--r--modules-available/locationinfo/install.inc.php7
-rw-r--r--modules-available/locationinfo/lang/de/messages.json5
-rw-r--r--modules-available/locationinfo/lang/de/template-tags.json46
-rw-r--r--modules-available/locationinfo/lang/en/backend-hisinone.json6
-rw-r--r--modules-available/locationinfo/lang/en/messages.json5
-rw-r--r--modules-available/locationinfo/lang/en/template-tags.json56
-rw-r--r--modules-available/locationinfo/page.inc.php63
-rwxr-xr-xmodules-available/locationinfo/templates/frontend-default.html181
-rw-r--r--modules-available/locationinfo/templates/frontend-summary.html225
-rw-r--r--modules-available/locationinfo/templates/page-config-panel-summary.html208
-rw-r--r--modules-available/locationinfo/templates/page-locations.html2
-rw-r--r--modules-available/locations/inc/location.inc.php42
-rw-r--r--modules-available/locations/lang/de/template-tags.json5
-rw-r--r--modules-available/locations/lang/en/template-tags.json11
-rw-r--r--modules-available/locations/page.inc.php108
-rw-r--r--modules-available/locations/permissions/permissions.json9
-rw-r--r--modules-available/locations/style.css13
-rw-r--r--modules-available/locations/templates/location-subnets.html134
-rw-r--r--modules-available/locations/templates/locations.html123
-rw-r--r--modules-available/locations/templates/subnets.html17
-rw-r--r--modules-available/main/lang/de/messages.json2
-rw-r--r--modules-available/main/lang/de/template-tags.json3
-rw-r--r--modules-available/main/lang/en/messages.json4
-rw-r--r--modules-available/main/lang/en/template-tags.json5
-rw-r--r--modules-available/main/page.inc.php3
-rw-r--r--modules-available/main/templates/page-main.html16
-rw-r--r--modules-available/minilinux/templates/filelist.html7
-rw-r--r--modules-available/news/config.json3
-rw-r--r--modules-available/news/install.inc.php4
-rw-r--r--modules-available/news/page.inc.php61
-rw-r--r--modules-available/news/permissions/permissions.json6
-rw-r--r--modules-available/news/style.css4
-rw-r--r--modules-available/news/templates/page-news.html65
-rw-r--r--modules-available/permissionmanager/api.inc.php7
-rw-r--r--modules-available/permissionmanager/clientscript.js48
-rw-r--r--modules-available/permissionmanager/config.json4
-rw-r--r--modules-available/permissionmanager/inc/getpermissiondata.inc.php90
-rw-r--r--modules-available/permissionmanager/inc/permissiondbupdate.inc.php53
-rw-r--r--modules-available/permissionmanager/inc/permissionutil.inc.php108
-rw-r--r--modules-available/permissionmanager/install.inc.php86
-rw-r--r--modules-available/permissionmanager/lang/de/module.json4
-rw-r--r--modules-available/permissionmanager/lang/de/template-tags.json25
-rw-r--r--modules-available/permissionmanager/lang/en/module.json4
-rw-r--r--modules-available/permissionmanager/lang/en/template-tags.json25
-rw-r--r--modules-available/permissionmanager/page.inc.php193
-rw-r--r--modules-available/permissionmanager/style.css88
-rw-r--r--modules-available/permissionmanager/templates/_page.html29
-rw-r--r--modules-available/permissionmanager/templates/locationstable.html37
-rw-r--r--modules-available/permissionmanager/templates/roleeditor.html78
-rw-r--r--modules-available/permissionmanager/templates/rolestable.html91
-rw-r--r--modules-available/permissionmanager/templates/treenode.html11
-rw-r--r--modules-available/permissionmanager/templates/treepanel.html13
-rw-r--r--modules-available/permissionmanager/templates/userstable.html170
-rw-r--r--modules-available/rebootcontrol/clientscript.js22
-rw-r--r--modules-available/rebootcontrol/inc/rebootqueries.inc.php10
-rw-r--r--modules-available/rebootcontrol/lang/de/template-tags.json1
-rw-r--r--modules-available/rebootcontrol/lang/en/template-tags.json1
-rw-r--r--modules-available/rebootcontrol/templates/_page.html13
-rw-r--r--modules-available/rebootcontrol/templates/status.html4
-rw-r--r--modules-available/roomplanner/baseconfig/getconfig.inc.php9
-rw-r--r--modules-available/roomplanner/clientscript.js40
-rw-r--r--modules-available/roomplanner/config.json2
-rw-r--r--modules-available/roomplanner/hooks/runmode/config.json7
-rw-r--r--modules-available/roomplanner/inc/pvsgenerator.inc.php47
-rw-r--r--modules-available/roomplanner/install.inc.php43
-rw-r--r--modules-available/roomplanner/js/grid.js9
-rw-r--r--modules-available/roomplanner/lang/de/module.json4
-rw-r--r--modules-available/roomplanner/page.inc.php51
-rw-r--r--modules-available/runmode/inc/runmode.inc.php146
-rw-r--r--modules-available/runmode/install.inc.php26
-rw-r--r--modules-available/runmode/lang/de/messages.json9
-rw-r--r--modules-available/runmode/lang/de/template-tags.json10
-rw-r--r--modules-available/runmode/lang/en/messages.json9
-rw-r--r--modules-available/runmode/lang/en/template-tags.json10
-rw-r--r--modules-available/runmode/page.inc.php56
-rw-r--r--modules-available/runmode/templates/machine-selector.html15
-rw-r--r--modules-available/runmode/templates/module-machine-list.html14
-rw-r--r--modules-available/serversetup-bwlp/lang/de/module.json2
-rw-r--r--modules-available/serversetup-bwlp/lang/de/template-tags.json1
-rw-r--r--modules-available/serversetup-bwlp/lang/en/template-tags.json5
-rw-r--r--modules-available/serversetup-bwlp/page.inc.php2
-rw-r--r--modules-available/serversetup-bwlp/templates/heading.html1
-rw-r--r--modules-available/serversetup-bwlp/templates/ipxe.html31
-rw-r--r--modules-available/session/lang/en/messages.json4
-rw-r--r--modules-available/statistics/api.inc.php209
-rw-r--r--modules-available/statistics/hooks/cron.inc.php42
-rw-r--r--modules-available/statistics/inc/filter.inc.php51
-rw-r--r--modules-available/statistics/inc/filterset.inc.php14
-rw-r--r--modules-available/statistics/inc/machine.inc.php5
-rw-r--r--modules-available/statistics/inc/statistics.inc.php51
-rw-r--r--modules-available/statistics/install.inc.php20
-rw-r--r--modules-available/statistics/lang/de/messages.json5
-rw-r--r--modules-available/statistics/lang/de/template-tags.json9
-rw-r--r--modules-available/statistics/lang/en/messages.json5
-rw-r--r--modules-available/statistics/lang/en/template-tags.json13
-rw-r--r--modules-available/statistics/page.inc.php176
-rw-r--r--modules-available/statistics/style.css11
-rw-r--r--modules-available/statistics/templates/clientlist.html203
-rw-r--r--modules-available/statistics/templates/cpumodels.html58
-rw-r--r--modules-available/statistics/templates/filterbox.html101
-rw-r--r--modules-available/statistics/templates/id44.html46
-rw-r--r--modules-available/statistics/templates/kvmstate.html30
-rw-r--r--modules-available/statistics/templates/machine-main.html23
-rw-r--r--modules-available/statistics/templates/machine-notes.html3
-rw-r--r--modules-available/statistics/templates/machine-usage.html80
-rw-r--r--modules-available/statistics/templates/memory.html46
-rw-r--r--modules-available/statistics/templates/newclients.html54
-rw-r--r--modules-available/statistics/templates/summary.html8
-rw-r--r--modules-available/statistics/templates/syslog.html2
-rw-r--r--modules-available/statistics_reporting/lang/de/template-tags.json2
-rw-r--r--modules-available/statistics_reporting/lang/en/template-tags.json2
-rw-r--r--modules-available/statistics_reporting/page.inc.php78
-rw-r--r--modules-available/statistics_reporting/permissions/permissions.json10
-rw-r--r--modules-available/statistics_reporting/style.css6
-rw-r--r--modules-available/statistics_reporting/templates/columnChooser.html127
-rw-r--r--modules-available/statistics_reporting/templates/table-client.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-location.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-total.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-user.html2
-rw-r--r--modules-available/statistics_reporting/templates/table-vm.html2
-rw-r--r--modules-available/sysconfig/addmodule_adauth.inc.php29
-rw-r--r--modules-available/sysconfig/addmodule_ldapauth.inc.php25
-rw-r--r--modules-available/sysconfig/clientscript.js89
-rw-r--r--modules-available/sysconfig/hooks/cron.inc.php7
-rw-r--r--modules-available/sysconfig/inc/configmodule.inc.php7
-rw-r--r--modules-available/sysconfig/inc/configmodulebaseldap.inc.php23
-rw-r--r--modules-available/sysconfig/install.inc.php26
-rw-r--r--modules-available/sysconfig/lang/de/messages.json2
-rw-r--r--modules-available/sysconfig/lang/de/module.json1
-rw-r--r--modules-available/sysconfig/lang/de/template-tags.json8
-rw-r--r--modules-available/sysconfig/lang/en/messages.json2
-rw-r--r--modules-available/sysconfig/lang/en/module.json3
-rw-r--r--modules-available/sysconfig/lang/en/template-tags.json6
-rw-r--r--modules-available/sysconfig/page.inc.php3
-rw-r--r--modules-available/sysconfig/templates/ad-selfsearch.html6
-rw-r--r--modules-available/sysconfig/templates/ad-start.html55
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkconnection.html11
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-checkcredentials.html6
-rw-r--r--modules-available/sysconfig/templates/ad_ldap-homedir.html3
-rw-r--r--modules-available/sysconfig/templates/branding-check.html3
-rw-r--r--modules-available/sysconfig/templates/branding-start.html4
-rw-r--r--modules-available/sysconfig/templates/cfg-finish.html8
-rw-r--r--modules-available/sysconfig/templates/cfg-start.html10
-rw-r--r--modules-available/sysconfig/templates/custom-upload.html1
-rw-r--r--modules-available/sysconfig/templates/ldap-start.html50
-rw-r--r--modules-available/sysconfig/templates/list-configs.html12
-rw-r--r--modules-available/sysconfig/templates/list-modules.html10
-rw-r--r--modules-available/sysconfig/templates/sshconfig-start.html13
-rw-r--r--modules-available/sysconfig/templates/start.html2
-rw-r--r--modules-available/sysconfig/templates/sysconfig_heading.html1
-rw-r--r--modules-available/syslog/hooks/cron.inc.php5
-rw-r--r--modules-available/syslog/lang/de/template-tags.json3
-rw-r--r--modules-available/syslog/lang/en/template-tags.json3
-rw-r--r--modules-available/syslog/page.inc.php32
-rw-r--r--modules-available/syslog/templates/page-syslog.html48
-rw-r--r--modules-available/systemstatus/lang/de/template-tags.json7
-rw-r--r--modules-available/systemstatus/lang/en/template-tags.json7
-rw-r--r--modules-available/systemstatus/page.inc.php48
-rw-r--r--modules-available/systemstatus/templates/_page.html22
-rw-r--r--modules-available/systemstatus/templates/services.html26
-rw-r--r--modules-available/translation/lang/de/template-tags.json4
-rw-r--r--modules-available/translation/lang/en/template-tags.json4
-rw-r--r--modules-available/translation/templates/edit.html145
-rw-r--r--modules-available/translation/templates/module-heading.html2
-rw-r--r--modules-available/translation/templates/module-list.html45
-rw-r--r--modules-available/vmstore/lang/en/module.json4
-rw-r--r--modules-available/vmstore/lang/en/template-tags.json2
-rw-r--r--modules-available/vmstore/page.inc.php3
-rw-r--r--modules-available/vmstore/templates/page-vmstore.html128
-rw-r--r--modules-available/webinterface/lang/de/template-tags.json1
-rw-r--r--modules-available/webinterface/lang/en/template-tags.json1
-rw-r--r--modules-available/webinterface/page.inc.php1
-rw-r--r--modules-available/webinterface/templates/customization.html2
-rw-r--r--modules-available/webinterface/templates/heading.html1
-rw-r--r--modules-available/webinterface/templates/https.html37
-rw-r--r--modules-available/webinterface/templates/passwords.html16
260 files changed, 5966 insertions, 2121 deletions
diff --git a/modules-available/backup/hooks/main-warning.inc.php b/modules-available/backup/hooks/main-warning.inc.php
new file mode 100644
index 00000000..0a0be7cc
--- /dev/null
+++ b/modules-available/backup/hooks/main-warning.inc.php
@@ -0,0 +1,8 @@
+<?php
+
+$last = Property::get('backup.last-time', 0);
+if ($last === 0) {
+ Message::addWarning('backup.last-time-unknown', true);
+} elseif ($last + (30 * 86400) < time()) {
+ Message::addWarning('backup.last-time', true, date('d.m.Y', $last));
+}
diff --git a/modules-available/backup/lang/de/messages.json b/modules-available/backup/lang/de/messages.json
index 6d16f366..36581b2a 100644
--- a/modules-available/backup/lang/de/messages.json
+++ b/modules-available/backup/lang/de/messages.json
@@ -1,5 +1,7 @@
{
"backup-failed": "Erstellen des Backups fehlgeschlagen",
+ "last-time": "Das letzte Backup wurde am {{0}} gemacht. Es wird empfohlen, regelm\u00e4\u00dfig ein Backup des Servers herunterzuladen.",
+ "last-time-unknown": "Es wird empfohlen, regelm\u00e4\u00dfig ein Backup des Servers herunterzuladen.",
"missing-file": "Es wurde keine Datei ausgew\u00e4hlt!",
"restore-done": "Wiederherstellung abgeschlossen",
"upload-failed": "Upload schlug fehl: {{0}}"
diff --git a/modules-available/backup/lang/de/template-tags.json b/modules-available/backup/lang/de/template-tags.json
index 770e34d2..1e41abbc 100644
--- a/modules-available/backup/lang/de/template-tags.json
+++ b/modules-available/backup/lang/de/template-tags.json
@@ -5,6 +5,7 @@
"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_lastBackup": "Letzte Sicherung",
"lang_reboot": "Systemneustart",
"lang_restore": "Hochladen",
"lang_restoreConfig": "Konfiguration wiederherstellen",
@@ -15,5 +16,6 @@
"lang_selectFile": "Bitte w\u00e4hlen Sie ein Backup-Archiv",
"lang_stopping": "Stoppe",
"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.",
+ "lang_unknown": "Unbekannt",
"lang_waitReboot": "Warte auf Reboot."
} \ No newline at end of file
diff --git a/modules-available/backup/lang/en/messages.json b/modules-available/backup/lang/en/messages.json
index 944cc80f..4c9685d5 100644
--- a/modules-available/backup/lang/en/messages.json
+++ b/modules-available/backup/lang/en/messages.json
@@ -1,5 +1,7 @@
{
"backup-failed": "Backup failed!",
+ "last-time": "Last backup was downloaded on {{0}}. It's recommended to download a server backup regularly.",
+ "last-time-unknown": "It's recommended to download a server backup regularly.",
"missing-file": "There was no file selected!",
"restore-done": "Restore done",
"upload-failed": "Upload failed: {{0}}"
diff --git a/modules-available/backup/lang/en/template-tags.json b/modules-available/backup/lang/en/template-tags.json
index cb8aced2..8cb131f7 100644
--- a/modules-available/backup/lang/en/template-tags.json
+++ b/modules-available/backup/lang/en/template-tags.json
@@ -1,10 +1,11 @@
{
"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_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_lastBackup": "Last backup",
"lang_reboot": "System reboot",
"lang_restore": "Upload",
"lang_restoreConfig": "Restore config",
@@ -15,5 +16,6 @@
"lang_selectFile": "Please select a backup archive",
"lang_stopping": "Stopping",
"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.",
+ "lang_unknown": "Unknown",
"lang_waitReboot": "Waiting for reboot."
} \ No newline at end of file
diff --git a/modules-available/backup/page.inc.php b/modules-available/backup/page.inc.php
index 34777db8..77d677c7 100644
--- a/modules-available/backup/page.inc.php
+++ b/modules-available/backup/page.inc.php
@@ -3,6 +3,8 @@
class Page_Backup extends Page
{
+ const LAST_BACKUP_PROP = 'backup.last-time';
+
private $action = false;
private $templateData = array();
@@ -26,7 +28,13 @@ class Page_Backup extends Page
if ($this->action === 'restore') {
Render::addTemplate('restore', $this->templateData);
} else {
- Render::addTemplate('_page');
+ $lastBackup = (int)Property::get(self::LAST_BACKUP_PROP, 0);
+ if ($lastBackup === 0) {
+ $lastBackup = false;
+ } else {
+ $lastBackup = date('d.m.Y', $lastBackup);
+ }
+ Render::addTemplate('_page', ['last_backup' => $lastBackup]);
}
}
@@ -64,6 +72,7 @@ class Page_Backup extends Page
}
@fclose($fh);
@unlink($task['data']['backupFile']);
+ Property::set(self::LAST_BACKUP_PROP, time());
die();
}
diff --git a/modules-available/backup/templates/_page.html b/modules-available/backup/templates/_page.html
index 47b5a174..88815897 100644
--- a/modules-available/backup/templates/_page.html
+++ b/modules-available/backup/templates/_page.html
@@ -7,7 +7,12 @@
<div class="panel-heading">{{lang_backup}}</div>
<div class="panel-body">
<p>{{lang_backupDescription}}</p>
- <button class="btn btn-primary" type="submit">{{lang_download}}</button>
+ <p class="text-right">
+ {{lang_lastBackup}}:
+ {{^last_backup}}{{lang_unknown}}{{/last_backup}}
+ {{last_backup}}
+ </p>
+ <button class="btn btn-primary pull-right" type="submit"><span class="glyphicon glyphicon-save"></span> {{lang_download}}</button>
</div>
</div>
</form>
@@ -28,14 +33,20 @@
</span>
</div>
<div>
- <label><input type="checkbox" name="restore_openslx" checked="checked"> {{lang_restoreSystemConfig}}</label>
+ <div class="checkbox">
+ <input type="checkbox" name="restore_openslx" checked="checked" id="id-sysonfig">
+ <label for="id-sysonfig"><b>{{lang_restoreSystemConfig}}</b></label>
+ </div>
<p><i>{{lang_systemExplanation}}</i></p>
</div>
<div>
- <label><input type="checkbox" name="restore_dozmod" checked="checked"> {{lang_restoreDozmodConfig}}</label>
+ <div class="checkbox">
+ <input type="checkbox" name="restore_dozmod" checked="checked" id="id-dozmod">
+ <label for="id-dozmod"><b>{{lang_restoreDozmodConfig}}</b></label>
+ </div>
<p><i>{{lang_dozmodExplanation}}</i></p>
</div>
- <button class="btn btn-primary" type="submit">{{lang_restore}}</button>
+ <button class="btn btn-primary pull-right" type="submit"><span class="glyphicon glyphicon-open"></span> {{lang_restore}}</button>
</div>
</div>
</form> \ No newline at end of file
diff --git a/modules-available/baseconfig/api.inc.php b/modules-available/baseconfig/api.inc.php
index 64204774..a4024c5e 100644
--- a/modules-available/baseconfig/api.inc.php
+++ b/modules-available/baseconfig/api.inc.php
@@ -172,12 +172,14 @@ if (Request::any('save') === 'true') {
} else {
echo "# Error saving config to $path\n";
}
+ echo "SLX_NOW='", time(), "'\n";
}
}
// Output to browser
echo $lines;
} else {
// Only output to client
+ ConfigHolder::add('SLX_NOW', time(), PHP_INT_MAX);
ConfigHolder::outputConfig();
}
diff --git a/modules-available/baseconfig/lang/de/module.json b/modules-available/baseconfig/lang/de/module.json
index 461bebdb..44b51ec3 100644
--- a/modules-available/baseconfig/lang/de/module.json
+++ b/modules-available/baseconfig/lang/de/module.json
@@ -1,3 +1,3 @@
{
- "module_name": "KonfigurationsVariablen"
+ "module_name": "Konfigurationsvariablen"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/de/template-tags.json b/modules-available/baseconfig/lang/de/template-tags.json
index 7d6da790..b96586bd 100644
--- a/modules-available/baseconfig/lang/de/template-tags.json
+++ b/modules-available/baseconfig/lang/de/template-tags.json
@@ -2,6 +2,7 @@
"lang_basicConfiguration": "Basiskonfiguration",
"lang_clientRelatedConfig": "Die Optionen auf dieser Seite beziehen sich auf das Verhalten der bwLehrpool-Clients.",
"lang_defaultValue": "Standard",
+ "lang_discardChanges": "Änderungen verwerfen",
"lang_editOverrideNotice": "Sie bearbeiten die Einstellungen f\u00fcr einen Unterbereich",
"lang_enableOverride": "\u00dcberschreiben",
"lang_inheritSource": "Geerbt von",
diff --git a/modules-available/baseconfig/lang/en/module.json b/modules-available/baseconfig/lang/en/module.json
index 48591f88..9ad9d10f 100644
--- a/modules-available/baseconfig/lang/en/module.json
+++ b/modules-available/baseconfig/lang/en/module.json
@@ -1,3 +1,3 @@
{
- "module_name": "Config variables"
+ "module_name": "Config Variables"
} \ No newline at end of file
diff --git a/modules-available/baseconfig/lang/en/template-tags.json b/modules-available/baseconfig/lang/en/template-tags.json
index 1a32cb0d..b39bf9aa 100644
--- a/modules-available/baseconfig/lang/en/template-tags.json
+++ b/modules-available/baseconfig/lang/en/template-tags.json
@@ -2,6 +2,7 @@
"lang_basicConfiguration": "Basic Configuration",
"lang_clientRelatedConfig": "The options on this page are related to the bwLehrpool client machines.",
"lang_defaultValue": "Default",
+ "lang_discardChanges": "Discard Changes",
"lang_editOverrideNotice": "You're editing the settings of a sub-section",
"lang_enableOverride": "Override",
"lang_inheritSource": "Inherited from",
diff --git a/modules-available/baseconfig/page.inc.php b/modules-available/baseconfig/page.inc.php
index bd9d6683..366a1238 100644
--- a/modules-available/baseconfig/page.inc.php
+++ b/modules-available/baseconfig/page.inc.php
@@ -178,7 +178,7 @@ class Page_BaseConfig extends Page
$settings[$var['catid']]['settings'][$key]['displayvalue'] = $var['defaultvalue'];
}
if (!isset($settings[$var['catid']]['settings'][$key]['shadows'])) {
- $settings[$var['catid']]['settings'][$key]['shadows'] = null;
+ $settings[$var['catid']]['settings'][$key]['shadows'] = isset($var['shadows']) ? $var['shadows'] : null;
}
//echo "<pre>";
//var_dump($settings[$var['catid']]['settings'][$key]);
diff --git a/modules-available/baseconfig/templates/_page.html b/modules-available/baseconfig/templates/_page.html
index e0be35bc..f2dfd17b 100644
--- a/modules-available/baseconfig/templates/_page.html
+++ b/modules-available/baseconfig/templates/_page.html
@@ -50,9 +50,12 @@
<div class="modal fade" id="help-{{setting}}" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{setting}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{setting}}</b></h4>
+
+ </div>
<div class="modal-body">{{{description}}}</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
@@ -61,14 +64,19 @@
</div>
</div>
{{/categories}}
- <button class="btn btn-primary" type="submit">{{lang_save}}</button>
- <button class="btn btn-default" type="reset">{{lang_reset}}</button>
- {{^override}}
- <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}">Download</a>
- {{/override}}
- {{#override}}
- <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}&amp;module={{target_module}}&amp;value={{field_value}}&amp;force=1">Download</a>
- {{/override}}
+ <div class="text-right">
+ <button class="btn btn-warning" type="button" onclick="window.location.hash = '#bottom'; window.location.reload(true);"><span class="glyphicon glyphicon-refresh"></span> {{lang_discardChanges}}</button>
+ <a name="bottom"></a>
+ {{^override}}
+ <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}"><span class="glyphicon glyphicon-download-alt"></span> Download</a>
+ {{/override}}
+ {{#override}}
+ <a class="btn btn-default" href="api.php?do=baseconfig&amp;user={{userid}}&amp;module={{target_module}}&amp;value={{field_value}}&amp;force=1">Download</a>
+ {{/override}}
+ <button class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+ <br/>
+
</form>
<script type="text/javascript">
diff --git a/modules-available/baseconfig_bwlp/baseconfig/settings.json b/modules-available/baseconfig_bwlp/baseconfig/settings.json
index d0da2253..18e87ab6 100644
--- a/modules-available/baseconfig_bwlp/baseconfig/settings.json
+++ b/modules-available/baseconfig_bwlp/baseconfig/settings.json
@@ -73,6 +73,12 @@
"permissions": "2",
"validator": "list:socks4|socks5|http-connect|http-relay"
},
+ "SLX_BRIDGE_OTHER_NICS": {
+ "catid": "networking",
+ "defaultvalue": "no",
+ "permissions": "2",
+ "validator": "list:no|yes"
+ },
"SLX_REMOTE_LOG_SESSIONS": {
"catid": "other",
"defaultvalue": "anonymous",
@@ -115,6 +121,18 @@
"permissions": "2",
"validator": "regex:\/^\\d*$\/"
},
+ "SLX_SYSTEM_STANDBY_TIMEOUT": {
+ "catid": "power",
+ "defaultvalue": "",
+ "permissions": "2",
+ "validator": "regex:\/^\\d*$\/"
+ },
+ "SLX_WAKEUP_SCHEDULE": {
+ "catid": "power",
+ "defaultvalue": "",
+ "permissions": "2",
+ "validator": "regex:\/^(\\s*\\d{1,2}:\\d{1,2})*\\s*$\/"
+ },
"SLX_VMCHOOSER_TAB": {
"catid": "vmchooser",
"defaultvalue": "AUTO",
@@ -133,6 +151,12 @@
"permissions": "2",
"validator": "list:IGNORE|BUMP|EXCLUSIVE"
},
+ "SLX_VMCHOOSER_TIMEOUT": {
+ "catid": "vmchooser",
+ "defaultvalue": "120",
+ "permissions": "2",
+ "validator": "regex:\/^\\d*$\/"
+ },
"SLX_PRINT_USER_PREFIX": {
"catid": "sysconfig",
"defaultvalue": "",
@@ -145,10 +169,27 @@
"permissions": "2",
"validator": "regex:\/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|)$\/i"
},
+ "SLX_AUTOLOGIN": {
+ "catid": "sysconfig",
+ "defaultvalue": "OFF",
+ "permissions": "2",
+ "validator": "list:ON|OFF",
+ "shadows": {
+ "ON": [
+ "SLX_VMCHOOSER_TIMEOUT"
+ ]
+ }
+ },
"SLX_PVS_DEFAULT": {
"catid": "vmchooser",
"defaultvalue": "OFF",
"permissions": "2",
"validator": "list:ON|OFF"
+ },
+ "SLX_PASSTHROUGH_USB_ID": {
+ "catid": "vmchooser",
+ "defaultvalue": "",
+ "permissions": "2",
+ "validator": "regex:\/^(([0-9a-f]{4}:[0-9a-f]{4}\\s*)+|)$\/i"
}
}
diff --git a/modules-available/baseconfig_bwlp/lang/de/config-variables.json b/modules-available/baseconfig_bwlp/lang/de/config-variables.json
index 06681a58..a1ef4bab 100644
--- a/modules-available/baseconfig_bwlp/lang/de/config-variables.json
+++ b/modules-available/baseconfig_bwlp/lang/de/config-variables.json
@@ -1,11 +1,15 @@
{
"SLX_ADDONS": "Zu ladende Addons. Zur Zeit steht nur *vmware* zur Verf\u00fcgung.",
+ "SLX_AUTOLOGIN": "Anmeldemaske \u00fcberspringen und direkt zum vmChooser gehen, bzw. die in SLX_AUTOSTART_UUID gesetzte Veranstaltung starten.",
"SLX_AUTOSTART_UUID": "ID einer Veranstaltung die automatisch gestartet werden soll. Die Veranstaltungs-ID finden Sie im Detailfenster innerhalb der bwLehrpool-Suite.\r\n\r\n*Hinweis: Diese Option ist eine tempor\u00e4re \u00dcbergangsl\u00f6sung. In sp\u00e4teren Versionen wird die Funktionalit\u00e4t einfacher erreichbar sein.*",
"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_BRIDGE_OTHER_NICS": "Sofern ein Client mehrere Netzwerkkarten besitzt, k\u00f6nnen Sie mittels dieser Option alle weiteren gefundenen Karten in die VM durchreichen.",
"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_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_NET_SEARCH": "Per Leerzeichen getrennte Liste von Suchdom\u00e4nen, die der Client verwenden soll, sofern der DHCP-Server keine Vorgabe macht.",
"SLX_NTP_SERVER": "Adresse des NTP-Zeitservers. Es k\u00f6nnen mehrere Server mit Leerzeichen getrennt angegeben werden.Die Server werden der Reihe nach angefragt, bis ein antwortender Server gefunden wird.",
+ "SLX_PASSTHROUGH_USB_ID": "Geben Sie hier eindeutige IDs von USB-Ger\u00e4ten an, die direkt in die VMs weitergereicht werden sollen. Das erwartete Format ist *vendorID:productID* , als jeweils vierstellige Hexadezimalzahlen, beispielsweise *1234:abcd* .\r\nMehrere IDs k\u00f6nnen als leerzeichengetrennte Liste angegeben werden.",
"SLX_PRINT_USER_PREFIX": "Pr\u00e4fix, was im Authentifizierungsdialog der PrinterGUI dem Benutzernamen vorangestellt wird.\r\nWenn das Drucksystem auf einem AD-Server l\u00e4uft und der Dom\u00e4nenname vorangestellt werden muss, tragen Sie hier *domain\\* ein. Achten Sie auf die Angabe des Backslashes, er wird nicht automatisch angeh\u00e4ngt. Falls das Drucksystem mit dem reinen Benutzernamen zurecht kommt, k\u00f6nnen Sie das Feld leer lassen.",
"SLX_PROXY_BLACKLIST": "Adressen bzw. Adressbereiche, f\u00fcr die der Proxyserver nicht verwendet werden soll (z.B. der Adressbereich der Einrichtung). G\u00fcltige Angaben sind einzelne IP-Adressen, sowie IP-Bereiche in CIDR-Notation (z.B. 1.2.0.0\/16). Mehrere Angaben k\u00f6nnen durch Leerzeichen getrennt werden.",
"SLX_PROXY_IP": "Die Adresse des zu verwendenden Proxy Servers.",
@@ -18,8 +22,11 @@
"SLX_ROOT_PASS": "Das root-Passwort des Grundsystems. Wird nur f\u00fcr Diagnosezwecke am Client ben\u00f6tigt.\r\nFeld leer lassen, um root-Logins zu verbieten.\r\n\/Hinweis\/: Das Passwort wird im Klartext in der lokalen Datenbank hinterlegt, jedoch immer gehasht an die Clients \u00fcbermittelt (SHA-512 mit Salt). Wenn Sie das Passwort auch im Satelliten nicht im Klartext speichern wollen, k\u00f6nnen Sie hier auch ein vorgehashtes Passwort eintragen (im *$6$....*-Format).",
"SLX_SCREEN_STANDBY_TIMEOUT": "Zeit in Sekunden, nach der der Bildschirm bei Inaktivit\u00e4t des Rechners in den Standby-Modus versetzt wird.",
"SLX_SHUTDOWN_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner ausschalten, auch wenn noch ein Benutzer aktiv ist.\r\nMehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.",
- "SLX_SHUTDOWN_TIMEOUT": "Zeit in Sekunden, nach dem ein Rechner abgeschaltet wird, sofern kein Benutzer angemeldet ist.\r\nFeld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_SHUTDOWN_TIMEOUT": "Zeit in Sekunden, nach der ein Rechner abgeschaltet wird, sofern kein Benutzer angemeldet ist.\r\nFeld leer lassen, um die Funktion zu deaktivieren.",
+ "SLX_SYSTEM_STANDBY_TIMEOUT": "Zeit in Sekunden, nach der ein Rechner in den Standby-Modus versetzt wird, sofern kein Benutzer angemeldet ist.\r\nFeld leer lassen, um die Funktion zu deaktivieren.\r\n\r\nBitte beachten Sie, dass der Standby-Modus auf bestimmter Hardware nicht oder nur unzuverl\u00e4ssig funktioniert. Es empfiehlt sich, diese Funktion nur in R\u00e4umen zu aktivieren bei denen vorher \u00fcberpr\u00fcft wurde, dass der Standby-Modus ordnungsgem\u00e4\u00df funktioniert.\r\nDer Standby-Modus kann manuell ausgel\u00f6st werden, indem auf dem Client als root-Benutzer der Befehl *systemctl suspend* ausgef\u00fchrt wird.",
"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"
+ "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",
+ "SLX_VMCHOOSER_TIMEOUT": "Zeit in Sekunden, die der Nutzer zur Auswahl einer Sitzung im vmChooser hat. Dieser Z\u00e4hler wird bei Maus-\/Tastaturaktivit\u00e4t zur\u00fcckgesetzt.",
+ "SLX_WAKEUP_SCHEDULE": "Feste Uhrzeit, zu der sich die Rechner einschalten. Der Zeitpunkt gilt derzeit f\u00fcr jeden Tag - auch am Wochenende. Mehrere Zeitpunkte k\u00f6nnen durch Leerzeichen getrennt angegeben werden.\r\n\r\nBitte beachten Sie, dass diese Funktion auf bestimmter Hardware nicht oder nur unzuverl\u00e4ssig funktioniert. Es empfiehlt sich, diese Funktion nur in R\u00e4umen zu aktivieren bei denen vorher \u00fcberpr\u00fcft wurde, dass das Aufwecken ordnungsgem\u00e4\u00df funktioniert."
} \ No newline at end of file
diff --git a/modules-available/baseconfig_bwlp/lang/en/config-variables.json b/modules-available/baseconfig_bwlp/lang/en/config-variables.json
index 6aee946b..3e33cbef 100644
--- a/modules-available/baseconfig_bwlp/lang/en/config-variables.json
+++ b/modules-available/baseconfig_bwlp/lang/en/config-variables.json
@@ -1,11 +1,15 @@
{
"SLX_ADDONS": "Addons to load. Currently, only *vmware* is available.",
+ "SLX_AUTOLOGIN": "Skip login screen and directly show vmChooser (or start the lecture specified in SLX_AUTOSTART_UUID).",
"SLX_AUTOSTART_UUID": "ID of a lecture which is automatically started. The lecture-ID is found in the detail window of a lecture in the bwLehrpool-Suite. \r\n\r\n*This solution is only temporary. In later versions this feature will probably be moved to another section*",
"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_BRIDGE_OTHER_NICS": "If enabled, additional network cards installed in the Client will be bridged to the VM. ",
"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_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_NET_SEARCH": "Space separated list of DNS search domains to use in case the DHCP server doesn't supply any.",
"SLX_NTP_SERVER": "Address of the NTP time server. Multiple servers can be specified separated by spaces.The servers are queried in sequence until a responding server is found.",
+ "SLX_PASSTHROUGH_USB_ID": "Specify IDs of USB devices that should be passed through to the VM directly.\r\nThe expected format is *vendorID:productID* , where each ID is a 4-digit hexadecimal number, e.g. *1234:abcd* \r\nMultiple IDs can be given as a space-separated list.",
"SLX_PRINT_USER_PREFIX": "Prefix to add to the user name in the authentication dialog of PrinterGUI.\r\nIf your print server belongs to a Windows domain and requires the domain name prefixed, set this field to *domainname\\*. Note the trailing backslash, it will not be inserted automatically. If your print server just wants the plain user name, this field should be left blank.",
"SLX_PROXY_BLACKLIST": "Address or addresses ranges in which the proxy server is not used (for example the address range of the device). Valid entries are individual IP addresses and IP ranges in CIDR notation (for example 1.2.0.0\/16). Multiple selections can be separated by spaces.",
"SLX_PROXY_IP": "The address to use for the proxy server.",
@@ -19,7 +23,10 @@
"SLX_SCREEN_STANDBY_TIMEOUT": "Time in seconds after which the screen will enter power saving mode, if the client is not in use.",
"SLX_SHUTDOWN_SCHEDULE": "Fixed time to turn off the computer, even if there is a user active.\r\nSeveral times can be specified, separated by spaces.",
"SLX_SHUTDOWN_TIMEOUT": "Time in seconds after which a computer is switched off, if no user is logged on.\r\nLeave blank to disable the function.",
+ "SLX_SYSTEM_STANDBY_TIMEOUT": "Timeout in seconds after which the computer enters stand-by mode if no user is logged on.\r\nLeave blank to disable.\r\n\r\nNote that some hardware might not properly support stand-by and crash or freeze on wakeup. It's recommended to only enable this feature for rooms where it's known that the hardware supports stand-by mode. You can manually trigger stand-by mode on a client by logging in as root and executing *systemctl suspend*.",
"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
+ "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",
+ "SLX_VMCHOOSER_TIMEOUT": "Timeout in seconds after which the session will be closed if the user doesn't make any selection in vmChooser. Mouse or keyboard activity resets this timeout.",
+ "SLX_WAKEUP_SCHEDULE": "Fixed time to turn on the computer. At the moment this feature takes effect on every day - also at the weekend. Several times can be specified, separated by spaces.\r\n\r\nNote that some hardware might not properly support wakeup. It's recommended to only enable this feature for rooms where it's known that the hardware supports it."
+}
diff --git a/modules-available/baseconfig_partitions_cdn/lang/de/template-tags.json b/modules-available/baseconfig_partitions_cdn/lang/de/template-tags.json
index ea4f9f5c..99e0a487 100644
--- a/modules-available/baseconfig_partitions_cdn/lang/de/template-tags.json
+++ b/modules-available/baseconfig_partitions_cdn/lang/de/template-tags.json
@@ -1,6 +1,8 @@
{
+ "lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
"lang_confirm": "Wollen Sie die Einstellungen unter \/srv\/openslx\/www\/boot\/config speichern?",
"lang_create": "Anlegen",
+ "lang_discardChanges": "Änderungen verwerfen",
"lang_explanationText": "Hier k\u00f6nnen Sie bestimmen, welche Partitionen auf dem Client angelegt werden.",
"lang_helpId": "Partitions-ID",
"lang_helpMountPoint": "Muss ein Absolutes Verzeichnis im Zieldateisystem sein, z.B. \/mnt\/shares\/data",
diff --git a/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json b/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json
index 819d2273..04ce6c80 100644
--- a/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json
+++ b/modules-available/baseconfig_partitions_cdn/lang/en/template-tags.json
@@ -1,6 +1,8 @@
{
+ "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!",
"lang_confirm": "Would you like to save the settings on [ \/srv\/openslx\/www\/boot\/config ] ?",
"lang_create": "Create",
+ "lang_discardChanges": "Discard Changes",
"lang_explanationText": "Here you can configure what kind of partitions will be created on the client computers, and where they will be mounted",
"lang_helpId": "Partition Id",
"lang_helpMountPoint": "Must be a directory: \/example\/directory\/",
diff --git a/modules-available/baseconfig_partitions_cdn/templates/_page.html b/modules-available/baseconfig_partitions_cdn/templates/_page.html
index f7331186..71cbb7db 100644
--- a/modules-available/baseconfig_partitions_cdn/templates/_page.html
+++ b/modules-available/baseconfig_partitions_cdn/templates/_page.html
@@ -23,7 +23,6 @@
<div class='col-sm-1 col-md-2'>
<a class='btn btn-danger' href='?do=BaseConfig_Partitions_CDN&amp;deletePartition={{id}}&amp;token={{token}}'>
<span class='glyphicon glyphicon-trash'></span>
- <span class="hidden-sm">{{lang_delete}}</span>
</a>
</div>
</div>
@@ -31,24 +30,52 @@
{{/partitions}}
<div class='list-group-item clearfix'>
<div class="pull-right">
- <a class='btn btn-default ' data-toggle='modal' data-target='#add-partition'>
+ <a class='btn btn-success ' data-toggle='modal' data-target='#add-partition'>
<span class='glyphicon glyphicon-plus'></span> {{lang_newPartition}}
</a>
</div>
</div>
</div>
- <button class="btn btn-lg btn-primary" type="submit">{{lang_save}}</button>
- <button class="btn btn-lg btn-primary" type="reset">{{lang_reset}}</button>
- <a class="btn btn-lg btn-primary" href="#" onclick="saveConfig()">Download</a>
+ <div class="pull-right">
+ <a class="btn btn-default" data-toggle="modal" data-target="#downloadModal"><span class="glyphicon glyphicon-download-alt"></span> Download</a>
+ <button class="btn btn-warning" type="reset"><span class="glyphicon glyphicon-refresh"></span> {{lang_discardChanges}}</button>
+ <button class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+
+ <div class ="modal fade" id="downloadModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_confirm}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" onclick="saveConfig(false)" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="button" onclick="saveConfig(true)" class="btn btn-sm btn-danger" name="download"> Download</button>
+ </div>
+ </div>
+ </div>
+ </div>
</form>
-<div>
- <form method="post" action="?do=BaseConfig_Partitions_CDN">
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="reset">
- <button class="btn btn-default" type="submit" onclick="return confirm('{{lang_resetConfirm}}');">{{lang_resetDefault}}</button>
- </form>
-</div>
+<form method="post" action="?do=BaseConfig_Partitions_CDN">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="reset">
+ <button class="btn btn-danger" type="button" data-toggle="modal" data-target="#resetDefaultModal">{{lang_resetDefault}}</button>
+
+ <div class ="modal fade" id="resetDefaultModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_areYouSureNoUndo}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-sm btn-danger" name="resetDefault"> {{lang_resetDefault}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form>
<!-- Create Partition Window -->
<form action="?do=BaseConfig_Partitions_CDN" method="post">
@@ -56,32 +83,37 @@
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_newPartition}}</div>
- <div class="modal-body">
+ <div class="modal-header">
+ <h4><b>{{lang_newPartition}}</b></h4>
+ </div>
+ <div class="modal-body">
<div class="input-group">
- <span class="input-group-addon">{{lang_partitionId}}</span>
+ <span class="input-group-addon" style="min-width:140px;">{{lang_partitionId}}</span>
<input name="new-partition-id" class="form-control" type="text">
</div>
<p class="help-block">{{lang_helpId}}</p>
<div class="input-group">
- <span class="input-group-addon">{{lang_partitionSize}}</span>
+ <span class="input-group-addon" style="min-width:140px;">{{lang_partitionSize}}</span>
<input name="new-partition-size" class="form-control" type="text">
</div>
<p class="help-block">{{lang_helpSize}}</p>
<div class="input-group">
- <span class="input-group-addon">{{lang_partitionMountPoint}}</span>
+ <span class="input-group-addon" style="min-width:140px;">{{lang_partitionMountPoint}}</span>
<input name="new-partition-mount-point" class="form-control" type="text">
</div>
<p class="help-block">{{lang_helpMountPoint}}</p>
<div class="input-group">
- <span class="input-group-addon">{{lang_partitionOptions}}</span>
+ <span class="input-group-addon" style="min-width:140px;">{{lang_partitionOptions}}</span>
<input name="new-partition-options" class="form-control" type="text">
</div>
<p class="help-block">{{lang_helpOptions}}</p>
+ </div>
+
+ <div class="modal-footer">
+ <a class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</a>
<input type="submit" class="btn btn-primary" value="{{lang_create}}">
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
@@ -89,8 +121,8 @@
<input type="hidden" name="token" value="{{token}}">
</form>
<script type="text/javascript">
- function saveConfig(){
- if(confirm('{{lang_confirm}}'))
+ function saveConfig(download){
+ if(download)
window.location = 'api.php?do=baseconfig&user={{user}}&save=true';
else
window.location = 'api.php?do=baseconfig&user={{user}}';
diff --git a/modules-available/dnbd3/baseconfig/getconfig.inc.php b/modules-available/dnbd3/baseconfig/getconfig.inc.php
index d614dc6f..fe1bef17 100644
--- a/modules-available/dnbd3/baseconfig/getconfig.inc.php
+++ b/modules-available/dnbd3/baseconfig/getconfig.inc.php
@@ -2,6 +2,12 @@
if (!Dnbd3::isEnabled()) return;
+if (!Dnbd3::hasNfsFallback()) {
+ ConfigHolder::add("SLX_VM_NFS", false, 1000);
+ ConfigHolder::add("SLX_VM_NFS_USER", false, 1000);
+ ConfigHolder::add("SLX_VM_NFS_PASSWD", false, 1000);
+}
+
// Locations from closest to furthest (order)
$locations = ConfigHolder::get('SLX_LOCATIONS');
if ($locations === false) {
@@ -18,6 +24,7 @@ $res = Database::simpleQuery('SELECT s.fixedip, m.clientip, sxl.locationid FROM
LEFT JOIN dnbd3_server_x_location sxl USING (serverid)
WHERE sxl.locationid IS NULL OR sxl.locationid IN (:lids)', array('lids' => $locationIds));
// Lookup of priority - first index (0) will be closest location in chain
+// low value is higher priority
$locationsAssoc = array_flip($locationIds);
$servers = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
@@ -27,7 +34,7 @@ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
} else {
$defPrio = 1000;
}
- $ip = $row['clientip'] ? $row['clientip'] : $row['fixedip'];
+ $ip = $row['fixedip'] ? $row['fixedip'] : $row['clientip'];
if ($defPrio === 1000 && is_null($row['locationid'])) {
$serverLoc = Location::getFromIp($ip);
if ($serverLoc !== false) {
diff --git a/modules-available/dnbd3/hooks/main-warning.inc.php b/modules-available/dnbd3/hooks/main-warning.inc.php
index 258d03d0..e38048e1 100644
--- a/modules-available/dnbd3/hooks/main-warning.inc.php
+++ b/modules-available/dnbd3/hooks/main-warning.inc.php
@@ -8,7 +8,7 @@ if (Dnbd3::isEnabled()) {
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$error = $row['errormsg'] ? $row['errormsg'] : '<unknown error>';
- $lastSeen = date('d.m.Y H:i', $row['dnbd3lastseen']);
+ $lastSeen = Util::prettyTime($row['dnbd3lastseen']);
if ($row['fixedip'] === '<self>') {
Message::addError('dnbd3.main-dnbd3-unreachable', true, $error, $lastSeen);
continue;
diff --git a/modules-available/dnbd3/inc/dnbd3.inc.php b/modules-available/dnbd3/inc/dnbd3.inc.php
index eb41c000..9607c544 100644
--- a/modules-available/dnbd3/inc/dnbd3.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3.inc.php
@@ -3,6 +3,7 @@
class Dnbd3 {
const PROP_ENABLED = 'dnbd3.enabled';
+ const PROP_NFS_FALLBACK = 'dnbd3.nfs-fallback';
public static function isEnabled()
{
@@ -19,6 +20,16 @@ class Dnbd3 {
return $task;
}
+ public static function hasNfsFallback()
+ {
+ return Property::get(self::PROP_NFS_FALLBACK, 0) ? true : false;
+ }
+
+ public static function setNfsFallback($bool)
+ {
+ Property::set(self::PROP_NFS_FALLBACK, $bool ? 1 : 0);
+ }
+
public static function getLocalStatus()
{
diff --git a/modules-available/dnbd3/inc/dnbd3rpc.inc.php b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
index 35d79a31..cdcda508 100644
--- a/modules-available/dnbd3/inc/dnbd3rpc.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
@@ -15,9 +15,11 @@ class Dnbd3Rpc {
* @param bool $clients include client list
* @param bool $images include image list
* @param bool $diskSpace include disk space stats
+ * @param bool $config get config
+ * @param bool $altservers list of alt servers with status
* @return int|array the queried data as an array, or false on error
*/
- public static function query($server, $port, $stats, $clients, $images, $diskSpace)
+ public static function query($server, $port, $stats, $clients, $images, $diskSpace = false, $config = false, $altservers = false)
{
// Special case - local server
if ($server === '<self>') {
@@ -36,6 +38,12 @@ class Dnbd3Rpc {
if ($diskSpace) {
$url .= 'q=space&';
}
+ if ($config) {
+ $url .= 'q=config&';
+ }
+ if ($altservers) {
+ $url .= 'q=altservers&';
+ }
$str = Download::asString($url, 3, $code);
if ($str === false)
return self::QUERY_UNREACHABLE;
diff --git a/modules-available/dnbd3/inc/dnbd3util.inc.php b/modules-available/dnbd3/inc/dnbd3util.inc.php
index a9b9241e..95b6ffe2 100644
--- a/modules-available/dnbd3/inc/dnbd3util.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3util.inc.php
@@ -11,10 +11,10 @@ class Dnbd3Util {
FROM dnbd3_server s
LEFT JOIN machine m USING (machineuuid)');
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if (!is_null($row['clientip'])) {
- $ip = $row['clientip'];
- } elseif (!is_null($row['fixedip'])) {
+ if (!is_null($row['fixedip'])) {
$ip = $row['fixedip'];
+ } elseif (!is_null($row['clientip'])) {
+ $ip = $row['clientip'];
} else {
continue; // Huh?
}
@@ -25,6 +25,9 @@ class Dnbd3Util {
RunMode::setRunMode($row['machineuuid'], 'dnbd3', null, null, null);
continue;
}
+ } elseif ($ip === $satServerIp) {
+ // Manually configured sat server as dnbd3 server - makes no sense
+ continue;
}
$server = array(
'serverid' => $row['serverid'],
@@ -141,7 +144,7 @@ class Dnbd3Util {
$self = Property::getServerIp();
$public[$self] = $self;
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $ip = $row['clientip'] ? $row['clientip'] : $row['fixedip'];
+ $ip = $row['fixedip'] ? $row['fixedip'] : $row['clientip'];
if ($ip === '<self>') {
continue;
}
@@ -171,6 +174,7 @@ class Dnbd3Util {
ConfigHolder::add('SLX_SHUTDOWN_SCHEDULE', '', 1000);
ConfigHolder::add('SLX_REBOOT_TIMEOUT', '', 1000);
ConfigHolder::add('SLX_REBOOT_SCHEDULE', '', 1000);
+ ConfigHolder::add('SLX_SYSTEM_STANDBY_TIMEOUT', '', 1000);
}
/**
diff --git a/modules-available/dnbd3/install.inc.php b/modules-available/dnbd3/install.inc.php
new file mode 100644
index 00000000..c5096cfe
--- /dev/null
+++ b/modules-available/dnbd3/install.inc.php
@@ -0,0 +1,41 @@
+<?php
+
+$res = array();
+
+$res[] = tableCreate('dnbd3_server', "
+ `serverid` int(11) NOT NULL AUTO_INCREMENT,
+ `machineuuid` char(36) CHARACTER SET ascii DEFAULT NULL,
+ `fixedip` varchar(45) CHARACTER SET ascii DEFAULT NULL,
+ `runid` bigint(20) NOT NULL DEFAULT '0',
+ `lastseen` bigint(20) NOT NULL DEFAULT '0',
+ `uptime` bigint(20) NOT NULL DEFAULT '0',
+ `totalup` bigint(20) NOT NULL DEFAULT '0',
+ `totaldown` bigint(20) NOT NULL DEFAULT '0',
+ `lastup` bigint(20) NOT NULL DEFAULT '0',
+ `lastdown` bigint(20) NOT NULL DEFAULT '0',
+ `clientcount` int(11) NOT NULL DEFAULT '0',
+ `disktotal` bigint(20) NOT NULL DEFAULT '0',
+ `diskfree` bigint(20) NOT NULL DEFAULT '0',
+ `errormsg` varchar(200) DEFAULT NULL,
+ PRIMARY KEY (`serverid`),
+ UNIQUE KEY `machineuuid` (`machineuuid`),
+ UNIQUE KEY `fixedip` (`fixedip`)
+");
+
+$res[] = tableCreate('dnbd3_server_x_location', '
+ `serverid` int(11) NOT NULL,
+ `locationid` int(11) NOT NULL,
+ PRIMARY KEY (`serverid`,`locationid`),
+ KEY `locationid` (`locationid`)
+');
+
+$res[] = tableAddConstraint('dnbd3_server', 'machineuuid', 'machine', 'machineuuid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$res[] = tableAddConstraint('dnbd3_server_x_location', 'locationid', 'location', 'locationid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$res[] = tableAddConstraint('dnbd3_server_x_location', 'serverid', 'dnbd3_server', 'serverid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+responseFromArray($res);
diff --git a/modules-available/dnbd3/lang/de/template-tags.json b/modules-available/dnbd3/lang/de/template-tags.json
index 0f9920a2..5406f1b5 100644
--- a/modules-available/dnbd3/lang/de/template-tags.json
+++ b/modules-available/dnbd3/lang/de/template-tags.json
@@ -1,6 +1,8 @@
{
"lang_addServer": "Server hinzuf\u00fcgen",
+ "lang_allowNfsFallback": "NFS-Fallback aktivieren",
"lang_allowedSubnets": "Zum Zugriff freigegebene Subnets",
+ "lang_altservers": "Uplinks",
"lang_backgroundReplication": "Replikation im Hintergrund",
"lang_backgroundReplicationInfo": "Sobald eine VM \u00fcber den Proxy angefragt wird, spiegelt der Proxy im Hintergrund den vollst\u00e4ndigen Inhalt des VM-Abbildes, nicht nur die angefragten Bl\u00f6cke.",
"lang_bytesSent": "Gesendet",
@@ -9,10 +11,11 @@
"lang_clientCount": "Clients",
"lang_clientList": "Liste der Clients",
"lang_clientsByLocation": "Clients nach Raum\/Ort",
+ "lang_comment": "Kommentar",
"lang_count": "Anzahl",
"lang_disabled": "Deaktiviert",
"lang_diskFree": "Freier Speicher",
- "lang_dnbd3IntroText": "DNBD3 ist ganz toll. Einfach einschalten und los gehts, zu beachten gibt es rein gar nichts!",
+ "lang_dnbd3IntroText": "DNBD3 ist ein verteiltes Speichersystem speziell f\u00fcr die Anforderungen von bwLehrpool. Erst in Verbindung mit mindestens einem Proxy (zus\u00e4tzlich zum Satellitenserver) kann das System seine Geschwindigkeitsvorteile gegen\u00fcber NFS\/CIFS ausspielen. F\u00fcr schlecht angebundene Poolr\u00e4ume empfiehlt sich jeweils ein eigener Proxy-Server.\r\nBitte beachten Sie die Hinweise im Wiki.",
"lang_dnbd3Management": "DNBD3 Verwaltung",
"lang_dnbd3Status": "DNBD3 Status",
"lang_editProxyHeading": "Proxy-Einstellungen bearbeiten",
@@ -24,19 +27,31 @@
"lang_externalServerHelp": "Ein externer Server wird nicht \u00fcber den Satellitenserver konfiguriert und verwaltet. Das Installieren, Einrichten und ggf. Aktualisieren der DNBD3-Serversoftware muss manuell durchgef\u00fchrt werden.\r\nDies bietet mehr Flexibilit\u00e4t bei der Konfiguration und Anpassung, z.B. bei der Verwendung von RAID- oder bcache-Setups, oder wenn der DNBD3-Server auf einer Maschine laufen soll, die noch andere Services bereitstellt.\r\nWeitere Informationen dazu finden Sie im Wiki.",
"lang_firewallInfo": "Wird ein Proxy auf einen oder mehrere R\u00e4ume beschr\u00e4nkt, werden Clients aus anderen R\u00e4umen diesen Proxy nicht verwenden. Technisch ist der Zugriff aus anderen R\u00e4umen jedoch trotzdem noch m\u00f6glich. Mit aktivieren dieser Option wird der Zugriff aus anderen R\u00e4umen per Firewall verhindert.",
"lang_firewalled": "Zugriff auf zugewiesene R\u00e4ume beschr\u00e4nken",
+ "lang_flags": "Flags",
"lang_global": "Global",
"lang_lastSeen": "Letzte Aktivit\u00e4t",
+ "lang_latency": "Latenz",
"lang_location": "Ort",
"lang_locations": "Orte",
"lang_manageAccessTo": "Zugriff auf Server festlegen:",
"lang_managedServer": "Automatisch konfigurierter DNBD3-Proxy",
"lang_managedServerAdd": "Automatisch konfigurierten Proxy hinzuf\u00fcgen",
"lang_managedServerHelp": "Automatisch konfigurierte DNBD3-Proxies booten wie gew\u00f6hnliche bwLehrpool-Clients via PXE \u00fcber den Satelliten-Server. Sobald ein bwLehrpool-Client als DNBD3-Proxy konfiguriert wird, erh\u00e4lt er beim Booten eine gesonderte Konfiguration, sodass er fortan exklusiv als DNBD3-Proxy arbeitet, und nicht mehr als Arbeitsstation zur Verf\u00fcgung steht.\r\nDer Vorteil ist, dass die Konfiguration automatisiert erfolgt, und durch w\u00f6chentliche Reboots sichergestellt wird, dass eventuelle Updates des MiniLinux angewendet werden.\r\nIn diesem Fall legen Sie bitte eine Partition mit der ID 45 auf der Festplatte des Proxy-Servers an; diese wird persistent Behandelt und im Gegensatz zur ID44-Partition nicht beim Booten formatiert. Generell sollte diese Partition so gro\u00df wie m\u00f6glich sein, abh\u00e4ngig von der Anzahl der genutzten VMs. Bei Platzmangel l\u00f6scht der Proxy automatisch die VM, die am l\u00e4ngsten nicht verwendet wurde, um neuen VMs Platz zu machen.\r\nWeitere Informationen dazu finden Sie im Wiki.",
- "lang_proxyLocationText": "Bla bla bla BLA!!!!",
+ "lang_numFails": "Fehler",
+ "lang_overrideIp": "Zu verwendende IP-Adresse",
+ "lang_overrideIpInfo": "Normalerweise wird die automatisch per DHCP zugewiesene Adresse auf dem Boot-Interface verwendet. Falls der Proxy mit weiteren Netzwerkkarten ausgestattet ist (die ebenfalls per DHCP konfiguriert werden) kann durch Angabe einer solchen Alternativadresse hier die Verwendung der entsprechenden Karte erzwungen werden.",
+ "lang_proxyConfig": "Konfiguration",
+ "lang_proxyLocationText": "Hier k\u00f6nnen Sie festlegen, dass nur Clients aus bestimmten R\u00e4umen\/Orten diesen Proxy verwenden. Damit vermeiden Sie die Metrikmessung zwischen Client und Proxy, wenn aufgrund der Infrastruktur bereits bekannt ist, dass dieser Proxy nur f\u00fcr bestimmte R\u00e4ume sinnvoll ist. ",
"lang_proxyServerTHead": "Server\/Proxy",
+ "lang_reboot": "Reboot",
+ "lang_rebootProxyHeading": "Proxy neustarten",
+ "lang_rebootProxyText": "Bei \u00c4nderungen an der Konfiguration muss der Server neugestartet werden, damit die \u00c4nderungen wirksam werden.",
"lang_recursiveCount": "Rekursiv",
"lang_rxTotal": "Gesamt empfangen",
"lang_serverList": "Serverliste",
+ "lang_sessionRx": "Seit Neustart empfangen",
+ "lang_sessionTx": "Seit Neustart gesendet",
+ "lang_settings": "Einstellungen",
"lang_storageSize": "Speichergr\u00f6\u00dfe",
"lang_test": "Testen",
"lang_txTotal": "Gesamt gesendet",
diff --git a/modules-available/dnbd3/lang/en/messages.json b/modules-available/dnbd3/lang/en/messages.json
index 4c658e11..731b4ad9 100644
--- a/modules-available/dnbd3/lang/en/messages.json
+++ b/modules-available/dnbd3/lang/en/messages.json
@@ -1,3 +1,11 @@
{
+ "dnbd3-proxy-unreachable": "DNBD3-Proxy {{0}} is offline since {{2}}. ({{1}}) ",
+ "invalid-ipv4": "{{0}} is no valid IPv4-Address",
+ "main-dnbd3-unreachable": "Main DNBD3-Server of the satellite server is offline since {{1}}. ({{0}})",
+ "not-automatic-server": "{{0}} is not managed by this satellite server",
+ "server-added": "Server {{0}} added",
+ "server-already-exists": "Server {{0}} already exists",
+ "server-deleted": "Server {{0}} deleted",
+ "server-non-existent": "Server {{0}} doesn't exist",
"server-unreachable": "Server not reachable"
} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/en/template-tags.json b/modules-available/dnbd3/lang/en/template-tags.json
new file mode 100644
index 00000000..81b9d538
--- /dev/null
+++ b/modules-available/dnbd3/lang/en/template-tags.json
@@ -0,0 +1,60 @@
+{
+ "lang_addServer": "Add server",
+ "lang_allowNfsFallback": "NFS fallback",
+ "lang_allowedSubnets": "Allowed subnets",
+ "lang_altservers": "Uplinks",
+ "lang_backgroundReplication": "Background replication",
+ "lang_backgroundReplicationInfo": "If a VM is requested by this proxy, the proxy mirrors the complete VM in the background, not only the requested data blocks.",
+ "lang_bytesSent": "Sent",
+ "lang_changeDnbd3Status": "Enable\/disable DNBD3",
+ "lang_client": "Client",
+ "lang_clientCount": "Clients",
+ "lang_clientList": "List of clients",
+ "lang_clientsByLocation": "Clients by location",
+ "lang_comment": "Comment",
+ "lang_count": "Count",
+ "lang_disabled": "Disabled",
+ "lang_diskFree": "Free space",
+ "lang_dnbd3IntroText": "DNBD3 is a distributed storage system for the special requirements of bwLehrpool. To see speed improvements over NFS\/CIFS you need at least one proxy (besides the satellite server). If you have locations with slow network connection it's advisable to have one proxy per location. Please mind the information in the wiki.",
+ "lang_dnbd3Management": "DNBD3 management",
+ "lang_dnbd3Status": "DNBD3 status",
+ "lang_editProxyHeading": "Edit proxy settings",
+ "lang_enableDnbd3": "Enable DNBD3",
+ "lang_enabled": "Enabled",
+ "lang_enterIpOfServer": "Please enter the ip address ot the server",
+ "lang_externalServer": "External DNBD3-Server",
+ "lang_externalServerAdd": "Add external server",
+ "lang_externalServerHelp": "An external server is not configured and managed by the satellite server. The installation, configuration and update of the DNBD3 software has to be done manually. This is a more flexible approach which is preferable for RAID or bcache setups or if the DNBD3-Server offers additional services. More information in the wiki.",
+ "lang_firewallInfo": "If the proxy is restricted to one or more locations, clients from other locations won't use that specific proxy. But technically it's still possible to gain access from other locations. If you activate this setting the access from other locations is blocked with iptables.",
+ "lang_firewalled": "Limit access to corresponding locations",
+ "lang_flags": "Flags",
+ "lang_global": "Global",
+ "lang_lastSeen": "Last seen",
+ "lang_latency": "Latency",
+ "lang_location": "Location",
+ "lang_locations": "Locations",
+ "lang_manageAccessTo": "Manage access to server:",
+ "lang_managedServer": "Automatically configured DNBD3-Proxy",
+ "lang_managedServerAdd": "Add automatically configured proxy",
+ "lang_managedServerHelp": "Automatically configured DNBD3-Proxies will boot like normal bwLehrpool-Clients over PXE and the satellite server. If a client is configured as proxy it will boot with a different configuration and acts exclusively as proxy. The client can therefore not be used as a normal working station.\r\nThe advantage is that you don't need to install or configure anything else. The client will reboot every week to get possible updates ot the minilinux.\r\nIf you want to use this feature, please create a partition with ID 45 on the local hard disk of the proxy server. In contrast to the ID 44 partition which is formated after every reboot, this partition is persistent. As a rule of thumb the partition should be as big as possible. If there is no space left the proxy will delete the VM which hasn't be used for the longest time. More information in the wiki.",
+ "lang_numFails": "Errors",
+ "lang_overrideIp": "IP address to use",
+ "lang_overrideIpInfo": "Usually the address that the DHCP server assigns to the boot interface of the proxy will be used. If the proxy has multiple interfaces (that also get an address assigned via DHCP) you can specify that address here to enforce their usage instead.",
+ "lang_proxyConfig": "Configuration",
+ "lang_proxyLocationText": "Here you can restrict the usage of this proxy to certain locations. This can be useful if the usage is only reasonable from some locations. That may be because of the network infrastructure.",
+ "lang_proxyServerTHead": "Server\/Proxy",
+ "lang_reboot": "Reboot",
+ "lang_rebootProxyHeading": "Reboot proxy",
+ "lang_rebootProxyText": "If the configuration has changed, the server needs a reboot to adapt to it.",
+ "lang_recursiveCount": "Recursive",
+ "lang_rxTotal": "Total received",
+ "lang_serverList": "List of servers",
+ "lang_sessionRx": "Received since boot",
+ "lang_sessionTx": "Sent since boot",
+ "lang_settings": "Settings",
+ "lang_storageSize": "Storage size",
+ "lang_test": "Test",
+ "lang_txTotal": "Total sent",
+ "lang_uptime": "Uptime",
+ "lang_wantToDelete": "Do you really want to delete this server? (Reboot\/Shutdown has to be done manually)"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/page.inc.php b/modules-available/dnbd3/page.inc.php
index 946df939..39500f83 100644
--- a/modules-available/dnbd3/page.inc.php
+++ b/modules-available/dnbd3/page.inc.php
@@ -39,6 +39,34 @@ class Page_Dnbd3 extends Page
}
$bgr = Request::post('bgr', false, 'bool');
$firewall = Request::post('firewall', false, 'bool');
+ $overrideIp = false;
+ $sip = Request::post('fixedip', null, 'string');
+ if (empty($sip)) {
+ $overrideIp = null;
+ } elseif ($server['fixedip'] !== $overrideIp) {
+ $ip = ip2long(trim($sip));
+ if ($ip !== false) {
+ $ip = long2ip($ip);
+ }
+ if ($ip === false) {
+ Message::addError('invalid-ipv4', $sip);
+ return;
+ }
+ $res = Database::queryFirst('SELECT serverid FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ WHERE s.fixedip = :ip OR m.clientip = :ip', compact('ip'));
+ if ($res !== false) {
+ Message::addError('server-already-exists', $ip);
+ return;
+ }
+ $overrideIp = $ip;
+ }
+ if ($overrideIp !== false) {
+ Database::exec('UPDATE dnbd3_server SET fixedip = :fixedip WHERE machineuuid = :uuid', array(
+ 'uuid' => $server['machineuuid'],
+ 'fixedip' => $overrideIp,
+ ));
+ }
RunMode::setRunMode($server['machineuuid'], 'dnbd3', 'proxy',
json_encode(compact('bgr', 'firewall')), false);
}
@@ -46,7 +74,9 @@ class Page_Dnbd3 extends Page
private function toggleUsage()
{
$enabled = Request::post('enabled', false, 'bool');
+ $nfs = Request::post('with-nfs', false, 'bool');
$task = Dnbd3::setEnabled($enabled);
+ Dnbd3::setNfsFallback($nfs);
Taskmanager::waitComplete($task, 5000);
}
@@ -113,8 +143,8 @@ class Page_Dnbd3 extends Page
protected function doRender()
{
$show = Request::get('show', false, 'string');
- if ($show === 'clients') {
- $this->showClientList();
+ if ($show === 'proxy') {
+ $this->showProxyDetails();
} elseif ($show === 'locations') {
$this->showServerLocationEdit();
} elseif ($show === false) {
@@ -144,7 +174,7 @@ class Page_Dnbd3 extends Page
if ($server['uptime'] != 0) {
$server['uptime'] += ($NOW - $server['dnbd3lastseen']);
}
- $server['dnbd3lastseen_s'] = $server['dnbd3lastseen'] ? date('d.m.Y H:i', $server['dnbd3lastseen']) : '-';
+ $server['dnbd3lastseen_s'] = $server['dnbd3lastseen'] ? Util::prettyTime($server['dnbd3lastseen']) : '-';
$server['uptime_s'] = $server['uptime'] ? floor($server['uptime'] / 86400) . 'd ' . gmdate('H:i', $server['uptime']) : '-';
$server['totalup_s'] = Util::readableFileSize($server['totalup']);
$server['totaldown_s'] = Util::readableFileSize($server['totaldown']);
@@ -181,28 +211,48 @@ class Page_Dnbd3 extends Page
Render::addTemplate('page-serverlist', array(
'list' => $servers,
'enabled' => Dnbd3::isEnabled(),
- 'checked_s' => Dnbd3::isEnabled() ? 'checked' : '',
+ 'enabled_checked_s' => Dnbd3::isEnabled() ? 'checked' : '',
+ 'nfs_checked_s' => Dnbd3::hasNfsFallback() ? 'checked' : '',
'rebootcontrol' => Module::isAvailable('rebootcontrol', false)
));
}
- private function showClientList()
+ private function showProxyDetails()
{
$server = $this->getServerById();
- Render::addTemplate('page-header-servername', $server);
- $data = Dnbd3Rpc::query($server['ip'], 5003,false, true, false, false);
- if (!is_array($data) || !isset($data['clients'])) {
+ Render::addTemplate('page-proxy-header', $server);
+ $stats = Dnbd3Rpc::query($server['ip'], 5003,true, true, false, true);
+ if (!is_array($stats) || !isset($stats['runId'])) {
Message::addError('server-unreachable');
return;
}
+ $stats['bytesSent_s'] = Util::readableFileSize($stats['bytesSent']);
+ $stats['bytesReceived_s'] = Util::readableFileSize($stats['bytesReceived']);
+ $stats['uptime_s'] = floor($stats['uptime'] / 86400) . 'd ' . gmdate('H:i:s', $stats['uptime']);
+ Render::addTemplate('page-proxy-stats', $stats);
+ $images = Dnbd3Rpc::query($server['ip'], 5003,false, false, true);
+ $confAlts = Dnbd3Rpc::query($server['ip'], 5003,false, false, false, false, true, true);
$ips = array();
$sort = array();
- foreach ($data['clients'] as &$c) {
+ foreach ($stats['clients'] as &$c) {
$c['bytesSent_s'] = Util::readableFileSize($c['bytesSent']);
$sort[] = $c['bytesSent'];
$ips[] = preg_replace('/:\d+$/', '', $c['address']);
}
- array_multisort($sort, SORT_DESC, $data['clients']);
+ array_multisort($sort, SORT_DESC, $stats['clients']);
+ Render::openTag('div', ['class' => 'row']);
+ // Config
+ if (is_string($confAlts['config'])) {
+ Render::addTemplate('page-proxy-config', $confAlts);
+ }
+ if (is_array($confAlts['altservers'])) {
+ foreach ($confAlts['altservers'] as &$as) {
+ $as['rtt'] = round(array_sum($as['rtt']) / count($as['rtt']) / 1000, 2);
+ }
+ unset($as);
+ Render::addTemplate('page-proxy-altservers', $confAlts);
+ }
+ Render::closeTag('div');
Render::openTag('div', ['class' => 'row']);
// Count locations
$res = Database::simpleQuery('SELECT locationid, Count(*) AS cnt FROM machine WHERE clientip IN (:ips) GROUP BY locationid', compact('ips'));
@@ -234,9 +284,9 @@ class Page_Dnbd3 extends Page
}
if ($showLocs) {
$locCount = array_filter($locCount, function ($v) { return isset($v['keep']); });
- Render::addTemplate('page-client-loclist', array('list' => array_values($locCount)));
+ Render::addTemplate('page-proxy-loclist', array('list' => array_values($locCount)));
}
- Render::addTemplate('page-clientlist', $data);
+ Render::addTemplate('page-proxy-clients', $stats);
Render::closeTag('div');
}
@@ -288,10 +338,10 @@ class Page_Dnbd3 extends Page
Message::addError('server-non-existent', 'server');
Util::redirect('?do=dnbd3');
}
- if (!is_null($server['clientip'])) {
- $server['ip'] = $server['clientip'];
- } elseif (!is_null($server['fixedip'])) {
+ if (!is_null($server['fixedip'])) {
$server['ip'] = $server['fixedip'];
+ } elseif (!is_null($server['clientip'])) {
+ $server['ip'] = $server['clientip'];
} else {
$server['ip'] = '127.0.0.1';
}
diff --git a/modules-available/dnbd3/templates/fragment-server-settings.html b/modules-available/dnbd3/templates/fragment-server-settings.html
index 61b0626d..be3e74e2 100644
--- a/modules-available/dnbd3/templates/fragment-server-settings.html
+++ b/modules-available/dnbd3/templates/fragment-server-settings.html
@@ -2,13 +2,21 @@
<div class="checkbox">
<input type="checkbox" name="bgr" id="bgr" {{#bgr}}checked{{/bgr}}>
- <label for="bgr">{{lang_backgroundReplication}}</label>
+ <label for="bgr"><b>{{lang_backgroundReplication}}</b></label>
</div>
<i>{{lang_backgroundReplicationInfo}}</i>
-<br>
+<br><br>
+
<div class="checkbox">
<input type="checkbox" name="firewall" id="firewall" {{#firewall}}checked{{/firewall}}>
- <label for="firewall">{{lang_firewalled}}</label>
+ <label for="firewall"><b>{{lang_firewalled}}</b></label>
</div>
<i>{{lang_firewallInfo}}</i>
+<br><br>
+
+<div>
+ <label for="fixedip">{{lang_overrideIp}}</label>
+ <input class="form-control" type="text" name="fixedip" id="fixedip" value="{{fixedip}}">
+</div>
+<i>{{lang_overrideIpInfo}}</i>
<br>
diff --git a/modules-available/dnbd3/templates/page-proxy-altservers.html b/modules-available/dnbd3/templates/page-proxy-altservers.html
new file mode 100644
index 00000000..00a884cc
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-altservers.html
@@ -0,0 +1,36 @@
+<div class="col-md-6">
+ <h2>{{lang_altservers}}</h2>
+ <table class="table table-condensed">
+ <tr>
+ <th>{{lang_proxyServerTHead}}</th>
+ <th>{{lang_numFails}}</th>
+ <th class="text-right">{{lang_latency}}</th>
+ <th>{{lang_flags}}</th>
+ <th>{{lang_comment}}</th>
+ </tr>
+ {{#altservers}}
+ <tr>
+ <td class="text-nowrap">{{host}}</td>
+ <td>
+ {{^isClientOnly}}
+ {{numFails}}
+ {{/isClientOnly}}
+ </td>
+ <td class="text-right text-nowrap">
+ {{^isClientOnly}}
+ {{rtt}}&thinsp;ms
+ {{/isClientOnly}}
+ </td>
+ <td>
+ {{#isClientOnly}}
+ [CO]
+ {{/isClientOnly}}
+ {{#isPrivate}}
+ [PO]
+ {{/isPrivate}}
+ </td>
+ <td><table class="slx-ellipsis"><tr><td>{{comment}}</td></tr></table></td>
+ </tr>
+ {{/altservers}}
+ </table>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-clientlist.html b/modules-available/dnbd3/templates/page-proxy-clients.html
index 9e7cec4c..9e7cec4c 100644
--- a/modules-available/dnbd3/templates/page-clientlist.html
+++ b/modules-available/dnbd3/templates/page-proxy-clients.html
diff --git a/modules-available/dnbd3/templates/page-proxy-config.html b/modules-available/dnbd3/templates/page-proxy-config.html
new file mode 100644
index 00000000..adc73a57
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-config.html
@@ -0,0 +1,4 @@
+<div class="col-md-6">
+ <h2>{{lang_proxyConfig}}</h2>
+ <pre>{{config}}</pre>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-header-servername.html b/modules-available/dnbd3/templates/page-proxy-header.html
index 6f3f1b7f..6f3f1b7f 100644
--- a/modules-available/dnbd3/templates/page-header-servername.html
+++ b/modules-available/dnbd3/templates/page-proxy-header.html
diff --git a/modules-available/dnbd3/templates/page-client-loclist.html b/modules-available/dnbd3/templates/page-proxy-loclist.html
index 67c90683..67c90683 100644
--- a/modules-available/dnbd3/templates/page-client-loclist.html
+++ b/modules-available/dnbd3/templates/page-proxy-loclist.html
diff --git a/modules-available/dnbd3/templates/page-proxy-stats.html b/modules-available/dnbd3/templates/page-proxy-stats.html
new file mode 100644
index 00000000..e7811028
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-stats.html
@@ -0,0 +1,9 @@
+<div class="panel panel-default">
+ <div class="panel-body">
+ {{lang_sessionTx}}: <b>{{bytesSent_s}}</b>
+ ––
+ {{lang_sessionRx}}: <b>{{bytesReceived_s}}</b>
+ ––
+ {{lang_uptime}}: <b>{{uptime_s}}</b>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-server-locations.html b/modules-available/dnbd3/templates/page-server-locations.html
index 20dddaac..7d7ddaf1 100644
--- a/modules-available/dnbd3/templates/page-server-locations.html
+++ b/modules-available/dnbd3/templates/page-server-locations.html
@@ -35,7 +35,7 @@
</div>
<div class="buttonbar text-right">
- <button type="submit" class="btn btn-success" name="action" value="savelocations">
+ <button type="submit" class="btn btn-primary" name="action" value="savelocations">
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
</button>
diff --git a/modules-available/dnbd3/templates/page-serverlist.html b/modules-available/dnbd3/templates/page-serverlist.html
index 1a4d8255..c5905dcd 100644
--- a/modules-available/dnbd3/templates/page-serverlist.html
+++ b/modules-available/dnbd3/templates/page-serverlist.html
@@ -15,9 +15,13 @@
<form method="post" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
<div class="checkbox">
- <input id="enable-dnbd3" type="checkbox" name="enabled" {{checked_s}}>
+ <input id="enable-dnbd3" type="checkbox" name="enabled" {{enabled_checked_s}}>
<label for="enable-dnbd3">{{lang_enableDnbd3}}</label>
</div>
+ <div class="checkbox">
+ <input id="allow-nfs" type="checkbox" name="with-nfs" {{nfs_checked_s}}>
+ <label for="allow-nfs">{{lang_allowNfsFallback}}</label>
+ </div>
<button type="submit" name="action" value="toggle-usage" class="btn btn-success">
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
@@ -58,7 +62,7 @@
<tbody>
{{#list}}
<tr>
- <td class="text-right">
+ <td class="text-right text-nowrap">
{{#slxOk}}
<span class="glyphicon glyphicon-ok text-success"></span>
{{/slxOk}}
@@ -73,11 +77,16 @@
{{/uptime}}
</td>
<td class="{{#self}}slx-bold{{/self}}">
- {{fixedip}}
{{#machineuuid}}
- <a href="?do=Statistics&uuid={{machineuuid}}">{{clientip}}</a>
- <div class="small">{{hostname}}</div>
+ <a class="pull-right btn btn-default btn-xs" href="?do=Statistics&uuid={{machineuuid}}">
+ <span class="glyphicon glyphicon-eye-open"></span>
+ </a>
{{/machineuuid}}
+ <a href="?do=dnbd3&amp;show=proxy&amp;server={{serverid}}">
+ {{fixedip}}
+ <span class="small">{{clientip}}</span>
+ </a>
+ <div class="small">{{hostname}}</div>
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{disktotal}}">
<div style="border:1px solid #ddd;background:linear-gradient(to right, #f85 {{diskUsePercent}}%, transparent {{diskUsePercent}}%)"
@@ -92,12 +101,7 @@
{{/errormsg}}
</td>
<td data-sort="int" data-sort-default="desc" class="text-right">
- {{#uptime}}
- <a href="?do=dnbd3&amp;show=clients&amp;server={{serverid}}">{{clientcount}}</a>
- {{/uptime}}
- {{^uptime}}
- -
- {{/uptime}}
+ {{clientcount}}
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{dnbd3lastseen}}" class="text-right text-nowrap">
{{dnbd3lastseen_s}}
@@ -124,7 +128,7 @@
</a>
{{/self}}
</td>
- <td class="text-right">
+ <td class="text-right text-nowrap">
{{#machineuuid}}
{{#rebootcontrol}}
<button class="btn btn-warning btn-xs reboot-btn" type="button" data-id="{{serverid}}"
@@ -241,7 +245,7 @@
<h4 class="modal-title"><b>{{lang_rebootProxyHeading}}</b></h4>
</div>
<div class="modal-body">
- <p>{{lang_rebootServerText}}</p>
+ <p>{{lang_rebootProxyText}}</p>
<p id="reboot-status"></p>
</div>
<div class="modal-footer text-right">
diff --git a/modules-available/dozmod/api.inc.php b/modules-available/dozmod/api.inc.php
index de0883a6..74aaa003 100644
--- a/modules-available/dozmod/api.inc.php
+++ b/modules-available/dozmod/api.inc.php
@@ -149,10 +149,12 @@ function getListForLocations($locationIds, $raw)
$key = 'lectures_' . cache_hash($locationIds);
$examMode = Request::get('exams', 'normal-mode', 'string') !== 'normal-mode';
$clientServerMismatch = false;
- if ($raw && Module::isAvailable('exams')) {
+ if (Module::isAvailable('exams')) {
// If we have the exam mode module, we can enforce a server side check and make sure it agrees with the client
$serverExamMode = Exams::isInExamMode($locationIds);
- $clientServerMismatch = ($serverExamMode !== $examMode);
+ if ($raw) {
+ $clientServerMismatch = ($serverExamMode !== $examMode);
+ }
$examMode = $serverExamMode;
}
// Only enforce exam mode validity check if the client requests the raw xml data
@@ -276,7 +278,7 @@ if (substr($ip, 0, 7) === '::ffff:') {
/* lookup location id(s) */
-$location_ids = Location::getFromIp($ip);
+$location_ids = Location::getFromIp($ip, true);
$location_ids = Location::getLocationRootChain($location_ids);
if ($resource === 'list') {
diff --git a/modules-available/dozmod/inc/pagedozmodusers.inc.php b/modules-available/dozmod/inc/pagedozmodusers.inc.php
index 8da07923..621f7d34 100644
--- a/modules-available/dozmod/inc/pagedozmodusers.inc.php
+++ b/modules-available/dozmod/inc/pagedozmodusers.inc.php
@@ -16,11 +16,22 @@ class Page_dozmod_users extends Page
protected function doAjax()
{
+ User::load();
+
$action = Request::post('action', '', 'string');
if ($action === 'setmail' || $action === 'setsu' || $action == 'setlogin') {
- $this->setUserOption($action);
+ if (User::hasPermission("users.".$action)) {
+ $this->setUserOption($action);
+ } else {
+ die("No permission.");
+ }
+
} elseif ($action === 'setorglogin') {
- $this->setOrgOption($action);
+ if (User::hasPermission("users.orglogin")) {
+ $this->setOrgOption($action);
+ } else {
+ die("No permission.");
+ }
} else {
die('No such action');
}
diff --git a/modules-available/dozmod/inc/pagemailtemplates.inc.php b/modules-available/dozmod/inc/pagemailtemplates.inc.php
index dc41d8c6..90734a50 100644
--- a/modules-available/dozmod/inc/pagemailtemplates.inc.php
+++ b/modules-available/dozmod/inc/pagemailtemplates.inc.php
@@ -7,13 +7,25 @@ class Page_mail_templates extends Page
protected function doPreprocess()
{
+ User::load();
+
$action = Request::post('action', 'show', 'string');
if ($action === 'show') {
$this->fetchTemplates();
} elseif ($action === 'save') {
- $this->handleSave();
+ if (User::hasPermission("templates.save")) {
+ $this->handleSave();
+ } else {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=dozmod&section=templates');
+ }
} elseif ($action === 'reset') {
- $this->handleReset();
+ if(User::hasPermission("templates.reset")) {
+ $this->handleReset();
+ } else {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=dozmod&section=templates');
+ }
} else {
Message::addError('main.invalid-action', $action);
Util::redirect('?do=dozmod&section=templates');
diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json
index 97ab0a21..4b49579a 100644
--- a/modules-available/dozmod/lang/de/template-tags.json
+++ b/modules-available/dozmod/lang/de/template-tags.json
@@ -4,6 +4,7 @@
"lang_allowLoginDescription": "Wenn diese Option aktiviert ist, k\u00f6nnen sich alle Mitarbeiter der Einrichtung \u00fcber die bwLehrpool-Suite anmelden und VMs\/Veranstaltungen verwalten. Wenn Sie diese Option deaktivieren, m\u00fcssen Sie in der Untersektion \"Benutzer und Berechtigungen\" jeden Benutzer nach dem ersten Loginversuch manuell freischalten.",
"lang_asteriskRequired": "Felder mit (*) sind erforderlich",
"lang_blockCount": "Anzahl Bl\u00f6cke",
+ "lang_bwlehrpoolsuite": "bwLehrpool-Suite",
"lang_canLoginOrganization": "Nutzer dieser Einrichtung k\u00f6nnen sich am Satelliten anmelden",
"lang_canLoginUser": "Nutzer kann sich am Satelliten anmelden",
"lang_createTime": "Erstellt",
@@ -44,6 +45,7 @@
"lang_mailTemplates": "E-Mail Templates",
"lang_maxImageValidity": "G\u00fcltigkeitsdauer neuer VM-Versionen (Tage)",
"lang_maxLectureVisibility": "Sp\u00e4testes Enddatum einer Veranstaltung (Tage in der Zukunft)",
+ "lang_maxLocationsPerLecture": "Max. explizite Orte pro Veranstaltung",
"lang_maxTransfers": "Maximale Zahl gleichzeitiger Up-\/Downloads pro Benutzer",
"lang_miscOptions": "Verschiedene Einstellungen",
"lang_modified": "Modifiziert",
@@ -53,11 +55,12 @@
"lang_os": "Betriebssystem",
"lang_owner": "Besitzer",
"lang_password": "Passwort",
+ "lang_passwordplaceholder": "SMTP Passwort",
"lang_placeholders": "Platzhalter",
"lang_port": "Port",
"lang_reallyResetTemplates": "Sind Sie sicher, dass Sie alle Texte l\u00f6schen und auf die Standardwerte zur\u00fccksetzen wollen?",
"lang_replaceWithOriginal": "Originaltext in Textbox laden",
- "lang_replyTo": "Reply-To Adresse (z.B. Helpdesk)",
+ "lang_replyTo": "Reply-To Adresse",
"lang_runtimeConfig": "Laufzeit-Konfiguration",
"lang_runtimeConfigHeadline": "Laufzeit-Konfiguration",
"lang_runtimeConfigLimits": "Beschr\u00e4nkungen",
@@ -84,7 +87,8 @@
"lang_userList": "Benutzerliste",
"lang_userListDescription": "Hier k\u00f6nnen Sie individuelle Nutzer zu \"Super-Usern\" machen. Diese haben in der bwLehrpool-Suite auf alle Veranstaltungen und VMs Vollzugriff, unabh\u00e4ngig von den gesetzten Berechtigungen. Au\u00dferdem k\u00f6nnen Sie hier Benutzer vom Zugriff mittels der bwLehrpool-Suite ausschlie\u00dfen.",
"lang_userListHeader": "Dem Satelliten bekannte Benutzer",
- "lang_username": "Benutzername (SMTP-Auth)",
+ "lang_username": "Benutzername",
+ "lang_usernameplaceholder": "SMTP Benutzername",
"lang_version": "Version vom",
"lang_when": "Wann"
} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/en/template-tags.json b/modules-available/dozmod/lang/en/template-tags.json
index e2e085b4..ed8f3465 100644
--- a/modules-available/dozmod/lang/en/template-tags.json
+++ b/modules-available/dozmod/lang/en/template-tags.json
@@ -4,6 +4,7 @@
"lang_allowLoginDescription": "If this option is enabled, all members of the organization marked as staff or employee are allowed to login to this server and manage VMs\/courses. Otherwise, new users need to be individually allowed access after their first login attempt by visiting the sub page \"users and permissions\" in this web interface.",
"lang_asteriskRequired": "Fields marked with (*) are required",
"lang_blockCount": "Block count",
+ "lang_bwlehrpoolsuite": "bwLehrpool-Suite",
"lang_canLoginOrganization": "Users from this organization can login",
"lang_canLoginUser": "This user can login",
"lang_createTime": "Created",
@@ -28,7 +29,7 @@
"lang_followingPlaceholdersUnused": "The following placeholders are not being used",
"lang_hasNewer": "Newer version exists",
"lang_hash": "Hash",
- "lang_heading": "Images marked for deletion",
+ "lang_heading": "Images Marked for Deletion",
"lang_host": "Host",
"lang_image": "VM",
"lang_lastEditor": "Edited by",
@@ -39,27 +40,29 @@
"lang_lecturePermissionEdit": "Edit",
"lang_loadDefaults": "Reset all templates to their defaults",
"lang_mailConfig": "SMTP configuration for sending mails",
- "lang_mailConfigHeadline": "email configuration",
+ "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_mailTemplates": "E-Mail templates",
+ "lang_mailTemplates": "E-Mail Templates",
"lang_maxImageValidity": "New VM validity (days)",
"lang_maxLectureVisibility": "Max time lecture end date may lie in the future (days)",
+ "lang_maxLocationsPerLecture": "Max. explicit locations per lecture",
"lang_maxTransfers": "Max concurrent transfers per user",
"lang_miscOptions": "Misc options",
"lang_modified": "modified",
"lang_organization": "Organization",
- "lang_organizationList": "List of organizations",
+ "lang_organizationList": "List of Organizations",
"lang_organizationListHeader": "Set access permissions for organizations",
"lang_os": "Operating System",
"lang_owner": "Owner",
"lang_password": "Password",
+ "lang_passwordplaceholder": "SMTP Password",
"lang_placeholders": "Placeholders",
"lang_port": "Port",
"lang_reallyResetTemplates": "Are you sure you want to reset all texts to their default values?",
"lang_replaceWithOriginal": "load original text into text box",
"lang_replyTo": "Reply-To address",
"lang_runtimeConfig": "Limits and Defaults",
- "lang_runtimeConfigHeadline": "Configure limits and defaults for bwLehrpool-Suite",
+ "lang_runtimeConfigHeadline": "Configure Limits and Defaults for bwLehrpool-Suite",
"lang_runtimeConfigLimits": "Limitations",
"lang_senderAddress": "Sender address",
"lang_senderName": "Sender's display name",
@@ -81,10 +84,11 @@
"lang_updateTime": "Last update",
"lang_user": "User name",
"lang_userId": "User id",
- "lang_userList": "User list",
+ "lang_userList": "User List",
"lang_userListDescription": "Here you can promote \"super users\", which will have all permissions in the bwLehrpool-Suite. You can also ban users from accessing this server via the bwLehrpool-Suite.",
"lang_userListHeader": "Users known to this satellite",
- "lang_username": "User name (SMTP auth)",
+ "lang_username": "Username",
+ "lang_usernameplaceholder": "SMTP Username",
"lang_version": "Version timestamp",
"lang_when": "When"
} \ No newline at end of file
diff --git a/modules-available/dozmod/page.inc.php b/modules-available/dozmod/page.inc.php
index 9c247770..93d38f48 100644
--- a/modules-available/dozmod/page.inc.php
+++ b/modules-available/dozmod/page.inc.php
@@ -27,7 +27,7 @@ class Page_DozMod extends Page
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
@@ -50,15 +50,30 @@ class Page_DozMod extends Page
$action = Request::post('action', false, 'string');
if ($action === 'mail') {
- $this->mailHandler();
+ if (User::hasPermission("mail.save")) {
+ $this->mailHandler();
+ } else {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=dozmod&section=mailconfig');
+ }
} elseif ($action === 'runtime') {
- $this->runtimeHandler();
+ if (User::hasPermission("runtimeconfig.save")) {
+ $this->runtimeHandler();
+ } else {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=dozmod&section=runtimeconfig');
+ }
} elseif ($action === 'delimages') {
- $result = $this->handleDeleteImages();
- if (!empty($result)) {
- Message::addInfo('delete-images', $result);
+ if (User::hasPermission("images.delete")) {
+ $result = $this->handleDeleteImages();
+ if (!empty($result)) {
+ Message::addInfo('delete-images', $result);
+ }
+ Util::redirect('?do=DozMod');
+ } else {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=dozmod');
}
- Util::redirect('?do=DozMod');
} elseif ($action !== false) {
Util::traceError('Invalid action: ' . $action);
}
@@ -180,6 +195,7 @@ class Page_DozMod extends Page
$row['name_extra_class'] = 'slx-strike';
}
$row['version'] = date('d.m.Y H:i:s', $row['createtime']);
+ $row['rawfilesize'] = $row['filesize'];
$row['filesize'] = Util::readableFileSize($row['filesize']);
$rows[] = $row;
}
@@ -203,8 +219,6 @@ class Page_DozMod extends Page
protected function doAjax()
{
User::load();
- if (!User::hasPermission('superadmin'))
- return;
$this->setupSubPage();
if ($this->subPage !== false) {
@@ -213,10 +227,19 @@ class Page_DozMod extends Page
}
$action = Request::post('action');
+
if ($action === 'mail') {
- $this->handleTestMail();
+ if (User::hasPermission("mail.testmail")) {
+ $this->handleTestMail();
+ } else {
+ die('No permission');
+ }
} elseif ($action === 'delimages') {
- die($this->handleDeleteImages());
+ if (User::hasPermission("images.delete")) {
+ die($this->handleDeleteImages());
+ } else {
+ die('No permission');
+ }
} elseif ($action === 'getblockinfo') {
$this->ajaxGetBlockInfo();
}
@@ -328,8 +351,9 @@ class Page_DozMod extends Page
$params = [
'int' => [
- 'maxImageValidityDays' => array('min' => 7, 'max' => 999),
- 'maxLectureValidityDays' => array('min' => 7, 'max' => 999),
+ 'maxImageValidityDays' => array('min' => 7, 'max' => 9999),
+ 'maxLectureValidityDays' => array('min' => 7, 'max' => 9999),
+ 'maxLocationsPerLecture' => array('min' => 0, 'max' => 999),
'maxTransfers' => array('min' => 1, 'max' => 10),
],
'bool' => [
diff --git a/modules-available/dozmod/permissions/permissions.json b/modules-available/dozmod/permissions/permissions.json
new file mode 100644
index 00000000..8295d0f0
--- /dev/null
+++ b/modules-available/dozmod/permissions/permissions.json
@@ -0,0 +1,12 @@
+{
+ "images.delete": "Delete images marked for deletion.",
+ "mail.save": "Save SMTP configuration for sending mails.",
+ "mail.testmail": "Send a testmail.",
+ "runtimeconfig.save": "Save limits and defaults of a runtime configuration.",
+ "templates.save": "Save email templates",
+ "templates.reset": "Reset email templates",
+ "users.setmail": "Enable/Disable Email Notification",
+ "users.setlogin": "Enable/Disable Login",
+ "users.setsu": "Set User to superuser",
+ "users.orglogin": "Enalbe/Disable Login for Users from certain organisations."
+} \ No newline at end of file
diff --git a/modules-available/dozmod/style.css b/modules-available/dozmod/style.css
index 22d769ed..23dd121e 100644
--- a/modules-available/dozmod/style.css
+++ b/modules-available/dozmod/style.css
@@ -30,8 +30,22 @@
.table-input-group tr.input-group input.form-control {
width: auto;
+ min-width: 95px;
}
.table-input-group tr.input-group td:last-child input {
border-radius: 0px 4px 4px 0px;
}
+
+.input-group-addon {
+ min-width:200px;
+ text-align: left;
+}
+
+.table > tbody > tr > td {
+ vertical-align: middle;
+}
+
+.table > tbody > tr > td > div {
+ display: inline-block;
+} \ No newline at end of file
diff --git a/modules-available/dozmod/templates/actionlog-log.html b/modules-available/dozmod/templates/actionlog-log.html
index 151d2545..09f3a183 100644
--- a/modules-available/dozmod/templates/actionlog-log.html
+++ b/modules-available/dozmod/templates/actionlog-log.html
@@ -1,41 +1,45 @@
-<table class="table table-striped table-bordered">
- <tr>
- <th class="text-nowrap">{{lang_when}}</th>
- {{#showActor}}
- <th class="text-nowrap">{{lang_user}}</th>
- {{/showActor}}
- {{#showTarget}}
- <th class="text-nowrap">{{lang_actionTarget}}</th>
- {{/showTarget}}
- <th class="text-nowrap">{{lang_event}}</th>
- </tr>
- {{#events}}
- <tr>
- <td class="text-nowrap">{{dateline_s}}</td>
- {{#showActor}}
- <td style="min-width:140px">
- {{#uuserid}}
- <a href="?do=dozmod&amp;section=actionlog&amp;action=showuser&amp;uuid={{uuserid}}">{{ulastname}}, {{ufirstname}}</a>
- {{/uuserid}}
- {{^uuserid}}
- {{lang_system}}
- {{/uuserid}}
- </td>
- {{/showActor}}
- {{#showTarget}}
- <td>
- {{#targeturl}}
- <a href="{{targeturl}}">{{targetname}}</a>
- {{/targeturl}}
- {{^targeturl}}
- {{targetname}}
- {{^targetname}}
- <span class="small">{{targetid}}</span>
- {{/targetname}}
- {{/targeturl}}
- </td>
- {{/showTarget}}
- <td>{{description}}</td>
- </tr>
- {{/events}}
+<table style="table-layout: fixed; width: 100%" class="table table-striped table-bordered stupidtable">
+ <thead>
+ <tr>
+ <th class="text-nowrap" data-sort="int">{{lang_when}}</th>
+ {{#showActor}}
+ <th class="text-nowrap" data-sort="string">{{lang_user}}</th>
+ {{/showActor}}
+ {{#showTarget}}
+ <th class="text-nowrap" data-sort="string">{{lang_actionTarget}}</th>
+ {{/showTarget}}
+ <th class="text-nowrap" data-sort="string">{{lang_event}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#events}}
+ <tr>
+ <td class="text-nowrap" data-sort-value="{{dateline}}">{{dateline_s}}</td>
+ {{#showActor}}
+ <td style="min-width:140px">
+ {{#uuserid}}
+ <a href="?do=dozmod&amp;section=actionlog&amp;action=showuser&amp;uuid={{uuserid}}">{{ulastname}}, {{ufirstname}}</a>
+ {{/uuserid}}
+ {{^uuserid}}
+ {{lang_system}}
+ {{/uuserid}}
+ </td>
+ {{/showActor}}
+ {{#showTarget}}
+ <td style="word-wrap: break-word">
+ {{#targeturl}}
+ <a href="{{targeturl}}">{{targetname}}</a>
+ {{/targeturl}}
+ {{^targeturl}}
+ {{targetname}}
+ {{^targetname}}
+ <span class="small">{{targetid}}</span>
+ {{/targetname}}
+ {{/targeturl}}
+ </td>
+ {{/showTarget}}
+ <td style="word-wrap: break-word">{{description}}</td>
+ </tr>
+ {{/events}}
+ </tbody>
</table> \ No newline at end of file
diff --git a/modules-available/dozmod/templates/images-delete.html b/modules-available/dozmod/templates/images-delete.html
index 0ee90835..dd4c61bc 100644
--- a/modules-available/dozmod/templates/images-delete.html
+++ b/modules-available/dozmod/templates/images-delete.html
@@ -1,8 +1,8 @@
-<h2>{{lang_heading}}</h2>
+<h1>{{lang_bwlehrpoolsuite}}</h1>
<div class="panel panel-default">
<div class="panel-heading">
- {{lang_subHeading}}
+ {{lang_heading}}
</div>
<div class="panel-body">
<p>{{lang_description_delete_images}}</p>
@@ -10,17 +10,20 @@
<form id="delform" method="post" action="?do=DozMod" onsubmit="return slxPostdel()">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="delimages">
- <table class="table table-stripped table-condensed">
+ <table class="table table-stripped table-condensed stupidtable">
<thead>
<tr>
- <th>{{lang_image}}</th>
- <th>{{lang_version}}</th>
- <th>{{lang_owner}}</th>
+ <th data-sort="string">{{lang_image}}</th>
+ <th data-sort="int">{{lang_version}}</th>
+ <th data-sort="string">{{lang_owner}}</th>
<th><span class="glyphicon glyphicon-upload" title="{{lang_hasNewer}}"></span></th>
- <th>{{lang_size}}</th>
+ <th data-sort="int">{{lang_size}}</th>
<th>
- <input id="del-all" type="checkbox" onclick="slxChangeAll()">
- <span class="glyphicon glyphicon-trash" title="{{lang_delete}}"></span>
+ <div class="checkbox">
+ <input id="del-all" type="checkbox" onclick="slxChangeAll()">
+ <label for="del-all"></label>
+ <span class="glyphicon glyphicon-trash" title="{{lang_delete}}"></span>
+ </div>
</th>
</tr>
</thead>
@@ -28,16 +31,21 @@
{{#images}}
<tr>
<td class="text-left text-nowrap {{name_extra_class}}">{{displayname}}<br><span class="small">{{imageversionid}}</span></td>
- <td class="text-left text-nowrap">{{version}}</td>
+ <td class="text-left text-nowrap" data-sort-value="{{createtime}}" >{{version}}</td>
<td class="text-left text-nowrap"><a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a></td>
<td class="text-left text-nowrap"><span class="glyphicon {{hasNewerClass}}"></span></td>
- <td class="text-right text-nowrap">{{filesize}}</td>
- <td><input class="del-check" name="images[{{imageversionid}}]" type="checkbox" {{checked}}></td>
+ <td class="text-left text-nowrap" data-sort-value="{{rawfilesize}}">{{filesize}}</td>
+ <td>
+ <div class="checkbox">
+ <input type="checkbox" id="images[{{imageversionid}}]" class="del-check" name="images[{{imageversionid}}]" {{checked}}>
+ <label for="images[{{imageversionid}}]"></label>
+ </div>
+ </td>
</tr>
{{/images}}
</tbody>
</table>
- <button id="delbtn" class="btn btn-danger" type="submit" name="button" value="save">{{lang_delButton}}</button>
+ <button style="margin-left: 20px" id="delbtn" class="btn btn-danger pull-right" type="submit" name="button" value="save"><span class="glyphicon glyphicon-trash"></span> {{lang_delButton}}</button>
</form>
<pre style="display:none" id="deloutput"></pre>
</div>
diff --git a/modules-available/dozmod/templates/mailconfig.html b/modules-available/dozmod/templates/mailconfig.html
index 4f8b81fa..6edc65ae 100644
--- a/modules-available/dozmod/templates/mailconfig.html
+++ b/modules-available/dozmod/templates/mailconfig.html
@@ -1,24 +1,25 @@
-<h2>{{lang_mailConfigHeadline}}</h2>
+<h1>{{lang_bwlehrpoolsuite}}</h1>
<div class="panel panel-default">
<div class="panel-heading">
{{lang_mailConfig}}
</div>
<div class="panel-body">
+
<p>{{lang_mailDescription}}</p>
<form action="?do=DozMod" method="post" id="mailconf">
<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
<input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="host-id">{{lang_host}} *</label>
+ <label class="input-group-addon" for="host-id">{{lang_host}} *</label>
<input type="text" name="host" id ="host-id" class="form-control" placeholder="smtp.example.com" value="{{host}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="port-id">{{lang_port}} *</label>
+ <label class="input-group-addon" for="port-id">{{lang_port}} *</label>
<input type="text" name="port" id ="port-id" class="form-control" placeholder="465" value="{{port}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="ssl-id">{{lang_ssl}} *</label>
+ <label class="input-group-addon" for="ssl-id">{{lang_ssl}} *</label>
<select class="form-control" name="ssl" id="ssl-id">
<option value="NONE" {{set_NONE}}>{{lang_sslNone}}</option>
<option value="IMPLICIT" {{set_IMPLICIT}}>{{lang_sslImplicit}}</option>
@@ -26,40 +27,42 @@
</select>
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="senderAddress-id">{{lang_senderAddress}} *</label>
+ <label class="input-group-addon" for="senderAddress-id">{{lang_senderAddress}} *</label>
<input type="text" name="senderAddress" id ="senderAddress-id" class="form-control" placeholder="smtp-username@hs-example.com" value="{{senderAddress}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="serverName-id">{{lang_senderName}}</label>
+ <label class="input-group-addon" for="serverName-id">{{lang_senderName}}</label>
<input type="text" name="serverName" id ="serverName-id" class="form-control" placeholder="bwLehrpool HS Example" value="{{serverName}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="replyTo-id">{{lang_replyTo}}</label>
+ <label class="input-group-addon" for="replyTo-id">{{lang_replyTo}}</label>
<input type="text" name="replyTo" id ="replyTo-id" class="form-control" placeholder="helpdesk@hs-example.com" value="{{replyTo}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="username-id">{{lang_username}}</label>
- <input type="text" name="username" id ="username-id" class="form-control" placeholder="smtp-username" value="{{username}}">
+ <label class="input-group-addon" for="username-id">{{lang_username}}</label>
+ <input type="text" name="username" id ="username-id" class="form-control" placeholder="{{lang_usernameplaceholder}}" value="{{username}}">
</div>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="password-id">{{lang_password}}</label>
- <input type="{{password_type}}" name="password" id ="password-id" class="form-control" placeholder="geheim" value="{{password}}">
+ <label class="input-group-addon" for="password-id">{{lang_password}}</label>
+ <input type="{{password_type}}" name="password" id ="password-id" class="form-control" placeholder="{{lang_passwordplaceholder}}" value="{{password}}">
</div>
<p>{{lang_asteriskRequired}}</p>
<br>
<p>{{lang_testConfiguration}}</p>
<div class="input-group">
- <label class="input-group-addon slx-ga2" for="test-id">{{lang_testRecipient}}</label>
+ <label class="input-group-addon" for="test-id">{{lang_testRecipient}}</label>
<input type="text" name="recipient" id ="test-id" class="form-control" placeholder="test@example.com" value="">
</div>
<br>
- <button class="btn btn-primary btn-sm" type="button" id="test-button" name="button" value="test" onclick="slxTestConfig()">{{lang_test}}</button>
- <span id="test-spin" style="display:none"><span class="glyphicon glyphicon-refresh slx-rotation"></span></span>
- <pre id="test-output" style="display:none"></pre>
- <button class="btn btn-primary btn-sm" type="submit" name="button" value="save">{{lang_save}}</button>
- <br>
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="mail">
+ <div class="text-right">
+ <button class="btn btn-warning" type="button" id="test-button" name="button" value="test" onclick="slxTestConfig()"><span class="glyphicon glyphicon-envelope"></span> {{lang_test}}</button>
+ <span id="test-spin" style="display:none"><span class="glyphicon glyphicon-refresh slx-rotation"></span></span>
+ <pre id="test-output" style="display:none"></pre>
+ <button class="btn btn-primary" type="submit" name="button" value="save"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <br>
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="mail">
+ </div>
</form>
</div>
</div>
diff --git a/modules-available/dozmod/templates/orglist.html b/modules-available/dozmod/templates/orglist.html
index 21495bba..361421c5 100644
--- a/modules-available/dozmod/templates/orglist.html
+++ b/modules-available/dozmod/templates/orglist.html
@@ -1,24 +1,25 @@
-<h2>{{lang_organizationList}}</h2>
-
<div class="panel panel-default">
<div class="panel-heading">
{{lang_organizationListHeader}}
</div>
<div class="panel-body">
<div class="table-responsive">
- <table class="table table-stripped table-condensed">
+ <table class="table table-stripped table-condensed table-hover stupidtable">
<thead>
<tr>
- <th>{{lang_organization}}</th>
- <th><span class="glyphicon glyphicon-ok" title="{{lang_canLoginOrganization}}"></span></th>
+ <th width="95%" data-sort="string">{{lang_organization}}</th>
+ <th width="5%"><span class="glyphicon glyphicon-ok" title="{{lang_canLoginOrganization}}"></span></th>
</tr>
</thead>
<tbody>
{{#organizations}}
<tr>
- <td class="text-left text-nowrap">{{displayname}}</td>
- <td class="text-nowrap">
- <input onclick="seto('setorglogin', this, '{{organizationid}}')" type="checkbox" {{{canlogin}}}>
+ <td width="95%" class="text-left text-nowrap">{{displayname}}</td>
+ <td width="5%">
+ <div class="checkbox">
+ <input onclick="seto('setorglogin', this, '{{organizationid}}')" type="checkbox" {{{canlogin}}}>
+ <label></label>
+ </div>
</td>
</tr>
{{/organizations}}
@@ -32,6 +33,7 @@
function seto(action, el, orgid) {
var box = $(el);
+ box = box.parent();
var v = el.checked ? '1' : '0';
var old = el.checked == true;
box.css('display', 'none');
diff --git a/modules-available/dozmod/templates/runtimeconfig.html b/modules-available/dozmod/templates/runtimeconfig.html
index 9bdc44b0..a1992631 100644
--- a/modules-available/dozmod/templates/runtimeconfig.html
+++ b/modules-available/dozmod/templates/runtimeconfig.html
@@ -1,11 +1,11 @@
-<h2 id="runtime-configuration">{{lang_runtimeConfigHeadline}}</h2>
+<h1>{{lang_bwlehrpoolsuite}}</h1>
<div class="panel panel-default">
<div class="panel-heading">
{{lang_runtimeConfig}}
</div>
<div class="panel-body">
- <h3>{{lang_defaultPermissions}}</h3>
+ <legend>{{lang_defaultPermissions}}</legend>
<p><i>{{lang_descriptionPermissionConfig}}</i></p>
<form action="?do=DozMod" method="post" id="runtimeconf" role="form">
<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
@@ -13,7 +13,7 @@
<fieldset class="form-group">
- <legend>{{lang_defaultLecturePermissions}}</legend>
+ <h4>{{lang_defaultLecturePermissions}}</h4>
<input type="hidden" name="defaultLecturePermissions[edit]" value="0"/>
<input type="hidden" name="defaultLecturePermissions[admin]" value="0"/>
@@ -34,69 +34,79 @@
</fieldset>
<fieldset class="form-group">
- <legend>{{lang_defaultImagePermissions}}</legend>
+ <h4>{{lang_defaultImagePermissions}}</h4>
<input type="hidden" name="defaultImagePermissions[edit]" value="0"/>
<input type="hidden" name="defaultImagePermissions[admin]" value="0"/>
<input type="hidden" name="defaultImagePermissions[download]" value="0"/>
<input type="hidden" name="defaultImagePermissions[link]" value="0"/>
<div class="checkbox">
- <input type="checkbox" name="defaultImagePermissions[admin]" value="1" {{defaultImagePermissions.admin}} id ="image_admin" class="form-control">
- <label class="" for="image_admin">
+ <input type="checkbox" name="defaultImagePermissions[admin]" value="1" {{defaultImagePermissions.admin}} id="image_admin" class="form-control">
+ <label for="image_admin">
{{lang_defaultImagePermissionAdmin}}
</label>
</div>
<div class="checkbox">
- <input type="checkbox" name="defaultImagePermissions[edit]" value="1" {{defaultImagePermissions.edit}} id ="image_edit" class="form-control">
- <label>
+ <input type="checkbox" name="defaultImagePermissions[edit]" value="1" {{defaultImagePermissions.edit}} id="image_edit" class="form-control">
+ <label for="image_edit">
{{lang_defaultImagePermissionEdit}}
</label>
</div>
<div class="checkbox">
- <input type="checkbox" name="defaultImagePermissions[download]" value="1" {{defaultImagePermissions.download}} id ="image_download" class="form-control">
- <label>
+ <input type="checkbox" name="defaultImagePermissions[download]" value="1" {{defaultImagePermissions.download}} id="image_download" class="form-control">
+ <label for="image_download">
{{lang_defaultImagePermissionDownload}}
</label>
</div>
<div class="checkbox">
- <input type="checkbox" name="defaultImagePermissions[link]" value="1" {{defaultImagePermissions.link}} id ="image_link" class="form-control">
- <label>
+ <input type="checkbox" name="defaultImagePermissions[link]" value="1" {{defaultImagePermissions.link}} id="image_link" class="form-control">
+ <label for="image_link">
{{lang_defaultImagePermissionLink}}
</label>
</div>
</fieldset>
<fieldset>
- <h3>{{lang_runtimeConfigLimits}}</h3>
+ <legend>{{lang_runtimeConfigLimits}}</legend>
<p><i>{{lang_descriptionRuntimeLimits}}</i></p>
<table class="table-input-group">
<tr class="input-group">
- <td class="input-group-addon" for="max_image_validity">{{lang_maxImageValidity}}</td>
+ <td class="input-group-addon">{{lang_maxImageValidity}}</td>
<td>
- <input name="maxImageValidityDays" id="max_image_validity" class="form-control" type="number" value="{{maxImageValidityDays}}" min="7" max="999" pattern="^\d+$">
+ <input name="maxImageValidityDays"
+ class="form-control" type="number"
+ value="{{maxImageValidityDays}}" min="7" max="9999" pattern="^\d+$">
</td>
</tr>
<tr class="input-group">
- <td class="input-group-addon" for="max_lecture_validity">{{lang_maxLectureVisibility}}</td>
+ <td class="input-group-addon">{{lang_maxLectureVisibility}}</td>
<td>
- <input name="maxLectureValidityDays" id="max_lecture_validity" class="form-control" type="number" value="{{maxLectureValidityDays}}" min="1" max="999" pattern="^\d+$">
+ <input name="maxLectureValidityDays"
+ class="form-control" type="number"
+ value="{{maxLectureValidityDays}}" min="1" max="9999" pattern="^\d+$">
</td>
</tr>
<tr class="input-group">
- <td class="input-group-addon" for="max_transfers">{{lang_maxTransfers}}</td>
+ <td class="input-group-addon">{{lang_maxLocationsPerLecture}}</td>
<td>
- <input name="maxTransfers" id="max_transfers" class="form-control" type="number" value="{{maxTransfers}}" min="1" max="10" pattern="^\d+$">
+ <input name="maxLocationsPerLecture" class="form-control" type="number" value="{{maxLocationsPerLecture}}" min="0" max="999" pattern="^\d+$">
+ </td>
+ </tr>
+ <tr class="input-group">
+ <td class="input-group-addon">{{lang_maxTransfers}}</td>
+ <td>
+ <input name="maxTransfers" class="form-control" type="number" value="{{maxTransfers}}" min="1" max="10" pattern="^\d+$">
</td>
</tr>
</table>
</fieldset>
-
+ <br/>
<fieldset>
- <h3>{{lang_miscOptions}}</h3>
+ <legend>{{lang_miscOptions}}</legend>
<div class="checkbox">
<input type="hidden" name="allowLoginByDefault" value="0">
- <input type="checkbox" name="allowLoginByDefault" value="1" {{allowLoginByDefault}} id ="allowLoginByDefault" class="form-control">
- <label>
+ <input type="checkbox" name="allowLoginByDefault" value="1" {{allowLoginByDefault}} id="allowLoginByDefault" class="form-control">
+ <label for="allowLoginByDefault">
{{lang_allowLoginByDefault}}
</label>
<p><i>{{lang_allowLoginDescription}}</i></p>
@@ -106,7 +116,9 @@
<br>
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="runtime">
- <button class="btn btn-primary" type="submit" name="button" value="save">{{lang_save}}</button>
+ <div class="text-right">
+ <button class="btn btn-primary" type="submit" name="button" value="save"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
</form>
</div>
</div>
diff --git a/modules-available/dozmod/templates/templates.html b/modules-available/dozmod/templates/templates.html
index 8a3caf26..4764c0e9 100644
--- a/modules-available/dozmod/templates/templates.html
+++ b/modules-available/dozmod/templates/templates.html
@@ -1,91 +1,119 @@
-<h1>{{lang_mailTemplates}}</h1>
+<h1>{{lang_bwlehrpoolsuite}}</h1>
-<p><i>{{lang_templatePageDescription}}</i></p>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_mailTemplates}}
+ </div>
+ <div class="panel-body">
-<form id="templateForm" role="form" method="POST" action="?do=dozmod&amp;section=templates">
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="save">
+ <p><i>{{lang_templatePageDescription}}</i></p>
+ <form id="templateForm" role="form" method="POST" action="?do=dozmod&amp;section=templates">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="save">
- <div class="panel-group" id="accordion">
- {{#templates}}
+ <div class="panel-group" id="accordion">
- <div id="frame_{{name}}" class="panel panel-default">
- <div class="panel-heading">
- <div class="panel-title">
- {{#conflict}}
- <span class="glyphicon glyphicon-exclamation-sign pull-left text-danger"></span>
- {{/conflict}}
- {{#modified}}
- <span class="glyphicon glyphicon-pencil pull-left"></span>
- {{/modified}}
- <h4>
- <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#panel_{{name}}">
- {{name}}
- </a>
- </h4>
- <small>{{description}}</small>
+ {{#templates}}
- </div>
- </div>
- <div id="panel_{{name}}" class="panel-collapse collapse">
- <div class="panel-body">
- <div id="msgbox_{{name}}">
- </div>
+ <div id="frame_{{name}}" class="panel panel-default">
+ <div class="panel-heading">
+ <div class="panel-title">
+ {{#conflict}}
+ <span class="glyphicon glyphicon-exclamation-sign pull-left text-danger"></span>
+ {{/conflict}}
+ {{#modified}}
+ <span class="glyphicon glyphicon-pencil pull-left"></span>
+ {{/modified}}
+ <h4>
+ <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#panel_{{name}}">
+ {{name}}
+ </a>
+ </h4>
+ <small>{{description}}</small>
- <label for="ta_{{name}}">{{lang_template}}</label>
- <div class="form-group">
- <textarea data-mandatory="{{list_mandatoryVariables}}" name="templates[{{name}}][template]"
- data-setting="{{name}}" id="ta_{{name}}"
- class="form-control templateEditor" style="min-height:200px">{{template}}</textarea>
+ </div>
</div>
+ <div id="panel_{{name}}" class="panel-collapse collapse">
+ <div class="panel-body">
+ <div id="msgbox_{{name}}">
+ </div>
+
+ <label for="ta_{{name}}">{{lang_template}}</label>
+ <div class="form-group">
+ <textarea data-mandatory="{{list_mandatoryVariables}}" name="templates[{{name}}][template]"
+ data-setting="{{name}}" id="ta_{{name}}"
+ class="form-control templateEditor" style="min-height:200px">{{template}}</textarea>
+ </div>
+
+ <h4>{{lang_placeholders}}</h4>
+ <select name="templates[{{name}}][mandatory_variables]" multiple="multiple" class="hidden">
+ {{{html_mandatoryVariables}}}
+ </select>
+ <select name="templates[{{name}}][optional_variables]" multiple="multiple" class="hidden">
+ {{{html_optionalVariables}}}
+ </select>
+ <ul>
+ {{{html_availableVariables}}}
+ </ul>
+ {{#original_template}}
+ <textarea class="hidden" id="orig_{{name}}">{{original_template}}</textarea>
+ <div class="pull-right">
+ <a href="#" class="btn btn-default" onclick="$('#ta_{{name}}').val($('#orig_{{name}}').val());return false">
+ <span class="glyphicon glyphicon-refresh"></span>
+ {{lang_replaceWithOriginal}}
+ </a>
+ </div>
+ {{/original_template}}
+ <div class="small">
+ {{lang_modified}}: {{#modified}}<b>{{lang_yes}}</b>{{/modified}}{{^modified}}{{lang_no}}{{/modified}},
+ {{lang_hasNewer}}: {{#conflict}}<b>{{lang_yes}}</b>{{/conflict}}{{^conflict}}{{lang_no}}{{/conflict}},
+ {{lang_thisVersion}}: {{edit_version}},
+ {{lang_latestVersion}}: {{version}}
+ </div>
+ </div>
+ </div>
+ </div>
- <h4>{{lang_placeholders}}</h4>
- <select name="templates[{{name}}][mandatory_variables]" multiple="multiple" class="hidden">
- {{{html_mandatoryVariables}}}
- </select>
- <select name="templates[{{name}}][optional_variables]" multiple="multiple" class="hidden">
- {{{html_optionalVariables}}}
- </select>
- <ul>
- {{{html_availableVariables}}}
- </ul>
- {{#original_template}}
- <textarea class="hidden" id="orig_{{name}}">{{original_template}}</textarea>
- <div class="pull-right">
- <a href="#" class="btn btn-default" onclick="$('#ta_{{name}}').val($('#orig_{{name}}').val());return false">
- <span class="glyphicon glyphicon-refresh"></span>
- {{lang_replaceWithOriginal}}
- </a>
+
+ {{/templates}}
+ </div>
+
+ <button type="submit" onclick="return validateForm()" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </form>
+ <form method="POST" action="?do=dozmod&amp;section=templates">
+ <input type="hidden" name="token" value="{{token}}">
+ <div>
+ <button type="button" data-toggle="modal" data-target="#resetTemplatesModal" class="btn btn-danger">{{lang_loadDefaults}}</button>
+ </div>
+
+ <div id="resetTemplatesModal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"></h4>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_reallyResetTemplates}}</p>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value ="reset" class="btn btn-primary">{{lang_reset}}</button>
+ </div>
</div>
- {{/original_template}}
- <div class="small">
- {{lang_modified}}: {{#modified}}<b>{{lang_yes}}</b>{{/modified}}{{^modified}}{{lang_no}}{{/modified}},
- {{lang_hasNewer}}: {{#conflict}}<b>{{lang_yes}}</b>{{/conflict}}{{^conflict}}{{lang_no}}{{/conflict}},
- {{lang_thisVersion}}: {{edit_version}},
- {{lang_latestVersion}}: {{version}}
+
</div>
</div>
- </div>
+
+ </form>
+ <div class="clearfix"></div>
</div>
+</div>
- {{/templates}}
- </div>
- <div class="pull-left">
- <button type="submit" onclick="return validateForm()" class="btn btn-primary">{{lang_save}}</button>
- </div>
-</form>
-<form method="POST" action="?do=dozmod&amp;section=templates">
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="reset">
- <div>
- <button type="submit" onclick="return confirm('{{lang_reallyResetTemplates}}')" class="btn btn-danger">{{lang_loadDefaults}}</button>
- </div>
-</form>
-<div class="clearfix"></div>
<script type="application/javascript"><!--
diff --git a/modules-available/dozmod/templates/userlist.html b/modules-available/dozmod/templates/userlist.html
index a4f415e1..378a2be1 100644
--- a/modules-available/dozmod/templates/userlist.html
+++ b/modules-available/dozmod/templates/userlist.html
@@ -1,4 +1,4 @@
-<h2 id="users">{{lang_userList}}</h2>
+<h1>{{lang_bwlehrpoolsuite}}</h1>
<div class="panel panel-default">
<div class="panel-heading">
@@ -7,13 +7,13 @@
<div class="panel-body">
<p>{{lang_userListDescription}}</p>
<div class="table-responsive">
- <table class="table table-stripped table-condensed">
+ <table class="table table-stripped table-condensed table-hover stupidtable">
<thead>
<tr>
- <th>{{lang_user}}</th>
- <th>{{lang_organization}}</th>
- <th>{{lang_lastLogin}}</th>
- <th>{{lang_email}}</th>
+ <th data-sort="string">{{lang_user}}</th>
+ <th data-sort="string">{{lang_organization}}</th>
+ <th data-sort="int">{{lang_lastLogin}}</th>
+ <th data-sort="string">{{lang_email}}</th>
<th><span class="glyphicon glyphicon-envelope" title="{{lang_emailNotifications}}"></span></th>
<th><span class="glyphicon glyphicon-king" title="{{lang_superUser}}"></span></th>
<th><span class="glyphicon glyphicon-ok" title="{{lang_canLoginUser}}"></span></th>
@@ -26,9 +26,24 @@
<td class="text-left text-nowrap">{{orgname}}</td>
<td class="text-left text-nowrap">{{lastlogin}}</td>
<td class="text-left text-nowrap"><a href="mailto:{{email}}">{{email}}</a></td>
- <td><input onclick="setu('setmail', this, '{{userid}}')" type="checkbox" {{{emailnotifications}}}></td>
- <td><input onclick="setu('setsu', this, '{{userid}}')" type="checkbox" {{{issuperuser}}}></td>
- <td><input onclick="setu('setlogin', this, '{{userid}}')" type="checkbox" {{{canlogin}}}></td>
+ <td>
+ <div class="checkbox">
+ <input onclick="setu('setmail', this, '{{userid}}')" type="checkbox" {{{emailnotifications}}}>
+ <label></label>
+ </div>
+ </td>
+ <td>
+ <div class="checkbox">
+ <input onclick="setu('setsu', this, '{{userid}}')" type="checkbox" {{{issuperuser}}}>
+ <label></label>
+ </div>
+ </td>
+ <td>
+ <div class="checkbox">
+ <input onclick="setu('setlogin', this, '{{userid}}')" type="checkbox" {{{canlogin}}}>
+ <label></label>
+ </div>
+ </td>
</tr>
{{/users}}
</tbody>
@@ -41,6 +56,7 @@
function setu(action, el, uid) {
var box = $(el);
+ box = box.parent();
var v = el.checked ? '1' : '0';
var old = el.checked == true;
box.css('display', 'none');
@@ -48,7 +64,19 @@ function setu(action, el, uid) {
if (data !== '1' && data !== '0') {
el.checked = !old;
- box.parent().css('background-color', 'red !important');
+ box.parent().css('background-color', '');
+ /* show success notification */
+ $notification = $('<span></span>')
+ .addClass('glyphicon glyphicon-remove')
+ .css('color', 'red')
+ .css('width', '0px')
+ .css('position', 'relative')
+ .css('right', '20px')
+ .hide();
+ box.before($notification);
+ $notification.fadeIn('fast', function () {
+ $notification.fadeOut('slow', function () { $notification.remove() });
+ });
} else {
el.checked = (data == 1);
box.parent().css('background-color', '');
diff --git a/modules-available/eventlog/lang/de/template-tags.json b/modules-available/eventlog/lang/de/template-tags.json
index b1a292e6..6ad75329 100644
--- a/modules-available/eventlog/lang/de/template-tags.json
+++ b/modules-available/eventlog/lang/de/template-tags.json
@@ -1,6 +1,6 @@
{
"lang_details": "Details",
"lang_event": "Ereignis",
- "lang_eventLog": "Ereignisprotokoll",
+ "lang_eventLog": "Serverprotokoll",
"lang_when": "Wann"
} \ No newline at end of file
diff --git a/modules-available/eventlog/lang/en/module.json b/modules-available/eventlog/lang/en/module.json
index 0fc536f3..8217fc02 100644
--- a/modules-available/eventlog/lang/en/module.json
+++ b/modules-available/eventlog/lang/en/module.json
@@ -1,3 +1,3 @@
{
- "module_name": "Server Log"
+ "module_name": "Server-Log"
} \ No newline at end of file
diff --git a/modules-available/eventlog/lang/en/template-tags.json b/modules-available/eventlog/lang/en/template-tags.json
index 21ec64ea..3132b97c 100644
--- a/modules-available/eventlog/lang/en/template-tags.json
+++ b/modules-available/eventlog/lang/en/template-tags.json
@@ -1,6 +1,6 @@
{
"lang_details": "Details",
"lang_event": "Event",
- "lang_eventLog": "Event log",
+ "lang_eventLog": "Server Log",
"lang_when": "When"
} \ No newline at end of file
diff --git a/modules-available/eventlog/templates/_page.html b/modules-available/eventlog/templates/_page.html
index 2e657805..0875664e 100644
--- a/modules-available/eventlog/templates/_page.html
+++ b/modules-available/eventlog/templates/_page.html
@@ -3,18 +3,18 @@
<table class="table table-striped table-condensed">
<thead>
<th width="1"></th>
- <th>{{lang_when}}</th>
- <th>{{lang_event}}</th>
+ <th class="text-center">{{lang_when}}</th>
+ <th class="text-center">{{lang_event}}</th>
<th width="1">{{lang_details}}</th>
</thead>
<tbody>
{{#list}}
<tr>
<td><span class="glyphicon glyphicon-{{icon}}" title="{{logtypeid}}"></span></td>
- <td class="text-right" nowrap="nowrap">{{date}}</td>
+ <td class="text-center" nowrap="nowrap">{{date}}</td>
<td class="{{color}}">{{description}}</td>
- <td>{{#extra}}
- <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
+ <td class="text-center">{{#extra}}
+ <a class="btn btn-default btn-xs" onclick="$('#details-body').html($('#extra-{{logid}}').html())" data-toggle="modal" data-target="#myModal">&raquo;</a>
<div class="hidden" id="extra-{{logid}}">{{extra}}</div>
{{/extra}}</td>
</tr>
@@ -33,9 +33,6 @@
<div class="modal-body">
<pre id="details-body"></pre>
</div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
- </div>
</div>
</div>
</div>
diff --git a/modules-available/exams/baseconfig/getconfig.inc.php b/modules-available/exams/baseconfig/getconfig.inc.php
index 325badb0..37a2caf4 100644
--- a/modules-available/exams/baseconfig/getconfig.inc.php
+++ b/modules-available/exams/baseconfig/getconfig.inc.php
@@ -7,11 +7,13 @@ if ($locations === false) {
$locationIds = explode(' ', $locations);
}
if (Exams::isInExamMode($locationIds, $lectureId, $autoLogin)) {
- ConfigHolder::add('SLX_EXAM', 'yes', 100000);
+ ConfigHolder::add('SLX_EXAM', 'yes', 10000);
if (strlen($lectureId) > 0) {
- ConfigHolder::add('SLX_EXAM_START', $lectureId, 100000);
+ ConfigHolder::add('SLX_EXAM_START', $lectureId, 10000);
}
if (strlen($autoLogin) > 0) {
- ConfigHolder::add('SLX_AUTOLOGIN', $autoLogin, 100000);
+ ConfigHolder::add('SLX_AUTOLOGIN', $autoLogin, 10000);
}
+ ConfigHolder::add('SLX_SYSTEMD_TARGET', 'exam-mode', 10000);
+ ConfigHolder::add("SLX_PVS_HYBRID", false, 10000);
}
diff --git a/modules-available/exams/config.json b/modules-available/exams/config.json
index 95d8aa3e..0780ebef 100644
--- a/modules-available/exams/config.json
+++ b/modules-available/exams/config.json
@@ -1,5 +1,5 @@
{
"category":"main.content",
- "dependencies": [ "locations", "js_vis", "bootstrap_datepicker", "bootstrap_timepicker", "bootstrap_multiselect"],
+ "dependencies": [ "locations", "js_vis", "js_stupidtable", "bootstrap_datepicker", "bootstrap_timepicker", "bootstrap_multiselect"],
"permission": "0"
}
diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json
index e011ee20..8bf37143 100644
--- a/modules-available/exams/lang/de/template-tags.json
+++ b/modules-available/exams/lang/de/template-tags.json
@@ -12,6 +12,7 @@
"lang_begin_date": "Beginn Datum",
"lang_begin_time": "Uhrzeit",
"lang_comfirmGlobalExam": "Wollen Sie wirklich eine globale Pr\u00fcfung definieren? Im gew\u00e4hlten Zeitraum werden s\u00e4mtliche R\u00e4ume in den Pr\u00fcfungsmodus geschaltet.",
+ "lang_dateTime": "Datum\/Uhrzeit",
"lang_deleteConfirmation": "Wirklich l\u00f6schen?",
"lang_description": "Beschreibung",
"lang_duration": "Dauer",
@@ -21,7 +22,9 @@
"lang_end_time": "Uhrzeit",
"lang_examModeDescription": "Hier k\u00f6nnen Sie bwLehrpool-R\u00e4ume zeitgesteuert in den Pr\u00fcfungsmodus versetzen. Im Pr\u00fcfungsmodus ist das Client-System st\u00e4rker abgeriegelt, sodass es sich zum Schreiben von E-Pr\u00fcfungen eignet. Nach dem Ein- bzw. Ausschalten des Pr\u00fcfungsmodus ist es notwendig, die Rechner in den betroffenen R\u00e4umen neuzustarten.",
"lang_global": "Global",
+ "lang_headingAddExam": "Zeitraum hinzuf\u00fcgen",
"lang_headingAllExamLectures": "Ausstehende Pr\u00fcfungsveranstaltungen (30 Tage)",
+ "lang_headingEditExam": "Zeitraum bearbeiten",
"lang_headingGraphicalOverview": "Grafische Darstellung",
"lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus",
"lang_id": "ID",
diff --git a/modules-available/exams/lang/en/module.json b/modules-available/exams/lang/en/module.json
index a066b3aa..a2403da1 100644
--- a/modules-available/exams/lang/en/module.json
+++ b/modules-available/exams/lang/en/module.json
@@ -1,5 +1,5 @@
{
- "module_name": "Exam mode",
+ "module_name": "Exam Mode",
"title_add-exam": "Add exam",
"title_edit-exam": "Edit exam",
"warning_lecture_is_not_enabled": "Warning: Lecture is not enabled by tutor"
diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json
index a4ae9325..af87bb01 100644
--- a/modules-available/exams/lang/en/template-tags.json
+++ b/modules-available/exams/lang/en/template-tags.json
@@ -2,7 +2,7 @@
"lang_actions": "Actions",
"lang_addExam": "Add exam period",
"lang_addingBasedOnLecture": "Adding exam period based on lecture",
- "lang_allExamPeriods": "All exam periods",
+ "lang_allExamPeriods": "All Exam Periods",
"lang_autoLogin": "Skip login",
"lang_autoLoginInfo": "If this option is enabled, the login mask will not be shown and instead, an anonymous session is opened. This is useful if there is another authentication mechanism in place inside the VM, e.g. web-based (LMS). The user will see the vmChooser right away after booting up the computer. You can combine this option with the \"Automatically launched course\" feature, so the boot process is completely automated. Note that when using \"skip login\", no personalization like mapping of home directories is possible inside the VM without additional actions by the user and\/or creator of the VM.",
"lang_autoStartInfo": "Here you can select a course that will be launched automatically after the user logged in. This will skip the vmChooser screen, and directly start the couse selected here. This function can be combined with the \"skip login\" feature to further automate the boot-up process.",
@@ -12,18 +12,21 @@
"lang_begin_date": "Begin Date",
"lang_begin_time": "Time",
"lang_comfirmGlobalExam": "Do you really want to create a global exam? Every single room will be set to lecture mode during the selected time period.",
+ "lang_dateTime": "Date\/Time",
"lang_deleteConfirmation": "Are you sure?",
"lang_description": "Description",
"lang_duration": "Duration",
- "lang_editExam": "Edit Exam Period",
+ "lang_editExam": "Edit exam period",
"lang_end": "End",
"lang_end_date": "End Date",
"lang_end_time": "Time",
"lang_examModeDescription": "Here you can define time spans during which selected rooms will be set to exam mode. In exam mode, the client computers are more locked down than usual so it is suitable for writing electronic exams.",
"lang_global": "Global",
- "lang_headingAllExamLectures": "Upcoming lectures marked as exams (30 days)",
- "lang_headingGraphicalOverview": "Graphical overview",
- "lang_headingMain": "bwLehrpool exam mode",
+ "lang_headingAddExam": "Add Exam Period",
+ "lang_headingAllExamLectures": "Upcoming Lectures Marked As Exams (30 Days)",
+ "lang_headingEditExam": "Edit Exam Period",
+ "lang_headingGraphicalOverview": "Graphical Overview",
+ "lang_headingMain": "bwLehrpool Exam Mode",
"lang_id": "ID",
"lang_lectureName": "Lecture name",
"lang_lectureOutOfRange": "Hint: Start or end date of given lecture lies outside of exam period given above",
diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php
index 48af287a..a6bd7e16 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -150,10 +150,10 @@ class Page_Exams extends Page
foreach ($this->exams as $exam) {
if ($exam['endtime'] < $now) {
$exam['rowClass'] = 'text-muted';
- $exam['btnClass'] = 'btn-success';
+ $exam['btnClass'] = 'btn-default';
$exam['liesInPast'] = true;
} else {
- $exam['btnClass'] = 'btn-default';
+ $exam['btnClass'] = 'btn-danger';
}
$exam['starttime_s'] = date('Y-m-d H:i', $exam['starttime']);
$exam['endtime_s'] = date('Y-m-d H:i', $exam['endtime']);
diff --git a/modules-available/exams/style.css b/modules-available/exams/style.css
index 4a6cd7da..c6e5bada 100644
--- a/modules-available/exams/style.css
+++ b/modules-available/exams/style.css
@@ -15,3 +15,11 @@
.vis-item.disabled {
background-color: rgba(189, 195, 199, 1.0) !important;
}
+
+.table > tbody > tr > td {
+ vertical-align: middle;
+}
+
+label {
+ font-weight: inherit;
+} \ No newline at end of file
diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html
index bf000df5..58c61b11 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -1,108 +1,122 @@
{{#exam.examid}}
-<h1>{{lang_editExam}}</h1>
+<h1>{{lang_headingEditExam}}</h1>
{{/exam.examid}}
{{^exam.examid}}
-<h1>{{lang_addExam}}</h1>
+<h1>{{lang_headingAddExam}}</h1>
{{/exam.examid}}
{{#exam.displayname}}
<div class="alert alert-info">{{lang_addingBasedOnLecture}}:<br><b>{{exam.displayname}}</b></div>
{{/exam.displayname}}
+
<form class="form" method="POST" action="?do=exams" id="tolleform">
- <div class="form-group">
- <div>
- <label for="locations">{{lang_location}}</label>
- <p><i>{{lang_locationInfo}}</i></p>
+ <div class="panel panel-default">
+ <div class="panel-heading"><label for="locations">{{lang_location}}</label></div>
+ <div class="panel-body">
+ <div class="form-group">
+ <div>
+ <p><i>{{lang_locationInfo}}</i></p>
+ </div>
+ <select id="locations" multiple name="locations[]">
+ {{#locations}}
+ <option value="{{locationid}}" {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/locations}}
+ </select>
+ </div>
</div>
- <select id="locations" multiple name="locations[]">
- {{#locations}}
- <option value="{{locationid}}" {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
- {{/locations}}
- </select>
</div>
- <div class="row form-group">
- <div class="col-xs-6">
- <label for="starttime_date">{{lang_begin_date}}</label>
- <div class="input-group">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-calendar"></span>
- </span>
- <input required class="form-control datepicker" name="starttime_date" id="starttime_date"
- value="{{exam.starttime_date}}">
- </div>
- </div>
- <div class="col-xs-6">
- <label for="starttime_time">{{lang_begin_time}}</label>
- <div class="input-group bootstrap-timepicker timepicker">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-time"></span>
- </span>
- <input required type="text" class="form-control timepicker2" name="starttime_time" id="starttime_time"
- value="{{exam.starttime_time}}"
- pattern="[0-9]{1,2}:[0-9]{2}">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_dateTime}}</div>
+ <div class="panel-body">
+ <div class="row form-group">
+ <div class="col-xs-6">
+ <label for="starttime_date">{{lang_begin_date}}</label>
+ <div class="input-group">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-calendar"></span>
+ </span>
+ <input required class="form-control datepicker" name="starttime_date" id="starttime_date"
+ value="{{exam.starttime_date}}">
+ </div>
+ </div>
+ <div class="col-xs-6">
+ <label for="starttime_time">{{lang_begin_time}}</label>
+ <div class="input-group bootstrap-timepicker timepicker">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-time"></span>
+ </span>
+ <input required type="text" class="form-control timepicker2" name="starttime_time" id="starttime_time"
+ value="{{exam.starttime_time}}"
+ pattern="[0-9]{1,2}:[0-9]{2}">
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div class="row form-group">
- <div class="col-xs-6">
- <label for="endtime_date">{{lang_end_date}}</label>
- <div class="input-group">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-calendar"></span>
- </span>
- <input required class="form-control datepicker" name="endtime_date" id="endtime_date"
- value="{{exam.endtime_date}}">
+ <div class="row form-group">
+ <div class="col-xs-6">
+ <label for="endtime_date">{{lang_end_date}}</label>
+ <div class="input-group">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-calendar"></span>
+ </span>
+ <input required class="form-control datepicker" name="endtime_date" id="endtime_date"
+ value="{{exam.endtime_date}}">
+ </div>
+ </div>
+ <div class="col-xs-6">
+ <label for="endtime_time">{{lang_end_time}}</label>
+ <div class="input-group bootstrap-timepicker timepicker">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-time"></span>
+ </span>
+ <input required type="text" class="form-control timepicker2" name="endtime_time" id="endtime_time"
+ value="{{exam.endtime_time}}"
+ pattern="[0-9]{1,2}:[0-9]{2}">
+ </div>
+ </div>
</div>
- </div>
- <div class="col-xs-6">
- <label for="endtime_time">{{lang_end_time}}</label>
- <div class="input-group bootstrap-timepicker timepicker">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-time"></span>
- </span>
- <input required type="text" class="form-control timepicker2" name="endtime_time" id="endtime_time"
- value="{{exam.endtime_time}}"
- pattern="[0-9]{1,2}:[0-9]{2}">
+
+ <div class="panel">
+ <div class="panel-body">
+ {{lang_duration}}: <span id="exam-duration">-</span>
+ </div>
</div>
</div>
</div>
- <div class="panel">
+ <div class="panel panel-default">
+ <div class="panel-heading"><label for="lecturelist">{{lang_autoStartLecture}}</label></div>
<div class="panel-body">
- {{lang_duration}}: <span id="exam-duration">-</span>
- </div>
- </div>
-
- <div class="row form-group">
- <div class="form-group col-xs-12">
- <label for="description">{{lang_autoStartLecture}}</label>
- <p><i>{{lang_autoStartInfo}}</i></p>
- <div class="input-group">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-pencil"></span>
- </span>
- <select class="form-control" id="lecturelist" name="lectureid">
- <option value="">{{lang_none}}</option>
- {{#lectures}}
- <option data-from="{{starttime}}" data-to="{{endtime}}" value="{{lectureid}}" {{selected}} >{{displayname}}</option>
- {{/lectures}}
- </select>
+ <div class="row form-group">
+ <div class="form-group col-xs-12">
+ <p><i>{{lang_autoStartInfo}}</i></p>
+ <div class="input-group">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-pencil"></span>
+ </span>
+ <select class="form-control" id="lecturelist" name="lectureid">
+ <option value="">{{lang_none}}</option>
+ {{#lectures}}
+ <option data-from="{{starttime}}" data-to="{{endtime}}" value="{{lectureid}}" {{selected}} >{{displayname}}</option>
+ {{/lectures}}
+ </select>
+ </div>
+ </div>
+ <div class="form-group col-xs-12">
+ <div class="checkbox"><input id="autologin" type="checkbox" name="autologin" value="demo" class="form-control" {{#exam.autologin}}checked{{/exam.autologin}}><label for="autologin">{{lang_autoLogin}}</label></div>
+ <p><i>{{lang_autoLoginInfo}}</i></p>
+ </div>
+ <div class="col-xs-12" id="lecture-info">
+ -
+ </div>
</div>
</div>
- <div class="form-group col-xs-12">
- <div class="checkbox"><input id="autologin" type="checkbox" name="autologin" value="demo" class="form-control" {{#exam.autologin}}checked{{/exam.autologin}}><label for="autologin">{{lang_autoLogin}}</label></div>
- <p><i>{{lang_autoLoginInfo}}</i></p>
- </div>
- <div class="col-xs-12" id="lecture-info">
- -
- </div>
</div>
- <div class="row form-group">
- <div class="form-group col-xs-12">
- <label for="description">{{lang_description}}</label>
+ <div class="panel panel-default">
+ <div class="panel-heading"><label for="description">{{lang_description}}</label></div>
+ <div class="panel-body">
<textarea class="form-control" type="textarea" name="description" id="description">{{exam.description}}</textarea>
</div>
</div>
@@ -110,10 +124,39 @@
<input type="hidden" name="action" value="save">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="examid" value="{{exam.examid}}">
- <button class="btn btn-success" type="submit">{{lang_save}}</button>
+ <div class="text-right" style="margin-bottom: 20px">
+ <button type="button" id="cancelButton" class="btn btn-default"< style="margin-right: 10px">{{lang_cancel}}</button>
+ <button type="button" onclick="checkGlobalExam()" id="saveButton" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+
+ <div class ="modal fade" id="confirmGlobalModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_comfirmGlobalExam}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
</form>
+
+
<script type="application/javascript"><!--
+
+function checkGlobalExam() {
+ if ($('#locations option:selected').length === 0 && $('#locations option').length > 1) {
+ $("#confirmGlobalModal").modal();
+ } else {
+ $('#tolleform').submit();
+ }
+}
+
document.addEventListener("DOMContentLoaded", function () {
var filename = "modules/bootstrap_datepicker/lang/bootstrap-datepicker." + LANG + ".js";
moment.locale(LANG);
@@ -189,7 +232,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
updateLectureInfo();
- }
+ };
var updateLectureInfo = function() {
var sel = $('#lecturelist option:selected');
@@ -198,7 +241,7 @@ document.addEventListener("DOMContentLoaded", function () {
} else {
$('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment(sel.data('from') * 1000).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment(sel.data('to') * 1000).format('YYYY-MM-DD H:mm') + ')');
}
- }
+ };
start_date.change(startEndChanged);
start_time.change(startEndChanged);
@@ -206,12 +249,8 @@ document.addEventListener("DOMContentLoaded", function () {
end_time.change(startEndChanged);
$('#lecturelist').change(updateLectureInfo);
- $('#tolleform').submit(function(ev) {
- if ($('#locations option:selected').length === 0 && $('#locations option').length > 1) {
- if (!confirm('{{lang_comfirmGlobalExam}}')) {
- ev.preventDefault();
- }
- }
+ $("#cancelButton").click(function () {
+ window.location.replace("?do=exams");
});
}, false);
diff --git a/modules-available/exams/templates/page-exams-vis.html b/modules-available/exams/templates/page-exams-vis.html
index e347900b..caf4aea2 100644
--- a/modules-available/exams/templates/page-exams-vis.html
+++ b/modules-available/exams/templates/page-exams-vis.html
@@ -1,6 +1,15 @@
-<h2>{{lang_headingGraphicalOverview}}</h2>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_headingGraphicalOverview}}
+ </div>
+ <div class="panel-body">
+ <div id="timeline" class="slx-space"></div>
+ </div>
+</div>
+
+
+
-<div id="timeline" class="slx-space"></div>
<script type="application/javascript"><!--
diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html
index fc88e4f4..29b8c319 100644
--- a/modules-available/exams/templates/page-exams.html
+++ b/modules-available/exams/templates/page-exams.html
@@ -1,52 +1,63 @@
-<h2>{{lang_allExamPeriods}}</h2>
-<div class="slx-space">
- <table class="table">
- <tr>
- <th>{{lang_id}}</th>
- <th>{{lang_locations}}</th>
- <th>{{lang_begin}}</th>
- <th>{{lang_end}}</th>
- <th>{{lang_actions}}</th>
- </tr>
- {{#exams}}
- <tr class="{{rowClass}}">
- <td>{{examid}}</td>
- <td>
- {{locationnames}}
- {{^locationnames}}
- <i>{{lang_global}}</i>
- {{/locationnames}}
- {{#lecturename}}
- <div>
- <b>{{lang_autostart}}</b>: {{lecturename}}
- </div>
- {{/lecturename}}
- <div class="small">
- {{description}}
- {{^description}}
- <i>{{lang_noDescription}}</i>
- {{/description}}
- </div>
- </td>
- <td class="text-nowrap">{{starttime_s}}</td>
- <td class="text-nowrap">{{endtime_s}}</td>
- <td class="text-nowrap text-right">
- <form method="POST" action="?do=exams&action=delete" {{^liesInPast}}onsubmit="return confirm('{{lang_deleteConfirmation}}');"{{/liesInPast}} >
- {{^liesInPast}}
- <a onclick="slxShow({{starttime}}, {{endtime}})" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-eye-open"></span></a>
- {{/liesInPast}}
- <a href="?do=exams&action=edit&examid={{examid}}" class="btn btn-default btn-sm" >{{lang_edit}}</a>
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="examid" value="{{examid}}">
- <button class="btn {{btnClass}} btn-sm">{{lang_delete}}</button>
- </form>
- </td>
- </tr>
- {{/exams}}
- </table>
-</div>
-
-<div class="btn-group" role="group">
- <a href="?do=exams&action=add" class="btn btn-success">{{lang_addExam}}</a>
-</div>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_allExamPeriods}}
+ </div>
+ <div class="panel-body">
+ <div class="slx-space">
+ <table class="table table-bordered stupidtable">
+ <thead>
+ <tr>
+ <th data-sort="int">{{lang_id}}</th>
+ <th data-sort="string">{{lang_locations}}</th>
+ <th data-sort="int">{{lang_begin}}</th>
+ <th data-sort="int">{{lang_end}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#exams}}
+ <tr class="{{rowClass}}">
+ <td>{{examid}}</td>
+ <td>
+ {{locationnames}}
+ {{^locationnames}}
+ <i>{{lang_global}}</i>
+ {{/locationnames}}
+ {{#lecturename}}
+ <div>
+ <b>{{lang_autostart}}</b>: {{lecturename}}
+ </div>
+ {{/lecturename}}
+ <div class="small">
+ {{description}}
+ {{^description}}
+ <i>{{lang_noDescription}}</i>
+ {{/description}}
+ </div>
+ </td>
+ <td class="text-nowrap" data-sort-value={{starttime}}>{{starttime_s}}</td>
+ <td class="text-nowrap" data-sort-value={{endtime}}>{{endtime_s}}</td>
+ <td class="text-nowrap text-right">
+ <form method="POST" action="?do=exams&action=delete" {{^liesInPast}}onsubmit="return confirm('{{lang_deleteConfirmation}}');"{{/liesInPast}} >
+ {{^liesInPast}}
+ <a onclick="slxShow({{starttime}}, {{endtime}})" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-eye-open"></span></a>
+ {{/liesInPast}}
+ <a href="?do=exams&action=edit&examid={{examid}}" class="btn btn-default btn-sm" >{{lang_edit}}</a>
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="examid" value="{{examid}}">
+ <button class="btn {{btnClass}} btn-sm">{{lang_delete}}</button>
+ </form>
+ </td>
+ </tr>
+ {{/exams}}
+ </tbody>
+ </table>
+ </div>
+ <div class="text-right">
+ <div class="btn-group" role="group">
+ <a href="?do=exams&action=add" class="btn btn-success"><span class="glyphicon glyphicon-plus-sign"></span> {{lang_addExam}}</a>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/exams/templates/page-upcoming-lectures.html b/modules-available/exams/templates/page-upcoming-lectures.html
index 323b1017..8ff8143e 100644
--- a/modules-available/exams/templates/page-upcoming-lectures.html
+++ b/modules-available/exams/templates/page-upcoming-lectures.html
@@ -1,46 +1,55 @@
-<h2>{{lang_headingAllExamLectures}}</h2>
-
-<div class="slx-space">
- <table class="table">
- <tr>
- <th>{{lang_lectureName}}</th>
- <th>{{lang_timeFrame}}</th>
- <th>{{lang_actions}}</th>
- </tr>
- {{#pending_lectures}}
- <tr class="{{class}}">
- <td>
- {{displayname}}
- <div class="small">
- <a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a>
- </div>
- </td>
- <td width="30%" class="text-nowrap">
- {{starttime_s}} &ensp; {{endtime_s}}
- <div class="small">
- {{lang_duration}}: {{duration_s}}
- {{^duration_s}}{{lang_moreThanOneDay}}{{/duration_s}}
- </div>
- </td>
- <td width="20%">
- <div class="pull-right text-nowrap">
- <a class="btn btn-sm btn-default" role="button" onclick="slxShow({{starttime}}, {{endtime}})"><span class="glyphicon glyphicon-eye-open"></span></a>
- <a href="?do=exams&amp;action=add&amp;lectureid={{lectureid}}" class="btn btn-sm btn-default" role="button">
- <span class="glyphicon glyphicon-plus-sign"></span>
- <span class="hidden-sm">{{lang_addExam}}</span>
- </a>
- </div>
- </td>
- </tr>
- {{/pending_lectures}}
- {{#decollapse}}
- <tr class="slx-decollapse">
- <td colspan="3">
- <span class="btn-group btn-group-justified">
- <span class="btn btn-default btn-sm"><span class="glyphicon glyphicon-menu-down"></span></span>
- </span>
- </td>
- </tr>
- {{/decollapse}}
- </table>
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_headingAllExamLectures}}
+ </div>
+ <div class="panel-body">
+ <div class="slx-space">
+ <table class="table stupidtable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_lectureName}}</th>
+ <th data-sort="int">{{lang_timeFrame}}</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#pending_lectures}}
+ <tr class="{{class}}">
+ <td>
+ {{displayname}}
+ <div class="small">
+ <a href="mailto:{{email}}">{{lastname}}, {{firstname}}</a>
+ </div>
+ </td>
+ <td data-sort-value={{starttime}} width="30%" class="text-nowrap">
+ {{starttime_s}} &ensp; {{endtime_s}}
+ <div class="small">
+ {{lang_duration}}: {{duration_s}}
+ {{^duration_s}}{{lang_moreThanOneDay}}{{/duration_s}}
+ </div>
+ </td>
+ <td width="20%">
+ <div class="pull-right text-nowrap">
+ <a class="btn btn-sm btn-default" role="button" onclick="slxShow({{starttime}}, {{endtime}})"><span class="glyphicon glyphicon-eye-open"></span></a>
+ <a href="?do=exams&amp;action=add&amp;lectureid={{lectureid}}" class="btn btn-sm btn-success" role="button">
+ <span class="glyphicon glyphicon-plus-sign"></span>
+ <span class="hidden-sm">{{lang_addExam}}</span>
+ </a>
+ </div>
+ </td>
+ </tr>
+ {{/pending_lectures}}
+ {{#decollapse}}
+ <tr class="slx-decollapse">
+ <td colspan="3">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm"><span class="glyphicon glyphicon-menu-down"></span></span>
+ </span>
+ </td>
+ </tr>
+ {{/decollapse}}
+ </tbody>
+ </table>
+ </div>
+ </div>
</div> \ No newline at end of file
diff --git a/modules-available/internetaccess/lang/en/template-tags.json b/modules-available/internetaccess/lang/en/template-tags.json
index 1be62f43..2ac50527 100644
--- a/modules-available/internetaccess/lang/en/template-tags.json
+++ b/modules-available/internetaccess/lang/en/template-tags.json
@@ -1,7 +1,7 @@
{
"lang_automatic": "Auto",
"lang_description": "Here you can configure how the satellite server has to access the internet.",
- "lang_internetAccess": "Internet access",
+ "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",
diff --git a/modules-available/internetaccess/page.inc.php b/modules-available/internetaccess/page.inc.php
index 246d4477..a92ba3e3 100644
--- a/modules-available/internetaccess/page.inc.php
+++ b/modules-available/internetaccess/page.inc.php
@@ -6,27 +6,35 @@ class Page_InternetAccess extends Page
protected function doPreprocess()
{
User::load();
- if (!User::hasPermission('superadmin')) {
+
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
- if (isset($_POST['PROXY_CONF'])) {
- $data = array();
- foreach (array('PROXY_CONF', 'PROXY_ADDR', 'PROXY_PORT', 'PROXY_USERNAME', 'PROXY_PASSWORD') as $key) {
- $data[$key] = Request::post($key, '');
- }
- if (!FileUtil::arrayToFile(CONFIG_PROXY_CONF, $data)) {
- Message::addError('main.error-write', CONFIG_PROXY_CONF);
- Util::redirect();
- } else {
- Message::addSuccess('settings-updated');
- Taskmanager::release(Taskmanager::submit('ReloadProxy'));
- $taskids = array();
- Trigger::stopDaemons(NULL, $taskids);
- $taskids = array();
- Trigger::startDaemons(NULL, $taskids);
- Session::set('ia-restart', $taskids);
- Util::redirect('?do=InternetAccess&show=update');
+
+ $action = Request::any('action', 'show');
+
+ if ($action == 'save') {
+ if (User::hasPermission("configuration.safe")) {
+ if (isset($_POST['PROXY_CONF'])) {
+ $data = array();
+ foreach (array('PROXY_CONF', 'PROXY_ADDR', 'PROXY_PORT', 'PROXY_USERNAME', 'PROXY_PASSWORD') as $key) {
+ $data[$key] = Request::post($key, '');
+ }
+ if (!FileUtil::arrayToFile(CONFIG_PROXY_CONF, $data)) {
+ Message::addError('main.error-write', CONFIG_PROXY_CONF);
+ Util::redirect();
+ } else {
+ Message::addSuccess('settings-updated');
+ Taskmanager::release(Taskmanager::submit('ReloadProxy'));
+ $taskids = array();
+ Trigger::stopDaemons(null, $taskids);
+ $taskids = array();
+ Trigger::startDaemons(null, $taskids);
+ Session::set('ia-restart', $taskids);
+ Util::redirect('?do=InternetAccess&show=update');
+ }
+ }
}
}
}
@@ -45,6 +53,9 @@ class Page_InternetAccess extends Page
if (!isset($data['PROXY_CONF']))
$data['PROXY_CONF'] = 'AUTO';
$data['selected_' . $data['PROXY_CONF']] = 'selected';
+
+ $data['allowedSave'] = User::hasPermission("configuration.safe");
+
Render::addTemplate('_page', $data);
}
diff --git a/modules-available/internetaccess/permissions/permissions.json b/modules-available/internetaccess/permissions/permissions.json
new file mode 100644
index 00000000..6d88ccc5
--- /dev/null
+++ b/modules-available/internetaccess/permissions/permissions.json
@@ -0,0 +1,3 @@
+{
+ "configuration.safe": "Safe new configuration."
+} \ No newline at end of file
diff --git a/modules-available/internetaccess/style.css b/modules-available/internetaccess/style.css
new file mode 100644
index 00000000..77fa7213
--- /dev/null
+++ b/modules-available/internetaccess/style.css
@@ -0,0 +1,3 @@
+.input-group-addon {
+ min-width:150px;
+}
diff --git a/modules-available/internetaccess/templates/_page.html b/modules-available/internetaccess/templates/_page.html
index ac82325c..1413e280 100644
--- a/modules-available/internetaccess/templates/_page.html
+++ b/modules-available/internetaccess/templates/_page.html
@@ -1,40 +1,38 @@
<h1>{{lang_internetAccess}}</h1>
-<form action="?do=InternetAccess" method="post">
+<form action="?do=InternetAccess&amp;action=save" method="post">
<input type="text" name="prevent_autofill" id="prevent_autofill" value="" style="position:absolute;top:-2000px" tabindex="-1">
<input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
<input type="hidden" name="token" value="{{token}}">
- <div class="panel panel-default">
- <div class="panel-heading">{{lang_internetAccess}}</div>
- <div class="panel-body">
- <p>{{lang_description}}</p>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_proxyType}}</span>
- <select name="PROXY_CONF" class="form-control">
- <option value="AUTO" {{selected_AUTO}}>{{lang_automatic}} (dns-wpad)</option>
- <option value="NO" {{selected_NO}}>{{lang_no}}</option>
- <option value="YES" {{selected_YES}}>{{lang_manual}}</option>
- </select>
- </div>
- <br>
- <p>{{lang_manualProxyConfig}}</p>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_proxyAddress}} *</span>
- <input name="PROXY_ADDR" value="{{PROXY_ADDR}}" type="text" class="form-control">
- </div>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_proxyPort}} *</span>
- <input name="PROXY_PORT" value="{{PROXY_PORT}}" type="text" class="form-control">
- </div>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_proxyUsername}}</span>
- <input name="PROXY_USERNAME" value="{{PROXY_USERNAME}}" type="text" class="form-control">
- </div>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_proxyPassword}}</span>
- <input name="PROXY_PASSWORD" value="{{PROXY_PASSWORD}}" type="{{password_type}}" class="form-control">
- </div>
- <button class="btn btn-primary" type="submit">{{lang_save}}</button>
- </div>
+ <p>{{lang_description}}</p>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_proxyType}}</span>
+ <select name="PROXY_CONF" class="form-control">
+ <option value="AUTO" {{selected_AUTO}}>{{lang_automatic}} (dns-wpad)</option>
+ <option value="NO" {{selected_NO}}>{{lang_no}}</option>
+ <option value="YES" {{selected_YES}}>{{lang_manual}}</option>
+ </select>
+ </div>
+ <br>
+ <p>{{lang_manualProxyConfig}}</p>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_proxyAddress}} *</span>
+ <input name="PROXY_ADDR" value="{{PROXY_ADDR}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_proxyPort}} *</span>
+ <input name="PROXY_PORT" value="{{PROXY_PORT}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_proxyUsername}}</span>
+ <input name="PROXY_USERNAME" value="{{PROXY_USERNAME}}" type="text" class="form-control">
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">{{lang_proxyPassword}}</span>
+ <input name="PROXY_PASSWORD" value="{{PROXY_PASSWORD}}" type="{{password_type}}" class="form-control">
+ </div>
+ <br/>
+ <div class="text-right">
+ <button {{^allowedSave}}disabled{{/allowedSave}} class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</form>
diff --git a/modules-available/js_stupidtable/clientscript.js b/modules-available/js_stupidtable/clientscript.js
index bfbc9112..8b8fc107 100644
--- a/modules-available/js_stupidtable/clientscript.js
+++ b/modules-available/js_stupidtable/clientscript.js
@@ -24,7 +24,180 @@
SOFTWARE.
*/
-(function(c){c.fn.stupidtable=function(b){return this.each(function(){var a=c(this);b=b||{};b=c.extend({},c.fn.stupidtable.default_sort_fns,b);a.data("sortFns",b);a.on("click.stupidtable","thead th",function(){c(this).stupidsort()})})};c.fn.stupidsort=function(b){var a=c(this),g=0,f=c.fn.stupidtable.dir,e=a.closest("table"),k=a.data("sort")||null;if(null!==k){a.parents("tr").find("th").slice(0,c(this).index()).each(function(){var a=c(this).attr("colspan")||1;g+=parseInt(a,10)});var d;1==arguments.length?
- d=b:(d=b||a.data("sort-default")||f.ASC,a.data("sort-dir")&&(d=a.data("sort-dir")===f.ASC?f.DESC:f.ASC));if(a.data("sort-dir")!==d)return a.data("sort-dir",d),e.trigger("beforetablesort",{column:g,direction:d}),e.css("display"),setTimeout(function(){var b=[],l=e.data("sortFns")[k],h=e.children("tbody").children("tr");h.each(function(a,d){var e=c(d).children().eq(g),f=e.data("sort-value");"undefined"===typeof f&&(f=e.text(),e.data("sort-value",f));b.push([f,d])});b.sort(function(a,b){return l(a[0],
- b[0])});d!=f.ASC&&b.reverse();h=c.map(b,function(a){return a[1]});e.children("tbody").append(h);e.find("th").data("sort-dir",null).removeClass("sorting-desc sorting-asc");a.data("sort-dir",d).addClass("sorting-"+d);e.trigger("aftertablesort",{column:g,direction:d});e.css("display")},10),a}};c.fn.updateSortVal=function(b){var a=c(this);a.is("[data-sort-value]")&&a.attr("data-sort-value",b);a.data("sort-value",b);return a};c.fn.stupidtable.dir={ASC:"asc",DESC:"desc"};c.fn.stupidtable.default_sort_fns=
- {"int":function(b,a){return parseInt(b,10)-parseInt(a,10)},"float":function(b,a){return parseFloat(b)-parseFloat(a)},string:function(b,a){return b.toString().localeCompare(a.toString())},"string-ins":function(b,a){b=b.toString().toLocaleLowerCase();a=a.toString().toLocaleLowerCase();return b.localeCompare(a)}}})(jQuery);
+(function($) {
+ $.fn.stupidtable = function(sortFns) {
+ return this.each(function() {
+ var $table = $(this);
+ sortFns = sortFns || {};
+ sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
+ $table.data('sortFns', sortFns);
+
+ $table.on("click.stupidtable", "thead th", function() {
+ $(this).stupidsort();
+ });
+
+ // to show the sort-arrow next to the table header
+ $table.on("aftertablesort", function (event, data) {
+ var th = $(this).find("th");
+ th.find(".arrow").remove();
+ var dir = $.fn.stupidtable.dir;
+ var arrow = data.direction === dir.ASC ? "down" : "up";
+ th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>');
+ });
+ });
+ };
+
+
+ // Expects $("#mytable").stupidtable() to have already been called.
+ // Call on a table header.
+ $.fn.stupidsort = function(force_direction){
+ var $this_th = $(this);
+ var th_index = 0; // we'll increment this soon
+ var dir = $.fn.stupidtable.dir;
+ var $table = $this_th.closest("table");
+ var datatype = $this_th.data("sort") || null;
+
+ // No datatype? Nothing to do.
+ if (datatype === null) {
+ return;
+ }
+
+ // Account for colspans
+ $this_th.parents("tr").find("th").slice(0, $(this).index()).each(function() {
+ var cols = $(this).attr("colspan") || 1;
+ th_index += parseInt(cols,10);
+ });
+
+ var sort_dir;
+ if(arguments.length == 1){
+ sort_dir = force_direction;
+ }
+ else{
+ sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC;
+ if ($this_th.data("sort-dir"))
+ sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
+ }
+
+ // Bail if already sorted in this direction
+ if ($this_th.data("sort-dir") === sort_dir) {
+ return;
+ }
+ // Go ahead and set sort-dir. If immediately subsequent calls have same sort-dir they will bail
+ $this_th.data("sort-dir", sort_dir);
+
+ $table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
+
+ // More reliable method of forcing a redraw
+ $table.css("display");
+
+ // Run sorting asynchronously on a timout to force browser redraw after
+ // `beforetablesort` callback. Also avoids locking up the browser too much.
+ setTimeout(function() {
+ // Gather the elements for this column
+ var column = [];
+ var sortFns = $table.data('sortFns');
+ var sortMethod = sortFns[datatype];
+ var collapsedCount = $table.children("tbody").children("tr.collapse").length;
+ var trs = $table.children("tbody").children("tr:not(.slx-decollapse)");
+
+ // Extract the data for the column that needs to be sorted and pair it up
+ // with the TR itself into a tuple. This way sorting the values will
+ // incidentally sort the trs.
+ trs.each(function(index,tr) {
+ var $e = $(tr).children().eq(th_index);
+ var sort_val = $e.data("sort-value");
+
+ // Store and read from the .data cache for display text only sorts
+ // instead of looking through the DOM every time
+ if(typeof(sort_val) === "undefined"){
+ var txt = $e.text();
+ $e.data('sort-value', txt);
+ sort_val = txt;
+ }
+ column.push([sort_val, tr]);
+ });
+
+ // Sort by the data-order-by value
+ column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
+ if (sort_dir != dir.ASC)
+ column.reverse();
+
+ // Replace the content of tbody with the sorted rows. Strangely
+ // enough, .append accomplishes this for us.
+ trs = $.map(column, function(kv) { return kv[1]; });
+
+ if (collapsedCount > 0) {
+ var showCount = trs.length - collapsedCount;
+ for (var i = 0; i < trs.length; i++) {
+ if (i < showCount) {
+ $(trs[i]).removeClass("collapse");
+ } else {
+ $(trs[i]).addClass("collapse");
+ }
+ }
+ }
+
+ $table.children("tbody").prepend(trs);
+
+ // Reset siblings
+ $table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
+ $this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
+
+ $table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
+ $table.css("display");
+ }, 10);
+
+ return $this_th;
+ };
+
+ // Call on a sortable td to update its value in the sort. This should be the
+ // only mechanism used to update a cell's sort value. If your display value is
+ // different from your sort value, use jQuery's .text() or .html() to update
+ // the td contents, Assumes stupidtable has already been called for the table.
+ $.fn.updateSortVal = function(new_sort_val){
+ var $this_td = $(this);
+ if($this_td.is('[data-sort-value]')){
+ // For visual consistency with the .data cache
+ $this_td.attr('data-sort-value', new_sort_val);
+ }
+ $this_td.data("sort-value", new_sort_val);
+ return $this_td;
+ };
+
+ // ------------------------------------------------------------------
+ // Default settings
+ // ------------------------------------------------------------------
+ $.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
+ $.fn.stupidtable.default_sort_fns = {
+ "int": function(a, b) {
+ return parseInt(a, 10) - parseInt(b, 10);
+ },
+ "float": function(a, b) {
+ return parseFloat(a) - parseFloat(b);
+ },
+ "string": function(a, b) {
+ return a.toString().localeCompare(b.toString());
+ },
+ "string-ins": function(a, b) {
+ a = a.toString().toLocaleLowerCase();
+ b = b.toString().toLocaleLowerCase();
+ return a.localeCompare(b);
+ },
+ "ipv4":function(a,b){
+ var aa = a.split(".");
+ var bb = b.split(".");
+
+ var resulta = aa[0]*0x1000000 + aa[1]*0x10000 + aa[2]*0x100 + aa[3]*1;
+ var resultb = bb[0]*0x1000000 + bb[1]*0x10000 + bb[2]*0x100 + bb[3]*1;
+
+ return resulta-resultb;
+ }
+ };
+})(jQuery);
+
+document.addEventListener("DOMContentLoaded", function() {
+ var table = $(".stupidtable");
+ if (table.length) {
+ table = table.stupidtable();
+ }
+}); \ No newline at end of file
diff --git a/modules-available/js_stupidtable/style.css b/modules-available/js_stupidtable/style.css
new file mode 100644
index 00000000..614a3d38
--- /dev/null
+++ b/modules-available/js_stupidtable/style.css
@@ -0,0 +1,3 @@
+th[data-sort] {
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/modules-available/locationinfo/api.inc.php b/modules-available/locationinfo/api.inc.php
index e14fe9d7..ceaf04c0 100644
--- a/modules-available/locationinfo/api.inc.php
+++ b/modules-available/locationinfo/api.inc.php
@@ -1,33 +1,5 @@
<?php
-if (Request::get('redirect', false, 'int') !== false) {
- // Redirect to actual panel from uuid
- $uuid = Request::get('uuid', false, 'string');
- if ($uuid === false) {
- http_response_code(400);
- die('Missing uuid parameter');
- }
- $row = Database::queryFirst('SELECT paneltype, panelconfig FROM locationinfo_panel WHERE paneluuid = :uuid', compact('uuid'));
- if ($row === false) {
- http_response_code(404);
- die('Panel not found');
- }
- if ($row['paneltype'] === 'DEFAULT') {
- Util::redirect(dirname($_SERVER['SCRIPT_NAME']) . '/modules/locationinfo/frontend/doorsign.html?uuid=' . $uuid);
- } elseif ($row['paneltype'] === 'SUMMARY') {
- Util::redirect(dirname($_SERVER['SCRIPT_NAME']) . '/modules/locationinfo/frontend/overview.html?uuid=' . $uuid);
- } elseif ($row['paneltype'] === 'URL') {
- $data = json_decode($row['panelconfig'], true);
- if (!$data || !isset($data['url'])) {
- http_response_code('500');
- die('Panel config corrupted on server');
- }
- Util::redirect($data['url']);
- }
- http_response_code('500');
- die('Panel has invalid type "' . $row['paneltype'] . '"');
-}
-
/*
* vvv - API to Panel - vvv
*/
@@ -46,7 +18,7 @@ function HandleParameters()
if ($get === "timestamp") {
$output = array('ts' => getLastChangeTs($uuid));
} elseif ($get === "machines") {
- $locationIds = getLocationsOr404($uuid);
+ $locationIds = LocationInfo::getLocationsOr404($uuid);
$output = array();
InfoPanel::appendMachineData($output, $locationIds, false);
$output = array_values($output);
@@ -57,13 +29,13 @@ function HandleParameters()
die('Panel not found');
}
} elseif ($get === "pcstates") {
- $locationIds = getLocationsOr404($uuid);
+ $locationIds = LocationInfo::getLocationsOr404($uuid);
$output = getPcStates($locationIds);
} elseif ($get === "locationtree") {
- $locationIds = getLocationsOr404($uuid);
+ $locationIds = LocationInfo::getLocationsOr404($uuid);
$output = getLocationTree($locationIds);
} elseif ($get === "calendar") {
- $locationIds = getLocationsOr404($uuid);
+ $locationIds = LocationInfo::getLocationsOr404($uuid);
$output = getCalendar($locationIds);
}
if ($output !== false) {
@@ -76,22 +48,6 @@ function HandleParameters()
}
/**
- * Return list of locationids associated with given panel.
- * @param string $paneluuid panel
- * @return int[] locationIds
- */
-function getLocationsOr404($paneluuid)
-{
- $panel = Database::queryFirst('SELECT locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid',
- compact('paneluuid'));
- if ($panel !== false) {
- return array_map('intval', explode(',', $panel['locationids']));
- }
- http_response_code(404);
- die('Panel not found');
-}
-
-/**
* Get last config modification timestamp for given panel.
* This was planned to be smart and check the involved locations,
* even going up the location tree if the opening time schedule
@@ -132,8 +88,9 @@ function getPcStates($idList)
'id' => $id,
'idle' => 0,
'occupied' => 0,
- 'off' => 0,
+ 'offline' => 0,
'broken' => 0,
+ 'standby' => 0,
);
}
@@ -218,10 +175,10 @@ function getCalendar($idList)
foreach ($serverList as $serverid => $server) {
$serverInstance = CourseBackend::getInstance($server['type']);
if ($serverInstance === false) {
- EventLog::warning('Cannot fetch schedule for locationid ' . $server['locationid']
+ EventLog::warning('Cannot fetch schedule for location (' . implode(', ', $server['idlist']) . ')'
. ': Backend type ' . $server['type'] . ' unknown. Disabling location.');
- Database::exec("UPDATE locationinfo_locationconfig SET serverid = 0 WHERE locationid = :lid",
- array('lid' => $server['locationid']));
+ Database::exec("UPDATE locationinfo_locationconfig SET serverid = NULL WHERE locationid IN (:lid)",
+ array('lid' => $server['idlist']));
continue;
}
$credentialsOk = $serverInstance->setCredentials($serverid, $server['credentials']);
diff --git a/modules-available/locationinfo/frontend/frontendscript.js b/modules-available/locationinfo/frontend/frontendscript.js
new file mode 100644
index 00000000..cc5c6827
--- /dev/null
+++ b/modules-available/locationinfo/frontend/frontendscript.js
@@ -0,0 +1,83 @@
+/**
+ * checks if a room is on a given date/time open
+ * @param date Date Object
+ * @param room Room object
+ * @returns {Boolean} for open or not
+ */
+function IsOpen(date, room) {
+ if (!room.openingTimes || room.openingTimes.length === 0) return true;
+ var tmp = room.openingTimes[date.getDay()];
+ if (!tmp) return false;
+ var openDate = new Date(date.getTime());
+ var closeDate = new Date(date.getTime());
+ for (var i = 0; i < tmp.length; i++) {
+ openDate.setHours(tmp[i].HourOpen);
+ openDate.setMinutes(tmp[i].MinutesOpen);
+ closeDate.setHours(tmp[i].HourClose);
+ closeDate.setMinutes(tmp[i].MinutesClose);
+ if (openDate < date && closeDate > date) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Convert passed argument to integer if possible, return NaN otherwise.
+ * The difference to parseInt() is that leading zeros are ignored and not
+ * interpreted as octal representation.
+ *
+ * @param str string or already a number
+ * @return {number} str converted to number, or NaN
+ */
+function toInt(str) {
+ var t = typeof str;
+ if (t === 'number') return str | 0;
+ if (t === 'string') return parseInt(str.replace(/^0+([^0])/, '$1'));
+ return NaN;
+}
+
+/**
+ * used for countdown
+ * computes the time difference between 2 Date objects
+ * @param {Date} a
+ * @param {Date} b
+ * @param {Array} globalConfig
+ * @returns {string} printable time
+ */
+function GetTimeDiferenceAsString(a, b, globalConfig = null) {
+ if (!a || !b) {
+ return "";
+ }
+ var milliseconds = a.getTime() - b.getTime();
+ var days = Math.floor((milliseconds / (1000 * 60 * 60 * 24)) % 31);
+ if (days !== 0) {
+ // don't show?
+ return "";
+ }
+ var seconds = Math.floor((milliseconds / 1000) % 60);
+ milliseconds -= seconds * 1000;
+ var minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
+ milliseconds -= minutes * 1000 * 60;
+ var hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
+
+ if (globalConfig && globalConfig.prettytime) {
+ var str = '';
+ if (hours > 0) {
+ str += hours + 'h ';
+ }
+ str += minutes + 'min ';
+ return str;
+ }
+
+ if (minutes < 10) {
+ minutes = "0" + minutes;
+ }
+ if (globalConfig && globalConfig.eco) {
+ return hours + ":" + minutes;
+ }
+ if (seconds < 10) {
+ seconds = "0" + seconds;
+ }
+ return hours + ":" + minutes + ":" + seconds;
+} \ No newline at end of file
diff --git a/modules-available/locationinfo/inc/coursebackend.inc.php b/modules-available/locationinfo/inc/coursebackend.inc.php
index 1fe87202..7162c885 100644
--- a/modules-available/locationinfo/inc/coursebackend.inc.php
+++ b/modules-available/locationinfo/inc/coursebackend.inc.php
@@ -47,17 +47,20 @@ abstract class CourseBackend
foreach (glob(dirname(__FILE__) . '/coursebackend/coursebackend_*.inc.php', GLOB_NOSORT) as $file) {
require_once $file;
preg_match('#coursebackend_([^/\.]+)\.inc\.php$#i', $file, $out);
- if (!class_exists('coursebackend_' . $out[1])) {
- trigger_error("Backend type source unit $file doesn't seem to define class CourseBackend_{$out[1]}", E_USER_ERROR);
+ $className = 'CourseBackend_' . $out[1];
+ if (!class_exists($className)) {
+ trigger_error("Backend type source unit $file doesn't seem to define class $className", E_USER_ERROR);
}
+ if (!CONFIG_DEBUG && defined("$className::DEBUG") && constant("$className::DEBUG"))
+ continue;
self::$backendTypes[$out[1]] = true;
}
}
/**
- * Get all known config module types.
+ * Get all known backend types.
*
- * @return array list of modules
+ * @return array list of backends
*/
public static function getList()
{
@@ -65,24 +68,30 @@ abstract class CourseBackend
return array_keys(self::$backendTypes);
}
+ public static function exists($backendType)
+ {
+ self::loadDb();
+ return isset(self::$backendTypes[$backendType]);
+ }
+
/**
- * Get fresh instance of ConfigModule subclass for given module type.
+ * Get fresh instance of CourseBackend subclass for given backend type.
*
- * @param string $moduleType name of module type
- * @return \CourseBackend module instance
+ * @param string $backendType name of module type
+ * @return \CourseBackend|false module instance
*/
- public static function getInstance($moduleType)
+ public static function getInstance($backendType)
{
self::loadDb();
- if (!isset(self::$backendTypes[$moduleType])) {
- error_log('Unknown module type: ' . $moduleType);
+ if (!isset(self::$backendTypes[$backendType])) {
+ error_log('Unknown module type: ' . $backendType);
return false;
}
- if (!is_object(self::$backendTypes[$moduleType])) {
- $class = "coursebackend_$moduleType";
- self::$backendTypes[$moduleType] = new $class;
+ if (!is_object(self::$backendTypes[$backendType])) {
+ $class = "coursebackend_$backendType";
+ self::$backendTypes[$backendType] = new $class;
}
- return self::$backendTypes[$moduleType];
+ return self::$backendTypes[$backendType];
}
/**
@@ -131,6 +140,19 @@ abstract class CourseBackend
*/
protected abstract function fetchSchedulesInternal($roomId);
+ private static function fixTime(&$start, &$end)
+ {
+ if (!preg_match('/^\d+-\d+-\d+T\d+:\d+:\d+$/', $start) || !preg_match('/^\d+-\d+-\d+T\d+:\d+:\d+$/', $start))
+ return false;
+ $start = strtotime($start);
+ $end = strtotime($end);
+ if ($start >= $end)
+ return false;
+ $start = date('Y-m-d\TH:i:s', $start);
+ $end = date('Y-m-d\TH:i:s', $end);
+ return true;
+ }
+
/**
* Method for fetching the schedule of the given rooms on a server.
*
@@ -184,18 +206,31 @@ abstract class CourseBackend
return false;
}
- if ($this->getCacheTime() > 0) {
- // Caching requested by backend, write to DB
- foreach ($backendResponse as $serverRoomId => $calendar) {
+ foreach ($backendResponse as $serverRoomId => &$calendar) {
+ $calendar = array_values($calendar);
+ for ($i = 0; $i < count($calendar); ++$i) {
+ if (empty($calendar[$i]['title'])) {
+ $calendar[$i]['title'] = '-';
+ }
+ if (!self::fixTime($calendar[$i]['start'], $calendar[$i]['end'])) {
+ error_log("Ignoring calendar entry '{$calendar[$i]['title']}' with bad time format");
+ unset($calendar[$i]);
+ }
+ }
+ $calendar = array_values($calendar);
+ if ($this->getCacheTime() > 0) {
+ // Caching requested by backend, write to DB
$value = json_encode($calendar);
Database::simpleQuery("UPDATE locationinfo_locationconfig SET calendar = :ttable, lastcalendarupdate = :now
- WHERE serverid = :serverid AND serverlocationid = :serverlocationid", array(
+ WHERE serverid = :serverid AND serverlocationid = :serverlocationid", array(
'serverid' => $this->serverId,
'serverlocationid' => $serverRoomId,
'ttable' => $value,
'now' => $NOW
));
}
+
+ unset($calendar);
}
// Add rooms that were requested to the final return value
foreach ($remoteIds as $location => $serverRoomId) {
@@ -302,6 +337,9 @@ abstract class CourseBackend
$xml = new SimpleXMLElement($cleanresponse);
} catch (Exception $e) {
$this->error = 'Could not parse reply as XML, got ' . get_class($e) . ': ' . $e->getMessage();
+ if (CONFIG_DEBUG) {
+ error_log($cleanresponse);
+ }
return false;
}
$array = json_decode(json_encode((array)$xml), true);
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php
index fac3f296..8843e372 100644
--- a/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php
@@ -106,6 +106,9 @@ class CourseBackend_Davinci extends CourseBackend
}
$return = $this->xmlStringToArray($return);
if ($return === false) {
+ if (CONFIG_DEBUG) {
+ error_log('Room was ' . $roomId);
+ }
continue;
}
$lessons = $this->getArrayPath($return, '/Lessons/Lesson');
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php
index e2577284..adff8b1b 100644
--- a/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php
@@ -4,6 +4,8 @@ class CourseBackend_Dummy extends CourseBackend
{
private $pw;
+ const DEBUG = true;
+
/**
* uses json to setCredentials, the json must follow the form given in
* getCredentials
@@ -87,13 +89,24 @@ class CourseBackend_Dummy extends CourseBackend
*
* @param $roomIds array with local ID as key and serverId as value
* @return array a recursive array that uses the roomID as key
- * and has the schedule array as value. A shedule array contains an array in this format:
- * ["start"=>'JJJJ-MM-DD HH:MM:SS',"end"=>'JJJJ-MM-DD HH:MM:SS',"title"=>string]
+ * and has the schedule array as value. A schedule array contains an array in this format:
+ * ["start"=>'YYYY-MM-DD<T>HH:MM:SS',"end"=>'YYYY-MM-DD<T>HH:MM:SS',"title"=>string]
*/
public function fetchSchedulesInternal($roomId)
{
$a = array();
foreach ($roomId as $id) {
+ if ($id == 1) {
+ $now = time();
+ return array($id => array(
+ array(
+ 'title' => 'Lange',
+ 'start' => date('Y-m-d', $now) . 'T0:00:00',
+ 'end' => date('Y-m-d', $now + 86400 * 3) . 'T0:00:00',
+ )
+ ));
+ }
+ // Normal
$x = array();
$time = strtotime('today');
$end = strtotime('+7 days', $time);
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php b/modules-available/locationinfo/inc/coursebackend/exchange.todo
index 538f7382..538f7382 100755
--- a/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php
+++ b/modules-available/locationinfo/inc/coursebackend/exchange.todo
diff --git a/modules-available/locationinfo/inc/infopanel.inc.php b/modules-available/locationinfo/inc/infopanel.inc.php
index 66ee0ae7..12b6aec7 100644
--- a/modules-available/locationinfo/inc/infopanel.inc.php
+++ b/modules-available/locationinfo/inc/infopanel.inc.php
@@ -20,6 +20,7 @@ class InfoPanel
}
if ($panel['paneltype'] === 'URL') {
+ // Shortcut for URL redirect
$config = json_decode($panel['panelconfig'], true);
return $panel['paneltype'];
}
@@ -31,6 +32,7 @@ class InfoPanel
if (!empty($panel['panelconfig'])) {
$json = json_decode($panel['panelconfig'], true);
if (is_array($json)) {
+ // Put location-specific overrides in separate variable for later use
if (isset($json['overrides']) && is_array($json['overrides'])) {
$overrides = $json['overrides'];
}
@@ -43,16 +45,27 @@ class InfoPanel
}
$config['locations'] = array();
$lids = array_map('intval', explode(',', $panel['locationids']));
- foreach ($lids as $lid) {
- $config['locations'][$lid] = array(
- 'id' => $lid,
- 'name' => isset($locations[$lid]) ? $locations[$lid]['locationname'] : 'noname00.pas',
- );
- if (isset($overrides[$lid]) && is_array($overrides[$lid])) {
- $config['locations'][$lid]['config'] = $overrides[$lid];
+ // Locations -
+ if ($panel['paneltype'] === 'SUMMARY') {
+ $lids = Location::getRecursiveFlat($lids);
+ $lids = array_keys($lids);
+ foreach ($lids as $lid) {
+ $config['locations'][$lid] = array('id' => $lid);
+ }
+ }
+ if ($panel['paneltype'] === 'DEFAULT') {
+ foreach ($lids as $lid) {
+ $config['locations'][$lid] = array(
+ 'id' => $lid,
+ 'name' => isset($locations[$lid]) ? $locations[$lid]['locationname'] : 'noname00.pas',
+ );
+ // Now apply any overrides from above
+ if (isset($overrides[$lid]) && is_array($overrides[$lid])) {
+ $config['locations'][$lid]['config'] = $overrides[$lid];
+ }
}
+ self::appendMachineData($config['locations'], $lids, true);
}
- self::appendMachineData($config['locations'], $lids, true);
self::appendOpeningTimes($config['locations'], $lids);
$config['ts'] = (int)$panel['lastchange'];
@@ -80,13 +93,21 @@ class InfoPanel
$idList = array_keys($array);
}
+ $ignoreList = array();
+ if (Module::isAvailable('runmode')) {
+ // Ignore clients with special runmode not marked as still being a client
+ $ignoreList = RunMode::getAllClients(false, false);
+ }
+
$positionCol = $withPosition ? 'm.position,' : '';
- $query = "SELECT m.locationid, m.machineuuid, $positionCol m.logintime, m.lastseen, m.lastboot FROM machine m
+ $query = "SELECT m.locationid, m.machineuuid, $positionCol m.logintime, m.lastseen, m.lastboot, m.state FROM machine m
WHERE m.locationid IN (:idlist)";
$dbquery = Database::simpleQuery($query, array('idlist' => $idList));
// Iterate over matching machines
while ($row = $dbquery->fetch(PDO::FETCH_ASSOC)) {
+ if (isset($ignoreList[$row['machineuuid']]))
+ continue;
settype($row['locationid'], 'int');
if (!isset($array[$row['locationid']])) {
$array[$row['locationid']] = array('id' => $row['locationid'], 'machines' => array());
@@ -107,7 +128,7 @@ class InfoPanel
}
}
$pc['pcState'] = LocationInfo::getPcState($row);
- //$pc['pcState'] = ['BROKEN', 'OFF', 'IDLE', 'OCCUPIED'][mt_rand(0,3)]; // XXX
+ //$pc['pcState'] = ['BROKEN', 'OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY'][mt_rand(0,4)]; // XXX
// Add the array to the machines list.
$array[$row['locationid']]['machines'][] = $pc;
@@ -216,4 +237,4 @@ class InfoPanel
return $result;
}
-} \ No newline at end of file
+}
diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php
index a5feb9ed..2ed3622d 100644
--- a/modules-available/locationinfo/inc/locationinfo.inc.php
+++ b/modules-available/locationinfo/inc/locationinfo.inc.php
@@ -6,7 +6,7 @@ class LocationInfo
/**
* Gets the pc data and returns it's state.
*
- * @param array $pc The pc data from the db. Array('logintime' =>, 'lastseen' =>, 'lastboot' =>)
+ * @param array $pc The pc data from the db. Array('state' => xx, 'lastseen' => xxx)
* @return int pc state
*/
public static function getPcState($pc)
@@ -16,16 +16,32 @@ class LocationInfo
$lastboot = (int)$pc['lastboot'];
$NOW = time();
- if ($NOW - $lastseen > 14 * 86400) {
+ if ($pc['state'] === 'OFFLINE' && $NOW - $lastseen > 21 * 86400) {
return "BROKEN";
- } elseif (($NOW - $lastseen > 610) || $lastboot === 0) {
- return "OFF";
- } elseif ($logintime === 0) {
- return "IDLE";
- } elseif ($logintime > 0) {
- return "OCCUPIED";
}
- return -1;
+ return $pc['state'];
+ }
+
+ /**
+ * Return list of locationids associated with given panel.
+ * @param string $paneluuid panel
+ * @param bool $recursive if true and paneltype == SUMMARY the result is recursive with all child room ids.
+ * @return int[] locationIds
+ */
+ public static function getLocationsOr404($paneluuid, $recursive = true)
+ {
+ $panel = Database::queryFirst('SELECT paneltype, locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid',
+ compact('paneluuid'));
+ if ($panel !== false) {
+ $idArray = array_map('intval', explode(',', $panel['locationids']));
+ if ($panel['paneltype'] == "SUMMARY" && $recursive) {
+ $idList = Location::getRecursiveFlat($idArray);
+ $idArray = array_keys($idList);
+ }
+ return $idArray;
+ }
+ http_response_code(404);
+ die('Panel not found');
}
/**
@@ -53,27 +69,38 @@ class LocationInfo
}
/**
- * Creates and returns a default config for room that didn't saved a config yet.
+ * Creates and returns a default config for room that didn't save a config yet.
*
* @return array Return a default config.
*/
public static function defaultPanelConfig($type)
{
- return array(
- 'language' => 'en',
- 'mode' => 1,
- 'vertical' => false,
- 'eco' => false,
- 'prettytime' => true,
- 'scaledaysauto' => true,
- 'daystoshow' => 7,
- 'rotation' => 0,
- 'scale' => 50,
- 'switchtime' => 20,
- 'calupdate' => 30,
- 'roomupdate' => 15,
- 'configupdate' => 180,
- );
+ if ($type === 'DEFAULT') {
+ return array(
+ 'language' => 'en',
+ 'mode' => 1,
+ 'vertical' => false,
+ 'eco' => false,
+ 'prettytime' => true,
+ 'scaledaysauto' => true,
+ 'daystoshow' => 7,
+ 'rotation' => 0,
+ 'scale' => 50,
+ 'switchtime' => 20,
+ 'calupdate' => 30,
+ 'roomupdate' => 15,
+ 'configupdate' => 180,
+ );
+ }
+ if ($type === 'SUMMARY') {
+ return array(
+ 'language' => 'en',
+ 'calupdate' => 30,
+ 'roomupdate' => 15,
+ 'configupdate' => 180,
+ );
+ }
+ return array();
}
/**
@@ -87,6 +114,12 @@ class LocationInfo
return $ret['panelname'];
}
+ /**
+ * Hook called by runmode module where we should modify the client config according to our
+ * needs. Disable standby/logout timeouts, enable autologin, set URL.
+ * @param $machineUuid
+ * @param $panelUuid
+ */
public static function configHook($machineUuid, $panelUuid)
{
$row = Database::queryFirst('SELECT paneltype, panelconfig FROM locationinfo_panel WHERE paneluuid = :uuid',
@@ -104,6 +137,9 @@ class LocationInfo
ConfigHolder::add('SLX_ADDONS', '', 1000);
ConfigHolder::add('SLX_LOGOUT_TIMEOUT', '', 1000);
ConfigHolder::add('SLX_SCREEN_STANDBY_TIMEOUT', '', 1000);
+ ConfigHolder::add('SLX_SYSTEM_STANDBY_TIMEOUT', '', 1000);
+ ConfigHolder::add('SLX_AUTOLOGIN', '1', 1000);
+ ConfigHolder::add('SLX_BROWSER_INSECURE', '1'); // TODO: Sat server might redirect to HTTPS, which in turn could have a self-signed cert - push to client
}
}
diff --git a/modules-available/locationinfo/install.inc.php b/modules-available/locationinfo/install.inc.php
index 7c47ac90..bbba3741 100644
--- a/modules-available/locationinfo/install.inc.php
+++ b/modules-available/locationinfo/install.inc.php
@@ -26,7 +26,7 @@ $t2 = $res[] = tableCreate('locationinfo_coursebackend', '
$t3 = $res[] = tableCreate('locationinfo_panel', "
`paneluuid` char(36) CHARACTER SET ascii NOT NULL,
`panelname` varchar(30) NOT NULL,
- `locationids` varchar(20) CHARACTER SET ascii NOT NULL,
+ `locationids` varchar(100) CHARACTER SET ascii NOT NULL,
`paneltype` enum('DEFAULT','SUMMARY', 'URL') NOT NULL,
`panelconfig` blob NOT NULL,
`lastchange` int(10) UNSIGNED NOT NULL DEFAULT 0,
@@ -52,7 +52,7 @@ if ($t1 === UPDATE_NOOP) {
if ($ret === false) {
finalResponse(UPDATE_FAILED, 'Could not add lastchange field');
} elseif ($ret > 0) {
- $ret[] = UPDATE_DONE;
+ $res[] = UPDATE_DONE;
}
}
}
@@ -71,6 +71,9 @@ if ($t1 === UPDATE_DONE) {
if ($t3 === UPDATE_NOOP) {
Database::exec("ALTER TABLE `locationinfo_panel` CHANGE `paneltype`
`paneltype` ENUM('DEFAULT', 'SUMMARY', 'URL') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL");
+ // 2017-12-02 expand locationids column
+ Database::exec("ALTER TABLE `locationinfo_panel` CHANGE `locationids`
+ `locationids` varchar(100) CHARACTER SET ascii NOT NULL");
}
// 2017-07-26 Add servername key
diff --git a/modules-available/locationinfo/lang/de/messages.json b/modules-available/locationinfo/lang/de/messages.json
index 8da5bd33..622b94ac 100644
--- a/modules-available/locationinfo/lang/de/messages.json
+++ b/modules-available/locationinfo/lang/de/messages.json
@@ -5,6 +5,9 @@
"ignored-invalid-range": "Eintrag mit ung\u00fcltiger Range ignoriert",
"ignored-invalid-start": "Eintrag mit ung\u00fcltiger Startzeit ignoriert",
"ignored-line-no-days": "Eintrag ohne ausgew\u00e4hlte Tage ignoriert",
+ "invalid-backend-type": "Ung\u00fcltiger Backend-Typ '{{0}}'",
"invalid-panel-id": "Ung\u00fcltige Panel-ID '{{0}}'",
- "invalid-server-id": "Ung\u00fcltige Server-ID '{{0}}'"
+ "invalid-panel-type": "Ung\u00fcltiger Panel-Typ '{{0}}'",
+ "invalid-server-id": "Ung\u00fcltige Server-ID '{{0}}'",
+ "server-id-missing": "Server-ID fehlt"
} \ No newline at end of file
diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json
index 1574d9e8..be2814d6 100644
--- a/modules-available/locationinfo/lang/de/template-tags.json
+++ b/modules-available/locationinfo/lang/de/template-tags.json
@@ -8,6 +8,7 @@
"lang_calendar": "Kalender",
"lang_calupdateTooltip": "Zeit nachdem der Kalender aktualisiert wird (in Minuten)",
"lang_checkConnection": "Verbindung pr\u00fcfen",
+ "lang_closed": "Geschlossen",
"lang_closingTime": "Schlie\u00dfungszeit",
"lang_config": "Einstellungen",
"lang_configupdateTooltip": "Zeit nach der die Einstellungen aktualisiert werden (in Minuten)",
@@ -24,12 +25,17 @@
"lang_ecoMode": "E-Ink Modus",
"lang_ecoTooltip": "Anstelle der Farb-basierten PC-Status Bilder, werden Symbol-basierte PC Bilder verwendet",
"lang_editDefaultPanelHints": "Hier k\u00f6nnen Sie ein Panel (z.B. digitales T\u00fcrschild) in Aussehen und Funktionsweise definieren. Um im Kalender \u00d6ffnungszeiten anzeigen zu k\u00f6nnen, m\u00fcssen Sie im Tab \"Raum-\/Ortsbezogene Einstellungen\" f\u00fcr den ausgew\u00e4hlten Raum entsprechend \u00d6ffnungszeiten eintragen. Damit im Kalender Veranstaltungen und andere Termine angezeigt werden k\u00f6nnen, muss ein funktionierendes Backend konfiguriert und den ausgew\u00e4hlten R\u00e4umen zugewiesen worden sein.",
+ "lang_editSummaryPanelHints": "Hier k\u00f6nnen Sie ein Summary-Panel definieren. Das Panel zeigt eine Übersicht der in den R\u00e4umen enthalten PCs.",
"lang_editPanel": "Panel bearbeiten",
+ "lang_editUrlPanelHints": "Hier k\u00f6nnen Sie konfigurieren, welche URL das Panel aufrufen soll. Dies erm\u00f6glicht Ihnen z.B. in Eingangsbereichen aktuelle Meldungen der Hochschule oder sonstige Webseiten anzuzeigen.",
"lang_entryName": "Name",
"lang_error": "Fehler",
"lang_expertMode": "Expertenmodus",
"lang_fourLocsHint": "Hier k\u00f6nnen Sie bis zu vier Orte ausw\u00e4hlen, die in diesem Panel angezeigt werden.",
+ "lang_free": "Frei",
"lang_general": "Allgemein",
+ "lang_ignoreSslTooltip": "Akzeptiere ung\u00fcltige, abgelaufene oder selbstsignierte SSL-Zertifikate",
+ "lang_insecureSsl": "Unsicheres SSL",
"lang_language": "Sprache",
"lang_languageTooltip": "Legt die Sprache der angezeigten Oberfl\u00e4che fest",
"lang_locationName": "Name",
@@ -37,6 +43,13 @@
"lang_locations": "Orte",
"lang_locationsTable": "R\u00e4ume \/ Orte",
"lang_locationsTableHints": "Hier k\u00f6nnen Sie f\u00fcr die R\u00e4ume und Orte Ihrer Einrichtung \u00d6ffnungszeiten hinterlegen, sowie die Verkn\u00fcpfung mit Raum-IDs aus konfigurierten Backends (z.B. HISinOne) vornehmen, damit Belegungspl\u00e4ne abgerufen werden k\u00f6nnen.",
+ "lang_longFri": "Freitag",
+ "lang_longMon": "Montag",
+ "lang_longSat": "Samstag",
+ "lang_longSun": "Sonntag",
+ "lang_longThu": "Donnerstag",
+ "lang_longTue": "Dienstag",
+ "lang_longWed": "Mittwoch",
"lang_mode": "Modus",
"lang_mode1": "Kalender & Raum",
"lang_mode2": "Kalender",
@@ -55,6 +68,8 @@
"lang_panelsTable": "Panels verwalten",
"lang_panelsTableHints": "Hier sehen Sie alle erstellen Anzeigetafeln, die per Browser an beliebiger Stelle angezeigt werden k\u00f6nnen, z.B. als digitales T\u00fcrschild.",
"lang_pleaseSelect": "Bitte w\u00e4hlen\u2026",
+ "lang_prettytime": "PrettyTime",
+ "lang_prettytimeTooltip": "Verwende ein anderes Anzeigeformat f\u00fcr die Uhrzeit",
"lang_recursiveServerSet": "Auch f\u00fcr alle untergeordneten R\u00e4ume setzen",
"lang_recursiveSetTooltip": "Wenn aktiviert, wird der Backend-Server auch f\u00fcr alle untergeordneten R\u00e4ume auf den hier gew\u00e4hlten Wert gesetzt",
"lang_remoteSchedule": "Abruf Belegungsplan",
@@ -68,6 +83,7 @@
"lang_rotation2": "180\u00b0",
"lang_rotation3": "90\u00b0 \u27f3",
"lang_rotationTooltip": "Rotiert den angezeigten Raum",
+ "lang_runmodeTHead": "Clients",
"lang_saturday": "Samstag",
"lang_scale": "Kalenderbreite",
"lang_scaleTooltip": "[10-90] Legt die Kalenderbreite fest (in Prozent)",
@@ -77,36 +93,30 @@
"lang_serverTableHints": "Liste aller definierten Backend-Server. Diese werden ben\u00f6tigt, um Belegungspl\u00e4ne f\u00fcr R\u00e4ume abzurufen.",
"lang_serverTooltip": "Legt fest, von welchem Backend-Server die Kalenderdaten bezogen werden",
"lang_serverType": "Typ",
+ "lang_shortFri": "Fr",
"lang_shortFriday": "Fr",
+ "lang_shortMon": "Mo",
"lang_shortMonday": "Mo",
+ "lang_shortSat": "Sa",
"lang_shortSaturday": "Sa",
+ "lang_shortSun": "So",
"lang_shortSunday": "So",
+ "lang_shortThu": "Do",
"lang_shortThursday": "Do",
+ "lang_shortTue": "Di",
"lang_shortTuesday": "Di",
+ "lang_shortWed": "Mi",
"lang_shortWednesday": "Mi",
"lang_summaryPanel": "\u00dcbersichts-Panel",
"lang_sunday": "Sonntag",
"lang_switchTime": "Wechselintervall",
"lang_switchTimeTooltip": "[1-120] Legt die Zeit fest, die vergeht bis ein Wechsel erfolgt (in Sekunden)",
+ "lang_to": "bis",
"lang_typeTooltip": "Legt fest um welchen Server-Typ es sich handelt",
"lang_updateRates": "Aktualisierungsintervall",
+ "lang_url": "URL",
+ "lang_urlPanel": "URL-Panel",
+ "lang_urlTooltip": "URL die aufgerufen wird",
"lang_vertical": "Vertikaler Modus",
- "lang_verticalTooltip": "Legt fest, ob Kalender und Raum \u00fcbereinander angezeigt werden sollen",
- "lang_closed": "Geschlossen",
- "lang_free": "Frei",
- "lang_shortSun": "So",
- "lang_shortMon": "Mo",
- "lang_shortTue": "Di",
- "lang_shortWed": "Mi",
- "lang_shortThu": "Do",
- "lang_shortFri": "Fr",
- "lang_shortSat": "Sa",
- "lang_longSun": "Sonntag",
- "lang_longMon": "Montag",
- "lang_longTue": "Dienstag",
- "lang_longWed": "Mittwoch",
- "lang_longThu": "Donnerstag",
- "lang_longFri": "Freitag",
- "lang_longSat": "Samstag",
- "lang_to": "bis"
+ "lang_verticalTooltip": "Legt fest, ob Kalender und Raum \u00fcbereinander angezeigt werden sollen"
} \ No newline at end of file
diff --git a/modules-available/locationinfo/lang/en/backend-hisinone.json b/modules-available/locationinfo/lang/en/backend-hisinone.json
index 0ff12c18..616b4c83 100644
--- a/modules-available/locationinfo/lang/en/backend-hisinone.json
+++ b/modules-available/locationinfo/lang/en/backend-hisinone.json
@@ -10,7 +10,7 @@
"username": "Username",
"username_helptext": "Authenticating user (only required for CourseService).",
"verifyCert": "Verify certificate",
- "verifyCert_helptext": "Wenn das Zertifikat abgelaufen ist, oder von keiner bekannten CA ausgestellt wurde, wird die Verbindung abgelehnt.",
+ "verifyCert_helptext": "If the certificate expired or was not signed by a known CA, the connection will be aborted.",
"verifyHostname": "Verify host name",
- "verifyHostname_helptext": "Der im Zertifikat angegebene Hostname muss mit dem Hostnamen aus der URL \u00fcbereinstimmen, sonst wird die Verbindung abgelehnt."
-} \ No newline at end of file
+ "verifyHostname_helptext": "The certificate's host name must match the host name given in the URL, otherwise the connection will be aborted."
+}
diff --git a/modules-available/locationinfo/lang/en/messages.json b/modules-available/locationinfo/lang/en/messages.json
index 6767bfcf..348390dd 100644
--- a/modules-available/locationinfo/lang/en/messages.json
+++ b/modules-available/locationinfo/lang/en/messages.json
@@ -5,6 +5,9 @@
"ignored-invalid-range": "Ignored entry with invalid range",
"ignored-invalid-start": "Ignored entry with invalid start time",
"ignored-line-no-days": "Ignored entry with no days selected",
+ "invalid-backend-type": "Invalid backend type '{{0}}'",
"invalid-panel-id": "Invalid panel id '{{0}}'",
- "invalid-server-id": "Invalid server id '{{0}}'"
+ "invalid-panel-type": "Invalid panel type '{{0}}'",
+ "invalid-server-id": "Invalid server id '{{0}}'",
+ "server-id-missing": "Server id is missing"
} \ No newline at end of file
diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json
index 900839a9..be927ee4 100644
--- a/modules-available/locationinfo/lang/en/template-tags.json
+++ b/modules-available/locationinfo/lang/en/template-tags.json
@@ -1,7 +1,5 @@
{
- "lang_defaultPanel": "Default panel",
"lang_addServer": "Server",
- "lang_summaryPanel": "Summary panel",
"lang_areYouSure": "Are you sure?",
"lang_autoScale": "Auto Days",
"lang_autoscaleTooltip": "Calculates the optimum amount of days to show from the display width",
@@ -10,6 +8,7 @@
"lang_calendar": "Calendar",
"lang_calupdateTooltip": "Time the calender querys for updates (in minutes)",
"lang_checkConnection": "Check connection",
+ "lang_closed": "Closed",
"lang_closingTime": "Closing time",
"lang_config": "Config",
"lang_configupdateTooltip": "Time interval the config gets updated (in minutes)",
@@ -18,24 +17,39 @@
"lang_day": "Day",
"lang_daysToShow": "Days",
"lang_daysToShowTooltip": "Defines the amount of days to show in the calendar",
+ "lang_defaultPanel": "Default panel",
"lang_deleteConfirmation": "Are you sure?",
"lang_display": "Display",
"lang_displayName": "Name",
"lang_displayNameTooltip": "Display name for this panel",
"lang_ecoMode": "E-Ink mode",
"lang_ecoTooltip": "Symbolic based pc state pictures are used instead of the colour based ones",
+ "lang_editDefaultPanelHints": "Here you can define panel properties for e.g. a digital door sign. To show opening times for a room you need to define corresponding times in the settings.\r\nIf you want to show calendar events you have to define a functioning backend first and link it to corresponding rooms.",
"lang_editPanel": "Edit panel",
+ "lang_editSummaryPanelHints": "Here you can define a summary panel which shows a overview of clients in your locations.",
+ "lang_editUrlPanelHints": "Here you can define which URL is opened by the panel. This enables you to show news about your university or any other website.",
"lang_entryName": "Name",
"lang_error": "Error",
"lang_expertMode": "Expert mode",
"lang_fourLocsHint": "You can pick up to four locations that will be shown in this panel.",
+ "lang_free": "Free",
"lang_general": "General",
+ "lang_ignoreSslTooltip": "Accept invalid, expired or self-signed ssl certificates",
+ "lang_insecureSsl": "Insecure SSL",
"lang_language": "Language",
"lang_languageTooltip": "The language the frontend uses",
"lang_locationName": "Name",
- "lang_locationSettings": "Settings",
+ "lang_locationSettings": "Location specific settings",
"lang_locations": "Locations",
"lang_locationsTable": "Rooms \/ Locations",
+ "lang_locationsTableHints": "Here you can define opening times for your locations and link the location ID to a configured backend (e.g. HISinOne) to show calendar events.",
+ "lang_longFri": "Friday",
+ "lang_longMon": "Monday",
+ "lang_longSat": "Saturday",
+ "lang_longSun": "Sunday",
+ "lang_longThu": "Thursday",
+ "lang_longTue": "Tuesday",
+ "lang_longWed": "Wednesday",
"lang_mode": "Mode",
"lang_mode1": "Calendar & Room",
"lang_mode2": "Calendar",
@@ -52,7 +66,10 @@
"lang_panelType": "Type",
"lang_panels": "Panels",
"lang_panelsTable": "Manage panels",
+ "lang_panelsTableHints": "This shows you all panels which can be shown anywhere by a browser of your choice, e.g. for a digital door sign.",
"lang_pleaseSelect": "Please select...",
+ "lang_prettytime": "PrettyTime",
+ "lang_prettytimeTooltip": "Use a different display format for the time",
"lang_recursiveServerSet": "Also set for all child locations",
"lang_recursiveSetTooltip": "If checked, all direct and indirect child locations will be configured to use the backend server selected above",
"lang_remoteSchedule": "Time table retrieval",
@@ -66,43 +83,40 @@
"lang_rotation2": "180\u00b0",
"lang_rotation3": "90\u00b0 \u27f3",
"lang_rotationTooltip": "Rotates the room",
+ "lang_runmodeTHead": "Clients",
"lang_saturday": "Saturday",
"lang_scale": "Calendar width",
"lang_scaleTooltip": "[10-90] Defines the calendar width (in percent)",
"lang_sec": "sec",
"lang_server": "Server",
"lang_serverTable": "Manage backend servers",
+ "lang_serverTableHints": "List of all configured server backends. Those are required if you want to show calendar events for your locations.",
"lang_serverTooltip": "Defines from which server the room queries the calendar data",
"lang_serverType": "Type",
+ "lang_shortFri": "Fri",
"lang_shortFriday": "Fri",
+ "lang_shortMon": "Mon",
"lang_shortMonday": "Mon",
+ "lang_shortSat": "Sat",
"lang_shortSaturday": "Sat",
+ "lang_shortSun": "Sun",
"lang_shortSunday": "Sun",
+ "lang_shortThu": "Thu",
"lang_shortThursday": "Thu",
+ "lang_shortTue": "Tue",
"lang_shortTuesday": "Tue",
+ "lang_shortWed": "Wed",
"lang_shortWednesday": "Wed",
+ "lang_summaryPanel": "Summary panel",
"lang_sunday": "Sunday",
"lang_switchTime": "Switchtime",
"lang_switchTimeTooltip": "[1-120] Sets the time between switching (in seconds)",
+ "lang_to": "to",
"lang_typeTooltip": "Defines on which type of server you want to connect to",
"lang_updateRates": "Update rates",
+ "lang_url": "URL",
+ "lang_urlPanel": "URL panel",
+ "lang_urlTooltip": "URL which is shown by the panel",
"lang_vertical": "Vertical mode",
- "lang_verticalTooltip": "Defines whether the room and calendar are shown above each other",
- "lang_closed": "Closed",
- "lang_free": "Free",
- "lang_shortSun": "Sun",
- "lang_shortMon": "Mon",
- "lang_shortTue": "Tue",
- "lang_shortWed": "Wed",
- "lang_shortThu": "Thu",
- "lang_shortFri": "Fri",
- "lang_shortSat": "Sat",
- "lang_longSun": "Sunday",
- "lang_longMon": "Monday",
- "lang_longTue": "Tuesday",
- "lang_longWed": "Wednesday",
- "lang_longThu": "Thursday",
- "lang_longFri": "Friday",
- "lang_longSat": "Saturday",
- "lang_to": "to"
+ "lang_verticalTooltip": "Defines whether the room and calendar are shown above each other"
} \ No newline at end of file
diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php
index f8aa1c5b..c6aa0860 100644
--- a/modules-available/locationinfo/page.inc.php
+++ b/modules-available/locationinfo/page.inc.php
@@ -223,14 +223,14 @@ class Page_LocationInfo extends Page
if ($locationids === false) {
if (!$failIfEmpty)
return array();
- Message::addError('main.paramter-missing', 'locationids');
+ Message::addError('main.parameter-missing', 'locationids');
Util::redirect('?do=locationinfo');
}
$locationids = explode(',', $locationids);
$all = array_map(function ($item) { return $item['locationid']; }, Location::queryLocations());
$locationids = array_filter($locationids, function ($item) use ($all) { return in_array($item, $all); });
if ($failIfEmpty && empty($locationids)) {
- Message::addError('main.paramter-empty', 'locationids');
+ Message::addError('main.parameter-empty', 'locationids');
Util::redirect('?do=locationinfo');
}
return $locationids;
@@ -254,11 +254,14 @@ class Page_LocationInfo extends Page
$params = $this->preparePanelConfigDefault();
} elseif ($paneltype === 'URL') {
$params = $this->preparePanelConfigUrl();
+ } elseif ($paneltype === 'SUMMARY') {
+ $params = $this->preparePanelConfigSummary();
} else {
Message::addError('invalid-panel-type', $paneltype);
Util::redirect('?do=locationinfo');
}
+
if ($paneluuid === 'new') {
$paneluuid = Util::randomUuid();
$query = "INSERT INTO `locationinfo_panel` (paneluuid, panelname, locationids, paneltype, panelconfig, lastchange)
@@ -320,6 +323,13 @@ class Page_LocationInfo extends Page
return array('config' => $conf, 'locationids' => []);
}
+ private function preparePanelConfigSummary()
+ {
+ // Check locations
+ $locationids = self::getLocationIdsFromRequest(true);
+ return array('locationids' => $locationids);
+ }
+
/**
* Updates the server settings in the db.
*/
@@ -331,7 +341,7 @@ class Page_LocationInfo extends Page
$backend = CourseBackend::getInstance($servertype);
if ($backend === false) {
- Messages::addError('invalid-backend-type', $servertype);
+ Message::addError('invalid-backend-type', $servertype);
Util::redirect('?do=locationinfo');
}
@@ -439,7 +449,7 @@ class Page_LocationInfo extends Page
$locations = Location::getLocations(0, 0, false, true);
// Get hidden state of all locations
- $dbquery = Database::simpleQuery("SELECT li.locationid, li.serverid, li.serverlocationid, li.openingtime, li.lastcalendarupdate, cb.servername
+ $dbquery = Database::simpleQuery("SELECT li.locationid, li.serverid, li.serverlocationid, li.openingtime, li.lastcalendarupdate, cb.servertype, cb.servername
FROM `locationinfo_locationconfig` AS li
LEFT JOIN `locationinfo_coursebackend` AS cb USING (serverid)");
@@ -456,6 +466,7 @@ class Page_LocationInfo extends Page
'openingGlyph' => $glyph,
'backend' => $backend,
'lastCalendarUpdate' => $row['lastcalendarupdate'], // TODO
+ 'backendMissing' => !CourseBackend::exists($row['servertype']),
);
}
@@ -582,7 +593,7 @@ class Page_LocationInfo extends Page
/**
* Ajax the time table
*
- * @param $id id of the location
+ * @param int $id id of the location
*/
private function ajaxConfigLocation($id)
{
@@ -798,9 +809,11 @@ class Page_LocationInfo extends Page
}
$langs = Dictionary::getLanguages(true);
- foreach ($langs as &$lang) {
- if ($lang['cc'] === $config['language']) {
- $lang['selected'] = 'selected';
+ if (isset($config['language'])) {
+ foreach ($langs as &$lang) {
+ if ($lang['cc'] === $config['language']) {
+ $lang['selected'] = 'selected';
+ }
}
}
@@ -832,7 +845,7 @@ class Page_LocationInfo extends Page
'url' => $config['url'],
'ssl_checked' => $config['insecure-ssl'] ? 'checked' : '',
));
- } else { // TODO
+ } else {
Render::addTemplate('page-config-panel-summary', array(
'new' => $id === 'new',
'uuid' => $id,
@@ -862,19 +875,37 @@ class Page_LocationInfo extends Page
Util::redirect($config['url']);
}
- $data = array(
- 'uuid' => $uuid,
- 'config' => json_encode($config),
- 'language' => $config['language'],
- );
-
+ $data = array();
preg_match('#^(.*)/#', $_SERVER['PHP_SELF'], $script);
preg_match('#^([^?]+)/#', $_SERVER['REQUEST_URI'], $request);
if ($script[1] !== $request[1]) {
$data['dirprefix'] = $script[1] . '/';
}
- echo Render::parse('frontend-default', $data);
+ if ($type === 'DEFAULT') {
+ $data += array(
+ 'uuid' => $uuid,
+ 'config' => json_encode($config),
+ 'language' => $config['language'],
+ );
+
+ die(Render::parse('frontend-default', $data));
+ }
+
+ if ($type === 'SUMMARY') {
+ $locations = LocationInfo::getLocationsOr404($uuid, false);
+ $config['tree'] = Location::getRecursive($locations);
+ $data += array(
+ 'uuid' => $uuid,
+ 'config' => json_encode($config),
+ 'language' => $config['language'],
+ );
+
+ die(Render::parse('frontend-summary', $data));
+ }
+
+ http_response_code(500);
+ die('Unknown panel type ' . $type);
}
}
diff --git a/modules-available/locationinfo/templates/frontend-default.html b/modules-available/locationinfo/templates/frontend-default.html
index fc9c3eac..92cad055 100755
--- a/modules-available/locationinfo/templates/frontend-default.html
+++ b/modules-available/locationinfo/templates/frontend-default.html
@@ -29,7 +29,7 @@ optional:
<link rel='stylesheet' type='text/css' href='{{dirprefix}}modules/js_jqueryui/style.css'/>
<link rel='stylesheet' type='text/css' href='{{dirprefix}}modules/js_weekcalendar/style.css'/>
- <style type='text/css'>
+ <style type="text/css">
body {
margin: 0;
@@ -237,17 +237,12 @@ optional:
background: #000;
}
- .OFF .screen-inner {
+ .OFFLINE .screen-inner {
background: #332;
}
- /*
- .OFF .screen-inner:after {
- content: "\01F4A4";
- }
- */
-
- .IDLE .screen-inner {
+ .IDLE .screen-inner,
+ .STANDBY .screen-inner {
background: #250;
}
@@ -348,6 +343,7 @@ optional:
<script type='text/javascript' src='{{dirprefix}}script/jquery.js'></script>
<script type='text/javascript' src='{{dirprefix}}modules/js_jqueryui/clientscript.js'></script>
<script type='text/javascript' src="{{dirprefix}}modules/js_weekcalendar/clientscript.js"></script>
+ <script type='text/javascript' src='{{dirprefix}}modules/locationinfo/frontend/frontendscript.js'></script>
</head>
<body>
@@ -390,6 +386,10 @@ optional:
})();
$(document).ready(function () {
+ if (!SetUpDate) {
+ fatalError("js_weekcalendar not loaded");
+ return;
+ }
applyConfig({{{config}}});
});
@@ -429,13 +429,13 @@ optional:
var time = false;
var p = result.time.split('-');
if (p.length === 6) {
- time = new Date(p[0], p[1], p[2], p[3], p[4], p[5]);
+ time = new Date(p[0], (p[1] - 1), p[2], p[3], p[4], p[5]);
console.log(time);
}
- if (time === false || isNaN(time.getTime()) || time.getYear() < 2010) {
+ if (time === false || isNaN(time.getTime()) || time.getFullYear() < 2010) {
time = new Date(result.time);
}
- if (isNaN(time.getTime()) || time.getYear() < 2010) {
+ if (isNaN(time.getTime()) || time.getFullYear() < 2010) {
time = new Date();
}
SetUpDate(time);
@@ -981,10 +981,11 @@ optional:
var columnWidth = $cal.find(".wc-day-1").width();
if (room.config.scaledaysauto) {
- var result = ($cal.weekCalendar("option", "daysToShow") * columnWidth) / 100;
+ var result = ($cal.weekCalendar("option", "daysToShow") * columnWidth) / 130;
result = parseInt(Math.min(Math.max(Math.abs(result), 1), 7));
if (result !== $cal.weekCalendar("option", "daysToShow")) {
$cal.weekCalendar("option", "daysToShow", result);
+ columnWidth = $cal.find(".wc-day-1").width();
}
}
if (((!room.config.scaledaysauto) || $cal.weekCalendar("option", "daysToShow") === 1) && columnWidth < 85) {
@@ -1008,21 +1009,21 @@ optional:
clientHeight -= 6;
var height = clientHeight / (room.openTimes * $cal.weekCalendar("option", "timeslotsPerHour"));
-
if (height < 30) {
height = 30;
}
+ var fontHeight = Math.min(height, columnWidth / 2.1);
// Scale calendar font
- if (height > 120) {
+ if (fontHeight > 120) {
$cal.weekCalendar("option", "textSize", 28);
}
- else if (height > 100) {
+ else if (fontHeight > 100) {
$cal.weekCalendar("option", "textSize", 24);
- } else if (height > 80) {
+ } else if (fontHeight > 80) {
$cal.weekCalendar("option", "textSize", 22);
- } else if (height > 70) {
+ } else if (fontHeight > 70) {
$cal.weekCalendar("option", "textSize", 20);
- } else if (height > 60) {
+ } else if (fontHeight > 60) {
$cal.weekCalendar("option", "textSize", 14);
} else {
$cal.weekCalendar("option", "textSize", 13);
@@ -1041,48 +1042,6 @@ optional:
}
/**
- * used for countdown
- * computes the time difference between 2 Date objects
- * @param {Date} a
- * @param {Date} b
- * @returns {string} printable time
- */
- function GetTimeDiferenceAsString(a, b) {
- if (!a || !b) {
- return "";
- }
- var milliseconds = a.getTime() - b.getTime();
- var days = Math.floor((milliseconds / (1000 * 60 * 60 * 24)) % 31);
- if (days !== 0) {
- // don't show?
- return "";
- }
- var seconds = Math.floor((milliseconds / 1000) % 60);
- milliseconds -= seconds * 1000;
- var minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
- milliseconds -= minutes * 1000 * 60;
- var hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
-
- if (globalConfig.prettytime) {
- var str = '';
- if (hours > 0) {
- str += hours + 'muh';
- }
- }
-
- if (minutes < 10) {
- minutes = "0" + minutes;
- }
- if (globalConfig.eco) {
- return hours + ":" + minutes;
- }
- if (seconds < 10) {
- seconds = "0" + seconds;
- }
- return hours + ":" + minutes + ":" + seconds;
- }
-
- /**
* returns next closing time of a given room
* @param room
* @returns {Date} Object of next closing
@@ -1113,32 +1072,6 @@ optional:
return null;
}
-
- /**
- * checks if a room is on a given date/time open
- * @param date Date Object
- * @param room Room object
- * @returns {Boolean} for open or not
- */
- function IsOpen(date, room) {
- if (!room.openingTimes || room.openingTimes.length === 0) return true;
- var tmp = room.openingTimes[date.getDay()];
- if (!tmp) return false;
- var openDate = new Date(date.getTime());
- var closeDate = new Date(date.getTime());
- for (var i = 0; i < tmp.length; i++) {
- openDate.setHours(tmp[i].HourOpen);
- openDate.setMinutes(tmp[i].MinutesOpen);
- closeDate.setHours(tmp[i].HourClose);
- closeDate.setMinutes(tmp[i].MinutesClose);
- if (openDate < date && closeDate > date) {
- return true;
- }
- }
- return false;
- }
-
-
/**
* Returns next Opening
* @param room Room Object
@@ -1199,7 +1132,7 @@ optional:
var newText = false, newTime = false;
var seats = room.freePcs;
if (tmp.state === 'closed' || tmp.state === 'CalendarEvent' || tmp.state === 'Free') {
- newTime = GetTimeDiferenceAsString(tmp.end, MyDate());
+ newTime = GetTimeDiferenceAsString(tmp.end, MyDate(), globalConfig);
} else if (!same) {
newTime = '';
}
@@ -1351,7 +1284,6 @@ optional:
function initRoomLayout(room) {
var maxX = false, maxY = false;
var minX = false, minY = false;
- var xDifference, yDifference;
var x, y;
generateRoomLayoutDiv((100 - room.config.scale) + "%", room);
@@ -1380,15 +1312,12 @@ optional:
}
}
- xDifference = maxX - minX;
- yDifference = maxY - minY;
-
- room.xDifference = xDifference;
- room.yDifference = yDifference;
room.minX = minX;
room.minY = minY;
- room.maxX = maxX;
- room.maxY = maxY;
+ room.maxX = maxX + picSizeX;
+ room.maxY = maxY + picSizeY;
+ room.xDifference = (room.maxX - room.minX);
+ room.yDifference = (room.maxY - room.minY);
setUpRoom(room, layout);
scaleRoom(room);
@@ -1401,7 +1330,7 @@ optional:
* @param room Room Object
*/
function generateOffsetAndScale(room) {
- var clientHeight;
+ var clientHeight, clientWidth;
if (room.config.vertical && room.config.mode === 1) {
clientHeight = room.$.container.height() - (room.$.calendar.position().top + room.$.calendar.height());
@@ -1409,37 +1338,21 @@ optional:
clientHeight = room.$.container.height() - (room.$.header.height() + 5);
}
- var clientWidth = room.$.layout.width();
+ clientWidth = room.$.layout.width();
- var scaleX;
- if (room.xDifference !== 0) {
- scaleX = clientWidth / room.xDifference;
- } else {
- scaleX = clientWidth;
+ var scaleX = clientWidth / picSizeX, scaleY = clientHeight / picSizeY;
+ if (room.xDifference > 0) {
+ scaleX = (clientWidth - 20) / room.xDifference;
}
- var scaleY;
- if (room.yDifference !== 0) {
- scaleY = clientHeight / room.yDifference;
- } else {
- scaleY = clientHeight;
- }
- var scaleYs = (clientHeight - (picSizeY * scaleY)) / room.yDifference;
- var scaleXs = (clientWidth - (picSizeX * scaleX)) / room.xDifference;
- if (scaleYs <= 0) {
- scaleYs = 9999;
- }
- if (scaleXs <= 0) {
- scaleXs = 9999;
+ if (room.yDifference > 0) {
+ scaleY = (clientHeight - 20) / room.yDifference;
}
- room.scale = Math.min(scaleYs, scaleY, scaleXs, scaleX, (clientHeight * 0.9) / picSizeY, (clientWidth * 0.9) / picSizeX);
- room.xOffset = 0 - room.minX;
- room.yOffset = 0 - room.minY;
- room.xOffset += ((1 / 2 * (clientWidth - (((room.maxX + room.xOffset) * room.scale) + picSizeX * room.scale))) / room.scale);
- room.yOffset += ((1 / 2 * (clientHeight - (((room.maxY + room.yOffset) * room.scale) + picSizeY * room.scale))) / room.scale);
+ room.scale = Math.min(scaleY, scaleX);
+ room.xOffset = -room.minX * room.scale + (clientWidth - room.xDifference * room.scale) / 2;
+ room.yOffset = -room.minY * room.scale + (clientHeight - room.yDifference * room.scale) / 2;
}
-
/**
* adds images for each pc to Room Layout
* @param room Room Object
@@ -1575,11 +1488,11 @@ optional:
for (var i = 0; i < update.length; i++) {
var $div = $("#pc_" + room.id + "_" + update[i].id);
// Pc free
- if (update[i].pcState === "IDLE" || update[i].pcState === "OFF") {
+ if (update[i].pcState === "IDLE" || update[i].pcState === "OFFLINE" || update[i].pcState === "STANDBY") {
freePcs++;
}
- $div.removeClass('BROKEN OFF IDLE OCCUPIED'.replace(update[i].pcState, '')).addClass(update[i].pcState);
+ $div.removeClass('BROKEN OFFLINE IDLE OCCUPIED STANDBY'.replace(update[i].pcState, '')).addClass(update[i].pcState);
}
room.freePcs = freePcs;
UpdateRoomHeader(room);
@@ -1608,6 +1521,7 @@ optional:
function scaleRoom(room) {
if (!room.$.layout || !room.$.layout.is(':visible')) return;
room.resizeRoom = false;
+ if (!room.layout) return;
generateOffsetAndScale(room);
room.$.layout.css('font-size', Math.floor(room.scale) + 'pt');
for (var i = 0; i < room.layout.length; i++) {
@@ -1617,8 +1531,8 @@ optional:
room.layout[i].$div.css({
width: pcWidth,
height: pcHeight,
- top: ((room.layout[i].y + room.yOffset) * room.scale) + "px",
- left: ((room.layout[i].x + room.xOffset) * room.scale) + "px"
+ top: (room.layout[i].y * room.scale + room.yOffset) + "px",
+ left: (room.layout[i].x * room.scale + room.xOffset) + "px"
});
}
}
@@ -1744,20 +1658,5 @@ optional:
}, interval);
}
- /**
- * Convert passed argument to integer if possible, return NaN otherwise.
- * The difference to parseInt() is that leading zeros are ignored and not
- * interpreted as octal representation.
- *
- * @param str string or already a number
- * @return {number} str converted to number, or NaN
- */
- function toInt(str) {
- var t = typeof str;
- if (t === 'number') return str | 0;
- if (t === 'string') return parseInt(str.replace(/^0+([^0])/, '$1'));
- return NaN;
- }
-
</script>
</html>
diff --git a/modules-available/locationinfo/templates/frontend-summary.html b/modules-available/locationinfo/templates/frontend-summary.html
index dd5fc25d..ec5d8aab 100644
--- a/modules-available/locationinfo/templates/frontend-summary.html
+++ b/modules-available/locationinfo/templates/frontend-summary.html
@@ -2,7 +2,9 @@
<html lang="de">
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8">
<head>
- <script type='text/javascript' src='../../../script/jquery.js'></script>
+ <script type='text/javascript' src='{{dirprefix}}script/jquery.js'></script>
+ <script type='text/javascript' src='{{dirprefix}}modules/locationinfo/frontend/frontendscript.js'></script>
+
<style type='text/css'>
body {
@@ -32,6 +34,7 @@
.parent .parent, .parent .child {
min-height: 5em;
+ min-width: 90px;
}
.border {
@@ -59,7 +62,7 @@
border-style: solid;
}
- .pc-idle, .pc-occupied, .pc-off, .pc-broken {
+ .pc-idle, .pc-occupied, .pc-offline, .pc-broken, .pc-standby {
padding: 2px 1px;
text-align: center;
font-size: 90%;
@@ -78,10 +81,15 @@
border-radius: 3px 0px 0px 3px;
}
- .pc-off {
+ .pc-offline {
background-color: darkgrey;
}
+ .pc-standby {
+ background-color: darkgreen;
+ }
+
+
.pc-broken {
background-color: black;
color: white;
@@ -95,7 +103,8 @@
.paperEffect {
margin: 0 auto;
background-color: #fff;
- box-shadow: 0 0 0.2vmin rgba(0, 0, 0, 0.4), inset 0 0 1vmin rgba(0, 0, 0, 0.1);
+ box-shadow: 0 0 3px rgba(0, 0, 0, 0.4), 0 0 10px rgba(0, 0, 0, 0.1) inset;
+ box-shadow: 0 0 0.2vmin rgba(0, 0, 0, 0.4), 0 0 1vmin rgba(0, 0, 0, 0.1) inset;
border-radius: 1px;
}
@@ -106,22 +115,30 @@
var rooms = {};
var startdate;
var roomidsString = "";
-
+ var config = {{{config}}};
$(document).ready(function () {
- //temp
- SetUpDate(new Date());
init();
});
function init() {
- var ids = getUrlParameter("id");
- $.getJSON("../../../api.php?do=locationinfo&action=locationtree&id=" + ids, function (result) {
- generateLayout(result);
-
- setTimeout(update, 1000);
- });
-
+ var time = false;
+ if (config.time) {
+ var p = config.time.split('-');
+ if (p.length === 6) {
+ time = new Date(p[0], (p[1] - 1), p[2], p[3], p[4], p[5]);
+ console.log(time);
+ }
+ if (time === false || isNaN(time.getTime()) || time.getFullYear() < 2010) {
+ time = new Date(config.time);
+ }
+ if (!time || isNaN(time.getTime()) || time.getFullYear() < 2010) {
+ time = new Date();
+ }
+ }
+ SetUpDate(time);
+ generateLayout(config.tree);
+ update();
}
function SetUpDate(d) {
@@ -140,7 +157,7 @@
}
/**
- * generates the divs, decidecs if parent or child
+ * generates the divs, decides if parent or child
* @param json Room tree json
* @param myParent parent div
* @param outermost if the object is a root node
@@ -177,59 +194,89 @@
const ROOMUPDATE_MS = 2*60*1000;
const CALUPDATE_MS = 20*60*1000;
+ var timeout = null;
function update() {
var calendarUpdateIds = "";
var rommUpdateIds = "";
var count = 0;
var nextUpdate = 15000;
- for (var property in rooms) {
+ var property;
+ // TODO: Only query a few rooms is not possible with the new api stuff ...
+ for (property in rooms) {
if (rooms[property].lastCalendarUpdate === null || rooms[property].lastCalendarUpdate + CALUPDATE_MS < MyDate().getTime()) {
+ // TODO: NOT NECESSARY ANYMORE?!
calendarUpdateIds = addIdToUpdateList(calendarUpdateIds, rooms[property].id);
count++;
rooms[property].lastCalendarUpdate = MyDate().getTime();
}
if (rooms[property].lastRoomUpdate === null || rooms[property].lastRoomUpdate + ROOMUPDATE_MS < MyDate().getTime()) {
+ // TODO: NOT NECESSARY ANYMORE?!
rommUpdateIds = addIdToUpdateList(rommUpdateIds, rooms[property].id);
count++;
rooms[property].lastRoomUpdate = MyDate().getTime();
}
- if (count > 7) break;
+ // TODO if (count > 7) break;
}
if (calendarUpdateIds !== "") {
- queryCalendars(calendarUpdateIds);
+ queryCalendars();
nextUpdate = 1000;
}
if (rommUpdateIds !== "") {
- queryRooms(rommUpdateIds);
+ queryRooms();
nextUpdate = 1000;
}
- for (var property in rooms) {
+ for (property in rooms) {
upDateRoomState(rooms[property]);
}
+ clearTimeout(timeout);
setTimeout(update, nextUpdate);
}
+ function cleanDate(d) {
+ if (typeof d === 'string') {
+ // if is numeric
+ if (!isNaN(Number(d))) {
+ return cleanDate(parseInt(d, 10));
+ }
+
+ // this is a human readable date
+ if (d[d.length - 1] !== 'Z') d += 'Z';
+ var o = new Date(d);
+ o.setTime(o.getTime() + (o.getTimezoneOffset() * 60 * 1000));
+ return o;
+ }
+
+ if (typeof d === 'number') {
+ return new Date(d);
+ }
+
+ return d;
+ }
function UpdateTimeTables(json) {
var l = json.length;
for (var i = 0; i < l; i++) {
+ if (rooms[json[i].id] == null) {
+ continue;
+ }
rooms[json[i].id].timetable = json[i].calendar;
for (var property in rooms[json[i].id].timetable) {
- rooms[json[i].id].timetable[property].start = new Date(rooms[json[i].id].timetable[property].start);
- rooms[json[i].id].timetable[property].end = new Date(rooms[json[i].id].timetable[property].end);
+ rooms[json[i].id].timetable[property].start = cleanDate(rooms[json[i].id].timetable[property].start);
+ rooms[json[i].id].timetable[property].end = cleanDate(rooms[json[i].id].timetable[property].end);
}
ComputeCurrentState(rooms[json[i].id]);
}
+ update();
}
/**
* Querys Pc states
- * @param ids Room ID's which should be queried. Format for e.g.: "20,5,6"
+ * Room are queried with the {{uuid}} of the panel.
*/
- function queryRooms(ids) {
+ function queryRooms() {
$.ajax({
- url: "../../../api.php?do=locationinfo&action=pcstates&id=" + ids,
+ url: "{{dirprefix}}api.php?do=locationinfo&get=pcstates&uuid={{uuid}}",
dataType: 'json',
cache: false,
timeout: 30000,
@@ -241,6 +288,7 @@
return;
}
updatePcStates(result);
+
}, error: function () {
}
@@ -272,7 +320,6 @@
updateCourseText(room.id, "Geschlossen");
updateCoursTimer(room.id, "");
}
-
}
/**
@@ -282,10 +329,14 @@
function updatePcStates(json) {
var l = json.length;
for (var i = 0; i < l; i++) {
- updateRoomUsage(json[i].id, json[i].idle, json[i].occupied, json[i].off, json[i].broken)
+ updateRoomUsage(json[i].id, json[i].idle, json[i].occupied, json[i].offline, json[i].broken, json[i].standby)
}
}
+
+ const OT_DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+ const OT_KEYS = ['HourOpen', 'HourClose', 'MinutesOpen', 'MinutesClose'];
+
/**
* Generates a room Object and adds it to the rooms array
* @param id ID of the room
@@ -293,6 +344,22 @@
* @param config Config Json of the room
*/
function addRoom(id, name) {
+ var ot = [];
+ if (config && config.locations) {
+ for (var i = 0; i < config.locations.length; ++i) {
+ if (config.locations[i].id == id) {
+ // TODO: Messed up transformation from default panel
+ if (config.locations[i].openingtime) {
+ var raw_ot = config.locations[i].openingtime;
+
+ for (var j = 0; j < OT_DAYS.length; ++j) {
+ ot.push(filterOpeningTimesDay(raw_ot[OT_DAYS[j]]));
+ }
+ }
+ }
+ }
+ }
+
var room = {
id: id,
name: name,
@@ -301,7 +368,7 @@
nextEventEnd: null,
timeTilFree: null,
state: null,
- openingTimes: null,
+ openingTimes: ot,
lastCalendarUpdate: null,
lastRoomUpdate: null,
getState: function () {
@@ -329,6 +396,33 @@
}
}
+ /**
+ * Filter out invalid opening time entries from given array,
+ * also make sure all the values are of type number (int)
+ *
+ * @param {Array} arr
+ * @return {Array} list of valid opening times
+ */
+ function filterOpeningTimesDay(arr) {
+ if (!arr || arr.constructor !== Array) return [];
+ return arr.map(function (el) {
+ if (!el || typeof el !== 'object') return null;
+ for (var i = 0; i < OT_KEYS.length; ++i) {
+ el[OT_KEYS[i]] = toInt(el[OT_KEYS[i]]);
+ if (isNaN(el[OT_KEYS[i]])) return null;
+ }
+ return el;
+ }).filter(function (el) {
+ if (!el) return false;
+ if (el.HourOpen < 0 || el.HourOpen > 23) return false;
+ if (el.HourClose < 0 || el.HourClose > 23) return false;
+ if (el.HourClose < el.HourOpen) return false;
+ if (el.MinutesOpen < 0 || el.MinutesOpen > 59) return false;
+ if (el.MinutesClose < 0 || el.MinutesClose > 59) return false;
+ if (el.HourOpen === el.HourClose && el.MinutesClose < el.MinutesOpen) return false;
+ return true;
+ });
+ }
/**
* computes state of a room, states are:
@@ -379,6 +473,7 @@
room.state = {state: "Free", end: closing, titel: "", next: "closing"};
}
}
+
/**
* checks if a room is open
* @param room Room object
@@ -391,6 +486,7 @@
// changes from falls needs testing
return true;
}
+
var tmp = room.openingTimes[now.getDay()];
if (tmp == null) {
return false;
@@ -460,7 +556,7 @@
openDate.setHours(tmp[i].HourOpen);
openDate.setMinutes(tmp[i].MinutesOpen);
if (openDate > now) {
- if (!IsOpen(new Date(openDate.getTime() - 60000))) {
+ if (!IsOpen(new Date(openDate.getTime() - 60000), room)) {
if (bestdate == null || bestdate > openDate) {
bestdate = openDate;
}
@@ -500,7 +596,7 @@
closeDate.setHours(tmp[i].HourClose);
closeDate.setMinutes(tmp[i].MinutesClose);
if (closeDate > now) {
- if (!IsOpen(new Date(closeDate.getTime() + 60000))) {
+ if (!IsOpen(new Date(closeDate.getTime() + 60000), room)) {
if (bestdate == null || bestdate > closeDate) {
bestdate = closeDate;
}
@@ -522,20 +618,24 @@
* @param id of the child
* @param idle PC's on
* @param occupied PC's used
- * @param off PC's that are off
+ * @param offline PC's that are off
* @param broken PC's that are broken
+ * @param standby PCs in standby mode
*/
- function updateRoomUsage(id, idle, occupied, off, broken) {
- if (idle == 0 && occupied == 0 && off == 0) {
+ function updateRoomUsage(id, idle, occupied, offline, broken, standby) {
+ /* TODO Broken
+ if (idle === 0 && occupied === 0 && offline === 0 && broken === 0 && standby === 0) {
$('#parent_' + id).parent().hide();
return;
}
$('#parent_' + id).parent().show();
- var total = parseInt(idle) + parseInt(occupied) + parseInt(off) + parseInt(broken);
+ */
+ var total = parseInt(idle) + parseInt(occupied) + parseInt(offline) + parseInt(broken) + parseInt(standby);
$("#pc_Idle_" + id).text(idle).width((idle / total) * 100 + '%');
$("#pc_Occupied_" + id).text(occupied).width((occupied / total) * 100 + '%');
- $("#pc_Off_" + id).text(off).width((off / total) * 100 + '%');
+ $("#pc_Offline_" + id).text(offline).width((offline / total) * 100 + '%');
$("#pc_Broken_" + id).text(broken).width((broken / total) * 100 + '%');
+ $("#pc_Standby_" + id).text(standby).width((standby / total) * 100 + '%');
}
/**
@@ -553,7 +653,13 @@
* @param time Time value
*/
function updateCoursTimer(id, time) {
- $("#div_Time_" + id).text(time);
+ // TODO: Add seconds again with a better update rate.
+ var time_split = time.split(":");
+ if (time != "") {
+ $("#div_Time_" + id).text(time_split[0] + ":" + time_split[1]);
+ } else {
+ $("#div_Time_" + id).text(time);
+ }
}
/**
@@ -577,7 +683,8 @@
"<div class='pc-state-wrapper'>" +
"<div id = 'pc_Occupied_" + id + "' class='pc-occupied'>?</div>" +
"<div id = 'pc_Idle_" + id + "' class='pc-idle'>?</div>" +
- "<div id = 'pc_Off_" + id + "' class='pc-off'>?</div>" +
+ "<div id = 'pc_Standby_" + id + "' class='pc-standby'>?</div>" +
+ "<div id = 'pc_Offline_" + id + "' class='pc-offline'>?</div>" +
"<div id = 'pc_Broken_" + id + "' class='pc-broken'>?</div>" +
"</div>" +
"<div class='aroundCourse'>" +
@@ -585,8 +692,7 @@
"<div id = 'div_Time_" + id + "'class='courseFont'></div></div></div></div>";
var obj = $(target).append(text);
addRoom(id, name);
- return obj
-
+ return obj;
}
/**
@@ -634,10 +740,11 @@
/**
* querys the Calendar data
- * @param ids ID'S of rooms to query as string, for e.g.: "5,17,8" or "5"
+ * Calender is queried with the {{uuid}} of the panel.
+ * api.inc.php / page.inc.php is getting the ids with the panel uuid.
*/
- function queryCalendars(ids) {
- var url = "../../../api.php?do=locationinfo&action=calendar&id=" + ids;
+ function queryCalendars() {
+ var url = "{{dirprefix}}api.php?do=locationinfo&get=calendar&uuid={{uuid}}";
// Todo reimplement Frontend methode if needed
/*
@@ -652,46 +759,12 @@
timeout: 30000,
success: function (result) {
UpdateTimeTables(result);
-
-
}, error: function () {
}
});
}
-
- /**
- * used for countdown
- * computes the time difference between 2 Date objects
- * @param a Date Object
- * @param b Date Object
- * @returns time string
- */
- function GetTimeDiferenceAsString(a, b) {
- if (a == null || b == null) {
- return "";
- }
- var milliseconds = a.getTime() - b.getTime();
- var seconds = Math.floor((milliseconds / 1000) % 60);
- milliseconds -= seconds * 1000;
- var minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
- milliseconds -= minutes * 1000 * 60;
- var hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
-
- var days = Math.floor((milliseconds / (1000 * 60 * 60 * 24)) % 31);
- if (seconds < 10) {
- seconds = "0" + seconds;
- }
- if (minutes < 10) {
- minutes = "0" + minutes;
- }
- if (days != 0) {
- // dont show?
- return "";
- }
- return hours + ":" + minutes + ":" + seconds;
- }
</script>
</head>
<body>
diff --git a/modules-available/locationinfo/templates/page-config-panel-summary.html b/modules-available/locationinfo/templates/page-config-panel-summary.html
new file mode 100644
index 00000000..2a968fc2
--- /dev/null
+++ b/modules-available/locationinfo/templates/page-config-panel-summary.html
@@ -0,0 +1,208 @@
+<h2>
+ {{#new}}{{lang_createPanel}}{{/new}}
+ {{^new}}{{lang_editPanel}}{{/new}}
+</h2>
+
+<p>{{lang_editSummaryPanelHints}}</p>
+
+<form method="post" action="?do=locationinfo" id="config-form">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="writePanelConfig">
+ <input type="hidden" name="ptype" value="SUMMARY">
+ <input type="hidden" name="uuid" value="{{uuid}}">
+
+ <div class="row">
+
+ <div class="col-md-6">
+ <div class="modify-inputs panel panel-default">
+ <div class="panel-heading">{{lang_display}}</div>
+ <div class="panel-body">
+ <div class="list-group">
+
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-sm-3">
+ <label for="panel-title">{{lang_displayName}}</label>
+ </div>
+ <div class="col-sm-7">
+ <input class="form-control" name="name" id="panel-title" type="text" value="{{panelname}}">
+ </div>
+ <div class="col-sm-2">
+ <a class="btn btn-default helptext" title="{{lang_displayNameTooltip}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-sm-3">
+ <label for="language">{{lang_language}}</label>
+ </div>
+ <div class="col-sm-7">
+ <select class="form-control" name="language" id="language">
+ {{#languages}}
+ <option value="{{cc}}" id="lang-{{cc}}" {{selected}}>{{name}}</option>
+ {{/languages}}
+ </select>
+ </div>
+ <div class="col-sm-2">
+ <a class="btn btn-default helptext" title="{{lang_languageTooltip}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-sm-3">
+ <label for="input-eco">{{lang_ecoMode}}</label>
+ </div>
+ <div class="col-sm-7">
+ <input id="input-eco" type="checkbox" name="eco" {{eco_checked}}>
+ </div>
+ <div class="col-sm-2">
+ <a class="btn btn-default helptext" title="{{lang_ecoTooltip}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+<!--
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-sm-3">
+ <label for="input-prettytime">{{lang_prettytime}}</label>
+ </div>
+ <div class="col-sm-7">
+ <input id="input-prettytime" type="checkbox" name="prettytime" {{prettytime_checked}}>
+ </div>
+ <div class="col-sm-2">
+ <a class="btn btn-default helptext" title="{{lang_prettytimeTooltip}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+-->
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="modify-inputs">
+ <div class="row">
+
+ <div class="col-md-6">
+ <div class="panel panel-default">
+ <div class="panel-heading">{{lang_locations}}</div>
+ <div class="panel-body">
+ <input type="hidden" name="locationids" value="{{locationids}}" id="locationids">
+ <p>{{lang_fourLocsHint}}</p>
+ <ul id="selected-locations" class="list-unstyled">
+
+ </ul>
+ <div class="dropdown pull-right">
+ <button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown">
+ <span class="glyphicon glyphicon-plus"></span>
+ </button>
+ <ul class="dropdown-menu" id="location-list">
+ {{#locations}}
+ <li><a href="#" data-lid="{{locationid}}">{{locationpad}} <span class="name">{{locationname}}</span></a></li>
+ {{/locations}}
+ </ul>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <a href="?do=locationinfo&amp;show=panels" class="btn btn-default">{{lang_cancel}}</a>
+</form>
+
+<div class="modal fade" id="no-locations-message" tabindex="-1" role="dialog">
+ <div class="modal-dialog"> <!--style="min-width:600px;width:70%"-->
+
+ <div class="modal-content">
+ <div class="modal-header">{{lang_error}}</div>
+ <div class="modal-body">
+ {{lang_noLocationsWarning}}
+ </div>
+ <div class="modal-footer">
+ <a class="btn btn-primary pull-right" data-dismiss="modal">{{lang_close}}</a>
+ <div class="clearfix"></div>
+ </div>
+ </div>
+
+ </div>
+</div>
+
+<script type="text/javascript"><!--
+
+document.addEventListener("DOMContentLoaded", function () {
+ var $selLocs = $('#selected-locations');
+ var $locList = $('#location-list');
+ var $locInput = $('#locationids');
+
+ // Initialize fancy tooltips
+ $('a.helptext').tooltip();
+ // Add listener to range sliders so their label can be updated
+ $('input[type="range"]').change(function () {
+ $(this).siblings().find('.range-display').text($(this).val());
+ });
+ // Set state of input controls that aren't statically initialized server side
+ $('.modify-inputs input[type="checkbox"]')
+ .bootstrapSwitch({size: 'small'});
+
+ var lids = $locInput.val().split(',');
+ $selLocs.empty();
+ for (var i = 0; i < lids.length; ++i) {
+ var $name = $locList.find('a[data-lid="' + lids[i] + '"] .name');
+ if ($name.length === 0) continue;
+ addLocation(lids[i], $name.text());
+ }
+
+ // Adding/removing locations
+ $locList.find('a').click(function(ev) {
+ ev.preventDefault();
+ var $this = $(this);
+ var name = $this.find('.name').text();
+ var id = $this.data('lid');
+ addLocation(id, name);
+ serializeLocs();
+ });
+
+ $('#config-form').submit(function(ev) {
+ if ($locInput.val().length > 0)
+ return;
+ ev.preventDefault();
+ $('#no-locations-message').modal('show');
+ });
+
+ function addLocation(id, name) {
+ $selLocs.find('li[data-lid="' + id + '"]').remove();
+ var delButton = $('<button class="btn btn-danger btn-xs" type="button">').append($('<span class="glyphicon glyphicon-remove">')).click(delParent);
+ $selLocs.append($('<li>').attr('data-lid', id).text(name).prepend(delButton));
+ }
+
+ function delParent() {
+ $(this).parent().remove();
+ serializeLocs();
+ }
+
+ function serializeLocs() {
+ var res = $selLocs.find('li[data-lid]').map( function() {
+ return $(this).data('lid');
+ }).get().join(',');
+ $locInput.val(res);
+ }
+
+});
+
+//--></script>
diff --git a/modules-available/locationinfo/templates/page-locations.html b/modules-available/locationinfo/templates/page-locations.html
index 3eafa7bf..de8dab7e 100644
--- a/modules-available/locationinfo/templates/page-locations.html
+++ b/modules-available/locationinfo/templates/page-locations.html
@@ -21,7 +21,7 @@
<span class="glyphicon glyphicon-edit"></span>
</a>
</td>
- <td>
+ <td {{#backendMissing}}class="text-danger"{{/backendMissing}}>
{{backend}}
</td>
<td>
diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php
index 1dae4deb..0576e660 100644
--- a/modules-available/locations/inc/location.inc.php
+++ b/modules-available/locations/inc/location.inc.php
@@ -37,6 +37,11 @@ class Location
return Database::queryFirst("SELECT * FROM location WHERE locationid = :locationId", compact('locationId'));
}
+ /**
+ * Get name of location
+ * @param int $locationId id of location to get name for
+ * @return string|false Name of location, false if locationId doesn't exist
+ */
public static function getName($locationId)
{
self::getLocationsAssoc();
@@ -46,6 +51,26 @@ class Location
return self::$assocLocationCache[$locationId]['locationname'];
}
+ /**
+ * Get all the names of the given location and its parents, up
+ * to the root element. Array keys will be locationids, value the names.
+ * @param int $locationId
+ * @return array|false locations, from furthest to nearest or false if locationId doesn't exist
+ */
+ public static function getNameChain($locationId)
+ {
+ self::getLocationsAssoc();
+ settype($locationId, 'int');
+ if (!isset(self::$assocLocationCache[$locationId]))
+ return false;
+ $ret = array();
+ while (isset(self::$assocLocationCache[$locationId])) {
+ $ret[$locationId] = self::$assocLocationCache[$locationId]['locationname'];
+ $locationId = self::$assocLocationCache[$locationId]['parentlocationid'];
+ }
+ return array_reverse($ret, true);
+ }
+
public static function getLocationsAssoc()
{
if (self::$assocLocationCache === false) {
@@ -255,16 +280,25 @@ class Location
* Ignores any manually assigned locationid (fixedlocationid).
*
* @param string $ip IP address of client
+ * @param bool $honorRoomPlanner consider a fixed location assigned manually by roomplanner
* @return bool|int locationid, or false if no match
*/
- public static function getFromIp($ip)
+ public static function getFromIp($ip, $honorRoomPlanner = false)
{
if (Module::get('statistics') !== false) {
// Shortcut - try to use subnetlocationid in machine table
- $ret = Database::queryFirst("SELECT subnetlocationid FROM machine WHERE clientip = :ip", compact('ip'));
+ if ($honorRoomPlanner) {
+ $ret = Database::queryFirst("SELECT locationid AS loc FROM machine
+ WHERE clientip = :ip
+ ORDER BY lastseen DESC LIMIT 1", compact('ip'));
+ } else {
+ $ret = Database::queryFirst("SELECT subnetlocationid AS loc FROM machine
+ WHERE clientip = :ip
+ ORDER BY lastseen DESC LIMIT 1", compact('ip'));
+ }
if ($ret !== false) {
- if ($ret['subnetlocationid'] > 0) {
- return (int)$ret['subnetlocationid'];
+ if ($ret['loc'] > 0) {
+ return (int)$ret['loc'];
}
return false;
}
diff --git a/modules-available/locations/lang/de/template-tags.json b/modules-available/locations/lang/de/template-tags.json
index 29a19b85..04d10d06 100644
--- a/modules-available/locations/lang/de/template-tags.json
+++ b/modules-available/locations/lang/de/template-tags.json
@@ -6,6 +6,7 @@
"lang_deleteChildLocations": "Untergeordnete Orte ebenfalls l\u00f6schen",
"lang_deleteLocation": "Ort l\u00f6schen",
"lang_deleteSubnet": "Bereich l\u00f6schen",
+ "lang_deleteSubnetWarning": "Alle zum L\u00f6schen markierten Subnetze werden gelöscht. Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
"lang_editConfigVariables": "Konfig.-Variablen",
"lang_editRoomplan": "Raumplan bearbeiten",
"lang_endAddress": "Endadresse",
@@ -28,7 +29,7 @@
"lang_startAddress": "Startadresse",
"lang_subnet": "IP-Bereich",
"lang_sysConfig": "Lokalisierung",
- "lang_thisListByLocation": "Zur Ortsansicht",
- "lang_thisListBySubnet": "Nach Subnetzen auflisten",
+ "lang_thisListByLocation": "Orte",
+ "lang_thisListBySubnet": "Subnetze",
"lang_unassignedMachines": "Rechner, die in keinen definierten Ort fallen"
} \ No newline at end of file
diff --git a/modules-available/locations/lang/en/template-tags.json b/modules-available/locations/lang/en/template-tags.json
index 6094ebce..ddb90f83 100644
--- a/modules-available/locations/lang/en/template-tags.json
+++ b/modules-available/locations/lang/en/template-tags.json
@@ -6,11 +6,12 @@
"lang_deleteChildLocations": "Delete child locations aswell",
"lang_deleteLocation": "Delete location",
"lang_deleteSubnet": "Delete range",
+ "lang_deleteSubnetWarning": "All subnets marked for deletion will be deleted. This cannot be undone!",
"lang_editConfigVariables": "Config vars",
- "lang_editRoomplan": "edit roomplan",
+ "lang_editRoomplan": "Edit roomplan",
"lang_endAddress": "End address",
"lang_listOfSubnets": "List of subnets",
- "lang_location": "Ort",
+ "lang_location": "Location",
"lang_locationInfo": "Location details",
"lang_locationName": "Name",
"lang_locationOtherOverlap": "Warning! These locations have overlapping address ranges",
@@ -28,7 +29,7 @@
"lang_startAddress": "Start address",
"lang_subnet": "IP range",
"lang_sysConfig": "Localization\/Integration",
- "lang_thisListByLocation": "List by location",
- "lang_thisListBySubnet": "List by subnet",
+ "lang_thisListByLocation": "Locations",
+ "lang_thisListBySubnet": "Subnets",
"lang_unassignedMachines": "Machines not matching any location"
-} \ No newline at end of file
+}
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
index 20af7b63..0cfa5b90 100644
--- a/modules-available/locations/page.inc.php
+++ b/modules-available/locations/page.inc.php
@@ -12,7 +12,7 @@ class Page_Locations extends Page
protected function doPreprocess()
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
@@ -47,6 +47,12 @@ class Page_Locations extends Page
Message::addError('main.value-invalid', 'locationid', $loc);
continue;
}
+
+ $oldLoc = Database::queryFirst("SELECT locationid FROM subnet WHERE subnetid = :subnetid", array("subnetid" => $subnetid))["locationid"];
+ if (($loc == $oldLoc && !User::hasPermission("subnet.edit", $loc)) ||
+ ($loc != $oldLoc && (!User::hasPermission("subnet.delete", $oldLoc) || !User::hasPermission("subnet.add", $loc))))
+ continue;
+
$range = $this->rangeToLongVerbose($start, $end);
if ($range === false)
continue;
@@ -57,13 +63,13 @@ class Page_Locations extends Page
}
AutoLocation::rebuildAll();
Message::addSuccess('subnets-updated', $count);
- Util::redirect('?do=Locations');
+ Util::redirect('?do=Locations&action=showsubnets');
}
private function addLocations()
{
$names = Request::post('newlocation', false);
- $parents = Request::post('newparent', false);
+ $parents = Request::post('newparent', []);
if (!is_array($names) || !is_array($parents)) {
Message::addError('main.empty-field');
Util::redirect('?do=Locations');
@@ -75,6 +81,8 @@ class Page_Locations extends Page
if (empty($name))
continue;
$parent = isset($parents[$idx]) ? (int)$parents[$idx] : 0;
+ if (!User::hasPermission("location.add", $parent))
+ continue;
if ($parent !== 0) {
$ok = false;
foreach ($locs as $loc) {
@@ -115,15 +123,25 @@ class Page_Locations extends Page
$change = false;
// Delete location?
if ($locationId === $del) {
+ if (!User::hasPermission("location.delete", $locationId)) {
+ Message::addError('main.no-permission', 'locationid', $locationId);
+ Util::redirect('?do=Locations');
+ }
$this->deleteLocation($location);
$change = true;
}
// Update subnets
$change |= $this->updateLocationSubnets();
- // Insert subnets
- $change |= $this->addNewLocationSubnets($location);
- // Update location!
- $change |= $this->updateLocationData($location);
+
+ if (User::hasPermission("subnet.add", $locationId)) {
+ // Insert subnets
+ $change |= $this->addNewLocationSubnets($location);
+ }
+ if (User::hasPermission("location.edit", $locationId)) {
+ // Update location!
+ $change |= $this->updateLocationData($location);
+ }
+
if ($change) {
// In case subnets or tree layout changed, recalc this
AutoLocation::rebuildAll();
@@ -180,6 +198,7 @@ class Page_Locations extends Page
}
}
}
+ // TODO: Check permissions for new parent (only if changed)
$ret = Database::exec('UPDATE location SET parentlocationid = :parent, locationname = :name'
. ' WHERE locationid = :lid', array(
'lid' => $locationId,
@@ -195,9 +214,12 @@ class Page_Locations extends Page
private function updateLocationSubnets()
{
$change = false;
+
+ $locationId = Request::post('locationid', false, 'integer');
+
// Deletion first
$dels = Request::post('deletesubnet', false);
- if (is_array($dels)) {
+ if (is_array($dels) && User::hasPermission("subnet.delete", $locationId)) {
$count = 0;
$stmt = Database::prepare('DELETE FROM subnet WHERE subnetid = :id');
foreach ($dels as $key => $value) {
@@ -212,6 +234,9 @@ class Page_Locations extends Page
$change = true;
}
}
+ if (!User::hasPermission("subnet.edit", $locationId))
+ return $change;
+
// Now actual updates
$starts = Request::post('startaddr', false);
$ends = Request::post('endaddr', false);
@@ -291,15 +316,28 @@ class Page_Locations extends Page
Util::redirect('?do=Locations&action=showlocations');
}
if ($getAction === 'showsubnets') {
- $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr, locationid FROM subnet");
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr, locationid FROM subnet
+ WHERE locationid IN (:locations) ORDER BY startaddr ASC",
+ array("locations" => User::getAllowedLocations("location.view")));
+ $allowedLocs = User::getAllowedLocations("subnet.add");
$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']);
+
+ foreach ($row['locations'] as &$loc) {
+ if (!(in_array($loc["locationid"], $allowedLocs) || $loc["locationid"] == $row['locationid'])) {
+ $loc["disabled"] = "disabled";
+ }
+ }
+
+ $row['editThisSubnetAllowed'] = User::hasPermission("subnet.edit", $row['locationid']);
+ $row['deleteThisSubnetAllowed'] = User::hasPermission("subnet.delete", $row['locationid']);
$rows[] = $row;
}
- Render::addTemplate('subnets', array('list' => $rows));
+
+ Render::addTemplate('subnets', array('list' => $rows, 'editSubnetAllowed' => User::hasPermission("subnet.edit")));
} elseif ($getAction === 'showlocations') {
$this->showLocationList();
}
@@ -339,6 +377,7 @@ class Page_Locations extends Page
unset($loc);
foreach ($locs as $loc) {
foreach ($loc['parents'] as $pid) {
+ $locs[(int)$pid]['hasChild'] = true;
$locs[(int)$pid]['clientCountSum'] += $loc['clientCount'];
}
}
@@ -388,6 +427,33 @@ class Page_Locations extends Page
}
}
}
+
+ $allowedLocs = User::getAllowedLocations("location.view");
+ $withParents = array();
+ foreach ($allowedLocs as $loc) {
+ $withParents = array_merge($withParents, Location::getLocationRootChain($loc));
+ }
+
+ foreach ($locs as $key => $loc) {
+ if (!in_array($loc["locationid"], $withParents)) {
+ unset($locs[$key]);
+ } elseif (!in_array($loc["locationid"], $allowedLocs)) {
+ $id = $locs[$key]["locationid"];
+ $name = $locs[$key]["locationname"];
+ $depth = $locs[$key]["depth"];
+ $locs[$key] = array("locationid" => $id, "locationname" => $name, "depth" => $depth, "linkClass" => "not-allowed");
+ }
+ }
+
+ $addAllowedLocs = User::getAllowedLocations("location.add");
+ $addAllowedLocs[] = 0;
+ $addAllowedList = Location::getLocations(0, 0, true);
+ foreach ($addAllowedList as &$loc) {
+ if (!in_array($loc["locationid"], $addAllowedLocs)) {
+ $loc["disabled"] = "disabled";
+ }
+ }
+
// Output
Render::addTemplate('locations', array(
'list' => array_values($locs),
@@ -400,6 +466,8 @@ class Page_Locations extends Page
'haveOverlapOther' => !empty($overlapOther),
'unassignedCount' => $unassigned,
'defaultConfig' => $defaultConfig,
+ 'addAllowed' => User::hasPermission("location.add"),
+ 'addAllowedList' => array_values($addAllowedList)
));
}
@@ -422,6 +490,11 @@ class Page_Locations extends Page
private function ajaxShowLocation()
{
$locationId = Request::any('locationid', 0, 'integer');
+
+ if (!User::hasPermission("location.view", $locationId)) {
+ die('Permission denied');
+ }
+
$loc = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location WHERE locationid = :lid',
array('lid' => $locationId));
if ($loc === false) {
@@ -442,6 +515,15 @@ class Page_Locations extends Page
'roomplanner' => Module::get('roomplanner') !== false && Location::isLeaf($locationId),
'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
);
+
+ $allowedLocs = User::getAllowedLocations("location.edit");
+ $allowedLocs[] = 0;
+ foreach ($data['parents'] as &$parent) {
+ if (!(in_array($parent["locationid"], $allowedLocs) || $parent["locationid"] == $loc['parentlocationid'])) {
+ $parent["disabled"] = "disabled";
+ }
+ }
+
if (Module::get('dozmod') !== false) {
$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)',
@@ -474,6 +556,12 @@ class Page_Locations extends Page
$data['havebaseconfig'] = Module::get('baseconfig') !== false;
$data['havesysconfig'] = Module::get('sysconfig') !== false;
+ $data['editAllowed'] = User::hasPermission("location.edit", $locationId);
+ $data['deleteAllowed'] = User::hasPermission("location.delete", $locationId);
+ $data['editSubnetAllowed'] = User::hasPermission("subnet.edit", $locationId);
+ $data['deleteSubnetAllowed'] = User::hasPermission("subnet.delete", $locationId);
+ $data['addSubnetAllowed'] = User::hasPermission("subnet.add", $locationId);
+ $data['saveButton'] = $data['editAllowed'] || $data['editSubnetAllowed'] || $data['deleteSubnetAllowed'] || $data['addSubnetAllowed'];
// echo '<pre>';
// var_dump($data);
diff --git a/modules-available/locations/permissions/permissions.json b/modules-available/locations/permissions/permissions.json
new file mode 100644
index 00000000..db0ac5f6
--- /dev/null
+++ b/modules-available/locations/permissions/permissions.json
@@ -0,0 +1,9 @@
+{
+ "location.view": "View locations.",
+ "location.edit": "Edit locations.",
+ "location.add": "Add locations.",
+ "location.delete": "Delete locations.",
+ "subnet.edit": "Edit subnets.",
+ "subnet.add": "Add subnets.",
+ "subnet.delete": "Delete subnets."
+} \ No newline at end of file
diff --git a/modules-available/locations/style.css b/modules-available/locations/style.css
index 86f9dfca..042ac4d1 100644
--- a/modules-available/locations/style.css
+++ b/modules-available/locations/style.css
@@ -1,3 +1,14 @@
table.locations tbody td:nth-of-type(even) {
background-color: rgba(0, 0, 0, 0.025);
-} \ No newline at end of file
+}
+
+.not-allowed {
+ pointer-events: none;
+ cursor: default;
+ color: inherit;
+}
+
+.disabled {
+ pointer-events: none;
+ opacity: 0.6;
+}
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
index b8a2b091..2cc8e98b 100644
--- a/modules-available/locations/templates/location-subnets.html
+++ b/modules-available/locations/templates/location-subnets.html
@@ -1,6 +1,6 @@
<div class="slx-well">
<div class="slx-bold">{{lang_locationSettings}}</div>
- <form method="post" action="?do=Locations">
+ <form id="locationForm{{locationid}}" method="post" action="?do=Locations">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="updatelocation">
<input type="hidden" name="locationid" value="{{locationid}}">
@@ -8,30 +8,25 @@
<button type="submit" class="btn btn-primary">Save</button>
</div>
<div class="row">
- <div class="col-sm-6">
- <div class="input-group">
- <span class="input-group-addon slx-ga2">{{lang_name}}</span>
- <input class="form-control" type="text" name="locationname" value="{{locationname}}" pattern=".*\S.*">
+ <div class="{{^editAllowed}}disabled{{/editAllowed}}">
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">{{lang_name}}</span>
+ <input class="form-control" type="text" name="locationname" value="{{locationname}}" pattern=".*\S.*">
+ </div>
</div>
- </div>
- <div class="col-sm-6">
- <div class="input-group">
- <span class="input-group-addon slx-ga2">{{lang_parentLocation}}</span>
- <select class="form-control" name="parentlocationid">
- {{#parents}}
- <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
- {{/parents}}
- </select>
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{lang_parentLocation}}</span>
+ <select class="form-control" name="parentlocationid">
+ {{#parents}}
+ <option {{disabled}} value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/parents}}
+ </select>
+ </div>
</div>
</div>
</div>
- <div>
- <div class="pull-right">
- <label><input type="checkbox" name="recursive" value="on"> {{lang_deleteChildLocations}}</label>
- <button type="submit" class="btn btn-sm btn-danger" name="deletelocation" value="{{locationid}}" onclick="return slxConfirm()">{{lang_deleteLocation}}</button>
- </div>
- <div class="clearfix"></div>
- </div>
<br>
<div class="slx-bold">{{lang_assignedSubnets}}</div>
<div><i>{{lang_assignSubnetExplanation}}</i></div>
@@ -40,47 +35,92 @@
<th>#</th>
<th>{{lang_startAddress}}</th>
<th>{{lang_endAddress}}</th>
- <th title="{{lang_deleteSubnet}}"><span class="glyphicon glyphicon-trash"></span></th>
+ <th title="{{lang_deleteSubnet}}" class="text-center"><span class="glyphicon glyphicon-trash"></span></th>
</tr>
{{#list}}
<tr class="cidrmagic">
<td>{{subnetid}}</td>
- <td><input class="form-control cidrstart" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
- <td><input class="form-control cidrend" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
- <td class="danger" align="center"><input type="checkbox" name="deletesubnet[{{subnetid}}]" value="on"></td>
+ <td class="{{^editSubnetAllowed}}disabled{{/editSubnetAllowed}}"><input class="form-control cidrstart" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td class="{{^editSubnetAllowed}}disabled{{/editSubnetAllowed}}"><input class="form-control cidrend" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td class="danger">
+ <div class="checkbox text-center" style="margin-left: 9px">
+ <input {{^deleteSubnetAllowed}}disabled{{/deleteSubnetAllowed}} type="checkbox" name="deletesubnet[{{subnetid}}]" value="on">
+ <label class="text-left"></label>
+ </div>
+ </td>
</tr>
{{/list}}
<tr id="loc-sub-{{locationid}}">
<td colspan="4">
- <button class="btn btn-success btn-sm" type="button" onclick="slxAddSubnetRow(this, {{locationid}})" title="{{lang_addNewSubnet}}">
- <span class="glyphicon glyphicon-plus-sign"></span> {{lang_subnet}}
+ <button {{^addSubnetAllowed}}disabled{{/addSubnetAllowed}} class="btn btn-success btn-sm pull-right" type="button" onclick="slxAddSubnetRow(this, {{locationid}})" title="{{lang_addNewSubnet}}">
+ <span class="glyphicon glyphicon-plus"></span> {{lang_subnet}}
</button>
</td>
</tr>
</table>
- <br>
- <div class="btn-group">
- {{#roomplanner}}
- <a class="btn btn-default" href="?do=roomplanner&amp;locationid={{locationid}}" target="_blank"
+ <div class="slx-bold">{{lang_locationInfo}}</div>
+
+ <div class="row">
+ <div class="col-md-4">
+ {{#haveDozmod}}
+ <div>
+ <span class="slx-ga2">{{lang_referencingLectures}}:</span> {{lectures}}
+ </div>
+ {{/haveDozmod}}
+ {{#haveStatistics}}
+ <div>
+ <span class="slx-ga2">{{lang_matchingMachines}}:</span> <a href="?do=Statistics&amp;show=list&amp;filters=location={{locationid}}">{{machines}} / {{machines_online}} / {{machines_used}} ({{used_percent}}%)</a>
+ </div>
+ {{/haveStatistics}}
+ </div>
+ <div class="col-md-4 text-center">
+ <div class="btn-group">
+ {{#roomplanner}}
+ <a class="btn btn-default" href="?do=roomplanner&amp;locationid={{locationid}}" target="_blank"
onclick="window.open(this.href, '_blank', 'toolbar=0,scrollbars,resizable');return false">
- <span class="glyphicon glyphicon-move"></span>{{lang_editRoomplan}}
- </a>
- {{/roomplanner}}
+ <span class="glyphicon glyphicon-move"></span> {{lang_editRoomplan}}
+ </a>
+ {{/roomplanner}}
+ </div>
+ </div>
+ <div class="col-md-4 text-right">
+ <button style="margin-right: 10px" {{^deleteAllowed}}disabled{{/deleteAllowed}} type="button" class="btn btn-danger" data-toggle="modal" data-target="#deleteLocationModal{{locationid}}"><span class="glyphicon glyphicon-trash"></span> {{lang_deleteLocation}}</button>
+ <button onclick="deleteSubnetWarning('{{locationid}}')" {{^saveButton}}disabled{{/saveButton}} type="button" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
</div>
- <div class="pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
- </div>
- <div class="slx-bold">{{lang_locationInfo}}</div>
- {{#haveDozmod}}
- <div>
- <span class="slx-ga2">{{lang_referencingLectures}}:</span> {{lectures}}
+
+ <div class="modal fade" id="deleteLocationModal{{locationid}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_areYouSureNoUndo}}
+ <div class="checkbox">
+ <input type="checkbox" name="recursive" value="on">
+ <label>{{lang_deleteChildLocations}}</label>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="deletelocation" value="{{locationid}}"><span class="glyphicon glyphicon-trash"></span> {{lang_deleteLocation}}</button>
+ </div>
+ </div>
+ </div>
</div>
- {{/haveDozmod}}
- {{#haveStatistics}}
- <div>
- <span class="slx-ga2">{{lang_matchingMachines}}:</span> <a href="?do=Statistics&amp;show=list&amp;filters=location={{locationid}}">{{machines}} / {{machines_online}} / {{machines_used}} ({{used_percent}}%)</a>
+
+ <div class="modal fade" id="saveWarningModal{{locationid}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_deleteSubnetWarning}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ </div>
+ </div>
+ </div>
</div>
- {{/haveStatistics}}
+
</form>
-</div>
+</div> \ No newline at end of file
diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html
index c2dc610e..be3d5115 100644
--- a/modules-available/locations/templates/locations.html
+++ b/modules-available/locations/templates/locations.html
@@ -1,8 +1,21 @@
<div>
- <div class="pull-right">
- <a href="?do=Locations&amp;action=showsubnets">{{lang_thisListBySubnet}}</a>
+ <div class="btn-group pull-right">
+ <a href="?do=Locations&amp;action=showlocations" class="btn btn-default active"><span class="glyphicon glyphicon-home"></span> {{lang_thisListByLocation}}</a>
+ <a href="?do=Locations&amp;action=showsubnets" class="btn btn-default"><span class="glyphicon glyphicon-list-alt"></span> {{lang_thisListBySubnet}}</a>
</div>
<h1>{{lang_locationsMainHeading}}</h1>
+
+ {{#overlapSelf}}
+ <div class="alert alert-warning">
+ {{lang_locationSelfOverlap}}: <b>{{locationname}}</b>
+ </div>
+ {{/overlapSelf}}
+ {{#overlapOther}}
+ <div class="alert alert-danger">
+ {{lang_locationOtherOverlap}}: <b>{{name1}}</b> – <b>{{name2}}</b>
+ </div>
+ {{/overlapOther}}
+
<table class="table table-condensed locations" style="margin-bottom:0px">
<tr>
<th width="100%">{{lang_locationName}}</th>
@@ -23,24 +36,29 @@
<tr>
<td>
<div style="display:inline-block;width:{{depth}}em"></div>
- <a href="#" onclick="slxOpenLocation(this, {{locationid}}); return false">{{locationname}} <b class="caret"></b></a>
+ <a href="#" class="{{linkClass}}" onclick="slxOpenLocation(this, {{locationid}}); return false">{{locationname}}{{^linkClass}} <b class="caret"></b>{{/linkClass}}</a>
</td>
<td class="text-nowrap" align="right">
+ {{^linkClass}}
{{#havestatistics}}
- {{clientCount}}
- <span class="text-muted">
- /
- <span style="display:inline-block;width:3ex">{{clientCountSum}}</span>
+ <a href="?do=Statistics&amp;show=list&amp;filters=location={{locationid}}">&nbsp;{{clientCount}}&nbsp;</a>
+ <span style="display:inline-block;width:5ex">
+ {{#hasChild}}
+ (<a href="?do=Statistics&amp;show=list&amp;filters=location~{{locationid}}">&downarrow;{{clientCountSum}}</a>)
+ {{/hasChild}}
</span>
- <a class="btn btn-default btn-xs" href="?do=Statistics&amp;show=list&amp;filters=location={{locationid}}"><span class="glyphicon glyphicon-eye-open"></span></a>
{{/havestatistics}}
+ {{/linkClass}}
</td>
<td class="text-nowrap" align="right">
+ {{^linkClass}}
{{#havestatistics}}
{{clientLoad}}
{{/havestatistics}}
+ {{/linkClass}}
</td>
<td class="text-nowrap">
+ {{^linkClass}}
{{#havebaseconfig}}
<div class="pull-right" style="z-index:-1">
<a class="btn btn-default btn-xs" href="?do=baseconfig&amp;module=locations&amp;locationid={{locationid}}"><span class="glyphicon glyphicon-edit"></span></a>
@@ -49,8 +67,10 @@
{{lang_overrideCount}}: {{overriddenVars}}&emsp;&emsp;
{{/overriddenVars}}
{{/havebaseconfig}}
+ {{/linkClass}}
</td>
<td class="text-nowrap">
+ {{^linkClass}}
{{#havesysconfig}}
<div class="pull-right">
<a class="btn btn-default btn-xs" href="?do=sysconfig&amp;locationid={{locationid}}"><span class="glyphicon glyphicon-edit"></span></a>
@@ -59,6 +79,7 @@
{{configName}}&emsp;&emsp;
</span>
{{/havesysconfig}}
+ {{/linkClass}}
</td>
</tr>
{{/list}}
@@ -66,9 +87,8 @@
<tr>
<td>{{lang_unassignedMachines}}</td>
<td class="text-nowrap" align="right">
- {{unassignedCount}}
- <a class="btn btn-default btn-xs" href="?do=Statistics&amp;show=list&amp;filters=location=0">
- <span class="glyphicon glyphicon-eye-open"></span>
+ <a href="?do=Statistics&amp;show=list&amp;filters=location=0">
+ &nbsp;{{unassignedCount}}&nbsp;
</a>
</td>
<td class="text-nowrap" align="right">
@@ -82,46 +102,48 @@
<form method="post" action="?do=Locations">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="addlocations">
- <table class="table table-condensed table-hover">
+ <table class="table table-condensed">
<tr id="lasttr">
- <td>
- <button class="btn btn-success btn-sm" type="button" onclick="slxAddLocationRow()">
- <span class="glyphicon glyphicon-plus-sign"></span> {{lang_location}}
+ <td width="60%">&emsp;</td>
+ <td class="text-right" colspan="2">
+ <button {{^addAllowed}}disabled{{/addAllowed}} class="btn btn-success" type="button" onclick="slxAddLocationRow()">
+ <span class="glyphicon glyphicon-plus"></span> {{lang_location}}
+ </button>
+ <button id="saveLocationRows" type="submit" class="btn btn-primary collapse">
+ <span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}
</button>
- </td>
- <td width="80%">&emsp;</td>
- <td width="20%" align="right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
</td>
</tr>
</table>
</form>
</div>
-{{#overlapSelf}}
-<div class="alert alert-warning">
- {{lang_locationSelfOverlap}}: <b>{{locationname}}</b>
-</div>
-{{/overlapSelf}}
-{{#overlapOther}}
-<div class="alert alert-danger">
- {{lang_locationOtherOverlap}}: <b>{{name1}}</b> – <b>{{name2}}</b>
-</div>
-{{/overlapOther}}
+
<script type="text/javascript"><!--
var slxAddCounter = 0;
var slxLastLocation = false;
+var newRowCounter = 0;
+
function slxAddLocationRow() {
+ $("#saveLocationRows").show();
var tr = $('#lasttr');
- tr.before('<tr>\
- <td>#</td>\
+ tr.before('<tr id="row' + slxAddCounter + '">\
<td><input class="form-control" type="text" name="newlocation[' + slxAddCounter + ']" placeholder="{{lang_locationName}}" pattern=".*\\S.*"></td>\
<td><select class="form-control" name="newparent[' + slxAddCounter + ']">\
- <option value="0">{{lang_noParent}}</option>\
- {{#list}}<option value="{{locationid}}">{{locationpad}} {{locationname}}</option>{{/list}}\
+ {{#addAllowedList}}<option {{disabled}} value="{{locationid}}">{{locationpad}} {{locationname}}</option>{{/addAllowedList}}\
</select></td>\
+ <td class="text-center"><button class="btn btn-default btn-sm" type="button" onclick="removeNewLocationRow(' + slxAddCounter + ')"><span class="glyphicon glyphicon-remove"></span></button></td>\
</tr>');
slxAddCounter++;
+ newRowCounter++;
+}
+
+function removeNewLocationRow(r) {
+ $("#row"+r).remove();
+ newRowCounter--;
+ if (newRowCounter === 0) {
+ $("#saveLocationRows").hide();
+ }
}
function slxOpenLocation(e, lid) {
@@ -134,9 +156,10 @@ function slxOpenLocation(e, lid) {
if (existing.is(slxLastLocation)) {
slxLastLocation = false;
} else {
- existing.show()[0].scrollIntoView();
+ existing.show();
$(e).closest('tr').addClass('active slx-bold');
slxLastLocation = existing;
+ scollIntoView(existing);
}
return;
}
@@ -146,25 +169,51 @@ function slxOpenLocation(e, lid) {
$(e).closest('tr').addClass('active slx-bold').after(tr);
td.load('?do=Locations&action=showlocation&locationid=' + lid, function() {
slxAttachCidr();
- $('#location-details-' + lid)[0].scrollIntoView();
+ scollIntoView(tr);
});
slxLastLocation = tr;
}
+function scollIntoView(el) {
+ var offset = $(el).offset();
+ var win = $(window);
+ var h = $(el).height();
+ if (offset.top + h > win.scrollTop() + win.height()) {
+ offset.top -= win.height();
+ offset.top += h;
+ $('html, body').animate({
+ scrollTop: offset.top
+ });
+ }
+}
+
function slxAddSubnetRow(e, lid) {
var tr = $('#loc-sub-' + lid);
- tr.before('<tr class="cidrmagic">\
+ tr.before('<tr id="row' + slxAddCounter + '" class="cidrmagic">\
<td>#</td>\
<td><input class="form-control cidrstart" type="text" name="newstartaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
<td><input class="form-control cidrend" type="text" name="newendaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
- <td></td>\
+ <td class="text-center"><button class="btn btn-default btn-sm" type="button" onclick="removeNewSubnetRow(' + slxAddCounter + ')"><span class="glyphicon glyphicon-remove"></span></button></td>\
</tr>');
slxAddCounter++;
slxAttachCidr();
}
+function removeNewSubnetRow(r) {
+ $("#row"+r).remove();
+}
+
function slxConfirm() {
return confirm('{{lang_areYouSureNoUndo}}');
}
+
+function deleteSubnetWarning(locid) {
+ var form = $("#locationForm"+locid);
+ if (form.find("input[type=checkbox]:checked").length > 0) {
+ $("#saveWarningModal"+locid).modal();
+ } else {
+ form.submit();
+ }
+}
// -->
</script>
diff --git a/modules-available/locations/templates/subnets.html b/modules-available/locations/templates/subnets.html
index 15fa28f2..cb7fb758 100644
--- a/modules-available/locations/templates/subnets.html
+++ b/modules-available/locations/templates/subnets.html
@@ -1,6 +1,7 @@
<div>
- <div class="pull-right">
- <a href="?do=Locations&amp;action=showlocations">{{lang_thisListByLocation}}</a>
+ <div class="btn-group pull-right">
+ <a href="?do=Locations&amp;action=showlocations" class="btn btn-default"><span class="glyphicon glyphicon-home"></span> {{lang_thisListByLocation}}</a>
+ <a href="?do=Locations&amp;action=showsubnets" class="btn btn-default active"><span class="glyphicon glyphicon-list-alt"></span> {{lang_thisListBySubnet}}</a>
</div>
<h1>{{lang_listOfSubnets}}</h1>
<form method="post" action="?do=Locations">
@@ -16,20 +17,20 @@
{{#list}}
<tr class="cidrmagic">
<td>{{subnetid}}</td>
- <td><input class="form-control cidrstart" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}"></td>
- <td><input class="form-control cidrend" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}"></td>
- <td>
+ <td class="{{^editThisSubnetAllowed}}disabled{{/editThisSubnetAllowed}}"><input class="form-control cidrstart" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}"></td>
+ <td class="{{^editThisSubnetAllowed}}disabled{{/editThisSubnetAllowed}}"><input class="form-control cidrend" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}"></td>
+ <td class="{{^deleteThisSubnetAllowed}}disabled{{/deleteThisSubnetAllowed}}">
<select class="form-control" name="location[{{subnetid}}]">
{{#locations}}
- <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ <option {{disabled}} value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
{{/locations}}
</select>
</td>
</tr>
{{/list}}
</table>
- <div>
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <div class="text-right" style="margin-bottom: 20px">
+ <button {{^editSubnetAllowed}}disabled{{/editSubnetAllowed}} type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</form>
</div>
diff --git a/modules-available/main/lang/de/messages.json b/modules-available/main/lang/de/messages.json
index 274a97bd..b6c2a5b3 100644
--- a/modules-available/main/lang/de/messages.json
+++ b/modules-available/main/lang/de/messages.json
@@ -7,6 +7,8 @@
"module-missing-deps": "Modul {{0}} hat fehlende Abh\u00e4ngigkeiten",
"no-permission": "Keine ausreichenden Rechte, um auf diese Seite zuzugreifen",
"no-such-module": "Modul {{0}} existiert nicht oder ist nicht aktiv",
+ "parameter-empty": "Parameter {{0}} ist leer",
+ "parameter-missing": "Parameter {{0}} wurde nicht gesetzt",
"task-error": "Ausf\u00fchrung fehlgeschlagen: {{0}}",
"taskmanager-error": "Verbindung zum Taskmanager fehlgeschlagen",
"taskmanager-format": "Taskmanager hat ung\u00fcltige Daten zur\u00fcckgeliefert",
diff --git a/modules-available/main/lang/de/template-tags.json b/modules-available/main/lang/de/template-tags.json
index e4de5737..00e27502 100644
--- a/modules-available/main/lang/de/template-tags.json
+++ b/modules-available/main/lang/de/template-tags.json
@@ -1,5 +1,7 @@
{
+ "lang_browserTime": "Browser",
"lang_changePassword": "Passwort \u00e4ndern",
+ "lang_clockDriftWarn": "Die Uhrzeit des Satelliten-Servers weicht von der Uhrzeit des lokalen Systems\/Browsers ab. Bitte stellen Sie sicher, dass die Uhrzeit des Servers korrekt ist, da sonst zeitabh\u00e4ngige Einstellungen und Aufgaben evtl. nicht korrekt durchgef\u00fchrt werden.",
"lang_goTo": "Gehe zu",
"lang_intro": "Dies ist die bwLehrpool Konfigurationsoberfl\u00e4che.",
"lang_introGuest": "Dies ist das Administrations-Interface der lokalen bwLehrpool-Installation. Bitte authentifizieren Sie sich, um Einstellungen vorzunehmen.",
@@ -11,6 +13,7 @@
"lang_needsSetup": "Einrichtung unvollst\u00e4ndig",
"lang_noExistingAccount": "Es existiert noch kein Administrator-Zugang f\u00fcr diesen Satelliten-Server.",
"lang_register": "Registrieren",
+ "lang_serverTime": "Server",
"lang_toggleNavigation": "Navigation ein\/ausblenden",
"lang_translations": "\u00dcbersetzungen",
"lang_warning": "Warnung",
diff --git a/modules-available/main/lang/en/messages.json b/modules-available/main/lang/en/messages.json
index e7314685..2a9c9c0d 100644
--- a/modules-available/main/lang/en/messages.json
+++ b/modules-available/main/lang/en/messages.json
@@ -6,7 +6,9 @@
"invalid-action": "Invalid action '{{0}}'",
"module-missing-deps": "Module {{0}} has missing dependencies",
"no-permission": "No sufficient privileges to access this page",
- "no-such-module": "Modul {{0}} existiert nicht",
+ "no-such-module": "Module {{0}} doesn't exist",
+ "parameter-empty": "Parameter {{0}} is empty",
+ "parameter-missing": "Parameter {{0}} is missing",
"task-error": "Execution failed: {{0}}",
"taskmanager-error": "Failed to connect to the Task Manager",
"taskmanager-format": "Task Manager has returned invalid data",
diff --git a/modules-available/main/lang/en/template-tags.json b/modules-available/main/lang/en/template-tags.json
index 0798290c..fdcbce06 100644
--- a/modules-available/main/lang/en/template-tags.json
+++ b/modules-available/main/lang/en/template-tags.json
@@ -1,6 +1,8 @@
{
+ "lang_browserTime": "Browser",
"lang_changePassword": "Change password",
- "lang_goTo": "Gehe zu",
+ "lang_clockDriftWarn": "The local system's\/browser's time doesn't match the server's time. Please make sure the server's clock is running correctly, otherwise time sensitive settings or tasks might not work properly.",
+ "lang_goTo": "Go to",
"lang_intro": "This is the bwLehrpool configuration interface.",
"lang_introGuest": "This is the administration interface of the local bwLehrpool intallation. Please authenticate yourself to adjust settings.",
"lang_language": "Language",
@@ -11,6 +13,7 @@
"lang_needsSetup": "Setup incomplete",
"lang_noExistingAccount": "No account has been created yet. Sign up to become the administrator.",
"lang_register": "Register",
+ "lang_serverTime": "Server",
"lang_toggleNavigation": "toggle navigation",
"lang_translations": "Translations",
"lang_warning": "Warning",
diff --git a/modules-available/main/page.inc.php b/modules-available/main/page.inc.php
index 08e8b5a6..70296a59 100644
--- a/modules-available/main/page.inc.php
+++ b/modules-available/main/page.inc.php
@@ -19,7 +19,8 @@ class Page_Main extends Page
// Logged in here
Render::addTemplate('page-main', array(
- 'user' => User::getName()
+ 'user' => User::getName(),
+ 'now' => time(),
));
// Warnings
diff --git a/modules-available/main/templates/page-main.html b/modules-available/main/templates/page-main.html
index a0b2d3b0..1b7cc62d 100644
--- a/modules-available/main/templates/page-main.html
+++ b/modules-available/main/templates/page-main.html
@@ -1,5 +1,19 @@
<div class="jumbotron">
<h1>{{lang_welcome}}, {{user}}</h1>
<p>{{lang_intro}}</p>
-
</div>
+
+<script type="application/javascript"><!--
+document.addEventListener("DOMContentLoaded", function () {
+ if (Date.now && Math.abs(Date.now() / 1000 - {{now}}) > 300) {
+ $('#browser-time').text(new Date().toLocaleString());
+ $('#server-time').text(new Date({{now}} * 1000).toLocaleString());
+ $('#time-warner').show();
+ }
+});
+//--></script>
+
+<div class="alert alert-warning collapse" id="time-warner">
+ {{lang_clockDriftWarn}}<br>
+ <b>{{lang_browserTime}}</b>: <span id="browser-time"></span>, <b>{{lang_serverTime}}</b>: <span id="server-time"></span>
+</div> \ No newline at end of file
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
index ca94f4d0..ec3aee57 100644
--- a/modules-available/minilinux/templates/filelist.html
+++ b/modules-available/minilinux/templates/filelist.html
@@ -1,9 +1,6 @@
{{#systems}}
- <div class="panel panel-default">
- <div class="panel-heading">
- <h4>{{title}}</h4>
- </div>
- <div class="panel-body" id="download-{{id}}">
+ <h1>{{title}}</h1>
+ <div id="download-{{id}}">
<div class="input-group pull-right" style="max-width: 400px">
<span class="input-group-addon slx-ga">{{lang_desiredVersion}}</span>
<select id="versionbox" class="form-control" onchange="loadSystemList($('#versionbox').val())">
diff --git a/modules-available/news/config.json b/modules-available/news/config.json
index 706412d0..e076ea5c 100644
--- a/modules-available/news/config.json
+++ b/modules-available/news/config.json
@@ -1,3 +1,4 @@
{
- "category":"main.content"
+ "category":"main.content",
+ "dependencies": [ "js_stupidtable" ]
}
diff --git a/modules-available/news/install.inc.php b/modules-available/news/install.inc.php
index 620a4580..e5e52256 100644
--- a/modules-available/news/install.inc.php
+++ b/modules-available/news/install.inc.php
@@ -27,9 +27,11 @@ $res[] = tableCreate('vmchooser_pages', "
`content` text,
`type` varchar(10),
PRIMARY KEY (`newsid`),
- KEY `dateline` (`dateline`)
+ KEY `type` (`type`, `dateline`)
");
+Database::exec('ALTER TABLE vmchooser_pages DROP KEY `dateline`, ADD KEY `type` (`type`, `dateline`)');
+
// Create response for browser
if (in_array(UPDATE_DONE, $res)) {
diff --git a/modules-available/news/page.inc.php b/modules-available/news/page.inc.php
index ee377dc4..5ad79b0e 100644
--- a/modules-available/news/page.inc.php
+++ b/modules-available/news/page.inc.php
@@ -31,12 +31,10 @@ class Page_News extends Page
// load user, we will need it later
User::load();
-
- // only admins should be able to edit news
- if (!User::hasPermission('superadmin')) {
- Message::addError('main.no-permission');
- Util::redirect('?do=Main');
- }
+ if (!User::isLoggedIn()) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main');
+ }
// check which action we need to do
$action = Request::any('action', 'show');
@@ -66,26 +64,41 @@ class Page_News extends Page
$pageType = Request::post('news-type');
if ($pageType == 'news') {
- 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');
- $lastId = Database::lastInsertId();
- Util::redirect("?do=News&newsid=$lastId");
- }
+ if (User::hasPermission("news.save")) {
+ 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');
+ $lastId = Database::lastInsertId();
+ Util::redirect("?do=News&newsid=$lastId");
+ }
+ }
} elseif ($pageType == 'help') {
- if ($this->saveHelp()) {
- Message::addSuccess('help-save-success');
- $lastId = Database::lastInsertId();
- Util::redirect("?do=News&newsid=$lastId");
- }
+ if (User::hasPermission("help.save")) {
+ if ($this->saveHelp()) {
+ Message::addSuccess('help-save-success');
+ $lastId = Database::lastInsertId();
+ Util::redirect("?do=News&newsid=$lastId");
+ }
+ }
}
} elseif ($action === 'delete') {
// delete it
- $this->delNews(Request::post('newsid'));
- Util::redirect('?do=News&editHelp='.Request::any('editHelp'));
+ $pageType = Request::post('news-type');
+
+ if ($pageType == 'news') {
+ if(User::hasPermission("news.delete")) {
+ $this->delNews(Request::post('newsid'));
+ Util::redirect('?do=News&editHelp='.Request::any('editHelp'));
+ }
+ } elseif ($pageType == 'help') {
+ if(User::hasPermission("help.delete")) {
+ $this->delNews(Request::post('newsid'));
+ Util::redirect('?do=News&editHelp='.Request::any('editHelp'));
+ }
+ }
} else {
// unknown action, redirect user
Message::addError('invalid-action', $action);
@@ -134,6 +147,10 @@ class Page_News extends Page
'editHelp' => $this->editHelp,
'list' => $lines,
'listHelp' => $linesHelp,
+ 'allowedNewsSave' => User::hasPermission("news.save"),
+ 'allowedNewsDelete' => User::hasPermission("news.delete"),
+ 'allowedHelpSave' => User::hasPermission("help.save"),
+ 'allowedHelpDelete' => User::hasPermission("help.delete"),
'hasSummernote' => $this->hasSummernote, ));
}
/**
diff --git a/modules-available/news/permissions/permissions.json b/modules-available/news/permissions/permissions.json
new file mode 100644
index 00000000..90d07aef
--- /dev/null
+++ b/modules-available/news/permissions/permissions.json
@@ -0,0 +1,6 @@
+{
+ "news.save": "Save new news.",
+ "news.delete": "Delete old news.",
+ "help.save": "Save new help texts.",
+ "help.delete": "Delete old help texts"
+} \ No newline at end of file
diff --git a/modules-available/news/style.css b/modules-available/news/style.css
index c11567ec..fc4bccab 100644
--- a/modules-available/news/style.css
+++ b/modules-available/news/style.css
@@ -4,3 +4,7 @@
background: none;
border: none;
}
+
+.bottom-margin-50 {
+ margin-bottom: 50px;
+}
diff --git a/modules-available/news/templates/page-news.html b/modules-available/news/templates/page-news.html
index 192635ea..6293b62d 100644
--- a/modules-available/news/templates/page-news.html
+++ b/modules-available/news/templates/page-news.html
@@ -1,5 +1,5 @@
-
<h1>{{lang_vmChooser_title}}</h1>
+
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{^editHelp}}active{{/editHelp}}"><a href="#news" role="tab" data-toggle="tab">{{lang_editNews}}</a></li>
<li role="presentation" class="{{#editHelp}}active{{/editHelp}}" ><a href="#help" role="tab" data-toggle="tab">{{lang_editHelp}}</a></li>
@@ -17,9 +17,16 @@
<label for="news-content-id">{{lang_content}}</label>
<textarea name="news-content" id ="news-content-id" class="form-control summernote" rows="5" cols="30" placeholder="">{{latestContent}}</textarea>
</div>
- <p>{{lang_latestUpdate}}: {{latestDate}}</p>
- <button class="btn btn-primary btn-sm sn-btn" name="news-type" value="news" type="submit">{{lang_save}}</button>
- <input type="hidden" name="token" value="{{token}}">
+ <div class="row">
+ <div class="text-left col-md-6">
+ <p>{{lang_latestUpdate}}: {{latestDate}}</p>
+ </div>
+ <div class="text-right col-md-6">
+ <button {{^allowedNewsSave}}disabled{{/allowedNewsSave}} class="btn btn-primary sn-btn" name="news-type" value="news" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <input type="hidden" name="token" value="{{token}}">
+ </div>
+ </div>
+
</form>
<br/>
<div class="panel panel-default">
@@ -29,24 +36,28 @@
<div class="panel-body">
<div class="table-responsive">
<form method="post" action="?do=News&amp;action=delete">
- <table class="table table-stripped table-condensed">
+ <table class="table table-stripped table-condensed stupidtable">
<thead>
<tr>
- <th>{{lang_date}}</th>
- <th>{{lang_title}}</th>
- <th>{{lang_content}}</th>
- <th></th>
+ <th data-sort="int">{{lang_date}}</th>
+ <th data-sort="string">{{lang_title}}</th>
+ <th data-sort="string">{{lang_content}}</th>
+ <th class="text-center">{{lang_show}}</th>
+ <th class="text-center">{{lang_delete}}</th>
</tr>
</thead>
<tbody>
{{#list}}
<tr {{#active}}class="active"{{/active}}>
- <td class="text-left text-nowrap">{{date}}</td>
+ <td class="text-left text-nowrap" data-sort-value={{dateline}}>{{date}}</td>
<td><table class="slx-ellipsis"><tr><td>{{title}}</td></tr></table></td>
<td><table class="slx-ellipsis"><tr><td>{{content}}</td></tr></table></td>
- <td class="text-nowrap">
- <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span> {{lang_show}}</a>
- <button class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-remove"></span> {{lang_delete}}</button>
+ <td class="text-center">
+ <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span></a>
+ </td>
+ <td class="text-center">
+ <input type="hidden" name="news-type" value="news">
+ <button {{^allowedNewsDelete}}disabled{{/allowedNewsDelete}} class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-trash"></span></button>
</td>
</tr>
{{/list}}
@@ -61,12 +72,16 @@
<div role="tabpanel" class="tab-pane {{#editHelp}}active{{/editHelp}}" id="help">
<form action="?do=News&amp;action=save" method="post">
<div class="form-group">
+ <br/>
<label for="news-content-id">{{lang_content}}</label>
<textarea name="help-content" id="help-content-id" class="form-control summernote" style="min-height:400px" placeholder="">{{latestHelp}}</textarea>
</div>
- <button class="btn btn-primary btn-sm sn-btn" name="news-type" value="help" type="submit">{{lang_save}}</button>
- <input type="hidden" name="token" value="{{token}}">
+ <div class="text-right">
+ <button {{^allowedHelpSave}}disabled{{/allowedHelpSave}} class="btn btn-primary sn-btn" name="news-type" value="help" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <input type="hidden" name="token" value="{{token}}">
+ </div>
</form>
+ <br/>
<div class="panel panel-default">
<div class="panel-heading">
{{lang_oldHelp}}
@@ -74,22 +89,26 @@
<div class="panel-body">
<div class="table-responsive">
<form method="post" action="?do=News&amp;action=delete&editHelp=true">
- <table class="table table-stripped table-condensed">
+ <table class="table table-stripped table-condensed stupidtable">
<thead>
<tr>
- <th>{{lang_date}}</th>
- <th>{{lang_content}}</th>
- <th></th>
+ <th data-sort="int">{{lang_date}}</th>
+ <th data-sort="string">{{lang_content}}</th>
+ <th class="text-center">{{lang_show}}</th>
+ <th class="text-center">{{lang_delete}}</th>
</tr>
</thead>
<tbody>
{{#listHelp}}
<tr {{#active}}class="active"{{/active}}>
- <td class="text-left text-nowrap">{{date}}</td>
+ <td class="text-left text-nowrap" data-sort-value={{dateline}}>{{date}}</td>
<td><table class="slx-ellipsis"><tr><td>{{content}}</td></tr></table></td>
- <td class="text-nowrap">
- <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span> {{lang_show}}</a>
- <button class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-remove"></span> {{lang_delete}}</button>
+ <td class="text-center">
+ <a class="btn btn-primary btn-xs" href="?do=news&amp;newsid={{newsid}}&amp;action=show"><span class="glyphicon glyphicon-share-alt"></span></a>
+ </td>
+ <td class="text-center">
+ <input type="hidden" name="news-type" value="help">
+ <button {{^allowedHelpDelete}}disabled{{/allowedHelpDelete}} class="btn btn-danger btn-xs" type="submit" name="newsid" value="{{newsid}}"><span class="glyphicon glyphicon-trash"></span></button>
</td>
</tr>
{{/listHelp}}
diff --git a/modules-available/permissionmanager/api.inc.php b/modules-available/permissionmanager/api.inc.php
new file mode 100644
index 00000000..0d84ebce
--- /dev/null
+++ b/modules-available/permissionmanager/api.inc.php
@@ -0,0 +1,7 @@
+<?php
+
+echo json_encode(array(
+ 'key' => 'value',
+ 'number' => 123,
+ 'list' => array(1,2,3,4,5,6,'foo')
+));
diff --git a/modules-available/permissionmanager/clientscript.js b/modules-available/permissionmanager/clientscript.js
new file mode 100644
index 00000000..700ebc11
--- /dev/null
+++ b/modules-available/permissionmanager/clientscript.js
@@ -0,0 +1,48 @@
+document.addEventListener("DOMContentLoaded", function() {
+ var selectize = $('#select-role');
+ if (selectize.length) {
+ selectize = selectize.selectize({
+ allowEmptyOption: false,
+ maxItems: null,
+ highlight: false,
+ hideSelected: true,
+ create: false,
+ plugins: ["remove_button"]
+ })[0].selectize;
+
+ // If Site gets refreshed, all data-selectizeCounts will be reset to 0, so delete the filters from the selectize
+ selectize.clear();
+
+ selectize.on('item_add', function (value, $item) {
+ // When first item gets added the filter isn't empty anymore, so hide all rows
+ if (selectize.items.length === 1) {
+ $('.dataTable tbody').find('tr').hide();
+ }
+ // Find all rows which shall be shown and increase their counter by 1
+ $(".roleid-" + value).closest("tr").each(function () {
+ $(this).data("selectizeCount", $(this).data("selectizeCount") + 1);
+ $(this).show();
+ });
+ });
+
+ selectize.on('item_remove', function (value, $item) {
+ // When no items in the filter, show all rows again
+ if (selectize.items.length === 0) {
+ $('.dataTable tbody').find('tr').show();
+ } else {
+ // Find all rows which have the delete role, decrease their counter by 1
+ $(".roleid-" + value).closest("tr").each(function () {
+ $(this).data("selectizeCount", $(this).data("selectizeCount") - 1);
+ // If counter is 0, hide the row (no filter given to show the row anymore)
+ if ($(this).data("selectizeCount") === 0) {
+ $(this).closest("tr").hide();
+ }
+ });
+ }
+ });
+ }
+
+ $("form input").keydown(function(e) {
+ if (e.keyCode === 13) e.preventDefault();
+ });
+}); \ No newline at end of file
diff --git a/modules-available/permissionmanager/config.json b/modules-available/permissionmanager/config.json
new file mode 100644
index 00000000..c92e917a
--- /dev/null
+++ b/modules-available/permissionmanager/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.content",
+ "dependencies": [ "locations", "js_stupidtable", "bootstrap_switch", "js_selectize" ]
+}
diff --git a/modules-available/permissionmanager/inc/getpermissiondata.inc.php b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
new file mode 100644
index 00000000..13c7ca89
--- /dev/null
+++ b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
@@ -0,0 +1,90 @@
+<?php
+
+class GetPermissionData {
+
+ // get UserIDs, User Login Names, User Roles
+ public static function getUserData() {
+ $res = self::queryUserData();
+ $userdata= array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $userdata[$row['userid'].' '.$row['login']][] = array(
+ 'roleid' => $row['roleid'],
+ 'rolename' => $row['rolename']
+ );
+ }
+ $data = array();
+ foreach($userdata AS $user => $roles) {
+ $user = explode(" ", $user, 2);
+ $data[] = array(
+ 'userid' => $user[0],
+ 'username' => $user[1],
+ 'roles' => $roles
+ );
+ }
+ return $data;
+ }
+
+ // get LocationIDs, Location Names, Roles of each Location
+ public static function getLocationData() {
+ $res = Database::simpleQuery("SELECT role.roleid as roleid, rolename, GROUP_CONCAT(COALESCE(locationid, 0)) AS locationids FROM role
+ INNER JOIN role_x_location ON role.roleid = role_x_location.roleid GROUP BY roleid ORDER BY rolename ASC");
+ $locations = Location::getLocations(0, 0, false, true);
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $locationids = explode(",", $row['locationids']);
+ if (in_array("0", $locationids)) {
+ $locationids = array_map("intval", Location::extractIds(Location::getTree()));
+ } else {
+ $locationids = PermissionUtil::getSublocations(Location::getTree(), $locationids);
+ }
+ foreach ($locationids as $locationid) {
+ $locations[$locationid]['roles'][] = array(
+ 'roleid' => $row['roleid'],
+ 'rolename' => $row['rolename']
+ );
+ }
+ }
+ return array_values($locations);
+ }
+
+ // get all roles from database (id and name)
+ public static function getRoles() {
+ $res = Database::simpleQuery("SELECT roleid, rolename FROM role ORDER BY rolename ASC");
+ $data = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $data[] = array(
+ 'roleid' => $row['roleid'],
+ 'rolename' => $row['rolename']
+ );
+ }
+ return $data;
+ }
+
+ public static function getRoleData($roleid) {
+ $query = "SELECT roleid, rolename FROM role WHERE roleid = :roleid";
+ $data = Database::queryFirst($query, array("roleid" => $roleid));
+ $query = "SELECT roleid, locationid FROM role_x_location WHERE roleid = :roleid";
+ $res = Database::simpleQuery($query, array("roleid" => $roleid));
+ $data["locations"] = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $data["locations"][] = $row['locationid'];
+ }
+ $query = "SELECT roleid, permissionid FROM role_x_permission WHERE roleid = :roleid";
+ $res = Database::simpleQuery($query, array("roleid" => $roleid));
+ $data["permissions"] = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $data["permissions"][] = $row['permissionid'];
+ }
+ return $data;
+ }
+
+ // UserID, User Login Name, Roles of each User
+ private static function queryUserData() {
+ $res = Database::simpleQuery("SELECT user.userid AS userid, user.login AS login, role.rolename AS rolename, role.roleid AS roleid
+ FROM user
+ LEFT JOIN user_x_role ON user.userid = user_x_role.userid
+ LEFT JOIN role ON user_x_role.roleid = role.roleid
+ ");
+ return $res;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/inc/permissiondbupdate.inc.php b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
new file mode 100644
index 00000000..f144b35e
--- /dev/null
+++ b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
@@ -0,0 +1,53 @@
+<?php
+
+class PermissionDbUpdate {
+
+ // insert new user_x_role to database. "ignore" to ignore duplicate entry try
+ public static function addRoleToUser($users, $roles) {
+ $query = "INSERT IGNORE INTO user_x_role (userid, roleid) VALUES (:userid, :roleid)";
+ foreach($users AS $userid) {
+ foreach ($roles AS $roleid) {
+ Database::exec($query, array("userid" => $userid, "roleid" => $roleid));
+ }
+ }
+ }
+
+ // remove user_x_role entry from database
+ public static function removeRoleFromUser($users, $roles) {
+ $query = "DELETE FROM user_x_role WHERE userid IN (:users) AND roleid IN (:roles)";
+ Database::exec($query, array("users" => $users, "roles" => $roles));
+ }
+
+ // delete role, delete user_x_role relationships, delete role_x_location relationships, delete role_x_permission relationships
+ public static function deleteRole($roleid) {
+ $query = "DELETE FROM role WHERE roleid = :roleid";
+ Database::exec($query, array("roleid" => $roleid));
+ $query = "DELETE FROM user_x_role WHERE roleid = :roleid";
+ Database::exec($query, array("roleid" => $roleid));
+ $query = "DELETE FROM role_x_location WHERE roleid = :roleid";
+ Database::exec($query, array("roleid" => $roleid));
+ $query = "DELETE FROM role_x_permission WHERE roleid = :roleid";
+ Database::exec($query, array("roleid" => $roleid));
+ }
+
+ public static function saveRole($rolename, $locations, $permissions, $roleid = NULL) {
+ if ($roleid) {
+ Database::exec("UPDATE role SET rolename = :rolename WHERE roleid = :roleid",
+ array("rolename" => $rolename, "roleid" => $roleid));
+ Database::exec("DELETE FROM role_x_location WHERE roleid = :roleid", array("roleid" => $roleid));
+ Database::exec("DELETE FROM role_x_permission WHERE roleid = :roleid", array("roleid" => $roleid));
+ } else {
+ Database::exec("INSERT INTO role (rolename) VALUES (:rolename)", array("rolename" => $rolename));
+ $roleid = Database::lastInsertId();
+ }
+ foreach ($locations as $locationid) {
+ Database::exec("INSERT INTO role_x_location (roleid, locationid) VALUES (:roleid, :locationid)",
+ array("roleid" => $roleid, "locationid" => $locationid));
+ }
+ foreach ($permissions as $permissionid) {
+ Database::exec("INSERT INTO role_x_permission (roleid, permissionid) VALUES (:roleid, :permissionid)",
+ array("roleid" => $roleid, "permissionid" => $permissionid));
+ }
+ }
+
+}
diff --git a/modules-available/permissionmanager/inc/permissionutil.inc.php b/modules-available/permissionmanager/inc/permissionutil.inc.php
new file mode 100644
index 00000000..391cd047
--- /dev/null
+++ b/modules-available/permissionmanager/inc/permissionutil.inc.php
@@ -0,0 +1,108 @@
+<?php
+
+class PermissionUtil
+{
+ public static function userHasPermission($userid, $permissionid, $locationid) {
+ $locations = array();
+ if (!is_null($locationid)) {
+ $locations = Location::getLocationRootChain($locationid);
+ if (count($locations) == 0) return false;
+ else $locations[] = 0;
+ }
+
+ $res = Database::simpleQuery("SELECT role_x_permission.permissionid as 'permissionid',
+ role_x_location.locationid as 'locationid'
+ FROM user_x_role
+ INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid
+ LEFT JOIN role_x_location ON role_x_permission.roleid = role_x_location.roleid
+ WHERE user_x_role.userid = :userid", array("userid" => $userid));
+
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $userPermission = trim($row["permissionid"], "*");
+ if (substr($permissionid, 0, strlen($userPermission)) === $userPermission
+ && (is_null($locationid) || in_array($row["locationid"], $locations))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static function getAllowedLocations($userid, $permissionid) {
+
+ $res = Database::simpleQuery("SELECT permissionid, COALESCE(locationid, 0) AS locationid FROM user_x_role
+ INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid
+ INNER JOIN role_x_location ON role_x_permission.roleid = role_x_location.roleid
+ WHERE user_x_role.userid = :userid", array("userid" => $userid));
+
+ $allowedLocations = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $userPermission = trim($row["permissionid"], "*");
+ if (!is_null($row["locationid"]) && substr($permissionid, 0, strlen($userPermission)) === $userPermission) {
+ $allowedLocations[$row["locationid"]] = 1;
+ }
+ }
+ $allowedLocations = array_keys($allowedLocations);
+ $locations = Location::getTree();
+ if (in_array("0", $allowedLocations)) {
+ $allowedLocations = array_map("intval", Location::extractIds($locations));
+ } else {
+ $allowedLocations = self::getSublocations($locations, $allowedLocations);
+ }
+ return $allowedLocations;
+ }
+
+ public static function getSublocations($tree, $locations) {
+ $result = array_flip($locations);
+ foreach ($tree as $location) {
+ if (array_key_exists("children", $location)) {
+ if (in_array($location["locationid"], $locations)) {
+ $result += array_flip(Location::extractIds($location["children"]));
+ } else {
+ $result += array_flip(self::getSublocations($location["children"], $locations));
+ }
+ }
+ }
+ return array_keys($result);
+ }
+
+ public static function getPermissions()
+ {
+ $permissions = array();
+ foreach (glob("modules/*/permissions/permissions.json", GLOB_NOSORT) as $file) {
+ $data = json_decode(file_get_contents($file), true);
+ if (!is_array($data))
+ continue;
+ preg_match('#^modules/([^/]+)/#', $file, $out);
+ $newData = array();
+ foreach( $data as $k => $v ) {
+ $newData[] = $v;
+ $permissions = self::putInPermissionTree($out[1].".".$k, $v, $permissions);
+ }
+ }
+ ksort($permissions);
+ global $MENU_CAT_OVERRIDE;
+ $sortingOrder = $MENU_CAT_OVERRIDE;
+ foreach ($permissions as $module => $v) $sortingOrder[Module::get($module)->getCategory()][] = $module;
+ $permissions = array_replace(array_flip(call_user_func_array('array_merge', $sortingOrder)), $permissions);
+ foreach ($permissions as $module => $v) if (is_int($v)) unset($permissions[$module]);
+
+
+ return $permissions;
+ }
+
+ private static function putInPermissionTree($permission, $description, $tree)
+ {
+ $subPermissions = explode('.', $permission);
+ $original =& $tree;
+ foreach ($subPermissions as $subPermission) {
+ if ($subPermission) {
+ if (!array_key_exists($subPermission, $tree)) {
+ $tree[$subPermission] = array();
+ }
+ $tree =& $tree[$subPermission];
+ }
+ }
+ $tree = $description;
+ return $original;
+ }
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/install.inc.php b/modules-available/permissionmanager/install.inc.php
new file mode 100644
index 00000000..71ee7a1e
--- /dev/null
+++ b/modules-available/permissionmanager/install.inc.php
@@ -0,0 +1,86 @@
+<?php
+
+$res = array();
+
+$res[] = tableCreate('role', "
+ roleid int(10) unsigned NOT NULL AUTO_INCREMENT,
+ rolename varchar(200) NOT NULL,
+ PRIMARY KEY (roleid)
+");
+
+$res[] = tableCreate('user_x_role', "
+ userid int(10) unsigned NOT NULL,
+ roleid int(10) unsigned NOT NULL,
+ PRIMARY KEY (userid, roleid)
+");
+
+$res[] = tableCreate('role_x_location', "
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
+ roleid int(10) unsigned NOT NULL,
+ locationid int(11),
+ PRIMARY KEY (id)
+");
+
+$res[] = tableCreate('role_x_permission', "
+ roleid int(10) unsigned NOT NULL,
+ permissionid varchar(200) NOT NULL,
+ PRIMARY KEY (roleid, permissionid)
+");
+
+if (!tableExists('user') || !tableExists('location')) {
+ finalResponse(UPDATE_RETRY, 'Cannot add constraint yet. Please retry.');
+} else {
+ $c = tableGetContraints('user_x_role', 'userid', 'user', 'userid');
+ if ($c === false)
+ finalResponse(UPDATE_FAILED, 'Cannot get constraints of user table: ' . Database::lastError());
+ if (empty($c)) {
+ $alter = Database::exec('ALTER TABLE user_x_role ADD FOREIGN KEY (userid) REFERENCES user (userid) ON DELETE CASCADE ON UPDATE CASCADE');
+ if ($alter === false)
+ finalResponse(UPDATE_FAILED, 'Cannot add userid constraint referencing user table: ' . Database::lastError());
+ $res[] = UPDATE_DONE;
+ }
+
+ $c = tableGetContraints('user_x_role', 'roleid', 'role', 'roleid');
+ if ($c === false)
+ finalResponse(UPDATE_FAILED, 'Cannot get constraints of role table: ' . Database::lastError());
+ if (empty($c)) {
+ $alter = Database::exec('ALTER TABLE user_x_role ADD FOREIGN KEY (roleid) REFERENCES role (roleid) ON DELETE CASCADE ON UPDATE CASCADE');
+ if ($alter === false)
+ finalResponse(UPDATE_FAILED, 'Cannot add roleid constraint referencing role table: ' . Database::lastError());
+ $res[] = UPDATE_DONE;
+ }
+
+ $c = tableGetContraints('role_x_location', 'roleid', 'role', 'roleid');
+ if ($c === false)
+ finalResponse(UPDATE_FAILED, 'Cannot get constraints of role table: ' . Database::lastError());
+ if (empty($c)) {
+ $alter = Database::exec('ALTER TABLE role_x_location ADD FOREIGN KEY (roleid) REFERENCES role (roleid) ON DELETE CASCADE ON UPDATE CASCADE');
+ if ($alter === false)
+ finalResponse(UPDATE_FAILED, 'Cannot add roleid constraint referencing role table: ' . Database::lastError());
+ $res[] = UPDATE_DONE;
+ }
+
+ $c = tableGetContraints('role_x_location', 'locationid', 'location', 'locationid');
+ if ($c === false)
+ finalResponse(UPDATE_FAILED, 'Cannot get constraints of location table: ' . Database::lastError());
+ if (empty($c)) {
+ $alter = Database::exec('ALTER TABLE role_x_location ADD FOREIGN KEY (locationid) REFERENCES location (locationid) ON DELETE CASCADE ON UPDATE CASCADE');
+ if ($alter === false)
+ finalResponse(UPDATE_FAILED, 'Cannot add locationid constraint referencing location table: ' . Database::lastError());
+ $res[] = UPDATE_DONE;
+ }
+
+ $c = tableGetContraints('role_x_permission', 'roleid', 'role', 'roleid');
+ if ($c === false)
+ finalResponse(UPDATE_FAILED, 'Cannot get constraints of role table: ' . Database::lastError());
+ if (empty($c)) {
+ $alter = Database::exec('ALTER TABLE role_x_permission ADD FOREIGN KEY (roleid) REFERENCES role (roleid) ON DELETE CASCADE ON UPDATE CASCADE');
+ if ($alter === false)
+ finalResponse(UPDATE_FAILED, 'Cannot add roleid constraint referencing role table: ' . Database::lastError());
+ $res[] = UPDATE_DONE;
+ }
+}
+if (in_array(UPDATE_DONE, $res)) {
+ finalResponse(UPDATE_DONE, 'Tables created successfully');
+}
+finalResponse(UPDATE_NOOP, 'Everything already up to date');
diff --git a/modules-available/permissionmanager/lang/de/module.json b/modules-available/permissionmanager/lang/de/module.json
new file mode 100644
index 00000000..aa73da91
--- /dev/null
+++ b/modules-available/permissionmanager/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Rechtemanager",
+ "page_title": "Rechtemanager"
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/lang/de/template-tags.json b/modules-available/permissionmanager/lang/de/template-tags.json
new file mode 100644
index 00000000..23b2dc68
--- /dev/null
+++ b/modules-available/permissionmanager/lang/de/template-tags.json
@@ -0,0 +1,25 @@
+{
+ "lang_Roles": "Rollen",
+ "lang_Users": "Nutzer",
+ "lang_Locations": "Räume",
+ "lang_addRole": "Rolle zuweisen",
+ "lang_removeRole": "Rolle entfernen",
+ "lang_newRole": "Rolle anlegen",
+ "lang_Selected": "Ausgewählt",
+ "lang_Edit": "Bearbeiten",
+ "lang_Remove": "Entfernen",
+ "lang_Delete": "Löschen",
+ "lang_removeCheck": "Sind Sie sich sicher, dass Sie diese Rolle entfernen wollen?",
+ "lang_deleteCheck": "Sind Sie sich sicher, dass Sie diese Rolle löschen wollen?",
+ "lang_emptyNameWarning": "Der Name der Rolle darf nicht leer sein!",
+ "lang_Name": "Name",
+ "lang_Cancel": "Abbrechen",
+ "lang_Save": "Speichern",
+ "lang_all": "alle",
+ "lang_selected": "ausgewählte",
+ "lang_Permissions": "Rechte",
+ "lang_selectizePlaceholder": "Nach Rollen filtern...",
+ "lang_searchPlaceholder": "Nach Rollen suchen...",
+ "lang_moduleName": "Rechtemanager",
+ "lang_roleEditor": "Rollen Editor"
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/lang/en/module.json b/modules-available/permissionmanager/lang/en/module.json
new file mode 100644
index 00000000..5a5c838b
--- /dev/null
+++ b/modules-available/permissionmanager/lang/en/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Permission Manager",
+ "page_title": "Permission Manager"
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/lang/en/template-tags.json b/modules-available/permissionmanager/lang/en/template-tags.json
new file mode 100644
index 00000000..01056632
--- /dev/null
+++ b/modules-available/permissionmanager/lang/en/template-tags.json
@@ -0,0 +1,25 @@
+{
+ "lang_Roles": "Roles",
+ "lang_Users": "Users",
+ "lang_Locations": "Locations",
+ "lang_addRole": "Add Role",
+ "lang_removeRole": "Remove Role",
+ "lang_newRole": "New Role",
+ "lang_Selected": "Selected",
+ "lang_Edit": "Edit",
+ "lang_Remove": "Remove",
+ "lang_Delete": "Delete",
+ "lang_removeCheck": "Are you sure you want to remove this role?",
+ "lang_deleteCheck": "Are you sure you want to delete this role?",
+ "lang_emptyNameWarning": "Role name can not be empty!",
+ "lang_Name": "Name",
+ "lang_Cancel": "Cancel",
+ "lang_Save": "Save",
+ "lang_all": "all",
+ "lang_selected": "selected",
+ "lang_Permissions": "Permissions",
+ "lang_selectizePlaceholder": "Filter for roles...",
+ "lang_searchPlaceholder": "Search for roles...",
+ "lang_moduleName": "Permission Manager",
+ "lang_roleEditor": "Role Editor"
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/page.inc.php b/modules-available/permissionmanager/page.inc.php
new file mode 100644
index 00000000..9aba80b3
--- /dev/null
+++ b/modules-available/permissionmanager/page.inc.php
@@ -0,0 +1,193 @@
+<?php
+
+class Page_PermissionManager extends Page
+{
+
+ /**
+ * Called before any page rendering happens - early hook to check parameters etc.
+ */
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::isLoggedIn()) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main'); // does not return
+ }
+
+ $action = Request::any('action', 'show', 'string');
+ if ($action === 'addRoleToUser') {
+ $users = Request::post('users', '');
+ $roles = Request::post('roles', '');
+ PermissionDbUpdate::addRoleToUser($users, $roles);
+ } elseif ($action === 'removeRoleFromUser') {
+ $users = Request::post('users', '');
+ $roles = Request::post('roles', '');
+ PermissionDbUpdate::removeRoleFromUser($users, $roles);
+ } elseif ($action === 'deleteRole') {
+ $id = Request::post('deleteId', false, 'string');
+ PermissionDbUpdate::deleteRole($id);
+ } elseif ($action === 'saveRole') {
+ $roleID = Request::post("roleid", false);
+ $rolename = Request::post("rolename");
+ $locations = self::processLocations(Request::post("locations"));
+ $permissions = self::processPermissions(Request::post("permissions"));
+ PermissionDbUpdate::saveRole($rolename, $locations, $permissions, $roleID);
+ }
+ }
+
+ /**
+ * Menu etc. has already been generated, now it's time to generate page content.
+ */
+ protected function doRender()
+ {
+ $show = Request::get("show", "roles");
+
+ // switch between tables, but always show menu to switch tables
+ if ( $show === 'roles' || $show === 'users' || $show === 'locations' ) {
+ // get menu button colors
+ $buttonColors = array();
+ $buttonColors['rolesButtonClass'] = $show === 'roles' ? 'active' : '';
+ $buttonColors['usersButtonClass'] = $show === 'users' ? 'active' : '';
+ $buttonColors['locationsButtonClass'] = $show === 'locations' ? 'active' : '';
+
+ Render::addtemplate('_page', $buttonColors);
+
+ if ($show === "roles") {
+ $data = array("roles" => GetPermissionData::getRoles());
+ Render::addTemplate('rolestable', $data);
+ } elseif ($show === "users") {
+ $data = array("user" => GetPermissionData::getUserData(), "roles" => GetPermissionData::getRoles());
+ Render::addTemplate('userstable', $data);
+ } elseif ($show === "locations") {
+ $data = array("location" => GetPermissionData::getLocationData(), "allroles" => GetPermissionData::getRoles());
+ Render::addTemplate('locationstable', $data);
+ }
+ } elseif ($show === "roleEditor") {
+ $data = array();
+
+ $selectedPermissions = array();
+ $selectedLocations = array();
+ $roleID = Request::get("roleid", false);
+ if ($roleID) {
+ $roleData = GetPermissionData::getRoleData($roleID);
+ $data["roleid"] = $roleID;
+ $data["rolename"] = $roleData["rolename"];
+ $selectedPermissions = $roleData["permissions"];
+ $selectedLocations = $roleData["locations"];
+ }
+
+ $data["permissionHTML"] = self::generatePermissionHTML(PermissionUtil::getPermissions(), $selectedPermissions);
+ $data["locationHTML"] = self::generateLocationHTML(Location::getTree(), $selectedLocations);
+
+ Render::addTemplate('roleeditor', $data);
+
+ }
+ }
+
+ private static function generatePermissionHTML($subPermissions, $selectedPermissions = array(), $selectAll = false, $permString = "")
+ {
+ $res = "";
+ $toplevel = $permString == "";
+ if ($toplevel && in_array("*", $selectedPermissions)) $selectAll = true;
+ foreach ($subPermissions as $k => $v) {
+ $leaf = !is_array($v);
+ $nextPermString = $permString ? $permString.".".$k : $k;
+ $id = $leaf ? $nextPermString : $nextPermString.".*";
+ $selected = $selectAll || in_array($id, $selectedPermissions);
+ $res .= Render::parse("treenode",
+ array("id" => $id,
+ "name" => $toplevel ? Module::get($k)->getDisplayName() : $k,
+ "toplevel" => $toplevel,
+ "checkboxname" => "permissions",
+ "selected" => $selected,
+ "HTML" => $leaf ? "" : self::generatePermissionHTML($v, $selectedPermissions, $selected, $nextPermString),
+ "description" => $leaf ? $v : ""));
+ }
+ if ($toplevel) {
+ $res = Render::parse("treepanel",
+ array("id" => "*",
+ "name" => Dictionary::translateFile("template-tags", "lang_Permissions"),
+ "checkboxname" => "permissions",
+ "selected" => $selectAll,
+ "HTML" => $res));
+ }
+ return $res;
+ }
+
+ private static function generateLocationHTML($locations, $selectedLocations = array(), $selectAll = false, $toplevel = true)
+ {
+ $res = "";
+ if ($toplevel && in_array(0, $selectedLocations)) $selectAll = true;
+ foreach ($locations as $location) {
+ $selected = $selectAll || in_array($location["locationid"], $selectedLocations);
+ $res .= Render::parse("treenode",
+ array("id" => $location["locationid"],
+ "name" => $location["locationname"],
+ "toplevel" => $toplevel,
+ "checkboxname" => "locations",
+ "selected" => $selected,
+ "HTML" => array_key_exists("children", $location) ?
+ self::generateLocationHTML($location["children"], $selectedLocations, $selected, false) : ""));
+ }
+ if ($toplevel) {
+ $res = Render::parse("treepanel",
+ array("id" => 0,
+ "name" => Dictionary::translateFile("template-tags", "lang_Locations"),
+ "checkboxname" => "locations",
+ "selected" => $selectAll,
+ "HTML" => $res));
+ }
+ return $res;
+ }
+
+ private static function processLocations($locations)
+ {
+ if (in_array(0, $locations)) return array(NULL);
+ $result = array();
+ foreach ($locations as $location) {
+ $rootchain = array_reverse(Location::getLocationRootChain($location));
+ foreach ($rootchain as $l) {
+ if (in_array($l, $result)) break;
+ if (in_array($l, $locations)) {
+ $result[] = $l;
+ break;
+ }
+ }
+ }
+ return $result;
+ }
+
+ private static function processPermissions($permissions)
+ {
+ if (in_array("*", $permissions)) return array("*");
+ $result = array();
+ foreach ($permissions as $permission) {
+ $x =& $result;
+ foreach (explode(".", $permission) as $p) {
+ $x =& $x[$p];
+ }
+ }
+ return self::extractPermissions($result);
+ }
+
+ private static function extractPermissions($permissions)
+ {
+ $result = array();
+ foreach ($permissions as $permission => $a) {
+ if (is_array($a)) {
+ if (array_key_exists("*", $a)) {
+ $result[] = $permission.".*";
+ } else {
+ foreach (self::extractPermissions($a) as $subPermission) {
+ $result[] = $permission.".".$subPermission;
+ }
+ }
+ } else {
+ $result[] = $permission;
+ }
+ }
+ return $result;
+ }
+
+}
diff --git a/modules-available/permissionmanager/style.css b/modules-available/permissionmanager/style.css
new file mode 100644
index 00000000..bb03d153
--- /dev/null
+++ b/modules-available/permissionmanager/style.css
@@ -0,0 +1,88 @@
+#switchForm {
+ text-align: center;
+ margin-bottom: 50px;
+}
+
+#saveButton {
+ margin-left: 10px;
+}
+
+#rolename {
+ width: 200px;
+ display: inline-block;
+ margin-left: 10px;
+}
+
+.table {
+ margin-top: 20px;
+}
+
+.table > tbody > tr > td {
+ vertical-align: middle;
+ height: 50px;
+}
+
+.scrollingTable {
+ height: 500px;
+ overflow: auto;
+}
+
+.customSpanMargin {
+ display: inline-block;
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+.panel-primary > .panel-heading {
+ background-image: none;
+}
+
+.panel{
+ margin-bottom: 20px;
+}
+
+
+.tree-container .selected {
+ background-color: rgba(0, 182, 41, 0.23);
+}
+
+.tree-container {
+ -moz-column-gap: 20px;
+ -webkit-column-gap: 20px;
+ column-gap: 20px;
+}
+
+
+.tree-container > ul {
+ display: inline-block;
+ width: 100%;
+ padding: 0;
+}
+
+@media (max-width: 767px) {
+ .tree-container {
+ -moz-column-count: 1;
+ -webkit-column-count: 1;
+ column-count: 1;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .tree-container {
+ -moz-column-count: 2;
+ -webkit-column-count: 2;
+ column-count: 2;
+ }
+}
+
+@media (min-width: 992px) {
+ .tree-container {
+ -moz-column-count: 3;
+ -webkit-column-count: 3;
+ column-count: 3;
+ }
+}
+
+ul {
+ list-style-type: none;
+} \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/_page.html b/modules-available/permissionmanager/templates/_page.html
new file mode 100644
index 00000000..405462f7
--- /dev/null
+++ b/modules-available/permissionmanager/templates/_page.html
@@ -0,0 +1,29 @@
+<div class="row">
+ <div class="col-md-12" style="margin-bottom: 0;">
+ <div class='page-header'>
+ <div class='pull-right'>
+ <form id="switchForm" method="GET" action="?do=permissionmanager">
+ <input type="hidden" name="do" value="permissionmanager">
+
+ <div class="btn-group">
+ <button class="btn btn-default {{rolesButtonClass}}" type="submit" name="show" value="roles">
+ <span class="glyphicon glyphicon-education"></span>
+ {{lang_Roles}}
+ </button>
+
+ <button class="btn btn-default {{usersButtonClass}}" type="submit" name="show" value="users">
+ <span class="glyphicon glyphicon-user"></span>
+ {{lang_Users}}
+ </button>
+
+ <button class="btn btn-default {{locationsButtonClass}}" type="submit" name="show" value="locations">
+ <span class="glyphicon glyphicon-home"></span>
+ {{lang_Locations}}
+ </button>
+ </div>
+ </form>
+ </div>
+ <h1>{{lang_moduleName}}</h1>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/locationstable.html b/modules-available/permissionmanager/templates/locationstable.html
new file mode 100644
index 00000000..dcfefa94
--- /dev/null
+++ b/modules-available/permissionmanager/templates/locationstable.html
@@ -0,0 +1,37 @@
+<div class="row">
+ <div class="col-md-4"></div>
+ <div class="col-md-4">
+ <select multiple name="roles[]" id="select-role">
+ <option value>{{lang_selectizePlaceholder}}</option>
+ {{#allroles}}
+ <option value="{{roleid}}">{{rolename}}</option>
+ {{/allroles}}
+ </select>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <table id="locationsTable" class="table table-condensed table-hover stupidtable dataTable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_Locations}}</th>
+ <th>{{lang_Roles}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#location}}
+ <tr data-selectizeCount='0'>
+ <td>{{locationpad}} {{locationname}}</td>
+ <td>
+ {{#roles}}
+ <span class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</span>
+ {{/roles}}
+ </td>
+ </tr>
+ {{/location}}
+ </tbody>
+ </table>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/roleeditor.html b/modules-available/permissionmanager/templates/roleeditor.html
new file mode 100644
index 00000000..14839ee2
--- /dev/null
+++ b/modules-available/permissionmanager/templates/roleeditor.html
@@ -0,0 +1,78 @@
+<h1>{{lang_roleEditor}}</h1>
+<form method="post" action="?do=permissionmanager">
+ <input type="hidden" name="action" value="saveRole">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="roleid" value="{{roleid}}">
+
+ <div class="row">
+ <div class="col-md-12" style="margin-bottom: 20px;">
+ <ul class="nav nav-tabs text-center" role="tablist">
+ <li role="presentation" class="active"><a href="#permissions" role="tab" data-toggle="tab">{{lang_Permissions}}</a></li>
+ <li role="presentation"><a href="#locations" role="tab" data-toggle="tab">{{lang_Locations}}</a></li>
+ <li style="float: none; display: inline-block">
+ <b>{{lang_Name}}:</b>
+ <input name="rolename" value="{{rolename}}" type="text" id="rolename" class="form-control">
+ </li>
+ <li style="float: right;">
+ <button type="button" id="cancelButton" class="btn btn-default">{{lang_Cancel}}</button>
+ <button type="submit" id="saveButton" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_Save}}</button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="row" style="margin-bottom: 20px;">
+ <div class="col-md-12">
+ <div class="tab-content">
+ <div role="tabpanel" class="tab-pane active" id="permissions">
+ {{{permissionHTML}}}
+ </div>
+ <div role="tabpanel" class="tab-pane" id="locations">
+ {{{locationHTML}}}
+ </div>
+ </div>
+ </div>
+ </div>
+
+</form>
+
+<script type="application/javascript">
+
+ document.addEventListener("DOMContentLoaded", function () {
+
+ $(".tree-panel input[type=checkbox]").change(function () {
+ var checked = $(this).prop("checked");
+ var parent = $(this).parent().parent();
+ if (parent.hasClass("panel-heading")) parent = parent.parent();
+
+ var checkboxes = parent.find("input[type=checkbox]");
+ if (checked) {
+ checkboxes.prop("checked", true);
+ } else {
+ checkboxes.prop("checked", false);
+ while (!parent.hasClass("tree-panel")) {
+ parent = parent.parent().parent();
+ if (parent.hasClass("tree-container")) parent = parent.parent().parent();
+ parent.find("input[type=checkbox]:first").prop("checked", false);
+ }
+ }
+ });
+
+ $("#cancelButton").click(function () {
+ window.location.replace("?do=permissionmanager&show=roles");
+ });
+
+ $('form').submit(function () {
+ var name = $.trim($('#rolename').val());
+ if (name === '') {
+ alert('{{lang_emptyNameWarning}}');
+ return false;
+ }
+ });
+
+ $('[data-toggle="tooltip"]').tooltip({
+ container: 'body',
+ trigger : 'hover'
+ });
+ });
+
+</script> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/rolestable.html b/modules-available/permissionmanager/templates/rolestable.html
new file mode 100644
index 00000000..99401624
--- /dev/null
+++ b/modules-available/permissionmanager/templates/rolestable.html
@@ -0,0 +1,91 @@
+<form method="post" action="?do=permissionmanager">
+ <input type="hidden" name="token" value="{{token}}">
+
+ <div class="row">
+ <div class="col-md-4">
+ <button class="btn btn-success" type="button" onclick="openRoleEditor()"><span class="glyphicon glyphicon-plus"></span> {{lang_newRole}}</button>
+ </div>
+ <div class="col-md-4">
+ <input type="text" class="form-control" id="roleNameSearchField" onkeyup="searchFieldFunction()" placeholder="{{lang_searchPlaceholder}}">
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12">
+ <table id="rolesTable" class="table table-condensed table-hover stupidtable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_Roles}}</th>
+ <th class="text-center">{{lang_Edit}}</th>
+ <th class="text-center">{{lang_Delete}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#roles}}
+ <tr class="rolesRow">
+ <td class="rolesData">{{rolename}}</td>
+ <td class="text-center">
+ <a class="btn btn-xs btn-info" href="?do=permissionmanager&show=roleEditor&roleid={{roleid}}"><span class="glyphicon glyphicon-edit"></span></a>
+ </td>
+ <td class="text-center">
+ <a class="btn btn-xs btn-danger" href="#deleteModal" data-toggle="modal" data-target="#deleteModal" onclick="deleteRole('{{roleid}}')"><span class="glyphicon glyphicon-trash"></span></a>
+ </td>
+ </tr>
+ {{/roles}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+
+ <!-- Modals -->
+ <div class ="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_Delete}}</h4>
+ </div>
+ <div class="modal-body">
+ {{lang_deleteCheck}}
+ </div>
+ <div class="modal-footer">
+ <input type="hidden" id="deleteId" name="deleteId" value=""/>
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value="deleteRole" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> {{lang_Delete}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+</form>
+
+<script>
+ function openRoleEditor() {
+ window.location.href = "?do=permissionmanager&show=roleEditor"
+ }
+
+ function deleteRole($roleid) {
+ $(".modal-footer #deleteId").val($roleid);
+ }
+
+ function searchFieldFunction() {
+ // Declare variables
+ var input, filter, table, trs, a, i;
+ input = document.getElementById('roleNameSearchField');
+ filter = input.value.toUpperCase();
+ table = document.getElementById("rolesTable");
+ trs = table.getElementsByClassName('rolesRow');
+
+ // Loop through all list items, and hide those who don't match the search query
+ for (i = 0; i < trs.length; i++) {
+ a = trs[i].getElementsByClassName("rolesData")[0];
+ if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
+ trs[i].style.display = "";
+ } else {
+ trs[i].style.display = "none";
+ }
+ }
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/treenode.html b/modules-available/permissionmanager/templates/treenode.html
new file mode 100644
index 00000000..336ca13e
--- /dev/null
+++ b/modules-available/permissionmanager/templates/treenode.html
@@ -0,0 +1,11 @@
+{{#toplevel}}<ul>{{/toplevel}}
+ <li title="{{description}}" data-toggle="tooltip" data-placement="left">
+ <div class='checkbox'>
+ <input name='{{checkboxname}}[]' value='{{id}}' type='checkbox' class='form-control' {{#selected}}checked{{/selected}}>
+ <label>{{#toplevel}}<b>{{/toplevel}}{{name}}{{#toplevel}}</b>{{/toplevel}}</label>
+ </div>
+ <ul>
+ {{{HTML}}}
+ </ul>
+ </li>
+{{#toplevel}}</ul>{{/toplevel}}
diff --git a/modules-available/permissionmanager/templates/treepanel.html b/modules-available/permissionmanager/templates/treepanel.html
new file mode 100644
index 00000000..53e316c9
--- /dev/null
+++ b/modules-available/permissionmanager/templates/treepanel.html
@@ -0,0 +1,13 @@
+<div class='panel panel-primary tree-panel'>
+ <div class='panel-heading'>
+ <div class='checkbox'>
+ <input name='{{checkboxname}}[]' value='{{id}}' type='checkbox' class='form-control' {{#selected}}checked{{/selected}}>
+ <label>{{name}}</label>
+ </div>
+ </div>
+ <div class='panel-body'>
+ <div class="tree-container" style="padding-left: 20px; padding-right: 20px;">
+ {{{HTML}}}
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/userstable.html b/modules-available/permissionmanager/templates/userstable.html
new file mode 100644
index 00000000..bd48d16d
--- /dev/null
+++ b/modules-available/permissionmanager/templates/userstable.html
@@ -0,0 +1,170 @@
+<form method="post" action="?do=permissionmanager&show=users">
+ <input type="hidden" name="token" value="{{token}}">
+
+ <div class="row">
+ <div class="col-md-4">
+ <button class="btn btn-success" type="button" data-toggle="modal" data-target="#addRoleToUserModal"><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button>
+ <button class="btn btn-danger" type="button" data-toggle="modal" data-target="#removeRoleFromUserModal"><span class="glyphicon glyphicon-trash"></span> {{lang_removeRole}}</button>
+ </div>
+ <div class="col-md-4 text-left">
+ <select multiple name="roles[]" id="select-role">
+ <option value>{{lang_selectizePlaceholder}}</option>
+ {{#roles}}
+ <option value="{{roleid}}">{{rolename}}</option>
+ {{/roles}}
+ </select>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12">
+ <table id="usersTable" class="table table-condensed table-hover stupidtable dataTable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_Users}}</th>
+ <th>{{lang_Roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#user}}
+ <tr data-selectizeCount='0'>
+ <td>{{username}}</td>
+ <td>
+ {{#roles}}
+ <span class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</span>
+ {{/roles}}
+ </td>
+ <td data-sort-value="0">
+ <div class="checkbox">
+ <input id="{{userid}}" type="checkbox" name="users[]" value='{{userid}}'>
+ <label for="{{userid}}"></label>
+ </div>
+ </td>
+ </tr>
+ {{/user}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <!-- Modals -->
+ <div class ="modal fade" id="addRoleToUserModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="myModalLabel">{{lang_addRole}}</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-12 scrollingTable">
+ <table id="addRoleToUserTable" class="table table-condensed table-hover stupidtable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_Roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#roles}}
+ <tr>
+ <td>{{rolename}}</td>
+ <td data-sort-value="0">
+ <div class="checkbox">
+ <input id="add{{roleid}}" type="checkbox" name="roles[]" value='{{roleid}}'>
+ <label for="add{{roleid}}"></label>
+ </div>
+ </td>
+ </tr>
+ {{/roles}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value="addRoleToUser" class="btn btn-success" onclick="clearRemoveRoleModal()"><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class ="modal fade" id="removeRoleFromUserModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <h4 class="modal-title" id="myModalLabel2">{{lang_Remove}}</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-12 scrollingTable">
+ <table id="removeRoleFromUserTable" class="table table-condensed table-hover stupidtable">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_Roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_Selected}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#roles}}
+ <tr>
+ <td>{{rolename}}</td>
+ <td data-sort-value="0">
+ <div class="checkbox">
+ <input id="remove{{roleid}}" type="checkbox" name="roles[]" value='{{roleid}}'>
+ <label for="remove{{roleid}}"></label>
+ </div>
+ </td>
+ </tr>
+ {{/roles}}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" name="action" value="removeRoleFromUser" class="btn btn-danger" onclick="clearAddRoleModal()"><span class="glyphicon glyphicon-trash"></span> {{lang_Remove}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form>
+
+<script>
+ document.addEventListener("DOMContentLoaded", function() {
+ // if checked,: mark green, else: unmark
+ $('input:checkbox').change(function() {
+ if ($(this).is(':checked')) {
+ $(this).closest("td").data("sort-value", 1);
+ $(this).closest("tr").css("background-color", "#f2ffe6");
+ } else {
+ $(this).closest("td").data("sort-value", 0);
+ $(this).closest("tr").css("background-color", "");
+ }
+
+ });
+ });
+
+ // if remove-Role button is clicked, uncheck all checkboxes in add-role modal so they aren't submitted too
+ function clearAddRoleModal () {
+ $('#addRoleToUserModal')
+ .find("input[type=checkbox]")
+ .prop("checked", "")
+ .end();
+ }
+
+ // if add-Role button is clicked, uncheck all checkboxes in remove-role modal so they aren't submitted too
+ function clearRemoveRoleModal() {
+ $('#removeRoleFromUserModal')
+ .find("input[type=checkbox]")
+ .prop("checked", "")
+ .end();
+ }
+</script> \ No newline at end of file
diff --git a/modules-available/rebootcontrol/clientscript.js b/modules-available/rebootcontrol/clientscript.js
deleted file mode 100644
index d3ecbe48..00000000
--- a/modules-available/rebootcontrol/clientscript.js
+++ /dev/null
@@ -1,22 +0,0 @@
-document.addEventListener("DOMContentLoaded", function() {
- var table = $("table");
- table.stupidtable({
- "ipsort":function(a,b){
- var aa = a.split(".");
- var bb = b.split(".");
-
- var resulta = aa[0]*0x1000000 + aa[1]*0x10000 + aa[2]*0x100 + aa[3]*1;
- var resultb = bb[0]*0x1000000 + bb[1]*0x10000 + bb[2]*0x100 + bb[3]*1;
-
- return resulta-resultb;
- }
- });
-
- table.on("aftertablesort", function (event, data) {
- var th = $(this).find("th");
- th.find(".arrow").remove();
- var dir = $.fn.stupidtable.dir;
- var arrow = data.direction === dir.ASC ? "down" : "up";
- th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>');
- });
-}); \ No newline at end of file
diff --git a/modules-available/rebootcontrol/inc/rebootqueries.inc.php b/modules-available/rebootcontrol/inc/rebootqueries.inc.php
index 8f65b756..1bdcb9a2 100644
--- a/modules-available/rebootcontrol/inc/rebootqueries.inc.php
+++ b/modules-available/rebootcontrol/inc/rebootqueries.inc.php
@@ -21,7 +21,7 @@ class RebootQueries
}
$res = Database::simpleQuery("
SELECT machine.machineuuid, machine.hostname, machine.clientip,
- machine.lastboot, machine.lastseen, machine.logintime,
+ machine.lastboot, machine.lastseen, machine.logintime, machine.state,
$sessionField, machine.currentuser, machine.locationid
FROM machine
$leftJoin
@@ -29,12 +29,12 @@ class RebootQueries
$ret = $res->fetchAll(PDO::FETCH_ASSOC);
$NOW = time();
foreach ($ret as &$row) {
- if ($row['lastboot'] == 0 || $NOW - $row['lastseen'] > 600) {
- $row['status'] = 0;
- } else {
+ if ($row['state'] === 'IDLE' || $row['state'] === 'OCCUPIED') {
$row['status'] = 1;
+ } else {
+ $row['status'] = 0;
}
- if ($row['status'] === 0 || $row['logintime'] == 0) {
+ if ($row['state'] !== 'OCCUPIED') {
$row['currentuser'] = '';
$row['currentsession'] = '';
}
diff --git a/modules-available/rebootcontrol/lang/de/template-tags.json b/modules-available/rebootcontrol/lang/de/template-tags.json
index 57164f02..c5bd1670 100644
--- a/modules-available/rebootcontrol/lang/de/template-tags.json
+++ b/modules-available/rebootcontrol/lang/de/template-tags.json
@@ -15,6 +15,7 @@
"lang_rebootAt": "Neustart um:",
"lang_rebootButton": "Neustarten",
"lang_rebootCheck": "Wollen Sie die ausgew\u00e4hlten Rechner wirklich neustarten?",
+ "lang_rebootControl": "Reboot Control",
"lang_rebooting": "Neustart...",
"lang_selectall": "Alle ausw\u00e4hlen",
"lang_selected": "Ausgew\u00e4hlt",
diff --git a/modules-available/rebootcontrol/lang/en/template-tags.json b/modules-available/rebootcontrol/lang/en/template-tags.json
index ca44171a..63a5b4a8 100644
--- a/modules-available/rebootcontrol/lang/en/template-tags.json
+++ b/modules-available/rebootcontrol/lang/en/template-tags.json
@@ -15,6 +15,7 @@
"lang_rebootAt": "Reboot at:",
"lang_rebootButton": "Reboot",
"lang_rebootCheck": "Do you really want to reboot the selected clients?",
+ "lang_rebootControl": "Reboot Control",
"lang_rebooting": "Rebooting...",
"lang_selectall": "Select all",
"lang_selected": "Selected",
diff --git a/modules-available/rebootcontrol/templates/_page.html b/modules-available/rebootcontrol/templates/_page.html
index 065a9f01..1bef8dd4 100644
--- a/modules-available/rebootcontrol/templates/_page.html
+++ b/modules-available/rebootcontrol/templates/_page.html
@@ -1,3 +1,9 @@
+<div class="page-header">
+ <button type="button" id="settingsButton" class="btn btn-default pull-right" data-toggle="modal" data-target="#settingsModal"><span class="glyphicon glyphicon-cog"></span> {{lang_settings}}</button>
+ <h1>{{lang_rebootControl}}</h1>
+</div>
+
+
<form id="tableDataForm" method="post" action="?do=rebootcontrol" class="form-inline">
<input type="hidden" name="token" value="{{token}}">
<div class="row">
@@ -9,7 +15,6 @@
{{/locations}}
</select>
</label>
- <button type="button" id="settingsButton" class="btn btn-default pull-right" data-toggle="modal" data-target="#settingsModal"><span class="glyphicon glyphicon-cog"></span></button>
<button type="button" id="selectAllButton" class="btn btn-primary pull-right" onclick="selectAllRows()"><span class="glyphicon glyphicon-check"></span> {{lang_selectall}}</button>
<button type="button" id="unselectAllButton" class="btn btn-default pull-right" onclick="unselectAllRows()" style="display: none;"><span class="glyphicon glyphicon-unchecked"></span> {{lang_unselectall}}</button>
<button type="button" id="rebootButton" class="btn btn-warning pull-right" data-toggle="modal" data-target="#rebootModal" disabled><span class="glyphicon glyphicon-repeat"></span> {{lang_rebootButton}}</button>
@@ -18,11 +23,11 @@
</div>
<div class="row">
<div class="col-md-12">
- <table class="table table-condensed table-hover" id="dataTable">
+ <table class="table table-condensed table-hover stupidtable" id="dataTable">
<thead>
<tr>
<th data-sort="string">{{lang_client}}</th>
- <th data-sort="ipsort">{{lang_ip}}</th>
+ <th data-sort="ipv4">{{lang_ip}}</th>
<th data-sort="string">{{lang_status}}</th>
<th data-sort="string">{{lang_session}}</th>
<th data-sort="string">{{lang_user}}</th>
@@ -74,7 +79,7 @@
</div>
<div class="modal-body">
<span id="pubKeyTitle">{{lang_pubKey}}</span>
- <button class="btn btn-s btn-warning pull-right" onclick="generateNewKeypair()" type="button">{{lang_genNew}}</button>
+ <button class="btn btn-s btn-warning pull-right" onclick="generateNewKeypair()" type="button"><span class="glyphicon glyphicon-refresh"></span> {{lang_genNew}}</button>
<pre id="pubKey">{{pubKey}}</pre>
</div>
<div class="modal-footer">
diff --git a/modules-available/rebootcontrol/templates/status.html b/modules-available/rebootcontrol/templates/status.html
index 35bbe42f..c2fdab46 100644
--- a/modules-available/rebootcontrol/templates/status.html
+++ b/modules-available/rebootcontrol/templates/status.html
@@ -10,11 +10,11 @@
<div data-tm-id="{{taskId}}" data-tm-log="error" data-tm-callback="updateStatus"></div>
<div>
- <table class="table table-hover" id="dataTable">
+ <table class="table table-hover stupidtable" id="dataTable">
<thead>
<tr>
<th data-sort="string">{{lang_client}}</th>
- <th data-sort="ipsort">{{lang_ip}}</th>
+ <th data-sort="ipv4">{{lang_ip}}</th>
<th data-sort="string">
{{lang_status}}
</th>
diff --git a/modules-available/roomplanner/baseconfig/getconfig.inc.php b/modules-available/roomplanner/baseconfig/getconfig.inc.php
index 92e7a8d3..f4708547 100644
--- a/modules-available/roomplanner/baseconfig/getconfig.inc.php
+++ b/modules-available/roomplanner/baseconfig/getconfig.inc.php
@@ -1,12 +1,3 @@
<?php
ConfigHolder::add("SLX_PVS_CONFIG_URL", 'http://' . $_SERVER['SERVER_ADDR'] . $_SERVER['SCRIPT_NAME'] . '?do=roomplanner');
-
-$res = Database::queryFirst('SELECT dedicatedmgr FROM location_roomplan WHERE managerip = :ip LIMIT 1', ['ip' => $ip]);
-if ($res !== false) {
- if ((int)$res['dedicatedmgr'] !== 0) {
- ConfigHolder::add("SLX_PVS_DEDICATED", 'yes');
- } else {
- ConfigHolder::add("SLX_PVS_HYBRID", 'yes');
- }
-} \ No newline at end of file
diff --git a/modules-available/roomplanner/clientscript.js b/modules-available/roomplanner/clientscript.js
index 1cd65132..722e3909 100644
--- a/modules-available/roomplanner/clientscript.js
+++ b/modules-available/roomplanner/clientscript.js
@@ -11,10 +11,16 @@ var selectMachinInitialized = false;
var placedMachines = [];
+function makeCombinedFieldSingle(item)
+{
+ item.combined = (item.machineuuid + " " + item.hostname + " " + item.clientip + " " + item.macaddr + " " + item.macaddr.replace(/-/g, ':')).toLocaleLowerCase();
+ item.sortField = (item.fixedlocationid === null ? 'a' : 'z') + item.hostname;
+}
+
function makeCombinedField(machineArray)
{
machineArray.forEach(function (v,i,a){
- machineArray[i].combined = (v.machineuuid + " " + v.hostname + " " + v.clientip + " " + v.macaddr + " " + v.macaddr.replace(/-/g, ':')).toLocaleLowerCase();
+ makeCombinedFieldSingle(machineArray[i]);
});
return machineArray;
}
@@ -25,15 +31,14 @@ function renderMachineEntry(item, escape) {
// console.log('used uuids is ');
// console.log(placedMachines);
- var isUsed = $.inArray(item.machineuuid, placedMachines) > -1;
var extraClass = '';
var extraText = '';
- if (isUsed) {
- extraText = ' (already placed)';
- extraClass = 'used';
- } else if (item.otherroom) {
+ if (item.otherroom) {
extraText = ' (in ' + item.otherroom + ')';
extraClass = 'used';
+ } else if (item.fixedlocationid !== null) {
+ extraText = ' (already placed)';
+ extraClass = 'used';
}
return '<div class="machine-entry ' + extraClass +'">'
//+ ' <div class="machine-logo"><i class="glyphicon glyphicon-hdd"></i></div>'
@@ -120,7 +125,7 @@ function initSelectize() {
render : { option : renderMachineEntry, item: renderMachineSelected},
load: loadMachines,
maxItems: 1,
- sortField: 'hostname',
+ sortField: 'sortField',
sortDirection: 'asc',
onChange: clearSubnetBox
});
@@ -136,7 +141,7 @@ function initSelectize() {
create: false,
render : { option : renderMachineEntry, item: renderMachineSelected},
maxItems: 1,
- sortField: 'hostname',
+ sortField: 'sortField',
sortDirection: 'asc',
onChange: clearSearchBox
});
@@ -148,13 +153,16 @@ function initSelectize() {
}
function onBtnSelect() {
/* check which one has a value */
- console.assert($selectizeSubnet.length == 1);
- console.assert($selectizeSearch.length == 1);
+ console.assert($selectizeSubnet.length === 1);
+ console.assert($selectizeSearch.length === 1);
var bySubnet = machineCache[$selectizeSubnet[0].selectize.getValue()];
var bySearch = machineCache[$selectizeSearch[0].selectize.getValue()];
- var value = (bySubnet === undefined || bySubnet == "") ? bySearch : bySubnet;
+ var value = !bySubnet ? bySearch : bySubnet;
+ value.fixedlocationid = -1;
+ makeCombinedFieldSingle(value);
+
var result = {muuid: value.machineuuid, ip: value.clientip, mac_address : value.macaddr, hostname: value.hostname};
currentCallback(result);
@@ -165,6 +173,14 @@ function onBtnSelect() {
clearSearchBox();
}
+function onPcDelete(muuid) {
+ var bySubnet = machineCache[muuid];
+ var bySearch = machineCache[muuid];
+ var value = !bySubnet ? bySearch : bySubnet;
+ value.fixedlocationid = null;
+ makeCombinedFieldSingle(value);
+}
+
/* to be called from berryous' code */
function selectMachine(usedUuids, callback) {
initSelectize();
@@ -172,7 +188,7 @@ function selectMachine(usedUuids, callback) {
placedMachines = usedUuids;
$modal.modal('show');
$modal.one('hidden.bs.modal', function () {
- if (currentCallback != null) {
+ if (currentCallback) {
currentCallback(false);
}
});
diff --git a/modules-available/roomplanner/config.json b/modules-available/roomplanner/config.json
index f620405a..537714c3 100644
--- a/modules-available/roomplanner/config.json
+++ b/modules-available/roomplanner/config.json
@@ -1,3 +1,3 @@
{
- "dependencies": ["js_jqueryui", "js_selectize", "bootstrap_dialog", "statistics", "locations"]
+ "dependencies": ["js_jqueryui", "js_selectize", "bootstrap_dialog", "statistics", "locations", "runmode"]
}
diff --git a/modules-available/roomplanner/hooks/runmode/config.json b/modules-available/roomplanner/hooks/runmode/config.json
new file mode 100644
index 00000000..27c601fd
--- /dev/null
+++ b/modules-available/roomplanner/hooks/runmode/config.json
@@ -0,0 +1,7 @@
+{
+ "getModeName": "PvsGenerator::getManagerName",
+ "isClient": false,
+ "configHook": "PvsGenerator::runmodeConfigHook",
+ "allowGenericEditor": false,
+ "deleteUrlSnippet": "locationid="
+} \ No newline at end of file
diff --git a/modules-available/roomplanner/inc/pvsgenerator.inc.php b/modules-available/roomplanner/inc/pvsgenerator.inc.php
index d61e826b..292441d4 100644
--- a/modules-available/roomplanner/inc/pvsgenerator.inc.php
+++ b/modules-available/roomplanner/inc/pvsgenerator.inc.php
@@ -54,9 +54,18 @@ class PvsGenerator
/* collect names and build room blocks - filter empty rooms while at it */
$roomNames = array();
$roomBlocks = '';
+ $overrides = [];
foreach ($rooms as $room) {
- if (is_null($room['notnull']) || isset($room['skip']) // Not leaf
- || empty($room['managerip'])) // rooms without managerips don't make sense
+ if (is_null($room['notnull']) || isset($room['skip'])) // Not leaf
+ continue;
+ if (Module::isAvailable('runmode')) {
+ $pc = RunMode::getForMode('roomplanner', $room['locationid'], true);
+ if (!empty($pc)) {
+ $pc = array_pop($pc);
+ $room['managerip'] = $pc['clientip'];
+ }
+ }
+ if (empty($room['managerip'])) // rooms without managerips don't make sense
continue;
$roomBlock = PvsGenerator::generateRoomBlock($room);
if ($roomBlock === false)
@@ -189,7 +198,7 @@ class PvsGenerator
private static function boundingBox($grid, &$minX, &$minY, &$maxX, &$maxY)
{
- $minX = PHP_INT_MAX; /* PHP_INT_MIN is only avaiable since PHP 7 */
+ $minX = PHP_INT_MAX; /* PHP_INT_MIN is only available since PHP 7 */
$maxX = ~PHP_INT_MAX;
$minY = PHP_INT_MAX;
$maxY = ~PHP_INT_MAX;
@@ -202,4 +211,36 @@ class PvsGenerator
}
}
+ public static function runmodeConfigHook($machineUuid, $locationId, $data)
+ {
+ if (!empty($data)) {
+ $data = json_decode($data, true);
+ }
+ if (!is_array($data)) {
+ $data = array();
+ }
+
+ if (isset($data['dedicatedmgr']) && $data['dedicatedmgr']) {
+ ConfigHolder::add("SLX_ADDONS", false, 100000);
+ ConfigHolder::add("SLX_PVS_DEDICATED", 'yes');
+ ConfigHolder::add("SLX_EXAM", false, 100000);
+ ConfigHolder::add("SLX_AUTOLOGIN", 'ON', 100000);
+ } else {
+ ConfigHolder::add("SLX_PVS_HYBRID", 'yes');
+ }
+ }
+
+ /**
+ * Get display name for manager of given locationId.
+ * @param $locationId
+ * @return bool|string
+ */
+ public static function getManagerName($locationId)
+ {
+ $names = Location::getNameChain($locationId);
+ if ($names === false)
+ return false;
+ return implode(' / ', $names);
+ }
+
}
diff --git a/modules-available/roomplanner/install.inc.php b/modules-available/roomplanner/install.inc.php
index a6d98384..13365fe1 100644
--- a/modules-available/roomplanner/install.inc.php
+++ b/modules-available/roomplanner/install.inc.php
@@ -6,7 +6,6 @@ $res = array();
$res[] = tableCreate('location_roomplan', "
`locationid` INT(11) NOT NULL,
`managerip` varchar(45) CHARACTER SET ascii DEFAULT '',
- `dedicatedmgr` tinyint(1) NOT NULL DEFAULT 0,
`tutoruuid` char(36) CHARACTER SET ascii DEFAULT NULL,
`roomplan` BLOB DEFAULT NULL,
PRIMARY KEY (`locationid`),
@@ -29,24 +28,40 @@ if (!tableHasColumn('location_roomplan', 'tutoruuid')) {
}
$res[] = UPDATE_DONE;
}
-if (!tableHasColumn('location_roomplan', 'dedicatedmgr')) {
- $ret = Database::exec("ALTER TABLE `location_roomplan` ADD `dedicatedmgr` tinyint(1) NOT NULL DEFAULT 0 AFTER `managerip`") !== false;
- if ($ret === false) {
- finalResponse(UPDATE_FAILED, 'Adding dedicatedmgr to location_roomplan failed: ' . Database::lastError());
- }
- $res[] = UPDATE_DONE;
-}
if (in_array(UPDATE_DONE, $res)) {
Database::exec("ALTER TABLE `location_roomplan`
- ADD CONSTRAINT `location_roomplan_ibfk_1` FOREIGN KEY (`locationid`) REFERENCES `location` (`locationid`) ON DELETE CASCADE");
+ ADD CONSTRAINT `location_roomplan_ibfk_1` FOREIGN KEY (`locationid`) REFERENCES `location` (`locationid`) ON DELETE CASCADE");
Database::exec("ALTER TABLE `location_roomplan`
- ADD CONSTRAINT `location_roomplan_ibfk_2` FOREIGN KEY (`tutoruuid`) REFERENCES `machine` (`machineuuid`) ON DELETE SET NULL ON UPDATE CASCADE");
+ ADD CONSTRAINT `location_roomplan_ibfk_2` FOREIGN KEY (`tutoruuid`) REFERENCES `machine` (`machineuuid`) ON DELETE SET NULL ON UPDATE CASCADE");
}
-if (in_array(UPDATE_DONE, $res)) {
- finalResponse(UPDATE_DONE, 'Table created successfully');
+// 2017-11-30: Refactor to runmode
+// managerip, dedicatedmgr, --> runmode
+if (tableHasColumn('location_roomplan', 'dedicatedmgr')) {
+ if (!tableExists('runmode') || !tableExists('machine')) {
+ $res[] = UPDATE_RETRY;
+ } else {
+ $ret = Database::simpleQuery('SELECT lr.locationid, lr.managerip, lr.dedicatedmgr, m.machineuuid
+ FROM location_roomplan lr INNER JOIN machine m ON (m.clientip = lr.managerip)');
+ if ($ret === false) {
+ $res[] = UPDATE_FAILED;
+ } else {
+ while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $dedi = $row['dedicatedmgr'] != 0;
+ $data = json_encode(array('dedicatedmgr' => $dedi));
+ Database::exec("INSERT IGNORE INTO runmode (machineuuid, module, modeid, modedata, isclient)
+ VALUES (:machineuuid, 'roomplanner', :locationid, :modedata, :isclient)", array(
+ 'machineuuid' => $row['machineuuid'],
+ 'locationid' => $row['locationid'],
+ 'modedata' => $data,
+ 'isclient' => ($dedi ? 0 : 1)
+ ));
+ }
+ Database::exec('ALTER TABLE location_roomplan DROP COLUMN dedicatedmgr');
+ $res[] = UPDATE_DONE;
+ }
+ }
}
-finalResponse(UPDATE_NOOP, 'Everything already up to date');
-
+responseFromArray($res);
diff --git a/modules-available/roomplanner/js/grid.js b/modules-available/roomplanner/js/grid.js
index 334057bf..466e42aa 100644
--- a/modules-available/roomplanner/js/grid.js
+++ b/modules-available/roomplanner/js/grid.js
@@ -74,10 +74,13 @@ if (!roomplanner) var roomplanner = {
initDelete: function(el) {
$(el).append('<div class="deleteHandle glyphicon glyphicon-remove-sign"></div>');
$(el).find('.deleteHandle').click(function() {
- if ($(this).parent().attr('itemtype') == "pc") {
+ if ($(this).parent().attr('itemtype') === "pc") {
var self = this;
BootstrapDialog.confirm(__('are you sure'),function(result) {
if (result) {
+ if (onPcDelete) {
+ onPcDelete($(self).parent().attr('muuid'));
+ }
$(self).parent().remove();
}
});
@@ -169,8 +172,8 @@ if (!roomplanner) var roomplanner = {
var mw = $(this).resizable("option","maxWidth");
var mh = $(this).resizable("option","maxHeight");
- var hLimit = ($(this).attr('scalable') == 'v');
- var vLimit = ($(this).attr('scalable') == 'h');
+ var hLimit = ($(this).attr('scalable') === 'v');
+ var vLimit = ($(this).attr('scalable') === 'h');
if(collides.length) {
$(collides).each(function(idx,item) {
diff --git a/modules-available/roomplanner/lang/de/module.json b/modules-available/roomplanner/lang/de/module.json
new file mode 100644
index 00000000..58dac918
--- /dev/null
+++ b/modules-available/roomplanner/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "PVS-Manager",
+ "page_title": "PVS-Manager"
+} \ No newline at end of file
diff --git a/modules-available/roomplanner/page.inc.php b/modules-available/roomplanner/page.inc.php
index 4e36d3ba..764d5cdb 100644
--- a/modules-available/roomplanner/page.inc.php
+++ b/modules-available/roomplanner/page.inc.php
@@ -58,7 +58,17 @@ class Page_Roomplanner extends Page
if ($this->action === 'show') {
/* do nothing */
Dashboard::disable();
- $config = Database::queryFirst('SELECT roomplan, managerip, dedicatedmgr, tutoruuid FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]);
+ $config = Database::queryFirst('SELECT roomplan, managerip, tutoruuid FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]);
+ $runmode = RunMode::getForMode(Page::getModule(), $this->locationid, true);
+ if (empty($runmode)) {
+ $config['dedicatedmgr'] = false;
+ } else {
+ $runmode = array_pop($runmode);
+ $config['managerip'] = $runmode['clientip'];
+ $config['manageruuid'] = $runmode['machineuuid'];
+ $data = json_decode($runmode['modedata'], true);
+ $config['dedicatedmgr'] = (isset($data['dedicatedmgr']) && $data['dedicatedmgr']);
+ }
if ($config !== false) {
$managerIp = $config['managerip'];
$dediMgr = $config['dedicatedmgr'] ? 'checked' : '';
@@ -90,7 +100,7 @@ class Page_Roomplanner extends Page
$query = Request::get('query', false, 'string');
$aquery = preg_replace('/[^\x01-\x7f]+/', '%', $query);
- $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
+ $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname, fixedlocationid '
. 'FROM machine '
. 'WHERE machineuuid LIKE :aquery '
. ' OR macaddr LIKE :aquery '
@@ -101,6 +111,9 @@ class Page_Roomplanner extends Page
$returnObject = ['machines' => []];
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ if (empty($row['hostname'])) {
+ $row['hostname'] = $row['clientip'];
+ }
$returnObject['machines'][] = $row;
}
echo json_encode($returnObject);
@@ -207,24 +220,39 @@ class Page_Roomplanner extends Page
protected function saveRoomConfig($furniture, $tutorUuid)
{
$obj = json_encode(['furniture' => $furniture]);
- Database::exec('INSERT INTO location_roomplan (locationid, roomplan, managerip, tutoruuid, dedicatedmgr)'
- . ' VALUES (:locationid, :roomplan, :managerip, :tutoruuid, :dedicatedmgr)'
+ $managerIp = Request::post('managerip', '', 'string');
+ Database::exec('INSERT INTO location_roomplan (locationid, roomplan, managerip, tutoruuid)'
+ . ' VALUES (:locationid, :roomplan, :managerip, :tutoruuid)'
. ' ON DUPLICATE KEY UPDATE '
- . ' roomplan=VALUES(roomplan), managerip=VALUES(managerip), tutoruuid=VALUES(tutoruuid), dedicatedmgr=VALUES(dedicatedmgr)', [
+ . ' roomplan=VALUES(roomplan), managerip=VALUES(managerip), tutoruuid=VALUES(tutoruuid)', [
'locationid' => $this->locationid,
'roomplan' => $obj,
- 'managerip' => Request::post('managerip', '', 'string'),
- 'dedicatedmgr' => (Request::post('dedimgr') === 'on' ? 1 : 0),
+ 'managerip' => $managerIp,
'tutoruuid' => $tutorUuid
]);
+ // See if the client is known, set run-mode
+ if (empty($managerIp)) {
+ RunMode::deleteMode(Page::getModule(), $this->locationid);
+ } else {
+ RunMode::deleteMode(Page::getModule(), $this->locationid);
+ $pc = Statistics::getMachinesByIp($managerIp, Machine::NO_DATA, 'lastseen DESC');
+ if (!empty($pc)) {
+ $dedicated = (Request::post('dedimgr') === 'on');
+ $pc = array_shift($pc);
+ RunMode::setRunMode($pc->machineuuid, Page::getModule()->getIdentifier(), $this->locationid, json_encode([
+ 'dedicatedmgr' => $dedicated
+ ]), !$dedicated);
+ }
+ }
}
protected function getFurniture($config)
{
- if ($config === false) {
+ if ($config === false)
return array();
- }
$config = json_decode($config['roomplan'], true);
+ if (!is_array($config))
+ return array();
return $config;
}
@@ -262,7 +290,7 @@ class Page_Roomplanner extends Page
protected function getPotentialMachines()
{
- $result = Database::simpleQuery('SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, l.locationname AS otherroom
+ $result = Database::simpleQuery('SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, l.locationname AS otherroom, m.fixedlocationid
FROM machine m
LEFT JOIN location l ON (m.fixedlocationid = l.locationid AND m.subnetlocationid <> m.fixedlocationid)
WHERE subnetlocationid = :locationid', ['locationid' => $this->locationid]);
@@ -270,6 +298,9 @@ class Page_Roomplanner extends Page
$machines = [];
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ if (empty($row['hostname'])) {
+ $row['hostname'] = $row['clientip'];
+ }
$machines[] = $row;
}
diff --git a/modules-available/runmode/inc/runmode.inc.php b/modules-available/runmode/inc/runmode.inc.php
index 271542b8..ad1f52bf 100644
--- a/modules-available/runmode/inc/runmode.inc.php
+++ b/modules-available/runmode/inc/runmode.inc.php
@@ -2,6 +2,9 @@
class RunMode
{
+ const DATA_DETAILED = 1;
+ const DATA_MACHINE_DATA = 2;
+ const DATA_STRINGS = 4;
private static $moduleConfigs = array();
@@ -29,58 +32,86 @@ class RunMode
* @param string|null $modeId an ID specific to the module to further specify the run mode, NULL to delete the run mode entry
* @param string|null $modeData optional, additional data for the run mode
* @param bool|null $isClient whether to count the machine as a client (in statistics etc.) NULL for looking at module's general runmode config
- * @return bool whether it was set
+ * @return bool whether it was set/deleted
*/
- public static function setRunMode($machineuuid, $moduleId, $modeId, $modeData, $isClient)
+ public static function setRunMode($machineuuid, $moduleId, $modeId, $modeData = null, $isClient = null)
{
- // - Check if module provides runmode config at all
- $config = self::getModuleConfig($moduleId);
- if ($config === false)
- return false;
+ if (is_object($moduleId)) {
+ $moduleId = $moduleId->getIdentifier();
+ }
// - Check if machine exists
$machine = Statistics::getMachine($machineuuid, Machine::NO_DATA);
if ($machine === false)
return false;
+ // - Delete entry if mode is null
+ if ($modeId === null) {
+ return Database::exec('DELETE FROM runmode WHERE machineuuid = :machineuuid', compact('machineuuid')) > 0;
+ }
// - Add/replace entry in runmode table
- if (is_null($modeId)) {
- Database::exec('DELETE FROM runmode WHERE machineuuid = :machineuuid', compact('machineuuid'));
- } else {
- if ($isClient === null) {
- $isClient = $config->isClient;
- }
- Database::exec('INSERT INTO runmode (machineuuid, module, modeid, modedata, isclient)'
- . ' VALUES (:uuid, :module, :modeid, :modedata, :isclient)'
- . ' ON DUPLICATE KEY'
- . ' UPDATE module = VALUES(module), modeid = VALUES(modeid), modedata = VALUES(modedata), isclient = VALUES(isclient)', array(
- 'uuid' => $machineuuid,
- 'module' => $moduleId,
- 'modeid' => $modeId,
- 'modedata' => $modeData,
- 'isclient' => ($isClient ? 1 : 0),
- ));
+ // - Check if module provides runmode config at all
+ $config = self::getModuleConfig($moduleId);
+ if ($config === false)
+ return false;
+ if ($isClient === null) {
+ $isClient = $config->isClient;
}
+ Database::exec('INSERT INTO runmode (machineuuid, module, modeid, modedata, isclient)'
+ . ' VALUES (:uuid, :module, :modeid, :modedata, :isclient)'
+ . ' ON DUPLICATE KEY'
+ . ' UPDATE module = VALUES(module), modeid = VALUES(modeid), modedata = VALUES(modedata), isclient = VALUES(isclient)', array(
+ 'uuid' => $machineuuid,
+ 'module' => $moduleId,
+ 'modeid' => $modeId,
+ 'modedata' => $modeData,
+ 'isclient' => ($isClient ? 1 : 0),
+ ));
return true;
}
/**
* @param string $machineuuid
- * @param bool $detailed whether to return meta data about machine, not just machineuuid
- * @param bool $assoc use machineuuid as array key
+ * @param int $returnData bitfield of data to return
* @return false|array {'machineuuid', 'isclient', 'module', 'modeid', 'modedata',
* <'hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'>}
*/
- public static function getRunMode($machineuuid, $detailed = false)
+ public static function getRunMode($machineuuid, $returnData = self::DATA_MACHINE_DATA)
{
- if ($detailed) {
- $sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen';
+ if ($returnData === true) {
+ $returnData = self::DATA_MACHINE_DATA | self::DATA_DETAILED;
+ }
+ if ($returnData & self::DATA_MACHINE_DATA) {
+ if ($returnData & self::DATA_DETAILED) {
+ $sel = ', m.hostname, m.clientip, m.macaddr, m.locationid, m.lastseen';
+ } else {
+ $sel = '';
+ }
+ $res = Database::queryFirst(
+ "SELECT m.machineuuid, r.isclient, r.module, r.modeid, r.modedata $sel
+ FROM machine m INNER JOIN runmode r USING (machineuuid)
+ WHERE m.machineuuid = :machineuuid LIMIT 1",
+ compact('machineuuid'));
} else {
- $sel = '';
+ $res = Database::queryFirst('SELECT r.machineuuid, r.isclient, r.module, r.modeid, r.modedata
+ FROM runmode r
+ WHERE r.machineuuid = :machineuuid LIMIT 1',
+ compact('machineuuid'));
+ }
+ if ($res === false)
+ return false;
+ if ($returnData & self::DATA_STRINGS) {
+ $module = Module::get($res['module']);
+ if ($module === false) {
+ $res['moduleName'] = $res['module'];
+ } else {
+ $res['moduleName'] = $module->getDisplayName();
+ }
+ $mode = self::getModeName($res['module'], $res['modeid']);
+ if ($mode === false) {
+ $mode = '???? unknown';
+ }
+ $res['modeName'] = $mode;
}
- return Database::queryFirst(
- "SELECT m.machineuuid, r.isclient, r.module, r.modeid, r.modedata $sel
- FROM machine m INNER JOIN runmode r USING (machineuuid)
- WHERE m.machineuuid = :machineuuid LIMIT 1",
- compact('machineuuid'));
+ return $res;
}
/**
@@ -129,7 +160,7 @@ class RunMode
$join = $sel = '';
}
$res = Database::simpleQuery(
- "SELECT r.machineuuid, r.modedata $sel
+ "SELECT r.machineuuid, r.modedata, r.isclient $sel
FROM runmode r $join
WHERE module = :module AND modeid = :modeId",
compact('module', 'modeId'));
@@ -148,6 +179,28 @@ class RunMode
}
/**
+ * Return assoc array of all configured clients.
+ * @param bool $withData also return data field?
+ * @param bool $isClient true = return clients only, false = return non-clients only, null = return both
+ * @return array all the entries from the table
+ */
+ public static function getAllClients($withData = false, $isClient = null)
+ {
+ $xtra = '';
+ if ($withData) {
+ $xtra .= ', modedata';
+ }
+ $res = Database::simpleQuery("SELECT machineuuid, module, modeid, isclient $xtra FROM runmode");
+ $ret = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (!is_null($isClient) && ($row['isclient'] != 0) !== $isClient)
+ continue;
+ $ret[$row['machineuuid']] = $row;
+ }
+ return $ret;
+ }
+
+ /**
* Get display name of a module's mode. If the module doesn't have a getModeName
* method configured, the modeId is simply returned. Otherwise the return value of
* that method is passed through. getModeName by contract should return false if
@@ -168,6 +221,21 @@ class RunMode
return call_user_func($conf->getModeName, $modeId);
}
+ /**
+ * Delete given runmode.
+ *
+ * @param string|\Module $module Module runmode belongs to
+ * @param string $modeId run mode id
+ */
+ public static function deleteMode($module, $modeId)
+ {
+ if (is_object($module)) {
+ $module = $module->getIdentifier();
+ }
+ Database::exec('DELETE FROM runmode WHERE module = :module AND modeid = :modeId',
+ compact('module', 'modeId'));
+ }
+
}
/* *\
@@ -207,6 +275,14 @@ class RunModeModuleConfig
* @var bool If true, config.tgz should not be downloaded by the client
*/
public $noSysconfig = false;
+ /**
+ * @var bool Allow adding and removing machines to this mode via the generic form
+ */
+ public $allowGenericEditor = true;
+ /**
+ * @var string Snippet to construct URL for delete
+ */
+ public $deleteUrlSnippet = false;
public function __construct($file)
{
@@ -220,6 +296,8 @@ class RunModeModuleConfig
$this->loadType($data, 'configHook', 'string');
$this->loadType($data, 'isClient', 'boolean');
$this->loadType($data, 'noSysconfig', 'boolean');
+ $this->loadType($data, 'allowGenericEditor', 'boolean');
+ $this->loadType($data, 'deleteUrlSnippet', 'string');
}
private function loadType($data, $key, $type)
diff --git a/modules-available/runmode/install.inc.php b/modules-available/runmode/install.inc.php
index e2b1ed0f..db7e07f6 100644
--- a/modules-available/runmode/install.inc.php
+++ b/modules-available/runmode/install.inc.php
@@ -12,35 +12,17 @@ $res[] = tableCreate('runmode', "
KEY `module` (`module`,`modeid`)
");
-if (!tableExists('machine')) {
- // Cannot add constraint yet
- $res[] = UPDATE_RETRY;
-} else {
- $c = tableGetContraints('runmode', 'machineuuid', 'machine', 'machineuuid');
- if ($c === false)
- finalResponse(UPDATE_FAILED, 'Cannot get constraints of runmode table: ' . Database::lastError());
- if (empty($c)) {
- $alter = Database::exec('ALTER TABLE runmode ADD FOREIGN KEY (machineuuid) REFERENCES machine (machineuuid)
- ON DELETE CASCADE ON UPDATE CASCADE');
- if ($alter === false)
- finalResponse(UPDATE_FAILED, 'Cannot add machineuuid constraint to runmode table: ' . Database::lastError());
- $res[] = UPDATE_DONE;
- }
-}
+$res[] = tableAddConstraint('runmode', 'machineuuid', 'machine', 'machineuuid',
+ 'ON DELETE CASCADE ON UPDATE CASCADE');
if (!tableHasColumn('runmode', 'isclient')) {
$ret = Database::exec("ALTER TABLE runmode ADD COLUMN isclient bool DEFAULT '1'");
if ($ret === false) {
finalResponse(UPDATE_FAILED, 'Could not add lastchange field');
} elseif ($ret > 0) {
- $ret[] = UPDATE_DONE;
+ $res[] = UPDATE_DONE;
}
}
// Create response for browser
-
-if (in_array(UPDATE_DONE, $res)) {
- finalResponse(UPDATE_DONE, 'Tables created successfully');
-}
-
-finalResponse(UPDATE_NOOP, 'Everything already up to date'); \ No newline at end of file
+responseFromArray($res);
diff --git a/modules-available/runmode/lang/de/messages.json b/modules-available/runmode/lang/de/messages.json
new file mode 100644
index 00000000..21b4b6ae
--- /dev/null
+++ b/modules-available/runmode/lang/de/messages.json
@@ -0,0 +1,9 @@
+{
+ "cannot-edit-module": "Modul {{0}} kann nicht direkt editiert werden",
+ "enabled-removed-save": "{{0}} Rechner gespeichert, {{1}} entfernt",
+ "invalid-modeid": "{{1}} ist kein g\u00fcltiger Betriebsmodus f\u00fcr Modul {{0}}",
+ "machine-not-found": "Rechner {{0}} nicht gefunden",
+ "machine-not-runmode": "Rechner {{0}} hatte keinen speziellen Betriebsmodus aktiviert",
+ "machine-removed": "Rechner {{0}} entfernt",
+ "module-hasnt-runmode": "Modul {{0}} bietet keine speziellen Betriebsmodi"
+} \ No newline at end of file
diff --git a/modules-available/runmode/lang/de/template-tags.json b/modules-available/runmode/lang/de/template-tags.json
new file mode 100644
index 00000000..6b45b82c
--- /dev/null
+++ b/modules-available/runmode/lang/de/template-tags.json
@@ -0,0 +1,10 @@
+{
+ "lang_addNewMachines": "Clients",
+ "lang_assignMachineIntroText": "Definieren Sie hier Clients, die in einem speziellen Betriebsmodus gestartet werden sollen. Sie k\u00f6nnen Rechner anhand der UUID, IP, Hostname oder MAC-Adresse suchen.",
+ "lang_assignRunmodeToMachine": "Betriebsmodus",
+ "lang_confirmDelete": "Wollen Sie den Betriebsmodus f\u00fcr diesen Client entfernen?",
+ "lang_isclient": "Pool-Client",
+ "lang_machine": "Client",
+ "lang_mode": "Modus",
+ "lang_specialMachinesForMode": "Betriebsmodus"
+} \ No newline at end of file
diff --git a/modules-available/runmode/lang/en/messages.json b/modules-available/runmode/lang/en/messages.json
new file mode 100644
index 00000000..1985ca66
--- /dev/null
+++ b/modules-available/runmode/lang/en/messages.json
@@ -0,0 +1,9 @@
+{
+ "cannot-edit-module": "Module {{0}} cannot be edited directly",
+ "enabled-removed-save": "Saved {{0}} clients, deleted {{1}}",
+ "invalid-modeid": "Module {{0}} doesn't provide mode {{1}}",
+ "machine-not-found": "Client {{0}} not found",
+ "machine-not-runmode": "No special mode of operation configured for client {{0}}",
+ "machine-removed": "Removed client {{0}}",
+ "module-hasnt-runmode": "Module {{0}} doesn't supply any special mode of operation"
+} \ No newline at end of file
diff --git a/modules-available/runmode/lang/en/template-tags.json b/modules-available/runmode/lang/en/template-tags.json
new file mode 100644
index 00000000..43fd3da5
--- /dev/null
+++ b/modules-available/runmode/lang/en/template-tags.json
@@ -0,0 +1,10 @@
+{
+ "lang_addNewMachines": "Clients",
+ "lang_assignMachineIntroText": "Define the clients which should start a special runmode configuration. You can search for clients by UUID, IP address, host name or MAC address.",
+ "lang_assignRunmodeToMachine": "Runmode",
+ "lang_confirmDelete": "Do you want to delete the runmode for this client?",
+ "lang_isclient": "Pool-Client",
+ "lang_machine": "Client",
+ "lang_mode": "Mode",
+ "lang_specialMachinesForMode": "Runmode"
+} \ No newline at end of file
diff --git a/modules-available/runmode/page.inc.php b/modules-available/runmode/page.inc.php
index 05f32f81..e26950d0 100644
--- a/modules-available/runmode/page.inc.php
+++ b/modules-available/runmode/page.inc.php
@@ -26,29 +26,58 @@ class Page_RunMode extends Page
$machines = array_filter(Request::post('machines', [], 'array'), 'is_string');
$module = Request::post('module', false, 'string');
$modeId = Request::post('modeid', false, 'string');
- // TODO Validate
+ $modConfig = RunMode::getModuleConfig($module);
+ if ($modConfig === false) {
+ Message::addError('runmode.module-hasnt-runmode', $module);
+ return;
+ }
+ if (!$modConfig->allowGenericEditor) {
+ Message::addError('runmode.cannot-edit-module', $module);
+ return;
+ }
+ $test = RunMode::getModeName($module, $modeId);
+ if ($test === false) {
+ Message::addError('runmode.invalid-modeid', $module, $modeId);
+ return;
+ }
$active = 0;
foreach ($machines as $machine) {
$ret = RunMode::setRunMode($machine, $module, $modeId, null, null);
if ($ret) {
$active++;
} else {
- Message::addError('invalid-module-or-machine', $module, $machine);
+ Message::addError('runmode.machine-not-found', $machine);
}
}
$deleted = Database::exec('DELETE FROM runmode
WHERE module = :module AND modeid = :modeId AND machineuuid NOT IN (:machines)',
compact('module', 'modeId', 'machines'));
- Message::addError('enabled-removed-save', $active, $deleted);
- $redirect = Request::post('redirect', false, 'string');
- if ($redirect !== false) {
- Util::redirect($redirect);
- }
- Util::redirect('?do=runmode&module=' . $module . '&modeid=' . $modeId);
+ Message::addSuccess('runmode.enabled-removed-save', $active, $deleted);
+ Util::redirect('?do=runmode&module=' . $module . '&modeid=' . $modeId, true);
} elseif ($action === 'delete-machine') {
$machineuuid = Request::post('machineuuid', false, 'string');
if ($machineuuid === false) {
-
+ Message::addError('main.parameter-missing', 'machineuuid');
+ return;
+ }
+ $mode = RunMode::getRunMode($machineuuid);
+ if ($mode === false) {
+ Message::addError('runmode.machine-not-found', $machineuuid);
+ return;
+ }
+ $modConfig = RunMode::getModuleConfig($mode['module']);
+ if ($modConfig === false) {
+ Message::addError('module-hasnt-runmode', $mode['moduleName']);
+ return;
+ }
+ if (!$modConfig->allowGenericEditor) {
+ Message::addError('runmode.cannot-edit-module', $mode['moduleName']);
+ return;
+ }
+ if (RunMode::setRunMode($machineuuid, null, null)) {
+ Message::addSuccess('machine-removed', $machineuuid);
+ } else {
+ Message::addWarning('machine-not-runmode', $machineuuid);
}
}
}
@@ -76,6 +105,10 @@ class Page_RunMode extends Page
Message::addError('module-hasnt-runmode', $moduleId);
Util::redirect('?do=runmode');
}
+ if (!$config->allowGenericEditor) {
+ Message::addError('runmode.cannot-edit-module', $moduleId);
+ return;
+ }
// Given modeId?
$modeId = Request::get('modeid', false, 'string');
if ($modeId !== false) {
@@ -118,10 +151,13 @@ class Page_RunMode extends Page
$module = Module::get($moduleId);
if ($module === false)
continue;
+ $config = RunMode::getModuleConfig($moduleId);
Render::addTemplate('module-machine-list', array(
'list' => $rows['list'],
'modulename' => $module->getDisplayName(),
'module' => $moduleId,
+ 'canedit' => $config !== false && $config->allowGenericEditor && $config->deleteUrlSnippet === false,
+ 'deleteUrl' => $config->deleteUrlSnippet
));
}
}
@@ -135,7 +171,7 @@ class Page_RunMode extends Page
$moduleId = $module->getIdentifier();
$modeName = RunMode::getModeName($moduleId, $modeId);
if ($modeName === false) {
- Message::addError('invalid-modeid', $modeId);
+ Message::addError('invalid-modeid', $moduleId, $modeId);
Util::redirect('?do=runmode');
}
Render::addTemplate('machine-selector', [
diff --git a/modules-available/runmode/templates/machine-selector.html b/modules-available/runmode/templates/machine-selector.html
index d3ff7378..7f37f5a2 100644
--- a/modules-available/runmode/templates/machine-selector.html
+++ b/modules-available/runmode/templates/machine-selector.html
@@ -108,11 +108,22 @@ function renderMachineSelected(item, escape) {
}
document.addEventListener('DOMContentLoaded', function () {
+ Selectize.define("no_bs", function (options) {
+ var original = this.deleteSelection;
+ this.deleteSelection = (function() {
+ return function (e) {
+ if (!e || e.keyCode !== 8) {
+ return original.apply(this, arguments);
+ }
+ return false;
+ };
+ })();
+ });
var old = {{{machines}}} || [];
var $box = $('#machine-sel').selectize({
options: old,
items: old.map(function(x) { return x.machineuuid; }),
- plugins: ["remove_button"],
+ plugins: ["remove_button", "no_bs"],
valueField: 'machineuuid',
searchField: "combined",
openOnFocus: false,
@@ -121,7 +132,7 @@ document.addEventListener('DOMContentLoaded', function () {
load: loadMachines,
maxItems: null,
sortField: 'hostname',
- sortDirection: 'asc'
+ sortDirection: 'asc',
});
});
diff --git a/modules-available/runmode/templates/module-machine-list.html b/modules-available/runmode/templates/module-machine-list.html
index a749a4a7..61bbbad9 100644
--- a/modules-available/runmode/templates/module-machine-list.html
+++ b/modules-available/runmode/templates/module-machine-list.html
@@ -3,7 +3,7 @@
<a href="?do={{module}}">{{modulename}}</a>
</h2>
-<form method="post" action="?do=runmode" onclick="return confirm('{{lang_confirmDelete}}')">
+<form method="post" action="?do=runmode" onsubmit="return confirm('{{lang_confirmDelete}}')">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="delete-machine">
<table class="table">
@@ -35,8 +35,16 @@
{{/isclient}}
</td>
<td class="text-center">
- <button class="btn btn-danger btn-sm" name="machineuuid" value="{{machineuuid}}"><span
- class="glyphicon glyphicon-remove"></span></button>
+ {{#canedit}}
+ <button type="submit" class="btn btn-danger btn-sm" name="machineuuid" value="{{machineuuid}}">
+ <span class="glyphicon glyphicon-trash"></span>
+ </button>
+ {{/canedit}}
+ {{#deleteUrl}}
+ <a class="btn btn-danger btn-sm" href="?do={{module}}&amp;{{deleteUrl}}{{modeid}}">
+ <span class="glyphicon glyphicon-trash"></span>
+ </a>
+ {{/deleteUrl}}
</td>
</tr>
{{/list}}
diff --git a/modules-available/serversetup-bwlp/lang/de/module.json b/modules-available/serversetup-bwlp/lang/de/module.json
index eb777343..da71d558 100644
--- a/modules-available/serversetup-bwlp/lang/de/module.json
+++ b/modules-available/serversetup-bwlp/lang/de/module.json
@@ -1,4 +1,4 @@
{
- "module_name": "PXE\/Boot",
+ "module_name": "iPXE \/ Boot Menu",
"page_title": "PXE- und Boot-Einstellungen"
} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/lang/de/template-tags.json b/modules-available/serversetup-bwlp/lang/de/template-tags.json
index 537b7761..f80febb0 100644
--- a/modules-available/serversetup-bwlp/lang/de/template-tags.json
+++ b/modules-available/serversetup-bwlp/lang/de/template-tags.json
@@ -21,6 +21,7 @@
"lang_menuCustomHint3": "und w\u00e4hlen Sie als Standard-Bootverhalten ebenfalls custom.",
"lang_menuDisplayTime": "Anzeigedauer des Men\u00fcs",
"lang_menuGeneration": "Erzeugen des Bootmen\u00fcs",
+ "lang_moduleHeading": "iPXE / Boot Menu",
"lang_seconds": "Sekunden",
"lang_set": "Setzen",
"lang_usbImage": "USB-Image",
diff --git a/modules-available/serversetup-bwlp/lang/en/template-tags.json b/modules-available/serversetup-bwlp/lang/en/template-tags.json
index 533dacba..1948718b 100644
--- a/modules-available/serversetup-bwlp/lang/en/template-tags.json
+++ b/modules-available/serversetup-bwlp/lang/en/template-tags.json
@@ -8,12 +8,12 @@
"lang_bootMenuCreate": "Create Boot Menu",
"lang_chooseIP": "Please select the IP address that the client server will use to boot.",
"lang_customEntry": "Custom entry",
- "lang_downloadImage": "Download USB image",
+ "lang_downloadImage": "Download USB Image",
"lang_downloadRufus": "Download Rufus",
"lang_example": "Example",
"lang_generationFailed": "Could not generate boot menu. The bwLehrpool-System might not work properly. If you can't fix the problem, please report the error message above to the bwLehrpool project.",
"lang_localHDD": "Local HDD",
- "lang_masterPassword": "Master password",
+ "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.",
@@ -21,6 +21,7 @@
"lang_menuCustomHint3": "and select as the default boot behavior custom as well.",
"lang_menuDisplayTime": "Menu Display Time",
"lang_menuGeneration": "Generating boot menu...",
+ "lang_moduleHeading": "iPXE / Boot Menu",
"lang_seconds": "Seconds",
"lang_set": "Set",
"lang_usbImage": "USB image",
diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php
index 9d7d11ac..a8d29d6e 100644
--- a/modules-available/serversetup-bwlp/page.inc.php
+++ b/modules-available/serversetup-bwlp/page.inc.php
@@ -44,7 +44,7 @@ class Page_ServerSetup extends Page
protected function doRender()
{
-
+ Render::addTemplate("heading");
$taskid = Request::any('taskid');
if ($taskid !== false && Taskmanager::isTask($taskid)) {
Render::addTemplate('ipxe_update', array('taskid' => $taskid));
diff --git a/modules-available/serversetup-bwlp/templates/heading.html b/modules-available/serversetup-bwlp/templates/heading.html
new file mode 100644
index 00000000..d68360f1
--- /dev/null
+++ b/modules-available/serversetup-bwlp/templates/heading.html
@@ -0,0 +1 @@
+<h1>{{lang_moduleHeading}}</h1> \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp/templates/ipxe.html
index e1ecb6f1..bb776dca 100644
--- a/modules-available/serversetup-bwlp/templates/ipxe.html
+++ b/modules-available/serversetup-bwlp/templates/ipxe.html
@@ -15,9 +15,18 @@
<div class="form-group">
<strong>{{lang_bootBehavior}}</strong>
- <div><label class="radio-inline"><input type="radio" name="defaultentry" value="net" {{active-net}}> bwLehrpool</label></div>
- <div><label class="radio-inline"><input type="radio" name="defaultentry" value="hdd" {{active-hdd}}> {{lang_localHDD}}</label></div>
- <div><label class="radio-inline"><input type="radio" name="defaultentry" value="custom" {{active-custom}}> {{lang_customEntry}} (&quot;custom&quot;)</label></div>
+ <div class="radio">
+ <input type="radio" name="defaultentry" value="net" {{active-net}} id="id-net">
+ <label for="id-net">bwLehrpool</label>
+ </div>
+ <div class="radio">
+ <input type="radio" name="defaultentry" value="hdd" {{active-hdd}} id="id-hdd">
+ <label for="id-hdd">{{lang_localHDD}}</label>
+ </div>
+ <div class="radio">
+ <input type="radio" name="defaultentry" value="custom" {{active-custom}} id="id-custom">
+ <label for="id-custom">{{lang_customEntry}} (&quot;custom&quot;)</label>
+ </div>
</div>
<div class="form-group">
@@ -43,7 +52,8 @@
</div>
<div class="panel-footer">
- <div class="pull-right">
+ <button class="btn btn-primary pull-right" name="action" value="ipxe">{{lang_bootMenuCreate}}</button>
+ <div>
<div class="btn-group" role="group">
<a class="btn btn-default" href="?do=ServerSetup&amp;action=getimage">
<span class="glyphicon glyphicon-download-alt"></span>
@@ -52,7 +62,6 @@
<span class="btn btn-default" data-toggle="modal" data-target="#help-usbimg"><span class="glyphicon glyphicon-question-sign"></span></span>
</div>
</div>
- <button class="btn btn-primary" name="action" value="ipxe">{{lang_bootMenuCreate}}</button>
</div>
</div>
</form>
@@ -60,7 +69,10 @@
<div class="modal fade" id="help-custom" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_menuCustom}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ {{lang_menuCustom}}
+ </div>
<div class="modal-body">
{{lang_menuCustomHint1}}
<br>{{lang_example}}:
@@ -73,7 +85,6 @@
{{lang_menuCustomHint2}} LABEL <strong>custom</strong>
{{lang_menuCustomHint3}}
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
@@ -81,7 +92,10 @@
<div class="modal fade" id="help-usbimg" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_usbImage}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ {{lang_usbImage}}
+ </div>
<div class="modal-body">
<p>{{lang_usbImgHelp}}</p>
<p>
@@ -98,7 +112,6 @@
<a href="https://rufus.akeo.ie/#download">{{lang_downloadRufus}}</a>
</p>
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div> \ No newline at end of file
diff --git a/modules-available/session/lang/en/messages.json b/modules-available/session/lang/en/messages.json
index de7ee4f3..a7b14d61 100644
--- a/modules-available/session/lang/en/messages.json
+++ b/modules-available/session/lang/en/messages.json
@@ -3,5 +3,5 @@
"pass-too-short": "Password too short",
"password-changed": "Password successfully changed",
"password-unchanged": "Password unchanged",
- "wrong-password": "Wrong passwort"
-} \ No newline at end of file
+ "wrong-password": "Wrong password"
+}
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php
index 566b1d69..a614658a 100644
--- a/modules-available/statistics/api.inc.php
+++ b/modules-available/statistics/api.inc.php
@@ -16,7 +16,7 @@ if ($type{0} === '~') {
$uuid = Request::post('uuid', '', 'string');
if (strlen($uuid) !== 36) die("Invalid UUID.\n");
$macaddr = Request::post('macaddr', '', 'string');
- if (!empty($macaddr) && substr($uuid, 0, 16) === '000000000000000-') {
+ if (!empty($macaddr) && substr($uuid, 0, 16) === '000000000000001-') {
// Override uuid if the mac is known and unique
$res = Database::simpleQuery('SELECT machineuuid FROM machine WHERE macaddr = :macaddr AND machineuuid <> :uuid', compact('macaddr', 'uuid'));
$override = false;
@@ -34,7 +34,7 @@ if ($type{0} === '~') {
// External mode of operation?
$mode = Request::post('mode', false, 'string');
$NOW = time();
- $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid));
+ $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid));
if ($old !== false) {
settype($old['logintime'], 'integer');
settype($old['lastseen'], 'integer');
@@ -43,7 +43,7 @@ if ($type{0} === '~') {
// Handle event type
if ($mode === false && $type === '~poweron') {
// Poweron & hw stats
- $uptime = Request::post('uptime', '', 'integer');
+ $uptime = Request::post('uptime', 0, 'integer');
if (strlen($macaddr) > 17) die("Invalid MAC.\n");
if ($uptime < 0 || $uptime > 4000000) die("Implausible uptime.\n");
$realcores = Request::post('realcores', 0, 'integer');
@@ -64,6 +64,65 @@ if ($type{0} === '~') {
$hostname = '';
}
$data = Request::post('data', '', 'string');
+ // Prepare insert/update to machine table
+ $new = array(
+ 'uuid' => $uuid,
+ 'macaddr' => $macaddr,
+ 'clientip' => $ip,
+ 'lastseen' => $NOW,
+ 'lastboot' => $NOW - $uptime,
+ 'realcores' => $realcores,
+ 'mbram' => $mbram,
+ 'kvmstate' => $kvmstate,
+ 'cpumodel' => $cpumodel,
+ 'systemmodel'=> $systemmodel,
+ 'id44mb' => $id44mb,
+ 'badsectors' => $badsectors,
+ 'data' => $data,
+ 'state' => 'IDLE',
+ );
+ // Create/update machine entry
+ if ($old === false) {
+ $new['firstseen'] = $NOW;
+ $new['hostname'] = $hostname;
+ $res = Database::exec('INSERT INTO machine '
+ . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,'
+ . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname, state) VALUES '
+ . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram,"
+ . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname, :state)', $new, true);
+ if ($res === false) {
+ die("Concurrent insert, ignored. (RESULT=0)\n");
+ }
+ } else {
+ // Update
+ $moresql = ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : '');
+ if (!empty($hostname)) {
+ $new['hostname'] = $hostname;
+ $moresql .= ' hostname = :hostname,';
+ }
+ $new['oldstate'] = $old['state'];
+ $new['oldlastseen'] = $old['lastseen'];
+ $res = Database::exec('UPDATE machine SET '
+ . ' macaddr = :macaddr,'
+ . ' clientip = :clientip,'
+ . ' lastseen = :lastseen,'
+ . ' lastboot = :lastboot,'
+ . $moresql
+ . ' realcores = :realcores,'
+ . ' mbram = :mbram,'
+ . ' kvmstate = :kvmstate,'
+ . ' cpumodel = :cpumodel,'
+ . ' systemmodel = :systemmodel,'
+ . ' id44mb = :id44mb,'
+ . ' badsectors = :badsectors,'
+ . ' data = :data,'
+ . ' state = :state '
+ . " WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", $new);
+ if ($res === 0) {
+ die("Concurrent update, ignored. (RESULT=0)\n");
+ }
+ }
+ // Maybe log old crashed session
if ($uptime < 120) {
// See if we have a lingering session, create statistic entry if so
if ($old !== false && $old['logintime'] !== 0) {
@@ -93,49 +152,17 @@ if ($type{0} === '~') {
}
}
}
- // Create/update machine entry
- Database::exec('INSERT INTO machine '
- . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,'
- . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname) VALUES '
- . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram,"
- . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname)'
- . ' ON DUPLICATE KEY UPDATE'
- . ' macaddr = VALUES(macaddr),'
- . ' clientip = VALUES(clientip),'
- . ' lastseen = VALUES(lastseen),'
- . ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : '')
- . ' lastboot = VALUES(lastboot),'
- . ' realcores = VALUES(realcores),'
- . ' mbram = VALUES(mbram),'
- . ' kvmstate = VALUES(kvmstate),'
- . ' cpumodel = VALUES(cpumodel),'
- . ' systemmodel = VALUES(systemmodel),'
- . ' id44mb = VALUES(id44mb),'
- . ' badsectors = VALUES(badsectors),'
- . ' data = VALUES(data),'
- . " hostname = If(VALUES(hostname) = '', hostname, VALUES(hostname))", array(
- 'uuid' => $uuid,
- 'macaddr' => $macaddr,
- 'clientip' => $ip,
- 'firstseen' => $NOW,
- 'lastseen' => $NOW,
- 'lastboot' => $NOW - $uptime,
- 'realcores' => $realcores,
- 'mbram' => $mbram,
- 'kvmstate' => $kvmstate,
- 'cpumodel' => $cpumodel,
- 'systemmodel'=> $systemmodel,
- 'id44mb' => $id44mb,
- 'badsectors' => $badsectors,
- 'data' => $data,
- 'hostname' => $hostname,
- ));
if (($old === false || $old['clientip'] !== $ip) && Module::isAvailable('locations')) {
// New, or ip changed (dynamic pool?), update subnetlicationid
Location::updateMapIpToLocation($uuid, $ip);
}
+ // Check for suspicious hardware changes
+ if ($old !== false) {
+ checkHardwareChange($old, $new);
+ }
+
// Write statistics data
} else if ($type === '~runstate') {
@@ -147,7 +174,7 @@ if ($type{0} === '~') {
die("Address changed.\n");
}
$used = Request::post('used', 0, 'integer');
- if ($old['lastboot'] === 0 && $NOW - $old['lastseen'] > 300) {
+ if ($old['state'] === 'OFFLINE' && $NOW - $old['lastseen'] > 600) {
$strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), ';
} else {
$strUpdateBoottime = '';
@@ -161,31 +188,42 @@ if ($type{0} === '~') {
}
$old['logintime'] = 0;
}
- $old['lastboot'] = 0;
}
// Figure out what's happening - state changes
- if ($used === 0 && $old['logintime'] !== 0) {
+ $params = array(
+ 'uuid' => $uuid,
+ 'oldlastseen' => $old['lastseen'],
+ 'oldstate' => $old['state'],
+ );
+ if ($used === 0 && $old['state'] !== 'IDLE') {
// Is not in use, was in use before
$sessionLength = $NOW - $old['logintime'];
- Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),'
+ $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),'
. $strUpdateBoottime
- . ' logintime = 0, currentuser = NULL WHERE machineuuid = :uuid', array('uuid' => $uuid));
- } elseif ($used === 1 && $old['logintime'] === 0) {
+ . " logintime = 0, currentuser = NULL, state = 'IDLE' "
+ . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate",
+ $params);
+ } elseif ($used === 1 && $old['state'] !== 'OCCUPIED') {
// Machine is in use, was free before
if ($sessionLength !== 0 || $old['logintime'] === 0) {
// This event is a start of a new session, rather than an update
- Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),'
+ $params['user'] = Request::post('user', null, 'string');
+ $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),'
. $strUpdateBoottime
- . ' logintime = UNIX_TIMESTAMP(), currentuser = :user WHERE machineuuid = :uuid', array(
- 'uuid' => $uuid,
- 'user' => Request::post('user', null, 'string'),
- ));
+ . " logintime = UNIX_TIMESTAMP(), currentuser = :user, currentsession = NULL, state = 'OCCUPIED' "
+ . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate", $params);
+ } else {
+ $res = 0;
}
} else {
// Nothing changed, simple lastseen update
- Database::exec('UPDATE machine SET '
+ $res = Database::exec('UPDATE machine SET '
. $strUpdateBoottime
- . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid', array('uuid' => $uuid));
+ . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate', $params);
+ }
+ // Did we update, or was there a concurrent update?
+ if ($res === 0) {
+ die("Concurrent update, ignored. (RESULT=0)\n");
}
// 9) Log last session length if applicable
if ($mode === false && $sessionLength > 0 && $sessionLength < 86400*2 && $old['logintime'] !== 0) {
@@ -215,8 +253,11 @@ if ($type{0} === '~') {
));
}
}
- Database::exec('UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), lastboot = 0 WHERE machineuuid = :uuid', array('uuid' => $uuid));
+ Database::exec("UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), state = 'OFFLINE'
+ WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen",
+ array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state']));
} elseif ($mode === false && $type === '~screens') {
+ if ($old === false) die("Unknown machine.\n");
$screens = Request::post('screen', false, 'array');
if (is_array($screens)) {
// `devicetype`, `devicename`, `subid`, `machineuuid`
@@ -287,6 +328,47 @@ if ($type{0} === '~') {
}
}
+ } else if ($type === '~suspend') {
+ // Client entering suspend
+ if ($old === false) die("Unknown machine.\n");
+ if ($old['clientip'] !== $ip) {
+ EventLog::warning("[suspend] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)");
+ die("Address changed.\n");
+ }
+ if ($NOW - $old['lastseen'] < 610 && $old['state'] !== 'OFFLINE') {
+ Database::exec("UPDATE machine SET lastseen = UNIX_TIMESTAMP(), state = 'STANDBY'
+ WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen",
+ array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state']));
+ } else {
+ EventLog::info("[suspend] Client $uuid reported switch to standby when it wasn't powered on first. Was: " . $old['state']);
+ }
+ } else if ($type === '~resume') {
+ // Waking up from suspend
+ if ($old === false) die("Unknown machine.\n");
+ if ($old['clientip'] !== $ip) {
+ EventLog::info("[resume] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip), allowed on resume.");
+ }
+ if ($old['state'] === 'STANDBY') {
+ $res = Database::exec("UPDATE machine SET state = 'IDLE', clientip = :ip, lastseen = UNIX_TIMESTAMP()
+ WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen",
+ array('uuid' => $uuid, 'ip' => $ip, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state']));
+ // Write standby period length to statistic table
+ if ($mode === false && $res > 0 && $old['lastseen'] !== 0) {
+ $lastSeen = $old['lastseen'];
+ $duration = $NOW - $lastSeen;
+ if ($duration > 500 && $duration < 86400 * 14) {
+ Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)'
+ . " VALUES (:suspend, '~suspend-length', :uuid, :clientip, '', :length)", array(
+ 'suspend' => $lastSeen,
+ 'uuid' => $uuid,
+ 'clientip' => $ip,
+ 'length' => $duration
+ ));
+ }
+ }
+ } else {
+ EventLog::info("[resume] Client $uuid reported wakeup from standby when it wasn't logged as being in standby. Was: " . $old['state']);
+ }
} else {
die("INVALID ACTION '$type'\n");
}
@@ -315,6 +397,7 @@ function writeStatisticLog($type, $username, $data)
));
}
+
// For backwards compat, we require the . prefix
if ($type{0} === '.') {
if ($type === '.vmchooser-session') {
@@ -336,4 +419,22 @@ if ($type{0} === '.') {
}
}
+/**
+ * @param array $old row from DB with client's old data
+ * @param array $new new data to be written
+ */
+function checkHardwareChange($old, $new)
+{
+ if ($new['mbram'] !== 0) {
+ if ($new['mbram'] + 1000 < $old['mbram']) {
+ $ram1 = round($old['mbram'] / 512) / 2;
+ $ram2 = round($new['mbram'] / 512) / 2;
+ EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM decreased from {$ram1}GB to {$ram2}GB");
+ }
+ if (!empty($old['cpumodel']) && !empty($new['cpumodel']) && $new['cpumodel'] !== $old['cpumodel']) {
+ EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): CPU changed from '{$old['cpumodel']}' to '{$new['cpumodel']}'");
+ }
+ }
+}
+
echo "OK.\n";
diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php
index 94c65248..4df7b0d4 100644
--- a/modules-available/statistics/hooks/cron.inc.php
+++ b/modules-available/statistics/hooks/cron.inc.php
@@ -1,18 +1,44 @@
<?php
-Database::exec("DELETE FROM statistic WHERE (UNIX_TIMESTAMP() - dateline) > 86400 * 190");
-Database::exec("DELETE FROM machine WHERE (UNIX_TIMESTAMP() - lastseen) > 86400 * 365");
-
-function logstats() {
+function logstats()
+{
$NOW = time();
$cutoff = $NOW - 86400 * 30;
- $online = $NOW - 610;
- $known = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $cutoff");
- $on = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online");
- $used = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online AND logintime <> 0");
+ $join = $where = '';
+ if (Module::get('runmode') !== false) {
+ $join = 'LEFT JOIN runmode r USING (machineuuid)';
+ $where = 'AND (r.isclient IS NULL OR r.isclient <> 0)';
+ }
+ $known = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.lastseen > $cutoff $where");
+ $on = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state IN ('IDLE', 'OCCUPIED') $where");
+ $used = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state = 'OCCUPIED' $where");
Database::exec("INSERT INTO statistic (dateline, typeid, clientip, username, data) VALUES (:now, '~stats', '', '', :vals)", array(
'now' => $NOW,
'vals' => $known['val'] . '#' . $on['val'] . '#' . $used['val'],
));
}
+
+function state_cleanup()
+{
+ // Fix online state of machines that crashed
+ $standby = time() - 86400 * 2; // Reset standby machines after two days
+ $on = time() - 610; // Reset others after ~10 minutes
+ Database::exec("UPDATE machine SET state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'");
+}
+
+state_cleanup();
+
logstats();
+
+if (mt_rand(1, 10) === 1) {
+ Database::exec("DELETE FROM statistic WHERE (UNIX_TIMESTAMP() - 86400 * 190) > dateline");
+ if (mt_rand(1, 100) === 1) {
+ Database::exec("OPTIMIZE TABLE statistic");
+ }
+}
+if (mt_rand(1, 10) === 1) {
+ Database::exec("DELETE FROM machine WHERE (UNIX_TIMESTAMP() - 86400 * 365) > lastseen");
+ if (mt_rand(1, 100) === 1) {
+ Database::exec("OPTIMIZE TABLE machine");
+ }
+}
diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php
index 0afce572..be6df752 100644
--- a/modules-available/statistics/inc/filter.inc.php
+++ b/modules-available/statistics/inc/filter.inc.php
@@ -29,7 +29,7 @@ class Filter
$addendum = '';
/* check if we have to do some parsing*/
- if (Page_Statistics::$columns[$this->column]['type'] == 'date') {
+ if (Page_Statistics::$columns[$this->column]['type'] === 'date') {
$args[$key] = strtotime($this->argument);
} else {
$args[$key] = $this->argument;
@@ -180,21 +180,18 @@ class StateFilter extends Filter
{
public function __construct($operator, $argument)
{
- $this->operator = $operator;
- $this->argument = $argument;
+ parent::__construct(null, $operator, $argument);
}
public function whereClause(&$args, &$joins)
{
+ $map = [ 'on' => ['IDLE', 'OCCUPIED'], 'off' => ['OFFLINE'], 'idle' => ['IDLE'], 'occupied' => ['OCCUPIED'], 'standby' => ['STANDBY'] ];
$neg = $this->operator == '!=' ? 'NOT ' : '';
- if ($this->argument === 'on') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() ) ";
- } elseif ($this->argument === 'off') {
- return " $neg (lastseen + 600 < UNIX_TIMESTAMP() ) ";
- } elseif ($this->argument === 'idle') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ) ";
- } elseif ($this->argument === 'occupied') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ) ";
+ if (array_key_exists($this->argument, $map)) {
+ global $unique_key;
+ $key = $this->column . '_arg' . ($unique_key++);
+ $args[$key] = $map[$this->argument];
+ return " machine.state $neg IN ( :$key ) ";
} else {
Message::addError('invalid-filter-argument', 'state', $this->argument);
return ' 1';
@@ -211,13 +208,22 @@ class LocationFilter extends Filter
public function whereClause(&$args, &$joins)
{
+ $recursive = (substr($this->operator, -1) === '~');
+ $this->operator = str_replace('~', '=', $this->operator);
+
settype($this->argument, 'int');
+ $neg = $this->operator === '=' ? '' : 'NOT';
if ($this->argument === 0) {
- $neg = $this->operator === '=' ? '' : 'NOT';
return "machine.locationid IS $neg NULL";
} else {
- $args['lid'] = $this->argument;
- return "machine.locationid {$this->operator} :lid";
+ global $unique_key;
+ $key = $this->column . '_arg' . ($unique_key++);
+ if ($recursive) {
+ $args[$key] = array_keys(Location::getRecursiveFlat($this->argument));
+ } else {
+ $args[$key] = $this->argument;
+ }
+ return "machine.locationid $neg IN (:$key)";
}
}
}
@@ -236,3 +242,20 @@ class SubnetFilter extends Filter
}
}
+class IsClientFilter extends Filter
+{
+ public function __construct($argument)
+ {
+ parent::__construct(null, null, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ if ($this->argument) {
+ $joins[] = ' LEFT JOIN runmode USING (machineuuid)';
+ return "(runmode.isclient <> 0 OR runmode.isclient IS NULL)";
+ }
+ $joins[] = ' INNER JOIN runmode USING (machineuuid)';
+ return "runmode.isclient = 0";
+ }
+}
diff --git a/modules-available/statistics/inc/filterset.inc.php b/modules-available/statistics/inc/filterset.inc.php
index c73feeef..25c5c8fa 100644
--- a/modules-available/statistics/inc/filterset.inc.php
+++ b/modules-available/statistics/inc/filterset.inc.php
@@ -2,6 +2,9 @@
class FilterSet
{
+ /**
+ * @var \Filter[]
+ */
private $filters;
private $sortDirection;
private $sortColumn;
@@ -39,7 +42,7 @@ class FilterSet
$where .= $sep . $filter->whereClause($args, $joins);
}
}
- $join = implode('', array_unique($joins));
+ $join = implode(' ', array_unique($joins));
$col = $this->sortColumn;
$isMapped = array_key_exists('map_sort', Page_Statistics::$columns[$col]);
@@ -72,4 +75,13 @@ class FilterSet
{
return $this->sortColumn;
}
+
+ public function filterNonClients()
+ {
+ if (Module::get('runmode') === false)
+ return;
+ // Runmode module exists, add filter
+ $this->filters[] = new IsClientFilter(true);
+ }
+
}
diff --git a/modules-available/statistics/inc/machine.inc.php b/modules-available/statistics/inc/machine.inc.php
index 8cb5e884..8605749b 100644
--- a/modules-available/statistics/inc/machine.inc.php
+++ b/modules-available/statistics/inc/machine.inc.php
@@ -51,6 +51,11 @@ class Machine
public $logintime;
/**
+ * @var string state of machine (OFFLINE, IDLE, OCCUPIED, STANDBY)
+ */
+ public $state;
+
+ /**
* @var string json data of position inside room (if any), null/empty otherwise
*/
public $position;
diff --git a/modules-available/statistics/inc/statistics.inc.php b/modules-available/statistics/inc/statistics.inc.php
index 1c9ebf07..2500f16f 100644
--- a/modules-available/statistics/inc/statistics.inc.php
+++ b/modules-available/statistics/inc/statistics.inc.php
@@ -7,17 +7,12 @@ class Statistics
private static $machineFields = false;
- /**
- * @param string $machineuuid
- * @param int $returnData
- * @return \Machine|false
- */
- public static function getMachine($machineuuid, $returnData)
+ private static function initFields($returnData)
{
if (self::$machineFields === false) {
$r = new ReflectionClass('Machine');
$props = $r->getProperties(ReflectionProperty::IS_PUBLIC);
- self::$machineFields = array_flip(array_map(function($e) { return $e->getName(); }, $props));
+ self::$machineFields = array_flip(array_map(function(/* @var ReflectionProperty $e */ $e) { return $e->getName(); }, $props));
}
if ($returnData === Machine::NO_DATA) {
unset(self::$machineFields['data']);
@@ -26,8 +21,19 @@ class Statistics
} else {
Util::traceError('Invalid $returnData option passed');
}
- $fields = implode(',', array_keys(self::$machineFields));
- $row = Database::queryFirst("SELECT * FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid'));
+ return implode(',', array_keys(self::$machineFields));
+ }
+
+ /**
+ * @param string $machineuuid
+ * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ...
+ * @return \Machine|false
+ */
+ public static function getMachine($machineuuid, $returnData)
+ {
+ $fields = self::initFields($returnData);
+
+ $row = Database::queryFirst("SELECT $fields FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid'));
if ($row === false)
return false;
$m = new Machine();
@@ -37,4 +43,31 @@ class Statistics
return $m;
}
+ /**
+ * @param string $ip
+ * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ...
+ * @param string $sort something like 'lastseen ASC' - not sanitized, don't pass user input!
+ * @return \Machine[] list of matches
+ */
+ public static function getMachinesByIp($ip, $returnData, $sort = false)
+ {
+ $fields = self::initFields($returnData);
+
+ if ($sort === false) {
+ $sort = '';
+ } else {
+ $sort = "ORDER BY $sort";
+ }
+ $res = Database::simpleQuery("SELECT $fields FROM machine WHERE clientip = :ip $sort", compact('ip'));
+ $list = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $m = new Machine();
+ foreach ($row as $key => $val) {
+ $m->{$key} = $val;
+ }
+ $list[] = $m;
+ }
+ return $list;
+ }
+
}
diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php
index bfa342c4..4e2dfcca 100644
--- a/modules-available/statistics/install.inc.php
+++ b/modules-available/statistics/install.inc.php
@@ -36,6 +36,7 @@ $res[] = $machineCreate = tableCreate('machine', "
`logintime` int(10) unsigned NOT NULL,
`position` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`lastboot` int(10) unsigned NOT NULL,
+ `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE',
`realcores` smallint(5) unsigned NOT NULL,
`mbram` int(10) unsigned NOT NULL,
`kvmstate` enum('UNKNOWN','UNSUPPORTED','DISABLED','ENABLED') NOT NULL,
@@ -51,6 +52,7 @@ $res[] = $machineCreate = tableCreate('machine', "
PRIMARY KEY (`machineuuid`),
KEY `macaddr` (`macaddr`),
KEY `clientip` (`clientip`),
+ KEY `state` (`state`),
KEY `realcores` (`realcores`),
KEY `mbram` (`mbram`),
KEY `kvmstate` (`kvmstate`),
@@ -198,6 +200,7 @@ if ($addTrigger) {
finalResponse(UPDATE_RETRY, 'Locations module not installed yet, retry later');
}
}
+ $res[] = UPDATE_DONE;
}
if ($machineHwCreate === UPDATE_DONE) {
@@ -217,12 +220,19 @@ if ($machineHwCreate === UPDATE_DONE) {
if ($ret === false) {
finalResponse(UPDATE_FAILED, 'Adding constraint to statistic_hw_prop failed: ' . Database::lastError());
}
+ $res[] = UPDATE_DONE;
}
-// Create response
-
-if (in_array(UPDATE_DONE, $res)) {
- finalResponse(UPDATE_DONE, 'Tables created successfully');
+// 2017-11-27: Add state column
+if (!tableHasColumn('machine', 'state')) {
+ $ret = Database::exec("ALTER TABLE `machine`
+ ADD COLUMN `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE' AFTER `lastboot`,
+ ADD INDEX `state` (`state`)");
+ if ($ret === false) {
+ finalResponse(UPDATE_FAILED, 'Adding state column to machine table failed: ' . Database::lastError());
+ }
+ $res[] = UPDATE_DONE;
}
-finalResponse(UPDATE_NOOP, 'Everything already up to date');
+// Create response
+responseFromArray($res);
diff --git a/modules-available/statistics/lang/de/messages.json b/modules-available/statistics/lang/de/messages.json
index 8bdf9cfc..c9667f7b 100644
--- a/modules-available/statistics/lang/de/messages.json
+++ b/modules-available/statistics/lang/de/messages.json
@@ -1,6 +1,7 @@
{
- "invalid-filter": "Ung\u00fcltiger Filter",
+ "deleted-n-machines": "{{0}} Clients gel\u00f6scht",
"invalid-filter-argument": "Das Argument {{1}} ist nicht g\u00fcltig f\u00fcr den Filter {{0}}",
"invalid-filter-key": "{{0}} ist kein g\u00fcltiges Filterkriterium",
- "notes-saved": "Anmerkungen gespeichert"
+ "notes-saved": "Anmerkungen gespeichert",
+ "unknown-machine": "Unbekannte Rechner-ID {{0}}"
} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/template-tags.json b/modules-available/statistics/lang/de/template-tags.json
index 02c3e4d6..3cdde813 100644
--- a/modules-available/statistics/lang/de/template-tags.json
+++ b/modules-available/statistics/lang/de/template-tags.json
@@ -37,6 +37,7 @@
"lang_machineOccupied": "Der Rechner ist eingeschaltet und wird benutzt",
"lang_machineOccupiedBy": "In Verwendung durch",
"lang_machineOff": "Der Rechner ist ausgeschaltet, oder hat kein bwLehrpool gebootet",
+ "lang_machineStandby": "Im Standby",
"lang_machineSummary": "Zusammenfassung",
"lang_maximumAbbrev": "Max.",
"lang_memoryStats": "Arbeitsspeicher",
@@ -45,6 +46,7 @@
"lang_modelName": "Modellname",
"lang_modelNo": "Modell",
"lang_modelStats": "PC-Modelle",
+ "lang_moduleHeading": "Client-Statistiken",
"lang_more": "Mehr",
"lang_newMachines": "Neue Ger\u00e4te",
"lang_noEdid": "Kein EDID",
@@ -64,17 +66,20 @@
"lang_ramSlots": "Speicher-Slots",
"lang_realCores": "Kerne",
"lang_reallocatedSectors": "Defekte Sektoren",
+ "lang_runMode": "Betriebsmodus",
+ "lang_runmodeMachines": "Mit besonderem Betriebsmodus",
"lang_screens": "Bildschirme",
"lang_serialNo": "Serien-Nr",
"lang_showList": "Liste",
"lang_showVisualization": "Visualisierung",
"lang_sockets": "Sockel",
"lang_subnet": "Subnetz",
+ "lang_sureDeletePermanent": "M\u00f6chten Sie diese(n) Rechner wirklich unwiderruflich aus der Datenbank entfernen?\r\n\r\nWichtig: L\u00f6schen verhindert nicht, dass ein Rechner nach erneutem Starten von bwLehrpool wieder in die Datenbank aufgenommen wird.",
"lang_tempPart": "Temp. Partition",
"lang_tempPartStats": "Tempor\u00e4re Partition",
"lang_thoseAreProjectors": "Diese Modellnamen werden als Beamer behandelt, auch wenn die EDID-Informationen des Ger\u00e4tes anderes berichten.",
"lang_timebarDesc": "Visuelle Darstellung der letzten Tage. Rote Abschnitte zeigen, wann der Rechner belegt war, gr\u00fcne, wann er nicht verwendet wurde, aber eingeschaltet war. Die leicht abgedunkelten Abschnitte markieren N\u00e4chte (22 bis 8 Uhr).",
- "lang_tmpGb": "HDD-Temp",
+ "lang_tmpGb": "Temp-HDD",
"lang_total": "Gesamt",
"lang_usageDetails": "Nutzungsdetails",
"lang_usageState": "Zustand",
@@ -82,4 +87,4 @@
"lang_virtualCores": "Virtuelle Kerne",
"lang_when": "Wann",
"lang_withBadSectors": "Clients mit potentiell defekten Festplatten (mehr als 10 defekte Sektoren)"
-}
+} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/messages.json b/modules-available/statistics/lang/en/messages.json
index 40eac8c9..3471c472 100644
--- a/modules-available/statistics/lang/en/messages.json
+++ b/modules-available/statistics/lang/en/messages.json
@@ -1,6 +1,7 @@
{
- "invalid-filter": "Invalid filter",
+ "deleted-n-machines": "Deleted {{0}} clients",
"invalid-filter-argument": "{{1}} is not a vald argument for filter {{0}}",
"invalid-filter-key": "{{0}} is not a valid filter",
- "notes-saved": "Notes have been saved"
+ "notes-saved": "Notes have been saved",
+ "unknown-machine": "Unknown machine uuid {{0}}"
} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/template-tags.json b/modules-available/statistics/lang/en/template-tags.json
index c097ce37..35c4e68a 100644
--- a/modules-available/statistics/lang/en/template-tags.json
+++ b/modules-available/statistics/lang/en/template-tags.json
@@ -37,6 +37,7 @@
"lang_machineOccupied": "Machine is powered on and in use",
"lang_machineOccupiedBy": "In use by",
"lang_machineOff": "Machine is powered down, or is not running bwLehrpool",
+ "lang_machineStandby": "In standby mode",
"lang_machineSummary": "Summary",
"lang_maximumAbbrev": "max.",
"lang_memoryStats": "Memory",
@@ -45,6 +46,7 @@
"lang_modelName": "Model name",
"lang_modelNo": "Model",
"lang_modelStats": "PC models",
+ "lang_moduleHeading": "Client Statistics",
"lang_more": "More",
"lang_newMachines": "New machines",
"lang_noEdid": "No EDID",
@@ -64,17 +66,20 @@
"lang_ramSlots": "Memory slots",
"lang_realCores": "Cores",
"lang_reallocatedSectors": "Bad sectors",
+ "lang_runMode": "Mode of operation",
+ "lang_runmodeMachines": "With special mode of operation",
"lang_screens": "Screens",
"lang_serialNo": "Serial no",
- "lang_showList": "Show list",
- "lang_showVisualization": "Show visualization",
+ "lang_showList": "List",
+ "lang_showVisualization": "Visualization",
"lang_sockets": "Sockets",
"lang_subnet": "Subnet",
+ "lang_sureDeletePermanent": "Are your sure you want to delete the selected machine(s) from the database? This cannot be undone.\r\n\r\nNote: Deleting machines from the database does not prevent booting up bwLehrpool again, which would recreate their respective database entries.",
"lang_tempPart": "Temp. partition",
"lang_tempPartStats": "Temporary partition",
"lang_thoseAreProjectors": "These model names will always be treated as beamers, even if the device's EDID data says otherwise.",
"lang_timebarDesc": "Visual representation of the last few days. Red parts mark periods where the client was occupied, green parts where the client was idle. Dimmed parts mark nights (10pm to 8am).",
- "lang_tmpGb": "HDD temp",
+ "lang_tmpGb": "Temp HDD",
"lang_total": "Total",
"lang_usageDetails": "Detailed usage",
"lang_usageState": "State",
@@ -82,4 +87,4 @@
"lang_virtualCores": "Virtual cores",
"lang_when": "When",
"lang_withBadSectors": "Clients with potentially bad HDDs (more than 10 reallocated sectors)"
-}
+} \ No newline at end of file
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index 2b12c69f..c3ecf52b 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -106,11 +106,17 @@ class Page_Statistics extends Page
'op' => Page_Statistics::$op_nominal,
'type' => 'string',
'column' => true
+ ],
+ 'state' => [
+ 'op' => Page_Statistics::$op_nominal,
+ 'type' => 'enum',
+ 'column' => true,
+ 'values' => ['occupied', 'on', 'off', 'idle', 'standby']
]
];
if (Module::isAvailable('locations')) {
Page_Statistics::$columns['location'] = [
- 'op' => Page_Statistics::$op_nominal,
+ 'op' => Page_Statistics::$op_stringcmp,
'type' => 'enum',
'column' => false,
'values' => array_keys(Location::getLocationsAssoc()),
@@ -189,9 +195,36 @@ class Page_Statistics extends Page
Util::redirect('?do=Statistics&uuid=' . $uuid);
} elseif ($action === 'addprojector' || $action === 'delprojector') {
$this->handleProjector($action);
+ } elseif ($action === 'delmachines') {
+ $this->deleteMachines();
+ Util::redirect('?do=statistics', true);
+ }
+ }
+
+ private function deleteMachines()
+ {
+ $ids = Request::post('uuid', [], 'array');
+ $ids = array_values($ids);
+ if (empty($ids)) {
+ Message::addError('main.parameter-empty', 'uuid');
+ return;
+ }
+ $res = Database::simpleQuery('SELECT machineuuid, locationid FROM machine WHERE machineuuid IN (:ids)', compact('ids'));
+ $ids = array_flip($ids);
+ $delete = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ // TODO: Check locationid permissions
+ unset($ids[$row['machineuuid']]);
+ $delete[] = $row['machineuuid'];
+ }
+ if (!empty($delete)) {
+ Database::exec('DELETE FROM machine WHERE machineuuid IN (:delete)', compact('delete'));
+ Message::addSuccess('deleted-n-machines', count($delete));
+ }
+ if (!empty($ids)) {
+ // TODO: Warn permissions
+ Message::addWarning('unknown-machine', implode(', ', array_keys($ids)));
}
- // Fix online state of machines that crashed -- TODO: Make cronjob for this
- Database::exec("UPDATE machine SET lastboot = 0 WHERE lastseen < UNIX_TIMESTAMP() - 610");
}
protected function doRender()
@@ -199,7 +232,12 @@ class Page_Statistics extends Page
$uuid = Request::get('uuid', false, 'string');
if ($uuid !== false) {
$this->showMachine($uuid);
+ return;
+ }
+ $show = Request::get('show', 'stat', 'string');
+ if ($show === 'projectors') {
+ $this->showProjectors();
return;
}
@@ -210,23 +248,19 @@ class Page_Statistics extends Page
}
$sortColumn = Request::any('sortColumn');
$sortDirection = Request::any('sortDirection');
- $filters = Filter::parseQuery($this->query);
+ $filters = Filter::parseQuery($this->query);
$filterSet = new FilterSet($filters);
$filterSet->setSort($sortColumn, $sortDirection);
-
- $show = Request::get('show', 'stat', 'string');
if ($show == 'list') {
Render::openTag('div', array('class' => 'row'));
$this->showFilter('list', $filterSet);
Render::closeTag('div');
$this->showMachineList($filterSet);
return;
- } elseif ($show === 'projectors') {
- $this->showProjectors();
- return;
}
+ $filterSet->filterNonClients();
Render::openTag('div', array('class' => 'row'));
$this->showFilter('stat', $filterSet);
$this->showSummary($filterSet);
@@ -253,11 +287,11 @@ class Page_Statistics extends Page
);
if ($show === 'list') {
- $data['listButtonClass'] = 'btn-primary';
- $data['statButtonClass'] = 'btn-default';
+ $data['listButtonClass'] = 'active';
+ $data['statButtonClass'] = '';
} else {
- $data['listButtonClass'] = 'btn-default';
- $data['statButtonClass'] = 'btn-primary';
+ $data['listButtonClass'] = '';
+ $data['statButtonClass'] = 'active';
}
@@ -308,6 +342,14 @@ class Page_Statistics extends Page
}
}
+ private function redirectFirst($where, $join, $args)
+ {
+ $res = Database::queryFirst("SELECT machineuuid FROM machine $join WHERE ($where) LIMIT 1", $args);
+ if ($res !== false) {
+ Util::redirect('?do=statistics&uuid=' . $res['machineuuid']);
+ }
+ }
+
/**
* @param \FilterSet $filterSet
*/
@@ -316,8 +358,12 @@ class Page_Statistics extends Page
$filterSet->makeFragments($where, $join, $sort, $args);
$known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE ($where)", $args);
- $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND ($where)", $args);
- $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND logintime <> 0 AND ($where)", $args);
+ // If we only have one machine, redirect to machine details
+ if ($known['val'] == 1) {
+ $this->redirectFirst($where, $join, $args);
+ }
+ $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state IN ('IDLE', 'OCCUPIED') AND ($where)", $args);
+ $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state = 'OCCUPIED' AND ($where)", $args);
$hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE badsectors >= 10 AND ($where)", $args);
if ($on['val'] != 0) {
$usedpercent = round($used['val'] / $on['val'] * 100);
@@ -355,6 +401,10 @@ class Page_Statistics extends Page
}
$data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2)));
$data['query'] = $this->query;
+ if (Module::get('runmode') !== false) {
+ $res = Database::queryFirst('SELECT Count(*) AS cnt FROM runmode');
+ $data['runmode'] = $res['cnt'];
+ }
// Draw
Render::addTemplate('summary', $data);
}
@@ -533,7 +583,8 @@ class Page_Statistics extends Page
if (empty($row['hostname'])) {
$row['hostname'] = $row['clientip'];
}
- $row['firstseen'] = date('d.m. H:i', $row['firstseen']);
+ $row['firstseen_int'] = $row['firstseen'];
+ $row['firstseen'] = Util::prettyTime($row['firstseen']);
$row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding..
$row['gbtmp'] = round($row['id44mb'] / 1024);
$row['ramclass'] = $this->ramColorClass($row['mbram']);
@@ -553,29 +604,37 @@ class Page_Statistics extends Page
*/
private function showMachineList($filterSet)
{
+ Module::isAvailable('js_stupidtable');
$filterSet->makeFragments($where, $join, $sort, $args);
$xtra = '';
if ($filterSet->isNoId44Filter()) {
- $xtra = ', data';
+ $xtra .= ', data';
+ }
+ if (Module::isAvailable('runmode')) {
+ $xtra .= ', runmode.module AS rmmodule';
+ if (strpos($join, 'runmode') === false) {
+ $join .= ' LEFT JOIN runmode USING (machineuuid) ';
+ }
}
- $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, firstseen, lastseen,'
- . ' logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,'
+ $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, lastseen,'
+ . ' logintime, state, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,'
. ' badsectors ' . $xtra . ' FROM machine'
. " $join WHERE $where $sort", $args);
$rows = array();
$NOW = time();
+ $singleMachine = 'none';
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- if ($row['lastboot'] == 0) {
- $row['state_off'] = true;
- } elseif ($row['logintime'] == 0) {
- $row['state_idle'] = true;
+ if ($singleMachine === 'none') {
+ $singleMachine = $row['machineuuid'];
} else {
- $row['state_occupied'] = true;
+ $singleMachine = false;
}
- //$row['firstseen'] = date('d.m.Y H:i', $row['firstseen']);
- $row['lastseen'] = date('d.m. H:i', $row['lastseen']);
- //$row['lastboot'] = date('d.m. H:i', $row['lastboot']);
+ $row['state_' . $row['state']] = true;
+ //$row['firstseen'] = Util::prettyTime($row['firstseen']);
+ $row['lastseen_int'] = $row['lastseen'];
+ $row['lastseen'] = Util::prettyTime($row['lastseen']);
+ //$row['lastboot'] = Util::prettyTime($row['lastboot']);
$row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding..
$row['gbtmp'] = round($row['id44mb'] / 1024);
$octets = explode('.', $row['clientip']);
@@ -594,8 +653,12 @@ class Page_Statistics extends Page
$row['nohdd'] = true;
}
}
+ $row['cpumodel'] = preg_replace('/\(R\)|\(TM\)|\bintel\b|\bamd\b|\bcpu\b|dual-core|\bdual\s+core\b|\bdual\b|\bprocessor\b/i', ' ', $row['cpumodel']);
$rows[] = $row;
}
+ if ($singleMachine !== false && $singleMachine !== 'none') {
+ Util::redirect('?do=statistics&uuid=' . $singleMachine);
+ }
Render::addTemplate('clientlist', array(
'rowCount' => count($rows),
'rows' => $rows,
@@ -605,7 +668,8 @@ class Page_Statistics extends Page
'sortColumn' => $filterSet->getSortColumn(),
'columns' => json_encode(Page_Statistics::$columns),
'showList' => 1,
- 'show' => 'list'
+ 'show' => 'list',
+ 'redirect' => $_SERVER['QUERY_STRING']
));
}
@@ -702,24 +766,40 @@ class Page_Statistics extends Page
private function showMachine($uuid)
{
- $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot,'
+ $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state,'
. ' mbram, kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid',
array('uuid' => $uuid));
+ if ($client === false) {
+ Message::addError('unknown-machine', $uuid);
+ return;
+ }
// Hack: Get raw collected data
if (Request::get('raw', false)) {
Header('Content-Type: text/plain; charset=utf-8');
die($client['data']);
}
+ // Runmode
+ if (Module::isAvailable('runmode')) {
+ $data = RunMode::getRunMode($uuid, RunMode::DATA_STRINGS);
+ if ($data !== false) {
+ $client += $data;
+ }
+ }
+ if (!isset($client['isclient'])) {
+ $client['isclient'] = true;
+ }
// Mangle fields
$NOW = time();
- if ($client['lastboot'] == 0) {
- $client['state_off'] = true;
- } elseif ($client['logintime'] == 0) {
- $client['state_idle'] = true;
+ if (!$client['isclient']) {
+ if ($client['state'] === 'IDLE') {
+ $client['state'] = 'OCCUPIED';
+ }
} else {
- $client['state_occupied'] = true;
- $this->fillSessionInfo($client);
+ if ($client['state'] === 'OCCUPIED') {
+ $this->fillSessionInfo($client);
+ }
}
+ $client['state_' . $client['state']] = true;
$client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']);
$client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']);
if ($client['lastboot'] == 0) {
@@ -727,7 +807,7 @@ class Page_Statistics extends Page
} else {
$uptime = $NOW - $client['lastboot'];
$client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']);
- if (!isset($client['state_off']) || !$client['state_off']) {
+ if ($client['state'] === 'IDLE' || $client['state'] === 'OCCUPIED') {
$client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')';
}
}
@@ -810,6 +890,8 @@ class Page_Statistics extends Page
$last = false;
$first = true;
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (!$client['isclient'] && $row['typeid'] === '~session-length')
+ continue; // Don't differentiate between session and idle for non-clients
if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) {
// Special case: offline before
$spans['graph'] .= '<div style="background:#444;left:0%;width:' . round((min($row['dateline'], $client['lastboot']) - $cutoff) * $scale, 2) . '%">&nbsp;</div>';
@@ -832,7 +914,7 @@ class Page_Statistics extends Page
$row['data'] -= ($cutoff - $row['dateline']);
$row['dateline'] = $cutoff;
}
- $row['from'] = date('d.m. H:i', $row['dateline']);
+ $row['from'] = Util::prettyTime($row['dateline']);
$row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']);
if ($row['typeid'] === '~offline-length') {
$row['glyph'] = 'off';
@@ -842,12 +924,17 @@ class Page_Statistics extends Page
$color = '#e77';
}
$spans['graph'] .= '<div style="background:' . $color . ';left:' . round(($row['dateline'] - $cutoff) * $scale, 2) . '%;width:' . round(($row['data']) * $scale, 2) . '%">&nbsp;</div>';
- $spans['rows'][] = $row;
+ if ($client['isclient']) {
+ $spans['rows'][] = $row;
+ }
$last = $row;
}
if ($first && $client['lastboot'] > $cutoff) {
// Special case: offline before
$spans['graph'] .= '<div style="background:#444;left:0%;width:' . round(($client['lastboot'] - $cutoff) * $scale, 2) . '%">&nbsp;</div>';
+ } elseif ($first) {
+ // Not seen in last two weeks
+ $spans['graph'] .= '<div style="background:#444;left:0%;width:100%">&nbsp;</div>';
}
if (isset($client['state_occupied'])) {
$spans['graph'] .= '<div style="background:#e99;left:' . round(($client['logintime'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['logintime'] + 900) * $scale, 2) . '%">&nbsp;</div>';
@@ -868,6 +955,7 @@ class Page_Statistics extends Page
$spans['rows2'] = array_slice($spans['rows'], ceil(count($spans['rows']) / 2));
$spans['rows'] = array_slice($spans['rows'], 0, ceil(count($spans['rows']) / 2));
}
+ $spans['isclient'] = $client['isclient'];
Render::addTemplate('machine-usage', $spans);
// Any hdds?
if (!empty($hdds['hdds'])) {
@@ -877,21 +965,13 @@ class Page_Statistics extends Page
if (Module::get('syslog') !== false) {
$lres = Database::simpleQuery('SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog'
. ' WHERE machineuuid = :uuid ORDER BY logid DESC LIMIT 25', array('uuid' => $client['machineuuid']));
- $today = date('d.m.Y');
- $yesterday = date('d.m.Y', time() - 86400);
$count = 0;
$log = array();
while ($row = $lres->fetch(PDO::FETCH_ASSOC)) {
if (substr($row['description'], -5) === 'on :0' && strpos($row['description'], 'root logged') === false) {
continue;
}
- $day = date('d.m.Y', $row['dateline']);
- if ($day === $today) {
- $day = Dictionary::translate('lang_today');
- } elseif ($day === $yesterday) {
- $day = Dictionary::translate('lang_yesterday');
- }
- $row['date'] = $day . date(' H:i', $row['dateline']);
+ $row['date'] = Util::prettyTime($row['dateline']);
$row['icon'] = $this->eventToIconName($row['logtypeid']);
$log[] = $row;
if (++$count === 10) {
@@ -899,7 +979,7 @@ class Page_Statistics extends Page
}
}
Render::addTemplate('syslog', array(
- 'clientip' => $client['clientip'],
+ 'machineuuid' => $client['machineuuid'],
'list' => $log,
));
}
diff --git a/modules-available/statistics/style.css b/modules-available/statistics/style.css
new file mode 100644
index 00000000..1496ac87
--- /dev/null
+++ b/modules-available/statistics/style.css
@@ -0,0 +1,11 @@
+.to-top-btn {
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ z-index: 100;
+ width: 50px;
+ height: 50px;
+ border-radius: 25px;
+ font-size: 20px;
+ line-height: 40px;
+} \ No newline at end of file
diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html
index 73148eb8..13e148fa 100644
--- a/modules-available/statistics/templates/clientlist.html
+++ b/modules-available/statistics/templates/clientlist.html
@@ -1,95 +1,125 @@
+<h2>{{lang_clientList}} ({{rowCount}})</h2>
+<form method="post" action="?do=statistics">
+<input type="hidden" name="token" value="{{token}}">
+<input type="hidden" name="redirect" value="?{{redirect}}">
-
-<h1>{{lang_clientList}} ({{rowCount}})</h1>
-
-<table class="table table-condensed table-striped">
- <tr>
- <th>{{lang_machine}}</th>
- <th>{{lang_address}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-clientip"></button>
- </div>
-
- </th>
- <th class="text-right">{{lang_lastSeen}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-lastseen"></button>
+<table class="stupidtable table table-condensed table-striped">
+ <thead>
+ <tr>
+ <td></td>
+ <td></td>
+ <td class="text-right">
<button class="btn btn-default btn-xs" onclick="popupFilter('lastseen')">
<span id="btn_filter_lastseen" class="glyphicon glyphicon-filter"></span>
</button>
- </div>
- </th>
- <th>
- {{lang_kvmSupport}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-kvmstate"></button>
- <button class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')">
- <span id="btn_filter_kvmstate" class="glyphicon glyphicon-filter"></span>
- </button>
- </div>
- </th>
- <th class="text-right">
- {{lang_gbRam}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-gbram"></button>
+ </td>
+ <td>
+ <button class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')">
+ <span id="btn_filter_kvmstate" class="glyphicon glyphicon-filter"></span>
+ </button>
+ </td>
+ <td class="text-right">
<button class="btn btn-default btn-xs" onclick="popupFilter('gbram')">
<span id="btn_filter_gbram" class="glyphicon glyphicon-filter"></span>
</button>
- </div>
- </th>
- <th class="text-right">
- {{lang_tmpGb}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-hddgb"></button>
- <button class="btn btn-default btn-xs" onclick="popupFilter('hddgb')">
- <span id="btn_filter_hddgb" class="glyphicon glyphicon-filter"></span>
- </button>
- </div>
- </th>
- <th>{{lang_cpuModel}}
- <div class="btn-group pull-right">
- <button class="btn btn-default btn-xs" id="sortButton-realcores"></button>
- <button class="btn btn-default btn-xs" onclick="popupFilter('realcores')">
- <span id="btn_filter_cpu" class="glyphicon glyphicon-filter"></span>
- </button>
- </div>
- </th>
- </tr>
- {{#rows}}
- <tr>
- <td class="text-nowrap">
- {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}}
- {{#state_off}}
- <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span>
- {{/state_off}}
- {{#state_idle}}
- <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span>
- {{/state_idle}}
- {{#state_occupied}}
- <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span>
- {{/state_occupied}}
- <a href="?do=Statistics&amp;uuid={{machineuuid}}"><b>{{hostname}}</b></a>
- <div class="small">{{machineuuid}}</div>
- </td>
- <td><b><a href="?do=Statistics&amp;show=list&amp;filters=subnet={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td>
- <td class="text-right">{{lastseen}}</td>
- <td class="{{kvmclass}}">{{kvmstate}}</td>
- <td class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
- <td class="text-right {{hddclass}}">
- {{gbtmp}}&thinsp;GiB
- {{#badsectors}}<div>
- <span class="glyphicon glyphicon-exclamation-sign"></span>
- {{badsectors}}
- </div>{{/badsectors}}
- {{#nohdd}}<div>
- <span class="glyphicon glyphicon-hdd red"></span>
- </div>{{/nohdd}}
- </td>
- <td>{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td>
- </tr>
- {{/rows}}
+ </td>
+ <td class="text-right">
+ <button class="btn btn-default btn-xs" onclick="popupFilter('hddgb')">
+ <span id="btn_filter_hddgb" class="glyphicon glyphicon-filter"></span>
+ </button>
+ </td>
+ <td>
+ <button class="btn btn-default btn-xs" onclick="popupFilter('realcores')">
+ <span id="btn_filter_cpu" class="glyphicon glyphicon-filter"></span>
+ </button>
+ </td>
+ </tr>
+ <tr>
+ <th data-sort="string">{{lang_machine}}</th>
+ <th data-sort="ipv4">{{lang_address}}</th>
+ <th data-sort="int" class="text-right">{{lang_lastSeen}}</th>
+ <th data-sort="string">{{lang_kvmSupport}}</th>
+ <th data-sort="int" class="text-right">{{lang_gbRam}}</th>
+ <th data-sort="int" class="text-right">{{lang_tmpGb}}</th>
+ <th data-sort="int">{{lang_cpuModel}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr>
+ <td data-sort-value="{{hostname}}" class="text-nowrap">
+ <div class="checkbox checkbox-inline">
+ <input type="checkbox" name="uuid[]" value="{{machineuuid}}">
+ <label></label>
+ </div>
+ {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}}
+ {{#state_OFFLINE}}
+ <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span>
+ {{/state_OFFLINE}}
+ {{#state_IDLE}}
+ <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span>
+ {{/state_IDLE}}
+ {{#state_OCCUPIED}}
+ <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span>
+ {{/state_OCCUPIED}}
+ {{#state_STANDBY}}
+ <span class="glyphicon glyphicon-off green" title="{{lang_machineStandby}}"></span>
+ {{/state_STANDBY}}
+ <a href="?do=Statistics&amp;uuid={{machineuuid}}"><b>{{hostname}}</b></a>
+ <div class="small">{{machineuuid}}</div>
+ {{#rmmodule}}<div class="small">{{lang_runMode}}: <a class="slx-bold" href="?do=runmode&amp;module={{rmmodule}}">{{rmmodule}}</a></div>{{/rmmodule}}
+ </td>
+ <td data-sort-value="{{clientip}}"><b><a href="?do=Statistics&amp;show=list&amp;filters=subnet={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td>
+ <td data-sort-value="{{lastseen_int}}" class="text-right text-nowrap">{{lastseen}}</td>
+ <td class="{{kvmclass}}">{{kvmstate}}</td>
+ <td data-sort-value="{{gbram}}" class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
+ <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}">
+ {{gbtmp}}&thinsp;GiB
+ {{#badsectors}}<div><span data-toggle="tooltip" title="{{lang_reallocatedSectors}}" data-placement="left">
+ <span class="glyphicon glyphicon-exclamation-sign"></span>
+ {{badsectors}}
+ </span></div>{{/badsectors}}
+ {{#nohdd}}<div>
+ <span class="glyphicon glyphicon-hdd red"></span>
+ </div>{{/nohdd}}
+ </td>
+ <td data-sort-value="{{realcores}}">{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td>
+ </tr>
+ {{/rows}}
+ </tbody>
</table>
+ <div class="text-right buttonbar">
+ <button type="reset" class="btn btn-default">
+ <span class="glyphicon glyphicon-remove"></span>
+ {{lang_reset}}
+ </button>
+ <button type="button" class="btn btn-danger" onclick="$('#del-confirm').modal()">
+ <span class="glyphicon glyphicon-trash"></span>
+ {{lang_delete}}
+ </button>
+ </div>
+ <div class="modal fade" id="del-confirm" tabindex="-1" role="dialog">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <b>{{lang_delete}}</b>
+ </div>
+ <div class="modal-body">
+ {{lang_sureDeletePermanent}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-danger" name="action" value="delmachines">
+ <span class="glyphicon glyphicon-trash"></span>
+ {{lang_delete}}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+</form>
<script type="application/javascript"><!--
document.addEventListener("DOMContentLoaded", function () {
@@ -103,6 +133,11 @@ document.addEventListener("DOMContentLoaded", function () {
$sortBtn.html('<span class="glyphicon glyphicon-arrow-' + order + '"></span>');
$sortBtn.attr('onclick', 'toggleButton(\'' + v + '\');');
});
+
+ $('[data-toggle="tooltip"]').tooltip({
+ container: 'body',
+ trigger : 'hover'
+ });
});
function toggleButton(v) {
@@ -126,4 +161,4 @@ function toggleButton(v) {
$queryForm.submit();
}
-//--></script> \ No newline at end of file
+//--></script>
diff --git a/modules-available/statistics/templates/cpumodels.html b/modules-available/statistics/templates/cpumodels.html
index d9b0298b..d684c914 100644
--- a/modules-available/statistics/templates/cpumodels.html
+++ b/modules-available/statistics/templates/cpumodels.html
@@ -6,34 +6,38 @@
<div class="panel-body">
<div class="row">
<div class="col-md-8">
- <table class="table table-condensed table-striped table-responsive">
- <tr>
- <th>{{lang_modelName}}</th>
- <th class="text-right text-nowrap">{{lang_cpuCores}}</th>
- <th class="text-right text-nowrap">{{lang_modelCount}}</th>
- </tr>
- {{#rows}}
- <tr id="{{id}}" class="{{collapse}}">
- <td class="text-left text-nowrap filter-col" data-filter-col="systemmodel">
- <table style="width:100%; table-layout: fixed;"><tr><td style="overflow:hidden;text-overflow: ellipsis;">
- <a class="filter-val" data-filter-val="{{systemmodel}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a>
- </td></tr></table>
- </td>
- <td class="text-right filter-col" data-filter-col="realcores">
- <a class="filter-val" data-filter-val="{{cores}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~realcores={{cores}}">{{cores}}</a>
- </td>
- <td class="text-right">{{count}}</td>
- </tr>
- {{/rows}}
- <tr class="slx-decollapse">
- <td colspan="3">
- <span class="btn-group btn-group-justified">
- <span class="btn btn-default btn-sm">
- <span class="glyphicon glyphicon-menu-down"></span>
+ <table class="stupidtable table table-condensed table-striped table-responsive">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_modelName}}</th>
+ <th data-sort="int" class="text-right text-nowrap">{{lang_cpuCores}}</th>
+ <th data-sort="int" class="text-right text-nowrap">{{lang_modelCount}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr id="{{id}}" class="{{collapse}}">
+ <td data-sort-value="{{systemmodel}}" class="text-left text-nowrap filter-col" data-filter-col="systemmodel">
+ <table style="width:100%; table-layout: fixed;"><tr><td style="overflow:hidden;text-overflow: ellipsis;">
+ <a class="filter-val" data-filter-val="{{systemmodel}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a>
+ </td></tr></table>
+ </td>
+ <td data-sort-value="{{cores}}" class="text-right filter-col" data-filter-col="realcores">
+ <a class="filter-val" data-filter-val="{{cores}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~realcores={{cores}}">{{cores}}</a>
+ </td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ <tr class="slx-decollapse">
+ <td colspan="3">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm">
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </span>
</span>
- </span>
- </td>
- </tr>
+ </td>
+ </tr>
+ </tbody>
</table>
</div>
<div class="col-md-4">
diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html
index 7cd0f617..31daabc6 100644
--- a/modules-available/statistics/templates/filterbox.html
+++ b/modules-available/statistics/templates/filterbox.html
@@ -1,59 +1,82 @@
-<div id="modal-add-filter" class="modal modal-sm fade" role="dialog"
- style="position:absolute; min-width:600px; min-height: 400px;margin:auto">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span
- class="sr-only">Close</span></button>
- {{lang_add_filter}}
- </div>
- <form class="modal-body form-inline center" onsubmit="$('#add-btn').click(); return false">
- <div class="form-group">
- <select id="columnSelect" name="column" class="form-control col-4-xs"> </select>
- </div>
- <div class="form-group">
- <select id="operatorSelect" name="operator" class="form-control col-4-xs"> </select>
- </div>
- <div class="form-group">
- <input name="argument" id="argumentInput" class="form-control col-4-xs"> </input>
- <select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select>
+<div id="modal-add-filter" class="modal fade" role="dialog" style="position: absolute">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content">
+ <div class="modal-header">
+ <b>{{lang_add_filter}}</b>
</div>
- <button id="add-btn" type="button" class="btn btn-primary" onclick="addFilterFromForm()">
- <span class="glyphicon glyphicon-plus"></span>
- {{lang_add}}
- </button>
- </form>
+ <form class="form-inline center" onsubmit="$('#add-btn').click(); return false">
+ <div class="modal-body">
+ <div class="form-group">
+ <select id="columnSelect" name="column" class="form-control col-4-xs"> </select>
+ </div>
+ <div class="form-group">
+ <select id="operatorSelect" name="operator" class="form-control col-4-xs"> </select>
+ </div>
+ <div class="form-group">
+ <input name="argument" id="argumentInput" class="form-control col-4-xs"> </input>
+ <select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select>
+ </div>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button id="add-btn" type="button" class="btn btn-success" onclick="addFilterFromForm()">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_add}}
+ </button>
+ </div>
+ </form>
+ </div>
</div>
</div>
+<a href="#top" class="btn btn-default to-top-btn"><span class="glyphicon glyphicon-menu-up"></span></a>
-<div style="height:120px" class="col-xs-12">
+
+<div class="col-md-12">
<!-- use GET here, to avoid the "resend form?" confirmation, and anyway this is stateless, so GET makes more sense -->
<form id="queryForm" method="GET" action="?do=Statistics" class="" role="form">
- <input type="hidden" name="do" value="statistics">
<input type="hidden" name="show" value="{{show}}">
- <label for="filterInput">{{lang_labelFilter}}</label>
- <input type="text" name="filters" class="" id="filterInput"/>
- <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/>
- <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/>
+ <button type="submit" hidden></button>
+
- <button type="button" class="btn btn-success pull-left" onclick="popupFilter(null)">
- <span class="glyphicon glyphicon-plus"></span>
- {{lang_add_filter}}
- </button>
<div class="btn-group pull-right">
- <button class="btn {{statButtonClass}}" type="submit" name="show" value="stat">
+ <button class="btn btn-default {{statButtonClass}}" type="submit" name="show" value="stat">
<span class="glyphicon glyphicon-stats"></span>
{{lang_showVisualization}}
</button>
- <button class="btn {{listButtonClass}}" type="submit" name="show" value="list">
+ <button class="btn btn-default {{listButtonClass}}" type="submit" name="show" value="list">
<span class="glyphicon glyphicon-list"></span>
{{lang_showList}}
</button>
</div>
+ <h1>{{lang_moduleHeading}}</h1>
+
+ <br/>
+
+ <input type="hidden" name="do" value="statistics">
+ <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/>
+ <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/>
+
+ <label for="filterInput">{{lang_labelFilter}}</label>
+ <div class="row">
+ <div class="col-md-12">
+ <div class="input-group">
+ <input type="text" name="filters" class="" id="filterInput"/>
+ <span class="input-group-btn" style=" width: 1%; padding-bottom: 5px;">
+ <button type="button" class="btn btn-success" onclick="popupFilter(null)">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_add_filter}}
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <br/>
</form>
- <br/>
- <br/>
</div>
+
<script type="application/javascript"><!--
var filterSelectize;
@@ -112,6 +135,9 @@ document.addEventListener("DOMContentLoaded", function () {
// if (initComplete && !$('#filterInput').is(':focus')) {
// reload();
// }
+ },
+ onItemRemove: function(value) {
+ refresh();
}
})[0].selectize;
/* add query */
@@ -144,6 +170,7 @@ document.addEventListener("DOMContentLoaded", function () {
$('#argumentInput').datepicker({format : 'yyyy-mm-dd'});
$('#argumentSelect').hide();
} else if(columns[col]['type'] == 'enum') {
+ $('#argumentSelect').empty();
$('#argumentInput').hide();
$('#argumentSelect').show();
columns[col]['values'].forEach(function (v) {
diff --git a/modules-available/statistics/templates/id44.html b/modules-available/statistics/templates/id44.html
index 38cf028f..d3b1ab1c 100644
--- a/modules-available/statistics/templates/id44.html
+++ b/modules-available/statistics/templates/id44.html
@@ -6,28 +6,32 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="filter-col table table-condensed table-striped" data-filter-col="hddgb">
- <tr>
- <th>{{lang_partitionSize}}</th>
- <th class="text-right">{{lang_machineCount}}</th>
- </tr>
- {{#rows}}
- <tr id="tmpid{{gb}}" class="{{class}} {{collapse}}">
- <td class="text-left text-nowrap">
- <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;GiB</a>
- </td>
- <td class="text-right">{{count}}</td>
- </tr>
- {{/rows}}
- <tr class="slx-decollapse">
- <td colspan="2">
- <span class="btn-group btn-group-justified">
- <span class="btn btn-default btn-sm">
- <span class="glyphicon glyphicon-menu-down"></span>
+ <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="hddgb">
+ <thead>
+ <tr>
+ <th data-sort="int">{{lang_partitionSize}}</th>
+ <th data-sort="int" class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr id="tmpid{{gb}}" class="{{class}} {{collapse}}">
+ <td data-sort-value="{{gb}}" class="text-left text-nowrap">
+ <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;GiB</a>
+ </td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ <tr class="slx-decollapse">
+ <td colspan="2">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm">
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </span>
</span>
- </span>
- </td>
- </tr>
+ </td>
+ </tr>
+ </tbody>
</table>
</div>
<div class="col-sm-6">
diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html
index 33a00d38..3704eda0 100644
--- a/modules-available/statistics/templates/kvmstate.html
+++ b/modules-available/statistics/templates/kvmstate.html
@@ -6,19 +6,23 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="filter-col table table-condensed table-striped" data-filter-col="kvmstate">
- <tr>
- <th>{{lang_kvmState}}</th>
- <th class="text-right">{{lang_machineCount}}</th>
- </tr>
- {{#rows}}
- <tr id="kvm{{kvmstate}}">
- <td class="text-left text-nowrap">
- <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a>
- </td>
- <td class="text-right">{{count}}</td>
- </tr>
- {{/rows}}
+ <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="kvmstate">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_kvmState}}</th>
+ <th data-sort="int" class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr id="kvm{{kvmstate}}">
+ <td data-sort-value="{{kvmstate}}" class="text-left text-nowrap">
+ <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a>
+ </td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ </tbody>
</table>
</div>
<div class="col-sm-6">
diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html
index 74df80c4..19deb8b3 100644
--- a/modules-available/statistics/templates/machine-main.html
+++ b/modules-available/statistics/templates/machine-main.html
@@ -50,13 +50,13 @@
<tr>
<td class="text-nowrap">{{lang_usageState}}</td>
<td>
- {{#state_off}}
+ {{#state_OFFLINE}}
<span class="glyphicon glyphicon-off"></span> {{lang_machineOff}}
- {{/state_off}}
- {{#state_idle}}
+ {{/state_OFFLINE}}
+ {{#state_IDLE}}
<span class="glyphicon glyphicon-ok green"></span> {{lang_machineIdle}}
- {{/state_idle}}
- {{#state_occupied}}
+ {{/state_IDLE}}
+ {{#state_OCCUPIED}}
{{#username}}
<span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupiedBy}} <b>{{username}}</b>
{{/username}}
@@ -64,7 +64,10 @@
<span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupied}}
{{/username}}
<div>{{logintime_s}}</div>
- {{/state_occupied}}
+ {{/state_OCCUPIED}}
+ {{#state_STANDBY}}
+ <span class="glyphicon glyphicon-off green"></span> {{lang_machineStandby}}
+ {{/state_STANDBY}}
{{#session}}
<div>
{{#lectureid}}
@@ -77,6 +80,14 @@
{{/session}}
</td>
</tr>
+ {{#modeid}}
+ <tr>
+ <td class="text-nowrap">{{lang_runMode}}</td>
+ <td>
+ <a href="?do=runmode&amp;module={{module}}">{{moduleName}}</a> – {{modeName}}
+ </td>
+ </tr>
+ {{/modeid}}
</table>
</div>
</div>
diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html
index c4f97543..22ed96e9 100644
--- a/modules-available/statistics/templates/machine-notes.html
+++ b/modules-available/statistics/templates/machine-notes.html
@@ -9,7 +9,8 @@
<input type="hidden" name="action" value="setnotes">
<input type="hidden" name="uuid" value="{{machineuuid}}">
<textarea name="content" class="form-control" cols="101" rows="10">{{notes}}</textarea>
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <br/>
+ <button type="submit" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</form>
</div>
</div>
diff --git a/modules-available/statistics/templates/machine-usage.html b/modules-available/statistics/templates/machine-usage.html
index ef969fc6..be435ee5 100644
--- a/modules-available/statistics/templates/machine-usage.html
+++ b/modules-available/statistics/templates/machine-usage.html
@@ -5,46 +5,50 @@
{{lang_usageDetails}}
</div>
<div class="panel-body">
- <div class="row">
- <div class="col-sm-6">
- <table class="table table-condensed">
- <tr>
- <th>{{lang_eventType}}</th>
- <th>{{lang_when}}</th>
- <th>{{lang_duration}}</th>
- </tr>
- {{#rows}}
- <tr>
- <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
- <td>{{from}}</td>
- <td>{{duration}}</td>
- </tr>
- {{/rows}}
- </table>
+ {{#isclient}}
+ <div class="row">
+ <div class="col-sm-6">
+ <table class="table table-condensed">
+ <tr>
+ <th>{{lang_eventType}}</th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_duration}}</th>
+ </tr>
+ {{#rows}}
+ <tr>
+ <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
+ <td>{{from}}</td>
+ <td>{{duration}}</td>
+ </tr>
+ {{/rows}}
+ </table>
+ </div>
+ <div class="col-sm-6">
+ <table class="table table-condensed">
+ {{#hasrows2}}
+ <tr>
+ <th>{{lang_eventType}}</th>
+ <th>{{lang_when}}</th>
+ <th>{{lang_duration}}</th>
+ </tr>
+ {{/hasrows2}}
+ {{#rows2}}
+ <tr>
+ <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
+ <td>{{from}}</td>
+ <td>{{duration}}</td>
+ </tr>
+ {{/rows2}}
+ </table>
+ </div>
</div>
- <div class="col-sm-6">
- <table class="table table-condensed">
- {{#hasrows2}}
- <tr>
- <th>{{lang_eventType}}</th>
- <th>{{lang_when}}</th>
- <th>{{lang_duration}}</th>
- </tr>
- {{/hasrows2}}
- {{#rows2}}
- <tr>
- <td><span class="glyphicon glyphicon-{{glyph}}"></span></td>
- <td>{{from}}</td>
- <td>{{duration}}</td>
- </tr>
- {{/rows2}}
- </table>
- </div>
- </div>
+ {{/isclient}}
<div class="timebar">&nbsp;{{{graph}}}</div>
- <div>
- {{lang_timebarDesc}}
- </div>
+ {{#isclient}}
+ <div>
+ {{lang_timebarDesc}}
+ </div>
+ {{/isclient}}
</div>
</div>
</div>
diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html
index f17f55ca..6bc13980 100644
--- a/modules-available/statistics/templates/memory.html
+++ b/modules-available/statistics/templates/memory.html
@@ -6,28 +6,32 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="filter-col table table-condensed table-striped" data-filter-col="gbram">
- <tr>
- <th>{{lang_ramSize}}</th>
- <th class="text-right">{{lang_machineCount}}</th>
- </tr>
- {{#rows}}
- <tr id="ramid{{gb}}" class="{{class}} {{collapse}}">
- <td class="text-left text-nowrap">
- <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;GiB</a>
- </td>
- <td class="text-right">{{count}}</td>
- </tr>
- {{/rows}}
- <tr class="slx-decollapse">
- <td colspan="2">
- <span class="btn-group btn-group-justified">
- <span class="btn btn-default btn-sm">
- <span class="glyphicon glyphicon-menu-down"></span>
+ <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="gbram">
+ <thead>
+ <tr>
+ <th data-sort="int">{{lang_ramSize}}</th>
+ <th data-sort="int" class="text-right">{{lang_machineCount}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr id="ramid{{gb}}" class="{{class}} {{collapse}}">
+ <td class="text-left text-nowrap" data-sort-value="{{gb}}">
+ <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;GiB</a>
+ </td>
+ <td class="text-right">{{count}}</td>
+ </tr>
+ {{/rows}}
+ <tr class="slx-decollapse">
+ <td colspan="2">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm">
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </span>
</span>
- </span>
- </td>
- </tr>
+ </td>
+ </tr>
+ </tbody>
</table>
</div>
<div class="col-sm-6">
diff --git a/modules-available/statistics/templates/newclients.html b/modules-available/statistics/templates/newclients.html
index c5c704d1..6dc04144 100644
--- a/modules-available/statistics/templates/newclients.html
+++ b/modules-available/statistics/templates/newclients.html
@@ -4,32 +4,36 @@
{{lang_newMachines}}
</div>
<div class="panel-body">
- <table class="table table-condensed table-striped">
- <tr>
- <th>{{lang_machine}}</th>
- <th class="text-right"></th>
- <th>64Bit</th>
- <th class="text-right">RAM</th>
- <th class="text-right">HDD</th>
- </tr>
- {{#rows}}
- <tr class="{{collapse}}">
- <td class="text-nowrap"><a href="?do=Statistics&amp;uuid={{machineuuid}}">{{hostname}}</a></td>
- <td class="text-right">{{firstseen}}</td>
- <td class="{{kvmclass}}">{{kvmicon}}</td>
- <td class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
- <td class="text-right {{hddclass}}">{{gbtmp}}&thinsp;GiB</td>
- </tr>
- {{/rows}}
- <tr class="slx-decollapse">
- <td colspan="5">
- <span class="btn-group btn-group-justified">
- <span class="btn btn-default btn-sm">
- <span class="glyphicon glyphicon-menu-down"></span>
+ <table class="stupidtable table table-condensed table-striped">
+ <thead>
+ <tr>
+ <th data-sort="string">{{lang_machine}}</th>
+ <th data-sort="int" class="text-right" style="min-width: 80px;">{{lang_firstSeen}}</th>
+ <th data-sort="string" class="text-center">64Bit</th>
+ <th data-sort="int" class="text-right">RAM</th>
+ <th data-sort="int" class="text-right">HDD</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#rows}}
+ <tr class="{{collapse}}">
+ <td data-sort-value="{{hostname}}" class="text-nowrap"><a href="?do=Statistics&amp;uuid={{machineuuid}}">{{hostname}}</a></td>
+ <td data-sort-value="{{firstseen_int}}" class="text-right">{{firstseen}}</td>
+ <td class="text-center {{kvmclass}}">{{kvmicon}}</td>
+ <td data-sort-value="{{gbram}}" class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
+ <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}">{{gbtmp}}&thinsp;GiB</td>
+ </tr>
+ {{/rows}}
+ <tr class="slx-decollapse">
+ <td colspan="5">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm">
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </span>
</span>
- </span>
- </td>
- </tr>
+ </td>
+ </tr>
+ </tbody>
</table>
</div>
</div>
diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html
index 642c48fc..fe9559ed 100644
--- a/modules-available/statistics/templates/summary.html
+++ b/modules-available/statistics/templates/summary.html
@@ -1,6 +1,11 @@
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
+ {{#runmode}}
+ <div class="pull-right">
+ <a href="?do=runmode">{{lang_runmodeMachines}}</a>: <b>{{runmode}}</b>
+ </div>
+ {{/runmode}}
<div>
{{lang_knownMachines}}: <b>{{known}}</b>&emsp;
<a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>&emsp;
@@ -10,8 +15,9 @@
<div>
<span class="glyphicon glyphicon-exclamation-sign red"></span>
<a href="?do=Statistics&amp;show=list&amp;filters={{query}}~,~badsectors>=10">
- {{lang_withBadSectors}}: <b>{{badhdd}}</b>
+ {{lang_withBadSectors}}:
</a>
+ <b>{{badhdd}}</b>
</div>
{{/badhdd}}
</div>
diff --git a/modules-available/statistics/templates/syslog.html b/modules-available/statistics/templates/syslog.html
index c82cb8ac..968d32ab 100644
--- a/modules-available/statistics/templates/syslog.html
+++ b/modules-available/statistics/templates/syslog.html
@@ -20,7 +20,7 @@
{{/list}}
</tbody>
</table>
-<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&amp;ip={{clientip}}">{{lang_more}} &raquo;</a></div>
+<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&machineuuid={{machineuuid}}">{{lang_more}} &raquo;</a></div>
<div class="clearfix"></div>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
diff --git a/modules-available/statistics_reporting/lang/de/template-tags.json b/modules-available/statistics_reporting/lang/de/template-tags.json
index fb624fad..f8829b79 100644
--- a/modules-available/statistics_reporting/lang/de/template-tags.json
+++ b/modules-available/statistics_reporting/lang/de/template-tags.json
@@ -10,11 +10,13 @@
"lang_location": "Ort",
"lang_longSessions": "Sitzungen \u2265 60s",
"lang_medianSessionLength": "Sitzungsdauer Median",
+ "lang_moduleName": "Statistikauswertung",
"lang_reportMoreInfo": "Weitere Informationen...",
"lang_reportingDescription": "Helfen Sie uns bwLehrpool durch das w\u00f6chentliche Verschicken eines anonymisierten Statistikberichts zu verbessern. Wenn Sie den Inhalt eines solchen Reports genauer inspizieren wollen, k\u00f6nnen Sie \u00fcber den untenstehenden Button einen aktuellen Report Ihres Servers herunterladen.",
"lang_reportingLabel": "Anonymisierte Nutzungsstatistiken \u00fcbermitteln",
"lang_sessions": "Sitzungen",
"lang_shortSessions": "Sitzungen < 60s",
+ "lang_show": "Anzeigen",
"lang_total": "Gesamt",
"lang_totalOffTime": "Gesamtzeit offline",
"lang_totalTime": "Gesamtzeit",
diff --git a/modules-available/statistics_reporting/lang/en/template-tags.json b/modules-available/statistics_reporting/lang/en/template-tags.json
index 4e33ffed..73c21112 100644
--- a/modules-available/statistics_reporting/lang/en/template-tags.json
+++ b/modules-available/statistics_reporting/lang/en/template-tags.json
@@ -10,11 +10,13 @@
"lang_location": "Location",
"lang_longSessions": "Sessions \u2265 60s",
"lang_medianSessionLength": "Median Session Length",
+ "lang_moduleName": "Statistics Reporting",
"lang_reportMoreInfo": "More information...",
"lang_reportingDescription": "Help us improve bwLehrpool by automatically sending an anonymized statistics report once a week. If you want to check what data the report contains, you can download such a report for reference below.",
"lang_reportingLabel": "Send anonymized usage statistics",
"lang_sessions": "Sessions",
"lang_shortSessions": "Sessions < 60s",
+ "lang_show": "Show",
"lang_total": "Total",
"lang_totalOffTime": "Total Time Offline",
"lang_totalTime": "Total Time",
diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php
index 52accaea..5d586b6c 100644
--- a/modules-available/statistics_reporting/page.inc.php
+++ b/modules-available/statistics_reporting/page.inc.php
@@ -52,16 +52,20 @@ class Page_Statistics_Reporting extends Page
// Export - handle in doPreprocess so we don't render the menu etc.
if ($this->action === 'export') {
- $this->doExport();
- // Does not return
+ if (User::hasPermission("table.export") && User::hasPermission("table.view.$this->type")) {
+ $this->doExport();
+ // Does not return
+ }
}
// Get report - fetch data exactly the way it would automatically be reported
// so the user can know what is going on
if ($this->action === 'getreport') {
- $report = RemoteReport::generateReport(time());
- Header('Content-Disposition: attachment; filename=remote-report.json');
- Header('Content-Type: application/json; charset=utf-8');
- die(json_encode($report));
+ if(User::hasPermission("reporting.download")) {
+ $report = RemoteReport::generateReport(time());
+ Header('Content-Disposition: attachment; filename=remote-report.json');
+ Header('Content-Type: application/json; charset=utf-8');
+ die(json_encode($report));
+ }
}
}
@@ -100,6 +104,7 @@ class Page_Statistics_Reporting extends Page
$data['tables'][] = array(
'name' => Dictionary::translate('table_' . $table, true),
'value' => $table,
+ 'allowed' => User::hasPermission("table.view.$table"),
'selected' => ($this->type === $table) ? 'selected' : '',
);
}
@@ -121,10 +126,17 @@ class Page_Statistics_Reporting extends Page
$data['settingsButtonClass'] = 'danger';
}
+ $data['allowedExport'] = User::hasPermission("table.export") && User::hasPermission("table.view.$this->type");
+ $data['allowedDownload'] = User::hasPermission("reporting.download");
+ $data['allowedReportChange'] = User::hasPermission("reporting.change");
+
Render::addTemplate('columnChooser', $data);
$data['data'] = $this->fetchData(GETDATA_PRINTABLE);
- Render::addTemplate('table-' . $this->type, $data);
+
+ if (User::hasPermission("table.view.$this->type")) {
+ Render::addTemplate('table-' . $this->type, $data);
+ }
}
}
@@ -132,23 +144,24 @@ class Page_Statistics_Reporting extends Page
{
$this->action = Request::any('action', false, 'string');
if ($this->action === 'setReporting') {
- if (!User::isLoggedIn()) {
- die("No.");
- }
- $state = Request::post('reporting', false, 'string');
- if ($state === false) {
- die('Missing setting value.');
- }
- RemoteReport::setReportingEnabled($state);
- $data = array();
- if (RemoteReport::isReportingEnabled()) {
- $data['class'] = 'default';
- $data['checked'] = true;
+ if (User::hasPermission("reporting.change")) {
+ $state = Request::post('reporting', false, 'string');
+ if ($state === false) {
+ die('Missing setting value.');
+ }
+ RemoteReport::setReportingEnabled($state);
+ $data = array();
+ if (RemoteReport::isReportingEnabled()) {
+ $data['class'] = 'default';
+ $data['checked'] = true;
+ } else {
+ $data['class'] = 'danger';
+ }
+ Header('Content-Type: application/json; charset=utf-8');
+ die(json_encode($data));
} else {
- $data['class'] = 'danger';
+ die('No permission.');
}
- Header('Content-Type: application/json; charset=utf-8');
- die(json_encode($data));
} else {
echo 'Invalid action.';
}
@@ -266,9 +279,28 @@ class Page_Statistics_Reporting extends Page
}
}
}
+ // only show locations which you have permission for
+ $filterLocs = User::getAllowedLocations("table.view.location");
+ foreach ($data as $key => $row) {
+ if (!in_array($row['locationId'], $filterLocs)) {
+ unset($data[$key]);
+ }
+ }
+ // correct indexing of array after deletions
+ $data = array_values($data);
return $data;
case 'client':
- return GetData::perClient($flags);
+ $data = GetData::perClient($flags);
+ // only show clients from locations which you have permission for
+ $filterLocs = User::getAllowedLocations("table.view.location");
+ foreach ($data as $key => $row) {
+ if (!in_array($row['locationId'], $filterLocs)) {
+ unset($data[$key]);
+ }
+ }
+ // correct indexing of array after deletions
+ $data = array_values($data);
+ return $data;
case 'user':
return GetData::perUser($flags);
case 'vm':
diff --git a/modules-available/statistics_reporting/permissions/permissions.json b/modules-available/statistics_reporting/permissions/permissions.json
new file mode 100644
index 00000000..e6e550eb
--- /dev/null
+++ b/modules-available/statistics_reporting/permissions/permissions.json
@@ -0,0 +1,10 @@
+{
+ "table.view.total": "View total table.",
+ "table.view.location": "View location table.",
+ "table.view.client": "View client table.",
+ "table.view.user": "View user table.",
+ "table.view.vm": "View lecture table.",
+ "table.export": "Export tables as JSON/CSV/XML. Needs the permission to view the table to export it.",
+ "reporting.download": "Download weekly report.",
+ "reporting.change": "Change weekly reporting settings."
+} \ No newline at end of file
diff --git a/modules-available/statistics_reporting/style.css b/modules-available/statistics_reporting/style.css
index 81dc74b0..3cd6653f 100644
--- a/modules-available/statistics_reporting/style.css
+++ b/modules-available/statistics_reporting/style.css
@@ -35,8 +35,4 @@
margin-left: -1.5em;
text-align: center;
line-height: 1.6em;
-}
-
-th[data-sort] {
- cursor: pointer;
-}
+} \ No newline at end of file
diff --git a/modules-available/statistics_reporting/templates/columnChooser.html b/modules-available/statistics_reporting/templates/columnChooser.html
index e4069be9..a6561c47 100644
--- a/modules-available/statistics_reporting/templates/columnChooser.html
+++ b/modules-available/statistics_reporting/templates/columnChooser.html
@@ -1,64 +1,79 @@
<form method="get" id="controlsForm">
<input type="hidden" name="do" value="statistics_reporting">
- <div class="row">
- <div class="col-md-12">
- <button id="button-settings" type="button" class="pull-right btn btn-{{settingsButtonClass}}" data-toggle="modal" data-target="#modal-settings"><span class="glyphicon glyphicon-cog"></span></button>
- <strong class="text-capitalize">{{lang_displaySelection}}</strong>
- </div>
+
+ <div class="page-header">
+ <button id="button-settings" type="button" class="btn btn-{{settingsButtonClass}} pull-right" data-toggle="modal" data-target="#modal-settings"><span class="glyphicon glyphicon-cog"></span> Settings</button>
+ <h1>{{lang_moduleName}}</h1>
</div>
- <div class="row top-row">
- <div class="col-md-4">
- <select name="type" id="select-table" class="form-control">
- {{#tables}}
- <option value="{{value}}" {{selected}}>{{name}}</option>
- {{/tables}}
- </select>
- </div>
- <div class="col-md-4">
- <select name="cutoff" id="select-cutoff" class="form-control">
- {{#days}}
- <option value="{{days}}" {{selected}}>{{days}} {{lang_days}}</option>
- {{/days}}
- </select>
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_displaySelection}}
</div>
- <div class="col-md-3">
- <div id="slider">
- <div id="lower-handle" class="ui-slider-handle"></div>
- <div id="upper-handle" class="ui-slider-handle"></div>
- <input type="hidden" id="lower-field" name="lower" value="{{lower}}">
- <input type="hidden" id="upper-field" name="upper" value="{{upper}}">
+ <div class="panel-body">
+ <div class="row top-row">
+ <div class="col-md-2">
+ <select name="type" id="select-table" class="form-control">
+ {{#tables}}
+ <option {{^allowed}}disabled{{/allowed}} value="{{value}}" {{selected}}>{{name}}</option>
+ {{/tables}}
+ </select>
+ </div>
+ <div class="col-md-2">
+ <select name="cutoff" id="select-cutoff" class="form-control">
+ {{#days}}
+ <option value="{{days}}" {{selected}}>{{days}} {{lang_days}}</option>
+ {{/days}}
+ </select>
+ </div>
+ <div class="col-md-3" style="margin-top: 10px;">
+ <div id="slider">
+ <div id="lower-handle" class="ui-slider-handle"></div>
+ <div id="upper-handle" class="ui-slider-handle"></div>
+ <input type="hidden" id="lower-field" name="lower" value="{{lower}}">
+ <input type="hidden" id="upper-field" name="upper" value="{{upper}}">
+ </div>
+ </div>
+ <div class="col-md-1">
+ <button type="submit" class="btn btn-primary">{{lang_show}}</button>
+ </div>
+ <div class="col-md-3">
+ <div class="input-group">
+ <select class="form-control" name="format">
+ <option value="json">JSON</option>
+ <option value="csv">CSV (Excel)</option>
+ <option value="xml">XML</option>
+ </select>
+ <div class="input-group-btn">
+ <button {{^allowedExport}}disabled {{/allowedExport}} type="submit" class="btn btn-default" name="action" value="export">{{lang_export}}</button>
+ </div>
+ </div>
+ </div>
+
</div>
+
</div>
</div>
- <div class="row top-row">
- <div class="col-md-12 form-inline">
- <div><strong class="text-capitalize">{{lang_displayColumns}}</strong></div>
- {{#columns}}
- <div class="checkbox">
- <input id="id_{{id}}" name="{{id}}" value="on" type="checkbox" class="column-toggle form-control" {{checked}}>
- <label for="id_{{id}}">{{name}}</label>
- </div>
- {{/columns}}
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_displayColumns}}
</div>
- </div>
- <div class="row top-row">
- <div class="col-md-12 form-inline">
- <div class="pull-right input-group">
- <select class="form-control" name="format">
- <option value="json">JSON</option>
- <option value="csv">CSV (Excel)</option>
- <option value="xml">XML</option>
- </select>
- <div class="input-group-btn">
- <button type="submit" class="btn btn-default" name="action" value="export">{{lang_export}}</button>
+ <div class="panel-body">
+ <div class="row top-row">
+ <div class="col-md-12 form-inline">
+ {{#columns}}
+ <div class="checkbox">
+ <input id="id_{{id}}" name="{{id}}" value="on" type="checkbox" class="column-toggle form-control" {{checked}}>
+ <label for="id_{{id}}">{{name}}</label>
+ </div>
+ {{/columns}}
</div>
</div>
- <button type="submit" class="btn btn-primary">{{lang_apply}}</button>
</div>
</div>
</form>
-<hr>
<div id="modal-settings" class="modal fade" role="dialog">
<div class="modal-dialog">
@@ -71,17 +86,18 @@
</div>
<div class="modal-body">
<div class="checkbox">
- <input id="checkbox-reporting" type="checkbox" value="on" {{reportChecked}}>
- <label for="checkbox-reporting" style="padding-left: 40px">{{lang_reportingLabel}}</label>
+ <input {{^allowedReportChange}}disabled {{/allowedReportChange}} id="checkbox-reporting" type="checkbox" value="on" {{reportChecked}}>
+ <label for="checkbox-reporting" style="padding-left: 20px">{{lang_reportingLabel}}</label>
</div>
<div>
<p>{{lang_reportingDescription}}</p>
<p><a href="https://www.bwlehrpool.de/doku.php/satellite/statistics_reporting" target="_blank">{{lang_reportMoreInfo}}</a></p>
- <a class="btn btn-success" href="?do=statistics_reporting&amp;action=getreport">{{lang_downloadReport}}</a>
</div>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="saveSettings()">{{lang_save}}</button>
+ <button {{^allowedDownload}}disabled {{/allowedDownload}} class="btn btn-warning pull-left" onclick="window.location.href='?do=statistics_reporting&amp;action=getreport'">{{lang_downloadReport}}</button>
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button {{^allowedReportChange}}disabled {{/allowedReportChange}} type="button" class="btn btn-primary" data-dismiss="modal" onclick="saveSettings()">{{lang_save}}</button>
</div>
</div>
@@ -112,15 +128,6 @@
},
});
- var table = $("table").stupidtable();
- table.on("aftertablesort", function (event, data) {
- var th = $(this).find("th");
- th.find(".arrow").remove();
- var dir = $.fn.stupidtable.dir;
- var arrow = data.direction === dir.ASC ? "up" : "down";
- th.eq(data.column).append(' <span class="arrow glyphicon glyphicon-chevron-'+arrow+'"></span>');
- });
-
$(".locationLink").click(function(e) {
e.preventDefault();
var form = $('#controlsForm');
diff --git a/modules-available/statistics_reporting/templates/table-client.html b/modules-available/statistics_reporting/templates/table-client.html
index be504cef..59153e01 100644
--- a/modules-available/statistics_reporting/templates/table-client.html
+++ b/modules-available/statistics_reporting/templates/table-client.html
@@ -1,4 +1,4 @@
-<table id="table-perclient" class="table table-condensed table-striped">
+<table id="table-perclient" class="table table-condensed table-striped stupidtable">
<thead>
<tr>
<th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th>
diff --git a/modules-available/statistics_reporting/templates/table-location.html b/modules-available/statistics_reporting/templates/table-location.html
index ccac623d..a0867208 100644
--- a/modules-available/statistics_reporting/templates/table-location.html
+++ b/modules-available/statistics_reporting/templates/table-location.html
@@ -1,4 +1,4 @@
-<table id="table-perlocation" class="table table-condensed table-striped">
+<table id="table-perlocation" class="table table-condensed table-striped stupidtable">
<thead>
<tr>
<th data-sort="string" class="text-left col-md-2">{{lang_location}}</th>
diff --git a/modules-available/statistics_reporting/templates/table-total.html b/modules-available/statistics_reporting/templates/table-total.html
index 4048a178..8d5d7571 100644
--- a/modules-available/statistics_reporting/templates/table-total.html
+++ b/modules-available/statistics_reporting/templates/table-total.html
@@ -1,4 +1,4 @@
-<table id="table-total" class="table table-condensed table-striped">
+<table id="table-total" class="table table-condensed table-striped stupidtable">
<thead>
<tr>
<th class="text-left col-md-2"></th>
diff --git a/modules-available/statistics_reporting/templates/table-user.html b/modules-available/statistics_reporting/templates/table-user.html
index 5c2ba56f..ea4d20f5 100644
--- a/modules-available/statistics_reporting/templates/table-user.html
+++ b/modules-available/statistics_reporting/templates/table-user.html
@@ -1,4 +1,4 @@
-<table id="table-peruser" class="table table-condensed table-striped">
+<table id="table-peruser" class="table table-condensed table-striped stupidtable">
<thead>
<tr>
<th data-sort="string" class="text-left col-md-4">{{lang_user}}</th>
diff --git a/modules-available/statistics_reporting/templates/table-vm.html b/modules-available/statistics_reporting/templates/table-vm.html
index 9a775709..4ffb4df2 100644
--- a/modules-available/statistics_reporting/templates/table-vm.html
+++ b/modules-available/statistics_reporting/templates/table-vm.html
@@ -1,4 +1,4 @@
-<table id="table-pervm" class="table table-condensed table-striped">
+<table id="table-pervm" class="table table-condensed table-striped stupidtable">
<thead>
<tr>
<th data-sort="string" class="text-left col-md-4">{{lang_vm}}</th>
diff --git a/modules-available/sysconfig/addmodule_adauth.inc.php b/modules-available/sysconfig/addmodule_adauth.inc.php
index 6e4463ae..07806061 100644
--- a/modules-available/sysconfig/addmodule_adauth.inc.php
+++ b/modules-available/sysconfig/addmodule_adauth.inc.php
@@ -13,7 +13,7 @@ class AdAuth_Start extends AddModule_Base
protected function renderInternal()
{
- $ADAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'fixnumeric', 'certificate');
+ $ADAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'fixnumeric', 'certificate', 'mapping');
$data = array();
if ($this->edit !== false) {
moduleToArray($this->edit, $data, $ADAUTH_COMMON_FIELDS);
@@ -31,7 +31,12 @@ class AdAuth_Start extends AddModule_Base
if (isset($data['server']) && preg_match('/^(.*)\:(636|3269|389|3268)$/', $data['server'], $out)) {
$data['server'] = $out[1];
}
+ if (isset($data['homeattr']) && !isset($data['mapping']['homemount'])) {
+ $data['mapping']['homemount'] = $data['homeattr'];
+ }
$data['step'] = 'AdAuth_CheckConnection';
+ $data['map_empty'] = true;
+ $data['mapping'] = ConfigModuleBaseLdap::getMapping(isset($data['mapping']) ? $data['mapping'] : false, $data['map_empty']);
Render::addDialog(Dictionary::translateFile('config-module', 'adAuth_title'), false, 'ad-start', $data);
}
@@ -67,10 +72,11 @@ class AdAuth_CheckConnection extends AddModule_Base
if (preg_match('/^([^\:]+)\:(\d+)$/', $this->server, $out)) {
$ports = array($out[2]);
$this->server = $out[1];
+ // Test the default ports twice since the other one might not return all required data (home directory)
} elseif ($ssl) {
- $ports = array(636, 3269);
+ $ports = array(636, 3269, 636);
} else {
- $ports = array(389, 3268);
+ $ports = array(389, 3268, 389);
}
$this->scanTask = Taskmanager::submit('PortScan', array(
'host' => $this->server,
@@ -97,7 +103,8 @@ class AdAuth_CheckConnection extends AddModule_Base
'ssl' => Request::post('ssl'),
'fixnumeric' => Request::post('fixnumeric'),
'certificate' => Request::post('certificate', ''),
- 'taskid' => $this->scanTask['id']
+ 'taskid' => $this->scanTask['id'],
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
);
$data['prev'] = 'AdAuth_Start';
if ((preg_match(AD_BOTH_REGEX, $this->bindDn) > 0) || (strlen($this->searchBase) < 2)) {
@@ -157,8 +164,8 @@ class AdAuth_SelfSearch extends AddModule_Base
$taskData['filter'] = 'sAMAccountName=' . $out[2];
} elseif (preg_match(AD_AT_REGEX, $binddn, $out) && !empty($out[1])) {
$this->originalBindDn = $binddn;
- $taskData['filter'] = 'sAMAccountName=' . $out[1];
- } elseif (preg_match('/^cn\=([^\=]+),.*?,dc\=([^\=]+),/i', Ldap::normalizeDn($binddn), $out)) {
+ $taskData['filter'] = 'userPrincipalName=' . $binddn;
+ } elseif (preg_match('/^cn\=([^\=]+),.*?dc\=([^\=]+),/i', Ldap::normalizeDn($binddn), $out)) {
if (empty($selfSearchBase)) {
$this->originalBindDn = $out[2] . '\\' . $out[1];
$taskData['filter'] = 'sAMAccountName=' . $out[1];
@@ -198,6 +205,7 @@ class AdAuth_SelfSearch extends AddModule_Base
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => $this->originalBindDn,
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'AdAuth_Start'
);
if (empty($data['homeattr'])) {
@@ -275,6 +283,7 @@ class AdAuth_HomeAttrCheck extends AddModule_Base
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
'tryHomeAttr' => true,
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_CheckCredentials'
))
@@ -316,7 +325,8 @@ class AdAuth_CheckCredentials extends AddModule_Base
'server' => $uri,
'searchbase' => $searchbase,
'binddn' => $binddn,
- 'bindpw' => $bindpw
+ 'bindpw' => $bindpw,
+ 'mapping' => Request::post('mapping', false, 'array'),
));
if (!isset($ldapSearch['id'])) {
AddModule_Base::setStep('AdAuth_Start'); // Continues with AdAuth_Start for render()
@@ -325,8 +335,6 @@ class AdAuth_CheckCredentials extends AddModule_Base
$this->taskIds = array(
'tm-search' => $ldapSearch['id']
);
- if (isset($selfSearch['id']))
- $this->taskIds['self-search'] = $selfSearch['id'];
}
protected function renderInternal()
@@ -345,6 +353,7 @@ class AdAuth_CheckCredentials extends AddModule_Base
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_HomeDir'
))
@@ -408,6 +417,7 @@ class AdAuth_HomeDir extends AddModule_Base
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'AdAuth_Start',
'next' => 'AdAuth_Finish'
);
@@ -466,6 +476,7 @@ class AdAuth_Finish extends AddModule_Base
$module->setData('homeattr', Request::post('homeattr'));
$module->setData('certificate', Request::post('certificate'));
$module->setData('ssl', $ssl);
+ $module->setData('mapping', Request::post('mapping', false, 'array'));
$module->setData('fixnumeric', Request::post('fixnumeric', '', 'string'));
foreach (AdAuth_HomeDir::getAttributes() as $key) {
$value = Request::post($key);
diff --git a/modules-available/sysconfig/addmodule_ldapauth.inc.php b/modules-available/sysconfig/addmodule_ldapauth.inc.php
index 07c494cc..a193f779 100644
--- a/modules-available/sysconfig/addmodule_ldapauth.inc.php
+++ b/modules-available/sysconfig/addmodule_ldapauth.inc.php
@@ -9,21 +9,26 @@ class LdapAuth_Start extends AddModule_Base
protected function renderInternal()
{
- $LDAPAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'ssl', 'fixnumeric', 'certificate');
+ $LDAPAUTH_COMMON_FIELDS = array('title', 'server', 'searchbase', 'binddn', 'bindpw', 'home', 'homeattr', 'ssl', 'fixnumeric', 'certificate', 'mapping');
$data = array();
if ($this->edit !== false) {
moduleToArray($this->edit, $data, $LDAPAUTH_COMMON_FIELDS);
$data['title'] = $this->edit->title();
$data['edit'] = $this->edit->id();
}
- if ($data['fixnumeric'] === false) {
+ if (!isset($data['fixnumeric']) || $data['fixnumeric'] === false) {
$data['fixnumeric'] = 's';
}
postToArray($data, $LDAPAUTH_COMMON_FIELDS, true);
- if (preg_match('/^(.*)\:(636|389)$/', $data['server'], $out)) {
+ if (isset($data['server']) && preg_match('/^(.*)\:(636|389)$/', $data['server'], $out)) {
$data['server'] = $out[1];
}
+ if (isset($data['homeattr']) && !isset($data['mapping']['homemount'])) {
+ $data['mapping']['homemount'] = $data['homeattr'];
+ }
$data['step'] = 'LdapAuth_CheckConnection';
+ $data['map_empty'] = true;
+ $data['mapping'] = ConfigModuleBaseLdap::getMapping(isset($data['mapping']) ? $data['mapping'] : false, $data['map_empty']);
Render::addDialog(Dictionary::translateFile('config-module', 'ldapAuth_title'), false, 'ldap-start', $data);
}
@@ -77,7 +82,8 @@ class LdapAuth_CheckConnection extends AddModule_Base
'ssl' => Request::post('ssl'),
'fixnumeric' => Request::post('fixnumeric'),
'certificate' => Request::post('certificate', ''),
- 'taskid' => $this->scanTask['id']
+ 'taskid' => $this->scanTask['id'],
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
);
$data['prev'] = 'LdapAuth_Start';
$data['next'] = 'LdapAuth_CheckCredentials';
@@ -123,6 +129,7 @@ class LdapAuth_CheckCredentials extends AddModule_Base
'binddn' => $binddn,
'bindpw' => $bindpw,
'plainldap' => true,
+ 'mapping' => Request::post('mapping', false, 'array'),
));
if (!isset($ldapSearch['id'])) {
AddModule_Base::setStep('LdapAuth_Start'); // Continues with LdapAuth_Start for render()
@@ -131,8 +138,6 @@ class LdapAuth_CheckCredentials extends AddModule_Base
$this->taskIds = array(
'tm-search' => $ldapSearch['id']
);
- if (isset($selfSearch['id']))
- $this->taskIds['self-search'] = $selfSearch['id'];
}
protected function renderInternal()
@@ -149,8 +154,9 @@ class LdapAuth_CheckCredentials extends AddModule_Base
'fixnumeric' => Request::post('fixnumeric'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'LdapAuth_Start',
- 'next' => 'LdapAuth_HomeDir'
+ 'next' => 'LdapAuth_HomeDir',
))
);
}
@@ -184,14 +190,14 @@ class LdapAuth_HomeDir extends AddModule_Base
'binddn' => Request::post('binddn'),
'bindpw' => Request::post('bindpw'),
'home' => Request::post('home'),
- 'homeattr' => Request::post('homeattr'),
'ssl' => Request::post('ssl') === 'on',
'fixnumeric' => Request::post('fixnumeric'),
'fingerprint' => Request::post('fingerprint'),
'certificate' => Request::post('certificate', ''),
'originalbinddn' => Request::post('originalbinddn'),
+ 'mapping' => ConfigModuleBaseLdap::getMapping(Request::post('mapping', false, 'array')),
'prev' => 'LdapAuth_Start',
- 'next' => 'LdapAuth_Finish'
+ 'next' => 'LdapAuth_Finish',
);
if ($this->edit !== false) {
foreach (self::getAttributes() as $key) {
@@ -249,6 +255,7 @@ class LdapAuth_Finish extends AddModule_Base
$module->setData('home', Request::post('home'));
$module->setData('certificate', Request::post('certificate'));
$module->setData('ssl', $ssl);
+ $module->setData('mapping', Request::post('mapping', false, 'array'));
$module->setData('fixnumeric', Request::post('fixnumeric', '', 'string'));
foreach (LdapAuth_HomeDir::getAttributes() as $key) {
$value = Request::post($key);
diff --git a/modules-available/sysconfig/clientscript.js b/modules-available/sysconfig/clientscript.js
index c86b8148..f3a47824 100644
--- a/modules-available/sysconfig/clientscript.js
+++ b/modules-available/sysconfig/clientscript.js
@@ -1,33 +1,86 @@
// Mouseover and clicking
+var $ct = $('#conftable').find('.confrow');
+$ct.click(function() {
+ showmod(this, 'bold');
+}).mouseenter(function() {
+ showmod(this, 'fade');
+}).mouseleave(function() {
+ showmod(this, 'reset');
+});
+var $mt = $('#modtable').find('.modrow');
+$mt.click(function() {
+ showconf(this, 'bold');
+}).mouseenter(function() {
+ showconf(this, 'fade');
+}).mouseleave(function() {
+ showconf(this, 'reset');
+});
+
var boldItem = false;
+var revList = false;
-function showmod(e, action) {
- var list = $(e).attr('data-modlist');
- list = list.split(',');
- if (action === 'bold') {
- $(boldItem).removeClass("slx-bold");
- if (boldItem === e) {
- action = 'fade';
- boldItem = false;
+function showpre(e, action) {
+ if (boldItem && action !== 'bold') return 'reset';
+ if (boldItem) {
+ if (e === boldItem) action = 'fade';
+ boldItem = false;
+ }
+ $mt.removeClass("slx-bold slx-fade");
+ $ct.removeClass("slx-bold slx-fade");
+ return action;
+}
+
+function buildRevList() {
+ revList = {};
+ $ct.each(function() {
+ var elem = $(this);
+ var cid = elem.data('id')+'';
+ var list = (elem.data('modlist')+'').split(',');
+ for (var i = 0; i < list.length; ++i) {
+ if (!revList[list[i]]) revList[list[i]] = [];
+ revList[list[i]].push(cid);
}
- } else if (boldItem !== false) {
- return;
+ });
+}
+
+function showconf(e, action) {
+ action = showpre(e, action);
+ if (action === 'reset') return;
+ var $e = $(e);
+ if (!revList) buildRevList();
+ var mid = $e.data('id')+'';
+ var list = revList[mid];
+ if (list && list.length > 0) $ct.each(function() {
+ var elem = $(this);
+ var cid = elem.data('id')+'';
+ if (list.indexOf(cid) === -1)
+ elem.addClass('slx-fade');
+ else if (action === 'bold')
+ elem.addClass('slx-bold');
+ }); else $ct.addClass('slx-fade');
+ if (action === 'bold') {
+ boldItem = e;
+ $e.addClass("slx-bold");
}
- $('.modrow').each(function () {
+}
+
+function showmod(e, action) {
+ action = showpre(e, action);
+ if (action === 'reset') return;
+ var $e = $(e);
+ var list = ($e.data('modlist')+'').split(',');
+ $mt.each(function () {
var elem = $(this);
- elem.removeClass("slx-fade slx-bold");
- if (action === 'reset')
- return;
- if (action === 'bold' && list.indexOf(elem.attr('data-id')) !== -1)
- elem.addClass("slx-bold");
- if (list.indexOf(elem.attr('data-id')) === -1)
+ if (list.indexOf(elem.data('id')+'') === -1)
elem.addClass("slx-fade");
+ else if (action === 'bold')
+ elem.addClass("slx-bold");
});
if (action === 'bold') {
boldItem = e;
- $(e).addClass("slx-bold");
+ $e.addClass("slx-bold");
}
}
diff --git a/modules-available/sysconfig/hooks/cron.inc.php b/modules-available/sysconfig/hooks/cron.inc.php
index d1f91437..b518ca06 100644
--- a/modules-available/sysconfig/hooks/cron.inc.php
+++ b/modules-available/sysconfig/hooks/cron.inc.php
@@ -1,3 +1,8 @@
<?php
-Trigger::ldadp(); \ No newline at end of file
+Trigger::ldadp();
+
+// Cleanup orphaned config<->location where the location has been deleted
+Database::exec("DELETE c FROM configtgz_location c
+ LEFT JOIN location l USING (locationid)
+ WHERE l.locationid IS NULL AND c.locationid <> 0");
diff --git a/modules-available/sysconfig/inc/configmodule.inc.php b/modules-available/sysconfig/inc/configmodule.inc.php
index ca40094a..54d06afe 100644
--- a/modules-available/sysconfig/inc/configmodule.inc.php
+++ b/modules-available/sysconfig/inc/configmodule.inc.php
@@ -16,6 +16,9 @@ abstract class ConfigModule
private $moduleTitle = false;
private $moduleStatus = false;
private $currentVersion = 0;
+ /**
+ * @var false|array Data of module, false if not initialized
+ */
protected $moduleData = false;
/**
@@ -86,7 +89,7 @@ abstract class ConfigModule
* Get fresh instance of ConfigModule subclass for given module type.
*
* @param string $moduleType name of module type
- * @return \ConfigModule module instance
+ * @return false|\ConfigModule module instance
*/
public static function getInstance($moduleType)
{
@@ -117,7 +120,7 @@ abstract class ConfigModule
* Get module instance from id.
*
* @param int $moduleId module id to get
- * @return ConfigModule The requested module from DB, or false on error
+ * @return false|\ConfigModule The requested module from DB, or false on error
*/
public static function get($moduleId)
{
diff --git a/modules-available/sysconfig/inc/configmodulebaseldap.inc.php b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
index 686bcbc0..d8a41a8b 100644
--- a/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
+++ b/modules-available/sysconfig/inc/configmodulebaseldap.inc.php
@@ -8,7 +8,28 @@ abstract class ConfigModuleBaseLdap extends ConfigModule
private static $REQUIRED_FIELDS = array('server', 'searchbase');
private static $OPTIONAL_FIELDS = array('binddn', 'bindpw', 'home', 'ssl', 'fixnumeric', 'fingerprint', 'certificate', 'homeattr',
'shareRemapMode', 'shareRemapCreate', 'shareDocuments', 'shareDownloads', 'shareDesktop', 'shareMedia',
- 'shareOther', 'shareHomeDrive', 'shareDomain', 'credentialPassthrough');
+ 'shareOther', 'shareHomeDrive', 'shareDomain', 'credentialPassthrough', 'mapping');
+
+ public static function getMapping($config = false, &$empty = true)
+ {
+ $list = array(
+ ['name' => 'uid', 'field' => 'uid', 'ad' => 'sAMAccountName'],
+ ['name' => 'uidnumber', 'field' => 'uidnumber', 'ad' => false],
+ ['name' => 'uncHomePath', 'field' => 'homemount', 'ad' => 'homeDirectory'],
+ ['name' => 'homeDirectory', 'field' => 'localhome', 'ad' => false],
+ ['name' => 'posixAccount', 'field' => 'posixAccount', 'ad' => 'user'],
+ //['name' => 'shadowAccount', 'field' => 'shadowAccount'],
+ );
+ if (is_array($config)) {
+ foreach ($list as &$item) {
+ if (!empty($config[$item['field']])) {
+ $item['value'] = $config[$item['field']];
+ $empty = false;
+ }
+ }
+ }
+ return $list;
+ }
protected function generateInternal($tgz, $parent)
{
diff --git a/modules-available/sysconfig/install.inc.php b/modules-available/sysconfig/install.inc.php
index 91f282dd..3e99b777 100644
--- a/modules-available/sysconfig/install.inc.php
+++ b/modules-available/sysconfig/install.inc.php
@@ -39,21 +39,18 @@ $res[] = tableCreate('configtgz_location', "
// Constraints
if (in_array(UPDATE_DONE, $res)) {
- $ret = Database::exec("ALTER TABLE `configtgz_x_module`
- ADD CONSTRAINT `configtgz_x_module_ibfk_1` FOREIGN KEY (`configid`) REFERENCES `configtgz` (`configid`)
- ON DELETE CASCADE");
- $ret = Database::exec("ALTER TABLE `configtgz_x_module`
- ADD CONSTRAINT `configtgz_x_module_ibfk_2` FOREIGN KEY (`moduleid`) REFERENCES `configtgz_module` (`moduleid`)") || $ret;
- $ret = Database::exec("ALTER TABLE `configtgz_location`
- ADD CONSTRAINT `configtgz_location_fk_configid` FOREIGN KEY ( `configid` ) REFERENCES `openslx`.`configtgz` (`configid`)
- ON DELETE CASCADE ON UPDATE CASCADE") || $ret;
- if ($ret) {
- $res[] = UPDATE_DONE;
- }
+ // To self
+ $res[] = tableAddConstraint('configtgz_x_module', 'configid', 'configtgz', 'configid',
+ '');
+ $res[] = tableAddConstraint('configtgz_x_module', 'moduleid', 'configtgz_module', 'moduleid',
+ '');
+ $res[] = tableAddConstraint('configtgz_location', 'configid', 'configtgz', 'configid',
+ 'ON DELETE CASCADE ON UPDATE CASCADE');
}
// Update path
+// #######################
// ##### 2014-12-12
// Rename config modules
Database::exec("UPDATE configtgz_module SET moduletype = 'Branding' WHERE moduletype = 'BRANDING'");
@@ -98,9 +95,4 @@ if ($list === false) {
}
// Create response for browser
-
-if (in_array(UPDATE_DONE, $res)) {
- finalResponse(UPDATE_DONE, 'Tables created successfully');
-}
-
-finalResponse(UPDATE_NOOP, 'Everything already up to date');
+responseFromArray($res);
diff --git a/modules-available/sysconfig/lang/de/messages.json b/modules-available/sysconfig/lang/de/messages.json
index 0a1f6de3..5bceb2f0 100644
--- a/modules-available/sysconfig/lang/de/messages.json
+++ b/modules-available/sysconfig/lang/de/messages.json
@@ -2,7 +2,7 @@
"config-activated": "Konfiguration {{0}} wurde aktiviert",
"config-deleted": "Konfiguration {{0}} wurde gel\u00f6scht",
"config-invalid": "Konfiguration mit ID {{0}} existiert nicht",
- "could-not-determine-binddn": "Konnte Bind-DN nicht ermitteln",
+ "could-not-determine-binddn": "Konnte Bind-DN nicht ermitteln ({{0}})",
"invalid-action": "Ung\u00fcltige Aktion: {{0}}",
"missing-file": "Es wurde keine Datei ausgew\u00e4hlt!",
"missing-title": "Kein Titel eingegeben",
diff --git a/modules-available/sysconfig/lang/de/module.json b/modules-available/sysconfig/lang/de/module.json
index 056cd158..4b401437 100644
--- a/modules-available/sysconfig/lang/de/module.json
+++ b/modules-available/sysconfig/lang/de/module.json
@@ -1,4 +1,5 @@
{
+ "config-module": "Konfigurationsmodul",
"lang_clientSshConfig": "SSH-Konfiguration",
"lang_configurationCompilation": "Konfiguration zusammenstellen",
"lang_contentOf": "Inhalt von",
diff --git a/modules-available/sysconfig/lang/de/template-tags.json b/modules-available/sysconfig/lang/de/template-tags.json
index d3b0bb9f..9ad29e90 100644
--- a/modules-available/sysconfig/lang/de/template-tags.json
+++ b/modules-available/sysconfig/lang/de/template-tags.json
@@ -33,6 +33,9 @@
"lang_customCertificate": "Zur Validierung zus\u00e4tzlich erforderliche (Intermediate-)Zertifikate",
"lang_customModuleInfo1": "\u00dcber ein benutzerdefiniertes Modul ist es m\u00f6glich, beliebige Dateien zum Linux-Grundsystem, das auf den Clients gebootet wird, hinzuzuf\u00fcgen. Dazu kann ein Archiv mit einer Dateisystemstruktur hochgeladen werden, die in dieser Form 1:1 in das gebootete Linux extrahiert wird.",
"lang_customModuleInfo2": "Beispiel: Enth\u00e4lt das hochgeladene Archiv eine Datei etc\/beispiel.conf, so wird auf einem gebooteten Client diese Datei als \/etc\/beispiel.conf zu finden sein.",
+ "lang_customizeAttrDesc": "Hier k\u00f6nnen Sie die Standardwerte f\u00fcr bestimmte Attribute und deren Werte \u00fcberschreiben, wenn ihr LDAP-Schema nicht dem \u00fcblichen Unix-Schema entspricht.",
+ "lang_customizeAttrDescAd": "Hier k\u00f6nnen Sie die Standardwerte f\u00fcr bestimmte Attribute und deren Werte \u00fcberschreiben, wenn Sie z.B. den cn oder userPrincipalName f\u00fcr den Login verwenden wollen, anstelle des sAMAccountNames, oder der Pfad zum Netzlaufwerk des Benutzers nicht im Attribut homeDirectory zu finden ist.",
+ "lang_customizeAttributes": "Attribute anpassen",
"lang_deleteLong": "Modul oder Konfiguration l\u00f6schen.",
"lang_determiningHomeDirectory": "Versuche Attribut f\u00fcr das Home-Verzeichnis zu ermitteln...",
"lang_dnLookup": "Ermitteln der Bind-DN",
@@ -41,7 +44,7 @@
"lang_driveLetterNote": "WICHTIG: Bitte w\u00e4hlen Sie einen Laufwerksbuchstaben, der in den eingesetzten VMs verf\u00fcgbar ist, da ansonsten auf einen anderen Buchstaben ausgewichen werden muss.",
"lang_editLong": "Modul oder Konfiguration bearbeiten.",
"lang_editingLocationInfo": "Sie setzen die Konfiguration eines bestimmten Raums\/Orts, nicht die globale Konfiguration",
- "lang_fixNumeric": "Nummerischen Account-Namen muss ein 's' vorangestellt werden",
+ "lang_fixNumeric": "Numerischen Account-Namen muss ein 's' vorangestellt werden",
"lang_fixNumericDescription": "Wenn Sie diese Option aktivieren, m\u00fcssen Benutzer, deren Account-Name nur aus Ziffern besteht, diesem ein 's' voranstellen beim Login. Diese Option ist beim alten Login-Manager (KDM) zwingend erforderlich, da sonst der Loginvorgang fehlschl\u00e4gt. Mit dem neuen lightdm-basierten Login-Screen lassen sich numerische Account-Namen jedoch direkt verwenden. Wenn Sie an Ihrer Einrichtung keine numerischen Account-Namen verwenden, hat diese Option keine Auswirkung.",
"lang_folderRedirection": "Folder Redirection",
"lang_generateModule": "Modul erzeugen",
@@ -52,6 +55,7 @@
"lang_helpSystemConfiguration": "\u00dcber eine Systemkonfiguration wird die grundlegende Lokalisierung des bwLehrpool-Systems durchgef\u00fchrt. Dazu geh\u00f6ren Aspekte wie das Authentifizierungsverfahren f\u00fcr Benutzer (z.B. Active Directory, LDAP), Druckerkonfiguration, Home-Verzeichnisse, etc. Eine Systemkonfiguration setzt sich aus einem oder mehreren Konfigurationsmodulen zusammen, welche im angrenzenden Panel verwaltet werden k\u00f6nnen.",
"lang_homeAttr": "Home-Attribut",
"lang_homeAttributeExplanation": "Bitte w\u00e4hlen Sie das Attribut, welches das Home-Verzeichnis der User enth\u00e4lt.",
+ "lang_homeFallback": "Home-Fallback",
"lang_homedirHandling": "(Home-)Verzeichnis Einbindung",
"lang_inheritFromParentLoc": "Von \u00fcbergeordnetem Ort erben",
"lang_ldapStarted": "Der LDAP-Proxy wurde gestartet",
@@ -109,7 +113,7 @@
"lang_supportedFiles": "Unterst\u00fctzte Archivformate",
"lang_systemConfiguration": "Systemkonfiguration",
"lang_systemConfigurationAlert": "Bevor Sie eine Systemkonfiguration erstellen k\u00f6nnen, m\u00fcssen Sie zun\u00e4chst ein Konfigurationsmodul erzeugen.",
- "lang_systemConfigurationNotFound": "Keine Systemkonfigurationen gefunden.Erstellen Sie eine neue Konfiguration aus den unten aufgef\u00fchrten Konfigurationsmodulen.",
+ "lang_systemConfigurationNotFound": "Keine Systemkonfigurationen gefunden. Erstellen Sie eine neue Konfiguration aus den unten aufgef\u00fchrten Konfigurationsmodulen.",
"lang_title": "Titel",
"lang_to": "Zur",
"lang_toSystemConfiguration": "Zur Systemkonfiguration",
diff --git a/modules-available/sysconfig/lang/en/messages.json b/modules-available/sysconfig/lang/en/messages.json
index 83f47903..6e50b80c 100644
--- a/modules-available/sysconfig/lang/en/messages.json
+++ b/modules-available/sysconfig/lang/en/messages.json
@@ -2,7 +2,7 @@
"config-activated": "Configuration {{0}} has been activated",
"config-deleted": "Deleted configuration {{0}}",
"config-invalid": "Configuration with id {{0}} does not exist",
- "could-not-determine-binddn": "Could not determine bind dn",
+ "could-not-determine-binddn": "Could not determine bind dn ({{0}})",
"invalid-action": "Invalid action: {{0}}",
"missing-file": "There was no file selected!",
"missing-title": "No title given",
diff --git a/modules-available/sysconfig/lang/en/module.json b/modules-available/sysconfig/lang/en/module.json
index 526f6562..ba2d0591 100644
--- a/modules-available/sysconfig/lang/en/module.json
+++ b/modules-available/sysconfig/lang/en/module.json
@@ -1,4 +1,5 @@
{
+ "config-module": "Config module",
"lang_clientSshConfig": "SSH configuration",
"lang_configurationCompilation": "Compile configuration",
"lang_contentOf": "Content of",
@@ -7,4 +8,4 @@
"lang_unknwonTaskManager": "Unknown Task Manager error",
"module_name": "Localization",
"page_title": "Localize and integrate"
-}
+} \ No newline at end of file
diff --git a/modules-available/sysconfig/lang/en/template-tags.json b/modules-available/sysconfig/lang/en/template-tags.json
index 1374d87f..e2cfd114 100644
--- a/modules-available/sysconfig/lang/en/template-tags.json
+++ b/modules-available/sysconfig/lang/en/template-tags.json
@@ -33,11 +33,14 @@
"lang_customCertificate": "Additional (intermediate) certificates required for certificate validation",
"lang_customModuleInfo1": "About a custom module, it is possible to add arbitrary files to a Linux system that is booted clients. For this purpose, an archive can be uploaded using a file system structure that is extracted in this form 1:1 in the booted Linux.",
"lang_customModuleInfo2": "Example: If the uploaded archive is the file etc\/example.conf, this file will be located as \/etc\/example.conf to a booted client.",
+ "lang_customizeAttrDesc": "Here you can override attribute names and values if your LDAP scheme doesn't adhere to the usual Unix scheme.",
+ "lang_customizeAttrDescAd": "Here you can override attribute names and values, for example if you don't want to use the sAMAccountName for identification, but something like cn oder userPrincipalName, or maybe the user's network drive isn't to be found in the homeDirectory attribute.",
+ "lang_customizeAttributes": "Customize attributes",
"lang_deleteLong": "Delete module or configuration.",
"lang_determiningHomeDirectory": "Trying to determine home directory attribute...",
"lang_dnLookup": "Looking up bind dn",
"lang_download": "Download",
- "lang_downloadLong": "Download module \"as is\".",
+ "lang_downloadLong": "Download module \"as it is\".",
"lang_driveLetterNote": "IMPORTANT: Pick a drive letter for the home directory that will be free in the Virtual Machines. Otherwise, a random letter will be assigned.",
"lang_editLong": "Edit module or configuration.",
"lang_editingLocationInfo": "You're setting the configuration for a specific location, not the global one",
@@ -52,6 +55,7 @@
"lang_helpSystemConfiguration": "The fundamental localization of the bwLehrpool system is done through a system configuration. These include aspects such as the authentication method for users (eg Active Directory, LDAP), printer configuration, home directories, etc. A system configuration is composed of one or more configuration modules, which can be managed in the panel next to this one.",
"lang_homeAttr": "Home attribute",
"lang_homeAttributeExplanation": "Please select the attribute which holds the user's home directory.",
+ "lang_homeFallback": "Home fallback",
"lang_homedirHandling": "(Home) directory handling",
"lang_inheritFromParentLoc": "Inherit from parent location",
"lang_ldapStarted": "The LDAP proxy has been launched",
diff --git a/modules-available/sysconfig/page.inc.php b/modules-available/sysconfig/page.inc.php
index 8e5c706b..043645df 100644
--- a/modules-available/sysconfig/page.inc.php
+++ b/modules-available/sysconfig/page.inc.php
@@ -135,6 +135,9 @@ class Page_SysConfig extends Page
*/
protected function doRender()
{
+
+ Render::addTemplate('sysconfig_heading');
+
$action = Request::any('action', 'list');
switch ($action) {
case 'addmodule':
diff --git a/modules-available/sysconfig/templates/ad-selfsearch.html b/modules-available/sysconfig/templates/ad-selfsearch.html
index 6c5bcb8c..6b85b9ed 100644
--- a/modules-available/sysconfig/templates/ad-selfsearch.html
+++ b/modules-available/sysconfig/templates/ad-selfsearch.html
@@ -39,6 +39,9 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
@@ -60,6 +63,9 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
<button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_skip}} &raquo;</button>
diff --git a/modules-available/sysconfig/templates/ad-start.html b/modules-available/sysconfig/templates/ad-start.html
index 1f8e1e01..7f211343 100644
--- a/modules-available/sysconfig/templates/ad-start.html
+++ b/modules-available/sysconfig/templates/ad-start.html
@@ -1,8 +1,10 @@
<p>
{{lang_adText1}}
<br>
+ <br/>
{{lang_adText2}}
<br>
+ <br/>
{{lang_adText3}}
</p>
<pre>dsquery user -name &quot;Username&quot;</pre>
@@ -18,59 +20,72 @@
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_moduleTitle}}</span>
+ <span class="input-group-addon slx-ga2">{{lang_moduleTitle}}</span>
<input tabindex="1" name="title" value="{{title}}" type="text" class="form-control" autofocus>
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">Server *</span>
+ <span class="input-group-addon slx-ga2">Server *</span>
<input tabindex="2" name="server" value="{{server}}" type="text" class="form-control" placeholder="dc0.institution.example.com">
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_bindDN}} *</span>
+ <span class="input-group-addon slx-ga2">{{lang_bindDN}} *</span>
<input tabindex="3" name="binddn" value="{{binddn}}" type="text" class="form-control" placeholder="domain\bwlp *ODER* CN=bwlp,OU=Benutzer,DC=domain,DC=hs-beispiel,DC=de">
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_password}} *</span>
+ <span class="input-group-addon slx-ga2">{{lang_password}} *</span>
<input tabindex="4" name="bindpw" value="{{bindpw}}" type="{{password_type}}" class="form-control" placeholder="{{lang_password}}">
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_searchBase}}</span>
+ <span class="input-group-addon slx-ga2">{{lang_searchBase}}</span>
<input tabindex="5" name="searchbase" value="{{searchbase}}" type="text" class="form-control" placeholder="dc=windows,dc=hs-beispiel,dc=de">
</div>
- <br>
<div class="input-group">
- <span class="input-group-addon slx-ga">Home</span>
+ <span class="input-group-addon slx-ga2">Home</span>
<input tabindex="6" name="home" value="{{home}}" type="text" class="form-control" placeholder="\\server.example.com\%s">
<span class="input-group-btn">
<a class="btn btn-default" data-toggle="modal" data-target="#help-home"><span class="glyphicon glyphicon-question-sign"></span></a>
</span>
</div>
- <div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_homeAttr}}</span>
- <input tabindex="6" name="homeattr" value="{{homeattr}}" type="text" class="form-control" placeholder="homeDirectory">
- <span class="input-group-btn">
- <a class="btn btn-default" data-toggle="modal" data-target="#help-homeattr"><span class="glyphicon glyphicon-question-sign"></span></a>
- </span>
+ <br>
+ <div class="{{#map_empty}}collapse{{/map_empty}}" id="attrbox">
+ <p>{{lang_customizeAttrDescAd}}</p>
+ {{#mapping}}
+ {{#ad}}
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{name}}</span>
+ <input name="mapping[{{field}}]" value="{{value}}" type="text" class="form-control" placeholder="{{ad}}">
+ </div>
+ {{/ad}}
+ {{/mapping}}
</div>
+ {{#map_empty}}
+ <div class="btn btn-default center-block" onclick="$('#attrbox').show();$(this).hide()">
+ {{lang_customizeAttributes}}
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </div>
+ {{/map_empty}}
<br>
<div>
- <label>
- <input type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}> {{lang_fixNumeric}}
- </label>
+ <div class="checkbox">
+ <input id="num-cb" type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}>
+ <label for="num-cb"><b>{{lang_fixNumeric}}</b></label>
+ </div>
<div>
<i>{{lang_fixNumericDescription}}</i>
</div>
</div>
<br>
<div>
- <label>
- <input type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}> {{lang_ssl}}
- </label>
+ <div class="checkbox">
+ <input if="ssl-cb" type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}>
+ <label for="ssl-cb"><b>{{lang_ssl}}</b></label>
+ </div>
<div>
<i>{{lang_sslDescription}}</i>
</div>
</div>
<br/>
+
<hr>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
@@ -78,8 +93,10 @@
<div class="btn-group pull-right">
<button type="submit" class="btn btn-primary">{{lang_next}} &raquo;</button>
</div>
+
<div class="clearfix"></div>
<div {{^ssl}}style="display:none"{{/ssl}} id="cert-box">
+ <hr>
<div class="well well-sm" id="wcustom">
{{lang_customCertificate}}
<pre class="small">
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkconnection.html b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
index f3194308..630da398 100644
--- a/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
+++ b/modules-available/sysconfig/templates/ad_ldap-checkconnection.html
@@ -11,7 +11,7 @@
<div id="supplied-cert-invalid" style="display:none" class="alert alert-danger">{{lang_userCertInvalid}}</div>
<div id="trying-fingerprint" style="display:none" class="alert alert-warning">{{lang_tryingFingerprint}}</div>
<br>
-<div class="pull-left">
+<div class="text-left">
<form role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{prev}}">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="edit" value="{{edit}}">
@@ -26,11 +26,15 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
+
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
</div>
-<div class="pull-right">
+<div class="text-right">
<form id="nextform" role="form" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{next}}">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="edit" value="{{edit}}">
@@ -47,6 +51,9 @@
<input id="fingerprint" name="fingerprint" value="" type="hidden">
<input id="certificate" type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="originalbinddn" value="{{binddn}}" type="hidden">
<button id="nextbutton" type="submit" class="btn btn-primary" style="display:none">{{lang_next}} &raquo;</button>
diff --git a/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
index bf151da3..4f822a9b 100644
--- a/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
+++ b/modules-available/sysconfig/templates/ad_ldap-checkcredentials.html
@@ -22,6 +22,9 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<button type="submit" class="btn btn-primary">&laquo; {{lang_back}}</button>
</form>
@@ -42,6 +45,9 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
<input name="originalbinddn" value="{{binddn}}" type="hidden">
diff --git a/modules-available/sysconfig/templates/ad_ldap-homedir.html b/modules-available/sysconfig/templates/ad_ldap-homedir.html
index 10a43030..ad543594 100644
--- a/modules-available/sysconfig/templates/ad_ldap-homedir.html
+++ b/modules-available/sysconfig/templates/ad_ldap-homedir.html
@@ -14,6 +14,9 @@
<input name="ssl" value="on" type="hidden">
<input type="hidden" name="certificate" value="{{certificate}}">
{{/ssl}}
+ {{#mapping}}
+ <input type="hidden" name="mapping[{{field}}]" value="{{value}}">
+ {{/mapping}}
<input name="fixnumeric" value="{{fixnumeric}}" type="hidden">
<input name="fingerprint" value="{{fingerprint}}" type="hidden">
diff --git a/modules-available/sysconfig/templates/branding-check.html b/modules-available/sysconfig/templates/branding-check.html
index 1a021309..d48f9631 100644
--- a/modules-available/sysconfig/templates/branding-check.html
+++ b/modules-available/sysconfig/templates/branding-check.html
@@ -13,6 +13,7 @@
</div>
<div class="clearfix"></div>
<div>{{error}}</div>
+<br/>
<div>
<form role="form" enctype="multipart/form-data" method="post" action="?do=SysConfig&amp;action=addmodule&amp;step={{step}}">
<input type="hidden" name="token" value="{{token}}">
@@ -25,7 +26,7 @@
<a class="btn btn-default" href="?do=SysConfig&action=addmodule&step=Branding_Start">{{lang_cancel}}</a>
</div>
<div class="btn-group pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</form>
</div>
diff --git a/modules-available/sysconfig/templates/branding-start.html b/modules-available/sysconfig/templates/branding-start.html
index 7f2a0132..0db085d9 100644
--- a/modules-available/sysconfig/templates/branding-start.html
+++ b/modules-available/sysconfig/templates/branding-start.html
@@ -8,9 +8,8 @@
<label for="input-url">{{lang_urlLoad}}</label>
<input class="form-control" type="text" name="url" id="input-url">
</div>
- {{lang_or}}
<div class="form-group">
- <label for="input-file">{{lang_computerLoad}}</label>
+ <label for="input-file">{{lang_or}} {{lang_computerLoad}}</label>
<div class="input-group upload-ex">
<input type="text" class="form-control" readonly placeholder="{{lang_selectFile}}">
<span class="input-group-btn">
@@ -21,6 +20,7 @@
</div>
</div>
+ <hr/>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
</div>
diff --git a/modules-available/sysconfig/templates/cfg-finish.html b/modules-available/sysconfig/templates/cfg-finish.html
index c9622db0..21c7c690 100644
--- a/modules-available/sysconfig/templates/cfg-finish.html
+++ b/modules-available/sysconfig/templates/cfg-finish.html
@@ -6,8 +6,10 @@
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="config">
<input type="hidden" name="activate" value="{{configid}}">
- <div class="btn-group">
- <button type="submit" class="btn btn-primary">{{lang_activateGlobally}}</button>
- <a href="?do=SysConfig" class="btn btn-default">{{lang_backToSysconfig}}</a>
+ <div class="text-right">
+ <div class="btn-group">
+ <a href="?do=SysConfig" class="btn btn-default">{{lang_backToSysconfig}}</a>
+ <button type="submit" class="btn btn-primary">{{lang_activateGlobally}}</button>
+ </div>
</div>
</form>
diff --git a/modules-available/sysconfig/templates/cfg-start.html b/modules-available/sysconfig/templates/cfg-start.html
index 8c33f0e5..b4628cba 100644
--- a/modules-available/sysconfig/templates/cfg-start.html
+++ b/modules-available/sysconfig/templates/cfg-start.html
@@ -15,10 +15,16 @@
<div class="input-group">
<span class="input-group-addon">
{{#unique}}
- <input type="radio" name="module[{{groupid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ <div class="radio">
+ <input type="radio" name="module[{{groupid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ <label></label>
+ </div>
{{/unique}}
{{^unique}}
- <input type="checkbox" name="module[{{moduleid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ <div class="checkbox">
+ <input type="checkbox" name="module[{{moduleid}}]" value="{{moduleid}}" id="module{{moduleid}}" {{#active}}checked{{/active}}>
+ <label></label>
+ </div>
{{/unique}}
</span>
<label class="form-control" for="module{{moduleid}}">{{title}}</label>
diff --git a/modules-available/sysconfig/templates/custom-upload.html b/modules-available/sysconfig/templates/custom-upload.html
index 51677f8b..43636a49 100644
--- a/modules-available/sysconfig/templates/custom-upload.html
+++ b/modules-available/sysconfig/templates/custom-upload.html
@@ -15,6 +15,7 @@
</div>
<p class="help-block">{{lang_supportedFiles}}: .tar.gz, .tar.bz2, .zip</p>
+ <hr/>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
</div>
diff --git a/modules-available/sysconfig/templates/ldap-start.html b/modules-available/sysconfig/templates/ldap-start.html
index 22f4e2fa..940316b9 100644
--- a/modules-available/sysconfig/templates/ldap-start.html
+++ b/modules-available/sysconfig/templates/ldap-start.html
@@ -10,32 +10,32 @@
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="edit" value="{{edit}}">
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_moduleTitle}}</span>
+ <span class="input-group-addon slx-ga2">{{lang_moduleTitle}}</span>
<input tabindex="1" name="title" value="{{title}}" type="text" class="form-control">
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">Server *</span>
+ <span class="input-group-addon slx-ga2">Server *</span>
<input tabindex="2" name="server" value="{{server}}" type="text" class="form-control" placeholder="dc0.institution.example.com">
<!--span class="input-group-btn">
<a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
</span-->
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_bindDN}}</span>
+ <span class="input-group-addon slx-ga2">{{lang_bindDN}}</span>
<input tabindex="3" name="binddn" value="{{binddn}}" type="text" class="form-control" placeholder="CN=bwlp,OU=Benutzer,DC=domain,DC=hs-beispiel,DC=de">
<!--span class="input-group-btn">
<a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
</span-->
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_password}}</span>
+ <span class="input-group-addon slx-ga2">{{lang_password}}</span>
<input tabindex="4" name="bindpw" value="{{bindpw}}" type="{{password_type}}" class="form-control" placeholder="{{lang_password}}">
<!--span class="input-group-btn">
<a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
</span-->
</div>
<div class="input-group">
- <span class="input-group-addon slx-ga">{{lang_searchBase}} *</span>
+ <span class="input-group-addon slx-ga2">{{lang_searchBase}} *</span>
<input tabindex="5" name="searchbase" value="{{searchbase}}" type="text" class="form-control" placeholder="ou=users,dc=hochschule,dc=de">
<!--span class="input-group-btn">
<a class="btn btn-default"><span class="glyphicon glyphicon-question-sign"></span></a>
@@ -43,31 +43,51 @@
</div>
<br>
<div class="input-group">
- <span class="input-group-addon slx-ga">Home</span>
+ <span class="input-group-addon slx-ga2">{{lang_homeFallback}}</span>
<input tabindex="6" name="home" value="{{home}}" type="text" class="form-control" placeholder="\\server.example.com\%s">
<span class="input-group-btn">
<a class="btn btn-default" data-toggle="modal" data-target="#help-home"><span class="glyphicon glyphicon-question-sign"></span></a>
</span>
</div>
<br>
+ <div class="{{#map_empty}}collapse{{/map_empty}}" id="attrbox">
+ <p>{{lang_customizeAttrDesc}}</p>
+ {{#mapping}}
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{name}}</span>
+ <input name="mapping[{{field}}]" value="{{value}}" type="text" class="form-control" placeholder="{{name}}">
+ </div>
+ {{/mapping}}
+ </div>
+ {{#map_empty}}
+ <div class="btn btn-default center-block" onclick="$('#attrbox').show();$(this).hide()">
+ {{lang_customizeAttributes}}
+ <span class="glyphicon glyphicon-menu-down"></span>
+ </div>
+ {{/map_empty}}
+ <br>
<div>
- <label>
- <input type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}> {{lang_fixNumeric}}
- </label>
+ <div class="checkbox">
+ <input id="num-cb" type="checkbox" name="fixnumeric" {{#fixnumeric}}checked{{/fixnumeric}}>
+ <label for="num-cb"><b>{{lang_fixNumeric}}</b></label>
+ </div>
<div>
<i>{{lang_fixNumericDescription}}</i>
</div>
</div>
<br>
<div>
- <label>
- <input type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}> {{lang_ssl}}
- </label>
+ <div class="checkbox">
+ <input id="ssl-cb" type="checkbox" name="ssl" onchange="$('#cert-box').css('display', this.checked ? '' : 'none')" {{#ssl}}checked{{/ssl}}>
+ <label for="ssl-cb"><b>{{lang_ssl}}</b></label>
+ </div>
+ <div>
+ <i>{{lang_sslDescription}}</i>
+ </div>
</div>
- <i>{{lang_sslDescription}}</i>
<br>
-
+ <hr>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
@@ -77,8 +97,8 @@
</div>
<div class="clearfix"></div>
- <hr>
<div {{^ssl}}style="display:none"{{/ssl}} id="cert-box">
+ <hr>
<div class="well well-sm" id="wcustom">
{{lang_customCertificate}}
<pre class="small">
diff --git a/modules-available/sysconfig/templates/list-configs.html b/modules-available/sysconfig/templates/list-configs.html
index d2cedca4..205317b8 100644
--- a/modules-available/sysconfig/templates/list-configs.html
+++ b/modules-available/sysconfig/templates/list-configs.html
@@ -21,7 +21,7 @@
<table id="conftable" class="slx-table table-hover" style="width:100%">
{{#configs}}
<tr>
- <td data-modlist="{{modlist}}" class="slx-pointer" onclick="showmod(this, 'bold')" onmouseover="showmod(this, 'fade')" onmouseout="showmod(this, 'reset')" width="100%">
+ <td data-id="{{configid}}" data-modlist="{{modlist}}" class="confrow slx-pointer" width="100%">
<table class="slx-ellipsis"><tr><td>{{config}}</td></tr></table>
</td>
<td>
@@ -100,7 +100,7 @@
</form>
</div>
{{^locationid}}
- <div class="panel-footer">
+ <div class="panel-footer text-right">
<a class="btn btn-primary" href="?do=SysConfig&amp;action=addconfig">{{lang_newConfiguration}}</a>
</div>
{{/locationid}}
@@ -109,13 +109,15 @@
<div class="modal fade" id="help-config" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_systemConfiguration}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_systemConfiguration}}</b></h4>
+ </div>
<div class="modal-body">
{{lang_helpSystemConfiguration}}
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
-</div> \ No newline at end of file
+</div>
diff --git a/modules-available/sysconfig/templates/list-modules.html b/modules-available/sysconfig/templates/list-modules.html
index c6622ee7..a55253ec 100644
--- a/modules-available/sysconfig/templates/list-modules.html
+++ b/modules-available/sysconfig/templates/list-modules.html
@@ -12,7 +12,7 @@
{{#modules}}
<tr>
<td class="badge text-nowrap">{{moduleType}}</td>
- <td data-id="{{id}}" class="modrow" width="100%"><table class="slx-ellipsis"><tr><td>{{title}}</td></tr></table></td>
+ <td data-id="{{id}}" class="modrow slx-pointer" width="100%"><table class="slx-ellipsis"><tr><td>{{title}}</td></tr></table></td>
<td class="text-nowrap">
{{#allowDownload}}
<button class="btn btn-default btn-xs" name="list" value="{{id}}" title="{{lang_show}}"><span class="glyphicon glyphicon-eye-open"></span></button>
@@ -39,7 +39,7 @@
{{/modules}}
</form>
</div>
- <div class="panel-footer">
+ <div class="panel-footer text-right">
<a class="btn btn-primary" href="?do=SysConfig&amp;action=addmodule">{{lang_newModule}}</a>
</div>
</div>
@@ -47,11 +47,13 @@
<div class="modal fade" id="help-module" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_moduleConfiguration}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_moduleConfiguration}}</b></h4>
+ </div>
<div class="modal-body">
{{lang_helpModuleConfiguration}}
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
diff --git a/modules-available/sysconfig/templates/sshconfig-start.html b/modules-available/sysconfig/templates/sshconfig-start.html
index a208dbac..c0b4b379 100644
--- a/modules-available/sysconfig/templates/sshconfig-start.html
+++ b/modules-available/sysconfig/templates/sshconfig-start.html
@@ -6,11 +6,13 @@
<input type="text" name="title" value="{{title}}" class="form-control" autofocus="autofocus">
</div>
<div class="form-group">
- <label>
+ <div class="checkbox">
<input type="checkbox" name="allowPasswordLogin" value="yes" {{#apl}}checked{{/apl}}>
- {{lang_allowPass}}
- </label>
- <p><i>{{lang_allowPassInfo}}</i></p>
+ <label><b>{{lang_allowPass}}</b></label>
+ </div>
+ <div>
+ <i>{{lang_allowPassInfo}}</i>
+ </div>
</div>
<div class="form-group">
<label for="root-key">{{lang_rootKey}}</label>
@@ -22,11 +24,12 @@
<input class="form-control" type="text" name="listenPort" value="{{listenPort}}" id="port" pattern="\d+">
<i>{{lang_listenPortInfo}}</i>
</div>
+ <hr/>
<div class="btn-group">
<a class="btn btn-default" href="?do=SysConfig&action=addmodule">{{lang_back}}</a>
</div>
<div class="btn-group pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</form>
diff --git a/modules-available/sysconfig/templates/start.html b/modules-available/sysconfig/templates/start.html
index 360d20fa..4bdd4853 100644
--- a/modules-available/sysconfig/templates/start.html
+++ b/modules-available/sysconfig/templates/start.html
@@ -10,6 +10,6 @@
</div>
</div>
{{/modules}}
-<div class="btn-group">
+<div class="text-right">
<a class="btn btn-default" href="?do=SysConfig">{{lang_cancel}}</a>
</div>
diff --git a/modules-available/sysconfig/templates/sysconfig_heading.html b/modules-available/sysconfig/templates/sysconfig_heading.html
new file mode 100644
index 00000000..2cef3326
--- /dev/null
+++ b/modules-available/sysconfig/templates/sysconfig_heading.html
@@ -0,0 +1 @@
+<h1>{{lang_systemConfiguration}}</h1> \ No newline at end of file
diff --git a/modules-available/syslog/hooks/cron.inc.php b/modules-available/syslog/hooks/cron.inc.php
index c796675f..bae882a9 100644
--- a/modules-available/syslog/hooks/cron.inc.php
+++ b/modules-available/syslog/hooks/cron.inc.php
@@ -1,5 +1,8 @@
<?php
if (mt_rand(1, 10) === 1) {
- Database::exec("DELETE FROM clientlog WHERE (UNIX_TIMESTAMP() - dateline) > 86400 * 190");
+ Database::exec("DELETE FROM clientlog WHERE (UNIX_TIMESTAMP() - 86400 * 190) > dateline");
+ if (mt_rand(1, 100) === 1) {
+ Database::exec("OPTIMIZE TABLE clientlog");
+ }
} \ No newline at end of file
diff --git a/modules-available/syslog/lang/de/template-tags.json b/modules-available/syslog/lang/de/template-tags.json
index 49e94602..c8b2bb45 100644
--- a/modules-available/syslog/lang/de/template-tags.json
+++ b/modules-available/syslog/lang/de/template-tags.json
@@ -6,5 +6,6 @@
"lang_filter": "Filter",
"lang_go": "Go",
"lang_not": "not",
- "lang_when": "Wann"
+ "lang_when": "Wann",
+ "lang_applyFilter": "Filter anwenden"
} \ No newline at end of file
diff --git a/modules-available/syslog/lang/en/template-tags.json b/modules-available/syslog/lang/en/template-tags.json
index 71f61693..7dae52d9 100644
--- a/modules-available/syslog/lang/en/template-tags.json
+++ b/modules-available/syslog/lang/en/template-tags.json
@@ -6,5 +6,6 @@
"lang_filter": "Filter",
"lang_go": "Go",
"lang_not": "not",
- "lang_when": "When"
+ "lang_when": "When",
+ "lang_applyFilter": "Apply Filter"
} \ No newline at end of file
diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php
index f2bc4854..c679877a 100644
--- a/modules-available/syslog/page.inc.php
+++ b/modules-available/syslog/page.inc.php
@@ -21,12 +21,13 @@ class Page_SysLog extends Page
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$types[$row['logtypeid']] = $row;
}
- if (isset($_GET['filter'])) {
- $filter = $_GET['filter'];
- $not = isset($_GET['not']) ? 'NOT' : '';
- } elseif (isset($_POST['filter'])) {
- $filter = $_POST['filter'];
- $not = isset($_POST['not']) ? 'NOT' : '';
+ if (Request::get('filter') !== false) {
+ $filter = Request::get('filter');
+ $not = Request::get('not') ? 'NOT' : '';
+ } elseif (Request::post('filter') !== false) {
+ $filter = Request::post('filter');
+ $not = Request::post('not') ? 'NOT' : '';
+
Session::set('log_filter', $filter);
Session::set('log_not', $not);
Session::save();
@@ -48,27 +49,19 @@ class Page_SysLog extends Page
if (!empty($whereClause)) $whereClause = ' WHERE logtypeid ' . $not . ' IN (' . implode(', ', $whereClause) . ')';
}
if (!isset($whereClause) || empty($whereClause)) $whereClause = '';
- if (Request::get('ip')) {
+ if (Request::get('machineuuid')) {
if (empty($whereClause))
$whereClause .= ' WHERE ';
else
$whereClause .= ' AND ';
- $whereClause .= "clientip LIKE '" . preg_replace('/[^0-9\.\:]/', '', Request::get('ip')) . "%'";
- }
- $today = date('d.m.Y');
- $yesterday = date('d.m.Y', time() - 86400);
+ $whereClause .= "machineuuid='" . preg_replace('/[^0-9a-zA-Z\-]/', '', Request::get('machineuuid', '', 'string')) . "'";
+ }
$lines = array();
$paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50);
$res = $paginate->exec();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $day = date('d.m.Y', $row['dateline']);
- if ($day === $today) {
- $day = Dictionary::translate('lang_today');
- } elseif ($day === $yesterday) {
- $day = Dictionary::translate('lang_yesterday');
- }
- $row['date'] = $day . date(' H:i', $row['dateline']);
+ $row['date'] = Util::prettyTime($row['dateline']);
$row['icon'] = $this->eventToIconName($row['logtypeid']);
$lines[] = $row;
}
@@ -78,9 +71,10 @@ class Page_SysLog extends Page
'not' => $not,
'list' => $lines,
'types' => json_encode(array_values($types)),
+ 'machineuuid' => Request::get('machineuuid'),
));
}
-
+
private function eventToIconName($event)
{
switch ($event) {
diff --git a/modules-available/syslog/templates/page-syslog.html b/modules-available/syslog/templates/page-syslog.html
index cb20a93a..8b590038 100644
--- a/modules-available/syslog/templates/page-syslog.html
+++ b/modules-available/syslog/templates/page-syslog.html
@@ -4,43 +4,51 @@
max-width: 500px;
}
</style>
-<form method="post" action="?do=SysLog">
+<form method="post" action="?do=SysLog{{#machineuuid}}&machineuuid={{machineuuid}}{{/machineuuid}}">
<input type="hidden" name="token" value="{{token}}">
<div class="pull-left">
<label for="filterstring">{{lang_filter}}</label>
</div>
<div class="clearfix"></div>
- <div class="input-group">
- <input id="filterstring" placeholder="id" value="{{filter}}" name="filter">
- <span class="input-group-btn">
- <button class="btn btn-default" type="submit">{{lang_go}}</button>
- </span>
- </div>
- <div class="pull-left">
- <div class="checkbox">
- <input id="notbox" type="checkbox" name="not" {{#not}}checked="checked"{{/not}}>
- <label for="notbox">{{lang_not}}</label>
+
+
+ <div class="row">
+ <div class="col-sm-1">
+ <div class="checkbox">
+ <input id="notbox" type="checkbox" name="not" {{#not}}checked="checked"{{/not}}>
+ <label for="notbox">{{lang_not}}</label>
+ </div>
+ </div>
+ <div class="col-sm-11">
+ <div class="input-group">
+ <input id="filterstring" placeholder="id" value="{{filter}}" name="filter">
+ <span style="padding-bottom: 5px;" class="input-group-btn">
+ <button class="btn btn-primary" type="submit">{{lang_applyFilter}}</button>
+ </span>
+ </div>
</div>
</div>
</form>
{{{pagenav}}}
+
+
<table class="table table-striped table-condensed">
<thead>
<th width="1"></th>
- <th>{{lang_when}}</th>
- <th>{{lang_client}}</th>
- <th>{{lang_event}}</th>
+ <th class="text-center">{{lang_when}}</th>
+ <th class="text-center">{{lang_client}}</th>
+ <th class="text-center">{{lang_event}}</th>
<th width="1">{{lang_details}}</th>
</thead>
<tbody>
{{#list}}
<tr>
<td><span class="type-button glyphicon {{icon}}" title="{{logtypeid}}"></span></td>
- <td class="text-right" nowrap="nowrap">{{date}}</td>
- <td>{{clientip}}</td>
+ <td class="text-center" nowrap="nowrap">{{date}}</td>
+ <td class="text-left">{{clientip}}</td>
<td>{{description}}</td>
- <td>{{#extra}}
- <a class="btn btn-default btn-xs pull-left" onclick="$('#details-body').html($('#extra-{{logid}}').html())"
+ <td class="text-center">{{#extra}}
+ <a class="btn btn-default btn-xs" onclick="$('#details-body').html($('#extra-{{logid}}').html())"
data-toggle="modal" data-target="#myModal">&raquo;</a>
<div class="hidden" id="extra-{{logid}}">{{extra}}</div>
{{/extra}}</td>
@@ -48,6 +56,7 @@
{{/list}}
</tbody>
</table>
+
{{{pagenav}}}
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
@@ -61,9 +70,6 @@
<div class="modal-body">
<pre id="details-body"></pre>
</div>
- <div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
- </div>
</div>
</div>
</div>
diff --git a/modules-available/systemstatus/lang/de/template-tags.json b/modules-available/systemstatus/lang/de/template-tags.json
index 1333fb7d..fcf836ec 100644
--- a/modules-available/systemstatus/lang/de/template-tags.json
+++ b/modules-available/systemstatus/lang/de/template-tags.json
@@ -1,22 +1,26 @@
{
+ "lang_OK": "OK",
"lang_addressConfiguration": "Adresskonfiguration",
+ "lang_areYouSureNoUndo": "Sind Sie sicher? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
"lang_attention": "Achtung!",
"lang_average": "Durchschnitt",
"lang_capacity": "Kapazit\u00e4t",
"lang_cpuLoad": "CPU-Last",
"lang_dmsdUnreachable": "dmsd nicht erreichbar",
"lang_downloads": "Downloads",
+ "lang_failure": "Fehler",
"lang_foundStore": "Vorgefunden:",
"lang_free": "Frei",
"lang_goToStoreConf": "Zur VM-Store-Konfiguration wechseln",
- "lang_iAmSure": "Ja, ich bin sicher",
"lang_logicCPUs": "Logische CPUs",
"lang_maintenance": "Maintenance",
+ "lang_moduleHeading": "System-Status",
"lang_notDetermined": "Konnte nicht ermittelt werden",
"lang_occupied": "Belegt",
"lang_onlyOS": "Nur OS",
"lang_overview": "\u00dcbersicht",
"lang_ramUsage": "RAM-Nutzung",
+ "lang_serverReboot": "Server neustarten",
"lang_services": "Dienste",
"lang_space": "Speicherplatz",
"lang_storeMissingExpected": "VM-Store nicht eingebunden. Erwartet:",
@@ -27,6 +31,7 @@
"lang_systemPartition": "Systempartition",
"lang_systemStoreError": "Fehler beim Ermitteln des verf\u00fcgbaren Systemspeichers",
"lang_total": "Gesamt",
+ "lang_unknownState": "Unbekannter Status",
"lang_updatedPackages": "Ausstehende Updates",
"lang_uploads": "Uploads",
"lang_uptimeOS": "OS Uptime",
diff --git a/modules-available/systemstatus/lang/en/template-tags.json b/modules-available/systemstatus/lang/en/template-tags.json
index 46b7e18b..159ae59d 100644
--- a/modules-available/systemstatus/lang/en/template-tags.json
+++ b/modules-available/systemstatus/lang/en/template-tags.json
@@ -1,22 +1,26 @@
{
+ "lang_OK": "OK",
"lang_addressConfiguration": "Address Configuration",
+ "lang_areYouSureNoUndo": "Are you sure? This cannot be undone!",
"lang_attention": "Attention!",
"lang_average": "Average",
"lang_capacity": "Capacity",
"lang_cpuLoad": "CPU Load",
"lang_dmsdUnreachable": "dmsd not reachable",
"lang_downloads": "Downloads",
+ "lang_failure": "Failure",
"lang_foundStore": "Found:",
"lang_free": "Free",
"lang_goToStoreConf": "Go to VM store configuration",
- "lang_iAmSure": "Yes, I am sure",
"lang_logicCPUs": "Logic CPUs",
"lang_maintenance": "Maintenance",
+ "lang_moduleHeading": "System Status",
"lang_notDetermined": "Could not be determined",
"lang_occupied": "Occupied",
"lang_onlyOS": "OS Only",
"lang_overview": "Overview",
"lang_ramUsage": "RAM Usage",
+ "lang_serverReboot": "Reboot Server",
"lang_services": "Services",
"lang_space": "Space",
"lang_storeMissingExpected": "VM store not mounted. Expected:",
@@ -27,6 +31,7 @@
"lang_systemPartition": "System Partition",
"lang_systemStoreError": "Error querying available system storage",
"lang_total": "Total",
+ "lang_unknownState": "Unknown status",
"lang_updatedPackages": "Pending updates",
"lang_uploads": "Uploads",
"lang_uptimeOS": "OS Uptime",
diff --git a/modules-available/systemstatus/page.inc.php b/modules-available/systemstatus/page.inc.php
index f7ec7022..c1c52af0 100644
--- a/modules-available/systemstatus/page.inc.php
+++ b/modules-available/systemstatus/page.inc.php
@@ -15,10 +15,6 @@ class Page_SystemStatus extends Page
}
if (Request::post('action') === 'reboot') {
- if (Request::post('confirm') !== 'yep') {
- Message::addError('reboot-unconfirmed');
- Util::redirect('?do=SystemStatus');
- }
$this->rebootTask = Taskmanager::submit('Reboot');
}
}
@@ -232,20 +228,42 @@ class Page_SystemStatus extends Page
protected function ajaxServices()
{
- $data = array();
+ $data = array('services' => array());
+ $tasks = array();
- $taskId = Trigger::ldadp();
- if ($taskId === false)
- return;
- $status = Taskmanager::waitComplete($taskId, 10000);
+ foreach (['dmsd', 'dnbd3-server', 'atftpd'] as $svc) {
+ $tasks[] = array(
+ 'name' => $svc,
+ 'task' => Taskmanager::submit('Systemctl', ['service' => $svc, 'operation' => 'is-active'])
+ );
+ }
+ $tasks[] = array(
+ 'name' => 'LDAP/AD-Proxy',
+ 'task' => Trigger::ldadp()
+ );
+ $deadline = time() + 10;
+ do {
+ $done = true;
+ foreach ($tasks as &$task) {
+ if (!is_string($task['task']) && (Taskmanager::isFailed($task['task']) || Taskmanager::isFinished($task['task'])))
+ continue;
+ $task['task'] = Taskmanager::waitComplete($task['task'], 100);
+ if (!Taskmanager::isFailed($task['task']) && !Taskmanager::isFinished($task['task'])) {
+ $done = false;
+ }
+ }
+ unset($task);
+ } while (!$done && time() < $deadline);
- if (Taskmanager::isFailed($status)) {
- if (isset($status['data']['messages']))
- $data['ldadpError'] = $status['data']['messages'];
- else
- $data['ldadpError'] = 'Taskmanager error';
+ foreach ($tasks as $task) {
+ $fail = Taskmanager::isFailed($task['task']);
+ $data['services'][] = array(
+ 'name' => $task['name'],
+ 'fail' => $fail,
+ 'data' => isset($task['data']) ? $task['data'] : null,
+ 'unknown' => $task['task'] === false
+ );
}
- // TODO: Dozentenmodul, tftp, ...
echo Render::parse('services', $data);
}
diff --git a/modules-available/systemstatus/templates/_page.html b/modules-available/systemstatus/templates/_page.html
index 48a7e5c4..ca3e6fd2 100644
--- a/modules-available/systemstatus/templates/_page.html
+++ b/modules-available/systemstatus/templates/_page.html
@@ -1,3 +1,5 @@
+<h1>{{lang_moduleHeading}}</h1>
+
{{#rebootTask}}
<div data-tm-id="{{rebootTask}}" data-tm-log="messages">Reboot...</div>
{{/rebootTask}}
@@ -84,9 +86,23 @@
<form class="form-adduser" action="?do=SystemStatus" method="post">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="reboot">
- <div>Server Reboot</div>
- <label><input type="checkbox" name="confirm" value="yep"> {{lang_iAmSure}}</label>
- <button class="btn btn-warning btn-xs" type="submit">Reboot</button>
+ <button class="btn btn-warning" type="button" data-toggle="modal" data-target="#rebootServerModal"><span class="glyphicon glyphicon-repeat"></span> {{lang_serverReboot}}</button>
+
+
+ <div class ="modal fade" id="rebootServerModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
+ <div class="modal-dialog" style="width: 400px" role="document">
+ <div class="modal-content">
+ <div class="modal-body">
+ {{lang_areYouSureNoUndo}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button>
+ <button type="submit" class="btn btn-sm btn-warning"><span class="glyphicon glyphicon-repeat"></span> {{lang_serverReboot}}</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
</form>
<div id="dmsd-users"></div>
</div>
diff --git a/modules-available/systemstatus/templates/services.html b/modules-available/systemstatus/templates/services.html
index 6c4f0b93..29b33687 100644
--- a/modules-available/systemstatus/templates/services.html
+++ b/modules-available/systemstatus/templates/services.html
@@ -1,6 +1,20 @@
-{{#ldadpError}}
-<pre>{{ldadpError}}</pre>
-{{/ldadpError}}
-{{^ldadpError}}
-<div class="alert alert-success">LDAP-AD-Proxy: OK</div>
-{{/ldadpError}}
+{{#services}}
+ {{#unknown}}
+ <div class="alert alert-warning">
+ {{name}}: {{lang_unknownState}}
+ </div>
+ {{/unknown}}
+ {{^unknown}}
+ {{#fail}}
+ <div class="alert alert-danger">
+ {{name}}: <b>{{lang_failure}}</b>
+ {{#data.messages}}<pre>{{data.messages}}</pre>{{/data.messages}}
+ </div>
+ {{/fail}}
+ {{^fail}}
+ <div class="alert alert-success">
+ {{name}}: {{lang_OK}}
+ </div>
+ {{/fail}}
+ {{/unknown}}
+{{/services}}
diff --git a/modules-available/translation/lang/de/template-tags.json b/modules-available/translation/lang/de/template-tags.json
index 99ec6cea..cb10d3b4 100644
--- a/modules-available/translation/lang/de/template-tags.json
+++ b/modules-available/translation/lang/de/template-tags.json
@@ -1,5 +1,6 @@
{
"lang_createTag": "Tag erstellen",
+ "lang_clear": "Läutern",
"lang_global": "Global",
"lang_globalTooltip": "Dieser Tag ist global verf\u00fcgbar und braucht normalerweise nicht explizit f\u00fcr dieses Modul \u00fcbersetzt zu werden",
"lang_menuCategories": "Men\u00fckategorien",
@@ -7,6 +8,7 @@
"lang_missing": "Fehlend",
"lang_missingDeps": "Fehlende Abh\u00e4ngigkeiten",
"lang_module": "Modul",
+ "lang_modullist": "Liste der Module",
"lang_otherStrings": "Andere Texte",
"lang_sample": "Beispiel",
"lang_status": "Status",
@@ -16,7 +18,7 @@
"lang_templateHint": "Hinweis: Gelbe Linien zeigen an, dass eine \u00dcbersetzung fehlt, rote Linien, dass ein Tag nicht von dem jeweiligen Template verwendet wird.",
"lang_templates": "Templates",
"lang_translation": "\u00dcbersetzung",
- "lang_translationHeading": "Verwalten der \u00dcbersetzungen",
+ "lang_translationHeading": "Verwaltung der \u00dcbersetzungen",
"lang_unused": "Ungenutzt",
"lang_unusedUnreliableHint": "Die Erkennung ungenutzter Tags bezieht nur aktivierte Module mit ein. Es k\u00f6nnte sein, dass ein ungenutzt gemeldeter Tag in einem nicht aktivierten Modul verwendet wird."
} \ No newline at end of file
diff --git a/modules-available/translation/lang/en/template-tags.json b/modules-available/translation/lang/en/template-tags.json
index 9f0795b1..e7365ca6 100644
--- a/modules-available/translation/lang/en/template-tags.json
+++ b/modules-available/translation/lang/en/template-tags.json
@@ -1,5 +1,6 @@
{
"lang_createTag": "Create Tag",
+ "lang_clear": "Clear",
"lang_global": "Global",
"lang_globalTooltip": "This tag is global; usually there is no need to translate it explicitly for this module",
"lang_menuCategories": "Menu categories",
@@ -7,6 +8,7 @@
"lang_missing": "Missing",
"lang_missingDeps": "Missing dependencies",
"lang_module": "Module",
+ "lang_modullist": "List of Modules",
"lang_otherStrings": "Other strings",
"lang_sample": "Sample",
"lang_status": "Status",
@@ -16,7 +18,7 @@
"lang_templateHint": "Hint: Yellow lines indicate a translation is missing and red lines indicate a tag is not being used by the template.",
"lang_templates": "Templates",
"lang_translation": "Translation",
- "lang_translationHeading": "Manage translations",
+ "lang_translationHeading": "Translation Management",
"lang_unused": "Unused",
"lang_unusedUnreliableHint": "Detection of unused tags only includes currently activated modules. It's possible that a tag marked \"unused\" is actually refered to in a module not activated."
} \ No newline at end of file
diff --git a/modules-available/translation/templates/edit.html b/modules-available/translation/templates/edit.html
index 082e2cb5..3c66aef6 100644
--- a/modules-available/translation/templates/edit.html
+++ b/modules-available/translation/templates/edit.html
@@ -1,21 +1,90 @@
-<div class="panel panel-default">
- <div class="panel-heading">
- {{module}} / {{section}}
+<h1>{{lang_translationHeading}}: {{module}} / {{section}}</h1>
+
+<div class="alert alert-info">
+ {{lang_templateHint}}
+</div>
+
+<form action="?do=Translation" method="post" class="slx-visible-rows">
+ <table id="moduleTable" class="table table-condensed table-hover stupidtable">
+ <thead>
+ <tr>
+ <th>{{lang_tag}}</th>
+ <th>{{lang_translation}} (<b>{{language}}</b>)</th>
+ <th class="hidden-xs">{{lang_sample}}</th>
+ <th style="text-align: center;">{{lang_clear}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#tags}}
+ <tr>
+ <td class="col-sm-3">
+ <div>
+ {{#unused}}
+ <span class="label label-danger">{{lang_unused}}</span>
+ {{/unused}}
+ {{#missing}}
+ <span class="label label-warning">{{lang_missing}}</span>
+ {{/missing}}
+ {{#isglobal}}
+ <span class="label label-success" title="{{lang_globalTooltip}}">{{lang_global}}</span>
+ {{/isglobal}}
+ {{tag}}
+ <div class="slx-notebox">{{{notes}}}</div>
+ </div>
+ </td>
+
+ <td class="col-sm-4" id="tagid-{{tagid}}">
+ {{^big}}
+ <input type="text" class="form-control switchable" value="{{translation}}" ondblclick="slxMb(this)" name="lang#!#{{tag}}" placeholder="{{placeholder}}">
+ {{/big}}
+ {{#big}}
+ <textarea rows="3" class="form-control" name="lang#!#{{tag}}" placeholder="{{placeholder}}">{{translation}}</textarea>
+ {{/big}}
+ </td>
+
+ <td class="hidden-xs col-sm-4">
+ <div class="badge">{{samplelang}}</div>
+ {{sampletext}}
+ </td>
+
+ <td class="col-sm-1" style="text-align:center; vertical-align: middle;">
+ <button type="button" class="btn btn-danger btn-xs" onclick="slxDelTag({{tagid}})" tabindex="-1">
+ <span class="glyphicon glyphicon-remove"></span>
+ </button>
+ </td>
+ </tr>
+ {{/tags}}
+ </tbody>
+ </table>
+
+
+ <input type="hidden" name="module" value="{{module}}">
+ <input type="hidden" name="destlang" value="{{destlang}}">
+ <input type="hidden" name="section" value="{{section}}">
+ <input type="hidden" name="subsection" value="{{subsection}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="text-right">
+ <a class="btn btn-default" href='?do=Translation' >{{lang_back}}</a>
+ <button type="submit" class="btn btn-primary" name="update" value="true">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ <button class="btn btn-success" type="button" onclick="slxAddTag()" >
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_createTag}}
+ </button>
</div>
- <div class="panel-body">
- <p>{{lang_templateAdminHelp}}</p>
- <div class="alert alert-info">
- {{lang_templateHint}}
- </div>
+</form>
+<br/>
+
+<!-- vorherige Version der "Tabelle" geregelt über Divisions
+
<form action="?do=Translation" method="post" class="slx-visible-rows">
<input type="hidden" name="module" value="{{module}}">
<input type="hidden" name="destlang" value="{{destlang}}">
<input type="hidden" name="section" value="{{section}}">
<input type="hidden" name="subsection" value="{{subsection}}">
<input type="hidden" name="token" value="{{token}}">
- <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
- <button class="btn btn-primary" type="button" onclick="slxAddTag()" >{{lang_createTag}}</button>
- <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
<div class="row">
<div class="col-xs-4 col-sm-3">{{lang_tag}}</div>
<div class="col-xs-6 col-sm-4">{{lang_translation}} (<b>{{language}}</b>)</div>
@@ -26,7 +95,7 @@
<div class="row">
<div class="col-xs-4 col-sm-3">
{{#unused}}
- <span class="label label-info">{{lang_unused}}</span>
+ <span class="label label-danger">{{lang_unused}}</span>
{{/unused}}
{{#missing}}
<span class="label label-warning">{{lang_missing}}</span>
@@ -53,23 +122,25 @@
</div>
<div class="col-xs-2 col-sm-2 col-lg-1">
<button type="button" class="btn btn-danger btn-xs" onclick="slxDelTag({{tagid}})" tabindex="-1">
- <span class="glyphicon glyphicon-remove"></span>
+ <span class="glyphicon glyphicon-trash"></span>
<span class="hidden-xs">{{lang_delete}}</span>
</button>
</div>
</div>
{{/tags}}
<div id="newTag"></div>
- <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
- <button class="btn btn-primary" type="button" onclick="slxAddTag()" >{{lang_createTag}}</button>
- <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ <br/>
+ <div class="text-right">
+ <a class="btn btn-default" href='?do=Translation' >{{lang_back}}</a>
+ <button class="btn btn-success" type="button" onclick="slxAddTag()" >{{lang_createTag}}</button>
+ <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ </div>
</form>
- </div>
-</div>
-<script type="text/javascript">
- var slxNewTagCounter = 0;
- function slxAddTag()
- {
+
+
+
+
+Function:
$('#newTag').before(
'<div class="row" id="new-delete-' + slxNewTagCounter + '">' +
' <div class="col-xs-4 col-sm-3">' +
@@ -79,14 +150,42 @@
' <input type="text" class="form-control" name="new-text[' + slxNewTagCounter + ']">' +
' </div>' +
' <div class="hidden-xs col-sm-4">' +
- ' </div>' +
+ ' </div>' +
' <div class="col-xs-2 col-sm-1">' +
' <button type="button" class="btn btn-danger btn-xs" onclick="slxDelNew(' + slxNewTagCounter + ')" tabindex="-1"><span class="glyphicon glyphicon-remove"></span> {{lang_delete}}</button>' +
' </div>' +
'</div>'
);
+
+
+
+-->
+
+<script type="text/javascript">
+ var slxNewTagCounter = 0;
+ function slxAddTag()
+ {
+ $('#moduleTable tr:last').after(
+ '<tr id="new-delete-' + slxNewTagCounter + '">' +
+ ' <td class="col-sm-3">' +
+ ' <input type="text" name="new-id[' + slxNewTagCounter + ']" class="form-control">' +
+ ' </td>' +
+ ' <td class="col-sm-4">' +
+ ' <input type="text" class="form-control" name="new-text[' + slxNewTagCounter + ']">' +
+ ' </td>' +
+ ' <td class="hidden-xs col-sm-4"></td>' +
+ ' <td class="col-sm-1" style="text-align:center; vertical-align: middle;">' +
+ ' <button type="button" class="btn btn-danger btn-xs" onclick="slxDelNew(' + slxNewTagCounter + ')" tabindex="-1">' +
+ ' <span class="glyphicon glyphicon-remove"></span> ' +
+ ' </button>' +
+ ' </td>' +
+ '</tr>'
+ );
+
slxNewTagCounter++;
+
}
+
function slxDelNew(id)
{
diff --git a/modules-available/translation/templates/module-heading.html b/modules-available/translation/templates/module-heading.html
index 540828e9..8e87e22f 100644
--- a/modules-available/translation/templates/module-heading.html
+++ b/modules-available/translation/templates/module-heading.html
@@ -1 +1 @@
-<h1>{{moduleName}} ({{module}})</h1> \ No newline at end of file
+<h1>{{lang_translationHeading}}: {{module}}</h1> \ No newline at end of file
diff --git a/modules-available/translation/templates/module-list.html b/modules-available/translation/templates/module-list.html
index 026e17e9..c0af6a9c 100644
--- a/modules-available/translation/templates/module-list.html
+++ b/modules-available/translation/templates/module-list.html
@@ -1,25 +1,30 @@
<h1>{{lang_translationHeading}}</h1>
<div class="panel panel-default">
- <table class="table">
- <thead>
- <tr>
- <th>{{lang_module}}</th>
- <th>{{lang_status}}</th>
- <tr>
- </thead>
- <tbody>
- {{#table}}
+ <div class="panel-heading">
+ {{lang_modullist}}
+ </div>
+ <div class="panel-body">
+ <table class="table">
+ <thead>
<tr>
- <td>
- {{#depfail}}
- <div class="pull-right"><span class="red glyphicon glyphicon-exclamation-sign" title="{{lang_missingDeps}}"></span></div>
- {{/depfail}}
- <a href="?do=Translation&amp;module={{module}}">{{module}}</a>
- </td>
- <td>{{{status}}}</td>
- </tr>
- {{/table}}
- </tbody>
- </table>
+ <th>{{lang_module}}</th>
+ <th>{{lang_status}}</th>
+ <tr>
+ </thead>
+ <tbody>
+ {{#table}}
+ <tr>
+ <td>
+ {{#depfail}}
+ <div class="pull-right"><span class="red glyphicon glyphicon-exclamation-sign" title="{{lang_missingDeps}}"></span></div>
+ {{/depfail}}
+ <a href="?do=Translation&amp;module={{module}}">{{module}}</a>
+ </td>
+ <td>{{{status}}}</td>
+ </tr>
+ {{/table}}
+ </tbody>
+ </table>
+ </div>
</div>
diff --git a/modules-available/vmstore/lang/en/module.json b/modules-available/vmstore/lang/en/module.json
index 95b2e66d..a424640e 100644
--- a/modules-available/vmstore/lang/en/module.json
+++ b/modules-available/vmstore/lang/en/module.json
@@ -1,4 +1,4 @@
{
- "module_name": "VM storage location",
- "page_title": "Setting VM storage location"
+ "module_name": "VM Storage Location",
+ "page_title": "Setting VM Storage Location"
} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/en/template-tags.json b/modules-available/vmstore/lang/en/template-tags.json
index 38047e09..b56cdd23 100644
--- a/modules-available/vmstore/lang/en/template-tags.json
+++ b/modules-available/vmstore/lang/en/template-tags.json
@@ -8,7 +8,7 @@
"lang_readOnly": "Read-only Access",
"lang_readWrite": "Read\/Write Access",
"lang_username": "Username",
- "lang_vmLocation": "VM Location",
+ "lang_vmLocation": "VM Storage Location",
"lang_vmLocationChoose": "Please choose where the images of virtual machines will be stored.",
"lang_vmLocationConfiguration": "VM location is configured",
"lang_vmLocationHelp1": "For test purposes, the VMs can be stored directly on the Satellite server. However, if you operate the delivered satellite vmdk please remember that you have only about 100GB of memory.",
diff --git a/modules-available/vmstore/page.inc.php b/modules-available/vmstore/page.inc.php
index 6ef04669..3314bfe2 100644
--- a/modules-available/vmstore/page.inc.php
+++ b/modules-available/vmstore/page.inc.php
@@ -38,8 +38,9 @@ class Page_VmStore extends Page
private function setStore()
{
+ $vmstore = array();
foreach (array('storetype', 'nfsaddr', 'cifsaddr', 'cifsuser', 'cifspasswd', 'cifsuserro', 'cifspasswdro') as $key) {
- $vmstore[$key] = trim(Request::post($key, ''));
+ $vmstore[$key] = trim(Request::post($key, '', 'string'));
}
$storetype = $vmstore['storetype'];
if (!in_array($storetype, array('internal', 'nfs', 'cifs'))) {
diff --git a/modules-available/vmstore/templates/page-vmstore.html b/modules-available/vmstore/templates/page-vmstore.html
index c1ab2472..4a508e74 100644
--- a/modules-available/vmstore/templates/page-vmstore.html
+++ b/modules-available/vmstore/templates/page-vmstore.html
@@ -3,72 +3,87 @@
<input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="action" value="setstore">
+
+ <h1>{{lang_vmLocation}}</h1>
+
+ <p>{{lang_vmLocationChoose}} <a class="btn btn-default" data-toggle="modal" data-target="#help-store"><span class="glyphicon glyphicon-question-sign"></span></a></p>
+
+
<div class="panel panel-default">
<div class="panel-heading">
- {{lang_vmLocation}} <a class="btn btn-default" data-toggle="modal" data-target="#help-store"><span class="glyphicon glyphicon-question-sign"></span></a>
+ <div class="radio">
+ <input type="radio" name="storetype" value="internal" {{pre-internal}} id="id-internal">
+ <label for="id-internal">{{lang_intern}}</label>
+ </div>
+ </div>
+ <div class="panel-body">
+ {{lang_noAdditionalInformation}}
</div>
- <div class="panel-body slx-md-width">
- <p>{{lang_vmLocationChoose}}</p>
- <div class="panel panel-default">
- <div class="panel-heading">
- <input type="radio" name="storetype" value="internal" {{pre-internal}}> {{lang_intern}}
- </div>
- <div class="panel-body">
- {{lang_noAdditionalInformation}}
- </div>
+ </div>
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="radio radio-inline">
+ <input type="radio" name="storetype" value="nfs" {{pre-nfs}} id="id-nfs">
+ <label for="id-nfs">NFS</label>
</div>
- <div class="panel panel-default">
- <div class="panel-heading">
- <input type="radio" name="storetype" value="nfs" {{pre-nfs}}> NFS
- <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#help-nfs"><span class="glyphicon glyphicon-question-sign"></span></a>
- </div>
- <div class="panel-body">
- <label for="nfsaddr">NFS-Export</label>
- <input type="text" class="form-control" name="nfsaddr" value="{{nfsaddr}}" placeholder="1.2.3.4:/export/bwlp" id="nfsaddr">
- </div>
+ <a class="btn btn-default btn-sm" data-toggle="modal" data-target="#help-nfs"><span class="glyphicon glyphicon-question-sign"></span></a>
+ </div>
+ <div class="panel-body">
+ <label for="nfsaddr">NFS-Export</label>
+ <input type="text" class="form-control" name="nfsaddr" value="{{nfsaddr}}" placeholder="1.2.3.4:/export/bwlp" id="nfsaddr">
+ </div>
+ </div>
+
+
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <div class="radio">
+ <input type="radio" name="storetype" value="cifs" {{pre-cifs}} id="id-cifs">
+ <label for="id-cifs">CIFS</label>
</div>
- <div class="panel panel-default">
- <div class="panel-heading">
- <input type="radio" name="storetype" value="cifs" {{pre-cifs}}> CIFS
- </div>
- <div class="panel-body">
- <label for="cifsaddr">UNC-Pfad</label>
- <input type="text" class="form-control" name="cifsaddr" value="{{cifsaddr}}" placeholder="\\samba.server.example.com\bwlp" id="cifsaddr">
- <br>
- <label for="cifsuser">{{lang_readWrite}}</label>
- <div class="input-group">
- <span class="input-group-addon slx-ga">
- {{lang_username}}
- </span>
- <input type="text" class="form-control" name="cifsuser" value="{{cifsuser}}" placeholder="{{lang_username}}" id="cifsuser">
- <span class="input-group-addon">
- {{lang_password}}
- </span>
- <input type="{{password_type}}" class="form-control" name="cifspasswd" value="{{cifspasswd}}" placeholder="{{lang_password}}">
- </div>
- <br>
- <label for="cifsuserro">{{lang_readOnly}}</label>
- <div class="input-group">
- <span class="input-group-addon slx-ga">
- {{lang_username}}
- </span>
- <input type="text" class="form-control" name="cifsuserro" value="{{cifsuserro}}" placeholder="{{lang_username}}" id="cifsuserro">
- <span class="input-group-addon">
- {{lang_password}}
- </span>
- <input type="{{password_type}}" class="form-control" name="cifspasswdro" value="{{cifspasswdro}}" placeholder="{{lang_password}}">
- </div>
- </div>
+ </div>
+ <div class="panel-body">
+ <label for="cifsaddr">UNC-Pfad</label>
+ <input type="text" class="form-control" name="cifsaddr" value="{{cifsaddr}}" placeholder="\\samba.server.example.com\bwlp" id="cifsaddr">
+ <br>
+ <label for="cifsuser">{{lang_readWrite}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuser" value="{{cifsuser}}" placeholder="{{lang_username}}" id="cifsuser">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswd" value="{{cifspasswd}}" placeholder="{{lang_password}}">
+ </div>
+ <br>
+ <label for="cifsuserro">{{lang_readOnly}}</label>
+ <div class="input-group">
+ <span class="input-group-addon slx-ga">
+ {{lang_username}}
+ </span>
+ <input type="text" class="form-control" name="cifsuserro" value="{{cifsuserro}}" placeholder="{{lang_username}}" id="cifsuserro">
+ <span class="input-group-addon">
+ {{lang_password}}
+ </span>
+ <input type="{{password_type}}" class="form-control" name="cifspasswdro" value="{{cifspasswdro}}" placeholder="{{lang_password}}">
</div>
- <button class="btn btn-primary" type="submit">{{lang_save}}</button>
</div>
</div>
+ <button class="btn btn-primary pull-right" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+
</form>
<div class="modal fade" id="help-store" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">{{lang_vmLocation}}</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ {{lang_vmLocation}}
+ </div>
<div class="modal-body">
<p>
{{lang_vmLocationHelp1}}
@@ -80,7 +95,6 @@
{{lang_vmLocationHelp3}}
</p>
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
@@ -88,7 +102,10 @@
<div class="modal fade" id="help-nfs" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
- <div class="modal-header">NFS</div>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ NFS
+ </div>
<div class="modal-body">
<p>
{{lang_nfsHelp1}}
@@ -105,7 +122,6 @@
/mnt/images *(ro,async)
</pre>
</div>
- <div class="modal-footer"><a class="btn btn-primary" data-dismiss="modal">{{lang_close}}</a></div>
</div>
</div>
</div>
diff --git a/modules-available/webinterface/lang/de/template-tags.json b/modules-available/webinterface/lang/de/template-tags.json
index 64ba84d0..e193938d 100644
--- a/modules-available/webinterface/lang/de/template-tags.json
+++ b/modules-available/webinterface/lang/de/template-tags.json
@@ -12,6 +12,7 @@
"lang_httpsSettings": "HTTPS-Konfiguration",
"lang_installAndRestart": "Zertifikat installieren und Webserver neustarten",
"lang_logoBackground": "Hintergrundfarbe des Logos",
+ "lang_moduleHeading": "Web-Schnittstelle",
"lang_noHttps": "HTTPS wieder deaktivieren, aktuelles Zertifikat l\u00f6schen",
"lang_offSelected": "HTTPS ist derzeit deaktiviert.",
"lang_pageTitlePrefix": "Pr\u00e4fix f\u00fcr den Seitentitel",
diff --git a/modules-available/webinterface/lang/en/template-tags.json b/modules-available/webinterface/lang/en/template-tags.json
index 0fb4cc96..28129e64 100644
--- a/modules-available/webinterface/lang/en/template-tags.json
+++ b/modules-available/webinterface/lang/en/template-tags.json
@@ -12,6 +12,7 @@
"lang_httpsSettings": "HTTPS settings",
"lang_installAndRestart": "Installing certificate and restarting web server",
"lang_logoBackground": "Logo background color",
+ "lang_moduleHeading": "Web Interface",
"lang_noHttps": "Disable HTTPS, delete current certificate",
"lang_offSelected": "HTTPS is currently disabled.",
"lang_pageTitlePrefix": "Page title prefix",
diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php
index e576807e..6dfc9faa 100644
--- a/modules-available/webinterface/page.inc.php
+++ b/modules-available/webinterface/page.inc.php
@@ -73,6 +73,7 @@ class Page_WebInterface extends Page
protected function doRender()
{
+ Render::addTemplate("heading");
//
// HTTPS
//
diff --git a/modules-available/webinterface/templates/customization.html b/modules-available/webinterface/templates/customization.html
index 7949f95b..c949c1f2 100644
--- a/modules-available/webinterface/templates/customization.html
+++ b/modules-available/webinterface/templates/customization.html
@@ -23,7 +23,7 @@
</label>
</div>
<div class="pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</div>
</div>
diff --git a/modules-available/webinterface/templates/heading.html b/modules-available/webinterface/templates/heading.html
new file mode 100644
index 00000000..d68360f1
--- /dev/null
+++ b/modules-available/webinterface/templates/heading.html
@@ -0,0 +1 @@
+<h1>{{lang_moduleHeading}}</h1> \ No newline at end of file
diff --git a/modules-available/webinterface/templates/https.html b/modules-available/webinterface/templates/https.html
index ecfe5f5d..f0accb57 100644
--- a/modules-available/webinterface/templates/https.html
+++ b/modules-available/webinterface/templates/https.html
@@ -28,7 +28,12 @@
{{#httpsEnabled}}
<div class="input-group" onclick="$('#moff').prop('checked', true);
$('#wcustom').hide()">
- <span class="input-group-addon"><input id="moff" type="radio" name="mode" value="off"></span>
+ <span class="input-group-addon">
+ <div class="radio">
+ <input id="moff" type="radio" name="mode" value="off">
+ <label></label>
+ </div>
+ </span>
<span class="form-control">
{{lang_noHttps}}
</span>
@@ -36,14 +41,24 @@
{{/httpsEnabled}}
<div class="input-group" onclick="$('#mrandom').prop('checked', true);
$('#wcustom').hide()">
- <span class="input-group-addon"><input id="mrandom" type="radio" name="mode" value="random"></span>
+ <span class="input-group-addon">
+ <div class="radio">
+ <input id="mrandom" type="radio" name="mode" value="random">
+ <label></label>
+ </div>
+ </span>
<span class="form-control">
{{lang_randomCert}}
</span>
</div>
<div class="input-group" onclick="$('#mcustom').prop('checked', true);
$('#wcustom').show()">
- <span class="input-group-addon"><input id="mcustom" type="radio" name="mode" value="custom"></span>
+ <span class="input-group-addon">
+ <div class="radio">
+ <input id="mcustom" type="radio" name="mode" value="custom">
+ <label></label>
+ </div>
+ </span>
<span class="form-control">
{{lang_customCert}}
</span>
@@ -73,13 +88,23 @@ MIIFfTCCA...
<br>
<div class="input-group">
- <span class="input-group-addon"><input id="httpsredirect" type="checkbox" name="httpsredirect" value="on" {{redirect_checked}}></span>
+ <span class="input-group-addon">
+ <div class="checkbox">
+ <input id="httpsredirect" type="checkbox" name="httpsredirect" value="on" {{redirect_checked}}>
+ <label></label>
+ </div>
+ </span>
<span class="form-control" onclick="$('#httpsredirect').prop('checked', !$('#httpsredirect').prop('checked'))">
{{lang_httpsRedirect}}
</span>
</div>
<div class="input-group">
- <span class="input-group-addon"><input id="usehsts" type="checkbox" name="usehsts" value="on" {{hsts_checked}}></span>
+ <span class="input-group-addon">
+ <div class="checkbox">
+ <input id="usehsts" type="checkbox" name="usehsts" value="on" {{hsts_checked}}>
+ <label></label>
+ </div>
+ </span>
<span class="form-control" onclick="$('#usehsts').prop('checked', !$('#usehsts').prop('checked'))">
{{lang_useHsts}}
</span>
@@ -87,7 +112,7 @@ MIIFfTCCA...
<br>
<div class="pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</div>
</div>
diff --git a/modules-available/webinterface/templates/passwords.html b/modules-available/webinterface/templates/passwords.html
index 8481d884..e190bb72 100644
--- a/modules-available/webinterface/templates/passwords.html
+++ b/modules-available/webinterface/templates/passwords.html
@@ -6,20 +6,30 @@
<div class="panel-body">
<p>{{lang_passwordsDescription}}</p>
<div class="input-group" onclick="$('#pmshow').prop('checked', true)">
- <span class="input-group-addon"><input id="pmshow" type="radio" name="mode" value="show" {{selected_show}}></span>
+ <span class="input-group-addon">
+ <div class="radio">
+ <input id="pmshow" type="radio" name="mode" value="show" {{selected_show}}>
+ <label></label>
+ </div>
+ </span>
<span class="form-control">
{{lang_showPasswords}}
</span>
</div>
<div class="input-group" onclick="$('#pmhide').prop('checked', true)">
- <span class="input-group-addon"><input id="pmhide" type="radio" name="mode" value="hide" {{selected_hide}}></span>
+ <span class="input-group-addon">
+ <div class="radio">
+ <input id="pmhide" type="radio" name="mode" value="hide" {{selected_hide}}>
+ <label></label>
+ </div>
+ </span>
<span class="form-control">
{{lang_hidePasswords}}
</span>
</div>
<br>
<div class="pull-right">
- <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</div>
</div>