summaryrefslogtreecommitdiffstats
path: root/modules-available
diff options
context:
space:
mode:
authorJannik Schönartz2018-02-06 13:45:54 +0100
committerJannik Schönartz2018-02-06 13:45:54 +0100
commit3d801dcde21c8166e3281a180ef21ff05b175fd6 (patch)
treef8508e11bc6df5a27f0417ad8098c4ed013a691c /modules-available
parent[usb-lock-off] Reworked config chooser. Switched from the dropdown config sel... (diff)
parent[statistics_reporting] Translation for 'settings' (diff)
downloadslx-admin-3d801dcde21c8166e3281a180ef21ff05b175fd6.tar.gz
slx-admin-3d801dcde21c8166e3281a180ef21ff05b175fd6.tar.xz
slx-admin-3d801dcde21c8166e3281a180ef21ff05b175fd6.zip
Merge branch 'origin/master' into usb-lock-off
Diffstat (limited to 'modules-available')
-rw-r--r--modules-available/adduser/lang/de/template-tags.json6
-rw-r--r--modules-available/adduser/lang/en/template-tags.json6
-rw-r--r--modules-available/adduser/lang/pt/template-tags.json6
-rw-r--r--modules-available/dnbd3/hooks/runmode/config.json4
-rw-r--r--modules-available/dnbd3/hooks/statistics/machine-replace.inc.php6
-rw-r--r--modules-available/dozmod/lang/de/messages.json4
-rw-r--r--modules-available/dozmod/lang/de/template-tags.json4
-rw-r--r--modules-available/dozmod/lang/en/messages.json4
-rw-r--r--modules-available/dozmod/lang/en/template-tags.json4
-rw-r--r--modules-available/eventlog/lang/de/permissions.json3
-rw-r--r--modules-available/eventlog/lang/en/module.json2
-rw-r--r--modules-available/eventlog/lang/en/permissions.json3
-rw-r--r--modules-available/eventlog/page.inc.php49
-rw-r--r--modules-available/eventlog/permissions/permissions.json3
-rw-r--r--modules-available/eventlog/templates/_page.html1
-rw-r--r--modules-available/eventlog/templates/heading.html1
-rw-r--r--modules-available/exams/lang/de/permissions.json5
-rw-r--r--modules-available/exams/lang/en/permissions.json5
-rw-r--r--modules-available/exams/page.inc.php303
-rw-r--r--modules-available/exams/permissions/permissions.json5
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html2
-rw-r--r--modules-available/exams/templates/page-exams.html7
-rw-r--r--modules-available/exams/templates/page-upcoming-lectures.html2
-rw-r--r--modules-available/locationinfo/inc/locationinfo.inc.php3
-rw-r--r--modules-available/locationinfo/lang/de/template-tags.json2
-rw-r--r--modules-available/locationinfo/lang/en/template-tags.json2
-rw-r--r--modules-available/locationinfo/page.inc.php2
-rw-r--r--modules-available/locationinfo/templates/page-config-panel-url.html22
-rw-r--r--modules-available/main/lang/de/global-tags.json3
-rw-r--r--modules-available/main/lang/en/global-tags.json3
-rw-r--r--modules-available/main/lang/pt/global-tags.json3
-rw-r--r--modules-available/minilinux/lang/de/template-tags.json2
-rw-r--r--modules-available/minilinux/lang/en/template-tags.json4
-rw-r--r--modules-available/minilinux/lang/pt/template-tags.json2
-rw-r--r--modules-available/minilinux/page.inc.php3
-rw-r--r--modules-available/minilinux/templates/filelist.html2
-rw-r--r--modules-available/permissionmanager/clientscript.js16
-rw-r--r--modules-available/permissionmanager/inc/getpermissiondata.inc.php40
-rw-r--r--modules-available/permissionmanager/inc/permissiondbupdate.inc.php37
-rw-r--r--modules-available/permissionmanager/inc/permissionutil.inc.php59
-rw-r--r--modules-available/permissionmanager/lang/de/template-tags.json25
-rw-r--r--modules-available/permissionmanager/lang/en/template-tags.json25
-rw-r--r--modules-available/permissionmanager/page.inc.php54
-rw-r--r--modules-available/permissionmanager/templates/_page.html6
-rw-r--r--modules-available/permissionmanager/templates/locationstable.html6
-rw-r--r--modules-available/permissionmanager/templates/roleeditor.html23
-rw-r--r--modules-available/permissionmanager/templates/rolestable.html44
-rw-r--r--modules-available/permissionmanager/templates/treenode.html4
-rw-r--r--modules-available/permissionmanager/templates/treepanel.html12
-rw-r--r--modules-available/permissionmanager/templates/userstable.html93
-rw-r--r--modules-available/roomplanner/hooks/statistics/machine-replace.inc.php7
-rw-r--r--modules-available/runmode/hooks/statistics/machine-replace.inc.php6
-rw-r--r--modules-available/runmode/inc/runmode.inc.php2
-rw-r--r--modules-available/runmode/lang/de/messages.json1
-rw-r--r--modules-available/runmode/lang/en/messages.json1
-rw-r--r--modules-available/runmode/page.inc.php24
-rw-r--r--modules-available/runmode/templates/module-machine-list.html4
-rw-r--r--modules-available/serversetup-bwlp/lang/de/permissions.json5
-rw-r--r--modules-available/serversetup-bwlp/lang/en/permissions.json5
-rw-r--r--modules-available/serversetup-bwlp/page.inc.php13
-rw-r--r--modules-available/serversetup-bwlp/permissions/permissions.json5
-rw-r--r--modules-available/serversetup-bwlp/style.css12
-rw-r--r--modules-available/serversetup-bwlp/templates/ipaddress.html2
-rw-r--r--modules-available/serversetup-bwlp/templates/ipxe.html6
-rw-r--r--modules-available/session/lang/de/template-tags.json6
-rw-r--r--modules-available/session/lang/en/template-tags.json6
-rw-r--r--modules-available/session/lang/pt/template-tags.json6
-rw-r--r--modules-available/statistics/inc/filter.inc.php44
-rw-r--r--modules-available/statistics/lang/de/messages.json6
-rw-r--r--modules-available/statistics/lang/de/permissions.json5
-rw-r--r--modules-available/statistics/lang/de/template-tags.json7
-rw-r--r--modules-available/statistics/lang/en/messages.json6
-rw-r--r--modules-available/statistics/lang/en/permissions.json5
-rw-r--r--modules-available/statistics/lang/en/template-tags.json7
-rw-r--r--modules-available/statistics/page.inc.php101
-rw-r--r--modules-available/statistics/pages/replace.inc.php119
-rw-r--r--modules-available/statistics/permissions/permissions.json5
-rw-r--r--modules-available/statistics/templates/clientlist.html62
-rw-r--r--modules-available/statistics/templates/filterbox.html8
-rw-r--r--modules-available/statistics/templates/machine-notes.html4
-rw-r--r--modules-available/statistics/templates/page-replace.html4
-rw-r--r--modules-available/statistics_reporting/inc/getdata.inc.php142
-rw-r--r--modules-available/statistics_reporting/inc/queries.inc.php512
-rw-r--r--modules-available/statistics_reporting/inc/remotereport.inc.php2
-rw-r--r--modules-available/statistics_reporting/lang/de/template-tags.json8
-rw-r--r--modules-available/statistics_reporting/lang/en/template-tags.json8
-rw-r--r--modules-available/statistics_reporting/page.inc.php30
-rw-r--r--modules-available/statistics_reporting/style.css9
-rw-r--r--modules-available/statistics_reporting/templates/columnChooser.html9
-rw-r--r--modules-available/statistics_reporting/templates/table-client.html50
-rw-r--r--modules-available/statistics_reporting/templates/table-location.html40
-rw-r--r--modules-available/statistics_reporting/templates/table-total.html40
-rw-r--r--modules-available/sysconfig/lang/de/template-tags.json3
-rw-r--r--modules-available/sysconfig/lang/en/template-tags.json3
-rw-r--r--modules-available/sysconfig/lang/pt/template-tags.json3
-rw-r--r--modules-available/syslog/hooks/statistics/machine-replace.inc.php5
-rw-r--r--modules-available/syslog/page.inc.php4
-rw-r--r--modules-available/syslog/templates/page-syslog.html2
-rw-r--r--modules-available/translation/templates/edit.html95
-rw-r--r--modules-available/usermanagement/lang/en/template-tags.json6
-rw-r--r--modules-available/usermanagement/lang/pt/template-tags.json6
-rw-r--r--modules-available/vmstore/lang/de/permissions.json5
-rw-r--r--modules-available/vmstore/lang/de/template-tags.json7
-rw-r--r--modules-available/vmstore/lang/en/permissions.json5
-rw-r--r--modules-available/vmstore/lang/en/template-tags.json7
-rw-r--r--modules-available/vmstore/lang/pt/template-tags.json4
-rw-r--r--modules-available/vmstore/page.inc.php12
-rw-r--r--modules-available/vmstore/permissions/permissions.json5
-rw-r--r--modules-available/vmstore/style.css12
-rw-r--r--modules-available/vmstore/templates/page-vmstore.html12
110 files changed, 1642 insertions, 830 deletions
diff --git a/modules-available/adduser/lang/de/template-tags.json b/modules-available/adduser/lang/de/template-tags.json
index 0f446025..99d17947 100644
--- a/modules-available/adduser/lang/de/template-tags.json
+++ b/modules-available/adduser/lang/de/template-tags.json
@@ -2,7 +2,5 @@
"lang_confirmation": "Wiederholen",
"lang_createUser": "Benutzer anlegen",
"lang_fullName": "Vollst\u00e4ndiger Name",
- "lang_password": "Passwort",
- "lang_telephone": "Telefon",
- "lang_username": "Benutzerkennung"
-} \ No newline at end of file
+ "lang_telephone": "Telefon"
+}
diff --git a/modules-available/adduser/lang/en/template-tags.json b/modules-available/adduser/lang/en/template-tags.json
index 42bae6dc..24f8cd42 100644
--- a/modules-available/adduser/lang/en/template-tags.json
+++ b/modules-available/adduser/lang/en/template-tags.json
@@ -2,7 +2,5 @@
"lang_confirmation": "Confirm Password",
"lang_createUser": "Create User",
"lang_fullName": "Full Name",
- "lang_password": "Password",
- "lang_telephone": "Telephone",
- "lang_username": "Username"
-} \ No newline at end of file
+ "lang_telephone": "Telephone"
+}
diff --git a/modules-available/adduser/lang/pt/template-tags.json b/modules-available/adduser/lang/pt/template-tags.json
index 524f3dd5..ba204937 100644
--- a/modules-available/adduser/lang/pt/template-tags.json
+++ b/modules-available/adduser/lang/pt/template-tags.json
@@ -2,7 +2,5 @@
"lang_confirmation": "Confirmar Senha",
"lang_createUser": "Criar Usu\u00e1rio",
"lang_fullName": "Nome Completo",
- "lang_password": "Senha",
- "lang_telephone": "Telefone",
- "lang_username": "Nome de Usu\u00e1rio"
-} \ No newline at end of file
+ "lang_telephone": "Telefone"
+}
diff --git a/modules-available/dnbd3/hooks/runmode/config.json b/modules-available/dnbd3/hooks/runmode/config.json
index 095cb42f..5db53f0b 100644
--- a/modules-available/dnbd3/hooks/runmode/config.json
+++ b/modules-available/dnbd3/hooks/runmode/config.json
@@ -2,5 +2,7 @@
"isClient": false,
"configHook": "Dnbd3Util::runmodeConfigHook",
"noSysconfig": true,
- "systemdDefaultTarget": "dnbd3-proxy"
+ "systemdDefaultTarget": "dnbd3-proxy",
+ "allowGenericEditor": false,
+ "deleteUrlSnippet": "dummyparam="
} \ No newline at end of file
diff --git a/modules-available/dnbd3/hooks/statistics/machine-replace.inc.php b/modules-available/dnbd3/hooks/statistics/machine-replace.inc.php
new file mode 100644
index 00000000..5e4c4e75
--- /dev/null
+++ b/modules-available/dnbd3/hooks/statistics/machine-replace.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+foreach ($list as $entry) {
+ unset($entry['datelimit']);
+ Database::exec('UPDATE IGNORE dnbd3_server SET machineuuid = :new WHERE machineuuid = :old', $entry);
+}
diff --git a/modules-available/dozmod/lang/de/messages.json b/modules-available/dozmod/lang/de/messages.json
index 47580bcb..60c37927 100644
--- a/modules-available/dozmod/lang/de/messages.json
+++ b/modules-available/dozmod/lang/de/messages.json
@@ -8,5 +8,7 @@
"nothing-submitted": "Es wurde nichts \u00fcbermittelt",
"runtimelimits-config-saved": "Einstellungen gespeichert",
"templates-saved": "Templates wurden gespeichert",
- "timeout": "Zeit\u00fcberschreitung"
+ "timeout": "Zeit\u00fcberschreitung",
+ "unknown-targetid": "Target {{0}} nicht bekannt",
+ "unknown-userid": "Unbekannter Nutzer, {{0}}"
} \ No newline at end of file
diff --git a/modules-available/dozmod/lang/de/template-tags.json b/modules-available/dozmod/lang/de/template-tags.json
index 4b49579a..a1c23d2e 100644
--- a/modules-available/dozmod/lang/de/template-tags.json
+++ b/modules-available/dozmod/lang/de/template-tags.json
@@ -54,7 +54,6 @@
"lang_organizationListHeader": "Nutzungsrechte f\u00fcr den Satelliten festlegen",
"lang_os": "Betriebssystem",
"lang_owner": "Besitzer",
- "lang_password": "Passwort",
"lang_passwordplaceholder": "SMTP Passwort",
"lang_placeholders": "Platzhalter",
"lang_port": "Port",
@@ -87,8 +86,7 @@
"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",
"lang_usernameplaceholder": "SMTP Benutzername",
"lang_version": "Version vom",
"lang_when": "Wann"
-} \ No newline at end of file
+}
diff --git a/modules-available/dozmod/lang/en/messages.json b/modules-available/dozmod/lang/en/messages.json
index a17eae37..6d7ea0ac 100644
--- a/modules-available/dozmod/lang/en/messages.json
+++ b/modules-available/dozmod/lang/en/messages.json
@@ -8,5 +8,7 @@
"nothing-submitted": "There was nothing submitted",
"runtimelimits-config-saved": "Configuration saved successfully",
"templates-saved": "Templates saved successfully",
- "timeout": "Timeout"
+ "timeout": "Timeout",
+ "unknown-targetid": "Unknown target id {{0}}",
+ "unknown-userid": "Unknown user id {{0}}"
} \ 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 ed8f3465..f12e4ab8 100644
--- a/modules-available/dozmod/lang/en/template-tags.json
+++ b/modules-available/dozmod/lang/en/template-tags.json
@@ -54,7 +54,6 @@
"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",
@@ -87,8 +86,7 @@
"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": "Username",
"lang_usernameplaceholder": "SMTP Username",
"lang_version": "Version timestamp",
"lang_when": "When"
-} \ No newline at end of file
+}
diff --git a/modules-available/eventlog/lang/de/permissions.json b/modules-available/eventlog/lang/de/permissions.json
new file mode 100644
index 00000000..7f1087bf
--- /dev/null
+++ b/modules-available/eventlog/lang/de/permissions.json
@@ -0,0 +1,3 @@
+{
+ "view": "Server Log anschauen."
+} \ 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 8217fc02..0fc536f3 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/permissions.json b/modules-available/eventlog/lang/en/permissions.json
new file mode 100644
index 00000000..ec438a4b
--- /dev/null
+++ b/modules-available/eventlog/lang/en/permissions.json
@@ -0,0 +1,3 @@
+{
+ "view": "View server log."
+} \ No newline at end of file
diff --git a/modules-available/eventlog/page.inc.php b/modules-available/eventlog/page.inc.php
index 87957479..320c3b07 100644
--- a/modules-available/eventlog/page.inc.php
+++ b/modules-available/eventlog/page.inc.php
@@ -6,36 +6,43 @@ class Page_EventLog extends Page
protected function doPreprocess()
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
- User::setLastSeenEvent(Property::getLastWarningId());
+ if (User::hasPermission("view")) {
+ User::setLastSeenEvent(Property::getLastWarningId());
+ }
}
protected function doRender()
{
- $today = date('d.m.Y');
- $yesterday = date('d.m.Y', time() - 86400);
- $lines = array();
- $paginate = new Paginate("SELECT logid, dateline, logtypeid, description, extra FROM eventlog 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');
+ Render::addTemplate("heading");
+ if (User::hasPermission("view")) {
+ $today = date('d.m.Y');
+ $yesterday = date('d.m.Y', time() - 86400);
+ $lines = array();
+ $paginate = new Paginate("SELECT logid, dateline, logtypeid, description, extra FROM eventlog 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['icon'] = $this->typeToIcon($row['logtypeid']);
+ $row['color'] = $this->typeToColor($row['logtypeid']);
+ $lines[] = $row;
}
- $row['date'] = $day . date(' H:i', $row['dateline']);
- $row['icon'] = $this->typeToIcon($row['logtypeid']);
- $row['color'] = $this->typeToColor($row['logtypeid']);
- $lines[] = $row;
- }
- $paginate->render('_page', array(
- 'list' => $lines
- ));
+ $paginate->render('_page', array(
+ 'list' => $lines
+ ));
+ } else {
+ Message::addError('main.no-permission');
+ }
}
private function typeToIcon($type)
diff --git a/modules-available/eventlog/permissions/permissions.json b/modules-available/eventlog/permissions/permissions.json
new file mode 100644
index 00000000..f04ea714
--- /dev/null
+++ b/modules-available/eventlog/permissions/permissions.json
@@ -0,0 +1,3 @@
+[
+ "view"
+] \ No newline at end of file
diff --git a/modules-available/eventlog/templates/_page.html b/modules-available/eventlog/templates/_page.html
index 0875664e..239286f8 100644
--- a/modules-available/eventlog/templates/_page.html
+++ b/modules-available/eventlog/templates/_page.html
@@ -1,4 +1,3 @@
-<h1>{{lang_eventLog}}</h1>
{{{pagenav}}}
<table class="table table-striped table-condensed">
<thead>
diff --git a/modules-available/eventlog/templates/heading.html b/modules-available/eventlog/templates/heading.html
new file mode 100644
index 00000000..37612a77
--- /dev/null
+++ b/modules-available/eventlog/templates/heading.html
@@ -0,0 +1 @@
+<h1>{{lang_eventLog}}</h1> \ No newline at end of file
diff --git a/modules-available/exams/lang/de/permissions.json b/modules-available/exams/lang/de/permissions.json
new file mode 100644
index 00000000..3ead6249
--- /dev/null
+++ b/modules-available/exams/lang/de/permissions.json
@@ -0,0 +1,5 @@
+{
+ "exams.add": "Neues Examen hinzufügen.",
+ "exams.delete": "Examen löschen.",
+ "exams.edit": "Examen bearbeiten."
+} \ No newline at end of file
diff --git a/modules-available/exams/lang/en/permissions.json b/modules-available/exams/lang/en/permissions.json
new file mode 100644
index 00000000..3e14a761
--- /dev/null
+++ b/modules-available/exams/lang/en/permissions.json
@@ -0,0 +1,5 @@
+{
+ "exams.add": "Add new exam.",
+ "exams.delete": "Delete exam.",
+ "exams.edit": "Edit exam."
+} \ No newline at end of file
diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php
index 75fb6a0b..b32f758c 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -9,6 +9,10 @@ class Page_Exams extends Page
private $currentExam;
private $rangeMin;
private $rangeMax;
+ private $userEditLocations = [];
+ private $userDeleteLocations = [];
+ private $userAddLocations = [];
+ private $allowedLocations = [];
/** if examid is set, also add a column 'selected' **/
@@ -37,9 +41,33 @@ class Page_Exams extends Page
. "LEFT JOIN sat.lecture l USING (lectureid) "
. "GROUP BY examid "
. "ORDER BY examid ASC");
+
while ($exam = $tmp->fetch(PDO::FETCH_ASSOC)) {
- $this->exams[] = $exam;
+ // check if allowed to edit this exam
+ if ($this->allowedToEdit($exam['examid'])) {
+ $exam['allowedEdit'] = True;
+ }
+ // check if allowed to delete this exam
+ if ($this->allowedToDelete($exam['examid'])) {
+ $exam['allowedDelete'] = True;
+ }
+
+
+ $locationids = explode(',', $exam['locationids']);
+ // if global permission, add all exams to the list, no filter required
+ if ($locationids[0] == 0) {
+ $this->exams[] = $exam;
+ } else {
+ foreach($locationids as $locid) {
+ // only add the exam if permisson for atleast one of the exam locations
+ if (in_array($locid, $this->allowedLocations)) {
+ $this->exams[] = $exam;
+ break;
+ }
+ }
+ }
}
+
}
protected function readLectures()
@@ -58,6 +86,67 @@ class Page_Exams extends Page
}
}
+ // Returns the list of locations of the exam
+ protected function getExamLocations($examid) {
+ $res = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", array('examid' => $examid));
+ return $res;
+ }
+
+ // Initialise the user-permission-based lists
+ protected function setUserLocations() {
+
+ // all locations the user has permission to edit
+ $this->userEditLocations = User::getAllowedLocations("exams.edit");
+ // all locations the user has permission to delete
+ $this->userDeleteLocations = User::getAllowedLocations("exams.delete");
+ // all locations the user has permission to add
+ $this->userAddLocations = User::getAllowedLocations("exams.add");
+ // all locations the user has at least one of the 3 permissions
+ $this->allowedLocations = array_unique(array_merge($this->userAddLocations, $this->userEditLocations, $this->userDeleteLocations));
+ }
+
+ // returns true if user is allowed to delete the exam
+ protected function allowedToDelete($examid) {
+
+ $res = $this->getExamLocations($examid);
+ $locations = [];
+ while ($locId = $res->fetch(PDO::FETCH_ASSOC)) {
+ $locations[] = $locId['locationid'];
+ }
+
+ return empty(array_diff($locations, $this->userDeleteLocations));
+
+ }
+
+ // returns true if user is allowed to add an exam
+ protected function allowedToAdd() {
+ return User::hasPermission("exams.add");
+ }
+
+ // returns true if user is allowed to edit the exam
+ protected function allowedToEdit($examid) {
+
+ $res = $this->getExamLocations($examid);
+ $locations = [];
+ while ($locId = $res->fetch(PDO::FETCH_ASSOC)) {
+ $locations[] = $locId['locationid'];
+ }
+
+ return empty(array_diff($locations, $this->userEditLocations));
+
+ }
+
+ // checks if user is allowed to save an exam with all the locations
+ // needs information if it's add (second para = true) or edit (second para = false)
+ protected function allowedToSave($locationids, $isAdd) {
+
+ if ($isAdd) {
+ return empty(array_diff($locationids, $this->userAddLocations));
+ } else {
+ return empty(array_diff($locationids, $this->userEditLocations));
+ }
+ }
+
protected function makeItemsForVis()
{
$out = [];
@@ -88,6 +177,7 @@ class Page_Exams extends Page
$locationids[] = $location['locationid'];
}
}
+
foreach ($locationids as $locationid) {
$out[] = [
'id' => 'shadow_' . $unique_ids++,
@@ -131,13 +221,17 @@ class Page_Exams extends Page
protected function makeGroupsForVis()
{
+
$out = [];
+
foreach ($this->locations as $l) {
- $out[] = [
- 'id' => $l['locationid'],
- 'content' => $l['locationpad'] . ' ' . $l['locationname'],
- 'sortIndex' => $l['sortIndex'],
- ];
+ if (in_array($l["locationid"], $this->allowedLocations)) {
+ $out[] = [
+ 'id' => $l['locationid'],
+ 'content' => $l['locationpad'] . ' ' . $l['locationname'],
+ 'sortIndex' => $l['sortIndex'],
+ ];
+ }
}
return json_encode($out);
}
@@ -189,7 +283,7 @@ class Page_Exams extends Page
}
return $out;
}
-
+
protected function makeEditFromArray($source)
{
if (!isset($source['description']) && isset($source['displayname'])) {
@@ -242,42 +336,45 @@ class Page_Exams extends Page
if ($examid === 0) {
// No examid given, is add
- $res = Database::exec("INSERT INTO exams(lectureid, starttime, endtime, autologin, description) VALUES(:lectureid, :starttime, :endtime, :autologin, :description);",
- compact('lectureid', 'starttime', 'endtime', 'autologin', 'description')) !== false;
+ if ($this->allowedToSave($locationids, True)) {
+ $res = Database::exec("INSERT INTO exams(lectureid, starttime, endtime, autologin, description) VALUES(:lectureid, :starttime, :endtime, :autologin, :description);",
+ compact('lectureid', 'starttime', 'endtime', 'autologin', 'description')) !== false;
- $exam_id = Database::lastInsertId();
- foreach ($locationids as $lid) {
- $res = $res && Database::exec("INSERT INTO exams_x_location(examid, locationid) VALUES(:exam_id, :lid)", compact('exam_id', 'lid')) !== false;
- }
- if ($res === false) {
- Message::addError('exam-not-added');
- } else {
- Message::addInfo('exam-added-success');
+ $exam_id = Database::lastInsertId();
+ foreach ($locationids as $lid) {
+ $res = $res && Database::exec("INSERT INTO exams_x_location(examid, locationid) VALUES(:exam_id, :lid)", compact('exam_id', 'lid')) !== false;
+ }
+ if ($res === false) {
+ Message::addError('exam-not-added');
+ } else {
+ Message::addInfo('exam-added-success');
+ }
}
Util::redirect('?do=exams');
}
// Edit
+ if ($this->allowedToSave($locationids, False)) {
+ $this->currentExam = Database::queryFirst("SELECT * FROM exams WHERE examid = :examid", array('examid' => $examid));
+ if ($this->currentExam === false) {
+ Message::addError('invalid-exam-id', $examid);
+ Util::redirect('?do=exams');
+ }
- $this->currentExam = Database::queryFirst("SELECT * FROM exams WHERE examid = :examid", array('examid' => $examid));
- if ($this->currentExam === false) {
- Message::addError('invalid-exam-id', $examid);
- Util::redirect('?do=exams');
- }
-
- /* update fields */
- $res = Database::exec("UPDATE exams SET lectureid = :lectureid, starttime = :starttime, endtime = :endtime, autologin = :autologin, description = :description WHERE examid = :examid",
- compact('lectureid', 'starttime', 'endtime', 'description', 'examid', 'autologin')) !== false;
- /* drop all connections and reconnect to rooms */
- $res = $res && Database::exec("DELETE FROM exams_x_location WHERE examid = :examid", compact('examid')) !== false;
- /* reconnect */
- foreach ($locationids as $lid) {
- $res = $res && Database::exec("INSERT INTO exams_x_location(examid, locationid) VALUES(:examid, :lid)", compact('examid', 'lid')) !== false;
- }
- if ($res !== false) {
- Message::addInfo("changes-successfully-saved");
- } else {
- Message::addError("error-while-saving-changes");
+ /* update fields */
+ $res = Database::exec("UPDATE exams SET lectureid = :lectureid, starttime = :starttime, endtime = :endtime, autologin = :autologin, description = :description WHERE examid = :examid",
+ compact('lectureid', 'starttime', 'endtime', 'description', 'examid', 'autologin')) !== false;
+ /* drop all connections and reconnect to rooms */
+ $res = $res && Database::exec("DELETE FROM exams_x_location WHERE examid = :examid", compact('examid')) !== false;
+ /* reconnect */
+ foreach ($locationids as $lid) {
+ $res = $res && Database::exec("INSERT INTO exams_x_location(examid, locationid) VALUES(:examid, :lid)", compact('examid', 'lid')) !== false;
+ }
+ if ($res !== false) {
+ Message::addInfo("changes-successfully-saved");
+ } else {
+ Message::addError("error-while-saving-changes");
+ }
}
Util::redirect('?do=exams');
}
@@ -286,7 +383,7 @@ class Page_Exams extends Page
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
@@ -299,26 +396,40 @@ class Page_Exams extends Page
$this->action = $req_action;
}
- if ($this->action === 'show') {
+ if (Request::isPost()) {
+ $examid = Request::post('examid', 0, 'int');
+ } else if (Request::isGet()) {
+ $examid = Request::get('examid', 0, 'int');
+ } else {
+ die('Neither Post nor Get Request send.');
+ }
+
+ // initialise user-permission-lists
+ $this->setUserLocations();
+ if ($this->action === 'show') {
$this->readExams();
$this->readLocations();
$this->readLectures();
} elseif ($this->action === 'add') {
- $this->readLectures();
+ if($this->allowedToAdd()) {
+ $this->readLectures();
+ }
} elseif ($this->action === 'edit') {
- $examid = Request::get('examid', 0, 'int');
- $this->currentExam = Database::queryFirst("SELECT * FROM exams WHERE examid = :examid", array('examid' => $examid));
- if ($this->currentExam === false) {
- Message::addError('invalid-exam-id', $examid);
- Util::redirect('?do=exams');
+ if($this->allowedToEdit($examid)) {
+ $this->currentExam = Database::queryFirst("SELECT * FROM exams WHERE examid = :examid", array('examid' => $examid));
+ if ($this->currentExam === false) {
+ Message::addError('invalid-exam-id', $examid);
+ Util::redirect('?do=exams');
+ }
+ $this->readLocations($examid);
+ $this->readLectures();
+
}
- $this->readLocations($examid);
- $this->readLectures();
} elseif ($this->action === 'save') {
@@ -329,14 +440,17 @@ class Page_Exams extends Page
if (!Request::isPost()) {
die('delete only works with a post request');
}
- $examid = Request::post('examid');
- $res1 = Database::exec("DELETE FROM exams WHERE examid = :examid;", compact('examid'));
- $res2 = Database::exec("DELETE FROM exams_x_location WHERE examid = :examid;", compact('examid'));
- if ($res1 === false || $res2 === false) {
- Message::addError('exam-not-deleted-error');
- } else {
- Message::addInfo('exam-deleted-success');
+
+ if ($this->allowedToDelete($examid)) {
+ $res1 = Database::exec("DELETE FROM exams WHERE examid = :examid;", compact('examid'));
+ $res2 = Database::exec("DELETE FROM exams_x_location WHERE examid = :examid;", compact('examid'));
+ if ($res1 === false || $res2 === false) {
+ Message::addError('exam-not-deleted-error');
+ } else {
+ Message::addInfo('exam-deleted-success');
+ }
}
+
Util::redirect('?do=exams');
} elseif ($this->action === false) {
@@ -348,13 +462,22 @@ class Page_Exams extends Page
protected function doRender()
{
+ if (Request::isPost()) {
+ $examid = Request::post('examid', 0, 'int');
+ } else if (Request::isGet()) {
+ $examid = Request::get('examid', 0, 'int');
+ } else {
+ die('Neither Post nor Get Request send.');
+ }
+
if ($this->action === "show") {
// General title and description
Render::addTemplate('page-main-heading');
// List of defined exam periods
Render::addTemplate('page-exams', [
- 'exams' => $this->makeExamsForTemplate()
+ 'exams' => $this->makeExamsForTemplate(),
+ 'allowedToAdd' => $this->allowedToAdd()
]);
// List of upcoming lectures marked as exam
$upcoming = $this->makeLectureExamList();
@@ -363,6 +486,7 @@ class Page_Exams extends Page
} else {
Render::addTemplate('page-upcoming-lectures', [
'pending_lectures' => $upcoming,
+ 'allowedToAdd' => $this->allowedToAdd(),
'decollapse' => array_key_exists('class', end($upcoming))
]);
}
@@ -380,37 +504,62 @@ class Page_Exams extends Page
} elseif ($this->action === "add") {
- Render::setTitle(Dictionary::translate('title_add-exam'));
- $data = [];
- $baseLecture = Request::any('lectureid', false, 'string');
- $locations = null;
- if ($baseLecture !== false) {
- foreach ($this->lectures as &$lecture) {
- if ($lecture['lectureid'] === $baseLecture) {
- $data['exam'] = $this->makeEditFromArray($lecture);
- $locations = explode(',', $lecture['lids']);
- $lecture['selected'] = 'selected';
- break;
+ if($this->allowedToAdd()) {
+ Render::setTitle(Dictionary::translate('title_add-exam'));
+ $data = [];
+ $baseLecture = Request::any('lectureid', false, 'string');
+ $locations = null;
+ if ($baseLecture !== false) {
+ foreach ($this->lectures as &$lecture) {
+ if ($lecture['lectureid'] === $baseLecture) {
+ $data['exam'] = $this->makeEditFromArray($lecture);
+ $locations = explode(',', $lecture['lids']);
+ $lecture['selected'] = 'selected';
+ break;
+ }
+ }
+ unset($lecture);
+ }
+
+ $this->readLocations($locations);
+ $data['lectures'] = $this->lectures;
+ $data['locations'] = $this->locations;
+
+ // if user has no permission to add for this location, disable the location in the select
+ foreach ($data['locations'] as &$loc) {
+ if (!in_array($loc["locationid"], $this->userAddLocations)) {
+ $loc["disabled"] = "disabled";
}
}
- unset($lecture);
+
+ Render::addTemplate('page-add-edit-exam', $data);
}
- $data['lectures'] = $this->lectures;
- $this->readLocations($locations);
- $data['locations'] = $this->locations;
- Render::addTemplate('page-add-edit-exam', $data);
} elseif ($this->action === 'edit') {
- Render::setTitle(Dictionary::translate('title_edit-exam'));
- $exam = $this->makeEditFromArray($this->currentExam);
- foreach ($this->lectures as &$lecture) {
- if ($lecture['lectureid'] === $this->currentExam['lectureid']) {
- $lecture['selected'] = 'selected';
+ if ($this->allowedToEdit($examid)) {
+ Render::setTitle(Dictionary::translate('title_edit-exam'));
+ $exam = $this->makeEditFromArray($this->currentExam);
+ foreach ($this->lectures as &$lecture) {
+ if ($lecture['lectureid'] === $this->currentExam['lectureid']) {
+ $lecture['selected'] = 'selected';
+ }
+ }
+
+ $data = [];
+ $data['exam'] = $exam;
+ $data['locations'] = $this->locations;
+ $data['lectures'] = $this->lectures;
+
+ // if user has no permission to edit for this location, disable the location in the select
+ foreach ($data['locations'] as &$loc) {
+ if (!in_array($loc["locationid"], $this->userEditLocations)) {
+ $loc["disabled"] = "disabled";
+ }
}
- }
- Render::addTemplate('page-add-edit-exam', ['exam' => $exam, 'locations' => $this->locations, 'lectures' => $this->lectures]);
+ Render::addTemplate('page-add-edit-exam', $data);
+ }
}
}
diff --git a/modules-available/exams/permissions/permissions.json b/modules-available/exams/permissions/permissions.json
new file mode 100644
index 00000000..215b3399
--- /dev/null
+++ b/modules-available/exams/permissions/permissions.json
@@ -0,0 +1,5 @@
+[
+ "exams.add",
+ "exams.delete",
+ "exams.edit"
+] \ 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 58c61b11..11bffed8 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -19,7 +19,7 @@
</div>
<select id="locations" multiple name="locations[]">
{{#locations}}
- <option value="{{locationid}}" {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
+ <option value="{{locationid}}" {{disabled}} {{#selected}}selected{{/selected}}>{{locationpad}} {{locationname}}</option>
{{/locations}}
</select>
</div>
diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html
index 29b8c319..bb6cbd0a 100644
--- a/modules-available/exams/templates/page-exams.html
+++ b/modules-available/exams/templates/page-exams.html
@@ -43,10 +43,9 @@
{{^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}}">
+ <a href="?do=exams&action=edit&examid={{examid}}" class="btn btn-default btn-sm {{^allowedEdit}}disabled{{/allowedEdit}}"><span class="glyphicon glyphicon-edit"></span></a> <input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="examid" value="{{examid}}">
- <button class="btn {{btnClass}} btn-sm">{{lang_delete}}</button>
+ <button {{^allowedDelete}}disabled{{/allowedDelete}} class="btn {{btnClass}} btn-sm"><span class="glyphicon glyphicon-trash"></span></button>
</form>
</td>
</tr>
@@ -56,7 +55,7 @@
</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>
+ <a href="?do=exams&action=add" class="btn btn-success {{^allowedToAdd}}disabled{{/allowedToAdd}}"><span class="glyphicon glyphicon-plus-sign"></span> {{lang_addExam}}</a>
</div>
</div>
</div>
diff --git a/modules-available/exams/templates/page-upcoming-lectures.html b/modules-available/exams/templates/page-upcoming-lectures.html
index 8ff8143e..074aa42d 100644
--- a/modules-available/exams/templates/page-upcoming-lectures.html
+++ b/modules-available/exams/templates/page-upcoming-lectures.html
@@ -31,7 +31,7 @@
<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">
+ <a href="?do=exams&amp;action=add&amp;lectureid={{lectureid}}" class="btn btn-sm btn-success {{^allowedToAdd}}disabled{{/allowedToAdd}}" role="button">
<span class="glyphicon glyphicon-plus-sign"></span>
<span class="hidden-sm">{{lang_addExam}}</span>
</a>
diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php
index e3d7db3d..38e271fe 100644
--- a/modules-available/locationinfo/inc/locationinfo.inc.php
+++ b/modules-available/locationinfo/inc/locationinfo.inc.php
@@ -130,6 +130,9 @@ class LocationInfo
if ($data && $data['insecure-ssl']) {
ConfigHolder::add('SLX_BROWSER_INSECURE', '1');
}
+ if ($data && $data['reload-minutes']) {
+ ConfigHolder::add('SLX_BROWSER_RELOAD_SECS', $data['reload-minutes'] * 60);
+ }
}
ConfigHolder::add('SLX_BROWSER_URL', 'http://' . $_SERVER['SERVER_ADDR'] . '/panel/' . $panelUuid);
ConfigHolder::add('SLX_ADDONS', '', 1000);
diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json
index b305f800..bcdf7148 100644
--- a/modules-available/locationinfo/lang/de/template-tags.json
+++ b/modules-available/locationinfo/lang/de/template-tags.json
@@ -71,6 +71,8 @@
"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_reloadIntervalMins": "Neuladen alle X Minuten",
+ "lang_reloadIntervalTooltip": "Setzen Sie dieses Feld auf einen Wert > 0 (in Minuten), um die Seite auf den Clients regelm\u00e4\u00dfig neu zu laden. Feld auf 0 setzen oder leer lassen deaktiviert diese Funktion.",
"lang_remoteSchedule": "Abruf Belegungsplan",
"lang_room": "Raum",
"lang_roomId": "Raum ID",
diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json
index 5679e8b8..558ddff0 100644
--- a/modules-available/locationinfo/lang/en/template-tags.json
+++ b/modules-available/locationinfo/lang/en/template-tags.json
@@ -71,6 +71,8 @@
"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_reloadIntervalMins": "Reload every X minutes",
+ "lang_reloadIntervalTooltip": "Set this field to a value > 0 (in minutes) to reload the page periodically. Set to 0 or leave blank to disable.",
"lang_remoteSchedule": "Time table retrieval",
"lang_room": "Room",
"lang_roomId": "Room ID",
diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php
index 22a21951..777b84db 100644
--- a/modules-available/locationinfo/page.inc.php
+++ b/modules-available/locationinfo/page.inc.php
@@ -362,6 +362,7 @@ class Page_LocationInfo extends Page
$conf = array(
'url' => Request::post('url', 'https://www.bwlehrpool.de/', 'string'),
'insecure-ssl' => Request::post('insecure-ssl', 0, 'int'),
+ 'reload-minutes' => max(0, Request::post('reloadminutes', 0, 'int')),
);
return array('config' => $conf, 'locationids' => []);
}
@@ -887,6 +888,7 @@ class Page_LocationInfo extends Page
'panelname' => $panel['panelname'],
'url' => $config['url'],
'ssl_checked' => $config['insecure-ssl'] ? 'checked' : '',
+ 'reloadminutes' => (int)$config['reload-minutes'],
));
} else {
Render::addTemplate('page-config-panel-summary', array(
diff --git a/modules-available/locationinfo/templates/page-config-panel-url.html b/modules-available/locationinfo/templates/page-config-panel-url.html
index 401214bd..cca81509 100644
--- a/modules-available/locationinfo/templates/page-config-panel-url.html
+++ b/modules-available/locationinfo/templates/page-config-panel-url.html
@@ -55,7 +55,10 @@
<label for="input-ssl">{{lang_insecureSsl}}</label>
</div>
<div class="col-sm-7">
- <input id="input-ssl" type="checkbox" name="insecure-ssl" {{ssl_checked}} value="1">
+ <div class="checkbox">
+ <input id="input-ssl" type="checkbox" name="insecure-ssl" {{ssl_checked}} value="1">
+ <label></label>
+ </div>
</div>
<div class="col-sm-2">
<a class="btn btn-default helptext" title="{{lang_ignoreSslTooltip}}">
@@ -64,6 +67,23 @@
</div>
</div>
</div>
+
+ <div class="list-group-item">
+ <div class="row">
+ <div class="col-sm-3">
+ <label for="input-reload">{{lang_reloadIntervalMins}}</label>
+ </div>
+ <div class="col-sm-7">
+ <input class="form-control" id="input-reload" type="number" min="0" max="999" name="reloadminutes" pattern="\d*" value="{{reloadminutes}}">
+ </div>
+ <div class="col-sm-2">
+ <a class="btn btn-default helptext" title="{{lang_reloadIntervalTooltip}}">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </a>
+ </div>
+ </div>
+ </div>
+
</div>
</div>
</div>
diff --git a/modules-available/main/lang/de/global-tags.json b/modules-available/main/lang/de/global-tags.json
index 451e016e..de6ee798 100644
--- a/modules-available/main/lang/de/global-tags.json
+++ b/modules-available/main/lang/de/global-tags.json
@@ -9,9 +9,12 @@
"lang_hours": "Stunde(n)",
"lang_next": "Weiter",
"lang_no": "Nein",
+ "lang_password": "Passwort",
"lang_reset": "Zur\u00fccksetzen",
"lang_save": "Speichern",
"lang_today": "Heute",
+ "lang_user": "Benutzer",
+ "lang_username": "Benutzername",
"lang_yes": "Ja",
"lang_yesterday": "Gestern"
} \ No newline at end of file
diff --git a/modules-available/main/lang/en/global-tags.json b/modules-available/main/lang/en/global-tags.json
index bfadf2f2..a915a625 100644
--- a/modules-available/main/lang/en/global-tags.json
+++ b/modules-available/main/lang/en/global-tags.json
@@ -9,9 +9,12 @@
"lang_hours": "hour(s)",
"lang_next": "Next",
"lang_no": "No",
+ "lang_password": "Password",
"lang_reset": "Reset",
"lang_save": "Save",
"lang_today": "Today",
+ "lang_user": "User",
+ "lang_username": "Username",
"lang_yes": "Yes",
"lang_yesterday": "Yesterday"
} \ No newline at end of file
diff --git a/modules-available/main/lang/pt/global-tags.json b/modules-available/main/lang/pt/global-tags.json
index 11e032e2..9029d936 100644
--- a/modules-available/main/lang/pt/global-tags.json
+++ b/modules-available/main/lang/pt/global-tags.json
@@ -7,8 +7,11 @@
"lang_edit": "Editar",
"lang_hours": "Hora(s)",
"lang_next": "Pr\u00f3ximo",
+ "lang_password": "Senha",
"lang_reset": "Limpar",
"lang_save": "Salvar",
"lang_today": "Hoje",
+ "lang_user": "User",
+ "lang_username": "Nome do Usu\u00e1rio",
"lang_yesterday": "Ontem"
} \ No newline at end of file
diff --git a/modules-available/minilinux/lang/de/template-tags.json b/modules-available/minilinux/lang/de/template-tags.json
index 6a4e7b57..18a8b7af 100644
--- a/modules-available/minilinux/lang/de/template-tags.json
+++ b/modules-available/minilinux/lang/de/template-tags.json
@@ -1,5 +1,5 @@
{
- "lang_actual": "Aktuell",
+ "lang_uptodate": "Aktuell",
"lang_canUpdate1": "Mindestens eine Komponente von",
"lang_canUpdate2": "kann aktualisiert werden. F\u00fcr einen reibungslosen Betrieb wird empfohlen, alle Komponenten auf dem aktuellen Stand zu halten.",
"lang_configurationPackageNotFound": "Keine Konfigurationspakete gefunden!",
diff --git a/modules-available/minilinux/lang/en/template-tags.json b/modules-available/minilinux/lang/en/template-tags.json
index 28942f93..aef15c03 100644
--- a/modules-available/minilinux/lang/en/template-tags.json
+++ b/modules-available/minilinux/lang/en/template-tags.json
@@ -1,7 +1,7 @@
{
- "lang_actual": "Actual",
+ "lang_uptodate": "Up to date",
"lang_canUpdate1": "At least one component of",
- "lang_canUpdate2": "can be updated. For a smooth operation, it is recommended to keep all components up to date.",
+ "lang_canUpdate2": "Can be updated. For a smooth operation, it is recommended to keep all components up to date.",
"lang_configurationPackageNotFound": "Configuration package not found!",
"lang_desiredVersion": "Desired version",
"lang_errorGetting": "Error while downloading list!",
diff --git a/modules-available/minilinux/lang/pt/template-tags.json b/modules-available/minilinux/lang/pt/template-tags.json
index f7225455..4a25f9be 100644
--- a/modules-available/minilinux/lang/pt/template-tags.json
+++ b/modules-available/minilinux/lang/pt/template-tags.json
@@ -1,5 +1,5 @@
{
- "lang_actual": "Atual",
+ "lang_uptodate": "Atual",
"lang_canUpdate1": "Pelo menos um componente de",
"lang_canUpdate2": "pode ser atualizado. Para um bom funcionamento, recomenda-se manter todos os componentes atualizados.",
"lang_configurationPackageNotFound": "Pacote de configura\u00e7\u00e3o n\u00e3o encontrado!",
diff --git a/modules-available/minilinux/page.inc.php b/modules-available/minilinux/page.inc.php
index 2623500b..98b0191d 100644
--- a/modules-available/minilinux/page.inc.php
+++ b/modules-available/minilinux/page.inc.php
@@ -61,7 +61,8 @@ class Page_MiniLinux extends Page
foreach ($selected['files'] as &$file) {
$file['uid'] = 'dlid' . $count++;
$local = CONFIG_HTTP_DIR . '/' . $system['id'] . '/' . $file['name'];
- if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime']) {
+ if (!file_exists($local) || filesize($local) !== $file['size'] || filemtime($local) < $file['mtime']
+ || md5_file($local) !== $file['md5']) {
$file['fileChanged'] = true;
$system['systemChanged'] = true;
}
diff --git a/modules-available/minilinux/templates/filelist.html b/modules-available/minilinux/templates/filelist.html
index ec3aee57..a1d0aa48 100644
--- a/modules-available/minilinux/templates/filelist.html
+++ b/modules-available/minilinux/templates/filelist.html
@@ -31,7 +31,7 @@
<div class="row">
<div class="col-sm-2">{{name}}</div>
<div class="col-xs-2">
- {{^fileChanged}}<span class="glyphicon glyphicon-ok"></span> <b>{{lang_actual}}</b>{{/fileChanged}}
+ {{^fileChanged}}<span class="glyphicon glyphicon-ok"></span> <b>{{lang_uptodate}}</b>{{/fileChanged}}
{{#fileChanged}}<span class="glyphicon glyphicon-exclamation-sign"></span> <b>{{lang_outdated}}</b>{{/fileChanged}}
</div>
<div class="col-xs-2">
diff --git a/modules-available/permissionmanager/clientscript.js b/modules-available/permissionmanager/clientscript.js
index 65065e6e..4770fa6a 100644
--- a/modules-available/permissionmanager/clientscript.js
+++ b/modules-available/permissionmanager/clientscript.js
@@ -1,5 +1,5 @@
document.addEventListener("DOMContentLoaded", function() {
- var selectize = $('#select-role');
+ var selectize = $("#select-role");
if (selectize.length) {
selectize = selectize.selectize({
allowEmptyOption: false,
@@ -13,10 +13,10 @@ document.addEventListener("DOMContentLoaded", function() {
// 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) {
+ 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();
+ $(".dataTable tbody").find("tr").hide();
}
// Find all rows which shall be shown and increase their counter by 1
$(".roleid-" + value).closest("tr").each(function () {
@@ -25,10 +25,10 @@ document.addEventListener("DOMContentLoaded", function() {
});
});
- selectize.on('item_remove', function (value, $item) {
+ 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();
+ $(".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 () {
@@ -42,9 +42,9 @@ document.addEventListener("DOMContentLoaded", function() {
});
}
- $("tr").on('click', function (e) {
- if (e.target.type !== "checkbox") {
- $(this).find("input:checkbox").trigger("click");
+ $("tr").on("click", function(e) {
+ if (e.target.type !== "checkbox" && e.target.tagName !== "A") {
+ $(this).find("input[type=checkbox]").trigger("click");
}
});
diff --git a/modules-available/permissionmanager/inc/getpermissiondata.inc.php b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
index 13c7ca89..982fa0b7 100644
--- a/modules-available/permissionmanager/inc/getpermissiondata.inc.php
+++ b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
@@ -2,9 +2,17 @@
class GetPermissionData {
- // get UserIDs, User Login Names, User Roles
+ /**
+ * Get data for all users.
+ *
+ * @return array array of users (each with userid, username and roles (each with roleid and rolename))
+ */
public static function getUserData() {
- $res = self::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
+ ");
$userdata= array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$userdata[$row['userid'].' '.$row['login']][] = array(
@@ -24,7 +32,11 @@ class GetPermissionData {
return $data;
}
- // get LocationIDs, Location Names, Roles of each Location
+ /**
+ * Get data for all locations.
+ *
+ * @return array array of locations (each including the roles that have permissions for them)
+ */
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");
@@ -46,7 +58,11 @@ class GetPermissionData {
return array_values($locations);
}
- // get all roles from database (id and name)
+ /**
+ * Get all roles.
+ *
+ * @return array array roles (each with roleid and rolename)
+ */
public static function getRoles() {
$res = Database::simpleQuery("SELECT roleid, rolename FROM role ORDER BY rolename ASC");
$data = array();
@@ -59,6 +75,12 @@ class GetPermissionData {
return $data;
}
+ /**
+ * Get permissions and locations for a given role.
+ *
+ * @param string $roleid id of the role
+ * @return array array containing an array of permissions and an array of locations
+ */
public static function getRoleData($roleid) {
$query = "SELECT roleid, rolename FROM role WHERE roleid = :roleid";
$data = Database::queryFirst($query, array("roleid" => $roleid));
@@ -77,14 +99,4 @@ class GetPermissionData {
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
index f144b35e..ffe5fac0 100644
--- a/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
+++ b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
@@ -2,7 +2,12 @@
class PermissionDbUpdate {
- // insert new user_x_role to database. "ignore" to ignore duplicate entry try
+ /**
+ * Insert all user/role combinations into the user_x_role table.
+ *
+ * @param array $users userids
+ * @param array $roles roleids
+ */
public static function addRoleToUser($users, $roles) {
$query = "INSERT IGNORE INTO user_x_role (userid, roleid) VALUES (:userid, :roleid)";
foreach($users AS $userid) {
@@ -12,24 +17,34 @@ class PermissionDbUpdate {
}
}
- // remove user_x_role entry from database
+ /**
+ * Remove all user/role combinations from the user_x_role table.
+ *
+ * @param array $users userids
+ * @param array $roles roleids
+ */
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
+ /**
+ * Delete role from the role table.
+ *
+ * @param string $roleid roleid
+ */
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));
+ Database::exec("DELETE FROM role WHERE roleid = :roleid", array("roleid" => $roleid));
}
+ /**
+ * Save changes to a role or create a new one.
+ *
+ * @param string $rolename rolename
+ * @param array $locations array of locations
+ * @param array $permissions array of permissions
+ * @param string|null $roleid roleid or null if the role does not exist yet
+ */
public static function saveRole($rolename, $locations, $permissions, $roleid = NULL) {
if ($roleid) {
Database::exec("UPDATE role SET rolename = :rolename WHERE roleid = :roleid",
diff --git a/modules-available/permissionmanager/inc/permissionutil.inc.php b/modules-available/permissionmanager/inc/permissionutil.inc.php
index 6fc33ad1..5ff41046 100644
--- a/modules-available/permissionmanager/inc/permissionutil.inc.php
+++ b/modules-available/permissionmanager/inc/permissionutil.inc.php
@@ -2,6 +2,14 @@
class PermissionUtil
{
+ /**
+ * Check if the user has the given permission (for the given location).
+ *
+ * @param string $userid userid to check
+ * @param string $permissionid permissionid to check
+ * @param int|null $locationid locationid to check or null if the location should be disregarded
+ * @return bool true if user has permission, false if not
+ */
public static function userHasPermission($userid, $permissionid, $locationid) {
$locations = array();
if (!is_null($locationid)) {
@@ -10,23 +18,29 @@ class PermissionUtil
else $locations[] = 0;
}
- $res = Database::simpleQuery("SELECT role_x_permission.permissionid as 'permissionid',
- role_x_location.locationid as 'locationid'
- FROM user_x_role
+ $res = Database::simpleQuery("SELECT permissionid, 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
+ LEFT JOIN (SELECT roleid, COALESCE(locationid, 0) AS locationid FROM role_x_location) t1
+ ON role_x_permission.roleid = t1.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))) {
+ $userPermission = rtrim($row["permissionid"], ".*").".";
+ if ((is_null($locationid) || (!is_null($row["locationid"]) && in_array($row["locationid"], $locations))) &&
+ (substr($permissionid.".", 0, strlen($userPermission)) === $userPermission || $userPermission === ".")) {
return true;
}
}
return false;
}
+ /**
+ * Get all locations where the user has the given permission.
+ *
+ * @param string $userid userid to check
+ * @param string $permissionid permissionid to check
+ * @return array array of locationids where the user has the given permission
+ */
public static function getAllowedLocations($userid, $permissionid) {
$res = Database::simpleQuery("SELECT permissionid, COALESCE(locationid, 0) AS locationid FROM user_x_role
@@ -36,8 +50,8 @@ class PermissionUtil
$allowedLocations = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $userPermission = trim($row["permissionid"], "*");
- if (!is_null($row["locationid"]) && substr($permissionid, 0, strlen($userPermission)) === $userPermission) {
+ $userPermission = rtrim($row["permissionid"], ".*").".";
+ if (substr($permissionid.".", 0, strlen($userPermission)) === $userPermission || $userPermission === ".") {
$allowedLocations[$row["locationid"]] = 1;
}
}
@@ -45,12 +59,20 @@ class PermissionUtil
$locations = Location::getTree();
if (in_array("0", $allowedLocations)) {
$allowedLocations = array_map("intval", Location::extractIds($locations));
+ $allowedLocations[] = 0;
} else {
$allowedLocations = self::getSublocations($locations, $allowedLocations);
}
return $allowedLocations;
}
+ /**
+ * Extend an array of locations by adding all sublocations.
+ *
+ * @param array $tree tree of all locations (structured like Location::getTree())
+ * @param array $locations the array of locationids to extend
+ * @return array extended array of locationids
+ */
public static function getSublocations($tree, $locations) {
$result = array_flip($locations);
foreach ($tree as $location) {
@@ -65,6 +87,11 @@ class PermissionUtil
return array_keys($result);
}
+ /**
+ * Get all permissions of all active modules that have permissions in their permissions/permissions.json file.
+ *
+ * @return array permission tree as a multidimensional array
+ */
public static function getPermissions()
{
$permissions = array();
@@ -75,7 +102,7 @@ class PermissionUtil
preg_match('#^modules/([^/]+)/#', $file, $out);
foreach( $data as $p ) {
$description = Dictionary::translateFileModule($out[1], "permissions", $p);
- $permissions = self::putInPermissionTree($out[1].".".$p, $description, $permissions);
+ self::putInPermissionTree($out[1].".".$p, $description, $permissions);
}
}
ksort($permissions);
@@ -89,10 +116,16 @@ class PermissionUtil
return $permissions;
}
- private static function putInPermissionTree($permission, $description, $tree)
+ /**
+ * Place a permission into the given permission tree.
+ *
+ * @param string $permission the permission to place in the tree
+ * @param string $description the description of the permission
+ * @param array $tree the permission tree to modify
+ */
+ private static function putInPermissionTree($permission, $description, &$tree)
{
$subPermissions = explode('.', $permission);
- $original =& $tree;
foreach ($subPermissions as $subPermission) {
if ($subPermission) {
if (!array_key_exists($subPermission, $tree)) {
@@ -101,6 +134,6 @@ class PermissionUtil
$tree =& $tree[$subPermission];
}
}
- return $original;
+ $tree = $description;
}
} \ 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
index 23b2dc68..71bd4075 100644
--- a/modules-available/permissionmanager/lang/de/template-tags.json
+++ b/modules-available/permissionmanager/lang/de/template-tags.json
@@ -1,23 +1,22 @@
{
- "lang_Roles": "Rollen",
- "lang_Users": "Nutzer",
- "lang_Locations": "Räume",
- "lang_addRole": "Rolle zuweisen",
- "lang_removeRole": "Rolle entfernen",
+ "lang_roles": "Rollen",
+ "lang_users": "Nutzer",
+ "lang_locations": "Räume",
+ "lang_addRole": "Rollen erteilen",
+ "lang_removeRole": "Rollen entziehen",
"lang_newRole": "Rolle anlegen",
- "lang_Selected": "Ausgewählt",
- "lang_Edit": "Bearbeiten",
- "lang_Remove": "Entfernen",
- "lang_Delete": "Löschen",
+ "lang_selected": "Ausgewählt",
+ "lang_edit": "Bearbeiten",
+ "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_name": "Name",
+ "lang_cancel": "Abbrechen",
+ "lang_save": "Speichern",
"lang_all": "alle",
"lang_selected": "ausgewählte",
- "lang_Permissions": "Rechte",
+ "lang_permissions": "Rechte",
"lang_selectizePlaceholder": "Nach Rollen filtern...",
"lang_searchPlaceholder": "Nach Rollen suchen...",
"lang_moduleName": "Rechtemanager",
diff --git a/modules-available/permissionmanager/lang/en/template-tags.json b/modules-available/permissionmanager/lang/en/template-tags.json
index 01056632..2d31b294 100644
--- a/modules-available/permissionmanager/lang/en/template-tags.json
+++ b/modules-available/permissionmanager/lang/en/template-tags.json
@@ -1,23 +1,22 @@
{
- "lang_Roles": "Roles",
- "lang_Users": "Users",
- "lang_Locations": "Locations",
- "lang_addRole": "Add Role",
- "lang_removeRole": "Remove Role",
+ "lang_roles": "Roles",
+ "lang_users": "Users",
+ "lang_locations": "Locations",
+ "lang_addRole": "Grant Roles",
+ "lang_removeRole": "Revoke Roles",
"lang_newRole": "New Role",
- "lang_Selected": "Selected",
- "lang_Edit": "Edit",
- "lang_Remove": "Remove",
- "lang_Delete": "Delete",
+ "lang_selected": "Selected",
+ "lang_edit": "Edit",
+ "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_name": "Name",
+ "lang_cancel": "Cancel",
+ "lang_save": "Save",
"lang_all": "all",
"lang_selected": "selected",
- "lang_Permissions": "Permissions",
+ "lang_permissions": "Permissions",
"lang_selectizePlaceholder": "Filter for roles...",
"lang_searchPlaceholder": "Search for roles...",
"lang_moduleName": "Permission Manager",
diff --git a/modules-available/permissionmanager/page.inc.php b/modules-available/permissionmanager/page.inc.php
index 9aba80b3..13d81c6a 100644
--- a/modules-available/permissionmanager/page.inc.php
+++ b/modules-available/permissionmanager/page.inc.php
@@ -64,14 +64,14 @@ class Page_PermissionManager extends Page
Render::addTemplate('locationstable', $data);
}
} elseif ($show === "roleEditor") {
- $data = array();
+ $data = array("cancelShow" => Request::get("cancel", "roles"));
$selectedPermissions = array();
$selectedLocations = array();
- $roleID = Request::get("roleid", false);
- if ($roleID) {
- $roleData = GetPermissionData::getRoleData($roleID);
- $data["roleid"] = $roleID;
+ $roleid = Request::get("roleid", false);
+ if ($roleid) {
+ $roleData = GetPermissionData::getRoleData($roleid);
+ $data["roleid"] = $roleid;
$data["rolename"] = $roleData["rolename"];
$selectedPermissions = $roleData["permissions"];
$selectedLocations = $roleData["locations"];
@@ -85,12 +85,21 @@ class Page_PermissionManager extends Page
}
}
- private static function generatePermissionHTML($subPermissions, $selectedPermissions = array(), $selectAll = false, $permString = "")
+ /**
+ * Recursively generate HTML code for the permission selection tree.
+ *
+ * @param array $permissions the permission tree
+ * @param array $selectedPermissions permissions that should be preselected
+ * @param array $selectAll true if all pemrissions should be preselected, false if only those in $selectedPermissions
+ * @param array $permString the prefix permission string with which all permissions in the permission tree should start
+ * @return string generated html code
+ */
+ private static function generatePermissionHTML($permissions, $selectedPermissions = array(), $selectAll = false, $permString = "")
{
$res = "";
$toplevel = $permString == "";
if ($toplevel && in_array("*", $selectedPermissions)) $selectAll = true;
- foreach ($subPermissions as $k => $v) {
+ foreach ($permissions as $k => $v) {
$leaf = !is_array($v);
$nextPermString = $permString ? $permString.".".$k : $k;
$id = $leaf ? $nextPermString : $nextPermString.".*";
@@ -107,7 +116,7 @@ class Page_PermissionManager extends Page
if ($toplevel) {
$res = Render::parse("treepanel",
array("id" => "*",
- "name" => Dictionary::translateFile("template-tags", "lang_Permissions"),
+ "name" => Dictionary::translateFile("template-tags", "lang_permissions"),
"checkboxname" => "permissions",
"selected" => $selectAll,
"HTML" => $res));
@@ -115,6 +124,15 @@ class Page_PermissionManager extends Page
return $res;
}
+ /**
+ * Recursively generate HTML code for the location selection tree.
+ *
+ * @param array $locations the location tree
+ * @param array $selectedLocations locations that should be preselected
+ * @param array $selectAll true if all locations should be preselected, false if only those in $selectedLocations
+ * @param array $toplevel true if the location tree are the children of the root location, false if not
+ * @return string generated html code
+ */
private static function generateLocationHTML($locations, $selectedLocations = array(), $selectAll = false, $toplevel = true)
{
$res = "";
@@ -133,7 +151,7 @@ class Page_PermissionManager extends Page
if ($toplevel) {
$res = Render::parse("treepanel",
array("id" => 0,
- "name" => Dictionary::translateFile("template-tags", "lang_Locations"),
+ "name" => Dictionary::translateFile("template-tags", "lang_locations"),
"checkboxname" => "locations",
"selected" => $selectAll,
"HTML" => $res));
@@ -141,6 +159,12 @@ class Page_PermissionManager extends Page
return $res;
}
+ /**
+ * Remove locations that are already covered by parent locations from the array.
+ *
+ * @param array $locations the locationid array
+ * @return array the locationid array without redundant locationids
+ */
private static function processLocations($locations)
{
if (in_array(0, $locations)) return array(NULL);
@@ -158,6 +182,12 @@ class Page_PermissionManager extends Page
return $result;
}
+ /**
+ * Remove permissions that are already covered by parent permissions from the array.
+ *
+ * @param array $permissions the permissionid array
+ * @return array the permissionid array without redundant permissionids
+ */
private static function processPermissions($permissions)
{
if (in_array("*", $permissions)) return array("*");
@@ -171,6 +201,12 @@ class Page_PermissionManager extends Page
return self::extractPermissions($result);
}
+ /**
+ * Convert a multidimensional array of permissions to a flat array of permissions.
+ *
+ * @param array $permissions multidimensional array of permissionids
+ * @return array flat array of permissionids
+ */
private static function extractPermissions($permissions)
{
$result = array();
diff --git a/modules-available/permissionmanager/templates/_page.html b/modules-available/permissionmanager/templates/_page.html
index 405462f7..4140ce78 100644
--- a/modules-available/permissionmanager/templates/_page.html
+++ b/modules-available/permissionmanager/templates/_page.html
@@ -8,17 +8,17 @@
<div class="btn-group">
<button class="btn btn-default {{rolesButtonClass}}" type="submit" name="show" value="roles">
<span class="glyphicon glyphicon-education"></span>
- {{lang_Roles}}
+ {{lang_roles}}
</button>
<button class="btn btn-default {{usersButtonClass}}" type="submit" name="show" value="users">
<span class="glyphicon glyphicon-user"></span>
- {{lang_Users}}
+ {{lang_users}}
</button>
<button class="btn btn-default {{locationsButtonClass}}" type="submit" name="show" value="locations">
<span class="glyphicon glyphicon-home"></span>
- {{lang_Locations}}
+ {{lang_locations}}
</button>
</div>
</form>
diff --git a/modules-available/permissionmanager/templates/locationstable.html b/modules-available/permissionmanager/templates/locationstable.html
index dcfefa94..153258fe 100644
--- a/modules-available/permissionmanager/templates/locationstable.html
+++ b/modules-available/permissionmanager/templates/locationstable.html
@@ -15,8 +15,8 @@
<table id="locationsTable" class="table table-condensed table-hover stupidtable dataTable">
<thead>
<tr>
- <th data-sort="string">{{lang_Locations}}</th>
- <th>{{lang_Roles}}</th>
+ <th data-sort="string">{{lang_locations}}</th>
+ <th>{{lang_roles}}</th>
</tr>
</thead>
@@ -26,7 +26,7 @@
<td>{{locationpad}} {{locationname}}</td>
<td>
{{#roles}}
- <span class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</span>
+ <a href="?do=permissionmanager&show=roleEditor&cancel=locations&roleid={{roleid}}" class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</a>
{{/roles}}
</td>
</tr>
diff --git a/modules-available/permissionmanager/templates/roleeditor.html b/modules-available/permissionmanager/templates/roleeditor.html
index b07e2112..871fd0cc 100644
--- a/modules-available/permissionmanager/templates/roleeditor.html
+++ b/modules-available/permissionmanager/templates/roleeditor.html
@@ -7,15 +7,15 @@
<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 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">
+ <label for="rolename">{{lang_name}}:</label>
+ <input id="rolename" name="rolename" value="{{rolename}}" type="text" 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>
+ <span><a href="?do=permissionmanager&show={{cancelShow}}" id="cancelButton" class="btn btn-default">{{lang_cancel}}</a></span>
+ <button type="submit" id="saveButton" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</li>
</ul>
</div>
@@ -56,15 +56,12 @@
}
}
});
-
- $("#cancelButton").click(function () {
- window.location.replace("?do=permissionmanager&show=roles");
- });
$('form').submit(function () {
- var name = $.trim($('#rolename').val());
- if (name === '') {
- $("#rolename").addClass("missingInput");
+ var input = $("#rolename");
+ var name = $.trim(input.val());
+ if (!name) {
+ input.addClass("missingInput");
return false;
}
});
diff --git a/modules-available/permissionmanager/templates/rolestable.html b/modules-available/permissionmanager/templates/rolestable.html
index 992feca1..b121a9e0 100644
--- a/modules-available/permissionmanager/templates/rolestable.html
+++ b/modules-available/permissionmanager/templates/rolestable.html
@@ -8,27 +8,27 @@
<input type="text" class="form-control" id="roleNameSearchField" onkeyup="searchFieldFunction()" placeholder="{{lang_searchPlaceholder}}">
</div>
<div class="col-md-4 text-right">
- <button class="btn btn-success" type="button" onclick="openRoleEditor()"><span class="glyphicon glyphicon-plus"></span> {{lang_newRole}}</button>
+ <a href="?do=permissionmanager&show=roleEditor" class="btn btn-success"><span class="glyphicon glyphicon-plus"></span> {{lang_newRole}}</a>
</div>
</div>
<div class="row">
<div class="col-md-12">
- <table id="rolesTable" class="table table-condensed table-hover stupidtable">
+ <table 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>
+ <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>
+ <tr>
+ <td class="rolename">{{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>
+ <a class="btn btn-xs btn-primary" 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>
@@ -47,7 +47,7 @@
<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>
+ <h4 class="modal-title" id="myModalLabel">{{lang_delete}}</h4>
</div>
<div class="modal-body">
{{lang_deleteCheck}}
@@ -55,7 +55,7 @@
<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>
+ <button type="submit" name="action" value="deleteRole" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> {{lang_delete}}</button>
</div>
</div>
</div>
@@ -64,30 +64,18 @@
</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 = "";
+ var filter = $("#roleNameSearchField").val().toLowerCase();
+ $(".rolename").each(function() {
+ if ($(this).text().toLowerCase().indexOf(filter) >= 0) {
+ $(this).closest("tr").show();
} else {
- trs[i].style.display = "none";
+ $(this).closest("tr").hide();
}
- }
+ });
}
</script> \ No newline at end of file
diff --git a/modules-available/permissionmanager/templates/treenode.html b/modules-available/permissionmanager/templates/treenode.html
index 336ca13e..ced973ca 100644
--- a/modules-available/permissionmanager/templates/treenode.html
+++ b/modules-available/permissionmanager/templates/treenode.html
@@ -1,8 +1,8 @@
{{#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>
+ <input id="{{id}}" name="{{checkboxname}}[]" value="{{id}}" type="checkbox" class="form-control" {{#selected}}checked{{/selected}}>
+ <label for="{{id}}">{{#toplevel}}<b>{{/toplevel}}{{name}}{{#toplevel}}</b>{{/toplevel}}</label>
</div>
<ul>
{{{HTML}}}
diff --git a/modules-available/permissionmanager/templates/treepanel.html b/modules-available/permissionmanager/templates/treepanel.html
index 53e316c9..6f358825 100644
--- a/modules-available/permissionmanager/templates/treepanel.html
+++ b/modules-available/permissionmanager/templates/treepanel.html
@@ -1,11 +1,11 @@
-<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 class="panel panel-primary tree-panel">
+ <div class="panel-heading">
+ <div class="checkbox">
+ <input id="{{id}}" name="{{checkboxname}}[]" value="{{id}}" type="checkbox" class="form-control" {{#selected}}checked{{/selected}}>
+ <label for="{{id}}">{{name}}</label>
</div>
</div>
- <div class='panel-body'>
+ <div class="panel-body">
<div class="tree-container" style="padding-left: 20px; padding-right: 20px;">
{{{HTML}}}
</div>
diff --git a/modules-available/permissionmanager/templates/userstable.html b/modules-available/permissionmanager/templates/userstable.html
index 9f684e99..bb0e228e 100644
--- a/modules-available/permissionmanager/templates/userstable.html
+++ b/modules-available/permissionmanager/templates/userstable.html
@@ -13,8 +13,8 @@
</select>
</div>
<div class="col-md-4 text-right">
- <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>
+ <button class="roleButtons btn btn-success" type="button" data-toggle="modal" data-target="#addRoleToUserModal" disabled><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button>
+ <button class="roleButtons btn btn-danger" type="button" data-toggle="modal" data-target="#removeRoleFromUserModal" disabled><span class="glyphicon glyphicon-remove-circle"></span> {{lang_removeRole}}</button>
</div>
</div>
@@ -23,9 +23,9 @@
<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>
+ <th data-sort="string">{{lang_users}}</th>
+ <th>{{lang_roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_selected}}</th>
</tr>
</thead>
@@ -35,7 +35,7 @@
<td>{{username}}</td>
<td>
{{#roles}}
- <span class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</span>
+ <a href="?do=permissionmanager&show=roleEditor&cancel=users&roleid={{roleid}}" class="label label-default customSpanMargin roleid-{{roleid}}">{{rolename}}</a>
{{/roles}}
</td>
<td data-sort-value="0">
@@ -65,8 +65,8 @@
<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>
+ <th data-sort="string">{{lang_roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_selected}}</th>
</tr>
</thead>
@@ -89,7 +89,7 @@
</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>
+ <button id="confirmAddButton" type="submit" name="action" value="addRoleToUser" class="btn btn-success" onclick="clearRemoveRoleModal()" disabled><span class="glyphicon glyphicon-share-alt"></span> {{lang_addRole}}</button>
</div>
</div>
</div>
@@ -100,7 +100,7 @@
<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>
+ <h4 class="modal-title" id="myModalLabel2">{{lang_removeRole}}</h4>
</div>
<div class="modal-body">
<div class="row">
@@ -108,8 +108,8 @@
<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>
+ <th data-sort="string">{{lang_roles}}</th>
+ <th data-sort="int" data-sort-default="desc">{{lang_selected}}</th>
</tr>
</thead>
@@ -132,7 +132,7 @@
</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>
+ <button id="confirmRemoveButton" type="submit" name="action" value="removeRoleFromUser" class="btn btn-danger" onclick="clearAddRoleModal()" disabled><span class="glyphicon glyphicon-remove-circle"></span> {{lang_removeRole}}</button>
</div>
</div>
</div>
@@ -140,34 +140,85 @@
</form>
<script>
+ selectedUsersCounter = 0;
+ selectedAddRolesCounter = 0;
+ selectedRemoveRolesCounter = 0;
+
document.addEventListener("DOMContentLoaded", function() {
- // if checked,: mark green, else: unmark
- $('input:checkbox').change(function() {
+ var checkboxes = $("input[type=checkbox]");
+ checkboxes.prop("checked", false);
+ $(".roleButtons, #confirmAddButton, #confirmRemoveButton").prop("disabled", true);
+
+ checkboxes.change(function() {
if ($(this).is(':checked')) {
+ var color = "#eeeeff";
+ if ($(this).closest("table").is("#addRoleToUserTable")) {
+ color = "#eeffee";
+ } else if ($(this).closest("table").is("#removeRoleFromUserTable")) {
+ color = "#ffeeee";
+ }
$(this).closest("td").data("sort-value", 1);
- $(this).closest("tr").css("background-color", "#f2ffe6");
+ $(this).closest("tr").css("background-color", color);
} else {
$(this).closest("td").data("sort-value", 0);
$(this).closest("tr").css("background-color", "");
}
+ });
+ $("#usersTable").find("input[type=checkbox]").change(function() {
+ if ($(this).is(':checked')) {
+ selectedUsersCounter++;
+ if (selectedUsersCounter === 1) {
+ $(".roleButtons").prop("disabled", false);
+ }
+ } else {
+ selectedUsersCounter--;
+ if (selectedUsersCounter === 0) {
+ $(".roleButtons").prop("disabled", true);
+ }
+ }
});
- });
+ $("#addRoleToUserTable").find("input[type=checkbox]").change(function() {
+ if ($(this).is(':checked')) {
+ selectedAddRolesCounter++;
+ if (selectedAddRolesCounter === 1) {
+ $("#confirmAddButton").prop("disabled", false);
+ }
+ } else {
+ selectedAddRolesCounter--;
+ if (selectedAddRolesCounter === 0) {
+ $("#confirmAddButton").prop("disabled", true);
+ }
+ }
+ });
+
+ $("#removeRoleFromUserTable").find("input[type=checkbox]").change(function() {
+ if ($(this).is(':checked')) {
+ selectedRemoveRolesCounter++;
+ if (selectedRemoveRolesCounter === 1) {
+ $("#confirmRemoveButton").prop("disabled", false);
+ }
+ } else {
+ selectedRemoveRolesCounter--;
+ if (selectedRemoveRolesCounter === 0) {
+ $("#confirmRemoveButton").prop("disabled", true);
+ }
+ }
+ });
+ });
// 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();
+ .prop("checked", false);
}
// 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();
+ .prop("checked", false);
}
</script> \ No newline at end of file
diff --git a/modules-available/roomplanner/hooks/statistics/machine-replace.inc.php b/modules-available/roomplanner/hooks/statistics/machine-replace.inc.php
new file mode 100644
index 00000000..651c4c25
--- /dev/null
+++ b/modules-available/roomplanner/hooks/statistics/machine-replace.inc.php
@@ -0,0 +1,7 @@
+<?php
+
+
+foreach ($list as $entry) {
+ unset($entry['datelimit']);
+ Database::exec('UPDATE IGNORE location_roomplan SET tutoruuid = :new WHERE tutoruuid = :old', $entry);
+} \ No newline at end of file
diff --git a/modules-available/runmode/hooks/statistics/machine-replace.inc.php b/modules-available/runmode/hooks/statistics/machine-replace.inc.php
new file mode 100644
index 00000000..cd23e552
--- /dev/null
+++ b/modules-available/runmode/hooks/statistics/machine-replace.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+foreach ($list as $entry) {
+ unset($entry['datelimit']);
+ Database::exec('UPDATE IGNORE runmode SET machineuuid = :new WHERE machineuuid = :old', $entry);
+}
diff --git a/modules-available/runmode/inc/runmode.inc.php b/modules-available/runmode/inc/runmode.inc.php
index ad1f52bf..2c8083ca 100644
--- a/modules-available/runmode/inc/runmode.inc.php
+++ b/modules-available/runmode/inc/runmode.inc.php
@@ -72,7 +72,7 @@ class RunMode
* @param string $machineuuid
* @param int $returnData bitfield of data to return
* @return false|array {'machineuuid', 'isclient', 'module', 'modeid', 'modedata',
- * <'hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'>}
+ * ('hostname', 'clientip', 'macaddr', 'locationid', 'lastseen'), ('moduleName', 'modeName')}
*/
public static function getRunMode($machineuuid, $returnData = self::DATA_MACHINE_DATA)
{
diff --git a/modules-available/runmode/lang/de/messages.json b/modules-available/runmode/lang/de/messages.json
index 21b4b6ae..911d48d4 100644
--- a/modules-available/runmode/lang/de/messages.json
+++ b/modules-available/runmode/lang/de/messages.json
@@ -5,5 +5,6 @@
"machine-not-found": "Rechner {{0}} nicht gefunden",
"machine-not-runmode": "Rechner {{0}} hatte keinen speziellen Betriebsmodus aktiviert",
"machine-removed": "Rechner {{0}} entfernt",
+ "machine-still-assigned": "Rechner {{0}} ist noch im Betriebsmodus {{1}}; ignoriert.",
"module-hasnt-runmode": "Modul {{0}} bietet keine speziellen Betriebsmodi"
} \ No newline at end of file
diff --git a/modules-available/runmode/lang/en/messages.json b/modules-available/runmode/lang/en/messages.json
index 1985ca66..6d890428 100644
--- a/modules-available/runmode/lang/en/messages.json
+++ b/modules-available/runmode/lang/en/messages.json
@@ -5,5 +5,6 @@
"machine-not-found": "Client {{0}} not found",
"machine-not-runmode": "No special mode of operation configured for client {{0}}",
"machine-removed": "Removed client {{0}}",
+ "machine-still-assigned": "Client {{0}} still set to mode {{1}}, not assigning new mode.",
"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/page.inc.php b/modules-available/runmode/page.inc.php
index e26950d0..b94b8a31 100644
--- a/modules-available/runmode/page.inc.php
+++ b/modules-available/runmode/page.inc.php
@@ -42,6 +42,14 @@ class Page_RunMode extends Page
}
$active = 0;
foreach ($machines as $machine) {
+ $oldMode = RunMode::getRunMode($machine, 0);
+ if ($oldMode !== false) {
+ $oldModule = RunMode::getModuleConfig($oldMode['module']);
+ if ($oldModule !== false && (!$oldModule->allowGenericEditor || $oldModule->deleteUrlSnippet !== false)) {
+ Message::addError('runmode.machine-still-assigned', $machine, $oldMode['module']);
+ continue;
+ }
+ }
$ret = RunMode::setRunMode($machine, $module, $modeId, null, null);
if ($ret) {
$active++;
@@ -105,10 +113,6 @@ 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) {
@@ -170,9 +174,17 @@ class Page_RunMode extends Page
{
$moduleId = $module->getIdentifier();
$modeName = RunMode::getModeName($moduleId, $modeId);
+ $redirect = Request::get('redirect', '', 'string');
+ if (empty($redirect)) {
+ $redirect = '?do=runmode';
+ }
if ($modeName === false) {
Message::addError('invalid-modeid', $moduleId, $modeId);
- Util::redirect('?do=runmode');
+ Util::redirect($redirect);
+ }
+ if (!RunMode::getModuleConfig($moduleId)->allowGenericEditor) {
+ Message::addError('runmode.cannot-edit-module', $module);
+ Util::redirect($redirect);
}
Render::addTemplate('machine-selector', [
'module' => $moduleId,
@@ -180,7 +192,7 @@ class Page_RunMode extends Page
'moduleName' => $module->getDisplayName(),
'modeName' => $modeName,
'machines' => json_encode(RunMode::getForMode($module, $modeId, true)),
- 'redirect' => Request::get('redirect', '', 'string'),
+ 'redirect' => $redirect,
]);
}
diff --git a/modules-available/runmode/templates/module-machine-list.html b/modules-available/runmode/templates/module-machine-list.html
index 61bbbad9..283fb393 100644
--- a/modules-available/runmode/templates/module-machine-list.html
+++ b/modules-available/runmode/templates/module-machine-list.html
@@ -41,8 +41,8 @@
</button>
{{/canedit}}
{{#deleteUrl}}
- <a class="btn btn-danger btn-sm" href="?do={{module}}&amp;{{deleteUrl}}{{modeid}}">
- <span class="glyphicon glyphicon-trash"></span>
+ <a class="btn btn-default btn-sm" href="?do={{module}}&amp;{{deleteUrl}}{{modeid}}">
+ <span class="glyphicon glyphicon-edit"></span>
</a>
{{/deleteUrl}}
</td>
diff --git a/modules-available/serversetup-bwlp/lang/de/permissions.json b/modules-available/serversetup-bwlp/lang/de/permissions.json
new file mode 100644
index 00000000..673bf153
--- /dev/null
+++ b/modules-available/serversetup-bwlp/lang/de/permissions.json
@@ -0,0 +1,5 @@
+{
+ "edit.address": "Boot-Adresse des Servers auswählen.",
+ "edit.menu": "Bootmenü anpassen.",
+ "download": "USB-Image herunteladen."
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/lang/en/permissions.json b/modules-available/serversetup-bwlp/lang/en/permissions.json
new file mode 100644
index 00000000..c04f09f7
--- /dev/null
+++ b/modules-available/serversetup-bwlp/lang/en/permissions.json
@@ -0,0 +1,5 @@
+{
+ "edit.address": "Choose boot address of the server.",
+ "edit.menu": "Customize boot menu.",
+ "download": "Download USB Image."
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/page.inc.php b/modules-available/serversetup-bwlp/page.inc.php
index a8d29d6e..16d3f8e2 100644
--- a/modules-available/serversetup-bwlp/page.inc.php
+++ b/modules-available/serversetup-bwlp/page.inc.php
@@ -12,12 +12,12 @@ class Page_ServerSetup extends Page
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
- if (Request::any('action') === 'getimage') {
+ if (Request::any('action') === 'getimage' && User::hasPermission("download")) {
$this->handleGetImage();
}
@@ -30,13 +30,13 @@ class Page_ServerSetup extends Page
$this->getLocalAddresses();
}
- if ($action === 'ip') {
+ if ($action === 'ip' && User::hasPermission("edit.address")) {
// New address is to be set
$this->getLocalAddresses();
$this->updateLocalAddress();
}
- if ($action === 'ipxe') {
+ if ($action === 'ipxe' && User::hasPermission("edit.menu")) {
// iPXE stuff changes
$this->updatePxeMenu();
}
@@ -52,7 +52,8 @@ class Page_ServerSetup extends Page
Render::addTemplate('ipaddress', array(
'ips' => $this->taskStatus['data']['addresses'],
- 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger'
+ 'chooseHintClass' => $this->hasIpSet ? '' : 'alert alert-danger',
+ 'editAllowed' => User::hasPermission("edit.address"),
));
$data = $this->currentMenu;
if (!isset($data['defaultentry'])) {
@@ -67,6 +68,8 @@ class Page_ServerSetup extends Page
if ($data['defaultentry'] === 'custom') {
$data['active-custom'] = 'checked';
}
+ $data['editAllowed'] = User::hasPermission("edit.menu");
+ $data['downloadAllowed'] = User::hasPermission("download");
Render::addTemplate('ipxe', $data);
}
diff --git a/modules-available/serversetup-bwlp/permissions/permissions.json b/modules-available/serversetup-bwlp/permissions/permissions.json
new file mode 100644
index 00000000..2166cf8e
--- /dev/null
+++ b/modules-available/serversetup-bwlp/permissions/permissions.json
@@ -0,0 +1,5 @@
+[
+ "edit.address",
+ "edit.menu",
+ "download"
+] \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/style.css b/modules-available/serversetup-bwlp/style.css
new file mode 100644
index 00000000..3aea98af
--- /dev/null
+++ b/modules-available/serversetup-bwlp/style.css
@@ -0,0 +1,12 @@
+.disabledPanel {
+ cursor: not-allowed;
+}
+
+.disabledPanel > .panel-body {
+ pointer-events: none;
+ opacity: 0.8;
+}
+
+.panel-footer .btn-group {
+ cursor: not-allowed;
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp/templates/ipaddress.html b/modules-available/serversetup-bwlp/templates/ipaddress.html
index 0b3b2ed7..e82253f5 100644
--- a/modules-available/serversetup-bwlp/templates/ipaddress.html
+++ b/modules-available/serversetup-bwlp/templates/ipaddress.html
@@ -1,4 +1,4 @@
-<div class="panel panel-default">
+<div class="panel panel-default {{^editAllowed}}disabledPanel{{/editAllowed}}">
<div class="panel-heading">
{{lang_bootAddress}}
</div>
diff --git a/modules-available/serversetup-bwlp/templates/ipxe.html b/modules-available/serversetup-bwlp/templates/ipxe.html
index bb776dca..e0dc53e5 100644
--- a/modules-available/serversetup-bwlp/templates/ipxe.html
+++ b/modules-available/serversetup-bwlp/templates/ipxe.html
@@ -3,7 +3,7 @@
<input type="password" name="password_fake" id="password_fake" value="" style="position:absolute;top:-2000px" tabindex="-1">
<input type="hidden" name="action" value="ipxe">
<input type="hidden" name="token" value="{{token}}">
- <div class="panel panel-default">
+ <div class="panel panel-default {{^editAllowed}}disabledPanel{{/editAllowed}}">
<div class="panel-heading">
{{lang_bootMenu}}
</div>
@@ -52,10 +52,10 @@
</div>
<div class="panel-footer">
- <button class="btn btn-primary pull-right" name="action" value="ipxe">{{lang_bootMenuCreate}}</button>
+ <button class="btn btn-primary pull-right" name="action" value="ipxe" {{^editAllowed}}disabled{{/editAllowed}}>{{lang_bootMenuCreate}}</button>
<div>
<div class="btn-group" role="group">
- <a class="btn btn-default" href="?do=ServerSetup&amp;action=getimage">
+ <a class="btn btn-default {{^downloadAllowed}}disabled{{/downloadAllowed}}" href="?do=ServerSetup&amp;action=getimage">
<span class="glyphicon glyphicon-download-alt"></span>
{{lang_downloadImage}}
</a>
diff --git a/modules-available/session/lang/de/template-tags.json b/modules-available/session/lang/de/template-tags.json
index 20f44be0..c7b6d881 100644
--- a/modules-available/session/lang/de/template-tags.json
+++ b/modules-available/session/lang/de/template-tags.json
@@ -4,9 +4,7 @@
"lang_enter": "Anmeldung",
"lang_login": "Anmelden",
"lang_newPassword": "Neues Passwort",
- "lang_password": "Passwort",
"lang_register": "Registrieren",
"lang_rememberID": "Angemeldet bleiben",
- "lang_repeatPassword": "Passwort wiederholen",
- "lang_username": "Benutzerkennung"
-} \ No newline at end of file
+ "lang_repeatPassword": "Passwort wiederholen"
+}
diff --git a/modules-available/session/lang/en/template-tags.json b/modules-available/session/lang/en/template-tags.json
index 663354a9..f9e0b393 100644
--- a/modules-available/session/lang/en/template-tags.json
+++ b/modules-available/session/lang/en/template-tags.json
@@ -4,9 +4,7 @@
"lang_enter": "Enter",
"lang_login": "Login",
"lang_newPassword": "New password",
- "lang_password": "Password",
"lang_register": "Register",
"lang_rememberID": "Remember ID",
- "lang_repeatPassword": "Repeat password",
- "lang_username": "Username"
-} \ No newline at end of file
+ "lang_repeatPassword": "Repeat password"
+}
diff --git a/modules-available/session/lang/pt/template-tags.json b/modules-available/session/lang/pt/template-tags.json
index 3d1e19eb..3a4b9478 100644
--- a/modules-available/session/lang/pt/template-tags.json
+++ b/modules-available/session/lang/pt/template-tags.json
@@ -1,8 +1,6 @@
{
"lang_enter": "Entrar",
"lang_login": "Entrar",
- "lang_password": "Senha",
"lang_register": "Registrar",
- "lang_rememberID": "Lembrar Usu\u00e1rio",
- "lang_username": "Nome de Usu\u00e1rio"
-} \ No newline at end of file
+ "lang_rememberID": "Lembrar Usu\u00e1rio"
+}
diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php
index be6df752..f6765059 100644
--- a/modules-available/statistics/inc/filter.inc.php
+++ b/modules-available/statistics/inc/filter.inc.php
@@ -88,15 +88,17 @@ class Filter
$lhs = trim(substr($q, 0, $pos));
$rhs = trim(substr($q, $pos + strlen($operator)));
- if ($lhs == 'gbram') {
+ if ($lhs === 'gbram') {
$filters[] = new RamGbFilter($operator, $rhs);
- } elseif ($lhs == 'state') {
+ } elseif ($lhs === 'runtime') {
+ $filters[] = new RuntimeFilter($operator, $rhs);
+ } elseif ($lhs === 'state') {
$filters[] = new StateFilter($operator, $rhs);
- } elseif ($lhs == 'hddgb') {
+ } elseif ($lhs === 'hddgb') {
$filters[] = new Id44Filter($operator, $rhs);
- } elseif ($lhs == 'location') {
+ } elseif ($lhs === 'location') {
$filters[] = new LocationFilter($operator, $rhs);
- } elseif ($lhs == 'subnet') {
+ } elseif ($lhs === 'subnet') {
$filters[] = new SubnetFilter($operator, $rhs);
} else {
if (array_key_exists($lhs, Page_Statistics::$columns) && Page_Statistics::$columns[$lhs]['column']) {
@@ -143,6 +145,38 @@ class RamGbFilter extends Filter
}
}
+class RuntimeFilter extends Filter
+{
+ public function __construct($operator, $argument)
+ {
+ parent::__construct('lastboot', $operator, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ global $SIZE_RAM;
+ $upper = time() - (int)$this->argument * 3600;
+ $lower = $upper - 3600;
+ $common = "state IN ('OCCUPIED', 'IDLE', 'STANDBY') AND";
+ if ($this->operator == '=') {
+ return "$common ({$this->column} BETWEEN $lower AND $upper)";
+ } elseif ($this->operator == '<') {
+ return "$common {$this->column} > $upper";
+ } elseif ($this->operator == '<=') {
+ return "$common {$this->column} > $lower";
+ } elseif ($this->operator == '>') {
+ return "$common {$this->column} < $lower";
+ } elseif ($this->operator == '>=') {
+ return "$common {$this->column} < $upper";
+ } elseif ($this->operator == '!=') {
+ return "$common ({$this->column} < $lower OR {$this->column} > $upper)";
+ } else {
+ error_log("unimplemented operator in RuntimeFilter: $this->operator");
+ return ' 1';
+ }
+ }
+}
+
class Id44Filter extends Filter
{
public function __construct($operator, $argument)
diff --git a/modules-available/statistics/lang/de/messages.json b/modules-available/statistics/lang/de/messages.json
index c9667f7b..a9256d5a 100644
--- a/modules-available/statistics/lang/de/messages.json
+++ b/modules-available/statistics/lang/de/messages.json
@@ -1,7 +1,11 @@
{
"deleted-n-machines": "{{0}} Clients gel\u00f6scht",
+ "ignored-both-in-use": "Rechnerpaar ignoriert, da beide noch in Betrieb zu sein scheinen. ({{0}} und {{1}})",
"invalid-filter-argument": "Das Argument {{1}} ist nicht g\u00fcltig f\u00fcr den Filter {{0}}",
"invalid-filter-key": "{{0}} ist kein g\u00fcltiges Filterkriterium",
+ "invalid-replace-format": "Ung\u00fcltiges Parameterformat ({{0}})",
+ "no-replacement-matches": "Keine Rechner gefunden, die den oben genannten Kriterien entsprechen",
"notes-saved": "Anmerkungen gespeichert",
- "unknown-machine": "Unbekannte Rechner-ID {{0}}"
+ "unknown-machine": "Unbekannte Rechner-ID {{0}}",
+ "x-machines-replaced": "{{0}} Rechner ersetzt"
} \ No newline at end of file
diff --git a/modules-available/statistics/lang/de/permissions.json b/modules-available/statistics/lang/de/permissions.json
new file mode 100644
index 00000000..15303993
--- /dev/null
+++ b/modules-available/statistics/lang/de/permissions.json
@@ -0,0 +1,5 @@
+{
+ "view": "Client Statistiken anschauen.",
+ "note": "Client Notizen speichern.",
+ "delete": "Client löschen."
+} \ 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 3cdde813..84c4690c 100644
--- a/modules-available/statistics/lang/de/template-tags.json
+++ b/modules-available/statistics/lang/de/template-tags.json
@@ -66,8 +66,14 @@
"lang_ramSlots": "Speicher-Slots",
"lang_realCores": "Kerne",
"lang_reallocatedSectors": "Defekte Sektoren",
+ "lang_replace": "Ersetzen",
+ "lang_replaceInstructions": "Hier k\u00f6nnen Sie Metadaten automatisch \u00fcbertragen, wenn in einem Raum die Rechner ausgetauscht wurden. Dies setzt voraus, dass alle neuen Rechner die gleiche IP Adresse erhalten haben wie der Rechner, der zuvor am entsprechenden Platz stand, und die neuen Rechner alle einmal gestartet wurden. In der Liste unten sehen Sie alle Rechnerpaare, auf die folgendes zutrifft: 1) Die IP-Adressen sind identisch 2) Der letzte Boot des einen Rechners liegt vor dem ersten Boot des anderen Rechners. W\u00e4hlen Sie alle Rechnerpaare aus, f\u00fcr die eine Ersetzung stattfinden soll. Bei der Ersetzung werden alle Logeintr\u00e4ge, Sitzungslogs, Position im Raumplan und evtl. spezielle Betriebsmodi vom alten Rechner auf den neuen \u00dcbertragen.",
+ "lang_replaceMachinesHeading": "Rechner ersetzen",
+ "lang_replaceNew": "Alter Rechner",
+ "lang_replaceOld": "Neuer Rechner",
"lang_runMode": "Betriebsmodus",
"lang_runmodeMachines": "Mit besonderem Betriebsmodus",
+ "lang_runtimeHours": "Laufzeit (Stunden)",
"lang_screens": "Bildschirme",
"lang_serialNo": "Serien-Nr",
"lang_showList": "Liste",
@@ -75,6 +81,7 @@
"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_sureReplaceNoUndo": "Wollen Sie die Daten ausgew\u00e4hlten Rechner \u00fcbertragen? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.",
"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.",
diff --git a/modules-available/statistics/lang/en/messages.json b/modules-available/statistics/lang/en/messages.json
index 3471c472..0f290f2e 100644
--- a/modules-available/statistics/lang/en/messages.json
+++ b/modules-available/statistics/lang/en/messages.json
@@ -1,7 +1,11 @@
{
"deleted-n-machines": "Deleted {{0}} clients",
+ "ignored-both-in-use": "Ignoring machine pair as both still seem to be in use. ({{0}} and {{1}})",
"invalid-filter-argument": "{{1}} is not a vald argument for filter {{0}}",
"invalid-filter-key": "{{0}} is not a valid filter",
+ "invalid-replace-format": "Invalid parameter format ({{0}})",
+ "no-replacement-matches": "No machines match the criteria from above",
"notes-saved": "Notes have been saved",
- "unknown-machine": "Unknown machine uuid {{0}}"
+ "unknown-machine": "Unknown machine uuid {{0}}",
+ "x-machines-replaced": "{{0}} machine(s) replaced"
} \ No newline at end of file
diff --git a/modules-available/statistics/lang/en/permissions.json b/modules-available/statistics/lang/en/permissions.json
new file mode 100644
index 00000000..7be32f22
--- /dev/null
+++ b/modules-available/statistics/lang/en/permissions.json
@@ -0,0 +1,5 @@
+{
+ "view": "View client statistics.",
+ "note": "Save client notes.",
+ "delete": "Delete client."
+} \ 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 35c4e68a..b064ee50 100644
--- a/modules-available/statistics/lang/en/template-tags.json
+++ b/modules-available/statistics/lang/en/template-tags.json
@@ -66,8 +66,14 @@
"lang_ramSlots": "Memory slots",
"lang_realCores": "Cores",
"lang_reallocatedSectors": "Bad sectors",
+ "lang_replace": "Replace",
+ "lang_replaceInstructions": "If some PCs\/clients have been physically replaced, you can re-assign log entries, session data, position information etc. from the old machine to the new one. This requires that the new machine gets assigned the same IP address as the old one and, if the room planner is used -- that it is placed in the same spot as the old one. The list below shows all machine pairs where 1) the last boot of one machine lies before the first boot of the other one 2) both machines had the same IP address last time they booted. The replacement action will reassign all log events, room plan location and special run mode from the old machine to the new machine.",
+ "lang_replaceMachinesHeading": "Replace machines",
+ "lang_replaceNew": "Old machine",
+ "lang_replaceOld": "New machine",
"lang_runMode": "Mode of operation",
"lang_runmodeMachines": "With special mode of operation",
+ "lang_runtimeHours": "Runtime (hours)",
"lang_screens": "Screens",
"lang_serialNo": "Serial no",
"lang_showList": "List",
@@ -75,6 +81,7 @@
"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_sureReplaceNoUndo": "Are you sure you want to replace the selected machine pairs? This action cannot be undone.",
"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.",
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index ea5b6f03..5fe4ebfa 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -21,6 +21,8 @@ class Page_Statistics extends Page
private $query;
+ private $locationsAllowedToView;
+
/**
* @var bool whether we have a SubPage from the pages/ subdir
*/
@@ -102,6 +104,11 @@ class Page_Statistics extends Page
'type' => 'string',
'column' => true
],
+ 'hostname' => [
+ 'op' => Page_Statistics::$op_stringcmp,
+ 'type' => 'string',
+ 'column' => true
+ ],
'subnet' => [
'op' => Page_Statistics::$op_nominal,
'type' => 'string',
@@ -117,7 +124,12 @@ class Page_Statistics extends Page
'type' => 'enum',
'column' => true,
'values' => ['occupied', 'on', 'off', 'idle', 'standby']
- ]
+ ],
+ 'runtime' => [
+ 'op' => Page_Statistics::$op_ordinal,
+ 'type' => 'int',
+ 'column' => true
+ ],
];
if (Module::isAvailable('locations')) {
Page_Statistics::$columns['location'] = [
@@ -134,11 +146,14 @@ class Page_Statistics extends Page
{
$this->initConstants();
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
+ $this->locationsAllowedToView = User::getAllowedLocations("view");
+
+
$show = Request::any('show', 'stat', 'string');
$show = preg_replace('/[^a-z0-9_\-]/', '', $show);
@@ -153,16 +168,20 @@ class Page_Statistics extends Page
$action = Request::post('action');
if ($action === 'setnotes') {
$uuid = Request::post('uuid', '', 'string');
- $text = Request::post('content', '', 'string');
- if (empty($text)) {
- $text = null;
+ $locationid = Database::queryFirst('SELECT locationid FROM machine WHERE machineuuid = :uuid',
+ array('uuid' => $uuid))['locationid'];
+ if (User::hasPermission("note", $locationid)) {
+ $text = Request::post('content', '', 'string');
+ if (empty($text)) {
+ $text = null;
+ }
+ Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array(
+ 'uuid' => $uuid,
+ 'text' => $text,
+ ));
+ Message::addSuccess('notes-saved');
+ Util::redirect('?do=Statistics&uuid=' . $uuid);
}
- Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array(
- 'uuid' => $uuid,
- 'text' => $text,
- ));
- Message::addSuccess('notes-saved');
- Util::redirect('?do=Statistics&uuid=' . $uuid);
} elseif ($action === 'delmachines') {
$this->deleteMachines();
Util::redirect('?do=statistics', true);
@@ -188,10 +207,12 @@ class Page_Statistics extends Page
$res = Database::simpleQuery('SELECT machineuuid, locationid FROM machine WHERE machineuuid IN (:ids)', compact('ids'));
$ids = array_flip($ids);
$delete = [];
+ $allowedLocations = User::getAllowedLocations("delete");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- // TODO: Check locationid permissions
- unset($ids[$row['machineuuid']]);
- $delete[] = $row['machineuuid'];
+ if (in_array($row['locationid'], $allowedLocations)) {
+ unset($ids[$row['machineuuid']]);
+ $delete[] = $row['machineuuid'];
+ }
}
if (!empty($delete)) {
Database::exec('DELETE FROM machine WHERE machineuuid IN (:delete)', compact('delete'));
@@ -277,7 +298,8 @@ class Page_Statistics extends Page
foreach (Location::getLocations() as $loc) {
$locsFlat['L' . $loc['locationid']] = array(
'pad' => $loc['locationpad'],
- 'name' => $loc['locationname']
+ 'name' => $loc['locationname'],
+ 'disabled' => !in_array($loc['locationid'], $this->locationsAllowedToView)
);
}
}
@@ -333,8 +355,9 @@ class Page_Statistics extends Page
private function showSummary($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
-
- $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE ($where)", $args);
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
+ $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE $where", $args);
// If we only have one machine, redirect to machine details
if ($known['val'] == 1) {
$this->redirectFirst($where, $join, $args);
@@ -394,7 +417,8 @@ class Page_Statistics extends Page
global $STATS_COLORS;
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$res = Database::simpleQuery('SELECT systemmodel, Round(AVG(realcores)) AS cores, Count(*) AS `count` FROM machine'
. " $join WHERE $where GROUP BY systemmodel ORDER BY `count` DESC, systemmodel ASC", $args);
$lines = array();
@@ -427,7 +451,8 @@ class Page_Statistics extends Page
global $STATS_COLORS, $SIZE_RAM;
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$res = Database::simpleQuery("SELECT mbram, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY mbram", $args);
$lines = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
@@ -473,7 +498,8 @@ class Page_Statistics extends Page
private function showKvmState($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6');
$res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY kvmstate ORDER BY `count` DESC", $args);
$lines = array();
@@ -497,7 +523,8 @@ class Page_Statistics extends Page
global $STATS_COLORS, $SIZE_ID44;
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$res = Database::simpleQuery("SELECT id44mb, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY id44mb", $args);
$lines = array();
$total = 0;
@@ -549,7 +576,8 @@ class Page_Statistics extends Page
private function showLatestMachines($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$args['cutoff'] = ceil(time() / 3600) * 3600 - 86400 * 10;
$res = Database::simpleQuery("SELECT machineuuid, clientip, hostname, firstseen, mbram, kvmstate, id44mb FROM machine $join"
@@ -583,7 +611,8 @@ class Page_Statistics extends Page
{
Module::isAvailable('js_stupidtable');
$filterSet->makeFragments($where, $join, $sort, $args);
-
+ $args['allowedLocations'] = $this->locationsAllowedToView;
+ $where = "locationid IN (:allowedLocations) AND ($where)";
$xtra = '';
if ($filterSet->isNoId44Filter()) {
$xtra .= ', data';
@@ -594,18 +623,20 @@ class Page_Statistics extends Page
$join .= ' LEFT JOIN runmode USING (machineuuid) ';
}
}
- $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, lastseen,'
+ $res = Database::simpleQuery('SELECT machineuuid, locationid, 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();
$singleMachine = 'none';
+ $deleteAllowedLocations = User::getAllowedLocations("delete");
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
if ($singleMachine === 'none') {
$singleMachine = $row['machineuuid'];
} else {
$singleMachine = false;
}
+ $row['deleteAllowed'] = in_array($row['locationid'], $deleteAllowedLocations);
$row['state_' . $row['state']] = true;
//$row['firstseen'] = Util::prettyTime($row['firstseen']);
$row['lastseen_int'] = $row['lastseen'];
@@ -630,6 +661,13 @@ class Page_Statistics extends Page
}
}
$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']);
+ if (!empty($row['rmmodule'])) {
+ $data = RunMode::getRunMode($row['machineuuid'], RunMode::DATA_STRINGS);
+ if ($data !== false) {
+ $row['moduleName'] = $data['moduleName'];
+ $row['modeName'] = $data['modeName'];
+ }
+ }
$rows[] = $row;
}
if ($singleMachine !== false && $singleMachine !== 'none') {
@@ -749,6 +787,10 @@ class Page_Statistics extends Page
Message::addError('unknown-machine', $uuid);
return;
}
+ if (!in_array($client['locationid'], $this->locationsAllowedToView)) {
+ Message::addError('main.no-permission');
+ return;
+ }
// Hack: Get raw collected data
if (Request::get('raw', false)) {
Header('Content-Type: text/plain; charset=utf-8');
@@ -912,9 +954,9 @@ class Page_Statistics extends Page
// Not seen in last two weeks
$spans['graph'] .= '<div style="background:#444;left:0;width:100%">&nbsp;</div>';
}
- if (isset($client['state_occupied'])) {
+ if ($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>';
- } elseif (isset($client['state_off'])) {
+ } elseif ($client['state'] === 'OFFLINE') {
$spans['graph'] .= '<div style="background:#444;left:' . round(($client['lastseen'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['lastseen'] + 900) * $scale, 2) . '%">&nbsp;</div>';
}
$t = explode('-', date('Y-n-j-G', $cutoff));
@@ -960,6 +1002,7 @@ class Page_Statistics extends Page
));
}
// Notes
+ $client["notesAllowed"] = User::hasPermission("note", $client["locationid"]);
Render::addTemplate('machine-notes', $client);
}
@@ -1026,7 +1069,11 @@ class Page_Statistics extends Page
public static function getPciId($cat, $id)
{
- return Database::queryFirst('SELECT value, dateline FROM pciid WHERE category = :cat AND id = :id LIMIT 1',
+ static $cache = [];
+ $key = $cat . '-' . $id;
+ if (isset($cache[$key]))
+ return $cache[$key];
+ return $cache[$key] = Database::queryFirst('SELECT value, dateline FROM pciid WHERE category = :cat AND id = :id LIMIT 1',
array('cat' => $cat, 'id' => $id));
}
diff --git a/modules-available/statistics/pages/replace.inc.php b/modules-available/statistics/pages/replace.inc.php
new file mode 100644
index 00000000..ae9c6108
--- /dev/null
+++ b/modules-available/statistics/pages/replace.inc.php
@@ -0,0 +1,119 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess()
+ {
+ $action = Request::post('action', false, 'string');
+ if ($action === 'replace') {
+ self::handleReplace();
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=statistics&show=replace');
+ }
+ }
+
+ private static function handleReplace()
+ {
+ $replace = Request::post('replace', false, 'array');
+ if ($replace === false || empty($replace)) {
+ Message::addError('main.parameter-empty', 'replace');
+ return;
+ }
+ $list = [];
+ foreach ($replace as $p) {
+ $split = explode('x', $p);
+ if (count($split) !== 2) {
+ Message::addError('invalid-replace-format', $p);
+ continue;
+ }
+ $entry = ['old' => $split[0], 'new' => $split[1]];
+ $old = Database::queryFirst('SELECT lastseen FROM machine WHERE machineuuid = :old',
+ ['old' => $entry['old']]);
+ if ($old === false) {
+ Message::addError('unknown-machine', $entry['old']);
+ continue;
+ }
+ $new = Database::queryFirst('SELECT firstseen FROM machine WHERE machineuuid = :new',
+ ['new' => $entry['new']]);
+ if ($new === false) {
+ Message::addError('unknown-machine', $entry['new']);
+ continue;
+ }
+ if ($old['lastseen'] - 86400*7 > $new['firstseen']) {
+ Message::addWarning('ignored-both-in-use', $entry['old'], $entry['new']);
+ continue;
+ }
+ $entry['datelimit'] = min($new['firstseen'], $old['lastseen']);
+ $list[] = $entry;
+ }
+ if (empty($list)) {
+ Message::addError('main.parameter-empty', 'replace');
+ return;
+ }
+
+ // First handle module internal tables
+ foreach ($list as $entry) {
+ Database::exec('UPDATE statistic SET machineuuid = :new WHERE machineuuid = :old AND dateline < :datelimit', $entry);
+ }
+
+ // Let other modules do their thing
+ $fun = function($file, $list) {
+ include $file;
+ };
+ foreach (Hook::load('statistics/machine-replace') as $hook) {
+ $fun($hook->file, $list);
+ }
+
+ // Finalize by updating machine table
+ foreach ($list as $entry) {
+ unset($entry['datelimit']);
+ Database::exec('UPDATE machine old, machine new SET
+ new.fixedlocationid = old.fixedlocationid,
+ new.position = old.position,
+ old.position = NULL,
+ new.notes = old.notes,
+ old.notes = NULL,
+ old.lastseen = new.firstseen
+ WHERE old.machineuuid = :old AND new.machineuuid = :new', $entry);
+ }
+ Message::addSuccess('x-machines-replaced', count($list));
+ }
+
+ public static function doRender()
+ {
+ self::listSuggestions();
+ }
+
+ private static function listSuggestions()
+ {
+ if (Request::get('debug', false) !== false) {
+ $oldCutoff = time() - 86400 * 180;
+ $newCutoff = time() - 86400 * 180;
+ } else {
+ $oldCutoff = time() - 86400 * 90;
+ $newCutoff = time() - 86400 * 30;
+ }
+ $res = Database::simpleQuery("SELECT
+ old.machineuuid AS olduuid, old.locationid AS oldlid, old.hostname AS oldhost,
+ old.clientip AS oldip, old.macaddr AS oldmac, old.lastseen AS oldlastseen, old.systemmodel AS oldmodel,
+ new.machineuuid AS newuuid, new.locationid AS newlid, new.hostname AS newhost,
+ new.clientip AS newip, new.macaddr AS newmac, new.firstseen AS newfirstseen, new.systemmodel AS newmodel
+ FROM machine old INNER JOIN machine new ON (old.clientip = new.clientip AND old.lastseen < new.firstseen AND old.lastseen > $oldCutoff AND new.firstseen > $newCutoff)
+ ORDER BY oldhost ASC, oldip ASC");
+ $list = [];
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['oldlastseen_s'] = Util::prettyTime($row['oldlastseen']);
+ $row['newfirstseen_s'] = Util::prettyTime($row['newfirstseen']);
+ $list[] = $row;
+ }
+ $data = array('pairs' => $list);
+ Render::addTemplate('page-replace', $data);
+ if (empty($list)) {
+ Message::addInfo('no-replacement-matches');
+ }
+ }
+
+}
+
diff --git a/modules-available/statistics/permissions/permissions.json b/modules-available/statistics/permissions/permissions.json
new file mode 100644
index 00000000..97a49036
--- /dev/null
+++ b/modules-available/statistics/permissions/permissions.json
@@ -0,0 +1,5 @@
+[
+ "view",
+ "note",
+ "delete"
+] \ No newline at end of file
diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html
index 13e148fa..d06eb4f7 100644
--- a/modules-available/statistics/templates/clientlist.html
+++ b/modules-available/statistics/templates/clientlist.html
@@ -10,28 +10,28 @@
<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 type="button" class="btn btn-default btn-xs" onclick="popupFilter('lastseen')">
+ <span class="glyphicon glyphicon-filter"></span>
</button>
</td>
<td>
- <button class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')">
- <span id="btn_filter_kvmstate" class="glyphicon glyphicon-filter"></span>
+ <button type="button" class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')">
+ <span 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 type="button" class="btn btn-default btn-xs" onclick="popupFilter('gbram')">
+ <span class="glyphicon glyphicon-filter"></span>
</button>
</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 type="button" class="btn btn-default btn-xs" onclick="popupFilter('hddgb')">
+ <span 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 type="button" class="btn btn-default btn-xs" onclick="popupFilter('realcores')">
+ <span class="glyphicon glyphicon-filter"></span>
</button>
</td>
</tr>
@@ -49,11 +49,15 @@
{{#rows}}
<tr>
<td data-sort-value="{{hostname}}" class="text-nowrap">
+ {{#deleteAllowed}}
<div class="checkbox checkbox-inline">
- <input type="checkbox" name="uuid[]" value="{{machineuuid}}">
+ <input type="checkbox" name="uuid[]" value="{{machineuuid}}" class="deleteCheckboxes">
<label></label>
</div>
- {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}}
+ {{/deleteAllowed}}
+ {{#hasnotes}}
+ <span class="glyphicon glyphicon-exclamation-sign pull-right"></span>
+ {{/hasnotes}}
{{#state_OFFLINE}}
<span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span>
{{/state_OFFLINE}}
@@ -68,7 +72,11 @@
{{/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}}
+ {{#rmmodule}}
+ <div class="small">{{lang_runMode}}:
+ <a class="slx-bold" href="?do=runmode&amp;module={{rmmodule}}">{{moduleName}}</a> / {{modeName}}
+ </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>
@@ -91,10 +99,10 @@
</table>
<div class="text-right buttonbar">
<button type="reset" class="btn btn-default">
- <span class="glyphicon glyphicon-remove"></span>
+ <span class="glyphicon glyphicon-refresh"></span>
{{lang_reset}}
</button>
- <button type="button" class="btn btn-danger" onclick="$('#del-confirm').modal()">
+ <button id="deleteButton" type="button" class="btn btn-danger" onclick="$('#del-confirm').modal()">
<span class="glyphicon glyphicon-trash"></span>
{{lang_delete}}
</button>
@@ -122,6 +130,9 @@
</form>
<script type="application/javascript"><!--
+
+selectedMachineCounter = 0;
+
document.addEventListener("DOMContentLoaded", function () {
['gbram', 'hddgb', 'realcores', 'kvmstate', 'lastseen', 'clientip'].forEach(function (v) {
var $sortBtn = $('#sortButton-' + v);
@@ -138,6 +149,27 @@ document.addEventListener("DOMContentLoaded", function () {
container: 'body',
trigger : 'hover'
});
+
+ $("#deleteButton").prop("disabled", true);
+ $(".deleteCheckboxes").change(function() {
+ if ($(this).is(':checked')) {
+ selectedMachineCounter++;
+ if (selectedMachineCounter === 1) {
+ $("#deleteButton").prop("disabled", false);
+ }
+ } else {
+ selectedMachineCounter--;
+ if (selectedMachineCounter === 0) {
+ $("#deleteButton").prop("disabled", true);
+ }
+ }
+ });
+
+ $("button[type=reset]").click(function() {
+ selectedMachineCounter = 0;
+ $("#deleteButton").prop("disabled", true);
+ });
+
});
function toggleButton(v) {
diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html
index 32464031..58b66a75 100644
--- a/modules-available/statistics/templates/filterbox.html
+++ b/modules-available/statistics/templates/filterbox.html
@@ -99,7 +99,9 @@ var slxFilterNames = {
state: '{{lang_usageState}}',
location: '{{lang_location}}',
currentuser: '{{lang_currentUser}}',
- subnet: '{{lang_subnet}}'
+ subnet: '{{lang_subnet}}',
+ runtime: '{{lang_runtimeHours}}',
+ hostname: '{{lang_hostname}}'
};
slxLocations = {{{locations}}};
@@ -176,10 +178,12 @@ document.addEventListener("DOMContentLoaded", function () {
$('#argumentSelect').show();
columns[col]['values'].forEach(function (v) {
var t = v;
+ var disabled = true;
if (col === 'location' && slxLocations['L' + v]) {
t = slxLocations['L' + v].pad + ' ' + slxLocations['L' + v].name;
+ disabled = slxLocations['L' + v].disabled;
}
- $('#argumentSelect').append($('<option>', { value: v, text: t }));
+ $('#argumentSelect').append($('<option>', { value: v, text: t, disabled: disabled }));
});
} else {
$('#argumentInput').datepicker('remove');
diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html
index 22ed96e9..66e44da4 100644
--- a/modules-available/statistics/templates/machine-notes.html
+++ b/modules-available/statistics/templates/machine-notes.html
@@ -8,9 +8,9 @@
<input type="hidden" name="token" value="{{token}}">
<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>
+ <textarea name="content" class="form-control" cols="101" rows="10" {{^notesAllowed}}disabled{{/notesAllowed}}>{{notes}}</textarea>
<br/>
- <button type="submit" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <button type="submit" class="btn btn-primary pull-right" {{^notesAllowed}}disabled{{/notesAllowed}}><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</form>
</div>
</div>
diff --git a/modules-available/statistics/templates/page-replace.html b/modules-available/statistics/templates/page-replace.html
index f87610a2..d0e9f766 100644
--- a/modules-available/statistics/templates/page-replace.html
+++ b/modules-available/statistics/templates/page-replace.html
@@ -17,6 +17,10 @@
}
</style>
+<p>
+ {{lang_replaceInstructions}}
+</p>
+
<form method="post" action="?do=statistics&amp;show=replace">
<input type="hidden" name="token" value="{{token}}">
<table class="reptable">
diff --git a/modules-available/statistics_reporting/inc/getdata.inc.php b/modules-available/statistics_reporting/inc/getdata.inc.php
index ae4d5aa4..13d39502 100644
--- a/modules-available/statistics_reporting/inc/getdata.inc.php
+++ b/modules-available/statistics_reporting/inc/getdata.inc.php
@@ -10,24 +10,59 @@ class GetData
public static $lowerTimeBound = 0;
public static $upperTimeBound = 24;
public static $salt;
+ private static $TS_LIST = false;
+ private static $SECS_LIST = false;
+
+ private static function fillLocation(&$entry, $anonymize)
+ {
+ $locations = Location::getLocationsAssoc();
+ if ($anonymize) {
+ $entry['locationname'] = md5($entry['locationid'] . self::$salt);
+ } elseif (isset($locations[$entry['locationid']])) {
+ $entry['locationname'] = $locations[$entry['locationid']]['locationname'];
+ $entry['parentlocations'] = array_reduce($locations[$entry['locationid']]['parents'], function ($carry, $item) {
+ return $carry . sprintf("%04d", $item);
+ }) . sprintf("%04d", $entry['locationid']);
+ } else {
+ $entry['locationname'] = Dictionary::translate('notAssigned', true);
+ }
+ if ($anonymize) {
+ unset($entry['locationid']);
+ }
+ }
+
+ private static function addPrintables(&$entry)
+ {
+ if (self::$SECS_LIST === false) {
+ self::$SECS_LIST = ['totalTime', 'totalOffTime', 'totalIdleTime', 'totalSessionTime', 'totalStandbyTime', 'medianSessionLength'];
+ }
+ if (self::$TS_LIST === false) {
+ self::$TS_LIST = ['lastStart', 'lastLogout'];
+ }
+ $perc = isset($entry['totalTime']) && $entry['totalTime'] > 0;
+ foreach (self::$SECS_LIST as $k) {
+ if (isset($entry[$k])) {
+ $entry[$k . '_s'] = self::formatSeconds($entry[$k]);
+ if ($perc && $k !== 'totalTime') {
+ $entry[$k . '_p'] = round($entry[$k] / $entry['totalTime'] * 100);
+ }
+ }
+ }
+ foreach (self::$TS_LIST as $k) {
+ if (isset($entry[$k])) {
+ $entry[$k . '_s'] = Util::prettyTime($entry[$k]);
+ }
+ }
+ }
// total
public static function total($flags = 0) {
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
// total time online, average time online, total number of logins
- $res = Queries::getOverallStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $row = $res->fetch(PDO::FETCH_ASSOC);
- $data = array('totalTime' => $row['sum'], 'medianSessionLength' => self::calcMedian($row['median']), 'longSessions' => $row['longSessions'], 'shortSessions' => $row['shortSessions']);
-
- //total time offline
- $res = Queries::getTotalOfflineStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $row = $res->fetch(PDO::FETCH_ASSOC);
- $data['totalOffTime'] = $row['timeOff'];
+ $data = Queries::getOverallStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
if ($printable) {
- $data["totalTime_s"] = self::formatSeconds($data["totalTime"]);
- $data["medianSessionLength_s"] = self::formatSeconds($data["medianSessionLength"]);
- $data["totalOffTime_s"] = self::formatSeconds($data["totalOffTime"]);
+ self::addPrintables($data);
}
$data['uniqueUsers'] = Queries::getUniqueUserCount(self::$from, self::$to);
@@ -38,63 +73,29 @@ class GetData
public static function perLocation($flags = 0) {
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
- $res = Queries::getLocationStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $data = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- self::nullToZero($row);
- $median = self::calcMedian(self::calcMedian($row['medianSessionLength']));
- $entry = array(
- 'location' => ($anonymize ? $row['locHash'] : $row['locName']),
- 'totalTime' => $row['timeSum'],
- 'medianSessionLength' => $median,
- 'totalOffTime' => $row['offlineSum'],
- 'longSessions' => $row['longSessions'],
- 'shortSessions' => $row['shortSessions']
- );
- if (!$anonymize) {
- $entry['locationId'] = $row['locId'];
- }
+ $data = Queries::getLocationStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
+ foreach ($data as &$entry) {
+ //self::nullToZero($entry);
+ self::fillLocation($entry, $anonymize);
if ($printable) {
- $entry['totalTime_s'] = self::formatSeconds($row['timeSum']);
- $entry['medianSessionLength_s'] = self::formatSeconds($median);
- $entry['totalOffTime_s'] = self::formatSeconds($row['offlineSum']);
+ self::addPrintables($entry);
}
- $data[] = $entry;
}
return $data;
}
// per client
- public static function perClient($flags = 0) {
+ public static function perClient($flags = 0, $new = false) {
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$printable = 0 !== ($flags & GETDATA_PRINTABLE);
- $res = Queries::getClientStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
- $data = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- self::nullToZero($row);
- $median = self::calcMedian(self::calcMedian($row['medianSessionLength']));
- $entry = array(
- 'hostname' => ($anonymize ? $row['clientHash'] : $row['clientName']),
- 'totalTime' => $row['timeSum'],
- 'medianSessionLength' => $median,
- 'totalOffTime' => $row['offlineSum'],
- 'lastStart' => $row['lastStart'],
- 'lastLogout' => $row['lastLogout'],
- 'longSessions' => $row['longSessions'],
- 'shortSessions' => $row['shortSessions'],
- 'location' => ($anonymize ? $row['locHash'] : $row['locName']),
- );
- if (!$anonymize) {
- $entry['locationId'] = $row['locId'];
- }
+ $data = Queries::getClientStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
+ foreach ($data as &$entry) {
+ //self::nullToZero($entry);
+ $entry['hostname'] = ($anonymize ? md5($entry['clientName'] . self::$salt) : $entry['clientName']);
+ self::fillLocation($entry, $anonymize);
if ($printable) {
- $entry['totalTime_s'] = self::formatSeconds($row['timeSum']);
- $entry['medianSessionLength_s'] = self::formatSeconds($median);
- $entry['totalOffTime_s'] = self::formatSeconds($row['offlineSum']);
- $entry['lastStart_s'] = $row['lastStart'] == 0 ? "" : date(DATE_ISO8601, $row['lastStart']);
- $entry['lastLogout_s'] = $row['lastLogout'] == 0 ? "" : date(DATE_ISO8601, $row['lastLogout']);
+ self::addPrintables($entry);
}
- $data[] = $entry;
}
return $data;
}
@@ -104,9 +105,11 @@ class GetData
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$res = Queries::getUserStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
$data = array();
- $user = $anonymize ? 'userHash' : 'name';
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $data[] = array('user' => $row[$user], 'sessions' => $row['count']);
+ if ($anonymize && $row['name'] !== 'anonymous') {
+ $row['name'] = md5($row['name'] . self::$salt);
+ }
+ $data[] = array('user' => $row['name'], 'sessions' => $row['count']);
}
return $data;
}
@@ -117,10 +120,12 @@ class GetData
$anonymize = 0 !== ($flags & GETDATA_ANONYMOUS);
$res = Queries::getVMStatistics(self::$from, self::$to, self::$lowerTimeBound, self::$upperTimeBound);
$data = array();
- $vm = $anonymize ? 'vmHash' : 'name';
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
self::nullToZero($row);
- $data[] = array('vm' => $row[$vm], 'sessions' => $row['count']);
+ if ($anonymize) {
+ $row['name'] = md5($row['name'] . self::$salt);
+ }
+ $data[] = array('vm' => $row['name'], 'sessions' => $row['count']);
}
return $data;
}
@@ -140,19 +145,4 @@ class GetData
return sprintf('%dd, %02d:%02d:%02d', $seconds / (3600*24), ($seconds % (3600*24)) / 3600, ($seconds%3600) / 60, $seconds%60);
}
- // Calculate Median
- private static function calcMedian($string) {
- $arr = explode(",", $string);
- sort($arr, SORT_NUMERIC);
- $count = count($arr); //total numbers in array
- $middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
- if($count % 2) { // odd number, middle is the median
- $median = $arr[(int) $middleval];
- } else { // even number, calculate avg of 2 medians
- $low = $arr[(int) $middleval];
- $high = $arr[(int) $middleval+1];
- $median = (($low+$high)/2);
- }
- return round($median);
- }
} \ No newline at end of file
diff --git a/modules-available/statistics_reporting/inc/queries.inc.php b/modules-available/statistics_reporting/inc/queries.inc.php
index bd8eb72e..395bb548 100644
--- a/modules-available/statistics_reporting/inc/queries.inc.php
+++ b/modules-available/statistics_reporting/inc/queries.inc.php
@@ -4,214 +4,342 @@
class Queries
{
- // Client Data: Name, Time Online, Median Time Online, Time Offline, last start, last logout, Last Time Booted, Number of Sessions > 60Sec, Number of Sessions < 60Sec, name of location, id of location (anonymized), machine uuid (anonymized)
- public static function getClientStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $notassigned = Dictionary::translate('notAssigned', true);
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT t2.name AS clientName, timeSum, medianSessionLength, offlineSum, IFNULL(lastStart, 0) as lastStart, IFNULL(lastLogout, 0) as lastLogout, longSessions, shortSessions, t2.locId, t2.locName, MD5(CONCAT(t2.locId, :salt)) AS locHash, MD5(CONCAT(t2.uuid, :salt)) AS clientHash FROM (
- SELECT machine.machineuuid AS 'uuid', SUM(CAST(sessionTable.length AS UNSIGNED)) AS 'timeSum', GROUP_CONCAT(sessionTable.length) AS 'medianSessionLength', SUM(sessionTable.length >= 60) AS 'longSessions', SUM(sessionTable.length < 60) AS 'shortSessions', MAX(sessionTable.endInBound) AS 'lastLogout'
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable
- RIGHT JOIN machine ON sessionTable.machineuuid = machine.machineuuid
- GROUP BY machine.machineuuid
- ) t1
- RIGHT JOIN (
- SELECT IF(machine.hostname = '', machine.clientip, machine.hostname) AS 'name', machine.machineuuid AS 'uuid', SUM(CAST(offlineTable.length AS UNSIGNED)) AS 'offlineSum', MAX(offlineTable.endInBound) AS 'lastStart', IFNULL(location.locationname, '$notassigned') AS 'locName', location.locationid AS 'locId'
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable
- RIGHT JOIN machine ON offlineTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.machineuuid
- ) t2
- ON t1.uuid = t2.uuid", array("salt" => GetData::$salt));
+ private static function keepKeys(&$array, $list)
+ {
+ foreach (array_keys($array) as $key) {
+ if (!in_array($key, $list)) {
+ unset($array[$key]);
+ }
+ }
+ }
- return $res;
+ public static function getClientStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ foreach ($machines as &$machine) {
+ $machine['medianSessionLength'] = self::calcMedian($machine['sessions']);
+ unset($machine['sessions']);
+ $machine['clientName'] = $machine['hostname'] ? $machine['hostname'] : $machine['clientip'];
+ }
+ return $machines;
}
- // Location Data: Name, ID (anonymized), Time Online, Median Time Online, Time Offline, Number of Sessions > 60Sec, Number of Sessions < 60Sec
- public static function getLocationStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $notassigned = Dictionary::translate('notAssigned', true);
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT t2.locId, t2.locName, MD5(CONCAT(t2.locId, :salt)) AS locHash, timeSum, medianSessionLength, offlineSum, longSessions, shortSessions FROM (
- SELECT location.locationid AS 'locId', SUM(CAST(sessionTable.length AS UNSIGNED)) AS 'timeSum', GROUP_CONCAT(sessionTable.length) AS 'medianSessionLength', SUM(sessionTable.length >= 60) AS 'longSessions', SUM(sessionTable.length < 60) AS 'shortSessions'
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable
- RIGHT JOIN machine ON sessionTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.locationid
- ) t1
- RIGHT JOIN (
- SELECT IFNULL(location.locationname, '$notassigned') AS 'locName', location.locationid AS 'locId', SUM(CAST(offlineTable.length AS UNSIGNED)) AS 'offlineSum'
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable
- RIGHT JOIN machine ON offlineTable.machineuuid = machine.machineuuid
- LEFT JOIN location ON machine.locationid = location.locationid
- GROUP BY machine.locationid
- ) t2
- ON t1.locId = t2.locId", array("salt" => GetData::$salt));
- return $res;
+ public static function getLocationStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ $locations = [];
+ $keys = ['locationid', 'totalTime', 'totalOffTime', 'totalSessionTime', 'totalStandbyTime', 'totalIdleTime', 'totalIdleTime', 'longSessions', 'shortSessions', 'sessions'];
+ while ($machine = array_pop($machines)) {
+ if (!isset($locations[$machine['locationid']])) {
+ self::keepKeys($machine, $keys);
+ $locations[$machine['locationid']] = $machine;
+ } else {
+ $l =& $locations[$machine['locationid']];
+ $l['totalTime'] += $machine['totalTime'];
+ $l['totalOffTime'] += $machine['totalOffTime'];
+ $l['totalSessionTime'] += $machine['totalSessionTime'];
+ $l['totalStandbyTime'] += $machine['totalStandbyTime'];
+ $l['totalIdleTime'] += $machine['totalIdleTime'];
+ $l['longSessions'] += $machine['longSessions'];
+ $l['shortSessions'] += $machine['shortSessions'];
+ $l['sessions'] = array_merge($l['sessions'], $machine['sessions']);
+ }
+ }
+ foreach ($locations as &$location) {
+ $location['medianSessionLength'] = self::calcMedian($location['sessions']);
+ unset($location['sessions']);
+ }
+ return $locations;
}
- // User Data: Name, Name(anonymized), Number of Logins
- public static function getUserStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT username AS name, IF(username = 'anonymous', 'anonymous', md5(CONCAT(username, :salt))) AS userHash, COUNT(*) AS 'count'
- FROM statistic
- WHERE typeid='.vmchooser-session-name' AND dateline >= $from and dateline <= $to
- AND FROM_UNIXTIME(dateline, '%H') >= $lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < $upperTimeBound
- GROUP BY username
- ORDER BY 2 DESC", array("salt" => GetData::$salt));
- return $res;
+ public static function getOverallStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.hostname, m.clientip,
+ m.locationid, m.firstseen -- , m.lastboot, m.logintime, m.state
+ FROM machine m WHERE firstseen <= $to"); // " WHERE lastseen >= :from", compact('from'));
+ $machines = self::getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound);
+ $total = false;
+ $keys = ['totalTime', 'totalOffTime', 'totalSessionTime', 'totalStandbyTime', 'totalIdleTime', 'totalIdleTime', 'longSessions', 'shortSessions', 'sessions'];
+ while ($machine = array_pop($machines)) {
+ if ($total === false) {
+ self::keepKeys($machine, $keys);
+ $total = $machine;
+ } else {
+ $total['totalTime'] += $machine['totalTime'];
+ $total['totalOffTime'] += $machine['totalOffTime'];
+ $total['totalSessionTime'] += $machine['totalSessionTime'];
+ $total['totalStandbyTime'] += $machine['totalStandbyTime'];
+ $total['totalIdleTime'] += $machine['totalIdleTime'];
+ $total['longSessions'] += $machine['longSessions'];
+ $total['shortSessions'] += $machine['shortSessions'];
+ $total['sessions'] = array_merge($total['sessions'], $machine['sessions']);
+ }
+ }
+ $total['medianSessionLength'] = self::calcMedian($total['sessions']);
+ unset($total['sessions']);
+ return $total;
}
- // Virtual Machine Data: Name, Number of Usages
- public static function getVMStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT data AS name, MD5(CONCAT(data, :salt)) AS vmHash, COUNT(*) AS 'count'
- FROM statistic
- WHERE typeid='.vmchooser-session-name' AND dateline >= $from and dateline <= $to
- AND FROM_UNIXTIME(dateline, '%H') >= $lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < $upperTimeBound
- GROUP BY data
- ORDER BY 2 DESC", array("salt" => GetData::$salt));
- return $res;
+ /**
+ * @param \PDOStatement $res
+ * @param int $from
+ * @param int $to
+ * @param int $lowerTimeBound
+ * @param int $upperTimeBound
+ * @return array
+ */
+ private static function getStats3($res, $from, $to, $lowerTimeBound, $upperTimeBound)
+ {
+ //$debug = false;
+ if ($lowerTimeBound === 0 && $upperTimeBound === 24 || $upperTimeBound <= $lowerTimeBound) {
+ $bounds = false;
+ } else {
+ $bounds = [$lowerTimeBound, $upperTimeBound];
+ }
+ $machines = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['firstseen'] = max($row['firstseen'], $from);
+ $row += array(
+ 'totalTime' => self::timeDiff($row['firstseen'], $to, $bounds),
+ 'totalOffTime' => 0,
+ 'totalSessionTime' => 0,
+ 'totalStandbyTime' => 0,
+ 'sessions' => [],
+ 'lastStart' => 0,
+ 'lastLogout' => 0,
+ 'longSessions' => 0,
+ 'shortSessions' => 0,
+ 'active' => false,
+ );
+ $machines[$row['machineuuid']] = $row;
+ }
+ // Don't filter by typeid in the query, still faster by being able to use the machineuuid/dateline index and filtering later
+ $last = $from - 86400; // Start 24h early to catch sessions already in progress
+ $dups = [];
+ // Fetch in batches of 1000 rows (for current 50 machines)
+ do {
+ $res = Database::simpleQuery("SELECT logid, dateline, typeid, machineuuid, data
+ FROM statistic WHERE dateline >= :last AND dateline <= :to AND machineuuid IS NOT NULL
+ ORDER BY dateline ASC LIMIT 1000", compact('last', 'to'));
+ $last = false;
+ $count = 0;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $count += 1; // Update count first, as we use it as a condition in outer loop. No continue before this
+ settype($row['logid'], 'int');
+ // Update for next query
+ $last = $row['dateline'];
+ // Ignore dups, we query >= last dateline as we can have multiple events at the same second, but
+ // only some of them got returned because of LIMIT. Skip here because excluding in query directly
+ // would make the query itself rather inefficient. We also cannot use logid > X because the logid
+ // is not strictly ascending with time, as dateline gets backdated to event start on insert
+ if ($count === 150) {
+ $dups = [];
+ } elseif ($count > 900) {
+ $dups[] = $row['logid'];
+ } elseif ($count < 150 && array_key_exists($row['logid'], $dups)) {
+ continue;
+ }
+ if (!isset($machines[$row['machineuuid']]))
+ continue;
+ if ($row['typeid'] !== '~offline-length' && $row['typeid'] !== '~suspend-length' && $row['typeid'] !== '~session-length')
+ continue;
+ settype($row['dateline'], 'int');
+ settype($row['data'], 'int');
+ if ($row['data'] <= 0)
+ continue;
+ // Clamp to $from and $to
+ if ($row['dateline'] < $from) {
+ $diff = $row['dateline'] + $row['data'] - $from;
+ if ($diff <= 0)
+ continue;
+ $row['dateline'] += $diff;
+ $row['data'] -= $diff;
+ }
+ if ($row['dateline'] + $row['data'] > $to) {
+ $row['data'] = $to - $row['dateline'];
+ if ($row['data'] < 0)
+ continue;
+ }
+ $machine =& $machines[$row['machineuuid']];
+ // Process event if applicable
+ if ($row['typeid'] === '~session-length') { // SESSION timespan
+ $row['typeid'] = 'totalSessionTime';
+ $machine['lastLogout'] = $row['dateline'] + $row['data'];
+ } elseif ($row['typeid'] === '~offline-length') { // OFFLINE timespan
+ $row['typeid'] = 'totalOffTime';
+ $machine['lastStart'] = $row['dateline'] + $row['data'];
+ } else { // STANDBY timespan
+ $row['typeid'] = 'totalStandbyTime';
+ }
+ self::addTime($machine, $row, $bounds);
+ }
+ $dups = array_flip($dups);
+ } while ($last !== false && $count === 1000); // Check if we need to fetch more rows for current batch
+ foreach ($machines as &$machine) {
+ if (!$machine['active']) {
+ $machine['totalOffTime'] = $machine['totalTime'];
+ }
+ $machine['totalIdleTime'] = $machine['totalTime'] - ($machine['totalOffTime'] + $machine['totalStandbyTime'] + $machine['totalSessionTime']);
+ }
+ return $machines;
}
- //Total Data: Time Online, Median Time Online, Number of Sessions > 60Sec, Number of Sessions < 60Sec
- public static function getOverallStatistics ($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- Database::exec("SET SESSION group_concat_max_len = 1000000000");
- $res = Database::simpleQuery("SELECT SUM(CAST(sessionTable.length AS UNSIGNED)) AS sum, GROUP_CONCAT(sessionTable.length) AS median, SUM(sessionTable.length >= 60) AS longSessions, SUM(sessionTable.length < 60) AS shortSessions
- FROM ".self::getBoundedTableQueryString('~session-length', $from, $to, $lowerTimeBound, $upperTimeBound)." sessionTable");
- return $res;
+ private static function addTime(&$machine, $row, $bounds)
+ {
+ // First event, handle difference
+ if (!$machine['active'] && $row['dateline'] + $row['data'] >= $machine['firstseen']) {
+ if ($row['dateline'] > $machine['firstseen']) {
+ $s = $machine['firstseen'];
+ $e = $row['dateline'];
+ /*
+ if ($debug) {
+ error_log('Initial offline time += ' . self::timeDiff($s, $e, $bounds, true));
+ }
+ */
+ $machine['totalOffTime'] += self::timeDiff($s, $e, $bounds);
+ $machine['active'] = true;
+ if ($machine['lastStart'] < $row['dateline']) {
+ $machine['lastStart'] = $row['dateline'];
+ }
+ } else {
+ // Not offline at beginning of period, do nothing
+ $machine['active'] = true;
+ }
+ }
+ // Current row
+ if ($bounds === false) {
+ // Simple case: No bounds
+ $machine[$row['typeid']] += $row['data'];
+ } else {
+ $start = $row['dateline'];
+ $end = $row['dateline'] + $row['data'];
+ /*
+ if ($debug) {
+ error_log('Adding ' . $row['typeid'] . ' += ' . self::timeDiff($start, $end, $bounds, true));
+ }
+ */
+ $machine[$row['typeid']] += self::timeDiff($start, $end, $bounds);
+ $sh = date('G', $start);
+ }
+ if ($row['typeid'] === 'totalSessionTime' && ($bounds === false || ($sh >= $bounds[0] && $sh < $bounds[1]))) {
+ if ($row['data'] >= 60) {
+ $machine['longSessions'] += 1;
+ $machine['sessions'][] = $row['data'];
+ } else {
+ $machine['shortSessions'] += 1;
+ }
+ }
}
- // Total Data(2): Time Offline
- public static function getTotalOfflineStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
- $res = Database::simpleQuery("SELECT SUM(CAST(offlineTable.length AS UNSIGNED)) AS timeOff
- FROM ".self::getBoundedTableQueryString('~offline-length', $from, $to, $lowerTimeBound, $upperTimeBound)." offlineTable");
- return $res;
+ private static function timeDiff($start, $end, $bounds)
+ {
+ // Put given timespan into bounds
+ /*
+ if ($debug) {
+ $os = $start;
+ $oe = $end;
+ }
+ */
+ if ($bounds !== false) {
+ // Put start time into bounds
+ if ($start !== null) {
+ $sh = date('G', $start);
+ if ($sh < $bounds[0]) {
+ $start = strtotime($bounds[0] . ':00:00', $start);
+ } elseif ($sh >= $bounds[1]) {
+ $start = strtotime($bounds[0] . ':00:00 +1day', $start);
+ }
+ }
+ // Put end time into bounds
+ if ($end !== null && $end > $start) {
+ $eh = date('G', $end);
+ if ($eh < $bounds[0]) {
+ $end = strtotime($bounds[1] . ':00:00 -1day', $end);
+ } elseif ($eh >= $bounds[1]) {
+ $end = strtotime($bounds[1] . ':00:00', $end);
+ }
+ }
+ }
+ if ($end !== null && $start !== null && $end < $start) {
+ $end = $start;
+ }
+ /*
+ if ($debug) {
+ if ($start >= $end) {
+ error_log('END < START: ' . date('d.m.Y H:i:s', $start) . ' - ' . date('d.m.Y H:i:s', $end));
+ } else {
+ if ($os != $start) {
+ error_log('Corrected start: ' . date('d.m.Y H:i:s', $os) . ' to ' . date('d.m.Y H:i:s', $start));
+ }
+ if ($oe != $end) {
+ error_log('Corrected end : ' . date('d.m.Y H:i:s', $oe) . ' to ' . date('d.m.Y H:i:s', $end));
+ }
+ }
+ }
+ */
+ // Calc time excluding out of range hours
+ return ($end - $start) - self::getIgnoredTime($start, $end, $bounds);
}
- // query string which provides table with time-cutoff and time-bounds
- private static function getBoundedTableQueryString($typeid, $from, $to, $lowerTimeBound, $upperTimeBound)
+ private static function getIgnoredTime($start, $end, $bounds)
{
- // get Clients that are currently oflfine (the offline time is not yet recorded in the statistic table)
- $union = $typeid == '~offline-length' ?
- "union
- select CAST(IF(lastseen < $from, $from, lastseen) as UNSIGNED) as start, $to as end,
- '~offline-length' as typeid, machineuuid, 'machine'
- from machine where lastseen <= $to and UNIX_TIMESTAMP() - lastseen >= 600" : "";
+ if ($bounds === false || $start >= $end)
+ return 0;
+ $end = strtotime('00:00:00', $end);
+ if ($start >= $end)
+ return 0;
+ /*
+ if ($debug) {
+ error_log('From ' . date('d.m.Y H:i:s', $start) . ' to ' . date('d.m.Y H:i:s', $end) . ' = ' . ceil(($end - $start) / 86400) * (24 - ($bounds[1] - $bounds[0])));
+ }
+ */
+ return (int)ceil(($end - $start) / 86400) * (24 - ($bounds[1] - $bounds[0])) * 3600;
+ }
+
+ /**
+ * Get median of array.
+ * @param int[] list of values
+ * @return int The median
+ */
+ private static function calcMedian($array) {
+ if (empty($array))
+ return 0;
+ sort($array, SORT_NUMERIC);
+ $count = count($array); //total numbers in array
+ $middleval = (int)floor(($count-1) / 2); // find the middle value, or the lowest middle value
+ if($count % 2 === 1) { // odd number, middle is the median
+ return (int)$array[$middleval];
+ }
+ // even number, calculate avg of 2 medians
+ $low = $array[$middleval];
+ $high = $array[$middleval+1];
+ return (int)round(($low+$high) / 2);
+ }
+ // User Data: Name, Name(anonymized), Number of Logins
+ public static function getUserStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
+ $res = Database::simpleQuery("SELECT username AS name, COUNT(*) AS 'count'
+ FROM statistic
+ WHERE typeid='.vmchooser-session-name' AND dateline >= :from and dateline <= :to
+ AND FROM_UNIXTIME(dateline, '%H') >= :lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < :upperTimeBound
+ GROUP BY username", compact('from', 'to', 'lowerTimeBound', 'upperTimeBound'));
+ return $res;
+ }
- $lowerFormat = "'%y-%m-%d $lowerTimeBound:00:00'";
- $upperFormat = "'%y-%m-%d ".($upperTimeBound-1).":59:59'";
- $queryString = "
- select
-
- # The whole length of the session/offline time.
- (end-start
-
- # Now the time that is not within the daily time bounds is subtracted.
- # This includes the time before the first daily bound, the time after the last daily bound
- # and the time between the daily bounds (if a session/offline time spans multiple days)
-
- # Time before the first daily bound is subtracted.
- - IF(
- start > startUpper,
- UNIX_TIMESTAMP(FROM_UNIXTIME(start, $lowerFormat) + INTERVAL 1 DAY) - start,
- IF(
- start < startLower,
- startLower - start,
- 0
- )
- )
-
- # Time after the last daily bound is subtracted.
- - IF(
- end > endUpper,
- end - (endUpper + 1),
- IF(
- end < endLower,
- end - (UNIX_TIMESTAMP(FROM_UNIXTIME(end, $upperFormat) - INTERVAL 1 DAY) + 1),
- 0
- )
- )
-
- # Time between the daily bounds is subtracted.
- - ( daysDiff - 2
- + IF(start <= startUpper, 1, 0)
- + IF(end >= endLower, 1, 0)
- ) * ((24 - ($upperTimeBound - $lowerTimeBound)) * 3600)
-
- # If the session crossed a clock change (to/from daylight saving time), the last subtraction may have subtracted
- # one hour too much or too little. This IF will correct this.
- - IF(
- innerStart < innerEnd,
- IF(
- timeDiff = 1 AND ($lowerTimeBound >= 2 OR $upperTimeBound <= 2),
- 3600,
- IF(timeDiff = -1 AND ($lowerTimeBound >= 3 OR $upperTimeBound <= 3), -3600, 0)
- ),
- 0
- )
-
- ) as 'length',
-
- IF(end < endUpper AND end > endLower AND end < $to, end, 0) as endInBound,
-
- machineuuid
-
-
- # These nested selects are necessary because some things need to be calculated before others.
- # (e.g. start is needed to calculate startLower)
- from (
- select
- *,
-
- # timeDiff is the clock change between innerStart and innerEnd. ( 0 = no clock change)
- ((CAST(date_format(from_unixtime(innerStart), '%H') as SIGNED) -
- CAST(date_format(convert_tz(from_unixtime(innerStart), @@session.time_zone, '+00:00'), '%H') as SIGNED) + 24) % 24
- -
- (CAST(date_format(from_unixtime(innerEnd), '%H') as SIGNED) -
- CAST(date_format(convert_tz(from_unixtime(innerEnd), @@session.time_zone, '+00:00'), '%H') as SIGNED) + 24) % 24) as timeDiff
- from (
- select
- *,
-
- # innerStart and innerEnd are start and end but excluding the time before the first daily upper bound and after the last daily lower bound.
- CAST(IF(start <= startUpper, startUpper, UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat) + INTERVAL 1 DAY)) as UNSIGNED) as innerStart,
- CAST(IF(end >= endLower, endLower, UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat) - INTERVAL 1 DAY)) as UNSIGNED) as innerEnd
- from (
- select
- *,
-
- # daysDiff = how many different days the start and end are apart (0 = start and end on the same day)
- (TO_DAYS(FROM_UNIXTIME(end, '%y-%m-%d')) - TO_DAYS(FROM_UNIXTIME(start, '%y-%m-%d'))) as daysDiff,
-
- # startLower = lower daily time bound on the starting day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(start, $lowerFormat)) as UNSIGNED) as startLower,
- # startUpper = upper daily time bound on the starting day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) as UNSIGNED) as startUpper,
- # endLower = lower daily time bound on the ending day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat)) as UNSIGNED) as endLower,
- # endUpper = upper daily time bound on the ending day
- CAST(UNIX_TIMESTAMP(FROM_UNIXTIME(end, $upperFormat)) as UNSIGNED) as endUpper
- from (
- # Statistic logs (combined with currently offline machines if offline times are requested) .
- select CAST(IF(dateline < $from, $from, dateline) as UNSIGNED) as start,
- CAST(IF(dateline+data > $to, $to, dateline+data) as UNSIGNED) as end,
- typeid, machineuuid, 'statistic'
- from statistic where dateline+data >= $from and dateline <= $to and typeid = '$typeid'
- $union
- ) t
- ) t
- ) t
- ) t
-
-
- # Filter out the session that are at least overlapping with the time bounds.
- where (
- (daysDiff = 0 and (start <= UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) and end >= UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat))))
- or
- (daysDiff = 1 and (start <= UNIX_TIMESTAMP(FROM_UNIXTIME(start, $upperFormat)) or end >= UNIX_TIMESTAMP(FROM_UNIXTIME(end, $lowerFormat))))
- or
- daysDiff >= 2
- )
- ";
- return "(".$queryString.")";
+ // Virtual Machine Data: Name, Number of Usages
+ public static function getVMStatistics($from, $to, $lowerTimeBound = 0, $upperTimeBound = 24) {
+ $res = Database::simpleQuery("SELECT data AS name, COUNT(*) AS 'count'
+ FROM statistic
+ WHERE typeid='.vmchooser-session-name' AND dateline >= :from and dateline <= :to
+ AND FROM_UNIXTIME(dateline, '%H') >= :lowerTimeBound AND FROM_UNIXTIME(dateline, '%H') < :upperTimeBound
+ GROUP BY data", compact('from', 'to', 'lowerTimeBound', 'upperTimeBound'));
+ return $res;
}
public static function getDozmodStats($from, $to)
diff --git a/modules-available/statistics_reporting/inc/remotereport.inc.php b/modules-available/statistics_reporting/inc/remotereport.inc.php
index 98af8888..fa84e7e5 100644
--- a/modules-available/statistics_reporting/inc/remotereport.inc.php
+++ b/modules-available/statistics_reporting/inc/remotereport.inc.php
@@ -83,7 +83,7 @@ class RemoteReport
GetData::$from = $from;
GetData::$to = $to;
$data = array('total' => GetData::total(GETDATA_ANONYMOUS));
- $data['perLocation'] = GetData::perLocation(GETDATA_ANONYMOUS);
+ $data['perLocation'] = array_values(GetData::perLocation(GETDATA_ANONYMOUS));
$data['perVM'] = GetData::perVM(GETDATA_ANONYMOUS);
$data['tsFrom'] = $from;
$data['tsTo'] = $to;
diff --git a/modules-available/statistics_reporting/lang/de/template-tags.json b/modules-available/statistics_reporting/lang/de/template-tags.json
index f8829b79..397d93de 100644
--- a/modules-available/statistics_reporting/lang/de/template-tags.json
+++ b/modules-available/statistics_reporting/lang/de/template-tags.json
@@ -1,5 +1,4 @@
{
- "lang_apply": "Anwenden",
"lang_displayColumns": "Auswahl angezeigter Spalten",
"lang_displaySelection": "Anzeigemodus, Auswahl Zeitfenster",
"lang_downloadReport": "Report herunterladen",
@@ -7,7 +6,7 @@
"lang_hostname": "Hostname",
"lang_lastLogout": "Letzter Logout",
"lang_lastStart": "Letzter Boot",
- "lang_location": "Ort",
+ "lang_locationname": "Ort",
"lang_longSessions": "Sitzungen \u2265 60s",
"lang_medianSessionLength": "Sitzungsdauer Median",
"lang_moduleName": "Statistikauswertung",
@@ -18,7 +17,10 @@
"lang_shortSessions": "Sitzungen < 60s",
"lang_show": "Anzeigen",
"lang_total": "Gesamt",
- "lang_totalOffTime": "Gesamtzeit offline",
+ "lang_totalIdleTime": "Ungenutzt",
+ "lang_totalOffTime": "Offline",
+ "lang_totalSessionTime": "Belegt",
+ "lang_totalStandbyTime": "Standby",
"lang_totalTime": "Gesamtzeit",
"lang_user": "Nutzer",
"lang_vm": "Veranstaltung"
diff --git a/modules-available/statistics_reporting/lang/en/template-tags.json b/modules-available/statistics_reporting/lang/en/template-tags.json
index 73c21112..a4d19222 100644
--- a/modules-available/statistics_reporting/lang/en/template-tags.json
+++ b/modules-available/statistics_reporting/lang/en/template-tags.json
@@ -1,5 +1,4 @@
{
- "lang_apply": "Apply",
"lang_displayColumns": "Select columns to display",
"lang_displaySelection": "Select display mode and specify time span",
"lang_downloadReport": "Download report",
@@ -7,7 +6,7 @@
"lang_hostname": "Hostname",
"lang_lastLogout": "Last logout",
"lang_lastStart": "Last boot",
- "lang_location": "Location",
+ "lang_locationname": "Location",
"lang_longSessions": "Sessions \u2265 60s",
"lang_medianSessionLength": "Median Session Length",
"lang_moduleName": "Statistics Reporting",
@@ -18,7 +17,10 @@
"lang_shortSessions": "Sessions < 60s",
"lang_show": "Show",
"lang_total": "Total",
- "lang_totalOffTime": "Total Time Offline",
+ "lang_totalIdleTime": "Idle",
+ "lang_totalOffTime": "Offline",
+ "lang_totalSessionTime": "Occupied",
+ "lang_totalStandbyTime": "Standby",
"lang_totalTime": "Total Time",
"lang_user": "User",
"lang_vm": "Lecture"
diff --git a/modules-available/statistics_reporting/page.inc.php b/modules-available/statistics_reporting/page.inc.php
index 863715d4..b30b5cab 100644
--- a/modules-available/statistics_reporting/page.inc.php
+++ b/modules-available/statistics_reporting/page.inc.php
@@ -24,8 +24,8 @@ class Page_Statistics_Reporting extends Page
/**
* @var array Names of columns that are being used by the various tables
*/
- private $COLUMNS = array('location', 'totalTime', 'medianSessionLength', 'sessions', 'longSessions', 'shortSessions',
- 'totalOffTime', 'lastLogout', 'lastStart');
+ private $COLUMNS = array('locationname', 'totalTime', 'medianSessionLength', 'sessions', 'longSessions', 'shortSessions',
+ 'totalOffTime', 'totalStandbyTime', 'totalSessionTime', 'totalIdleTime', 'lastLogout', 'lastStart');
/**
* @var array Names of the tables we can display
@@ -56,10 +56,16 @@ class Page_Statistics_Reporting extends Page
}
// timespan you want to see. default = last 7 days
- GetData::$from = strtotime("- " . ($this->days - 1) . " days 00:00:00");
+ GetData::$from = strtotime("-" . ($this->days - 1) . " days 00:00:00");
GetData::$to = time();
GetData::$lowerTimeBound = $this->lower;
GetData::$upperTimeBound = $this->upper;
+ /*
+ GetData::$from = strtotime("2017-12-01 00:00:00");
+ GetData::$to = strtotime("2017-12-15 00:00:00");
+ GetData::$lowerTimeBound = 1;
+ GetData::$upperTimeBound = 2;
+ */
// Export - handle in doPreprocess so we don't render the menu etc.
if ($this->action === 'export') {
@@ -193,20 +199,20 @@ class Page_Statistics_Reporting extends Page
if (Request::get('col_' . $column, 'delete', 'string') === 'delete') {
foreach ($res as &$row) {
unset($row[$column], $row[$column . '_s']);
- if ($column === 'location') {
- unset($row['locationId']);
+ if ($column === 'locationname') {
+ unset($row['locationid']);
}
}
} elseif ($printable && isset($row[0][$column . '_s'])) {
foreach ($res as &$row) {
unset($row[$column]);
}
- } elseif ($column === 'location' && (isset($res[0]['location']) || isset($res[0]['locationId']))) {
+ } elseif ($column === 'locationname' && (isset($res[0]['locationname']) || isset($res[0]['locationid']))) {
foreach ($res as &$row) {
if ($printable) {
- unset($row['locationId']);
+ unset($row['locationid']);
} else {
- unset($row['location']);
+ unset($row['locationname']);
}
}
}
@@ -285,7 +291,7 @@ class Page_Statistics_Reporting extends Page
$highlight = Request::get('location', false, 'int');
if ($highlight !== false) {
foreach ($data as &$row) {
- if ($row['locationId'] == $highlight) {
+ if ($row['locationid'] == $highlight) {
$row['highlight'] = true;
}
}
@@ -293,7 +299,7 @@ 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)) {
+ if (!in_array($row['locationid'], $filterLocs)) {
unset($data[$key]);
}
}
@@ -301,11 +307,11 @@ class Page_Statistics_Reporting extends Page
$data = array_values($data);
return $data;
case 'client':
- $data = GetData::perClient($flags);
+ $data = GetData::perClient($flags, Request::any('new', false, 'string'));
// 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)) {
+ if (!in_array($row['locationid'], $filterLocs)) {
unset($data[$key]);
}
}
diff --git a/modules-available/statistics_reporting/style.css b/modules-available/statistics_reporting/style.css
index 3cd6653f..c732099d 100644
--- a/modules-available/statistics_reporting/style.css
+++ b/modules-available/statistics_reporting/style.css
@@ -35,4 +35,13 @@
margin-left: -1.5em;
text-align: center;
line-height: 1.6em;
+}
+
+.table-nowrap td {
+ white-space: nowrap;
+}
+
+td.p-addon {
+ padding: 5px 5px 5px 0 !important;
+ text-align: right;
} \ 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 a6561c47..d6ff90b7 100644
--- a/modules-available/statistics_reporting/templates/columnChooser.html
+++ b/modules-available/statistics_reporting/templates/columnChooser.html
@@ -2,7 +2,10 @@
<input type="hidden" name="do" value="statistics_reporting">
<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>
+ <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> {{lang_settings}}</button>
<h1>{{lang_moduleName}}</h1>
</div>
@@ -82,7 +85,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
- <h4 class="modal-title"><b>Settings</b></h4>
+ <h4 class="modal-title"><b>{{lang_settings}}</b></h4>
</div>
<div class="modal-body">
<div class="checkbox">
@@ -186,4 +189,4 @@
}
});
}
-</script> \ No newline at end of file
+</script>
diff --git a/modules-available/statistics_reporting/templates/table-client.html b/modules-available/statistics_reporting/templates/table-client.html
index 59153e01..7bcf3ecf 100644
--- a/modules-available/statistics_reporting/templates/table-client.html
+++ b/modules-available/statistics_reporting/templates/table-client.html
@@ -1,29 +1,43 @@
-<table id="table-perclient" class="table table-condensed table-striped stupidtable">
+<table id="table-perclient" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th data-sort="string" class="text-left col-md-4">{{lang_hostname}}</th>
- <th data-sort="string" class="text-left col_location">{{lang_location}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_lastLogout">{{lang_lastLogout}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_lastStart">{{lang_lastStart}}</th>
+ <th data-sort="string">{{lang_hostname}}</th>
+ <th data-sort="string" class="col_locationname">{{lang_locationname}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalTime">{{lang_totalTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_longSessions">{{lang_longSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_lastLogout">{{lang_lastLogout}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_lastStart">{{lang_lastStart}}</th>
</tr>
</thead>
<tbody>
{{#data}}
<tr>
<td class="text-left">{{hostname}}</td>
- <td class="text-left col_location"><a class="locationLink" href="#" data-lid="{{locationId}}">{{location}}</a></td>
- <td data-sort-value="{{totalTime}}" class="text-left col_totalTime">{{totalTime_s}}</td>
- <td data-sort-value="{{medianSessionLength}}" class="text-left col_medianSessionLength">{{medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{longSessions}}</td>
- <td class="text-left col_shortSessions">{{shortSessions}}</td>
- <td data-sort-value="{{totalOffTime}}" class="text-left col_totalOffTime">{{totalOffTime_s}}</td>
- <td data-sort-value="{{lastLogout}}" class="text-left col_lastLogout">{{lastLogout_s}}</td>
- <td data-sort-value="{{lastStart}}" class="text-left col_lastStart">{{lastStart_s}}</td>
+ <td class="col_locationname"><a class="locationLink" href="#" data-lid="{{locationid}}">{{locationname}}</a></td>
+ <td data-sort-value="{{totalTime}}" class="col_totalTime">{{totalTime_s}}</td>
+ <td data-sort-value="{{medianSessionLength}}" class="col_medianSessionLength">{{medianSessionLength_s}}</td>
+ <td class="col_longSessions">{{longSessions}}</td>
+ <td class="col_shortSessions">{{shortSessions}}</td>
+ <td data-sort-value="{{totalOffTime}}" class="text-right col_totalOffTime">{{totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{totalOffTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalStandbyTime}}" class="text-right col_totalStandbyTime">{{totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{totalStandbyTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalSessionTime}}" class="text-right col_totalSessionTime">{{totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{totalSessionTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalIdleTime}}" class="text-right col_totalIdleTime">{{totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{totalIdleTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{lastLogout}}" class="col_lastLogout">{{lastLogout_s}}</td>
+ <td data-sort-value="{{lastStart}}" class="col_lastStart">{{lastStart_s}}</td>
</tr>
{{/data}}
</tbody>
diff --git a/modules-available/statistics_reporting/templates/table-location.html b/modules-available/statistics_reporting/templates/table-location.html
index a0867208..e7a0c84d 100644
--- a/modules-available/statistics_reporting/templates/table-location.html
+++ b/modules-available/statistics_reporting/templates/table-location.html
@@ -1,23 +1,37 @@
-<table id="table-perlocation" class="table table-condensed table-striped stupidtable">
+<table id="table-perlocation" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th data-sort="string" class="text-left col-md-2">{{lang_location}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th data-sort="int" data-sort-default="desc" class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="string">{{lang_locationname}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalTime">{{lang_totalTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_longSessions">{{lang_longSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalOffTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalStandbyTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalSessionTime">(%)</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th data-sort="int" data-sort-default="desc" class="col_totalIdleTime">(%)</th>
</tr>
</thead>
<tbody>
{{#data}}
<tr{{#highlight}} class="info"{{/highlight}}>
- <td class="locationName text-left">{{location}}</td>
- <td data-sort-value="{{totalTime}}" class="text-left col_totalTime">{{totalTime_s}}</td>
- <td data-sort-value="{{medianSessionLength}}" class="text-left col_medianSessionLength">{{medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{longSessions}}</td>
- <td class="text-left col_shortSessions">{{shortSessions}}</td>
- <td data-sort-value="{{totalOffTime}}" class="text-left col_totalOffTime">{{totalOffTime_s}}</td>
+ <td data-sort-value="{{parentlocations}}" class="locationName text-left">{{locationname}}</td>
+ <td data-sort-value="{{totalTime}}" class="text-right col_totalTime">{{totalTime_s}}</td>
+ <td data-sort-value="{{medianSessionLength}}" class="col_medianSessionLength">{{medianSessionLength_s}}</td>
+ <td class="text-right col_longSessions">{{longSessions}}</td>
+ <td class="text-right col_shortSessions">{{shortSessions}}</td>
+ <td data-sort-value="{{totalOffTime}}" class="text-right col_totalOffTime">{{totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{totalOffTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalStandbyTime}}" class="text-right col_totalStandbyTime">{{totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{totalStandbyTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalSessionTime}}" class="text-right col_totalSessionTime">{{totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{totalSessionTime_p}}&thinsp;%</td>
+ <td data-sort-value="{{totalIdleTime}}" class="text-right col_totalIdleTime">{{totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{totalIdleTime_p}}&thinsp;%</td>
</tr>
{{/data}}
</tbody>
diff --git a/modules-available/statistics_reporting/templates/table-total.html b/modules-available/statistics_reporting/templates/table-total.html
index 8d5d7571..83078714 100644
--- a/modules-available/statistics_reporting/templates/table-total.html
+++ b/modules-available/statistics_reporting/templates/table-total.html
@@ -1,22 +1,36 @@
-<table id="table-total" class="table table-condensed table-striped stupidtable">
+<table id="table-total" class="table table-condensed table-striped table-nowrap stupidtable">
<thead>
<tr>
- <th class="text-left col-md-2"></th>
- <th class="text-left col_totalTime">{{lang_totalTime}}</th>
- <th class="text-left col_medianSessionLength">{{lang_medianSessionLength}}</th>
- <th class="text-left col_longSessions">{{lang_longSessions}}</th>
- <th class="text-left col_shortSessions">{{lang_shortSessions}}</th>
- <th class="text-left col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th></th>
+ <th class="col_totalTime">{{lang_totalTime}}</th>
+ <th class="col_medianSessionLength">{{lang_medianSessionLength}}</th>
+ <th class="col_longSessions">{{lang_longSessions}}</th>
+ <th class="col_shortSessions">{{lang_shortSessions}}</th>
+ <th class="col_totalOffTime">{{lang_totalOffTime}}</th>
+ <th class="col_totalOffTime">(%)</th>
+ <th class="col_totalStandbyTime">{{lang_totalStandbyTime}}</th>
+ <th class="col_totalStandbyTime">(%)</th>
+ <th class="col_totalSessionTime">{{lang_totalSessionTime}}</th>
+ <th class="col_totalSessionTime">(%)</th>
+ <th class="col_totalIdleTime">{{lang_totalIdleTime}}</th>
+ <th class="col_totalIdleTime">(%)</th>
</tr>
</thead>
<tbody>
<tr>
- <th class="text-left">{{lang_total}}</th>
- <td class="text-left col_totalTime">{{data.totalTime_s}}</td>
- <td class="text-left col_medianSessionLength">{{data.medianSessionLength_s}}</td>
- <td class="text-left col_longSessions">{{data.longSessions}}</td>
- <td class="text-left col_shortSessions">{{data.shortSessions}}</td>
- <td class="text-left col_totalOffTime">{{data.totalOffTime_s}}</td>
+ <td>{{lang_total}}</td>
+ <td class="col_totalTime">{{data.totalTime_s}}</td>
+ <td class="col_medianSessionLength">{{data.medianSessionLength_s}}</td>
+ <td class="col_longSessions">{{data.longSessions}}</td>
+ <td class="col_shortSessions">{{data.shortSessions}}</td>
+ <td class="text-right col_totalOffTime">{{data.totalOffTime_s}}</td>
+ <td class="col_totalOffTime p-addon">{{data.totalOffTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalStandbyTime">{{data.totalStandbyTime_s}}</td>
+ <td class="col_totalStandbyTime p-addon">{{data.totalStandbyTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalSessionTime">{{data.totalSessionTime_s}}</td>
+ <td class="col_totalSessionTime p-addon">{{data.totalSessionTime_p}}&thinsp;%</td>
+ <td class="text-right col_totalIdleTime">{{data.totalIdleTime_s}}</td>
+ <td class="col_totalIdleTime p-addon">{{data.totalIdleTime_p}}&thinsp;%</td>
</tr>
</tbody>
</table>
diff --git a/modules-available/sysconfig/lang/de/template-tags.json b/modules-available/sysconfig/lang/de/template-tags.json
index 9ad29e90..7f8511e6 100644
--- a/modules-available/sysconfig/lang/de/template-tags.json
+++ b/modules-available/sysconfig/lang/de/template-tags.json
@@ -82,7 +82,6 @@
"lang_noValidCert": "Der Server besitzt kein oder ein nicht valides Zertifikat.",
"lang_onProblemSearchBase": "Werden keine Benutzer gefunden, dann \u00fcberpr\u00fcfen Sie bitte die Suchbasis",
"lang_or": "oder",
- "lang_password": "Passwort",
"lang_rebuild": "Neu generieren",
"lang_rebuildLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist aktuell und sollte nicht neu generiert werden m\u00fcssen.",
"lang_rebuildOutdatedLong": "Modul oder Konfiguration neu generieren. Das entsprechende Modul bzw. Konfiguration ist veraltet oder nicht vorhanden.",
@@ -125,4 +124,4 @@
"lang_userDirectoryInfo1": "Optionale Angabe: Wenn die Clients f\u00fcr die Benutzer ein eigenes Verzeichnis (Homeverzeichnis, Benutzerverzeichnis) von einem Server einbinden sollen, geben Sie bitte hier das Format in UNC-Notation an, also z.B.",
"lang_userDirectoryInfo2": "%s ist dabei ein Platzhalter f\u00fcr den Login-Namen des Benutzers.",
"lang_userDirectoryInfo3": "Das Verzeichnis wird mit den gleichen Zugangsdaten eingebunden, die der Benutzer beim Login angibt. (D.h. kein Kerberos Support o.\u00e4.)"
-} \ 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 e2cfd114..5a73c254 100644
--- a/modules-available/sysconfig/lang/en/template-tags.json
+++ b/modules-available/sysconfig/lang/en/template-tags.json
@@ -82,7 +82,6 @@
"lang_noValidCert": "The server did not supply a certificate, or the certificate is invalid.",
"lang_onProblemSearchBase": "If no users are found, please check the search base",
"lang_or": "or",
- "lang_password": "Password",
"lang_rebuild": "Rebuild",
"lang_rebuildLong": "Rebuild module or configuration.",
"lang_rebuildOutdatedLong": "Rebuild module or configuration. The module\/configuration is outdated or missing and should be regenerated.",
@@ -125,4 +124,4 @@
"lang_userDirectoryInfo1": "Optional: If the clients should embed a separate directory (home directory, user directory) from a server for the user, please enter here the format in UNC notation, eg",
"lang_userDirectoryInfo2": "%s is a placeholder for the user's login name.",
"lang_userDirectoryInfo3": "The directory is loaded with the same credentials that the user specifies when login. (That is no Kerberos support, etc.)"
-} \ No newline at end of file
+}
diff --git a/modules-available/sysconfig/lang/pt/template-tags.json b/modules-available/sysconfig/lang/pt/template-tags.json
index 2384a891..abf8e075 100644
--- a/modules-available/sysconfig/lang/pt/template-tags.json
+++ b/modules-available/sysconfig/lang/pt/template-tags.json
@@ -17,7 +17,6 @@
"lang_newConfiguration": "Nova Configura\u00e7\u00e3o",
"lang_newModule": "Novo M\u00f3dulo",
"lang_noContent": "Sem conte\u00fado!",
- "lang_password": "Senha",
"lang_searchBase": "Base de Pesquisa",
"lang_show": "Mostrar",
"lang_systemConfiguration": "Confgura\u00e7\u00e3o do Sistema",
@@ -27,4 +26,4 @@
"lang_userDirectoryInfo1": "Opcional: Se os clientes devem incorporar um diret\u00f3rio separado (diret\u00f3rio home, diret\u00f3rio de usu\u00e1rio) de um servidor para o usu\u00e1rio, digite aqui o formato em nota\u00e7\u00e3o UNC, por exemplo,",
"lang_userDirectoryInfo2": "%s \u00e9 um marcador para o nome de login do usu\u00e1rio.",
"lang_userDirectoryInfo3": "O diret\u00f3rio \u00e9 carregado com as mesmas credenciais que o usu\u00e1rio especifica quando entra. (Isto \u00e9, n\u00e3o h\u00e1 suporte Kerberos, etc)"
-} \ No newline at end of file
+}
diff --git a/modules-available/syslog/hooks/statistics/machine-replace.inc.php b/modules-available/syslog/hooks/statistics/machine-replace.inc.php
new file mode 100644
index 00000000..6be0dd76
--- /dev/null
+++ b/modules-available/syslog/hooks/statistics/machine-replace.inc.php
@@ -0,0 +1,5 @@
+<?php
+
+foreach ($list as $entry) {
+ Database::exec('UPDATE IGNORE clientlog SET machineuuid = :new WHERE machineuuid = :old AND dateline < :datelimit', $entry);
+}
diff --git a/modules-available/syslog/page.inc.php b/modules-available/syslog/page.inc.php
index c679877a..153b591f 100644
--- a/modules-available/syslog/page.inc.php
+++ b/modules-available/syslog/page.inc.php
@@ -55,10 +55,10 @@ class Page_SysLog extends Page
else
$whereClause .= ' AND ';
- $whereClause .= "machineuuid='" . preg_replace('/[^0-9a-zA-Z\-]/', '', Request::get('machineuuid', '', 'string')) . "'";
+ $whereClause .= "machineuuid='" . preg_replace('/[^0-9a-zA-Z\-]/', '', Request::get('machineuuid', '', 'string')) . "'";
}
$lines = array();
- $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50);
+ $paginate = new Paginate("SELECT logid, dateline, logtypeid, clientip, machineuuid, description, extra FROM clientlog $whereClause ORDER BY logid DESC", 50);
$res = $paginate->exec();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$row['date'] = Util::prettyTime($row['dateline']);
diff --git a/modules-available/syslog/templates/page-syslog.html b/modules-available/syslog/templates/page-syslog.html
index 8b590038..d4709456 100644
--- a/modules-available/syslog/templates/page-syslog.html
+++ b/modules-available/syslog/templates/page-syslog.html
@@ -45,7 +45,7 @@
<tr>
<td><span class="type-button glyphicon {{icon}}" title="{{logtypeid}}"></span></td>
<td class="text-center" nowrap="nowrap">{{date}}</td>
- <td class="text-left">{{clientip}}</td>
+ <td class="text-left"><a href="?do=statistics&uuid={{machineuuid}}">{{clientip}}</a></td>
<td>{{description}}</td>
<td class="text-center">{{#extra}}
<a class="btn btn-default btn-xs" onclick="$('#details-body').html($('#extra-{{logid}}').html())"
diff --git a/modules-available/translation/templates/edit.html b/modules-available/translation/templates/edit.html
index 3c66aef6..7a41d9b4 100644
--- a/modules-available/translation/templates/edit.html
+++ b/modules-available/translation/templates/edit.html
@@ -5,7 +5,7 @@
</div>
<form action="?do=Translation" method="post" class="slx-visible-rows">
- <table id="moduleTable" class="table table-condensed table-hover stupidtable">
+ <table id="moduleTable" class="table table-condensed table-hover">
<thead>
<tr>
<th>{{lang_tag}}</th>
@@ -35,10 +35,10 @@
<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}}">
+ <input type="text" class="form-control switchable {{#missing}}txt-empty{{/missing}}" 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>
+ <textarea rows="3" class="form-control {{#missing}}txt-empty{{/missing}}" name="lang#!#{{tag}}" placeholder="{{placeholder}}">{{translation}}</textarea>
{{/big}}
</td>
@@ -77,90 +77,6 @@
</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}}">
- <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>
- <div class="hidden-xs col-sm-3 col-lg-4">{{lang_sample}}</div>
- <div class="col-xs-2 col-sm-2 col-lg-1">!</div>
- </div>
- {{#tags}}
- <div class="row">
- <div class="col-xs-4 col-sm-3">
- {{#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>
- <div class="col-xs-6 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}}
- </div>
- <div class="hidden-xs col-sm-3 col-lg-4">
- <div class="slx-textpreview"><div>
- <div class="badge">{{samplelang}}</div>
- {{sampletext}}
- </div></div>
- </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-trash"></span>
- <span class="hidden-xs">{{lang_delete}}</span>
- </button>
- </div>
- </div>
- {{/tags}}
- <div id="newTag"></div>
- <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>
-
-
-
-
-Function:
- $('#newTag').before(
- '<div class="row" id="new-delete-' + slxNewTagCounter + '">' +
- ' <div class="col-xs-4 col-sm-3">' +
- ' <input type="text" name="new-id[' + slxNewTagCounter + ']" class="form-control">' +
- ' </div>' +
- ' <div class="col-xs-6 col-sm-4">' +
- ' <input type="text" class="form-control" name="new-text[' + slxNewTagCounter + ']">' +
- ' </div>' +
- ' <div class="hidden-xs col-sm-4">' +
- ' </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()
@@ -204,4 +120,9 @@ Function:
ta.val(old.val());
old.replaceWith(ta);
}
+
+ document.addEventListener('DOMContentLoaded', function () {
+ $('.txt-empty').first().focus();
+ });
+
</script>
diff --git a/modules-available/usermanagement/lang/en/template-tags.json b/modules-available/usermanagement/lang/en/template-tags.json
index 66b30c6a..35497fc9 100644
--- a/modules-available/usermanagement/lang/en/template-tags.json
+++ b/modules-available/usermanagement/lang/en/template-tags.json
@@ -5,11 +5,9 @@
"lang_email": "Email",
"lang_login": "Login",
"lang_operations": "Operation",
- "lang_password": "Password",
"lang_remove": "Remove",
"lang_telephone": "Telephone",
"lang_userAdmin": "Administrator",
"lang_userInfo": "On this section, you will be able to create website users, besides editing or removing existing users.",
- "lang_userPage": "Users",
- "lang_username": "Username"
-} \ No newline at end of file
+ "lang_userPage": "Users"
+}
diff --git a/modules-available/usermanagement/lang/pt/template-tags.json b/modules-available/usermanagement/lang/pt/template-tags.json
index ad84126a..fefb441d 100644
--- a/modules-available/usermanagement/lang/pt/template-tags.json
+++ b/modules-available/usermanagement/lang/pt/template-tags.json
@@ -5,11 +5,9 @@
"lang_email": "Email",
"lang_login": "Login",
"lang_operations": "Opera\u00e7\u00f5es",
- "lang_password": "Senha",
"lang_remove": "Remover",
"lang_telephone": "Telefone",
"lang_userAdmin": "Administrador",
"lang_userInfo": "Nesta se\u00e7\u00e3o voc\u00ea poder\u00e1 criar usu\u00e1rios para o site, al\u00e9m de editar as informa\u00e7\u00f5es ou remover usu\u00e1rios existentes.",
- "lang_userPage": "Usu\u00e1rios",
- "lang_username": "Nome do Usu\u00e1rio"
-} \ No newline at end of file
+ "lang_userPage": "Usu\u00e1rios"
+}
diff --git a/modules-available/vmstore/lang/de/permissions.json b/modules-available/vmstore/lang/de/permissions.json
new file mode 100644
index 00000000..f3148ea8
--- /dev/null
+++ b/modules-available/vmstore/lang/de/permissions.json
@@ -0,0 +1,5 @@
+{
+ "choose.internal": "Internen Speicher auswählen.",
+ "choose.nfs": "Einen NFS Netzwerkspeicher auswählen.",
+ "choose.cifs": "Einen CIFS Netzwerkspeicher auswählen."
+} \ No newline at end of file
diff --git a/modules-available/vmstore/lang/de/template-tags.json b/modules-available/vmstore/lang/de/template-tags.json
index 16414809..0b56d84f 100644
--- a/modules-available/vmstore/lang/de/template-tags.json
+++ b/modules-available/vmstore/lang/de/template-tags.json
@@ -3,16 +3,15 @@
"lang_cifsHelp2": "Geben Sie f\u00fcr den Satellitenserver einen User mit Lese- und\r\nSchreibberechtigungen an. F\u00fcr die Clients sollte ein User angegeben\r\nwerden, der nur Leseberechtigungen auf dem Share besitzt. Am einfachsten\r\nerreichen Sie dies, indem Sie passwortlosen Gastzugriff mit Leserechten\r\nauf die Freigabe erlauben.",
"lang_cifsHelp3": "Wenn exklusiv DNBD3 verwendet wird, k\u00f6nnen Sie den passwortlosen\r\nGastzugriff deaktivieren und die Zeile \"Nur-Lese-Zugangsdaten\" leer\r\nlassen. Dies erh\u00f6ht die Sicherheit.",
"lang_configure": "Konfigurieren",
- "lang_intern": "Intern",
+ "lang_internal": "Intern",
"lang_nfsHelp1": "Ben\u00f6tigt wird ein NFSv4\/3-Share, der f\u00fcr den Satelliten-Server schreibbar, und f\u00fcr die Arbeitsstationen lesbar ist. Beispielkonfiguration auf dem NFS-Server, wenn der Satelliten-Server die Adresse 1.2.3.4 hat:",
"lang_nfsHelp2": "Alternative Konfiguration mittels all_squash. In diesem Fall muss das Verzeichnis auf dem Server dem Benutzer mit der uid 1234 geh\u00f6ren:",
"lang_nfsHelp3": "Die erste Zeile erlaubt den Lese- und Schreibzugriff des\r\nSatellitenservers. Die zweite Zeile erteilt allen anderen Rechnern\r\nausschlie\u00dflich Lesezugriff. Sie k\u00f6nnen dies nat\u00fcrlich auch auf\r\nspezielle Subnetze oder IP-Bereiche beschr\u00e4nken.",
"lang_nfsHelp4": "Wenn exklusiv DNBD3 verwendet wird, kann die zweite Zeile ausgelassen\r\nwerden. Dies erh\u00f6ht die Sicherheit.",
"lang_noAdditionalInformation": "Keine weitere Konfiguration notwendig",
- "lang_password": "Passwort",
+ "lang_path": "Pfad",
"lang_readOnly": "Nur-Lese-Zugangsdaten",
"lang_readWrite": "Lese\/Schreib-Zugangsdaten",
- "lang_username": "Benutzerkennung",
"lang_vmLocation": "VM Speicherort",
"lang_vmLocationChoose": "Bitte w\u00e4hlen Sie, wo die Images der Virtuellen Maschinen gespeichert werden sollen.",
"lang_vmLocationConfiguration": "VM Speicherort wird konfiguriert",
@@ -20,4 +19,4 @@
"lang_vmLocationHelp2": "Im Produktivbetrieb bietet es sich an, hierf\u00fcr einen performanten\r\nNetzwerkspeicher zu benutzen. Dieser Netzwerkspeicher kann per NFS oder\r\nCIFS\/SMB eingebunden werden. In jedem Fall muss sichergestellt werden,\r\ndass der Satellitenserver zum Hinzuf\u00fcgen neuer Virtueller Maschinen\r\nSchreibzugriff auf diesen Netzwerkspeicher hat. Bei der Nutzung von\r\nNFSv3 kann dies IP-Basiert eingerichtet werden, f\u00fcr die Nutzung von\r\nCIFS\/SMB k\u00f6nnen Sie Zugangsdaten angeben, die zum Schreiben\r\nberechtigen.",
"lang_vmLocationHelp3": "Im Fall von NFS und CIFS ben\u00f6tigen die bwLehrpool-Clients\r\nausschlie\u00dflich Lesezugriff auf den Netzwerkspeicher (und sollten aus\r\nSicherheitsgr\u00fcnden auch wirklich nur lesen k\u00f6nnen). Wenn Sie exklusiv\r\nDNBD3 verwenden, sind f\u00fcr die Clients keine Freigaben oder\r\nBerechtigungen notwendig, da der Zugriff \u00fcber den Sallitenserver\r\nstattfindet.",
"lang_vmLocationHelp4": "Sie k\u00f6nnen DNBD3 entweder exklusiv oder mit Fallback auf NFS\/CIFS verwenden. Im exklusiven Modus wird der Zugriff der Clients auf den kompletten VM-Speicher verhindert bzw. erschwert. Die Clients ben\u00f6tigen keinerlei Berechtigungen auf dem konfigurierten Share. Wenn Sie den Fallback Modus nutzen, werden f\u00fcr die Clients weiterhin Leseberechtigungen ben\u00f6tigt."
-} \ No newline at end of file
+}
diff --git a/modules-available/vmstore/lang/en/permissions.json b/modules-available/vmstore/lang/en/permissions.json
new file mode 100644
index 00000000..00708f7d
--- /dev/null
+++ b/modules-available/vmstore/lang/en/permissions.json
@@ -0,0 +1,5 @@
+{
+ "choose.internal": "Choose internal storage.",
+ "choose.nfs": "Choose a NFS network storage.",
+ "choose.cifs": "Choose a CIFS network storage."
+} \ 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 1072f10e..1e712b13 100644
--- a/modules-available/vmstore/lang/en/template-tags.json
+++ b/modules-available/vmstore/lang/en/template-tags.json
@@ -3,16 +3,15 @@
"lang_cifsHelp2": "Please provide user credentials with read\/write permissions which will be used by the server. For the clients, user credentials that allow read-only access is required. You could also enable passwordless guest login for read-only access.",
"lang_cifsHelp3": "If you want to use DNBD3 in exclusive mode, you can leave the read only credentials empty, to prevent people from browsing the share.",
"lang_configure": "Configure",
- "lang_intern": "Intern",
+ "lang_internal": "Internal",
"lang_nfsHelp1": "An NFSv4\/3-Share is required. It should be readable by all the workstations, and writable for the satellite server. An example, assuming the satellite server has IP address 1.2.3.4:",
"lang_nfsHelp2": "Alternate configuration using all_squash. The exported directory should be owned (and be writable) by the user with uid 1234.",
"lang_nfsHelp3": "The first line allows read\/write access for the satellite server. The second line grants read-only access for every other IP address. You could limit the second line to specific IP ranges only if desired.",
"lang_nfsHelp4": "If using DNBD3 in exclusive mode, you can remove the second line completely, so only the satellite server has access to the NFS store.",
"lang_noAdditionalInformation": "No additional cofiguration required",
- "lang_password": "Password",
+ "lang_path": "Path",
"lang_readOnly": "Read-only Access",
"lang_readWrite": "Read\/Write Access",
- "lang_username": "Username",
"lang_vmLocation": "VM Storage Location",
"lang_vmLocationChoose": "Please choose where the images of virtual machines will be stored.",
"lang_vmLocationConfiguration": "VM location is configured",
@@ -20,4 +19,4 @@
"lang_vmLocationHelp2": "In productive operation, it makes sense for this to use a high-performance network storage. This network storage can be integrated via NFS or CIFS \/ SMB. In any case, it must be ensured that the satellite server has write access to this network storage to add a new Virtual Machine . When using NFSv3 this can be set up IP-based, for the use of CIFS \/ SMB, you can access data disclosures that would entitle them to write.",
"lang_vmLocationHelp3": "The bwLehrpool clients only need read access to the network storage (and for security reasons, really can only read). In CIFS \/ SMB You can do this most easily by allowing passwordless guest access with read access to the share.",
"lang_vmLocationHelp4": "You can use DNBD3 exclusively or with NFS\/CIFS as fallback. In exclusive mode, you can deny direct access to the NFS\/CIFS store for clients, to prevent anyone from browsing the VM store directly. In fallback mode, the clients first try to use DNBD3 to run a VM and if that fails, they use NFS\/CIFS directly."
-} \ No newline at end of file
+}
diff --git a/modules-available/vmstore/lang/pt/template-tags.json b/modules-available/vmstore/lang/pt/template-tags.json
index 90ae64ad..8e0e4267 100644
--- a/modules-available/vmstore/lang/pt/template-tags.json
+++ b/modules-available/vmstore/lang/pt/template-tags.json
@@ -6,15 +6,13 @@
"lang_nfsHelp1": "\u00c9 necess\u00e1rio um NFSv3-Share. Ele deve poder ser lido por todas as esta\u00e7\u00f5es de trabalho, e poder ser escrito pelo server. Um exemplo, assumindo que o server possui endere\u00e7o ip 1.2.3.4:",
"lang_nfsHelp2": "Configura\u00e7\u00e3o alternativa usando all_squash. O usu\u00e1rio com uid 1234 deve possuir ( e poder escrever em ) o diret\u00f3rio exportado.",
"lang_noAdditionalInformation": "Nenhuma configura\u00e7\u00e3o adicional necess\u00e1ria",
- "lang_password": "Senha",
"lang_readOnly": "Acesso Somente Leitura",
"lang_readWrite": "Acesso Leitura\/Escrita",
"lang_save": "Salvar",
- "lang_username": "Nome de Usu\u00e1rio",
"lang_vmLocation": "Localiza\u00e7\u00e3o da VM",
"lang_vmLocationChoose": "Por favor, escolha aonde as imagens das m\u00e1quinas virtuais ser\u00e3o armazenadas.",
"lang_vmLocationConfiguration": "Localiza\u00e7\u00e3o da VM \u00e9 configurada",
"lang_vmLocationHelp1": "Para fins de teste, as VMs podem ser armazenados diretamente no servidor sat\u00e9lite. No entanto, se voc\u00ea operar o vmdk do sat\u00e9lite entregue por favor lembre-se que voc\u00ea tem apenas cerca de 100 GB de mem\u00f3ria.",
"lang_vmLocationHelp2": "Em opera\u00e7\u00e3o, faz sentido para este usar um armazenamento de rede de alto desempenho. Este armazenamento de rede pode ser integrado atrav\u00e9s de NFS ou CIFS \/ SMB. Em qualquer caso, deve-se assegurar de que o servidor de sat\u00e9lite tenha acesso de grava\u00e7\u00e3o para este armazenamento de rede para poder adicionar uma nova m\u00e1quina virtual. Ao utilizar NFSv3 este pode ser configurado com base em IP, para o uso de CIFS \/ SMB, voc\u00ea pode acessar as divulga\u00e7\u00f5es de dados que lhe permitiria escrever.",
"lang_vmLocationHelp3": "Os clientes bwLehrpool s\u00f3 precisam ter acesso de leitura ao armazenamento de rede (e por raz\u00f5es de seguran\u00e7a, realmente s\u00f3 pode ler). Em CIFS \/ SMB Voc\u00ea pode fazer isso mais facilmente, permitindo o acesso a visitantes sem senha com acesso de leitura."
-} \ No newline at end of file
+}
diff --git a/modules-available/vmstore/page.inc.php b/modules-available/vmstore/page.inc.php
index 3314bfe2..c3d5da77 100644
--- a/modules-available/vmstore/page.inc.php
+++ b/modules-available/vmstore/page.inc.php
@@ -8,7 +8,7 @@ class Page_VmStore extends Page
{
User::load();
- if (!User::hasPermission('superadmin')) {
+ if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
@@ -33,6 +33,10 @@ class Page_VmStore extends Page
if (isset($vmstore['storetype'])) {
$vmstore['pre-' . $vmstore['storetype']] = 'checked';
}
+ $vmstore['internalAllowed'] = User::hasPermission("choose.internal");
+ $vmstore['nfsAllowed'] = User::hasPermission("choose.nfs");
+ $vmstore['cifsAllowed'] = User::hasPermission("choose.cifs");
+ $vmstore['saveAllowed'] = $vmstore['internalAllowed'] || $vmstore['nfsAllowed'] || $vmstore['cifsAllowed'];
Render::addTemplate('page-vmstore', $vmstore);
}
@@ -47,6 +51,12 @@ class Page_VmStore extends Page
Message::addError('main.value-invalid', 'type', $storetype);
Util::redirect('?do=VmStore');
}
+ if (($storetype === 'internal' && !User::hasPermission("choose.internal")) ||
+ ($storetype === 'nfs' && !User::hasPermission("choose.nfs")) ||
+ ($storetype === 'cifs' && !User::hasPermission("choose.cifs"))) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=VmStore');
+ }
// Validate syntax of nfs/cifs
if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#is', $vmstore['nfsaddr'])) {
Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']);
diff --git a/modules-available/vmstore/permissions/permissions.json b/modules-available/vmstore/permissions/permissions.json
new file mode 100644
index 00000000..f2c22c72
--- /dev/null
+++ b/modules-available/vmstore/permissions/permissions.json
@@ -0,0 +1,5 @@
+[
+ "choose.internal",
+ "choose.nfs",
+ "choose.cifs"
+] \ No newline at end of file
diff --git a/modules-available/vmstore/style.css b/modules-available/vmstore/style.css
new file mode 100644
index 00000000..cc38e8ed
--- /dev/null
+++ b/modules-available/vmstore/style.css
@@ -0,0 +1,12 @@
+.disabledPanel {
+ cursor: not-allowed;
+}
+
+.disabledPanel > .panel-heading, .disabledPanel > .panel-body {
+ pointer-events: none;
+ opacity: 0.8;
+}
+
+.disabledPanel > .panel-heading > .btn {
+ pointer-events: initial;
+} \ No newline at end of file
diff --git a/modules-available/vmstore/templates/page-vmstore.html b/modules-available/vmstore/templates/page-vmstore.html
index 6156597b..fece8e24 100644
--- a/modules-available/vmstore/templates/page-vmstore.html
+++ b/modules-available/vmstore/templates/page-vmstore.html
@@ -9,11 +9,11 @@
<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 panel-default {{^internalAllowed}}disabledPanel{{/internalAllowed}}">
<div class="panel-heading">
<div class="radio">
<input type="radio" name="storetype" value="internal" {{pre-internal}} id="id-internal">
- <label for="id-internal">{{lang_intern}}</label>
+ <label for="id-internal">{{lang_internal}}</label>
</div>
</div>
<div class="panel-body">
@@ -21,7 +21,7 @@
</div>
</div>
- <div class="panel panel-default">
+ <div class="panel panel-default {{^nfsAllowed}}disabledPanel{{/nfsAllowed}}">
<div class="panel-heading">
<div class="radio radio-inline">
<input type="radio" name="storetype" value="nfs" {{pre-nfs}} id="id-nfs">
@@ -37,7 +37,7 @@
- <div class="panel panel-default">
+ <div class="panel panel-default {{^cifsAllowed}}disabledPanel{{/cifsAllowed}}">
<div class="panel-heading">
<div class="radio radio-inline">
<input type="radio" name="storetype" value="cifs" {{pre-cifs}} id="id-cifs">
@@ -46,7 +46,7 @@
<a class="btn btn-default btn-sm" data-toggle="modal" data-target="#help-cifs"><span class="glyphicon glyphicon-question-sign"></span></a>
</div>
<div class="panel-body">
- <label for="cifsaddr">UNC-Pfad</label>
+ <label for="cifsaddr">UNC-{{lang_path}}</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>
@@ -75,7 +75,7 @@
</div>
</div>
<div class="text-right">
- <button class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <button class="btn btn-primary" type="submit" {{^saveAllowed}}disabled{{/saveAllowed}}><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
</form>