summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules-available/baseconfig/inc/validator.inc.php17
-rw-r--r--modules-available/baseconfig/page.inc.php84
-rw-r--r--modules-available/baseconfig_bwlp/baseconfig/settings.json12
-rw-r--r--modules-available/dozmod/api.inc.php51
-rw-r--r--modules-available/exams/baseconfig/getconfig.inc.php5
-rw-r--r--modules-available/exams/inc/exams.inc.php7
-rw-r--r--modules-available/exams/lang/de/template-tags.json6
-rw-r--r--modules-available/exams/lang/en/template-tags.json6
-rw-r--r--modules-available/exams/page.inc.php32
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html62
-rw-r--r--modules-available/exams/templates/page-exams.html11
-rw-r--r--modules-available/statistics/inc/filter.inc.php394
-rw-r--r--modules-available/statistics/inc/filterset.inc.php58
-rw-r--r--modules-available/statistics/page.inc.php59
-rw-r--r--modules-available/statistics/templates/cpumodels.html8
-rw-r--r--modules-available/statistics/templates/filterbox.html76
-rw-r--r--modules-available/statistics/templates/id44.html7
-rw-r--r--modules-available/statistics/templates/kvmstate.html6
-rw-r--r--modules-available/statistics/templates/memory.html6
-rw-r--r--tools/README23
-rw-r--r--tools/convert-modules.php226
-rwxr-xr-xtools/move-lang-files.sh34
22 files changed, 874 insertions, 316 deletions
diff --git a/modules-available/baseconfig/inc/validator.inc.php b/modules-available/baseconfig/inc/validator.inc.php
index 00e8c0e0..ec7b95aa 100644
--- a/modules-available/baseconfig/inc/validator.inc.php
+++ b/modules-available/baseconfig/inc/validator.inc.php
@@ -30,7 +30,7 @@ class Validator
case 'function':
return self::$data[1]($displayValue);
case 'multilist':
- return self::validateList($data[1], $displayValue);
+ return self::validateMultiList($data[1], $displayValue);
case 'multiinput':
return self::validateMultiInput($data[1], $displayValue);
default:
@@ -86,12 +86,21 @@ class Validator
return $displayValue;
return false;
}
+ private static function validateMultiList($list, &$displayValue)
+ {
+ $allowedValues = explode('|', $list);
+ $values = [];
+ foreach ($displayValue as $v) {
+ if (in_array($v, $allowedValues)) {
+ $values[] = $v;
+ }
+ }
+ $displayValue = implode(' ', $values);
+ return $displayValue;
+ }
private static function validateMultiInput(&$list, &$displayValue)
{
return $displayValue;
- //die("validateMultiInput: " . print_r($list, true) . ", disp = " . $displayValue);
- //return implode('~,~', $displayValue);
- /* TODO: DO I have to do validation */
}
}
diff --git a/modules-available/baseconfig/page.inc.php b/modules-available/baseconfig/page.inc.php
index 41a7523f..389aa0ce 100644
--- a/modules-available/baseconfig/page.inc.php
+++ b/modules-available/baseconfig/page.inc.php
@@ -49,6 +49,9 @@ class Page_BaseConfig extends Page
Util::redirect('?do=BaseConfig');
}
}
+ //echo "<pre>";
+ //var_dump($_POST);
+ //echo "</pre>";
// Honor override/enabled checkbox
$override = Request::post('override', array());
// Load all existing config options to validate input
@@ -169,12 +172,25 @@ class Page_BaseConfig extends Page
if (!isset($settings[$var['catid']]['settings'][$key]['displayvalue'])) {
$settings[$var['catid']]['settings'][$key]['displayvalue'] = $var['defaultvalue'];
}
+ if (!isset($settings[$var['catid']]['settings'][$key]['shadows'])) {
+ $settings[$var['catid']]['settings'][$key]['shadows'] = null;
+ }
+ //echo "<pre>";
+ //var_dump($settings[$var['catid']]['settings'][$key]);
+ //echo "</pre>";
$settings[$var['catid']]['settings'][$key] += array(
- 'item' => $this->makeInput($var['validator'], $key, $settings[$var['catid']]['settings'][$key]['displayvalue'], $settings[$var['catid']]['settings'][$key]['shadows']
+ 'item' => $this->makeInput(
+ $var['validator'],
+ $key,
+ $settings[$var['catid']]['settings'][$key]['displayvalue'],
+ $settings[$var['catid']]['settings'][$key]['shadows']
),
'description' => Util::markup(Dictionary::translateFileModule($var['module'], 'config-variables', $key))
);
}
+ //die();
+
+
// Sort categories
$sortvals = array();
foreach ($settings as $catid => &$setting) {
@@ -247,46 +263,74 @@ class Page_BaseConfig extends Page
*/
private function makeInput($validator, $setting, $current, $shadows)
{
-
/* for the html snippet we need: */
$tag = 'input';
- $type = 'text';
- $shadowjs = empty($shadows) ? "" : " data-shadows=\"$shadows\"";
- $classes = "form-control";
- $extras= "";
+ $args = array('type' => 'text', 'class' => 'form-control', 'name' => "setting[$setting]", 'id' => $setting);
+ if (!empty($shadows)) {
+ $args['data-shadows'] = $shadows;
+ }
$inner = "";
/* -- */
$parts = explode(':', $validator, 2);
- if ($parts[0] === 'list' || $parts[0] == 'multilist') {
- $items = explode('|', $parts[1]);
- $multiple = $parts[0] == 'multilist';
- if ($multiple) {
- $extras = 'multiple ';
- $classes .= " multilist";
+ $items = explode('|', $parts[1]);
+ if ($parts[0] === 'list') {
+
+ foreach ($items as $item) {
+ if ($item === $current) {
+ $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
+ } else {
+ $inner .= "<option value=\"$item\"> $item </option>";
+ }
}
+
$tag = 'select';
+ unset($args['type']);
+ $current = '';
+
+ }
+ if ($parts[0] == 'multilist') {
+
+ $args['multiple'] = 'multiple';
+ $args['class'] .= " multilist";
+ $args['name'] .= '[]';
+
+ $selected = explode(' ', $current);
foreach ($items as $item) {
- if ($item === $current) {
- $inner .= '<option selected="selected">' . $item . '</option>';
+ if (in_array($item, $selected)) {
+ $inner .= "<option selected=\"selected\" value=\"$item\"> $item </option>";
} else {
- $inner .= '<option>' . $item . '</option>';
+ $inner .= "<option value=\"$item\"> $item </option>";
}
}
+ $tag = 'select';
+ unset($args['type']);
+ $current = '';
}
+
/* multiinput: enter multiple free-form strings*/
- if (strtolower($validator) == 'multiinput') {
- $classes .= " multiinput";
+ if ($validator === 'multiinput') {
+ $args['class'] .= " multiinput";
+ $args['value'] = $current;
}
/* Password field guessing */
if (stripos($validator, 'password') !== false) {
- $type = Property::getPasswordFieldType();
+ $args['type'] = Property::getPasswordFieldType();
+ }
+
+ $output = "<$tag ";
+ foreach ($args as $key => $val) {
+ $output .= "$key=\"" . htmlspecialchars($val) . '" ';
+ }
+ if (empty($inner)) {
+ $output .= '/>';
+ } else {
+ $output .= '>' . $inner . "</$tag>";
}
- return "<$tag type=\"$type\" id=\"$setting\" name=\"setting['$setting']\" $shadowjs $extras class=\"$classes\" value=\"$current\""
- . ($inner == "" ? "/>" : ">$inner </$tag>");
+ return $output;
}
}
diff --git a/modules-available/baseconfig_bwlp/baseconfig/settings.json b/modules-available/baseconfig_bwlp/baseconfig/settings.json
index dfde3a09..d10e2a9e 100644
--- a/modules-available/baseconfig_bwlp/baseconfig/settings.json
+++ b/modules-available/baseconfig_bwlp/baseconfig/settings.json
@@ -119,17 +119,23 @@
"permissions": "2",
"validator": "list:IGNORE|BUMP|EXCLUSIVE"
},
- "SLX_VMCHOOSER_DUMMYSETTING_ONE": {
+ "SLX_VMCHOOSER_DUMMYSETTING1": {
"catid": "vmchooser",
"defaultvalue": "",
"permissions": "2",
"validator": "list:ON|OFF",
- "shadows": "OFF -> SLX_LOGOUT_TIMEOUT, OFF -> SLX_VMCHOOSER_DUMMYSETTING_TWO"
+ "shadows": "OFF -> SLX_LOGOUT_TIMEOUT, OFF -> SLX_VMCHOOSER_DUMMYSETTING2"
},
- "SLX_VMCHOOSER_DUMMYSETTING_TWO": {
+ "SLX_VMCHOOSER_DUMMYSETTING2": {
"catid": "vmchooser",
"defaultvalue": "",
"permissions": "2",
"validator": "multilist:Apples|Bananas|Pears"
+ },
+ "SLX_VMCHOOSER_DUMMYSETTING3": {
+ "catid": "vmchooser",
+ "defaultvalue": "",
+ "permissions": "2",
+ "validator": "multiinput"
}
}
diff --git a/modules-available/dozmod/api.inc.php b/modules-available/dozmod/api.inc.php
index 17ead3c1..dc9788fc 100644
--- a/modules-available/dozmod/api.inc.php
+++ b/modules-available/dozmod/api.inc.php
@@ -17,7 +17,7 @@ if (!Module::isAvailable('locations')) {
define('LIST_URL', CONFIG_DOZMOD_URL . '/vmchooser/list');
define('VMX_URL', CONFIG_DOZMOD_URL . '/vmchooser/lecture');
-$availableRessources = ['list', 'vmx', 'test', 'netrules'];
+$availableRessources = ['list', 'vmx', 'test', 'netrules', 'runscript'];
/* BEGIN: A simple caching mechanism ---------------------------- */
@@ -148,9 +148,12 @@ function outputLectureXmlForLocation($locationIds)
return getListForLocations($locationIds, true);
}
-function _getVMX($lecture_uuid)
+function _getVmData($lecture_uuid, $subResource = false)
{
$url = VMX_URL . '/' . $lecture_uuid;
+ if ($subResource !== false) {
+ $url .= '/' . $subResource;
+ }
$response = Download::asString($url, 60, $code);
return $response;
}
@@ -162,7 +165,35 @@ function outputVMX($lecture_uuid)
if (cache_has($key)) {
cache_get_passthru($key);
} else {
- $value = _getVMX($lecture_uuid);
+ $value = _getVmData($lecture_uuid);
+ if ($value === false)
+ return false;
+ cache_put($key, $value);
+ die($value);
+ }
+}
+
+function outputNetrules($lecture_uuid)
+{
+ $key = 'netrules_' . $lecture_uuid;
+ if (cache_has($key)) {
+ cache_get_passthru($key);
+ } else {
+ $value = _getVmData($lecture_uuid, 'netrules');
+ if ($value === false)
+ return false;
+ cache_put($key, $value);
+ die($value);
+ }
+}
+
+function outputRunscript($lecture_uuid)
+{
+ $key = 'netrules_' . $lecture_uuid;
+ if (cache_has($key)) {
+ cache_get_passthru($key);
+ } else {
+ $value = _getVmData($lecture_uuid, 'runscript');
if ($value === false)
return false;
cache_put($key, $value);
@@ -226,6 +257,20 @@ if ($resource === 'vmx') {
fatalDozmodUnreachable();
}
+if ($resource === 'netrules') {
+ $lecture = readLectureParam();
+ outputNetrules($lecture);
+ // no return on success
+ fatalDozmodUnreachable();
+}
+
+if ($resource === 'runscript') {
+ $lecture = readLectureParam();
+ outputRunscript($lecture);
+ // no return on success
+ fatalDozmodUnreachable();
+}
+
if ($resource === 'list') {
outputLectureXmlForLocation($location_ids);
// Won't return on success...
diff --git a/modules-available/exams/baseconfig/getconfig.inc.php b/modules-available/exams/baseconfig/getconfig.inc.php
index d26a20a9..2776d3a8 100644
--- a/modules-available/exams/baseconfig/getconfig.inc.php
+++ b/modules-available/exams/baseconfig/getconfig.inc.php
@@ -2,7 +2,10 @@
if (isset($configVars["SLX_LOCATIONS"])) {
$locationIds = explode(' ', $configVars["SLX_LOCATIONS"]);
- if (Exams::isInExamMode($locationIds)) {
+ if (Exams::isInExamMode($locationIds, $lectureId)) {
$configVars['SLX_EXAM'] = 'yes';
+ if (strlen($lectureId) > 0) {
+ $configVars['SLX_EXAM_START'] = $lectureId;
+ }
}
}
diff --git a/modules-available/exams/inc/exams.inc.php b/modules-available/exams/inc/exams.inc.php
index f781fc1e..e95a9392 100644
--- a/modules-available/exams/inc/exams.inc.php
+++ b/modules-available/exams/inc/exams.inc.php
@@ -7,7 +7,7 @@ class Exams
* @param int[] of location ids. must bot be an associative array.
* @return: bool true iff for any of the given location ids an exam is scheduled.
**/
- public static function isInExamMode($locationIds)
+ public static function isInExamMode($locationIds, &$lectureId)
{
if (!is_array($locationIds)) {
$locationIds = array($locationIds);
@@ -15,9 +15,12 @@ class Exams
return false;
}
$l = str_repeat(',?', count($locationIds));
- $res = Database::queryFirst("SELECT examid FROM exams"
+ $res = Database::queryFirst("SELECT lectureid FROM exams"
. " INNER JOIN exams_x_location USING (examid)"
. " WHERE UNIX_TIMESTAMP() BETWEEN starttime AND endtime AND locationid IN (0$l) LIMIT 1", $locationIds);
+ if ($res !== false) {
+ $lectureId = $res['lectureid'];
+ }
return $res !== false;
}
diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json
index 797cc371..22f777ad 100644
--- a/modules-available/exams/lang/de/template-tags.json
+++ b/modules-available/exams/lang/de/template-tags.json
@@ -3,6 +3,9 @@
"lang_addExam": "Zeitraum hinzuf\u00fcgen",
"lang_addingBasedOnLecture": "F\u00fcge neuen Pr\u00fcfungszeitraum basierend auf vorhandener Veranstaltung an",
"lang_allExamPeriods": "Alle Pr\u00fcfungszeitr\u00e4ume",
+ "lang_autoStartInfo": "W\u00e4hlen Sie hier optional eine Veranstaltung, die automatisch nach dem Booten der Rechner auf den Clients gestartet werden soll. Die Studierenden sehen keine Loginmaske und keinen vmChooser. Dadurch ist der Benutzer nicht angemeldet, und es steht u.a. kein Home-Verzeichnis zur Verf\u00fcgung. Diese Funktion eignet sich somit nur, wenn in der Pr\u00fcfung keine Autentifizierung des Benutzers notwendig ist, oder diese anderweitig innerhalb der VM-Sitzung umgesetzt wird, z.B. indem sich der Pr\u00fcfling gesondert auf einer Lernplattform im Browser anmeldet. Beachten Sie au\u00dferdem, dass die gew\u00e4hlte Veranstaltung zum Pr\u00fcfungszeitraum durch den Dozenten aktiviert sein muss. Wird keine Veranstaltung f\u00fcr den Autostart ausgew\u00e4hlt, sieht der Pr\u00fcfling nach wie vor eine Anmeldemaske und anschlie\u00dfend den vmChooser, in dem dann jedoch ausschlie\u00dflich als Pr\u00fcfung markierte Veranstaltungen aufgelistet werden.",
+ "lang_autoStartLecture": "Automatisch zu startende Veranstaltung",
+ "lang_autostart": "Autostart-Veranstaltung",
"lang_begin": "Beginn",
"lang_begin_date": "Beginn Datum",
"lang_begin_time": "Uhrzeit",
@@ -20,8 +23,11 @@
"lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus",
"lang_id": "ID",
"lang_lectureName": "Veranstaltungsname",
+ "lang_lectureOutOfRange": "Achtung: Start- bzw. Endzeitpunkt der Veranstaltung liegen au\u00dferhalb des oben angegebenen Zeitraums",
"lang_location": "Raum\/Ort",
+ "lang_locationInfo": "W\u00e4hlen Sie hier die R\u00e4ume und Orte aus, die w\u00e4hrend des unten ausgew\u00e4hlten Zeitraums in den Pr\u00fcfungsmodus versetzt werden. Wenn sie hier keine Auswahl treffen, werden alle R\u00e4ume in den Pr\u00fcfungsmodus versetzt.",
"lang_locations": "R\u00e4ume\/Orte",
"lang_noDescription": "Keine Beschreibung",
+ "lang_none": "(Keine)",
"lang_timeFrame": "Zeitraum"
} \ No newline at end of file
diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json
index 2e5abeff..b5ab87ae 100644
--- a/modules-available/exams/lang/en/template-tags.json
+++ b/modules-available/exams/lang/en/template-tags.json
@@ -3,6 +3,9 @@
"lang_addExam": "Add exam period",
"lang_addingBasedOnLecture": "Adding exam period based on lecture",
"lang_allExamPeriods": "All exam periods",
+ "lang_autoStartInfo": "Here you can select a course that will be launched automatically after the client finishes booting up. This will skip the login prompt and vmChooser screen, and directly start the selected couse. In this case, no personalization like mapping of home directories can happen in the VM, so this is mostly useful if the exam relies on other means of authentication, e.g. logging in to an LMS system where results need to be submitted.",
+ "lang_autoStartLecture": "Automatically launched course",
+ "lang_autostart": "Autorun lecture",
"lang_begin": "Begin",
"lang_begin_date": "Begin Date",
"lang_begin_time": "Time",
@@ -20,8 +23,11 @@
"lang_headingMain": "bwLehrpool exam mode",
"lang_id": "ID",
"lang_lectureName": "Lecture name",
+ "lang_lectureOutOfRange": "Hint: Start or end date of given lecture lies outside of exam period given above",
"lang_location": "Room\/Location",
+ "lang_locationInfo": "Select the rooms and locations you want to enable the exam mode in. Selecting nothing at all means that all clients will boot into exam mode during the given time period.",
"lang_locations": "Rooms\/Locations",
"lang_noDescription": "No description",
+ "lang_none": "(None)",
"lang_timeFrame": "Time frame"
} \ No newline at end of file
diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php
index d6b1ccea..e70fc3c7 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -30,9 +30,12 @@ class Page_Exams extends Page
protected function readExams()
{
- $tmp = Database::simpleQuery("SELECT examid, starttime, endtime, description, GROUP_CONCAT(locationid) AS locationids, "
- . "GROUP_CONCAT(locationname SEPARATOR ', ') AS locationnames FROM exams "
- . "NATURAL LEFT JOIN exams_x_location NATURAL LEFT JOIN location GROUP BY examid");
+ $tmp = Database::simpleQuery("SELECT e.examid, l.displayname AS lecturename, e.starttime, e.endtime, e.description, GROUP_CONCAT(exl.locationid) AS locationids, "
+ . "GROUP_CONCAT(loc.locationname SEPARATOR ', ') AS locationnames FROM exams e "
+ . "NATURAL LEFT JOIN exams_x_location exl "
+ . "NATURAL LEFT JOIN location loc "
+ . "LEFT JOIN sat.lecture l USING (lectureid) "
+ . "GROUP BY examid ");
while ($exam = $tmp->fetch(PDO::FETCH_ASSOC)) {
$this->exams[] = $exam;
}
@@ -211,7 +214,8 @@ class Page_Exams extends Page
$examid = Request::post('examid', 0, 'int');
$starttime = strtotime(Request::post('starttime_date') . " " . Request::post('starttime_time'));
$endtime = strtotime(Request::post('endtime_date') . " " . Request::post('endtime_time'));
- $description = Request::post('description');
+ $description = Request::post('description', '', 'string');
+ $lectureid = Request::post('lectureid', '', 'string');
if (!$this->isDateSane($starttime)) {
Message::addError('starttime-invalid', Request::post('starttime_date') . " " . Request::post('starttime_time'));
Util::redirect('?do=exams');
@@ -227,8 +231,8 @@ class Page_Exams extends Page
if ($examid === 0) {
// No examid given, is add
- $res = Database::exec("INSERT INTO exams(starttime, endtime, description) VALUES(:starttime, :endtime, :description);",
- compact('starttime', 'endtime', 'description')) !== false;
+ $res = Database::exec("INSERT INTO exams(lectureid, starttime, endtime, description) VALUES(:lectureid, :starttime, :endtime, :description);",
+ compact('lectureid', 'starttime', 'endtime', 'description')) !== false;
$exam_id = Database::lastInsertId();
foreach ($locationids as $lid) {
@@ -251,8 +255,8 @@ class Page_Exams extends Page
}
/* update fields */
- $res = Database::exec("UPDATE exams SET starttime = :starttime, endtime = :endtime, description = :description WHERE examid = :examid",
- compact('starttime', 'endtime', 'description', 'examid')) !== false;
+ $res = Database::exec("UPDATE exams SET lectureid = :lectureid, starttime = :starttime, endtime = :endtime, description = :description WHERE examid = :examid",
+ compact('lectureid', 'starttime', 'endtime', 'description', 'examid')) !== false;
/* drop all connections and reconnect to rooms */
$res = $res && Database::exec("DELETE FROM exams_x_location WHERE examid = :examid", compact('examid')) !== false;
/* reconnect */
@@ -369,14 +373,17 @@ class Page_Exams extends Page
$baseLecture = Request::any('lectureid', false, 'string');
$locations = null;
if ($baseLecture !== false) {
- foreach ($this->lectures as $lecture) {
+ foreach ($this->lectures as &$lecture) {
if ($lecture['lectureid'] === $baseLecture) {
$data['exam'] = $this->makeEditFromArray($lecture);
$locations = explode(',', $lecture['lids']);
+ $lecture['selected'] = 'selected';
break;
}
}
+ unset($lecture);
}
+ $data['lectures'] = $this->lectures;
$this->readLocations($locations);
$data['locations'] = $this->locations;
Render::addTemplate('page-add-edit-exam', $data);
@@ -385,7 +392,12 @@ class Page_Exams extends Page
Render::setTitle(Dictionary::translate('title_edit-exam'));
$exam = $this->makeEditFromArray($this->currentExam);
- Render::addTemplate('page-add-edit-exam', ['exam' => $exam, 'locations' => $this->locations]);
+ foreach ($this->lectures as &$lecture) {
+ if ($lecture['lectureid'] === $this->currentExam['lectureid']) {
+ $lecture['selected'] = 'selected';
+ }
+ }
+ Render::addTemplate('page-add-edit-exam', ['exam' => $exam, 'locations' => $this->locations, 'lectures' => $this->lectures]);
}
}
diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html
index 3f0ef372..744aad29 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -12,6 +12,7 @@
<div class="form-group">
<div>
<label for="locations">{{lang_location}}</label>
+ <p><i>{{lang_locationInfo}}</i></p>
</div>
<select id="locations" multiple name="locations[]">
{{#locations}}
@@ -76,6 +77,27 @@
<div class="row form-group">
<div class="form-group col-xs-12">
+ <label for="description">{{lang_autoStartLecture}}</label>
+ <p><i>{{lang_autoStartInfo}}</i></p>
+ <div class="input-group">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-pencil"></span>
+ </span>
+ <select class="form-control" id="lecturelist" name="lectureid">
+ <option value="">{{lang_none}}</option>
+ {{#lectures}}
+ <option data-from="{{starttime}}" data-to="{{endtime}}" value="{{lectureid}}" {{selected}} >{{displayname}}</option>
+ {{/lectures}}
+ </select>
+ </div>
+ </div>
+ <div class="col-xs-12" id="lecture-info">
+ bla
+ </div>
+ </div>
+
+ <div class="row form-group">
+ <div class="form-group col-xs-12">
<label for="description">{{lang_description}}</label>
<textarea class="form-control" type="textarea" name="description" id="description">{{exam.description}}</textarea>
</div>
@@ -112,7 +134,7 @@ document.addEventListener("DOMContentLoaded", function () {
$('.datepicker').datepicker(dateSettings);
$('.timepicker2').timepicker(timeSettings);
- showDuration();
+ startEndChanged();
});
$('#locations').multiselect({numberDisplayed: 1});
@@ -137,7 +159,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
- var showDuration = function () {
+ var startEndChanged = function () {
var sd = slxMoment(start_date.val() + ' ' + start_time.val(), 'YYYY-MM-DD H:mm');
var ed = slxMoment(end_date.val() + ' ' + end_time.val(), 'YYYY-MM-DD H:mm');
if (!sd.isValid() || !ed.isValid()) {
@@ -145,12 +167,40 @@ document.addEventListener("DOMContentLoaded", function () {
return;
}
rspan.text(slxMoment.duration(ed.diff(sd)).humanize());
+ // Lecture selection
+ $('#lecturelist option').each(function (idx, elem) {
+ var e = $(elem);
+ var from = e.data('from');
+ var to = e.data('to');
+ if (!from || !to)
+ return;
+ from = slxMoment(from);
+ to = slxMoment(to);
+ if (from.isBefore(sd) || to.isAfter(ed)) {
+ e.css('color', '#999');
+ e.data('inrange', false)
+ } else {
+ e.css('color', '');
+ e.data('inrange', true);
+ }
+ });
+ updateLectureInfo();
+ }
+
+ var updateLectureInfo = function() {
+ var sel = $('#lecturelist option:selected');
+ if (sel.val() === '' || sel.data('inrange')) {
+ $('#lecture-info').text('-');
+ } else {
+ $('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment(sel.data('from')).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment(sel.data('to')).format('YYYY-MM-DD H:mm') + ')');
+ }
}
- start_date.change(showDuration);
- start_time.change(showDuration);
- end_date.change(showDuration);
- end_time.change(showDuration);
+ start_date.change(startEndChanged);
+ start_time.change(startEndChanged);
+ end_date.change(startEndChanged);
+ end_time.change(startEndChanged);
+ $('#lecturelist').change(updateLectureInfo);
}, false);
// --></script>
diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html
index 184a69e3..fc88e4f4 100644
--- a/modules-available/exams/templates/page-exams.html
+++ b/modules-available/exams/templates/page-exams.html
@@ -17,6 +17,11 @@
{{^locationnames}}
<i>{{lang_global}}</i>
{{/locationnames}}
+ {{#lecturename}}
+ <div>
+ <b>{{lang_autostart}}</b>: {{lecturename}}
+ </div>
+ {{/lecturename}}
<div class="small">
{{description}}
{{^description}}
@@ -24,9 +29,9 @@
{{/description}}
</div>
</td>
- <td class="slx-nowrap">{{starttime_s}}</td>
- <td class="slx-nowrap">{{endtime_s}}</td>
- <td class="slx-nowrap">
+ <td class="text-nowrap">{{starttime_s}}</td>
+ <td class="text-nowrap">{{endtime_s}}</td>
+ <td class="text-nowrap text-right">
<form method="POST" action="?do=exams&action=delete" {{^liesInPast}}onsubmit="return confirm('{{lang_deleteConfirmation}}');"{{/liesInPast}} >
{{^liesInPast}}
<a onclick="slxShow({{starttime}}, {{endtime}})" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-eye-open"></span></a>
diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php
index a3f1cbb5..ecf222e9 100644
--- a/modules-available/statistics/inc/filter.inc.php
+++ b/modules-available/statistics/inc/filter.inc.php
@@ -2,209 +2,235 @@
/* base class with rudimentary SQL generation abilities.
* WARNING: argument is escaped, but $column and $operator are passed unfiltered into SQL */
+
class Filter
{
- /**
- * Delimiter for js_selectize filters
- */
- const DELIMITER = '~,~';
-
- public $column;
- public $operator;
- public $argument;
-
- public function __construct($column, $operator, $argument = null)
- {
- $this->column = trim($column);
- $this->operator = trim($operator);
- $this->argument = trim($argument);
- }
- /* returns a where clause and adds needed operators to the passed array */
- public function whereClause(&$args, &$joins)
- {
- global $unique_key;
- $key = $this->column.'_arg' . ($unique_key++);
-
- /* check if we have to do some parsing*/
- if (Page_Statistics::$columns[$this->column]['type'] == 'date') {
- $args[$key] = strtotime($this->argument);
- } else {
- $args[$key] = $this->argument;
- }
-
- $op = $this->operator;
- if ($this->operator == '~') {
- $op = 'LIKE';
- } elseif ($this->operator == '!~') {
- $op = 'NOT LIKE';
- }
-
- return $this->column.' '.$op.' :'.$key;
- }
- /* parse a query into an array of filters */
- public static function parseQuery($query)
- {
- $operators = ['<=', '>=', '!=', '!~', '=', '~', '<', '>'];
- $filters = [];
- foreach (explode(self::DELIMITER, $query) as $q) {
- $q = trim($q);
- /* find position of first operator */
- $pos = 10000;
- $operator = false;
- foreach ($operators as $op) {
- $newpos = strpos($q, $op);
- if ($newpos > -1 && ($newpos < $pos)) {
- $pos = $newpos;
- $operator = $op;
- }
- }
- if ($pos == 10000) {
- error_log("couldn't find operator in segment ".$q);
- /* TODO */
- continue;
- }
- $lhs = trim(substr($q, 0, $pos));
- $rhs = trim(substr($q, $pos + strlen($operator)));
-
- if ($lhs == 'gbram') {
- $filters[] = new RamGbFilter($operator, $rhs);
- } elseif ($lhs == 'state') {
- error_log('new state filter with ' . $rhs);
- $filters[] = new StateFilter($operator, $rhs);
- } elseif ($lhs == 'hddgb') {
- $filters[] = new Id44Filter($operator, $rhs);
- } elseif ($lhs == 'location') {
- $filters[] = new LocationFilter($operator, $rhs);
- } elseif ($lhs == 'subnet') {
- $filters[] = new SubnetFilter($operator, $rhs);
- } else {
- if (array_key_exists($lhs, Page_Statistics::$columns) && Page_Statistics::$columns[$lhs]['column']) {
- $filters[] = new Filter($lhs, $operator, $rhs);
- } else {
- Message::addError('invalid-filter-key', $lhs);
- }
- }
- }
-
- return $filters;
- }
+ /**
+ * Delimiter for js_selectize filters
+ */
+ const DELIMITER = '~,~';
+
+ public $column;
+ public $operator;
+ public $argument;
+
+ public function __construct($column, $operator, $argument = null)
+ {
+ $this->column = trim($column);
+ $this->operator = trim($operator);
+ $this->argument = trim($argument);
+ }
+
+ /* returns a where clause and adds needed operators to the passed array */
+ public function whereClause(&$args, &$joins)
+ {
+ global $unique_key;
+ $key = $this->column . '_arg' . ($unique_key++);
+
+ /* check if we have to do some parsing*/
+ if (Page_Statistics::$columns[$this->column]['type'] == 'date') {
+ $args[$key] = strtotime($this->argument);
+ } else {
+ $args[$key] = $this->argument;
+ }
+
+ $op = $this->operator;
+ if ($this->operator == '~') {
+ $op = 'LIKE';
+ } elseif ($this->operator == '!~') {
+ $op = 'NOT LIKE';
+ }
+
+ return $this->column . ' ' . $op . ' :' . $key;
+ }
+
+ /* parse a query into an array of filters */
+ public static function parseQuery($query)
+ {
+ $operators = ['<=', '>=', '!=', '!~', '=', '~', '<', '>'];
+ $filters = [];
+ if (empty($query))
+ return $filters;
+ foreach (explode(self::DELIMITER, $query) as $q) {
+ $q = trim($q);
+ if (empty($q))
+ continue;
+ // Special case: User pasted UUID, turn into filter
+ if (preg_match('/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/', $q)) {
+ $filters[] = new Filter('machineuuid', '=', $q);
+ continue;
+ }
+ // Special case: User pasted IP, turn into filter
+ if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $q)) {
+ $filters[] = new Filter('clientip', '=', $q);
+ continue;
+ }
+ /* find position of first operator */
+ $pos = 10000;
+ $operator = false;
+ foreach ($operators as $op) {
+ $newpos = strpos($q, $op);
+ if ($newpos > -1 && ($newpos < $pos)) {
+ $pos = $newpos;
+ $operator = $op;
+ }
+ }
+ if ($pos == 10000) {
+ error_log("couldn't find operator in segment " . $q);
+ /* TODO */
+ continue;
+ }
+ $lhs = trim(substr($q, 0, $pos));
+ $rhs = trim(substr($q, $pos + strlen($operator)));
+
+ if ($lhs == 'gbram') {
+ $filters[] = new RamGbFilter($operator, $rhs);
+ } elseif ($lhs == 'state') {
+ error_log('new state filter with ' . $rhs);
+ $filters[] = new StateFilter($operator, $rhs);
+ } elseif ($lhs == 'hddgb') {
+ $filters[] = new Id44Filter($operator, $rhs);
+ } elseif ($lhs == 'location') {
+ $filters[] = new LocationFilter($operator, $rhs);
+ } elseif ($lhs == 'subnet') {
+ $filters[] = new SubnetFilter($operator, $rhs);
+ } else {
+ if (array_key_exists($lhs, Page_Statistics::$columns) && Page_Statistics::$columns[$lhs]['column']) {
+ $filters[] = new Filter($lhs, $operator, $rhs);
+ } else {
+ Message::addError('invalid-filter-key', $lhs);
+ }
+ }
+ }
+
+ return $filters;
+ }
}
class RamGbFilter extends Filter
{
- public function __construct($operator, $argument)
- {
- parent::__construct('mbram', $operator, $argument);
- }
- public function whereClause(&$args, &$joins)
- {
- global $SIZE_RAM;
- $lower = floor(Page_Statistics::findBestValue($SIZE_RAM, (int) $this->argument, false) * 1024 - 100);
- $upper = ceil(Page_Statistics::findBestValue($SIZE_RAM, (int) $this->argument, true) * 1024 + 100);
- if ($this->operator == '=') {
- return " mbram BETWEEN $lower AND $upper";
- } elseif ($this->operator == '<') {
- return " mbram < $lower";
- } elseif ($this->operator == '<=') {
- return " mbram <= $upper";
- } elseif ($this->operator == '>') {
- return " mbram > $upper";
- } elseif ($this->operator == '>=') {
- return " mbram >= $lower";
- } elseif ($this->operator == '!=') {
- return " (mbram < $lower OR mbram > $upper)";
- } else {
- error_log("unimplemented operator in RamGbFilter: $this->operator");
-
- return ' 1';
- }
- }
+ public function __construct($operator, $argument)
+ {
+ parent::__construct('mbram', $operator, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ global $SIZE_RAM;
+ $lower = floor(Page_Statistics::findBestValue($SIZE_RAM, (int)$this->argument, false) * 1024 - 100);
+ $upper = ceil(Page_Statistics::findBestValue($SIZE_RAM, (int)$this->argument, true) * 1024 + 100);
+ if ($this->operator == '=') {
+ return " mbram BETWEEN $lower AND $upper";
+ } elseif ($this->operator == '<') {
+ return " mbram < $lower";
+ } elseif ($this->operator == '<=') {
+ return " mbram <= $upper";
+ } elseif ($this->operator == '>') {
+ return " mbram > $upper";
+ } elseif ($this->operator == '>=') {
+ return " mbram >= $lower";
+ } elseif ($this->operator == '!=') {
+ return " (mbram < $lower OR mbram > $upper)";
+ } else {
+ error_log("unimplemented operator in RamGbFilter: $this->operator");
+
+ return ' 1';
+ }
+ }
}
+
class Id44Filter extends Filter
{
- public function __construct($operator, $argument)
- {
- parent::__construct('id44mb', $operator, $argument);
- }
- public function whereClause(&$args, &$joins)
- {
- global $SIZE_ID44;
- $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100);
- $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100);
-
- if ($this->operator == '=') {
- return " id44mb BETWEEN $lower AND $upper";
- } elseif ($this->operator == '!=') {
- return " id44mb < $lower OR id44mb > $upper";
- } elseif ($this->operator == '<=') {
- return " id44mb < $upper";
- } elseif ($this->operator == '>=') {
- return " id44mb > $lower";
- } elseif ($this->operator == '<') {
- return " id44mb < $lower";
- } elseif ($this->operator == '>') {
- return " id44mb > $upper";
- } else {
- error_log("unimplemented operator in Id44Filter: $this->operator");
-
- return ' 1';
- }
- }
+ public function __construct($operator, $argument)
+ {
+ parent::__construct('id44mb', $operator, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ global $SIZE_ID44;
+ $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100);
+ $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100);
+
+ if ($this->operator == '=') {
+ return " id44mb BETWEEN $lower AND $upper";
+ } elseif ($this->operator == '!=') {
+ return " id44mb < $lower OR id44mb > $upper";
+ } elseif ($this->operator == '<=') {
+ return " id44mb < $upper";
+ } elseif ($this->operator == '>=') {
+ return " id44mb > $lower";
+ } elseif ($this->operator == '<') {
+ return " id44mb < $lower";
+ } elseif ($this->operator == '>') {
+ return " id44mb > $upper";
+ } else {
+ error_log("unimplemented operator in Id44Filter: $this->operator");
+
+ return ' 1';
+ }
+ }
}
+
class StateFilter extends Filter
{
- public function __construct($operator, $argument)
- {
- $this->operator = $operator;
- $this->argument = $argument;
- }
-
- public function whereClause(&$args, &$joins)
- {
- $neg = $this->operator == '!=' ? 'NOT ' : '';
- if ($this->argument === 'on') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() ) ";
- } elseif ($this->argument === 'off') {
- return " $neg (lastseen + 600 < UNIX_TIMESTAMP() ) ";
- } elseif ($this->argument === 'idle') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ) ";
- } elseif ($this->argument === 'occupied') {
- return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ) ";
- } else {
- Message::addError('invalid-filter-argument', 'state', $this->argument);
- return ' 1';
- }
- }
+ public function __construct($operator, $argument)
+ {
+ $this->operator = $operator;
+ $this->argument = $argument;
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ $neg = $this->operator == '!=' ? 'NOT ' : '';
+ if ($this->argument === 'on') {
+ return " $neg (lastseen + 600 > UNIX_TIMESTAMP() ) ";
+ } elseif ($this->argument === 'off') {
+ return " $neg (lastseen + 600 < UNIX_TIMESTAMP() ) ";
+ } elseif ($this->argument === 'idle') {
+ return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ) ";
+ } elseif ($this->argument === 'occupied') {
+ return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ) ";
+ } else {
+ Message::addError('invalid-filter-argument', 'state', $this->argument);
+ return ' 1';
+ }
+ }
}
class LocationFilter extends Filter
{
- public function __construct($operator, $argument) {
- parent::__construct('locationid', $operator, $argument);
- }
-
- public function whereClause(&$args, &$joins) {
- settype($this->argument, 'int');
- if ($this->argument === 0) {
- $joins[] = 'LEFT JOIN subnet s ON (INET_ATON(machine.clientip) BETWEEN s.startaddr AND s.endaddr)';
- return 'machine.locationid IS NULL AND s.locationid IS NULL';
- } else {
- $joins[] = ' INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) ';
- $args['lid'] = $this->argument;
- $neg = $this->operator == '=' ? '' : 'NOT';
- return "$neg (subnet.locationid = :lid OR machine.locationid = :lid)";
- }
- }
+ public function __construct($operator, $argument)
+ {
+ parent::__construct('locationid', $operator, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ settype($this->argument, 'int');
+ if ($this->argument === 0) {
+ $joins[] = 'LEFT JOIN subnet s ON (INET_ATON(machine.clientip) BETWEEN s.startaddr AND s.endaddr)';
+ return 'machine.locationid IS NULL AND s.locationid IS NULL';
+ } else {
+ $joins[] = ' INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) ';
+ $args['lid'] = $this->argument;
+ $neg = $this->operator == '=' ? '' : 'NOT';
+ return "$neg (subnet.locationid = :lid OR machine.locationid = :lid)";
+ }
+ }
}
class SubnetFilter extends Filter
{
- public function __construct($operator, $argument) {
- parent::__construct(null, $operator, $argument);
- }
- public function whereClause(&$args, &$joins) {
- $argument = preg_replace('/[^0-9\.:]/', '', $this->argument);
- return " clientip LIKE '$argument%'";
- }
+ public function __construct($operator, $argument)
+ {
+ parent::__construct(null, $operator, $argument);
+ }
+
+ public function whereClause(&$args, &$joins)
+ {
+ $argument = preg_replace('/[^0-9\.:]/', '', $this->argument);
+ return " clientip LIKE '$argument%'";
+ }
}
diff --git a/modules-available/statistics/inc/filterset.inc.php b/modules-available/statistics/inc/filterset.inc.php
index ac928ac4..7cc075c3 100644
--- a/modules-available/statistics/inc/filterset.inc.php
+++ b/modules-available/statistics/inc/filterset.inc.php
@@ -2,27 +2,31 @@
class FilterSet
{
- private $filters;
- private $sortDirection;
- private $sortColumn;
+ private $filters;
+ private $sortDirection;
+ private $sortColumn;
- public function __construct($filters) {
- $this->filters = $filters;
- }
+ public function __construct($filters)
+ {
+ $this->filters = $filters;
+ }
- public function setSort($col, $direction) {
- $this->sortDirection = $direction === 'DESC' ? 'DESC' : 'ASC';
+ public function setSort($col, $direction)
+ {
+ $this->sortDirection = $direction === 'DESC' ? 'DESC' : 'ASC';
if (array_key_exists($col, Page_Statistics::$columns)) {
- $isMapped = array_key_exists('map_sort', Page_Statistics::$columns[$col]);
- $this->sortColumn = $isMapped ? Page_Statistics::$columns[$col]['map_sort'] : $col;
- } else {
- /* default sorting column is clientip */
- $this->sortColumn = 'clientip';
- }
-
- }
- public function makeFragments(&$where, &$join, &$sort, &$args) {
+ $isMapped = array_key_exists('map_sort', Page_Statistics::$columns[$col]);
+ $this->sortColumn = $isMapped ? Page_Statistics::$columns[$col]['map_sort'] : $col;
+ } else {
+ /* default sorting column is clientip */
+ $this->sortColumn = 'clientip';
+ }
+
+ }
+
+ public function makeFragments(&$where, &$join, &$sort, &$args)
+ {
/* generate where clause & arguments */
$where = '';
$joins = [];
@@ -39,12 +43,16 @@ class FilterSet
$join = implode('', array_unique($joins));
- $sort = " ORDER BY " . $this->sortColumn . " " . $this->sortDirection;
- }
- public function getSortDirection() {
- return $this->sortDirection;
- }
- public function getSortColumn() {
- return $this->sortColumn;
- }
+ $sort = " ORDER BY " . $this->sortColumn . " " . $this->sortDirection;
+ }
+
+ public function getSortDirection()
+ {
+ return $this->sortDirection;
+ }
+
+ public function getSortColumn()
+ {
+ return $this->sortColumn;
+ }
}
diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php
index ca0ea96e..ab0afcea 100644
--- a/modules-available/statistics/page.inc.php
+++ b/modules-available/statistics/page.inc.php
@@ -148,7 +148,10 @@ class Page_Statistics extends Page
}
/* read filter */
- $this->query = Request::any('filters');
+ $this->query = Request::any('filters', false);
+ if ($this->query === false) {
+ $this->query = 'lastseen > ' . gmdate('Y-m-d', strtotime('-30 day'));
+ }
$sortColumn = Request::any('sortColumn');
$sortDirection = Request::any('sortDirection');
$filters = Filter::parseQuery($this->query);
@@ -159,7 +162,9 @@ class Page_Statistics extends Page
$show = Request::get('show', 'stat', 'string');
if ($show == 'list') {
+ Render::openTag('div', array('class' => 'row'));
$this->showFilter('list', $filterSet);
+ Render::closeTag('div');
$this->showMachineList($filterSet);
return;
}
@@ -174,6 +179,9 @@ class Page_Statistics extends Page
Render::closeTag('div');
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showFilter($show, $filterSet)
{
$data = array(
@@ -183,9 +191,15 @@ class Page_Statistics extends Page
'sortDirection' => $filterSet->getSortDirection(),
'sortColumn' => $filterSet->getSortColumn(),
'columns' => json_encode(Page_Statistics::$columns),
- 'showList' => 1);
+ );
- $data['showList'] = ($show == 'list');
+ if ($show === 'list') {
+ $data['listButtonClass'] = 'btn-primary';
+ $data['statButtonClass'] = 'btn-default';
+ } else {
+ $data['listButtonClass'] = 'btn-default';
+ $data['statButtonClass'] = 'btn-primary';
+ }
$locsFlat = array();
@@ -199,11 +213,6 @@ class Page_Statistics extends Page
}
$data['locations'] = json_encode($locsFlat);
- // if($show == 'list') {
- // $data['showList'] = true;
- // } else {
- // $data['showList'] = false;
- // }
Render::addTemplate('filterbox', $data);
@@ -237,16 +246,18 @@ class Page_Statistics extends Page
}
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showSummary($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
- $cutoff = time() - 86400 * 30;
$online = time() - 610;
- $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastseen > $cutoff AND $where", $args);
- $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastseen > $online AND $where", $args);
- $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastseen > $online AND logintime <> 0 AND $where", $args);
- $hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE badsectors > 10 AND lastseen > $cutoff AND $where", $args);
+ $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE ($where)", $args);
+ $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastseen > $online AND ($where)", $args);
+ $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastseen > $online AND logintime <> 0 AND ($where)", $args);
+ $hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE badsectors >= 10 AND ($where)", $args);
if ($on['val'] != 0) {
$usedpercent = round($used['val'] / $on['val'] * 100);
} else {
@@ -287,6 +298,9 @@ class Page_Statistics extends Page
Render::addTemplate('summary', $data);
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showSystemModels($filterSet)
{
global $STATS_COLORS;
@@ -317,6 +331,9 @@ class Page_Statistics extends Page
Render::addTemplate('cpumodels', array('rows' => $lines, 'query' => $this->query, 'json' => json_encode($json)));
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showMemory($filterSet)
{
global $STATS_COLORS, $SIZE_RAM;
@@ -362,6 +379,9 @@ class Page_Statistics extends Page
Render::addTemplate('memory', $data);
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showKvmState($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
@@ -381,6 +401,9 @@ class Page_Statistics extends Page
Render::addTemplate('kvmstate', array('rows' => $lines, 'query' => $this->query,'json' => json_encode($json)));
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showId44($filterSet)
{
global $STATS_COLORS, $SIZE_ID44;
@@ -432,6 +455,9 @@ class Page_Statistics extends Page
Render::addTemplate('id44', $data);
}
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showLatestMachines($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
@@ -461,7 +487,9 @@ class Page_Statistics extends Page
Render::addTemplate('newclients', array('rows' => $rows, 'openbutton' => $count > 5));
}
-
+ /**
+ * @param \FilterSet $filterSet
+ */
private function showMachineList($filterSet)
{
$filterSet->makeFragments($where, $join, $sort, $args);
@@ -504,7 +532,6 @@ class Page_Statistics extends Page
'sortDirection' => $filterSet->getSortDirection(),
'sortColumn' => $filterSet->getSortColumn(),
'columns' => json_encode(Page_Statistics::$columns),
- 'locations' => json_encode($locsFlat),
'showList' => 1,
'show' => 'list'
));
@@ -814,7 +841,7 @@ class Page_Statistics extends Page
foreach ($res as $entry) {
if (isset($entry['txt']) && substr($entry['txt'], 0, 2) === 'i=') {
$string = substr($entry['txt'], 2);
- Page_Statistic::setPciId($cat, $param, $string);
+ Page_Statistics::setPciId($cat, $param, $string);
echo $string, $add;
exit;
}
diff --git a/modules-available/statistics/templates/cpumodels.html b/modules-available/statistics/templates/cpumodels.html
index f4af9cd2..0ab5286f 100644
--- a/modules-available/statistics/templates/cpumodels.html
+++ b/modules-available/statistics/templates/cpumodels.html
@@ -15,10 +15,12 @@
</tr>
{{#rows}}
<tr id="{{id}}">
- <td class="text-left slx-nowrap" style="overflow:hidden;text-overflow: ellipsis;">
- <a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a>
+ <td class="text-left text-nowrap filter-col" data-filter-col="systemmodel" style="overflow:hidden;text-overflow: ellipsis;">
+ <a class="filter-val" data-filter-val="{{systemmodel}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a>
+ </td>
+ <td class="text-right filter-col" data-filter-col="realcores">
+ <a class="filter-val" data-filter-val="{{cores}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~realcores={{cores}}">{{cores}}</a>
</td>
- <td class="text-right"><a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~realcores={{cores}}">{{cores}}</a></td>
<td class="text-right">{{count}}</td>
</tr>
{{/rows}}
diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html
index 2e7928e9..a5370a0b 100644
--- a/modules-available/statistics/templates/filterbox.html
+++ b/modules-available/statistics/templates/filterbox.html
@@ -1,12 +1,14 @@
-<div id="modal-add-filter" class="modal modal-sm fade" role="dialog" style="position:absolute; min-width:600px; min-height: 400px;margin:auto">
+<div id="modal-add-filter" class="modal modal-sm fade" role="dialog"
+ style="position:absolute; min-width:600px; min-height: 400px;margin:auto">
<div class="modal-content">
<div class="modal-header">
- <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+ <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span
+ class="sr-only">Close</span></button>
{{lang_add_filter}}
</div>
<form class="modal-body form-inline center">
<div class="form-group">
- <select id="columnSelect" name="column" class="form-control col-4-xs"> </select>
+ <select id="columnSelect" name="column" class="form-control col-4-xs"> </select>
</div>
<div class="form-group">
<select id="operatorSelect" name="operator" class="form-control col-4-xs"> </select>
@@ -15,42 +17,41 @@
<input name="argument" id="argumentInput" class="form-control col-4-xs"> </input>
<select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select>
</div>
- <button type="button" class="btn btn-primary" onclick="addFilterFromForm()" >
+ <button type="button" class="btn btn-primary" onclick="addFilterFromForm()">
<span class="glyphicon glyphicon-plus"></span>
- {{lang_add}}</button>
+ {{lang_add}}
+ </button>
</form>
</div>
</div>
-<div style="height:120px">
+<div style="height:120px" class="col-xs-12">
<!-- use GET here, to avoid the "resend form?" confirmation, and anyway this is stateless, so GET makes more sense -->
<form id="queryForm" method="GET" action="?do=Statistics" class="" role="form">
- <input type="hidden" name="do" value="Statistics"/>
- <label for="filters">{{lang_labelFilter}}</label>
- <input type="text" name="filters" class="" id="filterInput"/>
- <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/>
- <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/>
+ <input type="hidden" name="do" value="Statistics"/>
+ <label for="filterInput">{{lang_labelFilter}}</label>
+ <input type="text" name="filters" class="" id="filterInput"/>
+ <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/>
+ <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/>
- <button type="button" class="btn btn-success" onclick="popupFilter(null)">
- <span class="glyphicon glyphicon-plus"></span>
- {{lang_add_filter}}</button>
- <button class="btn btn-primary pull-right" type="submit" name="show" value="{{show}}">
- <span class="glyphicon glyphicon-refresh"></span>
- {{lang_refresh}}</button>
- {{#showList}}
- <button class="btn btn-secondary pull-right" type="submit" name="show" value="stat">
+ <button type="button" class="btn btn-success pull-left" onclick="popupFilter(null)">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_add_filter}}
+ </button>
+ <div class="btn-group pull-right">
+ <button class="btn {{statButtonClass}}" type="submit" name="show" value="stat">
<span class="glyphicon glyphicon-stats"></span>
- {{lang_showVisualization}}</button>
- {{/showList}}
- {{^showList}}
- <button class="btn btn-secondary pull-right" type="submit" name="show" value="list">
+ {{lang_showVisualization}}
+ </button>
+ <button class="btn {{listButtonClass}}" type="submit" name="show" value="list">
<span class="glyphicon glyphicon-list"></span>
- {{lang_showList}}</button>
- {{/showList}}
-</form>
-<br/>
-<br/>
+ {{lang_showList}}
+ </button>
+ </div>
+ </form>
+ <br/>
+ <br/>
</div>
<script type="application/javascript"><!--
@@ -99,7 +100,8 @@ document.addEventListener("DOMContentLoaded", function () {
/* initialize selectize */
filterSelectize = $('#filterInput').selectize({
delimiter: slxFilterDel,
- plugins: ['restore_on_backspace', 'remove_button'],
+ persist: false,
+ plugins: ['remove_button'],
create: function(input) {
return {value: input, text: input}
},
@@ -156,6 +158,22 @@ document.addEventListener("DOMContentLoaded", function () {
initButtons();
+ $('.filter-col').each(function(idx, elem) {
+ var e = $(elem);
+ var col = e.data('filter-col');
+ if (!col) return;
+ e.find('.filter-val').each(function(idx, elem) {
+ var e = $(elem);
+ var val = e.data('filter-val');
+ if (!val) return;
+ e.click(function(ev) {
+ ev.preventDefault();
+ addFilter(col, '=', val);
+ refresh();
+ });
+ });
+ });
+
}, false);
diff --git a/modules-available/statistics/templates/id44.html b/modules-available/statistics/templates/id44.html
index f0b5b9f7..84b515d1 100644
--- a/modules-available/statistics/templates/id44.html
+++ b/modules-available/statistics/templates/id44.html
@@ -6,14 +6,16 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="table table-condensed table-striped">
+ <table class="filter-col table table-condensed table-striped" data-filter-col="hddgb">
<tr>
<th>{{lang_partitionSize}}</th>
<th class="text-right">{{lang_machineCount}}</th>
</tr>
{{#rows}}
<tr id="tmpid{{gb}}" class="{{class}}">
- <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;GiB</td>
+ <td class="text-left text-nowrap">
+ <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;GiB</a>
+ </td>
<td class="text-right">{{count}}</td>
</tr>
{{/rows}}
@@ -35,7 +37,6 @@
return;
}
sel = $('#tmpid' + String(tooltip.text));
- console.log('#tmpid' + String(tooltip.text));
sel.addClass('slx-bold');
}
});
diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html
index 4c286d36..c44fb9ab 100644
--- a/modules-available/statistics/templates/kvmstate.html
+++ b/modules-available/statistics/templates/kvmstate.html
@@ -6,14 +6,16 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="table table-condensed table-striped">
+ <table class="filter-col table table-condensed table-striped" data-filter-col="kvmstate">
<tr>
<th>{{lang_kvmState}}</th>
<th class="text-right">{{lang_machineCount}}</th>
</tr>
{{#rows}}
<tr id="kvm{{kvmstate}}">
- <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a></td>
+ <td class="text-left slx-nowrap">
+ <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a>
+ </td>
<td class="text-right">{{count}}</td>
</tr>
{{/rows}}
diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html
index 8a882fa6..2d50c47f 100644
--- a/modules-available/statistics/templates/memory.html
+++ b/modules-available/statistics/templates/memory.html
@@ -6,14 +6,16 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-6">
- <table class="table table-condensed table-striped">
+ <table class="filter-col table table-condensed table-striped" data-filter-col="gbram">
<tr>
<th>{{lang_ramSize}}</th>
<th class="text-right">{{lang_machineCount}}</th>
</tr>
{{#rows}}
<tr id="ramid{{gb}}" class="{{class}}">
- <td class="text-left slx-nowrap"><a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;GiB</a></td>
+ <td class="text-left slx-nowrap">
+ <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;GiB</a>
+ </td>
<td class="text-right">{{count}}</td>
</tr>
{{/rows}}
diff --git a/tools/README b/tools/README
new file mode 100644
index 00000000..969fd5b3
--- /dev/null
+++ b/tools/README
@@ -0,0 +1,23 @@
+The tools in this directory must be run when the current working directory
+is the slx-admin root.
+
+Warning: These scripts are very quick and dirty, so make sure to ONLY run
+them if your working set is clean, so you can check "git diff" first and
+revert easily if something goes wrong.
+
+
+-- move-lang-files.sh
+This is supposed to be run first.
+Move translation files from ./lang/... to ./modules/<mod>/lang/...
+Pass either "modules" or "templates" as parameter
+
+-- convert-modules.php
+Run on the command line via
+"php tools/convert-modules.php"
+This is supposed to somewhat intelligently merge and rearrange most
+of the remaining translations from the global lang directory to the
+respective modules.
+
+
+After running both scripts, double-check the git diff output and the
+web interface itself if in doubt.
diff --git a/tools/convert-modules.php b/tools/convert-modules.php
new file mode 100644
index 00000000..9b479886
--- /dev/null
+++ b/tools/convert-modules.php
@@ -0,0 +1,226 @@
+<?php
+
+error_reporting(E_ALL);
+
+$tags = array();
+foreach (glob('modules/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
+ if (preg_match('#.*/(\w+)/?$#', $dir, $out)) {
+ $tags[] = $out[1];
+ }
+}
+
+foreach (glob('lang/??/messages-hardcoded.json', GLOB_NOSORT) as $dir) {
+ $data = json_decode(file_get_contents($dir), true);
+ if (!is_array($data)) {
+ echo "Kackfile $dir\n";
+ continue;
+ }
+ echo "Handling $dir\n";
+ $lang = substr($dir, 5, 2);
+ foreach ($tags as $mod) {
+ $tag = 'lang_' . $mod;
+ if (isset($data[$tag])) {
+ @mkdir('modules/' . $mod . '/lang/' . $lang, 0755, true);
+ $destFile = 'modules/' . $mod . '/lang/' . $lang . '/module.json';
+ $dest = json_decode(file_get_contents($destFile), true);
+ if (!is_array($dest)) {
+ $dest = array();
+ }
+ $dest['module_name'] = $data[$tag];
+ unset($data[$tag]);
+ ksort($dest);
+ ksort($data);
+ file_put_contents($destFile, json_encode($dest, JSON_PRETTY_PRINT)) > 0 && file_put_contents($dir, json_encode($data, JSON_PRETTY_PRINT));
+ }
+ }
+}
+
+foreach (glob('lang/??/modules/*.json', GLOB_NOSORT) as $path) {
+ if (!preg_match('#lang/(..)/modules/(\w+)\.json#', $path, $out)) continue;
+ $data = json_decode(file_get_contents($path), true);
+ if (!is_array($data)) {
+ echo "Kackfile $path\n";
+ continue;
+ }
+ echo "Handling $path\n";
+ $lang = $out[1];
+ $mod = $out[2];
+ if (!in_array($mod, $tags)) continue;
+ @mkdir('modules/' . $mod . '/lang/' . $lang, 0755, true);
+ $destFile = 'modules/' . $mod . '/lang/' . $lang . '/module.json';
+ $dest = json_decode(file_get_contents($destFile), true);
+ if (!is_array($dest)) {
+ $dest = array();
+ }
+ foreach (array_keys($data) as $k) {
+ if (substr($k, 0, 5) !== 'lang_') continue;
+ if (empty($dest[$k]) && !empty($data[$k])) $dest[$k] = $data[$k];
+ unset($data[$k]);
+ }
+ ksort($dest);
+ ksort($data);
+ file_put_contents($destFile, json_encode($dest, JSON_PRETTY_PRINT)) > 0 && file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT));
+}
+
+foreach (
+ glob('modules/*/lang/??/templates/*.json', GLOB_NOSORT)
+ + glob('modules/*/lang/??/templates/*/*.json', GLOB_NOSORT)
+ + glob('modules/*/lang/??/templates/*/*/*.json', GLOB_NOSORT)
+ as $path) {
+ if (!preg_match('#modules/([^/]+)/lang/(..)/temp#', $path, $out)) continue;
+ $module = $out[1];
+ $lang = $out[2];
+ $old = @json_decode(@file_get_contents($path), true);
+ if (!is_array($old) || empty($old)) {
+ unlink($path);
+ continue;
+ }
+ $exFile = "modules/$module/lang/$lang/template-tags.json";
+ $existing = @json_decode(@file_get_contents($exFile), true);
+ if (!is_array($existing)) $existing = array();
+ $existing = $existing + $old;
+ ksort($existing);
+ if (file_put_contents($exFile, json_encode($existing, JSON_PRETTY_PRINT)) > 0) {
+ unlink($path);
+ }
+}
+
+echo "LAST SECTION\n";
+
+foreach (glob('modules/*/lang/??/module.json', GLOB_NOSORT) as $path) {
+ if (!preg_match('#modules/([^/]+)/lang/(..)/module.json#', $path, $out)) continue;
+ if (!is_array($data)) {
+ echo "Kackfile $path\n";
+ continue;
+ }
+ $old = json_decode(file_get_contents($path), true);
+ if (!is_array($old) || empty($old)) {
+ echo "Is empty\n";
+ unlink($path);
+ continue;
+ }
+ echo "Handling $path\n";
+ $module = $out[1];
+ $lang = $out[2];
+ $exFile = "modules/$module/lang/$lang/template-tags.json";
+ $existing = @json_decode(@file_get_contents($exFile), true);
+ if (!is_array($existing)) $existing = array();
+ foreach (array_keys($old) as $k) {
+ if (substr($k, 0, 5) === 'lang_') {
+ if (empty($existing[$k])) {
+ $existing[$k] = $old[$k];
+ }
+ unset($old[$k]);
+ }
+ }
+ ksort($existing);
+ ksort($old);
+ if (file_put_contents($exFile, json_encode($existing, JSON_PRETTY_PRINT)) > 0) {
+ if (empty($old)) {
+ echo "Old file deleted\n";
+ unlink($path);
+ } else {
+ echo "Old file shortened\n";
+ file_put_contents($path, json_encode($old, JSON_PRETTY_PRINT));
+ }
+ }
+}
+
+
+echo "Fixing up messages...\n";
+
+ function getAllFiles($dir, $extension)
+ {
+ $php = array();
+ $extLen = -strlen($extension);
+ foreach (scandir($dir, SCANDIR_SORT_NONE) as $name) {
+ if ($name === '.' || $name === '..')
+ continue;
+ $name = $dir . '/' . $name;
+ if (substr($name, $extLen) === $extension && is_file($name)) {
+ $php[] = $name;
+ } else if (is_dir($name)) {
+ $php = array_merge($php, getAllFiles($name, $extension));
+ }
+ }
+ return $php;
+ }
+
+$files = array_merge(getAllFiles('modules-available', '.php'), getAllFiles('inc', '.php'));
+$files[] = 'index.php';
+$messages = array();
+
+foreach ($files as $file) {
+ $content = file_get_contents($file);
+ if ($content === false)
+ continue;
+ if (preg_match_all('/Message\s*::\s*add\w+\s*\(\s*[\'"](?<tag>[^\'"\.]*)[\'"]\s*[\)\,]/i', $content, $out, PREG_SET_ORDER) < 1)
+ continue;
+ foreach ($out as $set) {
+ if (preg_match('#modules-available/([^/]+)/#', $file, $a)) {
+ $id = $a[1];
+ } else {
+ $id = 'main';
+ }
+ $messages[$set['tag']][$id]++;
+ }
+}
+
+$langs = array();
+foreach (glob('lang/??/messages.json', GLOB_NOSORT) as $path) {
+ $lang = substr($path, 5, 2);
+ if ($lang === '..') continue;
+ $data = json_decode(file_get_contents($path), true);
+ if (is_array($data)) {
+ $langs[$lang] = $data;
+ echo "Have $lang\n";
+ }
+}
+
+echo "Processing\n";
+foreach ($messages as $id => $modules) {
+ asort($modules, SORT_NUMERIC);
+ $modules = array_reverse($modules, true);
+ reset($modules);
+ list($topModule, $topCount) = each($modules);
+ reset($modules);
+ $sum = 0;
+ foreach ($modules as $c) {
+ $sum += $c;
+ }
+ $fac = $topCount / $sum;
+ echo "****************** $id\n";
+ print_r($modules);
+ if (count($modules) > 3 || isset($modules['main'])) {
+ $destMods = array('main');
+ } elseif (count($modules) === 1) {
+ $destMods = array($topModule);
+ } else {
+ $destMods = array_keys($modules);
+ }
+ foreach ($langs as $lang => &$source) {
+ if (!isset($source[$id]))
+ continue;
+ $del = true;
+ foreach ($destMods as $destMod) {
+ $dest = json_decode(file_get_contents('modules/' . $destMod . '/lang/' . $lang . '/messages.json'), true);
+ if (!is_array($dest)) $dest = array();
+ if (!empty($dest[$id]))
+ continue;
+ $dest[$id] = $source[$id];
+ mkdir('modules/' . $destMod . '/lang/' . $lang, 0775, true);
+ if (file_put_contents('modules/' . $destMod . '/lang/' . $lang . '/messages.json', json_encode($dest, JSON_PRETTY_PRINT)) < 1) {
+ $del = false;
+ }
+ }
+ if ($del) {
+ unset($source[$id]);
+ }
+ }
+ unset($source);
+}
+
+foreach ($langs as $lang => $source) {
+ file_put_contents('lang/' . $lang . '/messages.json', json_encode($source, JSON_PRETTY_PRINT));
+}
+
diff --git a/tools/move-lang-files.sh b/tools/move-lang-files.sh
new file mode 100755
index 00000000..39d0db51
--- /dev/null
+++ b/tools/move-lang-files.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+declare -rg PID=$$
+
+perror() {
+ echo "[ERROR] $*"
+ [ "$$" != "$PID" ] && kill "$PID"
+ exit 1
+}
+
+[ "$2" = "modules" -o "$2" = "templates" ] || perror "Second option must be modules or templates"
+
+declare -rg TRANS="$1"
+[ -z "$TRANS" ] && perror "Usage: $0 <language> modules|templates"
+declare -rg DIR="lang/${TRANS}/$2"
+[ -d "$DIR" ] || perror "No old modules dir for lang $TRANS"
+
+for mod in $(ls -1 "$DIR"); do
+ [ -d "$DIR/$mod" ] || continue
+ [ -z "$(ls -1 "$DIR/$mod")" ] && continue
+ DEST="modules/$mod/lang/$TRANS/templates"
+ echo " ******** $DIR/$mod --> $DEST *********"
+ mkdir -p "$DEST" || perror "Could not create $DEST"
+ cp -v -a "$DIR/$mod/"* "$DEST/" || perror "Could not copy"
+ git rm -r "$DIR/$mod"
+ git add "$DEST"
+done
+
+echo " -- Categories --"
+
+if [ -n "$(ls -1 "lang/${TRANS}/settings/")" ]; then
+ git mv "lang/${TRANS}/settings/"*.json "modules/baseconfig/lang/${TRANS}/" || perror "Could not move settings/categories names"
+fi
+