diff options
author | Jonathan Bauer | 2016-04-01 16:50:13 +0200 |
---|---|---|
committer | Jonathan Bauer | 2016-04-01 16:50:13 +0200 |
commit | dbc0d9614421e064cc62aacf116ebb783c83f2f3 (patch) | |
tree | 091844b8578ff1d9ac18edfd3cee3e63210133d7 /modules/locations | |
parent | [ldapauth] Add homedir conf to ldap wizard (diff) | |
download | slx-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.json | 4 | ||||
-rw-r--r-- | modules/locations/module.inc.php | 348 | ||||
-rw-r--r-- | modules/locations/templates/location-subnets.html | 73 | ||||
-rw-r--r-- | modules/locations/templates/locations.html | 96 | ||||
-rw-r--r-- | modules/locations/templates/subnets.html | 35 |
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&filter=location&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&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%"> </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&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> |