saveHwList(); } elseif ($action === 'save-location') { $this->saveLocation(); } if (Request::isPost()) { Util::redirect('?do=passthrough'); } } private function saveHwList() { User::assertPermission('edit.group'); $newgroups = Request::post('newgroup', [], 'array'); foreach ($newgroups as $id => $title) { $id = strtoupper(preg_replace('/[^a-z0-9_\-]/i', '', $id)); if (empty($id)) continue; Database::exec("INSERT IGNORE INTO passthrough_group (groupid, title) VALUES (:group, :title)", ['group' => $id, 'title' => $title]); } $groups = Request::post('ptgroup', Request::REQUIRED, 'array'); $insert = []; $delete = []; foreach ($groups as $hwid => $group) { if (empty($group)) { $delete[] = $hwid; } else { $insert[] = [$hwid, '@PASSTHROUGH', $group]; } } if (!empty($insert)) { Database::exec("INSERT INTO statistic_hw_prop (hwid, prop, `value`) VALUES :list ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)", ['list' => $insert]); } if (!empty($delete)) { Database::exec("DELETE FROM statistic_hw_prop WHERE hwid IN (:list) AND prop = '@PASSTHROUGH'", ['list' => $delete]); } Message::addSuccess('list-updated'); Util::redirect('?do=passthrough&show=hwlist'); } private function saveLocation() { $locationId = Request::post('locationid', Request::REQUIRED, 'int'); User::assertPermission('edit.location', $locationId); $list = []; $groups = []; foreach (Request::post('enabled', [], 'array') as $groupId) { $groupId = (string)$groupId; $list[] = [$groupId, $locationId]; $groups[] = $groupId; } if (!empty($list)) { Database::exec("INSERT IGNORE INTO passthrough_group_x_location (groupid, locationid) VALUES :list", ['list' => $list]); Database::exec("DELETE FROM passthrough_group_x_location WHERE locationid = :lid AND groupid NOT IN (:groups)", ['lid' => $locationId, 'groups' => $groups]); } else { Database::exec("DELETE FROM passthrough_group_x_location WHERE locationid = :lid", ['lid' => $locationId]); } Message::addSuccess('location-updated', Location::getName($locationId)); Util::redirect('?do=passthrough&show=assignlocation&locationid=' . $locationId); } /* * */ protected function doRender() { $show = Request::get('show'); if ($show === 'hwlist') { $this->showHardwareList(); } elseif ($show === 'assignlocation') { $this->showLocationMapping(); } else { Util::redirect('?do=passthrough&show=hwlist'); } } /** * Show all the hardware that is known. Start with video adapters. * @return void */ private function showHardwareList() { $q = new HardwareQuery(HardwareInfo::PCI_DEVICE, null, false); $q->addGlobalColumn('vendor'); $q->addGlobalColumn('device'); $q->addGlobalColumn('rev'); $q->addGlobalColumn('class'); $q->addGlobalColumn('@PASSTHROUGH'); $rows = []; foreach ($q->query('`shw`.`hwid`') as $row) { $row['ptlist'] = Passthrough::getGroupDropdown($row); $rows[] = $row; } // Sort Graphics Cards first, rest by class, vendor, device usort($rows, function ($row1, $row2) { $a = $row1['class']; $b = $row2['class']; if ($a === $b) return hexdec($row1['vendor'].$row1['device']) - hexdec($row2['vendor'] . $row2['device']); if ($a === '0300') return -1; if ($b === '0300') return 1; return hexdec($a) - hexdec($b); }); $finalRows = []; $missing = []; $lastClass = ''; foreach ($rows as $row) { if ($row['class'] !== $lastClass) { // Add class row header $lastClass = $row['class']; $finalRows[$lastClass] = [ 'collapse' => $row['class'] !== '0300', 'class' => $row['class'], 'class_name' => PciId::getPciId(PciId::DEVCLASS, $row['class'], true) ?: 'Unknown', 'devlist' => [], ]; } $row['vendor_name'] = PciId::getPciId(PciId::VENDOR, $row['vendor'] ?? ''); $row['device_name'] = PciId::getPciId(PciId::DEVICE, $row['vendor'] . ':' . $row['device']); $finalRows[$lastClass]['devlist'][] = $row; // Build up query if ($row['vendor_name'] === false) { $missing[$row['vendor']] = true; } if ($row['device_name'] === false) { $missing[$row['vendor'] . ':' . $row['device']] = true; } } Render::addTemplate('hardware-list', ['classlist' => array_values($finalRows)]); if (!empty($missing)) { Render::addTemplate('js-pciquery', ['missing_ids' => json_encode(array_keys($missing))], 'statistics'); } } /** * Show mapping between specific location and passthrough groups. * @return void */ private function showLocationMapping() { $locationId = Request::get('locationid', Request::REQUIRED, 'int'); $locationIds = Location::getLocationRootChain($locationId); $res = Database::queryAll("SELECT g.groupid, g.title, GROUP_CONCAT(gxl.locationid) AS lids FROM passthrough_group g LEFT JOIN passthrough_group_x_location gxl ON (g.groupid = gxl.groupid AND gxl.locationid IN (:lids)) GROUP BY groupid, title ORDER BY lids ASC", ['lids' => $locationIds]); foreach ($res as &$item) { if ($item['lids'] === null) continue; $item['checked'] = 'checked'; $list = explode(',', $item['lids']); if (!in_array($locationId, $list)) { $item['disabled'] = true; $item['parent_location'] = Location::getName($list[0]); } } Render::addTemplate('location-assign', [ 'list' => array_reverse($res), 'locationid' => $locationId, 'locationname' => Location::getName($locationId), ]); } /* * */ protected function doAjax() { // } }