summaryrefslogtreecommitdiffstats
path: root/modules/locations
diff options
context:
space:
mode:
authorJonathan Bauer2016-04-01 16:50:13 +0200
committerJonathan Bauer2016-04-01 16:50:13 +0200
commitdbc0d9614421e064cc62aacf116ebb783c83f2f3 (patch)
tree091844b8578ff1d9ac18edfd3cee3e63210133d7 /modules/locations
parent[ldapauth] Add homedir conf to ldap wizard (diff)
downloadslx-admin-dbc0d9614421e064cc62aacf116ebb783c83f2f3.tar.gz
slx-admin-dbc0d9614421e064cc62aacf116ebb783c83f2f3.tar.xz
slx-admin-dbc0d9614421e064cc62aacf116ebb783c83f2f3.zip
[merge] merging c3sl / fr - initial commit
Diffstat (limited to 'modules/locations')
-rw-r--r--modules/locations/config.json4
-rw-r--r--modules/locations/module.inc.php348
-rw-r--r--modules/locations/templates/location-subnets.html73
-rw-r--r--modules/locations/templates/locations.html96
-rw-r--r--modules/locations/templates/subnets.html35
5 files changed, 556 insertions, 0 deletions
diff --git a/modules/locations/config.json b/modules/locations/config.json
new file mode 100644
index 00000000..4e7fa5fb
--- /dev/null
+++ b/modules/locations/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"content",
+ "enabled":"true"
+}
diff --git a/modules/locations/module.inc.php b/modules/locations/module.inc.php
new file mode 100644
index 00000000..60af719b
--- /dev/null
+++ b/modules/locations/module.inc.php
@@ -0,0 +1,348 @@
+<?php
+
+class Page_Locations extends Page
+{
+
+ private $action;
+
+ /*
+ * Action handling
+ */
+
+ protected function doPreprocess()
+ {
+ User::load();
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+ $this->action = Request::post('action');
+ if ($this->action === 'updatelocation') {
+ $this->updateLocation();
+ } elseif ($this->action === 'addlocations') {
+ $this->addLocations();
+ }
+ }
+
+ private function addLocations()
+ {
+ $names = Request::post('newlocation', false);
+ $parents = Request::post('newparent', false);
+ if (!is_array($names) || !is_array($parents)) {
+ Message::addError('empty-field');
+ Util::redirect('?do=Locations');
+ }
+ $locs = Location::getLocations();
+ $count = 0;
+ foreach ($names as $idx => $name) {
+ $name = trim($name);
+ if (empty($name)) continue;
+ $parent = isset($parents[$idx]) ? (int)$parents[$idx] : 0;
+ if ($parent !== 0) {
+ $ok = false;
+ foreach ($locs as $loc) {
+ if ($loc['locationid'] == $parent) {
+ $ok = true;
+ }
+ }
+ if (!$ok) {
+ Message::addWarning('value-invalid', 'parentlocationid', $parent);
+ continue;
+ }
+ }
+ Database::exec("INSERT INTO location (parentlocationid, locationname)"
+ . " VALUES (:parent, :name)", array(
+ 'parent' => $parent,
+ 'name' => $name
+ ));
+ $count++;
+ }
+ Message::addSuccess('added-x-entries', $count);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocation()
+ {
+ $locationId = Request::post('locationid', false, 'integer');
+ $del = Request::post('deletelocation', false, 'integer');
+ if ($locationId === false) {
+ Message::addError('parameter-missing', 'locationid');
+ Util::redirect('?do=Locations');
+ }
+ $location = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location'
+ . ' WHERE locationid = :lid', array('lid' => $locationId));
+ if ($location === false) {
+ Message::addError('value-invalid', 'locationid', $locationId);
+ Util::redirect('?do=Locations');
+ }
+ // Delete location?
+ if ($locationId === $del) {
+ $this->deleteLocation($location);
+ }
+ // Update subnets
+ $this->updateLocationSubnets($location);
+ // Insert subnets
+ $this->addNewLocationSubnets($location); // TODO
+ // Update location!
+ $this->updateLocationData($location);
+ Util::redirect('?do=Locations');
+ }
+
+ private function deleteLocation($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $ids = $locationId;
+ if (Request::post('recursive', false) === 'on') {
+ $rows = Location::queryLocations();
+ $rows = Location::buildTree($rows, $locationId);
+ $rows = Location::extractIds($rows);
+ if (!empty($rows)) {
+ $ids .= ',' . implode(',', $rows);
+ }
+ }
+ $subs = Database::exec("DELETE FROM subnet WHERE locationid IN ($ids)");
+ $locs = Database::exec("DELETE FROM location WHERE locationid IN ($ids)");
+ Database::exec('UPDATE location SET parentlocationid = :newparent WHERE parentlocationid = :oldparent', array(
+ 'newparent' => $location['parentlocationid'],
+ 'oldparent' => $location['locationid']
+ ));
+ Message::addSuccess('location-deleted', $locs, $subs);
+ Util::redirect('?do=Locations');
+ }
+
+ private function updateLocationData($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $newParent = Request::post('parentlocationid', false, 'integer');
+ $newName = Request::post('locationname', false, 'string');
+ if ($newName === false || preg_match('/^\s*$/', $newName)) {
+ if ($newName !== false) {
+ Message::addWarning('value-invalid', 'location name', $newName);
+ }
+ $newName = $location['locationname'];
+ }
+ if ($newParent === false) {
+ $newParent = $location['parentlocationid'];
+ } else if ($newParent !== 0) {
+ $rows = Location::queryLocations();
+ $all = Location::extractIds(Location::buildTree($rows));
+ if (!in_array($newParent, $all) || $newParent === $locationId) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ } else {
+ $rows = Location::extractIds(Location::buildTree($rows, $locationId));
+ if (in_array($newParent, $rows)) {
+ Message::addWarning('value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ }
+ }
+ }
+ $ret = Database::exec('UPDATE location SET parentlocationid = :parent, locationname = :name'
+ . ' WHERE locationid = :lid', array(
+ 'lid' => $locationId,
+ 'parent' => $newParent,
+ 'name' => $newName
+ ));
+ if ($ret > 0) {
+ Message::addSuccess('location-updated', $newName);
+ }
+ }
+
+ private function updateLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ // Deletion first
+ $dels = Request::post('deletesubnet', false);
+ if (is_array($dels)) {
+ $count = 0;
+ $stmt = Database::prepare('DELETE FROM subnet WHERE subnetid = :id');
+ foreach ($dels as $key => $value) {
+ if (!is_numeric($key) || $value !== 'on') continue;
+ if ($stmt->execute(array('id' => $key))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-deleted', $count);
+ }
+ }
+ // Now actual updates
+ // TODO: Warn on mismatch/overlap (should lie entirely in parent's subnet, not overlap with others)
+ $starts = Request::post('startaddr', false);
+ $ends = Request::post('endaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('UPDATE subnet SET startaddr = :start, endaddr = :end'
+ . ' WHERE subnetid = :id');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('id' => $key, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-updated', $count);
+ }
+ }
+
+ private function addNewLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $starts = Request::post('newstartaddr', false);
+ $ends = Request::post('newendaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return;
+ }
+ $count = 0;
+ $stmt = Database::prepare('INSERT INTO subnet SET startaddr = :start, endaddr = :end, locationid = :location');
+ foreach ($starts as $key => $start) {
+ if (!isset($ends[$key]) || !is_numeric($key)) continue;
+ $end = $ends[$key];
+ list($startLong, $endLong) = $this->rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('value-invalid', 'new start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('value-invalid', 'new end addr', $start);
+ }
+ if ($startLong === false || $endLong === false) continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('value-invalid', 'range', $start . ' - ' . $end);
+ continue;
+ }
+ if ($stmt->execute(array('location' => $locationId, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-created', $count);
+ }
+ }
+
+ /*
+ * Rendering normal pages
+ */
+
+ protected function doRender()
+ {
+ //Render::setTitle(Dictionary::translate('lang_titleBackup'));
+ $getAction = Request::get('action');
+ if (empty($getAction)) {
+ // Until we have a main landing page?
+ Util::redirect('?do=Locations&action=showlocations');
+ }
+ if ($getAction === 'showsubnets') {
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr, locationid FROM subnet");
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $row['locations'] = Location::getLocations($row['locationid']);
+ $rows[] = $row;
+ }
+ Render::addTemplate('subnets', array('list' => $rows));
+ } elseif ($getAction === 'showlocations') {
+ $locs = Location::getLocations();
+ Render::addTemplate('locations', array('list' => $locs));
+ }
+ }
+
+ /*
+ * Ajax
+ */
+
+ protected function doAjax()
+ {
+ User::load();
+ if (!User::isLoggedIn()) {
+ die('Unauthorized');
+ }
+ $action = Request::any('action');
+ if ($action === 'showlocation') {
+ $this->ajaxShowLocation();
+ }
+ }
+
+ private function ajaxShowLocation()
+ {
+ $locationId = Request::any('locationid', 0, 'integer');
+ $loc = Database::queryFirst('SELECT locationid, parentlocationid, locationname FROM location WHERE locationid = :lid',
+ array('lid' => $locationId));
+ if ($loc === false) {
+ die('Unknown locationid');
+ }
+ $res = Database::simpleQuery("SELECT subnetid, startaddr, endaddr FROM subnet WHERE locationid = :lid",
+ array('lid' => $locationId));
+ $rows = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $row['startaddr'] = long2ip($row['startaddr']);
+ $row['endaddr'] = long2ip($row['endaddr']);
+ $rows[] = $row;
+ }
+ $data = array(
+ 'locationid' => $loc['locationid'],
+ 'locationname' => $loc['locationname'],
+ 'list' => $rows,
+ 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
+ );
+ // if (moduleEnabled(DOZMOD) {
+ $lectures = Database::queryFirst('SELECT Count(*) AS cnt FROM sat.lecture l '
+ . ' INNER JOIN sat.lecture_x_location ll ON (l.lectureid = ll.lectureid AND ll.locationid = :lid)',
+ array('lid' => $locationId));
+ $data['lectures'] = $lectures['cnt'];
+ // }
+ // Get clients matching this location's subnet(s)
+ $mres = Database::simpleQuery("SELECT lastseen, logintime FROM machine"
+ . " INNER JOIN subnet ON (INET_ATON(machine.clientip) BETWEEN startaddr AND endaddr)"
+ . " WHERE subnet.locationid = :lid OR machine.locationid = :lid", array('lid' => $locationId));
+ $count = $online = $used = 0;
+ $DL = time() - 605;
+ while ($row = $mres->fetch(PDO::FETCH_ASSOC)) {
+ $count++;
+ if ($row['lastseen'] > $DL) {
+ $online++;
+ if ($row['logintime'] != 0) {
+ $used++;
+ }
+ }
+ }
+ $data['machines'] = $count;
+ $data['machines_online'] = $online;
+ $data['machines_used'] = $used;
+ $data['used_percent'] = round(100 * $used / $online);
+ echo Render::parse('location-subnets', $data);
+ }
+
+ /*
+ * Helpers
+ */
+
+ private function rangeToLong($start, $end)
+ {
+ $startLong = ip2long($start);
+ $endLong = ip2long($end);
+ if ($startLong !== false) {
+ $startLong = sprintf("%u", $startLong);
+ }
+ if ($endLong !== false) {
+ $endLong = sprintf("%u", $endLong);
+ }
+ return array($startLong, $endLong);
+ }
+
+}
diff --git a/modules/locations/templates/location-subnets.html b/modules/locations/templates/location-subnets.html
new file mode 100644
index 00000000..76b7442a
--- /dev/null
+++ b/modules/locations/templates/location-subnets.html
@@ -0,0 +1,73 @@
+<div class="slx-well">
+ <div class="slx-bold">{{lang_locationSettings}}</div>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="updatelocation">
+ <input type="hidden" name="locationid" value="{{locationid}}">
+ <div style="display:none">
+ <button type="submit" class="btn btn-primary">Save</button>
+ </div>
+ <div class="row">
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{lang_name}}</span>
+ <input class="form-control" type="text" name="locationname" value="{{locationname}}" pattern=".*\S.*">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="input-group">
+ <span class="input-group-addon slx-ga2">{{lang_parentLocation}}</span>
+ <select class="form-control" name="parentlocationid">
+ {{#parents}}
+ <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/parents}}
+ </select>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="pull-right">
+ <label><input type="checkbox" name="recursive" value="on"> {{lang_deleteChildLocations}}</label>
+ <button type="submit" class="btn btn-sm btn-danger" name="deletelocation" value="{{locationid}}" onclick="return slxConfirm()">{{lang_deleteLocation}}</button>
+ </div>
+ <div class="clearfix"></div>
+ </div>
+ <br>
+ <div class="slx-bold">{{lang_assignedSubnets}}</div>
+ <div><i>{{lang_assignSubnetExplanation}}</i></div>
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>#</th>
+ <th>{{lang_startAddress}}</th>
+ <th>{{lang_endAddress}}</th>
+ <th title="{{lang_deleteSubnet}}"><span class="glyphicon glyphicon-trash"></span></th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{subnetid}}</td>
+ <td><input class="form-control" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td><input class="form-control" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"></td>
+ <td class="danger" align="center"><input type="checkbox" name="deletesubnet[{{subnetid}}]" value="on"></td>
+ </tr>
+ {{/list}}
+ <tr id="loc-sub-{{locationid}}">
+ <td colspan="2">
+ <button class="btn btn-success btn-sm" type="button" onclick="slxAddSubnetRow(this, {{locationid}})" title="{{lang_addNewSubnet}}">
+ <span class="glyphicon glyphicon-plus-sign"></span> {{lang_subnet}}
+ </button>
+ </td>
+ <td colspan="2" align="right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </div>
+ </tr>
+ </table>
+ </form>
+ <br>
+ <div class="slx-bold">{{lang_locationInfo}}</div>
+ <div>
+ <span class="slx-ga2">{{lang_referencingLectures}}:</span> {{lectures}}
+ </div>
+ <div>
+ <span class="slx-ga2">{{lang_matchingMachines}}:</span> <a href="?do=Statistics&amp;filter=location&amp;argument={{locationid}}">{{machines}} / {{machines_online}} / {{machines_used}} ({{used_percent}}%)</a>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules/locations/templates/locations.html b/modules/locations/templates/locations.html
new file mode 100644
index 00000000..76c8f97c
--- /dev/null
+++ b/modules/locations/templates/locations.html
@@ -0,0 +1,96 @@
+<div>
+ <div class="pull-right">
+ <a href="?do=Locations&amp;action=showsubnets">{{lang_thisListBySubnet}}</a>
+ </div>
+ <h1>{{lang_locationsMainHeading}}</h1>
+ <table class="table table-condensed" style="margin-bottom:0px">
+ <tr>
+ <th>#</th>
+ <th width="100%">{{lang_locationName}}</th>
+ <th></th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{locationid}}</td>
+ <td><div style="display:inline-block;width:{{depth}}em"></div>{{locationname}}</td>
+ <td align="right">
+ <a class="btn btn-success btn-xs" onclick="slxOpenLocation(this, {{locationid}})"><span class="glyphicon glyphicon-edit"></span> {{lang_edit}}</a>
+ </td>
+ </tr>
+ {{/list}}
+ </table>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="addlocations">
+ <table class="table table-condensed table-hover">
+ <tr id="lasttr">
+ <td>
+ <button class="btn btn-success btn-sm" type="button" onclick="slxAddLocationRow()">
+ <span class="glyphicon glyphicon-plus-sign"></span> {{lang_location}}
+ </button>
+ </td>
+ <td width="80%">&emsp;</td>
+ <td width="20%" align="right">
+ <button type="submit" class="btn btn-primary">{{lang_save}}</button>
+ </td>
+ </tr>
+ </table>
+ </form>
+</div>
+<script type="text/javascript"><!--
+var slxAddCounter = 0;
+var slxLastLocation = false;
+
+function slxAddLocationRow() {
+ var tr = $('#lasttr');
+ tr.before('<tr>\
+ <td>#</td>\
+ <td><input class="form-control" type="text" name="newlocation[' + slxAddCounter + ']" placeholder="{{lang_locationName}}" pattern=".*\\S.*"></td>\
+ <td><select class="form-control" name="newparent[' + slxAddCounter + ']">\
+ <option value="0">{{lang_noParent}}</option>\
+ {{#list}}<option value="{{locationid}}">{{locationpad}} {{locationname}}</option>{{/list}}\
+ </select></td>\
+ </tr>');
+ slxAddCounter++;
+}
+
+function slxOpenLocation(e, lid) {
+ if (slxLastLocation !== false) {
+ slxLastLocation.hide();
+ $(slxLastLocation).prev().removeClass('active slx-bold');
+ }
+ var existing = $('#location-details-' + lid);
+ if (existing.length > 0) {
+ if (existing.is(slxLastLocation)) {
+ slxLastLocation = false;
+ } else {
+ existing.show();
+ $(e).closest('tr').addClass('active slx-bold');
+ slxLastLocation = existing;
+ }
+ return;
+ }
+ var td = $('<td>').attr('colspan', '12').css('padding', '0px 0px 12px');
+ var tr = $('<tr>').attr('id', 'location-details-' + lid);
+ tr.append(td);
+ $(e).closest('tr').addClass('active slx-bold').after(tr);
+ td.load('?do=Locations&action=showlocation&locationid=' + lid);
+ slxLastLocation = tr;
+}
+
+function slxAddSubnetRow(e, lid) {
+ var tr = $('#loc-sub-' + lid);
+ tr.before('<tr>\
+ <td>#</td>\
+ <td><input class="form-control" type="text" name="newstartaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
+ <td><input class="form-control" type="text" name="newendaddr[' + slxAddCounter + ']" pattern="\\d{1,3}\.\\d{1,3}\.\\d{1,3}\.\\d{1,3}"></td>\
+ <td></td>\
+ </tr>');
+ slxAddCounter++;
+}
+
+function slxConfirm() {
+ return confirm('{{lang_areYouSureNoUndo}}');
+}
+ // -->
+</script>
diff --git a/modules/locations/templates/subnets.html b/modules/locations/templates/subnets.html
new file mode 100644
index 00000000..2294f42b
--- /dev/null
+++ b/modules/locations/templates/subnets.html
@@ -0,0 +1,35 @@
+<div>
+ <div class="pull-right">
+ <a href="?do=Locations&amp;action=showlocations">{{lang_thisListByLocation}}</a>
+ </div>
+ <h1>{{lang_listOfSubnets}}</h1>
+ <form method="post" action="?do=Locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="updatesubnets">
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>#</th>
+ <th>{{lang_startAddress}}</th>
+ <th>{{lang_endAddress}}</th>
+ <th>{{lang_location}}</th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>{{subnetid}}</td>
+ <td><input class="form-control" type="text" name="startaddr[{{subnetid}}]" value="{{startaddr}}"></td>
+ <td><input class="form-control" type="text" name="endaddr[{{subnetid}}]" value="{{endaddr}}"></td>
+ <td>
+ <select class="form-control" name="location[{{subnetid}}]">
+ {{#locations}}
+ <option value="{{locationid}}" {{#selected}}selected="selected"{{/selected}}>{{locationpad}} {{locationname}}</option>
+ {{/locations}}
+ </select>
+ </td>
+ </tr>
+ {{/list}}
+ </table>
+ <div>
+ <button type="submit" class="btn btn-primary">Späschohn (geht noch nicht!)</button>
+ </div>
+ </form>
+</div>