From 7c539fd8736b0ff9acafe32d857b2a2021d778e6 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 31 Jul 2019 16:58:14 +0200 Subject: [locations] Add warnings/cleanup for bad machine to roomplan mappings --- modules-available/locations/page.inc.php | 637 +------------------------------ 1 file changed, 19 insertions(+), 618 deletions(-) (limited to 'modules-available/locations/page.inc.php') 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); } } -- cgit v1.2.3-55-g7522