summaryrefslogblamecommitdiffstats
path: root/modules-available/systemstatus/page.inc.php
blob: 00f36cbef5b52910d768b131da8ba521a5055cf4 (plain) (tree)
1
2
3
4
5
6
7



                                    
 

                                                              




                                          
                                                                

                                                   
 
                                                                   


                                  
                                                               



                                                                                         


                                       
                                                                       




















                                                                 
                        






                                                                                                                                


                                                           
                 
                                            

         




                                                                                                          

                                                                        









                                                                                                                       

                                     
                                

                                                                                 
                                                                                                                  
                                        
                                                                                                                                  
                                                                                                                                     
                                                              


                                                
                                                                               
                                                                                

                                                                                                                                                        

                          
                                                                                  


                                                                   
                 
                                                    









                                                          
                                                    
                                         

                                              

                                                                               
                 

         

                                               
                                                             









                                                                                                       
                                                          

                                                              


                                                                                       













                                                                                                                 

                                                                

                 








                                                                         

                                                       


                                                                                     
                                         
                                                                                
                                                                                                  


                   

                                         
                                                                 
                                                                                                      
                               
                                                                                 
                                                                                        
                                                      
                                                                               
                                                                                     



                                                                                           
                 
                                                      

         

                                            




                                                                  
 
                                                        

                                                            
                 
 










                                                                                
         

                                         



                                                              
                                                                                       


                                             
                                                                                                                                                     

                                                                                                                                                                              

                                                                           


                             
 

                                           



                                                                              
                                                         





                                                


                                                                                                                              


                                                                                                                                                                                                 
                 

                                                                                                       
                                                                                                                  
                                                                                             

                                                                                              


                                                                                                                    


                                                                                                                 








                                                                                                       
         


                                        
                                                                   









                                                                                                                                         
 

                                         


                                                                 
 
                                              
                                                     
                                                 
                                                       
                 
 
                                         
                                         

                                                                                                                           
                          
                 









                                                                                                                              








                                                                                                                                                  
                                 
                         



                                                       

                                                  
                                                                     
                                       
                                                        
                                                           
                                                
                          

























                                                                                                                                      
                 

                                                      

         
                                        
         









                                                                           
                                                                        




                                                                                                                      

                                                                                    

                                                                             

         
                                                                       
         







                                                                                
                 

                                                                     

                                                                                                             



                                                                                                                 
                                                                                                                 
                                               

                         










                                                                                                                     
                 
                                                                                                                    




                                                       







                                                                  
                                               
                                               
                                
                                                         
                         





                                                                                                                              
                 

                                                                           

         

                                        




                                                                   
 
                                                                           
 
                                                                                                      



                                       




                                                                   
 
                                                                           
 
                                                                                                      

         


                                                                                                         
                                                                            
         



                                                                                 
                                                                        
                                                            


                  
                                                                        
         














                                                                                                            


                                

                                                              

                               


                                                   


                                                           
 
<?php

class Page_SystemStatus extends Page
{

	const TM_UPDATE_UUID = '345-45763457-24356-234324556';

	protected function doPreprocess()
	{
		User::load();

		if (!User::isLoggedIn()) {
			Message::addError('main.no-permission');
			Util::redirect('?do=Main');
		}

		$action = Request::post('action', false, 'string');
		$aptAction = null;
		switch ($action) {
		case 'reboot':
			User::assertPermission("serverreboot");
			$task = Taskmanager::submit('Reboot');
			if (Taskmanager::isTask($task)) {
				Util::redirect('?do=systemstatus&taskid=' . $task['id']);
			}
			break;
		case 'service-start':
		case 'service-restart':
			$this->handleServiceAction(substr($action, 8));
			break;
		case 'apt-update':
			User::assertPermission('apt.update');
			$aptAction = 'UPDATE';
			break;
		case 'apt-upgrade':
			User::assertPermission('apt.upgrade');
			$aptAction = 'UPGRADE';
			break;
		case 'apt-full-upgrade':
			User::assertPermission('apt.upgrade');
			$aptAction = 'FULL_UPGRADE';
			break;
		case 'apt-autoremove':
			User::assertPermission('apt.autoremove');
			$aptAction = 'AUTOREMOVE';
			break;
		case 'apt-fix':
			User::assertPermission('apt.fix');
			$aptAction = 'FIX';
			break;
		default:
		}
		if ($aptAction !== null) {
			if (!Taskmanager::isRunning(Taskmanager::status(self::TM_UPDATE_UUID))) {
				$task = Taskmanager::submit('AptUpgrade', ['mode' => $aptAction, 'id' => self::TM_UPDATE_UUID]);
				Taskmanager::release($task);
			}
			Util::redirect('?do=systemstatus#id-ListUpgradable_pane');
		}
		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 = ['DmsdLog', 'Netstat', 'PsList', 'LdadpLog', 'LighttpdLog', 'Dnbd3Log', 'ListUpgradable'];
		$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')
		// Dictionary::translate('tab_ListUpgradable')
		foreach ($tabs as $tab) {
			$data['tabs'][] = array(
				'type' => $tab,
				'name' => Dictionary::translate('tab_' . $tab),
				'enabled' => User::hasPermission('tab.' . $tab),
				'important' => $tab === 'ListUpgradable'
					&& (SystemStatus::getAptLastDbUpdateTime() + 864000 < time() || SystemStatus::getUpgradableSecurityCount() > 0),
			);
		}
		Permission::addGlobalTags($data['perms'], null, ['serverreboot']);
		$pkgs = SystemStatus::getPackagesRequiringReboot();
		if (!empty($pkgs)) {
			$data['packages'] = implode(', ', $pkgs);
		}
		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 {
			// get_class() !== get_class($this)
			echo "Action $action not known in " . get_class($this);
		}
	}

	protected function ajaxListUpgradable()
	{
		User::assertPermission("tab.listupgradable");

		if (User::hasPermission('apt.update')
				&& Taskmanager::isRunning(Taskmanager::status(self::TM_UPDATE_UUID))) {
			echo Render::parse('sys-update-update', [
				'taskid' => self::TM_UPDATE_UUID,
				'rnd' => mt_rand(),
			]);
			return;
		}

		$task = SystemStatus::getUpgradableTask();

		// Estimate last time package list was updated
		$lastPackageInstalled = SystemStatus::getDpkgLastPackageChanges();
		$lastListDownloadAttempt = SystemStatus::getAptLastUpdateAttemptTime();
		$updateDbTime = SystemStatus::getAptLastDbUpdateTime();

		$perms = [];
		Permission::addGlobalTags($perms, 0, ['apt.update', 'apt.upgrade', 'apt.autoremove', 'apt.fix']);

		if ($task !== false) {
			$task = Taskmanager::waitComplete($task, 30000);

			if (Taskmanager::isFailed($task) || !Taskmanager::isFinished($task)) {
				Taskmanager::addErrorMessage($task);
				return;
			}
			if (!Taskmanager::isFailed($task) && empty($task['data']['packages'])) {
				$task['data']['error'] = '';
			}
		} else {
			$task['data']['error'] = 'ECONNREFUSED';
		}

		foreach ($task['data']['packages'] as &$pkg) {
			if (substr($pkg['source'], -9) === '-security') {
				$pkg['row_class'] = 'bg-danger';
			} else {
				$pkg['row_class'] = '';
			}
		}
		unset($pkg);

		echo Render::parse('sys-update-main', [
			'task' => $task['data'],
			'lastDownload' => Util::prettyTime($lastListDownloadAttempt),
			'lastChanged' => Util::prettyTime($updateDbTime),
			'lastInstalled' => Util::prettyTime($lastPackageInstalled),
			'perm' => $perms,
			'list_old' => $lastListDownloadAttempt + 86400 < time(),
			'needReboot' => implode(', ', SystemStatus::getPackagesRequiringReboot()),
		]);
	}

	protected function ajaxDiskStat()
	{
		User::assertPermission("show.overview.diskstat");
		if (!SystemStatus::diskStat($systemUsage, $storeUsage, $currentSource, $wantedSource))
			return;
		$data = ['system' => $this->convertDiskStat($systemUsage, 3000)];
		if ($wantedSource === false) { // Not configured yet, nothing to display
			$data['notConfigured'] = true;
		} elseif ($wantedSource === $currentSource) { // Fine and dandy
			$data['store'] = $this->convertDiskStat($storeUsage, 250000);
		} 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 (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(): array
	{
		$data = array();
		$memInfo = file_get_contents('/proc/meminfo');
		$stat = file_get_contents('/proc/stat');
		preg_match_all('/\b(\w+):\s+(\d+)\s/', $memInfo, $out, PREG_SET_ORDER);
		foreach ($out as $e) {
			$data[$e[1]] = $e[2];
		}
		/** @var array{user: numeric, nice: numeric, system: numeric, idle: numeric, iowait: numeric, irq: numeric, softirq: numeric} $out */
		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);
		$out = parse_ini_file('/etc/os-release');
		$data = array(
			'cpuCount' => $cpuCount,
			'memTotal' => '???',
			'memFree' => '???',
			'swapTotal' => '???',
			'swapUsed' => '???',
			'uptime' => '???',
			'kernel' => php_uname('r'),
			'distribution' => $out['PRETTY_NAME'] ?? (($out['NAME'] ?? '???') . ' ' . ($out['VERSION'] ?? '???')),
		);
		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'])) {
			$avail = $info['MemAvailable'] ?? ($info['MemFree'] + $info['Buffers'] + $info['Cached']);
			$data['memTotal'] = Util::readableFileSize($info['MemTotal'] * 1024);
			$data['memFree'] = Util::readableFileSize($avail * 1024);
			$data['memPercent'] = 100 - round(($avail / $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);
			if ($data['swapTotal'] > 0 && $data['memPercent'] > 75) {
				$data['swapWarning'] = ($data['swapPercent'] > 80 || $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]]);
	}

	private function grepLighttpdLog(string $file, int $num): array
	{
		$fh = @fopen($file, 'r');
		if ($fh === false)
			return ['Error opening ' . $file];
		$ret = [];
		fseek($fh, -($num * 2000), SEEK_END);
		if (ftell($fh) > 0) {
			// Throw away first line, as it's most likely incomplete
			fgets($fh, 1000);
		}
		while (($line = fgets($fh, 1000))) {
			if (strpos($line, ':SSL routines:') === false
					&& strpos($line, ' SSL: -1 5 104 Connection reset by peer') === false
					&& strpos($line, 'GET/HEAD with content-length') === false
					&& strpos($line, 'connection closed: write failed on fd ') === false
					&& strpos($line, 'unexpected TLS ClientHello on clear port ') === false
					&& strpos($line, 'invalid request-line -> sending Status 400 ') === false
					&& strpos($line, 'SSL (error): 5 -1: Bad message') === false
					&& strpos($line, 'POST-request, but content-length missing') === false) {
				$ret[] = $line;
			}
		}
		fclose($fh);
		return array_slice($ret, -$num);
	}

	protected function ajaxLighttpdLog()
	{
		User::assertPermission("tab.lighttpdlog");
		$lines = $this->grepLighttpdLog('/var/log/lighttpd/error.log', 60);
		if (count($lines) < 50) {
			$lines = array_merge(
				$this->grepLighttpdLog('/var/log/lighttpd/error.log.1', 60 - count($lines)), $lines);
		}
		echo '<pre>', htmlspecialchars(implode('', $lines), 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 === null) {
				$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);

		$data = $status['data']['messages'] ?? '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);

		$data = $status['data']['messages'] ?? 'Taskmanager error';

		echo '<pre>', htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), '</pre>';
	}

	/**
	 * @return array{percent: numeric, size: string, free: string, color: string, filesystem: string}
	 */
	private function convertDiskStat(array $stat, int $minFreeMb): array
	{
		return [
			'percent' => $stat['usedPercent'],
			'size' => Util::readableFileSize($stat['sizeKb'] * 1024),
			'free' => Util::readableFileSize($stat['freeKb'] * 1024),
			'color' => $this->usageColor($stat, $minFreeMb),
			'filesystem' => $stat['fileSystem'],
		];
	}

	private function usageColor(array $stat, int $minFreeMb): string
	{
		$freeMb = round($stat['freeKb'] / 1024);
		// All good is half space free, or 4x the min free amount, whatever is more
		$okFreeMb = max($minFreeMb * 4, round($stat['sizeKb']) / (1024 * 2));
		if ($freeMb > $okFreeMb) {
			$usedPercent = 0;
		} elseif ($freeMb < $minFreeMb) {
			$usedPercent = 100;
		} else {
			$usedPercent = 100 - round(($freeMb - $minFreeMb) / ($okFreeMb - $minFreeMb) * 100);
		}
		if ($usedPercent <= 50) {
			$r = $b = $usedPercent / 3;
			$g = (100 - $usedPercent * (50 / 80));
		} elseif ($usedPercent <= 70) {
			$r = 55 + ($usedPercent - 50) * (30 / 20);
			$g = 60;
			$b = 0;
		} else {
			$r = ($usedPercent - 70) / 3 + 90;
			$g = (100 - $usedPercent) * (60 / 30);
			$b = 0;
		}
		$r = dechex((int)round($r * 2.55));
		$g = dechex((int)round($g * 2.55));
		$b = dechex((int)round($b * 2.55));
		return sprintf("%02s%02s%02s", $r, $g, $b);
	}

}