<?php
class Page_SystemStatus extends Page
{
protected function doPreprocess()
{
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main');
}
$action = Request::post('action', false, 'string');
if ($action === 'reboot') {
User::assertPermission("serverreboot");
$task = Taskmanager::submit('Reboot');
if (Taskmanager::isTask($task)) {
Util::redirect('?do=systemstatus&taskid=' . $task['id']);
}
} elseif ($action === 'service-start' || $action === 'service-restart') {
$this->handleServiceAction(substr($action, 8));
}
if (Request::isPost()) {
Util::redirect('?do=systemstatus');
}
User::assertPermission('*');
}
private function handleServiceAction(string $action)
{
$service = Request::post('service', Request::REQUIRED, 'string');
$task = Taskmanager::submit('Systemctl', ['operation' => $action, 'service' => $service]);
$extra = '';
$cmp = preg_replace('/(@.*|\.service)$/', '', $service);
User::assertPermission("restart.$cmp");
if ($cmp === 'dmsd') {
$extra = '#id-DmsdLog_pane';
} elseif ($cmp === 'ldadp') {
$extra = '#id-LdadpLog_pane';
} elseif ($cmp === 'dnbd3-server') {
$extra = '#id-Dnbd3Log_pane';
}
Util::redirect('?do=systemstatus&taskid=' . $task['id'] . '&taskname=' . urlencode($service) . $extra);
}
protected function doRender()
{
$data = array();
$data['taskid'] = Request::get('taskid', '', 'string');
$data['taskname'] = Request::get('taskname', 'Reboot', 'string');
$tabs = array('DmsdLog', 'Netstat', 'PsList', 'LdadpLog', 'LighttpdLog', 'Dnbd3Log');
$data['tabs'] = array();
// Dictionary::translate('tab_DmsdLog') Dictionary::translate('tab_LdadpLog') Dictionary::translate('tab_Netstat')
// Dictionary::translate('tab_LighttpdLog') Dictionary::translate('tab_PsList') Dictionary::translate('tab_Dnbd3Log')
foreach ($tabs as $tab) {
$data['tabs'][] = array(
'type' => $tab,
'name' => Dictionary::translate('tab_' . $tab, true),
'enabled' => User::hasPermission('tab.' . $tab),
);
}
Permission::addGlobalTags($data['perms'], null, ['serverreboot']);
if (file_exists('/run/reboot-required.pkgs')) {
$lines = file('/run/reboot-required.pkgs', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$lines = array_unique($lines);
$data['packages'] = implode(', ', $lines);
}
Render::addTemplate('_page', $data);
}
protected function doAjax()
{
User::load();
if (!User::isLoggedIn())
return;
$action = 'ajax' . Request::any('action');
if (method_exists($this, $action)) {
$this->$action();
Message::renderList();
} else {
echo "Action $action not known in " . get_class();
}
}
protected function ajaxDiskStat()
{
User::assertPermission("show.overview.diskstat");
if (!SystemStatus::diskStat($systemUsage, $storeUsage, $currentSource, $wantedSource))
return;
$data = ['system' => $this->convertDiskStat($systemUsage)];
if ($wantedSource === false) { // Not configured yet, nothing to display
$data['notConfigured'] = true;
} elseif ($wantedSource === $currentSource) { // Fine and dandy
$data['store'] = $this->convertDiskStat($storeUsage);
} elseif ($currentSource === false) { // No current source, nothing mounted
$data['storeMissing'] = true;
} else { // Something else mounted
$data['wrongStore'] = $currentSource;
}
echo Render::parse('diskstat', $data);
}
protected function ajaxAddressList()
{
User::assertPermission("show.overview.addresses");
$task = Taskmanager::submit('LocalAddressesList');
if ($task === false)
return;
$task = Taskmanager::waitComplete($task, 3000);
if (!isset($task['data']['addresses']) || empty($task['data']['addresses'])) {
Taskmanager::addErrorMessage($task);
return;
}
$sort = array();
$primary = Property::getServerIp();
foreach ($task['data']['addresses'] as &$addr) {
$sort[] = $addr['type'] . $addr['ip'];
if ($addr['ip'] === $primary)
$addr['primary'] = true;
}
array_multisort($sort, SORT_STRING, $task['data']['addresses']);
echo Render::parse('addresses', array(
'addresses' => $task['data']['addresses']
));
}
private function sysInfo()
{
$data = array();
$memInfo = file_get_contents('/proc/meminfo');
$stat = file_get_contents('/proc/stat');
preg_match_all('/\b(\w+):\s+(\d+)\s/s', $memInfo, $out, PREG_SET_ORDER);
foreach ($out as $e) {
$data[$e[1]] = $e[2];
}
if (preg_match('/\bcpu\s+(?<user>\d+)\s+(?<nice>\d+)\s+(?<system>\d+)\s+(?<idle>\d+)\s+(?<iowait>\d+)\s+(?<irq>\d+)\s+(?<softirq>\d+)(\s|$)/', $stat, $out)) {
$data['CpuTotal'] = $out['user'] + $out['nice'] + $out['system'] + $out['idle'] + $out['iowait'] + $out['irq'] + $out['softirq'];
$data['CpuIdle'] = $out['idle'] + $out['iowait'];
$data['CpuSystem'] = $out['irq'] + $out['softirq'];
}
return $data;
}
protected function ajaxSystemInfo()
{
User::assertPermission("show.overview.systeminfo");
$cpuInfo = file_get_contents('/proc/cpuinfo');
$uptime = file_get_contents('/proc/uptime');
$cpuCount = preg_match_all('/\bprocessor\s/', $cpuInfo, $out);
//$cpuCount = count($out);
$data = array(
'cpuCount' => $cpuCount,
'memTotal' => '???',
'memFree' => '???',
'swapTotal' => '???',
'swapUsed' => '???',
'uptime' => '???'
);
if (preg_match('/^(\d+)\D/', $uptime, $out)) {
$data['uptime'] = floor($out[1] / 86400) . ' ' . Dictionary::translate('lang_days') . ', ' . floor(($out[1] % 86400) / 3600) . ' ' . Dictionary::translate('lang_hours');
}
$info = $this->sysInfo();
if (isset($info['MemTotal']) && isset($info['MemFree']) && isset($info['SwapTotal'])) {
$data['memTotal'] = Util::readableFileSize($info['MemTotal'] * 1024);
$data['memFree'] = Util::readableFileSize(($info['MemFree'] + $info['Buffers'] + $info['Cached']) * 1024);
$data['memPercent'] = 100 - round((($info['MemFree'] + $info['Buffers'] + $info['Cached']) / $info['MemTotal']) * 100);
$data['swapTotal'] = Util::readableFileSize($info['SwapTotal'] * 1024);
$data['swapUsed'] = Util::readableFileSize(($info['SwapTotal'] - $info['SwapFree']) * 1024);
$data['swapPercent'] = 100 - round(($info['SwapFree'] / $info['SwapTotal']) * 100);
$data['swapWarning'] = ($data['swapPercent'] > 50 || $info['SwapFree'] < 400000);
}
if (isset($info['CpuIdle']) && isset($info['CpuSystem']) && isset($info['CpuTotal'])) {
$data['cpuLoad'] = 100 - round(($info['CpuIdle'] / $info['CpuTotal']) * 100);
$data['cpuSystem'] = round(($info['CpuSystem'] / $info['CpuTotal']) * 100);
$data['cpuLoadOk'] = true;
$data['CpuTotal'] = $info['CpuTotal'];
$data['CpuIdle'] = $info['CpuIdle'];
}
echo Render::parse('systeminfo', $data);
}
protected function ajaxSysPoll()
{
User::assertPermission("show.overview.systeminfo");
$info = $this->sysInfo();
$data = array(
'CpuTotal' => $info['CpuTotal'],
'CpuIdle' => $info['CpuIdle'],
'MemPercent' => 100 - round((($info['MemFree'] + $info['Buffers'] + $info['Cached']) / $info['MemTotal']) * 100),
'SwapPercent' => 100 - round(($info['SwapFree'] / $info['SwapTotal']) * 100)
);
Header('Content-Type: application/json; charset=utf-8');
die(json_encode($data));
}
protected function ajaxServices()
{
User::assertPermission("show.overview.services");
$data = array('services' => array());
$tasks = array();
$todo = ['dmsd', 'tftpd-hpa'];
if (Module::get('dnbd3') !== false) {
$todo[] = 'dnbd3-server';
$todo[] = 'dnbd3-master-proxy';
}
foreach ($todo as $svc) {
$tasks[] = array(
'name' => $svc,
'task' => Taskmanager::submit('Systemctl', ['service' => $svc, 'operation' => 'is-active'])
);
}
$ldapIds = $ldadp = false;
if (Module::isAvailable('sysconfig')) {
$ldapIds = ConfigModuleBaseLdap::getActiveModuleIds();
if (!empty($ldapIds)) {
$ldadp = array( // No name - no display
'task' => ConfigModuleBaseLdap::ldadp('check', $ldapIds) // TODO: Proper --check usage
);
$tasks[] =& $ldadp;
}
}
$deadline = time() + 10;
do {
$done = true;
foreach ($tasks as &$task) {
if (!is_string($task['task']) && (Taskmanager::isFailed($task['task']) || Taskmanager::isFinished($task['task'])))
continue;
$task['task'] = Taskmanager::waitComplete($task['task'], 100);
if (!Taskmanager::isFailed($task['task']) && !Taskmanager::isFinished($task['task'])) {
$done = false;
}
}
unset($task);
} while (!$done && time() < $deadline);
foreach ($tasks as $task) {
if (!isset($task['name']))
continue;
$fail = Taskmanager::isFailed($task['task']);
$entry = array(
'name' => $task['name'],
'service' => $task['name'],
'fail' => $fail,
);
if ($fail) {
if (!isset($task['task']['data'])) {
$entry['error'] = 'Taskmanager Error';
} elseif (isset($task['task']['data']['messages'])) {
$entry['error'] = $task['task']['data']['messages'];
}
}
$data['services'][] = $entry;
}
if ($ldadp !== false) {
//error_log(print_r($ldadp, true));
preg_match_all('/^ldadp@(\d+)\.service\s+(\S+)$/m', $ldadp['task']['data']['messages'], $out, PREG_SET_ORDER);
$instances = [];
foreach ($out as $instance) {
$instances[$instance[1]] = $instance[2];
}
foreach ($ldapIds as $id) {
$status = $instances[$id] ?? 'failed';
$fail = ($status !== 'running');
$data['services'][] = [
'name' => 'LDAP/AD Proxy #' . $id,
'service' => 'ldadp@' . $id,
'fail' => $fail,
'error' => $fail ? $status : false,
];
}
}
echo Render::parse('services', $data);
}
protected function ajaxDmsdLog()
{
$this->showJournal('dmsd.service', 'tab.dmsdlog');
}
protected function ajaxDnbd3Log()
{
$this->showJournal('dnbd3-server.service', 'tab.dnbd3log');
}
protected function showJournal($service, $permission)
{
$cmp = preg_replace('/(@.*|\.service)$/', '', $service);
User::assertPermission($permission);
$output = [
'name' => $service,
'service' => $service,
'task' => Taskmanager::submit('Systemctl', ['operation' => 'journal', 'service' => $service]),
'restart_disabled' => User::hasPermission('restart.' . $cmp)
? '' : 'disabled',
];
echo Render::parse('ajax-journal', ['modules' => [$output]]);
}
protected function ajaxLighttpdLog()
{
User::assertPermission("tab.lighttpdlog");
$fh = @fopen('/var/log/lighttpd/error.log', 'r');
if ($fh === false) {
echo 'Error opening log file';
return;
}
fseek($fh, -6000, SEEK_END);
$data = fread($fh, 6000);
@fclose($fh);
if ($data === false) {
echo 'Error reading from log file';
return;
}
// If we could read less, try the .1 file too
$amount = 6000 - strlen($data);
if ($amount > 100) {
$fh = @fopen('/var/log/lighttpd/error.log.1', 'r');
if ($fh !== false) {
fseek($fh, -$amount, SEEK_END);
$data = fread($fh, $amount) . $data;
@fclose($fh);
}
}
if (strlen($data) < 5990) {
$start = 0;
} else {
$start = strpos($data, "\n") + 1;
}
echo '<pre>', htmlspecialchars(substr($data, $start), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), '</pre>';
}
protected function ajaxLdadpLog()
{
User::assertPermission("tab.ldadplog");
if (!Module::isAvailable('sysconfig')) {
die('SysConfig module not enabled');
}
$ids = ConfigModuleBaseLdap::getActiveModuleIds();
//error_log(print_r($ids, true));
$output = [];
foreach ($ids as $id) {
$module = ConfigModule::get($id);
if ($module === false) {
$name = "#$id";
} else {
$name = $module->title();
}
$service = "ldadp@{$id}.service";
$output[] = [
'name' => $name,
'service' => $service,
'task' => Taskmanager::submit('Systemctl', ['operation' => 'journal', 'service' => $service]),
];
}
//error_log(print_r($output, true));
echo Render::parse('ajax-journal', ['modules' => $output]);
}
protected function ajaxNetstat()
{
User::assertPermission("tab.netstat");
$taskId = Taskmanager::submit('Netstat');
if ($taskId === false)
return;
$status = Taskmanager::waitComplete($taskId, 3500);
if (isset($status['data']['messages']))
$data = $status['data']['messages'];
else
$data = 'Taskmanager error';
echo '<pre>', htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), '</pre>';
}
protected function ajaxPsList()
{
User::assertPermission("tab.pslist");
$taskId = Taskmanager::submit('PsList');
if ($taskId === false)
return;
$status = Taskmanager::waitComplete($taskId, 3500);
if (isset($status['data']['messages']))
$data = $status['data']['messages'];
else
$data = 'Taskmanager error';
echo '<pre>', htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), '</pre>';
}
private function convertDiskStat($stat)
{
if (!is_array($stat))
return false;
return [
'percent' => $stat['usedPercent'],
'size' => Util::readableFileSize($stat['sizeKb'] * 1024),
'free' => Util::readableFileSize($stat['freeKb'] * 1024),
'color' => $this->usageColor($stat['usedPercent']),
];
}
private function usageColor($percent)
{
if ($percent <= 50) {
$r = $b = $percent / 3;
$g = (100 - $percent * (50 / 80));
} elseif ($percent <= 70) {
$r = 55 + ($percent - 50) * (30 / 20);
$g = 60;
$b = 0;
} else {
$r = ($percent - 70) / 3 + 90;
$g = (100 - $percent) * (60 / 30);
$b = 0;
}
$r = dechex(round($r * 2.55));
$g = dechex(round($g * 2.55));
$b = dechex(round($b * 2.55));
return sprintf("%02s%02s%02s", $r, $g, $b);
}
}