summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2019-07-31 16:58:14 +0200
committerSimon Rettberg2019-07-31 16:58:14 +0200
commit7c539fd8736b0ff9acafe32d857b2a2021d778e6 (patch)
treeb6a44076cba8443b2840340256b9829451214229
parent[locations] Optimize some functions in Location class (diff)
downloadslx-admin-7c539fd8736b0ff9acafe32d857b2a2021d778e6.tar.gz
slx-admin-7c539fd8736b0ff9acafe32d857b2a2021d778e6.tar.xz
slx-admin-7c539fd8736b0ff9acafe32d857b2a2021d778e6.zip
[locations] Add warnings/cleanup for bad machine to roomplan mappings
-rw-r--r--modules-available/locations/inc/autolocation.inc.php3
-rw-r--r--modules-available/locations/inc/location.inc.php91
-rw-r--r--modules-available/locations/inc/locationutil.inc.php203
-rw-r--r--modules-available/locations/lang/de/messages.json4
-rw-r--r--modules-available/locations/lang/de/template-tags.json10
-rw-r--r--modules-available/locations/lang/en/messages.json4
-rw-r--r--modules-available/locations/lang/en/template-tags.json10
-rw-r--r--modules-available/locations/page.inc.php637
-rw-r--r--modules-available/locations/pages/cleanup.inc.php93
-rw-r--r--modules-available/locations/pages/details.inc.php310
-rw-r--r--modules-available/locations/pages/locations.inc.php271
-rw-r--r--modules-available/locations/pages/subnets.inc.php79
-rw-r--r--modules-available/locations/templates/location-subnets.html1
-rw-r--r--modules-available/locations/templates/locations.html13
-rw-r--r--modules-available/locations/templates/mismatch-cleanup.html69
-rw-r--r--modules-available/locations/templates/subnets.html11
16 files changed, 1112 insertions, 697 deletions
diff --git a/modules-available/locations/inc/autolocation.inc.php b/modules-available/locations/inc/autolocation.inc.php
index 61507ebf..82c61251 100644
--- a/modules-available/locations/inc/autolocation.inc.php
+++ b/modules-available/locations/inc/autolocation.inc.php
@@ -43,6 +43,9 @@ class AutoLocation
Database::exec("UPDATE machine SET subnetlocationid = :lid WHERE machineuuid IN (:machines)",
['lid' => $lid, 'machines' => $machines]);
}
+ // While we're at it, try to fix invalid entries, having a fixedlocationid but no actual position information
+ Database::exec('UPDATE machine SET fixedlocationid = NULL
+ WHERE fixedlocationid IS NOT NULL AND Length(position) = 0');
}
}
diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php
index f1f05402..e35670cc 100644
--- a/modules-available/locations/inc/location.inc.php
+++ b/modules-available/locations/inc/location.inc.php
@@ -329,26 +329,30 @@ class Location
if ($uuid !== false) {
// Machine ip maps to a location, and we have a client supplied uuid (which might not be known if the client boots for the first time)
$uuidLoc = self::getFromMachineUuid($uuid);
- if ($uuidLoc === $ipLoc) {
+ if (self::isUuidLocationValid($uuidLoc, $ipLoc)) {
$locationId = $uuidLoc;
- } else if ($uuidLoc !== false) {
- // Validate that the location the IP maps to is in the chain we get using the
- // location determined by the uuid
- $uuidLocations = self::getLocationRootChain($uuidLoc);
- $ipLocations = self::getLocationRootChain($ipLoc);
- if (in_array($uuidLoc, $ipLocations) // UUID loc is further up, OK
- || (in_array($ipLoc, $uuidLocations) && count($ipLocations) + 1 >= count($uuidLocations)) // UUID is max one level deeper than IP loc, accept as well
- ) {
- // Close enough, allow
- $locationId = $uuidLoc;
- }
- // UUID and IP disagree too much, play safe and ignore the UUID
}
}
}
return $locationId;
}
+ public static function isUuidLocationValid($uuidLoc, $ipLoc)
+ {
+ if ($ipLoc !== false && $uuidLoc !== false && $uuidLoc !== $ipLoc) {
+ // Validate that the location the IP maps to is in the chain we get using the
+ // location determined by the uuid
+ $uuidLocations = self::getLocationRootChain($uuidLoc);
+ $ipLocations = self::getLocationRootChain($ipLoc);
+ if (count($ipLocations) + 2 >= count($uuidLocations) && in_array($ipLoc, $uuidLocations)) {
+ // UUID is max one level deeper than IP loc, accept
+ return true;
+ }
+ // UUID and IP disagree too much, play safe and ignore the UUID
+ }
+ return false;
+ }
+
/**
* Get all location IDs from the given location up to the root.
*
@@ -401,62 +405,6 @@ class Location
return $subnets;
}
- public static function getOverlappingSubnets(&$overlapSelf = false, &$overlapOther = false)
- {
- if ($overlapSelf === false && $overlapOther === false) {
- return;
- }
- $locs = self::getLocationsAssoc();
- $subnets = self::getSubnets();
- if ($overlapSelf) {
- $self = array();
- }
- if ($overlapOther) {
- $other = array();
- }
- $cnt = count($subnets);
- for ($i = 0; $i < $cnt; ++$i) {
- for ($j = $i + 1; $j < $cnt; ++$j) {
- if ($overlapSelf && $subnets[$i]['locationid'] === $subnets[$j]['locationid']
- && self::overlap($subnets[$i], $subnets[$j])
- ) {
- $self[$subnets[$i]['locationid']] = $subnets[$i]['locationid'];
- }
- if ($overlapOther && $subnets[$i]['locationid'] !== $subnets[$j]['locationid']
- && self::overlap($subnets[$i], $subnets[$j])
- ) {
- $a = min($subnets[$i]['locationid'], $subnets[$j]['locationid']);
- $b = max($subnets[$i]['locationid'], $subnets[$j]['locationid']);
- $other["$a|$b"] = array('lid1' => $subnets[$i]['locationid'], 'lid2' => $subnets[$j]['locationid']);
- }
- }
- }
- if ($overlapSelf) {
- $overlapSelf = array();
- foreach ($self as $entry) {
- if (!isset($locs[$entry]))
- continue;
- $overlapSelf[]['locationname'] = $locs[$entry]['locationname'];
- }
- }
- if ($overlapOther) {
- $overlapOther = array();
- foreach ($other as $entry) {
- if (!isset($locs[$entry['lid1']]) || !isset($locs[$entry['lid2']]))
- continue;
- if (in_array($entry['lid1'], $locs[$entry['lid2']]['parents']) || in_array($entry['lid2'], $locs[$entry['lid1']]['parents']))
- continue;
- if (isset($locs[$entry['lid1']])) {
- $entry['name1'] = $locs[$entry['lid1']]['locationname'];
- }
- if (isset($locs[$entry['lid2']])) {
- $entry['name2'] = $locs[$entry['lid2']]['locationname'];
- }
- $overlapOther[] = $entry;
- }
- }
- }
-
/**
* @return array|bool assoc array mapping from locationid to subnets
*/
@@ -535,9 +483,4 @@ class Location
}
}
- private static function overlap($net1, $net2)
- {
- return ($net1['startaddr'] <= $net2['endaddr'] && $net1['endaddr'] >= $net2['startaddr']);
- }
-
}
diff --git a/modules-available/locations/inc/locationutil.inc.php b/modules-available/locations/inc/locationutil.inc.php
index b3d9bbc7..6b18d864 100644
--- a/modules-available/locations/inc/locationutil.inc.php
+++ b/modules-available/locations/inc/locationutil.inc.php
@@ -1 +1,204 @@
<?php
+
+class LocationUtil
+{
+
+ /**
+ * @param array $overlapSelf List of locations which have subnet definitions that overlap with itself
+ * @param array $overlapOther List of location pairs which have overlapping subnets
+ */
+ public static function getOverlappingSubnets(&$overlapSelf = false, &$overlapOther = false)
+ {
+ if ($overlapSelf === false && $overlapOther === false) {
+ return;
+ }
+ $locs = Location::getLocationsAssoc();
+ $subnets = Location::getSubnets();
+ if ($overlapSelf) {
+ $self = array();
+ }
+ if ($overlapOther) {
+ $other = array();
+ }
+ $cnt = count($subnets);
+ for ($i = 0; $i < $cnt; ++$i) {
+ for ($j = $i + 1; $j < $cnt; ++$j) {
+ if ($overlapSelf && $subnets[$i]['locationid'] === $subnets[$j]['locationid']
+ && self::overlap($subnets[$i], $subnets[$j])
+ ) {
+ $self[$subnets[$i]['locationid']] = $subnets[$i]['locationid'];
+ }
+ if ($overlapOther && $subnets[$i]['locationid'] !== $subnets[$j]['locationid']
+ && self::overlap($subnets[$i], $subnets[$j])
+ ) {
+ $a = min($subnets[$i]['locationid'], $subnets[$j]['locationid']);
+ $b = max($subnets[$i]['locationid'], $subnets[$j]['locationid']);
+ $other["$a|$b"] = array('lid1' => $subnets[$i]['locationid'], 'lid2' => $subnets[$j]['locationid']);
+ }
+ }
+ }
+ if ($overlapSelf) {
+ $overlapSelf = array();
+ foreach ($self as $entry) {
+ if (!isset($locs[$entry]))
+ continue;
+ $overlapSelf[]['locationname'] = $locs[$entry]['locationname'];
+ }
+ }
+ if ($overlapOther) {
+ $overlapOther = array();
+ foreach ($other as $entry) {
+ if (!isset($locs[$entry['lid1']]) || !isset($locs[$entry['lid2']]))
+ continue;
+ if (in_array($entry['lid1'], $locs[$entry['lid2']]['parents']) || in_array($entry['lid2'], $locs[$entry['lid1']]['parents']))
+ continue;
+ if (isset($locs[$entry['lid1']])) {
+ $entry['name1'] = $locs[$entry['lid1']]['locationname'];
+ }
+ if (isset($locs[$entry['lid2']])) {
+ $entry['name2'] = $locs[$entry['lid2']]['locationname'];
+ }
+ $overlapOther[] = $entry;
+ }
+ }
+ }
+
+ /**
+ * Get information about machines where the location assigned by roomplanner
+ * mismatches what the subnet configuration says.
+ * If $locationId is 0, return list of all locations where a mismatch occurs,
+ * grouped by the location the client was assigned to via roomplanner.
+ * Otherwise, just return an assoc array with the requested locationid, name
+ * and a list of all clients that are wrongfully assigned to that room.
+ * @param int $locationId
+ * @return array
+ */
+ public static function getMachinesWithLocationMismatch($locationId = 0, $checkPerms = false)
+ {
+ $locationId = (int)$locationId;
+ if ($checkPerms) {
+ if ($locationId !== 0) {
+ // Query details for specific location -- use assert and fake array
+ User::assertPermission('.roomplanner.edit', $locationId);
+ $roomplannerLocs = [$locationId];
+ } else {
+ // Query summary for all locations -- get actual list
+ $roomplannerLocs = User::getAllowedLocations('.roomplanner.edit');
+ }
+ if (User::hasPermission('subnets.edit')) {
+ $ipLocs = [0];
+ } else {
+ $ipLocs = User::getAllowedLocations('location.edit.subnets');
+ }
+ if (in_array(0, $ipLocs)) {
+ $ipLocs = true;
+ }
+ if (in_array(0, $roomplannerLocs)) {
+ $roomplannerLocs = true;
+ }
+ if ($ipLocs === true && $roomplannerLocs === true) {
+ $checkPerms = false; // User can do everything
+ } elseif ($ipLocs === true || $roomplannerLocs === true) {
+ $combinedLocs = true;
+ } else {
+ $combinedLocs = array_unique(array_merge($ipLocs, $roomplannerLocs));
+ }
+ }
+ if ($checkPerms && empty($combinedLocs))
+ return [];
+ if ($locationId === 0) {
+ if (!$checkPerms || $combinedLocs === true) {
+ $extra = 'IS NOT NULL';
+ $params = [];
+ } else {
+ $extra = 'IN (:locs)';
+ $params = ['locs' => $combinedLocs];
+ }
+ $query = "SELECT subnetlocationid, fixedlocationid
+ FROM machine WHERE fixedlocationid $extra";
+ } else {
+ $query = "SELECT machineuuid, hostname, clientip, subnetlocationid, fixedlocationid
+ FROM machine WHERE fixedlocationid = :locationid";
+ $params = ['locationid' => $locationId];
+ }
+ $res = Database::simpleQuery($query, $params);
+ $return = [];
+ $locs = false;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['subnetlocationid'] === $row['fixedlocationid'])
+ continue;
+ if (Location::isUuidLocationValid((int)$row['fixedlocationid'], (int)$row['subnetlocationid']))
+ continue;
+ $lid = (int)$row['fixedlocationid'];
+ if (!isset($return[$lid])) {
+ if ($locs === false) {
+ $locs = Location::getLocationsAssoc();
+ }
+ $return[$lid] = [
+ 'locationid' => $lid,
+ 'locationname' => $locs[$lid]['locationname'],
+ 'clients' => [],
+ 'count' => 0,
+ ];
+ }
+ if ($locationId === 0) {
+ $return[$lid]['count']++;
+ } else {
+ $slid = (int)$row['subnetlocationid'];
+ $return[$lid]['clients'][] = [
+ 'machineuuid' => $row['machineuuid'],
+ 'hostname' => $row['hostname'],
+ 'clientip' => $row['clientip'],
+ 'iplocationid' => $slid,
+ 'iplocationname' => $locs[$slid]['locationname'],
+ 'canmove' => !$checkPerms || $ipLocs === true || in_array($slid, $ipLocs), // Can machine be moved to subnet's locationid?
+ ];
+ }
+ }
+ if (empty($return))
+ return $return;
+ if ($locationId === 0) {
+ return array_values($return);
+ } else {
+ return $return[$locationId];
+ }
+ }
+
+ private static function overlap($net1, $net2)
+ {
+ return ($net1['startaddr'] <= $net2['endaddr'] && $net1['endaddr'] >= $net2['startaddr']);
+ }
+
+ public static function rangeToLongVerbose($start, $end)
+ {
+ $result = self::rangeToLong($start, $end);
+ list($startLong, $endLong) = $result;
+ if ($startLong === false) {
+ Message::addWarning('main.value-invalid', 'start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('main.value-invalid', 'end addr', $start);
+ }
+ if ($startLong === false || $endLong === false)
+ return false;
+ if ($startLong > $endLong) {
+ Message::addWarning('main.value-invalid', 'range', $start . ' - ' . $end);
+ return false;
+ }
+ return $result;
+ }
+
+ public static 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-available/locations/lang/de/messages.json b/modules-available/locations/lang/de/messages.json
index 6b682c7e..44855daf 100644
--- a/modules-available/locations/lang/de/messages.json
+++ b/modules-available/locations/lang/de/messages.json
@@ -3,8 +3,12 @@
"invalid-location-id": "Ung\u00fcltige Orts-id: {{0}}",
"location-deleted": "Location wurde gel\u00f6scht (Locations: {{0}}, Subnets: {{1}})",
"location-updated": "Location {{0}} wurde aktualisiert",
+ "moved-n-machines": "{{0}} Rechner verschoben",
+ "no-mismatch-location": "Keine widerspr\u00fcchlichen Zuweisungen f\u00fcr diesen Ort",
"no-permission-location": "Keine Rechte f\u00fcr Ort {{0}}",
+ "no-valid-machines-selected": "Keine g\u00fcltigen Rechner ausgew\u00e4hlt",
"parameter-missing": "Fehlender Parameter: {{0}}",
+ "reset-n-machines": "{{0}} Rechner aus Raumplan entfernt",
"subnets-created": "Subnetze angelegt: {{0}}",
"subnets-deleted": "Subnetze gel\u00f6scht: {{0}}",
"subnets-updated": "Subnetze aktualisiert: {{0}}"
diff --git a/modules-available/locations/lang/de/template-tags.json b/modules-available/locations/lang/de/template-tags.json
index 96273ce4..45464e48 100644
--- a/modules-available/locations/lang/de/template-tags.json
+++ b/modules-available/locations/lang/de/template-tags.json
@@ -11,21 +11,31 @@
"lang_editConfigVariables": "Konfig.-Variablen",
"lang_editRoomplan": "Raumplan bearbeiten",
"lang_endAddress": "Endadresse",
+ "lang_fixMachineAssign": "Zuweisungen anzeigen\/aufheben",
+ "lang_ip": "IP-Adresse",
"lang_listOfSubnets": "Liste der Subnetze",
"lang_location": "Ort",
+ "lang_locationBySubnet": "Zugeh\u00f6rigkeit durch IP-Adresse",
"lang_locationInfo": "Details zu diesem Ort",
+ "lang_locationMismatch": "Fehlerhafte Rechnerzuweisung durch Raumplaner",
"lang_locationName": "Name",
"lang_locationOtherOverlap": "Achtung! Folgende Orte haben sich \u00fcberlappende Netzbereiche",
"lang_locationSelfOverlap": "Folgender Ort hat mehrere sich \u00fcberschneidende Netzbereiche",
"lang_locationSettings": "Raum\/Ort bearbeiten",
"lang_locationsMainHeading": "Verwaltung von R\u00e4umen\/Orten",
+ "lang_machine": "Rechner",
"lang_machineCount": "Rechner",
"lang_machineLoad": "Besetzt",
"lang_matchingMachines": "Enthaltene Rechner",
+ "lang_mismatchHeading": "Rechner mit widerspr\u00fcchlicher Raumzuweisung",
+ "lang_mismatchIntroText": "Die hier aufgelisteten Rechner wurden mittels des Raumplaners im oben genannten Raum platziert. Ihrer IP-Adresse nach fallen diese jedoch in einen anderen Raum (durch die f\u00fcr diesen definierten IP-Ranges). Wenn Sie die entsprechenden Rechner hier markieren und auf \"Zur\u00fccksetzen\" klicken, werden die Rechner aus dem oben genannten Raumplan entfernt. Wenn Sie stattdessen auf \"Verschieben\" klicken, werden die Rechner mit ihrer aktuellen Position aus dem jetzigen Raum in den eigentlichen Raum (siehe letzte Spalte der Tabelle) verschoben.",
+ "lang_moveMachines": "In durch Subnet zugeordneten Raum verschieben",
+ "lang_moveable": "Verschiebbar",
"lang_name": "Name",
"lang_overrideCount": "Angepasst",
"lang_parentLocation": "\u00dcbergeordneter Ort",
"lang_referencingLectures": "Veranstaltungen",
+ "lang_resetMachines": "Raumzuweisung zur\u00fccksetzen",
"lang_showRoomplan": "Raumplan anzeigen",
"lang_startAddress": "Startadresse",
"lang_subnet": "IP-Bereich",
diff --git a/modules-available/locations/lang/en/messages.json b/modules-available/locations/lang/en/messages.json
index 95788c47..80b99ff0 100644
--- a/modules-available/locations/lang/en/messages.json
+++ b/modules-available/locations/lang/en/messages.json
@@ -3,8 +3,12 @@
"invalid-location-id": "Invalid location id: {{0}}",
"location-deleted": "Location has been deleted (locations: {{0}}, subnets: {{1}})",
"location-updated": "Location {{0}} has been updated",
+ "moved-n-machines": "Moved {{0}} machines",
+ "no-mismatch-location": "No mismatches for this location",
"no-permission-location": "No permissions for location {{0}}",
+ "no-valid-machines-selected": "No valid machines selected",
"parameter-missing": "Missing parameter: {{0}}",
+ "reset-n-machines": "Removed {{0}} machines from room plan",
"subnets-created": "{{0}} subnet(s) created",
"subnets-deleted": "{{0}} subnet(s) deleted",
"subnets-updated": "{{0}} subnet(s) updated"
diff --git a/modules-available/locations/lang/en/template-tags.json b/modules-available/locations/lang/en/template-tags.json
index 64211e27..abcb7606 100644
--- a/modules-available/locations/lang/en/template-tags.json
+++ b/modules-available/locations/lang/en/template-tags.json
@@ -11,21 +11,31 @@
"lang_editConfigVariables": "Config vars",
"lang_editRoomplan": "Edit roomplan",
"lang_endAddress": "End address",
+ "lang_fixMachineAssign": "Fix or remove assignment",
+ "lang_ip": "IP address",
"lang_listOfSubnets": "List of subnets",
"lang_location": "Location",
+ "lang_locationBySubnet": "Location by IP address",
"lang_locationInfo": "Location details",
+ "lang_locationMismatch": "Mismatching assignment via room planner",
"lang_locationName": "Name",
"lang_locationOtherOverlap": "Warning! These locations have overlapping address ranges",
"lang_locationSelfOverlap": "The following location has multiple address ranges that are overlapping",
"lang_locationSettings": "Edit this room or location",
"lang_locationsMainHeading": "Manage rooms and locations",
+ "lang_machine": "Machine",
"lang_machineCount": "Clients",
"lang_machineLoad": "In use",
"lang_matchingMachines": "Matching clients",
+ "lang_mismatchHeading": "Machines with mismatching room plan assignment",
+ "lang_mismatchIntroText": "Machines listed here are assigned to the room above, but judging from their IP address, should actually be in another room (because of the IP range(s) assigned to that room). By selecting machines below and clicking \"reset\", they will be removed from their current room plan. If you choose \"move\", they will be transferred to the plan of the room they should actually belong to (see last column of table).",
+ "lang_moveMachines": "Move to room designated by IP address",
+ "lang_moveable": "Moveable",
"lang_name": "Name",
"lang_overrideCount": "Overridden",
"lang_parentLocation": "Parent location",
"lang_referencingLectures": "Assigned Lectures",
+ "lang_resetMachines": "Reset room assignment",
"lang_showRoomplan": "Show room plan",
"lang_startAddress": "Start address",
"lang_subnet": "IP range",
diff --git a/modules-available/locations/page.inc.php b/modules-available/locations/page.inc.php
index 658b4b18..97886bd2 100644
--- a/modules-available/locations/page.inc.php
+++ b/modules-available/locations/page.inc.php
@@ -3,7 +3,15 @@
class Page_Locations extends Page
{
- private $action;
+ private static function loadPage()
+ {
+ $page = Request::any('page', 'locations', 'string');
+ if (!in_array($page, ['locations', 'subnets', 'details', 'cleanup'])) {
+ Message::addError('main.invalid-action', $page);
+ Util::redirect('?do=Main');
+ }
+ require_once Page::getModule()->getDir() . '/pages/' . $page . '.inc.php';
+ }
/*
* Action handling
@@ -16,289 +24,14 @@ class Page_Locations extends Page
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
- $this->action = Request::post('action');
- if ($this->action === 'updatelocation') {
- $this->updateLocation();
- } elseif ($this->action === 'addlocations') {
- $this->addLocations();
- } elseif ($this->action === 'updatesubnets') {
- $this->updateSubnets();
- }
+ self::loadPage();
if (Request::isPost()) {
- Util::redirect('?do=locations');
- }
- }
-
- private function updateSubnets()
- {
- User::assertPermission('subnets.edit', NULL, '?do=locations');
- $count = 0;
- $starts = Request::post('startaddr', false);
- $ends = Request::post('endaddr', false);
- $locs = Request::post('location', false);
- if (!is_array($starts) || !is_array($ends) || !is_array($locs)) {
- Message::addError('main.empty-field');
- Util::redirect('?do=Locations');
- }
- $existingLocs = Location::getLocationsAssoc();
- $stmt = Database::prepare("UPDATE subnet SET startaddr = :startLong, endaddr = :endLong, locationid = :loc WHERE subnetid = :subnetid");
- foreach ($starts as $subnetid => $start) {
- if (!isset($ends[$subnetid]) || !isset($locs[$subnetid]))
- continue;
- $loc = (int)$locs[$subnetid];
- $end = $ends[$subnetid];
- if (!isset($existingLocs[$loc])) {
- Message::addError('main.value-invalid', 'locationid', $loc);
- continue;
- }
- $range = $this->rangeToLongVerbose($start, $end);
- if ($range === false)
- continue;
- list($startLong, $endLong) = $range;
- if ($stmt->execute(compact('startLong', 'endLong', 'loc', 'subnetid'))) {
- $count += $stmt->rowCount();
- }
- }
- AutoLocation::rebuildAll();
- Message::addSuccess('subnets-updated', $count);
- Util::redirect('?do=Locations');
- }
-
- private function addLocations()
- {
- $names = Request::post('newlocation', false);
- $parents = Request::post('newparent', []);
- if (!is_array($names) || !is_array($parents)) {
- Message::addError('main.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 (!User::hasPermission("location.add", $parent)) {
- Message::addError('no-permission-location', isset($locs[$parent]) ? $locs[$parent]['locationname'] : $parent);
- continue;
- }
- if ($parent !== 0) {
- $ok = false;
- foreach ($locs as $loc) {
- if ($loc['locationid'] == $parent) {
- $ok = true;
- }
- }
- if (!$ok) {
- Message::addWarning('main.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('main.value-invalid', 'locationid', $locationId);
- Util::redirect('?do=Locations');
- }
- $change = false;
- // Delete location?
- if ($locationId === $del) {
- User::assertPermission("location.delete", $locationId, '?do=locations');
- $this->deleteLocation($location);
- $change = true;
- }
- // Update subnets
- $change |= $this->updateLocationSubnets();
- // Insert subnets
- $change |= $this->addNewLocationSubnets($location);
- // Update location!
- $change |= $this->updateLocationData($location);
-
- if ($change) {
- // In case subnets or tree layout changed, recalc this
- AutoLocation::rebuildAll();
- }
- Util::redirect('?do=Locations');
- }
-
- private function deleteLocation($location)
- {
- $locationId = (int)$location['locationid'];
- if (Request::post('recursive', false) === 'on') {
- $rows = Location::queryLocations();
- $rows = Location::buildTree($rows, $locationId);
- $ids = Location::extractIds($rows);
- } else {
- $ids = [$locationId];
- }
- $locs = Database::exec("DELETE FROM location WHERE locationid IN (:ids)", ['ids' => $ids]);
- Database::exec('UPDATE location SET parentlocationid = :newparent WHERE parentlocationid = :oldparent', array(
- 'newparent' => $location['parentlocationid'],
- 'oldparent' => $location['locationid']
- ));
- AutoLocation::rebuildAll($ids);
- Message::addSuccess('location-deleted', $locs, implode(', ', $ids));
- 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 (!User::hasPermission('location.edit.name', $locationId)) {
- $newName = $location['locationname'];
- } elseif ($newName === false || preg_match('/^\s*$/', $newName)) {
- if ($newName !== false) {
- Message::addWarning('main.value-invalid', 'location name', $newName);
- }
- $newName = $location['locationname'];
- }
- if ($newParent === false || !User::hasPermission('location.edit.parent', $locationId)
- || !User::hasPermission('location.edit.parent', $newParent)
- || !User::hasPermission('location.edit.*', $location['parentlocationid'])) {
- $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('main.value-invalid', 'parent', $newParent);
- $newParent = $location['parentlocationid'];
- } else {
- $rows = Location::extractIds(Location::buildTree($rows, $locationId));
- if (in_array($newParent, $rows)) {
- Message::addWarning('main.value-invalid', 'parent', $newParent);
- $newParent = $location['parentlocationid'];
- }
+ $action = Request::post('action');
+ if (!SubPage::doPreprocess($action)) {
+ Message::addError('main.invalid-action', $action);
}
+ Util::redirect('?do=locations');
}
- // TODO: Check permissions for new parent (only if changed)
- $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);
- }
- return $newParent != $location['parentlocationid'];
- }
-
- private function updateLocationSubnets()
- {
- $locationId = Request::post('locationid', false, 'integer');
- if (!User::hasPermission('location.edit.subnets', $locationId))
- return false;
-
- $change = false;
-
- // 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);
- $change = true;
- }
- }
-
- // Now actual updates
- $starts = Request::post('startaddr', false);
- $ends = Request::post('endaddr', false);
- if (!is_array($starts) || !is_array($ends)) {
- return $change;
- }
- $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];
- $range = $this->rangeToLongVerbose($start, $end);
- if ($range === false)
- continue;
- list($startLong, $endLong) = $range;
- if ($stmt->execute(array('id' => $key, 'start' => $startLong, 'end' => $endLong))) {
- $count += $stmt->rowCount();
- }
- }
- if ($count > 0) {
- Message::addInfo('subnets-updated', $count);
- $change = true;
- }
- return $change;
- }
-
- private function addNewLocationSubnets($location)
- {
- $locationId = (int)$location['locationid'];
- if (!User::hasPermission('location.edit.subnets', $locationId))
- return false;
-
- $change = false;
- $starts = Request::post('newstartaddr', false);
- $ends = Request::post('newendaddr', false);
- if (!is_array($starts) || !is_array($ends)) {
- return $change;
- }
- $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('main.value-invalid', 'new start addr', $start);
- }
- if ($endLong === false) {
- Message::addWarning('main.value-invalid', 'new end addr', $start);
- }
- if ($startLong === false || $endLong === false)
- continue;
- if ($startLong > $endLong) {
- Message::addWarning('main.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);
- $change = true;
- }
- return $change;
}
/*
@@ -308,204 +41,12 @@ class Page_Locations extends Page
protected function doRender()
{
$getAction = Request::get('action', false, 'string');
- if ($getAction === false) {
- if (User::hasPermission('location.view')) {
- Util::redirect('?do=locations&action=showlocations');
- } elseif (User::hasPermission('subnets.edit')) {
- Util::redirect('?do=locations&action=showsubnets');
- } else {
- // Trigger permission denied by asserting non-existent permission
- User::assertPermission('location.view');
- }
- }
- if ($getAction === 'showsubnets') {
- User::assertPermission('subnets.edit', NULL, '?do=locations');
- $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') {
- $this->showLocationList();
- } else {
+ if (!SubPage::doRender($getAction)) {
+ Message::addError('main.invalid-action', $getAction);
Util::redirect('?do=locations');
}
}
- private function showLocationList()
- {
- // Warn admin about overlapping subnet definitions
- $overlapSelf = $overlapOther = true;
- Location::getOverlappingSubnets($overlapSelf, $overlapOther);
- //$locs = Location::getLocations(0, 0, false, true);
- $locationList = Location::getLocationsAssoc();
- unset($locationList[0]);
- // Statistics: Count machines for each subnet
- $unassigned = false;
- $unassignedLoad = 0;
-
- // Filter view: Remove locations we can't reach at all, but show parents to locations
- // we have permission to, so the tree doesn't look all weird
- $visibleLocationIds = $allowedLocationIds = User::getAllowedLocations("location.view");
- foreach ($allowedLocationIds as $lid) {
- if (!isset($locationList[$lid]))
- continue;
- $visibleLocationIds = array_merge($visibleLocationIds, $locationList[$lid]['parents']);
- }
- $visibleLocationIds = array_unique($visibleLocationIds);
- foreach (array_keys($locationList) as $lid) {
- if (User::hasPermission('.baseconfig.view', $lid)) {
- $visibleLocationIds[] = $lid;
- } else {
- $locationList[$lid]['havebaseconfig'] = false;
- }
- if (User::hasPermission('.sysconfig.config.view-list', $lid)) {
- $visibleLocationIds[] = $lid;
- } else {
- $locationList[$lid]['havesysconfig'] = false;
- }
- if (User::hasPermission('.statistics.view.list', $lid)) {
- $visibleLocationIds[] = $lid;
- } else {
- $locationList[$lid]['havestatistics'] = false;
- }
- if (User::hasPermission('.serversetup.ipxe.menu.assign', $lid)) {
- $visibleLocationIds[] = $lid;
- } else {
- $locationList[$lid]['haveipxe'] = false;
- }
- if (!in_array($lid, $visibleLocationIds)) {
- unset($locationList[$lid]);
- } elseif (!in_array($lid, $allowedLocationIds)) {
- $locationList[$lid]['show-only'] = true;
- }
- }
-
- // Client statistics
- if (Module::get('statistics') !== false) {
- $unassigned = 0;
- $extra = '';
- if (in_array(0, $allowedLocationIds)) {
- $extra = ' OR locationid IS NULL';
- }
- $res = Database::simpleQuery("SELECT locationid, Count(*) AS cnt, Sum(If(state = 'OCCUPIED', 1, 0)) AS used
- FROM machine WHERE (locationid IN (:allowedLocationIds) $extra) GROUP BY locationid", compact('allowedLocationIds'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $locId = (int)$row['locationid'];
- if (isset($locationList[$locId])) {
- $locationList[$locId]['clientCount'] = $row['cnt'];
- $locationList[$locId]['clientLoad'] = round(100 * $row['used'] / $row['cnt']) . ' %';
- } else {
- $unassigned += $row['cnt'];
- $unassignedLoad += $row['used'];
- }
- }
- unset($loc);
- foreach ($locationList as &$loc) {
- if (!in_array($loc['locationid'], $allowedLocationIds))
- continue;
- if (!isset($loc['clientCountSum'])) {
- $loc['clientCountSum'] = 0;
- }
- if (!isset($loc['clientCount'])) {
- $loc['clientCount'] = 0;
- $loc['clientLoad'] = '0%';
- }
- $loc['clientCountSum'] += $loc['clientCount'];
- foreach ($loc['parents'] as $pid) {
- if (!in_array($pid, $allowedLocationIds))
- continue;
- $locationList[(int)$pid]['hasChild'] = true;
- $locationList[(int)$pid]['clientCountSum'] += $loc['clientCount'];
- }
- }
- unset($loc);
- }
- // Show currently active sysconfig for each location
- $defaultConfig = false;
- if (Module::isAvailable('sysconfig')) {
- $confs = SysConfig::getAll();
- foreach ($confs as $conf) {
- if (strlen($conf['locs']) === 0)
- continue;
- $confLocs = explode(',', $conf['locs']);
- foreach ($confLocs as $locId) {
- settype($locId, 'int');
- if ($locId === 0) {
- $defaultConfig = $conf['title'];
- }
- if (!isset($locationList[$locId]))
- continue;
- $locationList[$locId] += array('configName' => $conf['title'], 'configClass' => 'slx-bold');
- }
- }
- $this->propagateFields($locationList, $defaultConfig, 'configName', 'configClass');
- }
- // Count overridden config vars
- if (Module::get('baseconfig') !== false) {
- $res = Database::simpleQuery("SELECT locationid, Count(*) AS cnt FROM `setting_location`
- WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $lid = (int)$row['locationid'];
- if (isset($locationList[$lid])) {
- $locationList[$lid]['overriddenVars'] = $row['cnt'];
- }
- }
- // Confusing because the count might be inaccurate within a branch
- //$this->propagateFields($locationList, '', 'overriddenVars', 'overriddenClass');
- }
- // Show ipxe menu
- if (Module::isAvailable('serversetup') && class_exists('IPxe')) {
- $res = Database::simpleQuery("SELECT ml.locationid, m.title, ml.defaultentryid FROM serversetup_menu m
- INNER JOIN serversetup_menu_location ml USING (menuid)
- WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $lid = (int)$row['locationid'];
- if (isset($locationList[$lid])) {
- if ($row['defaultentryid'] !== null) {
- $row['title'] .= '(*)';
- }
- $locationList[$lid]['customMenu'] = $row['title'];
- }
- }
- $this->propagateFields($locationList, '', 'customMenu', 'customMenuClass');
- }
-
- $addAllowedLocs = User::getAllowedLocations("location.add");
- $addAllowedList = Location::getLocations(0, 0, true);
- foreach ($addAllowedList as &$loc) {
- if (!in_array($loc["locationid"], $addAllowedLocs)) {
- $loc["disabled"] = "disabled";
- }
- }
- unset($loc);
-
- // Output
- $data = array(
- 'list' => array_values($locationList),
- 'havestatistics' => Module::get('statistics') !== false,
- 'havebaseconfig' => Module::get('baseconfig') !== false,
- 'havesysconfig' => Module::get('sysconfig') !== false,
- 'haveipxe' => Module::isAvailable('serversetup') && class_exists('IPxe'),
- 'overlapSelf' => $overlapSelf,
- 'overlapOther' => $overlapOther,
- 'haveOverlapSelf' => !empty($overlapSelf),
- 'haveOverlapOther' => !empty($overlapOther),
- 'unassignedCount' => $unassigned,
- 'unassignedLoad' => ($unassigned ? (round(($unassignedLoad / $unassigned) * 100) . ' %') : ''),
- 'defaultConfig' => $defaultConfig,
- 'addAllowedList' => array_values($addAllowedList),
- );
- // TODO: Buttons for config vars and sysconfig are currently always shown, as their availability
- // depends on permissions in the according modules, not this one
- Permission::addGlobalTags($data['perms'], NULL, ['subnets.edit', 'location.add']);
- Render::addTemplate('locations', $data);
- }
-
/*
* Ajax
*/
@@ -516,150 +57,10 @@ class Page_Locations extends Page
if (!User::isLoggedIn()) {
die('Unauthorized');
}
+ self::loadPage();
$action = Request::any('action');
- if ($action === 'showlocation') {
- $this->ajaxShowLocation();
- }
- }
-
- private function ajaxShowLocation()
- {
- $locationId = Request::any('locationid', 0, 'integer');
-
- User::assertPermission("location.view", $locationId);
-
- $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,
- 'roomplanner' => Module::get('roomplanner') !== false,
- 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
- );
-
- // Disable locations in the parent selector where the user cannot change to
- if (!User::hasPermission('location.edit.*', $loc['parentlocationid'])
- || !User::hasPermission('location.edit.parent', $locationId)) {
- $allowedLocs = [];
- } else {
- $allowedLocs = User::getAllowedLocations("location.edit.*");
- foreach ($data['parents'] as &$parent) {
- if (!(in_array($parent["locationid"], $allowedLocs) || $parent["locationid"] == $loc['parentlocationid'])) {
- $parent["disabled"] = "disabled";
- }
- }
- }
-
- if (Module::get('dozmod') !== false) {
- $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'];
- $data['haveDozmod'] = true;
- }
- // Get clients matching this location's subnet(s)
- $count = $online = $used = 0;
- if (Module::get('statistics') !== false) {
- $mres = Database::simpleQuery("SELECT state FROM machine"
- . " WHERE machine.locationid = :lid", array('lid' => $locationId));
- while ($row = $mres->fetch(PDO::FETCH_ASSOC)) {
- $count++;
- if ($row['state'] === 'IDLE') {
- $online++;
- }
- if ($row['state'] === 'OCCUPIED') {
- $online++;
- $used++;
- }
- }
- $data['haveStatistics'] = true;
- // Link
- if (User::hasPermission('.statistics.view.list')) {
- $data['statsLink'] = 'list';
- } elseif (User::hasPermission('.statistics.view.summary')) {
- $data['statsLink'] = 'summary';
- }
- }
- $data['machines'] = $count;
- $data['machines_online'] = $online;
- $data['machines_used'] = $used;
- $data['used_percent'] = $count === 0 ? 0 : round(($used / $count) * 100);
-
-
- Permission::addGlobalTags($data['perms'], $locationId, ['location.edit.name', 'location.edit.subnets', 'location.delete', '.roomplanner.edit'], 'save_button');
- if (empty($allowedLocs)) {
- $data['perms']['location']['edit']['parent']['disabled'] = 'disabled';
- } else {
- unset($data['perms']['save_button']);
- }
-
- 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);
- }
-
- private function rangeToLongVerbose($start, $end)
- {
- $result = $this->rangeToLong($start, $end);
- list($startLong, $endLong) = $result;
- if ($startLong === false) {
- Message::addWarning('main.value-invalid', 'start addr', $start);
- }
- if ($endLong === false) {
- Message::addWarning('main.value-invalid', 'end addr', $start);
- }
- if ($startLong === false || $endLong === false)
- return false;
- if ($startLong > $endLong) {
- Message::addWarning('main.value-invalid', 'range', $start . ' - ' . $end);
- return false;
- }
- return $result;
- }
-
- private function propagateFields(&$locationList, $defaultValue, $name, $class)
- {
- $depth = array();
- foreach ($locationList as &$loc) {
- $d = $loc['depth'];
- if (!isset($loc[$name])) {
- // Has no explicit config assignment
- if ($d === 0) {
- $loc[$name] = $defaultValue;
- } else {
- $loc[$name] = $depth[$d - 1];
- }
- $loc[$class] = 'gray';
- }
- $depth[$d] = $loc[$name];
- unset($depth[$d + 1]);
+ if (!SubPage::doAjax($action)) {
+ die('Invalid action ' . $action);
}
}
diff --git a/modules-available/locations/pages/cleanup.inc.php b/modules-available/locations/pages/cleanup.inc.php
new file mode 100644
index 00000000..d10dbac0
--- /dev/null
+++ b/modules-available/locations/pages/cleanup.inc.php
@@ -0,0 +1,93 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess($action)
+ {
+ if ($action === 'resetmachines') {
+ self::resetMachines();
+ return true;
+ }
+ if ($action === 'movemachines') {
+ self::moveMachines();
+ return true;
+ }
+ return false;
+ }
+
+ public static function doRender($action)
+ {
+ $list = self::loadForLocation();
+ if ($list === false)
+ return true;
+ Permission::addGlobalTags($list['perms'], NULL, ['subnets.edit', 'location.view']);
+ Render::addTemplate('mismatch-cleanup', $list);
+ return true;
+ }
+
+ public static function doAjax($action)
+ {
+ return false;
+ }
+
+ private static function resetMachines()
+ {
+ $delete = self::getSelectedMachines(true);
+ if ($delete === false)
+ return;
+ $num = Database::exec("UPDATE machine SET fixedlocationid = NULL, position = '' WHERE machineuuid IN (:machines)",
+ ['machines' => $delete]);
+ Message::addSuccess('reset-n-machines', $num);
+ }
+
+ private static function moveMachines()
+ {
+ $move = self::getSelectedMachines(false);
+ if ($move === false)
+ return;
+ // Move to subnet's location, or NULL if position field was empty (Which should never be the case)
+ $num = Database::exec("UPDATE machine SET fixedlocationid = If(Length(position) > 0, subnetlocationid, NULL) WHERE machineuuid IN (:machines)",
+ ['machines' => $move]);
+ Message::addSuccess('moved-n-machines', $num);
+ }
+
+ private static function getSelectedMachines($forDelete)
+ {
+ $list = self::loadForLocation();
+ if ($list === false)
+ return false;
+ $machines = Request::post('machines', false, 'array');
+ if ($machines === false) {
+ Message::addError('main.parameter-missing', 'machines');
+ return false;
+ }
+ $valid = array_map(function($item) use ($forDelete) {
+ return $item['canmove'] || $forDelete ? $item['machineuuid'] : 'x';
+ }, $list['clients']);
+ $retList = array_filter($machines, function($item) use ($valid) {
+ return in_array($item, $valid);
+ });
+ if (empty($retList)) {
+ Message::addError('no-valid-machines-selected');
+ return false;
+ }
+ return $retList;
+ }
+
+ private static function loadForLocation()
+ {
+ $locationid = Request::any('locationid', false, 'int');
+ if ($locationid === false) {
+ Message::addError('main.parameter-missing', 'locationid');
+ return false;
+ }
+ $list = LocationUtil::getMachinesWithLocationMismatch($locationid, true);
+ if (empty($list)) {
+ Message::addInfo('no-mismatch-location');
+ return false;
+ }
+ return $list;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/locations/pages/details.inc.php b/modules-available/locations/pages/details.inc.php
new file mode 100644
index 00000000..6acf31bf
--- /dev/null
+++ b/modules-available/locations/pages/details.inc.php
@@ -0,0 +1,310 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess($action)
+ {
+ if ($action === 'updatelocation') {
+ self::updateLocation();
+ return true;
+ }
+ return false;
+ }
+
+ public static function doRender($action)
+ {
+ return false;
+ }
+
+ public static function doAjax($action)
+ {
+ if ($action === 'showlocation') {
+ self::ajaxShowLocation();
+ return true;
+ }
+ return false;
+ }
+
+ private static 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('main.value-invalid', 'locationid', $locationId);
+ Util::redirect('?do=Locations');
+ }
+ $change = false;
+ // Delete location?
+ if ($locationId === $del) {
+ User::assertPermission("location.delete", $locationId, '?do=locations');
+ self::deleteLocation($location);
+ $change = true;
+ }
+ // Update subnets
+ $change |= self::updateLocationSubnets();
+ // Insert subnets
+ $change |= self::addNewLocationSubnets($location);
+ // Update location!
+ $change |= self::updateLocationData($location);
+
+ if ($change) {
+ // In case subnets or tree layout changed, recalc this
+ AutoLocation::rebuildAll();
+ }
+ Util::redirect('?do=Locations');
+ }
+
+ private static function deleteLocation($location)
+ {
+ $locationId = (int)$location['locationid'];
+ if (Request::post('recursive', false) === 'on') {
+ $rows = Location::queryLocations();
+ $rows = Location::buildTree($rows, $locationId);
+ $ids = Location::extractIds($rows);
+ } else {
+ $ids = [$locationId];
+ }
+ $locs = Database::exec("DELETE FROM location WHERE locationid IN (:ids)", ['ids' => $ids]);
+ Database::exec('UPDATE location SET parentlocationid = :newparent WHERE parentlocationid = :oldparent', array(
+ 'newparent' => $location['parentlocationid'],
+ 'oldparent' => $location['locationid']
+ ));
+ AutoLocation::rebuildAll($ids);
+ Message::addSuccess('location-deleted', $locs, implode(', ', $ids));
+ Util::redirect('?do=Locations');
+ }
+
+ private static function updateLocationData($location)
+ {
+ $locationId = (int)$location['locationid'];
+ $newParent = Request::post('parentlocationid', false, 'integer');
+ $newName = Request::post('locationname', false, 'string');
+ if (!User::hasPermission('location.edit.name', $locationId)) {
+ $newName = $location['locationname'];
+ } elseif ($newName === false || preg_match('/^\s*$/', $newName)) {
+ if ($newName !== false) {
+ Message::addWarning('main.value-invalid', 'location name', $newName);
+ }
+ $newName = $location['locationname'];
+ }
+ if ($newParent === false || !User::hasPermission('location.edit.parent', $locationId)
+ || !User::hasPermission('location.edit.parent', $newParent)
+ || !User::hasPermission('location.edit.*', $location['parentlocationid'])) {
+ $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('main.value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ } else {
+ $rows = Location::extractIds(Location::buildTree($rows, $locationId));
+ if (in_array($newParent, $rows)) {
+ Message::addWarning('main.value-invalid', 'parent', $newParent);
+ $newParent = $location['parentlocationid'];
+ }
+ }
+ }
+ // TODO: Check permissions for new parent (only if changed)
+ $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);
+ }
+ return $newParent != $location['parentlocationid'];
+ }
+
+ private static function updateLocationSubnets()
+ {
+ $locationId = Request::post('locationid', false, 'integer');
+ if (!User::hasPermission('location.edit.subnets', $locationId))
+ return false;
+
+ $change = false;
+
+ // 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);
+ $change = true;
+ }
+ }
+
+ // Now actual updates
+ $starts = Request::post('startaddr', false);
+ $ends = Request::post('endaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return $change;
+ }
+ $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];
+ $range = LocationUtil::rangeToLongVerbose($start, $end);
+ if ($range === false)
+ continue;
+ list($startLong, $endLong) = $range;
+ if ($stmt->execute(array('id' => $key, 'start' => $startLong, 'end' => $endLong))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ if ($count > 0) {
+ Message::addInfo('subnets-updated', $count);
+ $change = true;
+ }
+ return $change;
+ }
+
+ private static function addNewLocationSubnets($location)
+ {
+ $locationId = (int)$location['locationid'];
+ if (!User::hasPermission('location.edit.subnets', $locationId))
+ return false;
+
+ $change = false;
+ $starts = Request::post('newstartaddr', false);
+ $ends = Request::post('newendaddr', false);
+ if (!is_array($starts) || !is_array($ends)) {
+ return $change;
+ }
+ $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) = LocationUtil::rangeToLong($start, $end);
+ if ($startLong === false) {
+ Message::addWarning('main.value-invalid', 'new start addr', $start);
+ }
+ if ($endLong === false) {
+ Message::addWarning('main.value-invalid', 'new end addr', $start);
+ }
+ if ($startLong === false || $endLong === false)
+ continue;
+ if ($startLong > $endLong) {
+ Message::addWarning('main.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);
+ $change = true;
+ }
+ return $change;
+ }
+
+ private static function ajaxShowLocation()
+ {
+ $locationId = Request::any('locationid', 0, 'integer');
+
+ User::assertPermission("location.view", $locationId);
+
+ $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,
+ 'roomplanner' => Module::get('roomplanner') !== false,
+ 'parents' => Location::getLocations($loc['parentlocationid'], $locationId, true)
+ );
+
+ // Disable locations in the parent selector where the user cannot change to
+ if (!User::hasPermission('location.edit.*', $loc['parentlocationid'])
+ || !User::hasPermission('location.edit.parent', $locationId)) {
+ $allowedLocs = [];
+ } else {
+ $allowedLocs = User::getAllowedLocations("location.edit.*");
+ foreach ($data['parents'] as &$parent) {
+ if (!(in_array($parent["locationid"], $allowedLocs) || $parent["locationid"] == $loc['parentlocationid'])) {
+ $parent["disabled"] = "disabled";
+ }
+ }
+ }
+
+ if (Module::get('dozmod') !== false) {
+ $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'];
+ $data['haveDozmod'] = true;
+ }
+ // Get clients matching this location's subnet(s)
+ $count = $online = $used = 0;
+ if (Module::get('statistics') !== false) {
+ $mres = Database::simpleQuery("SELECT state FROM machine"
+ . " WHERE machine.locationid = :lid", array('lid' => $locationId));
+ while ($row = $mres->fetch(PDO::FETCH_ASSOC)) {
+ $count++;
+ if ($row['state'] === 'IDLE') {
+ $online++;
+ }
+ if ($row['state'] === 'OCCUPIED') {
+ $online++;
+ $used++;
+ }
+ }
+ $data['haveStatistics'] = true;
+ // Link
+ if (User::hasPermission('.statistics.view.list')) {
+ $data['statsLink'] = 'list';
+ } elseif (User::hasPermission('.statistics.view.summary')) {
+ $data['statsLink'] = 'summary';
+ }
+ }
+ $data['machines'] = $count;
+ $data['machines_online'] = $online;
+ $data['machines_used'] = $used;
+ $data['used_percent'] = $count === 0 ? 0 : round(($used / $count) * 100);
+
+
+ Permission::addGlobalTags($data['perms'], $locationId, ['location.edit.name', 'location.edit.subnets', 'location.delete', '.roomplanner.edit'], 'save_button');
+ if (empty($allowedLocs)) {
+ $data['perms']['location']['edit']['parent']['disabled'] = 'disabled';
+ } else {
+ unset($data['perms']['save_button']);
+ }
+
+ echo Render::parse('location-subnets', $data);
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/locations/pages/locations.inc.php b/modules-available/locations/pages/locations.inc.php
new file mode 100644
index 00000000..275aafdb
--- /dev/null
+++ b/modules-available/locations/pages/locations.inc.php
@@ -0,0 +1,271 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess($action)
+ {
+ if ($action === 'addlocations') {
+ self::addLocations();
+ return true;
+ }
+ return false;
+ }
+
+ public static function doRender($getAction)
+ {
+ if ($getAction === false) {
+ if (User::hasPermission('location.view')) {
+ // OK
+ } elseif (User::hasPermission('subnets.edit')) {
+ // Fallback to something else?
+ Util::redirect('?do=locations&page=subnets');
+ } else {
+ // Trigger permission denied by asserting non-existent permission
+ User::assertPermission('location.view');
+ }
+ }
+ if ($getAction === false) {
+ self::showLocationList();
+ return true;
+ }
+ return false;
+ }
+
+ public static function doAjax($action)
+ {
+ return false;
+ }
+
+ private static function addLocations()
+ {
+ $names = Request::post('newlocation', false);
+ $parents = Request::post('newparent', []);
+ if (!is_array($names) || !is_array($parents)) {
+ Message::addError('main.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 (!User::hasPermission("location.add", $parent)) {
+ Message::addError('no-permission-location', isset($locs[$parent]) ? $locs[$parent]['locationname'] : $parent);
+ continue;
+ }
+ if ($parent !== 0) {
+ $ok = false;
+ foreach ($locs as $loc) {
+ if ($loc['locationid'] == $parent) {
+ $ok = true;
+ }
+ }
+ if (!$ok) {
+ Message::addWarning('main.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');
+ }
+
+ public static function showLocationList()
+ {
+ // Warn admin about overlapping subnet definitions
+ $overlapSelf = $overlapOther = true;
+ LocationUtil::getOverlappingSubnets($overlapSelf, $overlapOther);
+ // Find machines assigned to a room with a UUID mismatch
+ $mismatchMachines = LocationUtil::getMachinesWithLocationMismatch(0, true);
+ $locationList = Location::getLocationsAssoc();
+ unset($locationList[0]);
+ // Statistics: Count machines for each subnet
+ $unassigned = false;
+ $unassignedLoad = 0;
+
+ // Filter view: Remove locations we can't reach at all, but show parents to locations
+ // we have permission to, so the tree doesn't look all weird
+ $visibleLocationIds = $allowedLocationIds = User::getAllowedLocations("location.view");
+ foreach ($allowedLocationIds as $lid) {
+ if (!isset($locationList[$lid]))
+ continue;
+ $visibleLocationIds = array_merge($visibleLocationIds, $locationList[$lid]['parents']);
+ }
+ $visibleLocationIds = array_unique($visibleLocationIds);
+ foreach (array_keys($locationList) as $lid) {
+ if (User::hasPermission('.baseconfig.view', $lid)) {
+ $visibleLocationIds[] = $lid;
+ } else {
+ $locationList[$lid]['havebaseconfig'] = false;
+ }
+ if (User::hasPermission('.sysconfig.config.view-list', $lid)) {
+ $visibleLocationIds[] = $lid;
+ } else {
+ $locationList[$lid]['havesysconfig'] = false;
+ }
+ if (User::hasPermission('.statistics.view.list', $lid)) {
+ $visibleLocationIds[] = $lid;
+ } else {
+ $locationList[$lid]['havestatistics'] = false;
+ }
+ if (User::hasPermission('.serversetup.ipxe.menu.assign', $lid)) {
+ $visibleLocationIds[] = $lid;
+ } else {
+ $locationList[$lid]['haveipxe'] = false;
+ }
+ if (!in_array($lid, $visibleLocationIds)) {
+ unset($locationList[$lid]);
+ } elseif (!in_array($lid, $allowedLocationIds)) {
+ $locationList[$lid]['show-only'] = true;
+ }
+ }
+
+ // Client statistics
+ if (Module::get('statistics') !== false) {
+ $unassigned = 0;
+ $extra = '';
+ if (in_array(0, $allowedLocationIds)) {
+ $extra = ' OR locationid IS NULL';
+ }
+ $res = Database::simpleQuery("SELECT locationid, Count(*) AS cnt, Sum(If(state = 'OCCUPIED', 1, 0)) AS used
+ FROM machine WHERE (locationid IN (:allowedLocationIds) $extra) GROUP BY locationid", compact('allowedLocationIds'));
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $locId = (int)$row['locationid'];
+ if (isset($locationList[$locId])) {
+ $locationList[$locId]['clientCount'] = $row['cnt'];
+ $locationList[$locId]['clientLoad'] = round(100 * $row['used'] / $row['cnt']) . ' %';
+ } else {
+ $unassigned += $row['cnt'];
+ $unassignedLoad += $row['used'];
+ }
+ }
+ unset($loc);
+ foreach ($locationList as &$loc) {
+ if (!in_array($loc['locationid'], $allowedLocationIds))
+ continue;
+ if (!isset($loc['clientCountSum'])) {
+ $loc['clientCountSum'] = 0;
+ }
+ if (!isset($loc['clientCount'])) {
+ $loc['clientCount'] = 0;
+ $loc['clientLoad'] = '0%';
+ }
+ $loc['clientCountSum'] += $loc['clientCount'];
+ foreach ($loc['parents'] as $pid) {
+ if (!in_array($pid, $allowedLocationIds))
+ continue;
+ $locationList[(int)$pid]['hasChild'] = true;
+ $locationList[(int)$pid]['clientCountSum'] += $loc['clientCount'];
+ }
+ }
+ unset($loc);
+ }
+ // Show currently active sysconfig for each location
+ $defaultConfig = false;
+ if (Module::isAvailable('sysconfig')) {
+ $confs = SysConfig::getAll();
+ foreach ($confs as $conf) {
+ if (strlen($conf['locs']) === 0)
+ continue;
+ $confLocs = explode(',', $conf['locs']);
+ foreach ($confLocs as $locId) {
+ settype($locId, 'int');
+ if ($locId === 0) {
+ $defaultConfig = $conf['title'];
+ }
+ if (!isset($locationList[$locId]))
+ continue;
+ $locationList[$locId] += array('configName' => $conf['title'], 'configClass' => 'slx-bold');
+ }
+ }
+ self::propagateFields($locationList, $defaultConfig, 'configName', 'configClass');
+ }
+ // Count overridden config vars
+ if (Module::get('baseconfig') !== false) {
+ $res = Database::simpleQuery("SELECT locationid, Count(*) AS cnt FROM `setting_location`
+ WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $lid = (int)$row['locationid'];
+ if (isset($locationList[$lid])) {
+ $locationList[$lid]['overriddenVars'] = $row['cnt'];
+ }
+ }
+ // Confusing because the count might be inaccurate within a branch
+ //$this->propagateFields($locationList, '', 'overriddenVars', 'overriddenClass');
+ }
+ // Show ipxe menu
+ if (Module::isAvailable('serversetup') && class_exists('IPxe')) {
+ $res = Database::simpleQuery("SELECT ml.locationid, m.title, ml.defaultentryid FROM serversetup_menu m
+ INNER JOIN serversetup_menu_location ml USING (menuid)
+ WHERE locationid IN (:allowedLocationIds) GROUP BY locationid", compact('allowedLocationIds'));
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $lid = (int)$row['locationid'];
+ if (isset($locationList[$lid])) {
+ if ($row['defaultentryid'] !== null) {
+ $row['title'] .= '(*)';
+ }
+ $locationList[$lid]['customMenu'] = $row['title'];
+ }
+ }
+ self::propagateFields($locationList, '', 'customMenu', 'customMenuClass');
+ }
+
+ $addAllowedLocs = User::getAllowedLocations("location.add");
+ $addAllowedList = Location::getLocations(0, 0, true);
+ foreach ($addAllowedList as &$loc) {
+ if (!in_array($loc["locationid"], $addAllowedLocs)) {
+ $loc["disabled"] = "disabled";
+ }
+ }
+ unset($loc);
+
+ // Output
+ $data = array(
+ 'list' => array_values($locationList),
+ 'havestatistics' => Module::get('statistics') !== false,
+ 'havebaseconfig' => Module::get('baseconfig') !== false,
+ 'havesysconfig' => Module::get('sysconfig') !== false,
+ 'haveipxe' => Module::isAvailable('serversetup') && class_exists('IPxe'),
+ 'overlapSelf' => $overlapSelf,
+ 'overlapOther' => $overlapOther,
+ 'mismatchMachines' => $mismatchMachines,
+ 'unassignedCount' => $unassigned,
+ 'unassignedLoad' => ($unassigned ? (round(($unassignedLoad / $unassigned) * 100) . ' %') : ''),
+ 'defaultConfig' => $defaultConfig,
+ 'addAllowedList' => array_values($addAllowedList),
+ );
+ // TODO: Buttons for config vars and sysconfig are currently always shown, as their availability
+ // depends on permissions in the according modules, not this one
+ Permission::addGlobalTags($data['perms'], NULL, ['subnets.edit', 'location.add']);
+ Render::addTemplate('locations', $data);
+ }
+
+ private static function propagateFields(&$locationList, $defaultValue, $name, $class)
+ {
+ $depth = array();
+ foreach ($locationList as &$loc) {
+ $d = $loc['depth'];
+ if (!isset($loc[$name])) {
+ // Has no explicit config assignment
+ if ($d === 0) {
+ $loc[$name] = $defaultValue;
+ } else {
+ $loc[$name] = $depth[$d - 1];
+ }
+ $loc[$class] = 'gray';
+ }
+ $depth[$d] = $loc[$name];
+ unset($depth[$d + 1]);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/locations/pages/subnets.inc.php b/modules-available/locations/pages/subnets.inc.php
new file mode 100644
index 00000000..6c37129a
--- /dev/null
+++ b/modules-available/locations/pages/subnets.inc.php
@@ -0,0 +1,79 @@
+<?php
+
+class SubPage
+{
+
+ public static function doPreprocess($action)
+ {
+ if ($action === 'updatesubnets') {
+ self::updateSubnets();
+ return true;
+ }
+ return false;
+ }
+
+ private static function updateSubnets()
+ {
+ User::assertPermission('subnets.edit', NULL, '?do=locations');
+ $count = 0;
+ $starts = Request::post('startaddr', false);
+ $ends = Request::post('endaddr', false);
+ $locs = Request::post('location', false);
+ if (!is_array($starts) || !is_array($ends) || !is_array($locs)) {
+ Message::addError('main.empty-field');
+ Util::redirect('?do=Locations');
+ }
+ $existingLocs = Location::getLocationsAssoc();
+ $stmt = Database::prepare("UPDATE subnet SET startaddr = :startLong, endaddr = :endLong, locationid = :loc WHERE subnetid = :subnetid");
+ foreach ($starts as $subnetid => $start) {
+ if (!isset($ends[$subnetid]) || !isset($locs[$subnetid]))
+ continue;
+ $loc = (int)$locs[$subnetid];
+ $end = $ends[$subnetid];
+ if (!isset($existingLocs[$loc])) {
+ Message::addError('main.value-invalid', 'locationid', $loc);
+ continue;
+ }
+ $range = LocationUtil::rangeToLongVerbose($start, $end);
+ if ($range === false)
+ continue;
+ list($startLong, $endLong) = $range;
+ if ($stmt->execute(compact('startLong', 'endLong', 'loc', 'subnetid'))) {
+ $count += $stmt->rowCount();
+ }
+ }
+ AutoLocation::rebuildAll();
+ Message::addSuccess('subnets-updated', $count);
+ Util::redirect('?do=Locations');
+ }
+
+ public static function doRender($getAction)
+ {
+ if ($getAction === false) {
+ User::assertPermission('subnets.edit', NULL, '?do=locations');
+ $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;
+ }
+ $data = array('list' => $rows);
+ Permission::addGlobalTags($data['perms'], NULL, ['location.view']);
+ Render::addTemplate('subnets', $data);
+ return true;
+ }
+ return false;
+ }
+
+ public static function doAjax($action)
+ {
+ return false;
+ }
+
+ /*
+ * Helpers
+ */
+
+} \ No newline at end of file
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
index 78ef99a0..6062b559 100644
--- a/modules-available/locations/templates/location-subnets.html
+++ b/modules-available/locations/templates/location-subnets.html
@@ -2,6 +2,7 @@
<div class="slx-bold">{{lang_locationSettings}}</div>
<form id="locationForm{{locationid}}" method="post" action="?do=Locations">
<input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="page" value="details">
<input type="hidden" name="action" value="updatelocation">
<input type="hidden" name="locationid" value="{{locationid}}">
<div style="display:none">
diff --git a/modules-available/locations/templates/locations.html b/modules-available/locations/templates/locations.html
index 7bdf3038..a63e3b8c 100644
--- a/modules-available/locations/templates/locations.html
+++ b/modules-available/locations/templates/locations.html
@@ -1,10 +1,10 @@
<div>
<div class="btn-group pull-right">
- <a href="?do=Locations&amp;action=showlocations" class="btn btn-default active">
+ <a href="?do=Locations&amp;page=locations" class="btn btn-default active">
<span class="glyphicon glyphicon-home"></span>
{{lang_thisListByLocation}}
</a>
- <a href="?do=Locations&amp;action=showsubnets" class="btn btn-default {{perms.subnets.edit.disabled}}">
+ <a href="?do=Locations&amp;page=subnets" class="btn btn-default {{perms.subnets.edit.disabled}}">
<span class="glyphicon glyphicon-list-alt"></span>
{{lang_thisListBySubnet}}
</a>
@@ -21,6 +21,12 @@
{{lang_locationOtherOverlap}}: <b>{{name1}}</b> – <b>{{name2}}</b>
</div>
{{/overlapOther}}
+ {{#mismatchMachines}}
+ <div class="alert alert-danger">
+ <div class="pull-right"><a href="?do=locations&amp;page=cleanup&amp;locationid={{locationid}}">{{lang_fixMachineAssign}} &raquo;</a></div>
+ {{lang_locationMismatch}}: <b>{{locationname}}</b> ({{lang_machineCount}}: <b>{{count}}</b>)
+ </div>
+ {{/mismatchMachines}}
<table class="table table-condensed locations" style="margin-bottom:0">
<tr>
@@ -121,6 +127,7 @@
</table>
<form method="post" action="?do=Locations">
<input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="page" value="locations">
<input type="hidden" name="action" value="addlocations">
<table class="table table-condensed">
<tr id="lasttr">
@@ -187,7 +194,7 @@ function slxOpenLocation(e, lid) {
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, function() {
+ td.load('?do=Locations&page=details&action=showlocation&locationid=' + lid, function() {
slxAttachCidr();
scollIntoView(tr);
});
diff --git a/modules-available/locations/templates/mismatch-cleanup.html b/modules-available/locations/templates/mismatch-cleanup.html
new file mode 100644
index 00000000..a6f45d27
--- /dev/null
+++ b/modules-available/locations/templates/mismatch-cleanup.html
@@ -0,0 +1,69 @@
+
+<div class="btn-group pull-right">
+ <a href="?do=Locations&amp;page=locations" class="btn btn-default {{perms.location.view.disabled}}">
+ <span class="glyphicon glyphicon-home"></span>
+ {{lang_thisListByLocation}}
+ </a>
+ <a href="?do=Locations&amp;page=subnets" class="btn btn-default {{perms.subnets.edit.disabled}}">
+ <span class="glyphicon glyphicon-list-alt"></span>
+ {{lang_thisListBySubnet}}
+ </a>
+</div>
+<h1>{{lang_mismatchHeading}}</h1>
+<h2>{{locationname}}</h2>
+
+<p>{{lang_mismatchIntroText}}</p>
+
+<form method="post" action="?do=locations">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="page" value="cleanup">
+ <input type="hidden" name="locationid" value="{{locationid}}">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>{{lang_machine}}</th>
+ <th>{{lang_ip}}</th>
+ <th class="slx-smallcol">{{lang_moveable}}</th>
+ <th>{{lang_locationBySubnet}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#clients}}
+ <tr>
+ <td>
+ <div class="checkbox checkbox-inline">
+ <input id="machine-{{machineuuid}}" type="checkbox" name="machines[]" value="{{machineuuid}}" {{disabled}}>
+ <label for="machine-{{machineuuid}}">{{hostname}}{{^hostname}}{{clientip}}{{/hostname}}</label>
+ </div>
+ </td>
+ <td>
+ {{clientip}}
+ </td>
+ <td>
+ {{#canmove}}
+ <span class="glyphicon glyphicon-ok"></span>
+ {{/canmove}}
+ {{^canmove}}
+ <span class="glyphicon glyphicon-remove text-danger"></span>
+ {{/canmove}}
+ </td>
+ <td>
+ {{iplocationname}}
+ </td>
+ </tr>
+ {{/clients}}
+ </tbody>
+ </table>
+
+ <div class="buttonbar pull-right">
+ <button type="submit" class="btn btn-primary" name="action" value="resetmachines">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_resetMachines}}
+ </button>
+ <button type="submit" class="btn btn-success" name="action" value="movemachines">
+ <span class="glyphicon glyphicon-arrow-right"></span>
+ {{lang_moveMachines}}
+ </button>
+ </div>
+</form>
+<div class="clearfix"></div> \ No newline at end of file
diff --git a/modules-available/locations/templates/subnets.html b/modules-available/locations/templates/subnets.html
index d027d800..59acf212 100644
--- a/modules-available/locations/templates/subnets.html
+++ b/modules-available/locations/templates/subnets.html
@@ -1,11 +1,18 @@
<div>
<div class="btn-group pull-right">
- <a href="?do=Locations&amp;action=showlocations" class="btn btn-default"><span class="glyphicon glyphicon-home"></span> {{lang_thisListByLocation}}</a>
- <a href="?do=Locations&amp;action=showsubnets" class="btn btn-default active"><span class="glyphicon glyphicon-list-alt"></span> {{lang_thisListBySubnet}}</a>
+ <a href="?do=Locations&amp;page=locations" class="btn btn-default {{perms.location.view.disabled}}">
+ <span class="glyphicon glyphicon-home"></span>
+ {{lang_thisListByLocation}}
+ </a>
+ <a href="?do=Locations&amp;page=subnets" class="btn btn-default active">
+ <span class="glyphicon glyphicon-list-alt"></span>
+ {{lang_thisListBySubnet}}
+ </a>
</div>
<h1>{{lang_listOfSubnets}}</h1>
<form method="post" action="?do=Locations">
<input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="page" value="subnets">
<input type="hidden" name="action" value="updatesubnets">
<table class="table table-condensed table-striped">
<tr>