$row['currentsession']));
if ($lecture !== false) {
$row['currentsession'] = $lecture['displayname'];
$row['lectureid'] = $lecture['lectureid'];
}
$row['session'] = $row['currentsession'];
return;
}
}
$res = Database::simpleQuery('SELECT dateline, username, data FROM statistic'
. " WHERE clientip = :ip AND typeid = '.vmchooser-session-name'"
. ' AND dateline BETWEEN :start AND :end', array(
'ip' => $row['clientip'],
'start' => $row['logintime'] - 60,
'end' => $row['logintime'] + 300,
));
$session = false;
foreach ($res as $r) {
if ($session === false || abs($session['dateline'] - $row['logintime']) > abs($r['dateline'] - $row['logintime'])) {
$session = $r;
}
}
if ($session !== false) {
$row['session'] = $session['data'];
if (empty($row['currentuser'])) {
$row['username'] = $session['username'];
}
}
}
private static function showMachine($uuid)
{
$client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state,
mbram, live_tmpsize, live_tmpfree, live_id45size, live_id45free, live_swapsize, live_swapfree,
live_memsize, live_memfree, live_cpuload, live_cputemp,
Length(position) AS hasroomplan, kvmstate, cpumodel, id44mb, id45mb, data, hostname, currentuser, currentsession, notes
FROM machine WHERE machineuuid = :uuid',
array('uuid' => $uuid));
if ($client === false) {
Message::addError('unknown-machine', $uuid);
return;
}
Render::setTitle(empty($client['hostname']) ? $client['clientip'] : $client['hostname']);
$locations = [];
if ($client['locationid'] > 0 && Module::isAvailable('locations')) {
if (!Location::isLeaf($client['locationid'])) {
$client['hasroomplan'] = false;
}
$locations = Location::getLocationRootChain($client['locationid']);
}
if ($client['locationid'] && $client['hasroomplan'] && Module::isAvailable('roomplanner')) {
$client['roomsvg'] = PvsGenerator::generateSvg($client['locationid'], $client['machineuuid'],
0, 1, true);
}
User::assertPermission('machine.view-details', (int)$client['locationid']);
// Hack: Get raw collected data
if (Request::get('raw', false)) {
Header('Content-Type: application/json');
die($client['data']);
}
// Parse data
$hdds = array();
if ($client['data'][0] === '{') {
$json = json_decode($client['data'], true);
if (is_array($json)) {
$client += self::parseJson($uuid, $json);
$hdds['hdds'] = self::queryHddData($uuid);
}
} else {
self::parseLegacy($client, $hdds);
}
unset($client['data']);
// Get rid of configured speed, if equal to maximum speed
foreach ($client['ram'] as &$item) {
if (isset($item['Configured Memory Speed']) && $item['Configured Memory Speed'] === $item['Speed']) {
unset($item['Configured Memory Speed']);
}
}
unset($item);
// PCI
// 1) get passthrough groups
$passthroughTypes = [];
if (!empty($locations)) {
$hw = new HardwareQuery(HardwareInfo::PCI_DEVICE, $uuid, true);
// TODO: Get list of enabled pass through groups for this client's location
$hw->addForeignJoin(true, '@PASSTHROUGH', 'passthrough_group_x_location', 'groupid',
'locationid', $locations);
$hw->addGlobalColumn('vendor');
$hw->addGlobalColumn('device');
$hw->addGlobalColumn('rev');
$res = $hw->query();
foreach ($res as $row) {
$devId = $row['vendor'] . ':' . $row['device'] . ':' . $row['rev'];
if (!isset($passthroughTypes[$devId])) {
$passthroughTypes[$devId] = [];
}
$passthroughTypes[$devId][$row['@PASSTHROUGH']] = $row['@PASSTHROUGH'];
}
}
// 2) Sort and mangle list
$client['lspci1'] = $client['lspci2'] = [];
foreach ($client['lspci'] as $item) {
$devId = $item['vendor'] . ':' . $item['device'];
$item['vendor_s'] = PciId::getPciId(PciId::VENDOR, $item['vendor']);
$item['device_s'] = PciId::getPciId(PciId::DEVICE, $item['vendor'] . $item['device']);
if ($item['vendor_s'] === false) {
$pciLookup[$item['vendor']] = true;
}
if ($item['device_s'] === false) {
$pciLookup[$devId] = true;
}
// Passthrough enabled?
if (isset($passthroughTypes[$devId . ':' . ($item['rev'] ?? '')])) {
$item['pt'] = implode(', ', $passthroughTypes[$devId . ':' . ($item['rev'] ?? '')]);
}
$class = $item['class'];
if ($class === '0300' || $class === '0200' || $class === '0403' || !empty($item['pt'])) {
$dst =& $client['lspci1'];
} else {
$dst =& $client['lspci2'];
}
if (!isset($dst[$class])) {
$dst[$class] = [
'class' => $class,
'class_s' => PciId::getPciId(PciId::DEVCLASS, $class, true),
'entries' => [],
];
}
$dst[$class]['entries'][] = $item;
}
unset($dst, $client['lspci']);
ksort($client['lspci1']);
ksort($client['lspci2']);
$client['lspci1'] = array_values($client['lspci1']);
$client['lspci2'] = array_values($client['lspci2']);
// Runmode
if (Module::isAvailable('runmode')) {
$data = RunMode::getRunMode($uuid, RunMode::DATA_STRINGS);
if ($data !== false) {
$client += $data;
}
}
// Rebootcontrol
if (Module::isAvailable('rebootcontrol')) {
$client['canReboot'] = (User::hasPermission('.rebootcontrol.action.reboot', (int)$client['locationid']));
$client['canShutdown'] = (User::hasPermission('.rebootcontrol.action.shutdown', (int)$client['locationid']));
$client['canWol'] = (User::hasPermission('.rebootcontrol.action.wol', (int)$client['locationid']));
$client['canExec'] = (User::hasPermission('.rebootcontrol.action.exec', (int)$client['locationid']));
$client['rebootcontrol'] = $client['canReboot'] || $client['canShutdown'] || $client['canWol'] || $client['canExec'];
$client['execList'] = ExecTemplate::list();
}
// Baseconfig
if (Module::get('baseconfig') !== false
&& User::hasPermission('.baseconfig.view', (int)$client['locationid'])) {
$cvs = Database::queryFirst('SELECT Count(*) AS cnt FROM setting_machine WHERE machineuuid = :uuid', ['uuid' => $uuid]);
$client['overriddenVars'] = is_array($cvs) ? $cvs['cnt'] : 0;
$client['hasBaseconfig'] = true;
}
if (!isset($client['isclient'])) {
$client['isclient'] = true;
}
// Mangle fields
$NOW = time();
if (!$client['isclient']) {
if ($client['state'] === 'IDLE') {
$client['state'] = 'OCCUPIED';
}
} else {
if ($client['state'] === 'OCCUPIED') {
self::fillSessionInfo($client);
}
}
$client['state_' . $client['state']] = true;
$client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']);
$client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']);
$client['logintime_s'] = date('d.m.Y H:i', $client['logintime']);
if ($client['lastboot'] == 0) {
$client['lastboot_s'] = '-';
} else {
$uptime = $NOW - $client['lastboot'];
$client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']);
if ($client['state'] === 'IDLE' || $client['state'] === 'OCCUPIED') {
$client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')';
}
}
$client['gbram'] = Dictionary::number(ceil($client['mbram'] / 512) / 2, 1);
$client['gbtmp'] = Dictionary::number($client['id44mb'] / 1024);
$client['gbid45'] = Dictionary::number($client['id45mb'] / 1024);
foreach (['tmp', 'id45', 'swap', 'mem'] as $item) {
if ($client['live_' . $item . 'size'] == 0)
continue;
$client['live_' . $item . 'percent'] = round(($client['live_' . $item . 'free'] / $client['live_' . $item . 'size']) * 100, 2);
$client['live_' . $item . 'free_s'] = Util::readableFileSize($client['live_' . $item . 'free'], -1, 2);
}
if ($client['live_cpuload'] <= 100) {
$client['live_cpuload_s'] = $client['live_cpuload'] . "\xe2\x80\x89%";
$client['live_cpuidle'] = 100 - $client['live_cpuload'];
}
$client['live_cputemppercent'] = max(0, min(100, 110 - $client['live_cputemp']));
$client['ramclass'] = StatisticsStyling::ramColorClass((int)$client['mbram']);
$client['kvmclass'] = StatisticsStyling::kvmColorClass($client['kvmstate']);
$client['hddclass'] = StatisticsStyling::hddColorClass((int)$client['gbtmp']);
// Format HDD data to strings
foreach ($hdds['hdds'] as &$hdd) {
$hdd['smart_status_failed'] = !($client['smart_status//passed'] ?? 1);
self::mangleHdd($hdd);
}
// BIOS update check
if (!empty($client['bios']['BIOS Revision']) || !empty($client['bios']['Release Date'])) {
if (preg_match('#^(\d{1,2})/(\d{1,2})/(\d{4})#', $client['bios']['Release Date'] ?? '', $out)) {
$client['bios']['Release Date'] = $out[2] . '.' . $out[1] . '.' . $out[3];
}
$mainboard = ($client['mainboard']['Manufacturer'] ?? '') . '##' . ($client['mainboard']['Product Name'] ?? '');
$system = ($client['system']['Manufacturer'] ?? '') . '##' . ($client['system']['Product Name'] ?? '');
$ret = self::checkBios($mainboard, $system,
$client['bios']['Release Date'] ?? null,
$client['bios']['BIOS Revision'] ?? null);
if ($ret === false) { // Not loaded, use AJAX
$params = [
'mainboard' => $mainboard,
'system' => $system,
'date' => $client['bios']['Release Date'] ?? null,
'revision' => $client['bios']['BIOS Revision'] ?? null,
];
$client['biosurl'] = '?do=statistics&action=bios&' . http_build_query($params);
} elseif (!isset($ret['status']) || $ret['status'] !== 0) {
$client['bioshtml'] = Render::parse('machine-bios-update', $ret);
}
}
// Last booted system. The boot-system entry is created when the client fetches the config, so
// early on, *before* we get the ~poweron event. But in the ~poweron event, the client provides the
// kernel uptime, which is subtracted from what we write to lastboot, so it is actually *before*
// boot-system.
$os = Database::queryFirst("SELECT `data` AS `system`, `dateline`
FROM statistic
WHERE (dateline >= :lastboot) AND typeid = 'boot-system' AND machineuuid = :uuid
ORDER BY dateline ASC LIMIT 1",
['lastboot' => $client['lastboot'], 'uuid' => $uuid]);
if ($os !== false) {
$client['minilinux'] = $os['system'];
$graphical = Database::queryFirst("SELECT `dateline`
FROM statistic
WHERE (dateline >= :lastboot) AND typeid = 'graphical-startup' AND machineuuid = :uuid
ORDER BY dateline ASC LIMIT 1",
['lastboot' => $client['lastboot'], 'uuid' => $uuid]);
if ($graphical !== false) {
$boottime = $graphical['dateline'] - $client['lastboot'];
if ($boottime < 400) { // Sanity-check
$client['boottime_s'] = gmdate('i:s', $boottime);
}
}
}
// Get locations
if (Module::isAvailable('locations')) {
$locs = Location::getLocationsAssoc();
$next = (int)$client['locationid'];
$output = array();
while (isset($locs[$next])) {
array_unshift($output, $locs[$next]);
$next = $locs[$next]['parentlocationid'];
}
$client['locations'] = $output;
}
// Screens TODO Move everything else to hw table instead of blob parsing above
// `devicetype`, `devicename`, `subid`, `machineuuid`
$res = Database::simpleQuery("SELECT m.hwid, h.hwname, m.devpath AS connector, m.disconnecttime,"
. " p.value AS resolution, q.prop AS projector FROM machine_x_hw m"
. " INNER JOIN statistic_hw h ON (m.hwid = h.hwid AND h.hwtype = :screen)"
. " LEFT JOIN machine_x_hw_prop p ON (m.machinehwid = p.machinehwid AND p.prop = 'resolution')"
. " LEFT JOIN statistic_hw_prop q ON (m.hwid = q.hwid AND q.prop = 'projector')"
. " WHERE m.machineuuid = :uuid",
array('screen' => HardwareInfo::SCREEN, 'uuid' => $uuid));
$client['screens'] = array();
$ports = array();
foreach ($res as $row) {
if ($row['disconnecttime'] != 0)
continue;
$ports[] = $row['connector'];
$client['screens'][] = $row;
}
array_multisort($ports, SORT_ASC, $client['screens']);
Permission::addGlobalTags($client['perms'], null, ['hardware.projectors.edit', 'hardware.projectors.view']);
// Throw output at user
Render::addTemplate('machine-main', $client);
if (!empty($pciLookup)) {
Render::addTemplate('js-pciquery',
['missing_ids' => json_encode(array_keys($pciLookup))]);
}
// Sessions
$NOW = time();
$cutoff = $NOW - 86400 * 10;
//if ($cutoff < $client['firstseen']) $cutoff = $client['firstseen'];
$scale = 100 / ($NOW - $cutoff);
$res = Database::simpleQuery('SELECT dateline, typeid, data FROM statistic
WHERE dateline > :cutoff
AND typeid IN (:sessionLength, :offlineLength, :suspendLength)
AND machineuuid = :uuid ORDER BY dateline ASC', [
'cutoff' => $cutoff - 86400 * 30,
'uuid' => $uuid,
'sessionLength' => Statistics::SESSION_LENGTH,
'offlineLength' => Statistics::OFFLINE_LENGTH,
'suspendLength' => Statistics::SUSPEND_LENGTH,
]);
$spans = [
'rows' => array(),
'graph' => '',
];
$timeOffline = $timeStandby = $timeIdle = $timeUse = 0;
$last = false;
$first = true;
foreach ($res as $row) {
if (!$client['isclient'] && $row['typeid'] === Statistics::SESSION_LENGTH)
continue; // Don't differentiate between session and idle for non-clients
if ($first && $row['dateline'] > $cutoff && $client['lastboot'] > $cutoff) {
// Special case: offline before query start (longer than 30 days)
$timeOffline += min($row['dateline'], $client['lastboot']) - $cutoff;
$spans['graph'] .= '
';
}
$first = false;
if ($row['dateline'] + $row['data'] < $cutoff || $row['dateline'] + $row['data'] > $NOW) {
continue;
}
if ($last !== false && abs($last['dateline'] - $row['dateline']) < 30
&& abs($last['data'] - $row['data']) < 30
) {
continue;
}
if ($last !== false && $last['dateline'] + $last['data'] > $row['dateline']) {
$point = $last['dateline'] + $last['data'];
$row['data'] -= ($point - $row['dateline']);
$row['dateline'] = $point;
}
if ($row['dateline'] < $cutoff) {
$row['data'] -= ($cutoff - $row['dateline']);
$row['dateline'] = $cutoff;
}
$row['from'] = Util::prettyTime($row['dateline']);
$row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']);
if ($row['typeid'] === Statistics::OFFLINE_LENGTH) {
$row['glyph'] = 'off';
$color = '#444';
$timeOffline += $row['data'];
} elseif ($row['typeid'] === Statistics::SUSPEND_LENGTH) {
$row['glyph'] = 'pause';
$color = '#686';
$timeStandby += $row['data'];
} else {
$row['glyph'] = 'user';
$color = '#e77';
$timeUse += $row['data'];
}
$spans['graph'] .= '
';
// Reconstructed entry for table: idle - not logged in DB directly
if ($last !== false) {
$lastEnd = $last['dateline'] + $last['data'];
$duration = $row['dateline'] - $lastEnd;
if ($client['isclient'] && $duration > 90) {
$spans['rows'][] = [
'glyph' => 'ok-circle',
'from' => Util::prettyTime($lastEnd),
'duration' => floor($duration / 86400) . 'd ' . gmdate('H:i', $duration),
];
}
if ($duration > 0) {
$timeIdle += $duration;
}
}
// Normal entry for table
if ($client['isclient']) {
$spans['rows'][] = $row;
}
$last = $row;
}
if ($first && $client['lastboot'] > $cutoff) {
// Special case: offline before and no activity besides bootup
$spans['graph'] .= '
';
} elseif ($first) {
// Not seen in last weeks
$spans['graph'] .= '
';
}
if ($client['state'] === 'OCCUPIED') {
$spans['graph'] .= '
';
$spans['rows'][] = [
'from' => Util::prettyTime($client['logintime']),
'duration' => '-',
'glyph' => 'user',
];
$timeUse += $NOW - $client['logintime'];
} elseif ($client['state'] === 'OFFLINE') {
$spans['graph'] .= '
';
$spans['rows'][] = [
'from' => Util::prettyTime($client['lastseen']),
'duration' => '-',
'glyph' => 'off',
];
$timeOffline += $NOW - $client['lastseen'];
} elseif ($client['state'] === 'STANDBY') {
$spans['graph'] .= '
';
$spans['rows'][] = [
'from' => Util::prettyTime($client['lastseen']),
'duration' => '-',
'glyph' => 'pause',
];
$timeStandby += $NOW - $client['lastseen'];
} elseif ($last !== false && $client['state'] === 'IDLE') {
$lastEnd = $last['dateline'] + $last['data'];
$timeIdle += $NOW - $lastEnd;
}
$t = explode('-', date('Y-n-j-G', $cutoff));
if ($t[3] >= 8 && $t[3] <= 22) {
$start = mktime(22, 0, 0, $t[1], $t[2], $t[0]);
} else {
$start = mktime(22, 0, 0, $t[1], $t[2] - 1, $t[0]);
}
for ($i = $start; $i < $NOW; $i += 86400) {
$spans['graph'] .= '
';
}
$spans['isclient'] = $client['isclient'];
$spans['chart'] = json_encode([
['value' => $timeOffline, 'color' => '#444', 'tooltip' => 'OFFLINE: ' . Util::formatDuration($timeOffline)],
['value' => $timeStandby, 'color' => '#686', 'tooltip' => 'STANDBY: ' . Util::formatDuration($timeStandby)],
['value' => $timeIdle, 'color' => '#afc', 'tooltip' => 'IDLE: ' . Util::formatDuration($timeIdle)],
['value' => $timeUse, 'color' => '#e99', 'tooltip' => 'OCCUPIED: ' . Util::formatDuration($timeUse)],
]);
Render::addTemplate('machine-usage', $spans);
// Any hdds?
if (!empty($hdds['hdds'])) {
Render::addTemplate('machine-hdds', $hdds);
}
// Client log
if (Module::get('syslog') !== false) {
$lres = Database::simpleQuery('SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog'
. ' WHERE machineuuid = :uuid ORDER BY logid DESC LIMIT 25', array('uuid' => $client['machineuuid']));
$count = 0;
$log = array();
foreach ($lres as $row) {
if (substr($row['description'], -5) === 'on :0' && strpos($row['description'], 'root logged') === false) {
continue;
}
$row['date'] = Util::prettyTime($row['dateline']);
$row['icon'] = self::eventToIconName($row['logtypeid']);
$log[] = $row;
if (++$count === 10) {
break;
}
}
Render::addTemplate('syslog', array(
'machineuuid' => $client['machineuuid'],
'list' => $log,
));
}
// Notes
if (User::hasPermission('machine.note.*', (int)$client['locationid'])) {
Permission::addGlobalTags($client['perms'], (int)$client['locationid'], ['machine.note.edit']);
Render::addTemplate('machine-notes', $client);
}
}
private static function parseLegacy(array &$client, array &$hdds)
{
// Parse the giant blob of data
if (strpos($client['data'], "\r") !== false) {
$client['data'] = str_replace("\r", "\n", $client['data']);
}
if (preg_match_all('/##### ([^#]+) #+$(.*?)^#####/ims', $client['data'] . '########', $out, PREG_SET_ORDER)) {
foreach ($out as $section) {
if ($section[1] === 'CPU') {
HardwareParserLegacy::parseCpu($client, $section[2]);
}
if ($section[1] === 'dmidecode') {
HardwareParserLegacy::parseDmiDecode($client, $section[2]);
}
if ($section[1] === 'Partition tables') {
HardwareParserLegacy::parseHdd($hdds, $section[2]);
}
if ($section[1] === 'PCI ID') {
$client['lspci'] = HardwareParserLegacy::parsePci($section[2]);
}
if (isset($hdds['hdds']) && $section[1] === 'smartctl') {
// This currently requires that the partition table section comes first...
HardwareParserLegacy::parseSmartctl($hdds['hdds'], $section[2]);
}
}
}
}
private static function parseJson(string $uuid, array $json): array
{
$return = [
'lspci' => $json['lspci'] ?? [],
'ram' => array_map(function($item) {
return HardwareParser::prepareDmiProperties($item);
}, HardwareParser::getDmiHandles($json, 17)),
];
foreach ($return['ram'] as $ram) {
if (!empty($ram['Form Factor']) && !empty($ram['Type'])) {
$return['ramtype'] = $ram['Type'] . '-' . $ram['Form Factor'];
break;
}
}
$need = [
'bios' => 0,
'system' => 1,
'mainboard' => 2,
];
foreach ($need as $name => $id) {
$return[$name] = HardwareParser::prepareDmiProperties(
HardwareParser::getDmiHandles($json, $id)[0] ?? []);
}
$q = new HardwareQuery(HardwareInfo::MAINBOARD, $uuid);
$q->addGlobalColumn('Memory Maximum Capacity');
$q->addGlobalColumn('Memory Slot Count');
$q->addLocalColumn('cpu-sockets');
$q->addLocalColumn('cpu-cores');
$q->addLocalColumn('cpu-threads');
$q->addLocalColumn('nic-speed');
$q->addLocalColumn('nic-duplex');
$res = $q->query()->fetch();
if (is_array($res)) {
$return += $res;
}
return $return;
}
private static function queryHddData(string $uuid): array
{
$hdds = [];
$ret = Database::simpleQuery("SELECT mp.`machinehwid`, mp.`prop`, mp.`value`, mp.`numeric`
FROM machine_x_hw_prop mp
INNER JOIN machine_x_hw mxhw ON (mp.machinehwid = mxhw.machinehwid AND mxhw.machineuuid = :uuid AND mxhw.disconnecttime = 0)
INNER JOIN statistic_hw sh ON (mxhw.hwid = sh.hwid AND sh.hwtype = :type)
UNION SELECT mxhw.`machinehwid`, hwp.`prop`, hwp.`value`, hwp.`numeric`
FROM statistic_hw_prop hwp
INNER JOIN machine_x_hw mxhw ON (hwp.hwid = mxhw.hwid AND mxhw.machineuuid = :uuid AND mxhw.disconnecttime = 0)
INNER JOIN statistic_hw sh ON (mxhw.hwid = sh.hwid AND sh.hwtype = :type)
",
['type' => HardwareInfo::HDD, 'uuid' => $uuid]);
foreach ($ret as $row) {
if (!isset($hdds[$row['machinehwid']])) {
$hdds[$row['machinehwid']] = ['partitions' => []];
}
$hdd =& $hdds[$row['machinehwid']];
if (preg_match('/^(attr_[0-9]+)_(.*)$/', $row['prop'], $out)) {
// SMART attributes
if (!isset($hdd[$out[1]])) {
$hdd[$out[1]] = [];
}
$hdd[$out[1]][$out[2]] = $row['numeric'] ?? $row['value'];
} elseif (preg_match('/^part_([0-9]+)_(.*)$/', $row['prop'], $out)) {
// Partitions
if (!isset($hdd['partitions'][$out[1]])) {
$hdd['partitions'][$out[1]] = ['id' => 'dev-' . count($hdds) . '-' . $out[1], 'index' => $out[1]];
}
$hdd['partitions'][$out[1]][$out[2]] = $row['numeric'] ?? $row['value'];
} else {
$hdd[$row['prop']] = $row['numeric'] ?? $row['value'];
}
}
$result = [];
foreach ($hdds as $k => &$hdd) {
if (substr($hdd['dev'] ?? '/dev/sr', 0, 7) === '/dev/sr')
continue;
$hdd['devid'] = 'k' . $k;
$hdd['partitions'] = array_values($hdd['partitions']);
$result[] = $hdd;
}
return $result;
}
private static function mangleHdd(array &$hdd)
{
static $hddidx = 0;
if (!isset($hdd['size']) || !is_numeric($hdd['size'])) {
$hdd['size'] = 0;
}
$hdd['hddidx'] = $hddidx++;
$hours = $hdd['power_on_time//hours'] ?? $hdd['attr_9']['raw'] ?? $hdd['power_on_hours']
?? $hdd['power_on_time']['hours'] ?? null;
if ($hours !== null) {
$hdd['PowerOnTime'] = '';
$val = (int)str_replace('.', '', $hours);
if ($val > 8760) {
$hdd['PowerOnTime'] .= floor($val / 8760) . 'Y, ';
$val %= 8760;
}
if ($val > 720) {
$hdd['PowerOnTime'] .= floor($val / 720) . 'M, ';
$val %= 720;
}
if ($val > 24) {
$hdd['PowerOnTime'] .= floor($val / 24) . 'd, ';
$val %= 24;
}
$hdd['PowerOnTime'] .= $val . 'h';
}
// Sort by start for building pie-chart
$xx = array_column($hdd['partitions'], 'start');
array_multisort($xx, SORT_ASC, SORT_NUMERIC,
$hdd['partitions']);
$used = 0;
$json = [];
$lastEnd = 0;
$minDisplaySize = $hdd['size'] / 150;
$i = 0;
foreach ($hdd['partitions'] as &$part) {
$dist = $part['start'] - $lastEnd;
if ($dist > $minDisplaySize) {
$json[] = ['value' => $dist, 'color' => '#aaa'];
$i++;
}
if ($part['size'] > $minDisplaySize) {
$json[] = ['value' => $part['size'], 'color' => self::typeToColor($part)];
$part['idx'] = $i++;
}
$part['size_s'] = Util::readableFileSize($part['size']);
$used += $part['size'];
$lastEnd = $part['start'] + $part['size'];
if (!isset($part['name']) || isset($part['slxtype'])) {
$part['name'] = self::partTypeToName($part['slxtype'] ?? $part['type']);
}
}
$dist = $hdd['size'] - $lastEnd;
if ($dist > $minDisplaySize) {
$json[] = ['value' => $dist, 'color' => '#aaa'];
}
$hdd['json'] = json_encode($json);
$hdd['size_s'] = Util::readableFileSize($hdd['size']);
if ($hdd['size'] - $used > 1000000000) {
$hdd['unused_s'] = Util::readableFileSize($hdd['size'] - $used);
}
// Finally sort by index for table display
array_multisort(array_column($hdd['partitions'], 'index'), SORT_ASC,
$hdd['partitions']);
}
private static function typeToColor(array $part): string
{
switch ($part['slxtype'] ?? $part['type']) {
case 44:
return '#5c1';
case 45:
return '#0d7';
case 82:
return '#48f';
}
return '#e55';
}
private static function eventToIconName($event): string
{
switch ($event) {
case 'session-open':
return 'glyphicon-log-in';
case 'session-close':
return 'glyphicon-log-out';
case 'partition-swap':
return 'glyphicon-info-sign';
case 'partition-temp':
case 'smartctl-realloc':
return 'glyphicon-exclamation-sign';
default:
return 'glyphicon-minus';
}
}
const BIOS_CACHE = '/tmp/bwlp-bios.json';
public static function ajaxCheckBios()
{
$mainboard = Request::any('mainboard', false, 'string');
$system = Request::any('system', false, 'string');
$date = Request::any('date', false, 'string');
$revision = Request::any('revision', false, 'string');
$reply = self::checkBios($mainboard, $system, $date, $revision);
if ($reply === false) {
$data = Download::asString(CONFIG_BIOS_URL, 3, $err);
if ($err < 200 || $err >= 300) {
$reply = ['error' => 'HTTP: ' . $err];
} else {
file_put_contents(self::BIOS_CACHE, $data);
$data = json_decode($data, true);
$reply = self::checkBios($mainboard, $system, $date, $revision, $data);
}
}
if ($reply === false) {
$reply = ['error' => 'Internal Error'];
}
if (isset($reply['status']) && $reply['status'] === 0)
exit; // Show nothing, 0 means OK
die(Render::parse('machine-bios-update', $reply));
}
private static function checkBios(string $mainboard, string $system, ?string $date, ?string $revision, $json = null)
{
if ($json === null) {
if (!file_exists(self::BIOS_CACHE) || filemtime(self::BIOS_CACHE) + 3600 < time())
return false;
$json = json_decode(file_get_contents(self::BIOS_CACHE), true);
}
if (!is_array($json) || !isset($json['system']))
return ['error' => 'Malformed JSON, no system key'];
if (isset($json['system'][$system]['fixes']) && isset($json['system'][$system]['match'])) {
$match =& $json['system'][$system];
} elseif (isset($json['mainboard'][$mainboard]['fixes']) && isset($json['mainboard'][$mainboard]['match'])) {
$match =& $json['mainboard'][$mainboard];
} else {
return ['status' => 0];
}
$key = $match['match'];
if ($key === 'revision' && $revision !== null) {
$cmp = function ($item) { $s = explode('.', $item); return $s[0] * 0x10000 + $s[1]; };
$reference = $cmp($revision);
} elseif ($key === 'date' && $date !== null) {
$cmp = function ($item) { $s = explode('.', $item); return $s[2] * 10000 + $s[1] * 100 + $s[0]; };
$reference = $cmp($date);
} else {
return ['error' => 'Invalid comparison key: ' . $key];
}
$retval = ['fixes' => []];
$level = 0;
foreach ($match['fixes'] as $fix) {
if ($cmp($fix[$key]) > $reference) {
class_exists('Dictionary'); // Trigger setup of lang stuff
$lang = isset($fix['text'][LANG]) ? LANG : 'en';
$fix['text'] = $fix['text'][$lang];
$retval['fixes'][] = $fix;
$level = max($level, $fix['level']);
}
}
$retval['url'] = $match['url'];
$retval['status'] = $level;
if ($level > 5) {
$retval['class'] = 'danger';
} elseif ($level > 3) {
$retval['class'] = 'warning';
} else {
$retval['class'] = 'info';
}
return $retval;
}
/**
* @param string $type MBR-type or GPT UUID
* @return string Name of partition type if known, otherwise, $type is returned
*/
private static function partTypeToName(string $type): string
{
switch ($type) {
case '44':
case '45':
return 'OpenSLX-ID' . $type;
case '82':
return 'Linux Swap';
case '83':
return 'Linux';
case '7':
return 'NTFS/Windows';
case 'ef':
return 'EFI';
}
return HardwareInfo::GPT[$type] ?? $type;
}
}