summaryrefslogtreecommitdiffstats
path: root/modules-available/roomplanner
diff options
context:
space:
mode:
authorChristian Klinger2016-09-07 16:56:39 +0200
committerChristian Klinger2016-09-07 16:56:39 +0200
commitd369776f806ed5f2429046802b5e4e447824465d (patch)
treeec6ac4d39e679146eb6e7e9600551731bffee52f /modules-available/roomplanner
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
Diffstat (limited to 'modules-available/roomplanner')
-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
8 files changed, 431 insertions, 223 deletions
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>