diff options
author | Christian Klinger | 2016-09-07 16:56:39 +0200 |
---|---|---|
committer | Christian Klinger | 2016-09-07 16:56:39 +0200 |
commit | d369776f806ed5f2429046802b5e4e447824465d (patch) | |
tree | ec6ac4d39e679146eb6e7e9600551731bffee52f /modules-available/roomplanner | |
parent | Added a pvs.ini file generator. (diff) | |
parent | [roomplanner] Sanity checks/fixups when saving computers, more user feedback,... (diff) | |
download | slx-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.json | 3 | ||||
-rw-r--r-- | modules-available/roomplanner/js/grid.js | 134 | ||||
-rw-r--r-- | modules-available/roomplanner/js/init.js | 39 | ||||
-rw-r--r-- | modules-available/roomplanner/lang/de/template-tags.json | 3 | ||||
-rw-r--r-- | modules-available/roomplanner/lang/en/template-tags.json | 3 | ||||
-rw-r--r-- | modules-available/roomplanner/page.inc.php | 369 | ||||
-rw-r--r-- | modules-available/roomplanner/style.css | 34 | ||||
-rw-r--r-- | modules-available/roomplanner/templates/page.html | 69 |
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> |