<?php
class Page_Passthrough extends Page
{
protected function doPreprocess()
{
User::load();
User::assertPermission('view');
$action = Request::post('action');
if ($action === 'save-hwlist') {
$this->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()
{
//
}
}