summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Klinger2016-09-07 16:56:39 +0200
committerChristian Klinger2016-09-07 16:56:39 +0200
commitd369776f806ed5f2429046802b5e4e447824465d (patch)
treeec6ac4d39e679146eb6e7e9600551731bffee52f
parentAdded a pvs.ini file generator. (diff)
parent[roomplanner] Sanity checks/fixups when saving computers, more user feedback,... (diff)
downloadslx-admin-d369776f806ed5f2429046802b5e4e447824465d.tar.gz
slx-admin-d369776f806ed5f2429046802b5e4e447824465d.tar.xz
slx-admin-d369776f806ed5f2429046802b5e4e447824465d.zip
Merge branch 'modularization' of git.openslx.org:openslx-ng/slx-admin into modularization
-rw-r--r--inc/dashboard.inc.php8
-rw-r--r--inc/render.inc.php4
-rw-r--r--index.php3
-rw-r--r--modules-available/dozmod/style.css59
-rw-r--r--modules-available/exams/baseconfig/getconfig.inc.php5
-rw-r--r--modules-available/exams/inc/exams.inc.php5
-rw-r--r--modules-available/exams/install.inc.php4
-rw-r--r--modules-available/exams/lang/de/template-tags.json4
-rw-r--r--modules-available/exams/page.inc.php73
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html6
-rw-r--r--modules-available/locations/inc/location.inc.php5
-rw-r--r--modules-available/locations/page.inc.php2
-rw-r--r--modules-available/locations/templates/location-subnets.html4
-rw-r--r--modules-available/locations/templates/subnets.html2
-rw-r--r--modules-available/roomplanner/config.json3
-rw-r--r--modules-available/roomplanner/js/grid.js134
-rw-r--r--modules-available/roomplanner/js/init.js39
-rw-r--r--modules-available/roomplanner/lang/de/template-tags.json3
-rw-r--r--modules-available/roomplanner/lang/en/template-tags.json3
-rw-r--r--modules-available/roomplanner/page.inc.php369
-rw-r--r--modules-available/roomplanner/style.css34
-rw-r--r--modules-available/roomplanner/templates/page.html69
-rw-r--r--modules-available/session/lang/de/module.json3
-rw-r--r--modules-available/session/lang/en/module.json3
-rw-r--r--style/default.css63
25 files changed, 579 insertions, 328 deletions
diff --git a/inc/dashboard.inc.php b/inc/dashboard.inc.php
index 01572461..2cf0b494 100644
--- a/inc/dashboard.inc.php
+++ b/inc/dashboard.inc.php
@@ -5,9 +5,17 @@ class Dashboard
private static $iconCache = array();
private static $subMenu = array();
+ private static $disabled = false;
+
+ public static function disable()
+ {
+ self::$disabled = true;
+ }
public static function createMenu()
{
+ if (self::$disabled)
+ return;
global $MENU_CAT_OVERRIDE;
$modByCategory = array();
$modById = array();
diff --git a/inc/render.inc.php b/inc/render.inc.php
index d870768e..10419d6f 100644
--- a/inc/render.inc.php
+++ b/inc/render.inc.php
@@ -107,8 +107,10 @@ class Render
/**
* Set the page title (title-tag)
*/
- public static function setTitle($title)
+ public static function setTitle($title, $override = true)
{
+ if (!$override && !empty(self::$title))
+ return;
self::$title = $title . ' - ';
}
diff --git a/index.php b/index.php
index d9b7c371..5fff7e5e 100644
--- a/index.php
+++ b/index.php
@@ -42,7 +42,7 @@ abstract class Page
{
$pageTitle = self::$module->getPageTitle();
if ($pageTitle !== false) {
- Render::setTitle($pageTitle);
+ Render::setTitle($pageTitle, false);
}
self::$instance->doRender();
}
@@ -136,6 +136,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// AJAX Stuff? Just do so. Otherwise, run preprocessing
if (AJAX) {
+ ob_start('ob_gzhandler');
Page::ajax();
exit(0);
}
diff --git a/modules-available/dozmod/style.css b/modules-available/dozmod/style.css
index 8cc5548d..22d769ed 100644
--- a/modules-available/dozmod/style.css
+++ b/modules-available/dozmod/style.css
@@ -1,62 +1,3 @@
-/* this is based on https://github.com/flatlogic/awesome-bootstrap-checkbox */
-
-.checkbox {
- padding-left: 20px;
-}
-.checkbox label {
- display: inline-block;
- vertical-align: middle;
- position: relative;
- padding-left: 5px;
-}
-.checkbox label::before {
- content: "";
- display: inline-block;
- position: absolute;
- width: 17px;
- height: 17px;
- left: 0;
- margin-left: -20px;
- border: 1px solid #cccccc;
- border-radius: 3px;
- background-color: #fff;
- -webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
- -o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
- transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
-}
-.checkbox label::after {
- display: inline-block;
- position: absolute;
- width: 16px;
- height: 16px;
- left: 0;
- top: 0;
- margin-left: -20px;
- padding-left: 3px;
- padding-top: 1px;
- font-size: 11px;
- color: #555555;
-}
-.checkbox input[type="checkbox"],
-.checkbox input[type="radio"] {
- opacity: 0;
- z-index: 1;
- top: -6px;
-}
-
-
-.checkbox input[type="checkbox"]:focus + label::before,
-.checkbox input[type="radio"]:focus + label::before {
- outline: thin dotted;
- outline: 5px auto -webkit-focus-ring-color;
- outline-offset: -2px;
-}
-.checkbox input[type="checkbox"]:checked + label::after,
-.checkbox input[type="radio"]:checked + label::after {
- font-family: "Glyphicons Halflings";
- content: "\E013";
-}
-
.witherror {
border: 1px solid red;
}
diff --git a/modules-available/exams/baseconfig/getconfig.inc.php b/modules-available/exams/baseconfig/getconfig.inc.php
index 6a9bf03a..5e506755 100644
--- a/modules-available/exams/baseconfig/getconfig.inc.php
+++ b/modules-available/exams/baseconfig/getconfig.inc.php
@@ -5,9 +5,12 @@ if (isset($configVars["SLX_LOCATIONS"])) {
} else {
$locationIds = array();
}
-if (Exams::isInExamMode($locationIds, $lectureId)) {
+if (Exams::isInExamMode($locationIds, $lectureId, $autoLogin)) {
$configVars['SLX_EXAM'] = 'yes';
if (strlen($lectureId) > 0) {
$configVars['SLX_EXAM_START'] = $lectureId;
}
+ if (strlen($autoLogin) > 0) {
+ $configVars['SLX_AUTOLOGIN'] = $autoLogin;
+ }
}
diff --git a/modules-available/exams/inc/exams.inc.php b/modules-available/exams/inc/exams.inc.php
index 5fc497c7..dfc38273 100644
--- a/modules-available/exams/inc/exams.inc.php
+++ b/modules-available/exams/inc/exams.inc.php
@@ -7,17 +7,18 @@ 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, &$lectureId)
+ public static function isInExamMode($locationIds, &$lectureId = false, &$autoLogin = false)
{
if (!is_array($locationIds)) {
$locationIds = array($locationIds);
}
$l = str_repeat(',?', count($locationIds));
- $res = Database::queryFirst("SELECT lectureid FROM exams"
+ $res = Database::queryFirst("SELECT lectureid, autologin 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'];
+ $autoLogin = $res['autologin'];
}
return $res !== false;
}
diff --git a/modules-available/exams/install.inc.php b/modules-available/exams/install.inc.php
index b1032118..45e9e41a 100644
--- a/modules-available/exams/install.inc.php
+++ b/modules-available/exams/install.inc.php
@@ -7,6 +7,7 @@ $res[] = tableCreate('exams', '
`lectureid` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
`starttime` int(11) NOT NULL,
`endtime` int(11) NOT NULL,
+ `autologin` char(36) NULL,
`description` varchar(500) DEFAULT NULL,
PRIMARY KEY (`examid`)
');
@@ -28,6 +29,9 @@ if (Database::exec("ALTER TABLE exams ADD INDEX `idx_daterange` ( `starttime` ,
if (!tableHasColumn('exams', 'lectureid')) {
Database::exec("ALTER TABLE `exams` ADD `lectureid` CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NULL DEFAULT NULL AFTER `examid`");
}
+if (!tableHasColumn('exams', 'autologin')) {
+ Database::exec("ALTER TABLE `exams` ADD `autologin` CHAR(36) NULL DEFAULT NULL AFTER `endtime`");
+}
Database::exec("ALTER TABLE `exams` CHANGE `description` `description` varchar(500) DEFAULT NULL");
diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json
index 22f777ad..6f4dce62 100644
--- a/modules-available/exams/lang/de/template-tags.json
+++ b/modules-available/exams/lang/de/template-tags.json
@@ -3,7 +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_autoLogin": "Login \u00fcberspringen",
+ "lang_autoLoginInfo": "Wenn Sie diese Option w\u00e4hlen, wird die bwLehrpool Loginmaske \u00fcbersprungen und eine anonyme Sitzung gestartet, d.h. die Studierenden sehen nach dem Booten des Rechners keine Loginmaske, sondern direkt den vmChooser mit den f\u00fcr den Raum verf\u00fcgbaren Pr\u00fcfungen. Dies ist z.B. sinnvoll, wenn die Studierenden zur Abgabe ein LMS nutzen, an dem sie sich separat authentifizieren. Bedenken Sie, dass in diesem Modus keine Home-Verzeichnisse zur Verf\u00fcgung stehen. Wenn Sie zus\u00e4tzlich eine automatisch zu startende Veranstaltung w\u00e4hlen, wird der vmChooser nicht angezeigt, sodass die Rechner ohne Nutzerinteraktion direkt in die ausgew\u00e4hlte Veranstaltung booten.",
+ "lang_autoStartInfo": "W\u00e4hlen Sie hier optional eine Veranstaltung, die automatisch auf den Clients gestartet werden soll. Die Studierenden sehen nach der Loginmaske keinen vmChooser; die hier festgelegte Veranstaltung wird direkt gestartet. Beachten Sie, 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 den vmChooser, in dem dann jedoch ausschlie\u00dflich als Pr\u00fcfung markierte Veranstaltungen aufgelistet werden. Sie k\u00f6nnen diese Funktion mit der Option \"Login \u00fcberspringen\" kombinieren, um einen komplett automatisierten Start zu erm\u00f6glichen.",
"lang_autoStartLecture": "Automatisch zu startende Veranstaltung",
"lang_autostart": "Autostart-Veranstaltung",
"lang_begin": "Beginn",
diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php
index e70fc3c7..49b48bb6 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -30,7 +30,7 @@ class Page_Exams extends Page
protected function readExams()
{
- $tmp = Database::simpleQuery("SELECT e.examid, l.displayname AS lecturename, e.starttime, e.endtime, e.description, GROUP_CONCAT(exl.locationid) AS locationids, "
+ $tmp = Database::simpleQuery("SELECT e.examid, e.autologin, 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 "
@@ -75,26 +75,28 @@ class Page_Exams extends Page
*/
$unique_ids = 1;
/* add the red shadows */
- foreach ($this->exams as $e) {
- if ($e['starttime'] > $this->rangeMax || $e['endtime'] < $this->rangeMin)
- continue;
- $locationids = explode(',', $e['locationids']);
- if ($locationids[0] == 0) {
- $locationids = [];
- foreach($this->locations as $location) {
- $locationids[] = $location['locationid'];
+ if (is_array($this->exams)) {
+ foreach ($this->exams as $e) {
+ if ($e['starttime'] > $this->rangeMax || $e['endtime'] < $this->rangeMin)
+ continue;
+ $locationids = explode(',', $e['locationids']);
+ if ($locationids[0] == 0) {
+ $locationids = [];
+ foreach ($this->locations as $location) {
+ $locationids[] = $location['locationid'];
+ }
+ }
+ foreach ($locationids as $locationid) {
+ $out[] = [
+ 'id' => 'shadow_' . $unique_ids++,
+ 'content' => $e['description'],
+ 'title' => $e['description'],
+ 'start' => intval($e['starttime']) * 1000,
+ 'end' => intval($e['endtime']) * 1000,
+ 'type' => 'background',
+ 'group' => $locationid,
+ ];
}
- }
- foreach ($locationids as $locationid) {
- $out[] = [
- 'id' => 'shadow_' . $unique_ids++,
- 'content' => $e['description'],
- 'title' => $e['description'],
- 'start' => intval($e['starttime']) * 1000,
- 'end' => intval($e['endtime']) * 1000,
- 'type' => 'background',
- 'group' => $locationid,
- ];
}
}
/* add the lectures */
@@ -142,17 +144,19 @@ class Page_Exams extends Page
{
$out = [];
$now = time();
- foreach ($this->exams as $exam) {
- if ($exam['endtime'] < $now) {
- $exam['rowClass'] = 'text-muted';
- $exam['btnClass'] = 'btn-success';
- $exam['liesInPast'] = true;
- } else {
- $exam['btnClass'] = 'btn-default';
+ if (is_array($this->exams)) {
+ foreach ($this->exams as $exam) {
+ if ($exam['endtime'] < $now) {
+ $exam['rowClass'] = 'text-muted';
+ $exam['btnClass'] = 'btn-success';
+ $exam['liesInPast'] = true;
+ } else {
+ $exam['btnClass'] = 'btn-default';
+ }
+ $exam['starttime_s'] = date('Y-m-d H:i', $exam['starttime']);
+ $exam['endtime_s'] = date('Y-m-d H:i', $exam['endtime']);
+ $out[] = $exam;
}
- $exam['starttime_s'] = date('Y-m-d H:i', $exam['starttime']);
- $exam['endtime_s'] = date('Y-m-d H:i', $exam['endtime']);
- $out[] = $exam;
}
return $out;
}
@@ -216,6 +220,7 @@ class Page_Exams extends Page
$endtime = strtotime(Request::post('endtime_date') . " " . Request::post('endtime_time'));
$description = Request::post('description', '', 'string');
$lectureid = Request::post('lectureid', '', 'string');
+ $autologin = Request::post('autologin', '', 'string');
if (!$this->isDateSane($starttime)) {
Message::addError('starttime-invalid', Request::post('starttime_date') . " " . Request::post('starttime_time'));
Util::redirect('?do=exams');
@@ -231,8 +236,8 @@ class Page_Exams extends Page
if ($examid === 0) {
// No examid given, is add
- $res = Database::exec("INSERT INTO exams(lectureid, starttime, endtime, description) VALUES(:lectureid, :starttime, :endtime, :description);",
- compact('lectureid', 'starttime', 'endtime', 'description')) !== false;
+ $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) {
@@ -255,8 +260,8 @@ class Page_Exams extends Page
}
/* update fields */
- $res = Database::exec("UPDATE exams SET lectureid = :lectureid, starttime = :starttime, endtime = :endtime, description = :description WHERE examid = :examid",
- compact('lectureid', 'starttime', 'endtime', 'description', 'examid')) !== false;
+ $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 */
diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html
index 744aad29..afdb0b71 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -91,6 +91,10 @@
</select>
</div>
</div>
+ <div class="form-group col-xs-12">
+ <div class="checkbox"><input id="autologin" type="checkbox" name="autologin" value="demo" class="form-control" {{#exam.autologin}}checked{{/exam.autologin}}><label for="autologin">{{lang_autoLogin}}</label></div>
+ <p><i>{{lang_autoLoginInfo}}</i></p>
+ </div>
<div class="col-xs-12" id="lecture-info">
bla
</div>
@@ -192,7 +196,7 @@ document.addEventListener("DOMContentLoaded", function () {
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') + ')');
+ $('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment(sel.data('from') * 1000).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment(sel.data('to') * 1000).format('YYYY-MM-DD H:mm') + ')');
}
}
diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php
index 71a621db..81ee342f 100644
--- a/modules-available/locations/inc/location.inc.php
+++ b/modules-available/locations/inc/location.inc.php
@@ -26,6 +26,11 @@ class Location
return $rows;
}
+ public static function get($locationId)
+ {
+ return Database::queryFirst("SELECT * FROM location WHERE locationid = :locationId", compact('locationId'));
+ }
+
public static function getName($locationId)
{
self::getLocationsAssoc();
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
index b1146959..8574e4f4 100644
--- a/modules-available/locations/page.inc.php
+++ b/modules-available/locations/page.inc.php
@@ -412,7 +412,7 @@ class Page_Locations extends Page
'locationid' => $loc['locationid'],
'locationname' => $loc['locationname'],
'list' => $rows,
- 'isLeaf' => Location::isLeaf($locationId),
+ 'roomplanner' => Module::get('roomplanner') !== false && Location::isLeaf($locationId),
'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
);
if (Module::get('dozmod') !== false) {
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
index bc507107..5da931e1 100644
--- a/modules-available/locations/templates/location-subnets.html
+++ b/modules-available/locations/templates/location-subnets.html
@@ -60,13 +60,13 @@
</table>
<br>
<div class="btn-group">
- {{#isLeaf}}
+ {{#roomplanner}}
<a class="btn btn-default" href="?do=roomplanner&amp;locationid={{locationid}}"
target="_blank"
>
<span class="glyphicon glyphicon-move"></span>{{lang_editRoomplan}}
</a>
- {{/isLeaf}}
+ {{/roomplanner}}
</div>
<div class="pull-right">
diff --git a/modules-available/locations/templates/subnets.html b/modules-available/locations/templates/subnets.html
index 0320e333..15fa28f2 100644
--- a/modules-available/locations/templates/subnets.html
+++ b/modules-available/locations/templates/subnets.html
@@ -29,7 +29,7 @@
{{/list}}
</table>
<div>
- <button type="submit" class="btn btn-primary">Späschohn (geht noch nicht!)</button>
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
</div>
</form>
</div>
diff --git a/modules-available/roomplanner/config.json b/modules-available/roomplanner/config.json
index 48ddbb30..f620405a 100644
--- a/modules-available/roomplanner/config.json
+++ b/modules-available/roomplanner/config.json
@@ -1,4 +1,3 @@
{
- "category":"main.content",
- "dependencies": ["js_jqueryui", "js_selectize", "bootstrap_dialog"]
+ "dependencies": ["js_jqueryui", "js_selectize", "bootstrap_dialog", "statistics", "locations"]
}
diff --git a/modules-available/roomplanner/js/grid.js b/modules-available/roomplanner/js/grid.js
index f0c18316..2cca26cd 100644
--- a/modules-available/roomplanner/js/grid.js
+++ b/modules-available/roomplanner/js/grid.js
@@ -19,8 +19,8 @@ if (!roomplanner) var roomplanner = {
cellsep: 4,
scale: 100,
room: {
- width: 1000,
- height: 1000
+ width: 33,
+ height: 33
}
},
selectFromServer: selectMachine,
@@ -101,33 +101,27 @@ if (!roomplanner) var roomplanner = {
$(this).addClass("obstacle");
}
- if ($(this).attr('itemtype') == "pc_drag") {
- $(this).attr('itemtype','pc');
+ if ($(this).attr('itemtype').indexOf('_drag') > -1) {
+ var itemtype = $(this).attr('itemtype').replace('_drag','');
+ $(this).attr('itemtype',itemtype);
}
-
},
"preventCollision" : true,
"restraint": "#draw-element-area",
- "obstacle" : ".obstacle",
+ "obstacle" : '[itemtype="'+$(el).attr('itemtype')+'"]',
"start": function(ev,ui) {
if (roomplanner.isElementResizable(this)) {
$(this).resizable("option","maxHeight",null);
$(this).resizable("option","maxWidth",null);
}
- if ($(this).attr('itemtype') == "pc") {
- $(this).attr('itemtype','pc_drag');
- }
+ var itemtype = $(this).attr('itemtype');
+ $(this).attr('itemtype',itemtype+'_drag');
$(this).removeClass("obstacle");
}
};
- // pcs can be placed everywhere
- if ($(el).attr('itemtype') == "pc") {
- options.obstacle = '[itemtype="pc"]';
- }
-
for (var o in options) {
$(el).draggable("option",o,options[o]);
}
@@ -137,7 +131,7 @@ if (!roomplanner) var roomplanner = {
$(el).resizable({
containment : "#draw-element-area",
- obstacle: ".obstacle",
+ obstacle: '[itemtype="'+$(el).attr('itemtype')+'"]',
handles: "se",
autoHide: true,
grid: [(roomplanner.settings.scale / 4), (roomplanner.settings.scale / 4)],
@@ -145,7 +139,10 @@ if (!roomplanner) var roomplanner = {
var gridSteps = $(this).resizable("option","grid");
- var collides = $(this).collision(".obstacle");
+ var collides = $(this).collision('[itemtype="'+$(el).attr('itemtype').replace('_drag','')+'"]');
+
+
+
var pos = $(this).offset();
var self = this;
@@ -187,6 +184,10 @@ if (!roomplanner) var roomplanner = {
},
start: function(ev,ui) {
$(this).removeClass("obstacle");
+
+ var itemtype = $(this).attr('itemtype');
+ $(this).attr('itemtype',itemtype+'_drag');
+
$(this).css('opacity',0.8);
var gridSteps = $(this).resizable("option","grid");
@@ -219,6 +220,11 @@ if (!roomplanner) var roomplanner = {
$(this).addClass("obstacle");
}
+ if ($(this).attr('itemtype').indexOf('_drag') > -1) {
+ var itemtype = $(this).attr('itemtype').replace('_drag','');
+ $(this).attr('itemtype',itemtype);
+ }
+
var gridSteps = $(this).resizable("option","grid");
var mw = $(this).resizable("option","maxWidth");
if (mw) {
@@ -280,11 +286,15 @@ if (!roomplanner) var roomplanner = {
return JSON.stringify(objects);
},
load: function(object) {
- try {
- var objects = JSON.parse(object);
- } catch(e) {
- alert('invalid JSON format');
- return false;
+ if (typeof object === 'string') {
+ try {
+ var objects = JSON.parse(object);
+ } catch (e) {
+ alert('invalid JSON format');
+ return false;
+ }
+ } else {
+ var objects = object;
}
$('#draw-element-area').html('');
@@ -347,6 +357,18 @@ roomplanner.grid = (function() {
$('#drawarea').height(h);
},
scale: function(num) {
+
+ var area_left = parseInt($('#drawarea').css('left')) - $('#drawpanel .panel-body').width()/2 ;
+ var area_top = parseInt($('#drawarea').css('top')) - $('#drawpanel .panel-body').height()/2;
+
+ var opts = {
+ left: ((parseInt(area_left) * num / roomplanner.settings.scale ) + $('#drawpanel .panel-body').width()/2)+ "px" ,
+ top: ((parseInt(area_top) * num / roomplanner.settings.scale ) + $('#drawpanel .panel-body').height()/2)+ "px"
+ };
+
+ $('#drawarea').css(opts);
+
+
$('#drawarea').css('background-size',num);
roomplanner.settings.scale = num;
$('#draw-element-area .ui-draggable').each(function(idx,item) {
@@ -382,16 +404,19 @@ roomplanner.grid = (function() {
$(document).ready(function(){
roomplanner.grid.init();
+
+ var update = function(event,ui) {
+ roomplanner.grid.scale(ui.value);
+ };
- $('#scaleslider').slider({
+ roomplanner.slider = $('#scaleslider').slider({
orientation: "horizontal",
range: "min",
- min: 20,
- max: 200,
+ min: 40,
+ max: 150,
value: 100,
- slide: function(event,ui) {
- roomplanner.grid.scale(ui.value);
- },
+ change: update,
+ slide: update,
stop: function(e, ui) {
$('#drawarea').trigger('checkposition');
}
@@ -427,10 +452,54 @@ $(document).ready(function(){
// the element is already in drawing area
var el = (ui.helper == ui.draggable) ? ui.draggable : $(ui.helper.clone());
- var collidingSelector = ($(el).attr('itemtype') =="pc_drag") ? '[itemtype="pc"]' : '.obstacle';
- if ($(el).collision(collidingSelector).length) {
- return;
+ var collidingElements = $(el).collision('[itemtype="'+$(el).attr('itemtype').replace('_drag','')+'"]');
+
+ var i = 0;
+ while (collidingElements.length > 0) {
+ // too much tries - abort
+ if (i > 5) { return; }
+
+
+ if (ui.helper != ui.draggable) {
+ var leftPos = parseInt($(el).css('left'))-parseInt($('#drawarea').css('left'))-$('#drawpanel').offset().left;
+ var topPos = parseInt($(el).css('top'))-parseInt($('#drawarea').css('top'))-($('#drawpanel').offset().top + $('#drawpanel .panel-heading').height());
+ var cp = roomplanner.getCellPositionFromPixels(leftPos,topPos);
+ leftPos = cp[0];
+ topPos = cp[1];
+
+ } else {
+ var leftPos = parseInt($(el).css('left'));
+ var topPos = parseInt($(el).css('top'));
+ }
+
+ var collider = $(collidingElements[0]);
+ var colliderTop = parseInt(collider.css('top'));
+ var colliderLeft = parseInt(collider.css('left'));
+
+ var overlap = {
+ x: Math.min(colliderLeft+collider.outerWidth(),leftPos+$(el).outerWidth()) - Math.max(leftPos,colliderLeft),
+ y: Math.min(colliderTop+collider.outerHeight(),topPos+$(el).outerHeight()) - Math.max(topPos,colliderTop)
+ };
+
+ if (overlap.x <= overlap.y) {
+ var lpos = parseInt($(el).css('left'));
+ if (colliderLeft + overlap.x == leftPos + $(el).width()) {
+ $(el).css('left',(lpos - (overlap.x+2))+"px");
+ } else {
+ $(el).css('left',(lpos + overlap.x+2)+"px");
+ }
+ } else {
+ var tpos = parseInt($(el).css('top'));
+ if (colliderTop + overlap.y == topPos + $(el).height()) {
+ $(el).css('top',(tpos - (overlap.y+2))+"px");
+ } else {
+ $(el).css('top',(tpos + overlap.y+2)+"px");
+ }
+ }
+ collidingElements = $(el).collision('[itemtype="'+$(el).attr('itemtype').replace('_drag','')+'"]');
+
+ i++;
}
var itemtype = $(el).attr('itemtype');
@@ -504,9 +573,8 @@ $(document).ready(function(){
var type = $(ui.helper).attr('itemtype');
$(ui.helper).attr('itemtype',type+"_drag");
},
- drag: function(ev,ui) {
- var collidingSelector = ($(ui.helper).attr('itemtype') =="pc_drag") ? '[itemtype="pc"]' : '.obstacle';
- if ($(ui.helper).collision(collidingSelector).length) {
+ drag: function(ev,ui) {
+ if ($(ui.helper).collision('[itemtype="'+$(ui.helper).attr('itemtype').replace('_drag','')+'"]').length) {
$(ui.helper).addClass('collides');
} else {
$(ui.helper).removeClass('collides');
diff --git a/modules-available/roomplanner/js/init.js b/modules-available/roomplanner/js/init.js
index 1d3c18fb..67090cc2 100644
--- a/modules-available/roomplanner/js/init.js
+++ b/modules-available/roomplanner/js/init.js
@@ -2,13 +2,6 @@
function initRoomplanner() {
- console.log('initRoomplanner');
-
- /* make it fullscreen, otherwise there are too many positioning bugs */
- $('.sidebar-bg, .navbar').hide();
- $('#mainpage').css('position', 'static').css('width', '100%').css('max-width', '100%').css('left', '0px');
-
-
$('#drawarea').css('top',(-roomplanner.settings.scale*10)+'px');
$('#drawarea').css('left',(-roomplanner.settings.scale*10)+'px');
@@ -28,8 +21,36 @@ function initRoomplanner() {
});
$("#saveBtn").click(function() {
- $('#serializedRoom').val(roomplanner.serialize());
- $('#roomForm').submit();
+ $('#saveBtn').prop('disabled', true);
+ $('#error-msg').hide();
+ $('#success-msg').hide();
+ $('#saving-msg').show();
+ var serializedCurrent = roomplanner.serialize();
+ $.post('?do=roomplanner&locationid=' + locationId,
+ { token: TOKEN, action: 'save', serializedRoom: serializedCurrent }
+ ).done(function ( data ) {
+ if (data.indexOf('SUCCESS') !== -1) {
+ window.close();
+ // If window.close() failed, we give some feedback and remember the state as saved
+ $('#success-msg').show();
+ plannerLoadState = serializedCurrent;
+ return;
+ }
+ $('#error-msg').text('Error: ' + data).show();
+ }).fail(function () {
+ $('#error-msg').text('AJAX save call failed').show();
+ }).always(function() {
+ $('#saveBtn').prop('disabled', false);
+ $('#saving-msg').hide();
+ });
+ });
+
+ $('#zoom-out').click(function() {
+ roomplanner.slider.slider('value', roomplanner.settings.scale - 10);
+ });
+
+ $('#zoom-in').click(function() {
+ roomplanner.slider.slider('value', roomplanner.settings.scale + 10);
});
}
diff --git a/modules-available/roomplanner/lang/de/template-tags.json b/modules-available/roomplanner/lang/de/template-tags.json
index bd7b3ce2..42da4a56 100644
--- a/modules-available/roomplanner/lang/de/template-tags.json
+++ b/modules-available/roomplanner/lang/de/template-tags.json
@@ -18,6 +18,7 @@
"lang_classroomtable": "Klassenzimmertisch",
"lang_coatrack": "Garderobe",
"lang_conferencetable": "Konferenztisch",
+ "lang_confirmDiscardChanges": "Wollen Sie alle \u00c4nderungen verwerfen?",
"lang_couch": "Couch",
"lang_descriptionBySearch": "Hier k\u00f6nnen aus der Liste aller bekannter Rechner suchen.",
"lang_descriptionBySubnet": "Hier sehen Sie Computer, die sich in den zum Raum geh\u00f6renden Subnetzen befinden.",
@@ -31,6 +32,8 @@
"lang_locker": "Schlie\u00dffach",
"lang_papertray": "Papierkorb",
"lang_photocopier": "Kopierer",
+ "lang_planBeingSaved": "Der Raumplan wird gespeichert...",
+ "lang_planSuccessfullySaved": "Plan erfolgreich gespeichert!",
"lang_plant": "Pflanze",
"lang_podium": "Podium",
"lang_printer": "Drucker",
diff --git a/modules-available/roomplanner/lang/en/template-tags.json b/modules-available/roomplanner/lang/en/template-tags.json
index 5dac56a8..5022ad06 100644
--- a/modules-available/roomplanner/lang/en/template-tags.json
+++ b/modules-available/roomplanner/lang/en/template-tags.json
@@ -18,6 +18,7 @@
"lang_classroomtable": "classroom table",
"lang_coatrack": "coatrack",
"lang_conferencetable": "conference table",
+ "lang_confirmDiscardChanges": "Do you want to discard all changes?",
"lang_couch": "couch",
"lang_descriptionBySearch": "Select a computer from a list of all known computers here.",
"lang_descriptionBySubnet": "Select a computer from a related subnet.",
@@ -31,6 +32,8 @@
"lang_locker": "locker",
"lang_papertray": "papertray",
"lang_photocopier": "photocopier",
+ "lang_planBeingSaved": "Saving room layout",
+ "lang_planSuccessfullySaved": "Layout successfully saved!",
"lang_plant": "plant",
"lang_podium": "podium",
"lang_printer": "printer",
diff --git a/modules-available/roomplanner/page.inc.php b/modules-available/roomplanner/page.inc.php
index 0d4b7ee9..2d3b5187 100644
--- a/modules-available/roomplanner/page.inc.php
+++ b/modules-available/roomplanner/page.inc.php
@@ -2,65 +2,95 @@
class Page_Roomplanner extends Page
{
- protected function doPreprocess()
- {
- User::load();
- if (!User::hasPermission('superadmin')) {
- Message::addError('main.no-permission');
- Util::redirect('?do=Main');
- }
- }
+ /**
+ * @var int locationid of location we're editing
+ */
+ private $locationid = false;
- protected function doRender()
- {
+ /**
+ * @var array location data from location table
+ */
+ private $location = false;
- $locationid = Request::get('locationid', null, 'integer');
+ /**
+ * @var string action to perform
+ */
+ private $action = false;
- if ($locationid === null) { die('please specify locationid'); }
+ private function loadRequestedLocation()
+ {
+ $this->locationid = Request::get('locationid', false, 'integer');
+ if ($this->locationid !== false) {
+ $this->location = Location::get($this->locationid);
+ }
+ }
- if (Request::get('pvs', false, 'bool')) {
- /* return a pvs-file */
- echo "<pre>";
- echo PvsGenerator::generate($locationid);
- echo "</pre>";
- die();
- }
+ protected function doPreprocess()
+ {
+ User::load();
- $furniture = $this->getFurniture($locationid);
- $subnetMachines = $this->getPotentialMachines($locationid);
- $machinesOnPlan = $this->getMachinesOnPlan($locationid);
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main');
+ }
- $action = Request::any('action', 'show', 'string');
+ $this->action = Request::any('action', 'show', 'string');
+ $this->loadRequestedLocation();
+ if ($this->locationid === false) {
+ Message::addError('need-locationid');
+ Util::redirect('?do=locations');
+ }
+ if ($this->location === false) {
+ Message::addError('locations.invalid-location-id', $this->locationid);
+ Util::redirect('?do=locations');
+ }
- $roomConfig = array_merge($furniture, $machinesOnPlan);
+ if ($this->action === 'save') {
+ $this->handleSaveRequest(false);
+ Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show");
+ }
+ Render::setTitle($this->location['locationname']);
+ }
+ protected function doRender()
+ {
+ /* deliver the pvs.ini file temporarily here */
+ /* TODO: Move this to api.inc.php */
+ if (Request::get('pvs', false, 'bool')) {
+ /* return a pvs-file */
+ echo "<pre>";
+ echo PvsGenerator::generate($locationid);
+ echo "</pre>";
+ die();
+ }
- if ($action === 'show') {
- /* do nothing */
- Render::addTemplate('page', [
- 'subnetMachines' => json_encode($subnetMachines),
- 'locationid' => $locationid,
- 'roomConfiguration' => json_encode($roomConfig)]);
- } else if ($action === 'save') {
- /* save */
- $config = Request::post('serializedRoom', null, 'string');
- $config = json_decode($config, true);
- $this->saveRoomConfig($locationid, $config['furniture']);
- $this->saveComputerConfig($locationid, $config['computers'], $machinesOnPlan);
- Util::redirect("?do=roomplanner&locationid=$locationid&action=show");
- }
+ if ($this->action === 'show') {
+ /* do nothing */
+ Dashboard::disable();
+ $furniture = $this->getFurniture();
+ $subnetMachines = $this->getPotentialMachines();
+ $machinesOnPlan = $this->getMachinesOnPlan();
+ $roomConfig = array_merge($furniture, $machinesOnPlan);
+ Render::addTemplate('page', [
+ 'location' => $this->location,
+ 'subnetMachines' => json_encode($subnetMachines),
+ 'locationid' => $this->locationid,
+ 'roomConfiguration' => json_encode($roomConfig)]);
+ } else {
+ Message::addError('main.invalid-action', $this->action);
+ }
- }
+ }
- protected function doAjax()
- {
- $action = Request::get('action', null, 'string');
+ protected function doAjax()
+ {
+ $this->action = Request::any('action', false, 'string');
- if ($action === 'getmachines') {
- $query = Request::get('query', null, 'string');
+ if ($this->action === 'getmachines') {
+ $query = Request::get('query', false, 'string');
- /* the query could be anything: UUID, IP or macaddr */
+ /* the query could be anything: UUID, IP or macaddr */
// $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
// . ', MATCH (machineuuid, macaddr, clientip, hostname) AGAINST (:query) AS relevance '
// . 'FROM machine '
@@ -69,97 +99,160 @@ class Page_Roomplanner extends Page
// . 'LIMIT 5'
// , ['query' => $query]);
//
- $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
- .'FROM machine '
- .'WHERE machineuuid LIKE :query '
- .' OR macaddr LIKE :query '
- .' OR clientip LIKE :query '
- .' OR hostname LIKE :query ', ['query' => "%$query%"]);
-
- $returnObject = ['machines' => []];
-
- while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
- $returnObject['machines'][] = $row;
- }
- echo json_encode($returnObject);
- }
- }
-
- protected function saveComputerConfig($locationid, $computers, $oldComputers) {
-
- $oldUuids = [];
- /* collect all uuids from the old computers */
- foreach($oldComputers['computers'] as $c) {
- $oldUuids[] = $c['muuid'];
- }
-
- $newUuids = [];
- foreach($computers as $computer) {
- $newUuids[] = $computer['muuid'];
-
- $position = json_encode(['gridRow' => $computer['gridRow'],
- 'gridCol' => $computer['gridCol'],
- 'itemlook' => $computer['itemlook']]);
-
- Database::exec('UPDATE machine SET position = :position, locationid = :locationid WHERE machineuuid = :muuid',
- ['locationid' => $locationid, 'muuid' => $computer['muuid'], 'position' => $position]);
- }
-
- $toDelete = array_diff($oldUuids, $newUuids);
-
- foreach($toDelete as $d) {
- Database::exec("UPDATE machine SET position = '', locationid = NULL WHERE machineuuid = :uuid", ['uuid' => $d]);
- }
- }
- protected function saveRoomConfig($locationid, $furniture) {
- $obj = json_encode(['furniture' => $furniture]);
- Database::exec('INSERT INTO location_roomplan (locationid, roomplan) VALUES (:locationid, :roomplan) ON DUPLICATE KEY UPDATE roomplan=:roomplan',
- ['locationid' => $locationid,
- 'roomplan' => $obj]);
- }
-
- protected function getFurniture($locationid) {
- $config = Database::queryFirst('SELECT roomplan FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $locationid]);
- if ($config == null) { return null; }
- $config = json_decode($config['roomplan'], true);
- return $config;
- }
- protected function getMachinesOnPlan($locationid) {
- $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname, position FROM machine WHERE locationid = :locationid',
- ['locationid' => $locationid]);
- $machines = [];
- while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
- $machine = [];
- $pos = json_decode($row['position'], true);
- // TODO: Check if pos is valid (has required keys)
-
- $machine['muuid'] = $row['machineuuid'];
- $machine['ip'] = $row['clientip'];
- $machine['mac_address'] = $row['macaddr'];
- $machine['hostname'] = $row['hostname'];
- $machine['gridRow'] = (int) $pos['gridRow'];
- $machine['gridCol'] = (int) $pos['gridCol'];
- $machine['itemlook'] = $pos['itemlook'];
- $machine['data-width'] = 100;
- $machine['data-height'] = 100;
- $machines[] = $machine;
- }
- return ['computers' => $machines];
- }
-
- protected function getPotentialMachines($locationid)
- {
- $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
- .'FROM machine INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) '
- .'WHERE subnet.locationid = :locationid', ['locationid' => $locationid]);
-
- $machines = [];
-
- while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
- $row['combined'] = implode(' ', array_values($row));
- $machines[] = $row;
- }
-
- return $machines;
- }
+ $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
+ . 'FROM machine '
+ . 'WHERE machineuuid LIKE :query '
+ . ' OR macaddr LIKE :query '
+ . ' OR clientip LIKE :query '
+ . ' OR hostname LIKE :query ', ['query' => "%$query%"]);
+
+ $returnObject = ['machines' => []];
+
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $returnObject['machines'][] = $row;
+ }
+ echo json_encode($returnObject);
+ } elseif ($this->action === 'save') {
+ $this->loadRequestedLocation();
+ if ($this->locationid === false) {
+ die('Missing locationid in save data');
+ }
+ if ($this->location === false) {
+ die('Location with id ' . $this->locationid . ' does not exist.');
+ }
+ $this->handleSaveRequest(true);
+ die('SUCCESS');
+ } else {
+ echo 'Invalid AJAX action';
+ }
+ }
+
+ private function handleSaveRequest($isAjax)
+ {
+ /* save */
+ $machinesOnPlan = $this->getMachinesOnPlan();
+ $config = Request::post('serializedRoom', null, 'string');
+ $config = json_decode($config, true);
+ if (!is_array($config) || !isset($config['furniture']) || !isset($config['computers'])) {
+ if ($isAjax) {
+ die('JSON data incomplete');
+ } else {
+ Message::addError('json-data-invalid');
+ Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show");
+ }
+ }
+ $this->saveRoomConfig($config['furniture']);
+ $this->saveComputerConfig($config['computers'], $machinesOnPlan);
+ }
+
+ private function sanitizeNumber(&$number, $lower, $upper)
+ {
+ if (!is_numeric($number) || $number < $lower) {
+ $number = $lower;
+ } elseif ($number > $upper) {
+ $number = $upper;
+ }
+ }
+
+ protected function saveComputerConfig($computers, $oldComputers)
+ {
+
+ $oldUuids = [];
+ /* collect all uuids from the old computers */
+ foreach ($oldComputers['computers'] as $c) {
+ $oldUuids[] = $c['muuid'];
+ }
+
+ $newUuids = [];
+ foreach ($computers as $computer) {
+ $newUuids[] = $computer['muuid'];
+
+ // Fix/sanitize properties
+ // TODO: The list of items, computers, etc. in general is copied and pasted in multiple places. We need a central definition with generators for the various formats we need it in
+ if (!isset($computer['itemlook']) || !in_array($computer['itemlook'], ['pc-north', 'pc-south', 'pc-west', 'pc-east', 'copier', 'telephone'])) {
+ $computer['itemlook'] = 'pc-north';
+ }
+ if (!isset($computer['gridRow'])) {
+ $computer['gridRow'] = 0;
+ } else {
+ $this->sanitizeNumber($computer['gridRow'], 0, 32 * 4);
+ }
+ if (!isset($computer['gridCol'])) {
+ $computer['gridCol'] = 0;
+ } else {
+ $this->sanitizeNumber($computer['gridCol'], 0, 32 * 4);
+ }
+
+ $position = json_encode(['gridRow' => $computer['gridRow'],
+ 'gridCol' => $computer['gridCol'],
+ 'itemlook' => $computer['itemlook']]);
+
+ Database::exec('UPDATE machine SET position = :position, locationid = :locationid WHERE machineuuid = :muuid',
+ ['locationid' => $this->locationid, 'muuid' => $computer['muuid'], 'position' => $position]);
+ }
+
+ $toDelete = array_diff($oldUuids, $newUuids);
+
+ foreach ($toDelete as $d) {
+ Database::exec("UPDATE machine SET position = '', locationid = NULL WHERE machineuuid = :uuid", ['uuid' => $d]);
+ }
+ }
+
+ protected function saveRoomConfig($furniture)
+ {
+ $obj = json_encode(['furniture' => $furniture]);
+ Database::exec('INSERT INTO location_roomplan (locationid, roomplan) VALUES (:locationid, :roomplan) ON DUPLICATE KEY UPDATE roomplan=:roomplan',
+ ['locationid' => $this->locationid,
+ 'roomplan' => $obj]);
+ }
+
+ protected function getFurniture()
+ {
+ $config = Database::queryFirst('SELECT roomplan FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]);
+ if ($config === false) {
+ return array();
+ }
+ $config = json_decode($config['roomplan'], true);
+ return $config;
+ }
+
+ protected function getMachinesOnPlan()
+ {
+ $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname, position FROM machine WHERE locationid = :locationid',
+ ['locationid' => $this->locationid]);
+ $machines = [];
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $machine = [];
+ $pos = json_decode($row['position'], true);
+ // TODO: Check if pos is valid (has required keys)
+
+ $machine['muuid'] = $row['machineuuid'];
+ $machine['ip'] = $row['clientip'];
+ $machine['mac_address'] = $row['macaddr'];
+ $machine['hostname'] = $row['hostname'];
+ $machine['gridRow'] = (int)$pos['gridRow'];
+ $machine['gridCol'] = (int)$pos['gridCol'];
+ $machine['itemlook'] = $pos['itemlook'];
+ $machine['data-width'] = 100;
+ $machine['data-height'] = 100;
+ $machines[] = $machine;
+ }
+ return ['computers' => $machines];
+ }
+
+ protected function getPotentialMachines()
+ {
+ $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname '
+ . 'FROM machine INNER JOIN subnet ON (INET_ATON(clientip) BETWEEN startaddr AND endaddr) '
+ . 'WHERE subnet.locationid = :locationid', ['locationid' => $this->locationid]);
+
+ $machines = [];
+
+ while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
+ $row['combined'] = implode(' ', array_values($row));
+ $machines[] = $row;
+ }
+
+ return $machines;
+ }
}
diff --git a/modules-available/roomplanner/style.css b/modules-available/roomplanner/style.css
index 1ec55969..a6e8a859 100644
--- a/modules-available/roomplanner/style.css
+++ b/modules-available/roomplanner/style.css
@@ -1,15 +1,27 @@
@CHARSET "UTF-8";
-body.split #drawpanel {
- width:60%;
- float: right;}
+/* override style to make the room planner full screen */
+body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+}
-body.full #drawpanel {
- width:90%;
- float: none;
- margin: 0 auto 20px;
- }
+.sidebar-bg, .navbar {
+ display: none;
+}
+
+#mainpage {
+ position: static;
+ width: 100%;
+ max-width: 100%;
+ left: 0px;
+ top: 0px;
+ margin: 0;
+}
+
+/* end full screen changes */
#drawpanel {
position:relative;}
@@ -22,7 +34,8 @@ body.full #drawpanel {
padding:0;
margin: 0;
overflow: hidden;
- height: 350px;}
+ height: 600px;
+}
#scaleContainer {
position: absolute;
@@ -35,6 +48,9 @@ body.full #drawpanel {
#scaleslider {
position:relative;}
+#zoom-out, #zoom-in {
+ cursor:pointer;
+}
#scaleContainer .glyphicon {
diff --git a/modules-available/roomplanner/templates/page.html b/modules-available/roomplanner/templates/page.html
index 262d7947..f4487ad2 100644
--- a/modules-available/roomplanner/templates/page.html
+++ b/modules-available/roomplanner/templates/page.html
@@ -42,14 +42,13 @@
</div>
<!-- berryous raumplaner -->
-</head>
-<body class="full">
- <h1>{{lang_roomplanner}}</h1>
+<h1>{{lang_roomplanner}} – {{location.locationname}}</h1>
+
+<div class="alert alert-danger" style="display:none" id="error-msg"></div>
+<div class="alert alert-success" style="display:none" id="success-msg">{{lang_planSuccessfullySaved}}</div>
+<div class="alert alert-info" style="display:none" id="saving-msg">{{lang_planBeingSaved}}</div>
<div id="toolpanel" class="panel panel-default" style="z-index:200;">
- <div class="panel-heading">
- <h3 class="panel-title">Werkzeuge</h3>
- </div>
<div class="panel-body">
<ul role="tablist" class="nav nav-tabs">
<li role="presentation" class="active"><a href="#computers"
@@ -123,13 +122,13 @@
<div itemtype="pc" itemlook="pc-south" class="draggable" obstacle=true style="width:100px; height:100px;" data-height="100" data-width="100" title="PC" noresize=1></div>
</li>
<li>
- <div itemtype="pc" itemlook="copier" class="draggable" obstacle=true style="width:200px; height:100px;" data-height="100" data-width="200" title="{{lang_photocopier}}" noresize=1></div>
+ <div itemtype="pc" itemlook="copier" class="draggable" obstacle=true style="width:100px; height:100px;" data-height="100" data-width="100" title="{{lang_photocopier}}" noresize=1></div>
</li>
<li>
<div itemtype="pc" itemlook="printer" class="draggable" obstacle=true style="width:100px; height:100px;" data-height="100" data-width="100" title="{{lang_printer}}" noresize=1></div>
</li>
<li>
- <div itemtype="pc" itemlook="telephone" class="draggable" obstacle=true style="width:50px; height:50px;" data-height="50" data-width="50" title="{{lang_telephone}}" noresize=1></div>
+ <div itemtype="pc" itemlook="telephone" class="draggable" obstacle=true style="width:100px; height:100px;" data-height="100" data-width="100" title="{{lang_telephone}}" noresize=1></div>
</li>
</ul>
</div>
@@ -303,40 +302,32 @@
</div>
<div id="scaleContainer">
<div id="scaleslider"></div>
- <span class="glyphicon glyphicon-zoom-out" aria-hidden="true"></span>
- <span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span>
+ <span id="zoom-out" class="glyphicon glyphicon-zoom-out" aria-hidden="true"></span>
+ <span id="zoom-in" class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span>
</div>
</div>
</div>
- <div class="panel panel-default" style="display:none">
- <div class="panel-heading"><h3 class="panel-title">Store / Restore</h3></div>
- <div class="panel-body">
- <form id="roomForm" method="POST" action="?do=roomplanner&locationid={{locationid}}">
- <input type="hidden" name="token" value="{{token}}">
- <input type="hidden" name="action" value="save">
- <div class="form-group">
- <label for="serializedRoom" class="col-sm-2 control-label">In-/Output</label>
- <textarea class="form-control" rows="5" name="serializedRoom" id="serializedRoom">{{{roomConfiguration}}}</textarea>
- </div>
- </form>
- </div>
- </div>
- <button id="saveBtn" class="btn btn-success" onclick="window.close(); return false">Save</button>
-
-</body>
-</html>
-
-
+<div class="pull-left">
+</div>
+<div class="pull-right">
+ <button class="btn btn-default" onclick="triggerCancel()">{{lang_cancel}}</button>
+ <button id="saveBtn" class="btn btn-primary">{{lang_save}}</button>
+</div>
+<div class="clearfix"></div>
<script type="application/javascript"><!--
+var locationId = '{{locationid}}';
+var subnetMachines, roomConfiguration;
+var plannerLoadState = 'invalid';
document.addEventListener("DOMContentLoaded", function () {
subnetMachines = {{{subnetMachines}}};
+ roomConfiguration = {{{roomConfiguration}}};
$.when(
$.getScript("modules/roomplanner/js/lib/jquery-collision.js"),
@@ -348,10 +339,24 @@ document.addEventListener("DOMContentLoaded", function () {
).done(function() {
$.getScript("modules/roomplanner/js/init.js", function() {
initRoomplanner();
- roomplanner.load($('#serializedRoom').val());
- console.log(subnetMachines);
+ loadRoom();
});
});
});
-</script>
+function loadRoom() {
+ roomplanner.load(roomConfiguration); // TODO: Filter invalid PCs, they're currently invisible and cannot be removed
+ plannerLoadState = roomplanner.serialize();
+}
+
+function triggerCancel() {
+ if (roomplanner.serialize() !== plannerLoadState) {
+ if (!confirm('{{lang_confirmDiscardChanges}}'))
+ return;
+ }
+ window.close();
+ // In case this page wasn't opened via JS, it will not close on modern browsers, so let's reset
+ loadRoom();
+}
+
+// --></script>
diff --git a/modules-available/session/lang/de/module.json b/modules-available/session/lang/de/module.json
new file mode 100644
index 00000000..0d2b001c
--- /dev/null
+++ b/modules-available/session/lang/de/module.json
@@ -0,0 +1,3 @@
+{
+ "page_title": "Anmelden"
+} \ No newline at end of file
diff --git a/modules-available/session/lang/en/module.json b/modules-available/session/lang/en/module.json
new file mode 100644
index 00000000..5fb22548
--- /dev/null
+++ b/modules-available/session/lang/en/module.json
@@ -0,0 +1,3 @@
+{
+ "page_title": "Log in"
+} \ No newline at end of file
diff --git a/style/default.css b/style/default.css
index a5450742..06833121 100644
--- a/style/default.css
+++ b/style/default.css
@@ -494,4 +494,65 @@ nav.navbar.sidebar {
.slx-smallsubmenu a {
color: #fff;
-} \ No newline at end of file
+}
+
+/* this is based on https://github.com/flatlogic/awesome-bootstrap-checkbox
+and "fixes" the style of radio buttons and check boxes.
+it only applies if they're in a container that has the checkbox class */
+
+.checkbox {
+ padding-left: 20px;
+}
+.checkbox label {
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+ padding-left: 5px;
+}
+.checkbox label::before {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ width: 17px;
+ height: 17px;
+ left: 0;
+ margin-left: -20px;
+ border: 1px solid #cccccc;
+ border-radius: 3px;
+ background-color: #fff;
+ -webkit-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+ -o-transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+ transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
+}
+.checkbox label::after {
+ display: inline-block;
+ position: absolute;
+ width: 16px;
+ height: 16px;
+ left: 0;
+ top: 0;
+ margin-left: -20px;
+ padding-left: 3px;
+ padding-top: 1px;
+ font-size: 11px;
+ color: #555555;
+}
+.checkbox input[type="checkbox"],
+.checkbox input[type="radio"] {
+ opacity: 0;
+ z-index: 1;
+ top: -6px;
+}
+
+
+.checkbox input[type="checkbox"]:focus + label::before,
+.checkbox input[type="radio"]:focus + label::before {
+ outline: thin dotted;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.checkbox input[type="checkbox"]:checked + label::after,
+.checkbox input[type="radio"]:checked + label::after {
+ font-family: "Glyphicons Halflings";
+ content: "\E013";
+}