diff options
author | root | 2019-02-19 18:53:50 +0100 |
---|---|---|
committer | root | 2019-02-19 18:53:50 +0100 |
commit | 0ad4c0f8196b61699754762aacbaab0223478ab9 (patch) | |
tree | de434c4aea8d07ecd01cd3badd48d057d62c2d1b /modules-available/statistics | |
parent | [usb-lock-off] Edit rule cleanup and fix of the dropdown boxes. (diff) | |
parent | [statistics] Fix RAM change warning to handle increase too (diff) | |
download | slx-admin-usb-lock-off.tar.gz slx-admin-usb-lock-off.tar.xz slx-admin-usb-lock-off.zip |
Merge branch 'master' into usb-lock-offusb-lock-off
Diffstat (limited to 'modules-available/statistics')
26 files changed, 606 insertions, 257 deletions
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index a7a636b3..d4b8f346 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -34,7 +34,8 @@ if ($type{0} === '~') { // External mode of operation? $mode = Request::post('mode', false, 'string'); $NOW = time(); - $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); + $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel, live_memfree, live_swapfree, live_tmpfree + FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($old !== false) { settype($old['logintime'], 'integer'); settype($old['lastseen'], 'integer'); @@ -114,6 +115,7 @@ if ($type{0} === '~') { . ' cpumodel = :cpumodel,' . ' systemmodel = :systemmodel,' . ' id44mb = :id44mb,' + . ' live_tmpsize = 0, live_swapsize = 0, live_memsize = 0,' . ' badsectors = :badsectors,' . ' data = :data,' . ' state = :state ' @@ -123,32 +125,20 @@ if ($type{0} === '~') { } } // Maybe log old crashed session - if ($uptime < 120) { + if ($uptime < 150 && $old !== false) { // See if we have a lingering session, create statistic entry if so - if ($old !== false && $old['logintime'] !== 0) { + if ($old['state'] === 'OCCUPIED' && $old['logintime'] !== 0) { $sessionLength = $old['lastseen'] - $old['logintime']; if ($sessionLength > 30 && $sessionLength < 86400*2) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } // Write poweroff period length to statistic table - if ($old !== false && $old['lastseen'] !== 0) { + if ($old['lastseen'] !== 0) { $lastSeen = $old['lastseen']; $offtime = ($NOW - $uptime) - $lastSeen; - if ($offtime > 300 && $offtime < 86400 * 90) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:shutdown, '~offline-length', :uuid, :clientip, '', :length)", array( - 'shutdown' => $lastSeen, - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $offtime - )); + if ($offtime > 90 && $offtime < 86400 * 30) { + Statistics::logMachineState($uuid, $ip, $old['state'] === 'STANDBY' ? Statistics::SUSPEND_LENGTH : Statistics::OFFLINE_LENGTH, $lastSeen, $offtime); } } } @@ -161,6 +151,14 @@ if ($type{0} === '~') { // Check for suspicious hardware changes if ($old !== false) { checkHardwareChange($old, $new); + + // Log potential crash + if ($old['state'] === 'IDLE' || $old['state'] === 'OCCUPIED') { + writeClientLog('machine-mismatch-poweron', 'Poweron event, but previous known state is ' . $old['state'] + . '. RAM: ' . Util::readableFileSize($old['live_memfree'], -1, 2) + . ', Swap: ' . Util::readableFileSize($old['live_swapfree'], -1, 2) + . ', ID44: ' . Util::readableFileSize($old['live_tmpfree'], -1, 2)); + } } // Write statistics data @@ -168,36 +166,45 @@ if ($type{0} === '~') { } else if ($type === '~runstate') { // Usage (occupied/free) $sessionLength = 0; + $strUpdateBoottime = ''; if ($old === false) die("Unknown machine.\n"); if ($old['clientip'] !== $ip) { EventLog::warning("[runstate] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); die("Address changed.\n"); } $used = Request::post('used', 0, 'integer'); - if ($old['state'] === 'OFFLINE' && $NOW - $old['lastseen'] > 600) { - $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; - } else { - $strUpdateBoottime = ''; - } - // 1) Log last session length if we didn't see the machine for a while - if ($NOW - $old['lastseen'] > 610 && $old['lastseen'] !== 0) { - // Old session timed out - might be caused by hard reboot - if ($old['logintime'] !== 0) { - if ($old['lastseen'] > $old['logintime']) { - $sessionLength = $old['lastseen'] - $old['logintime']; - } - $old['logintime'] = 0; - } - } - // Figure out what's happening - state changes $params = array( 'uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'], ); + if ($old['state'] === 'OFFLINE') { + // This should never happen -- we expect a poweron event before runstate, which would set the state to IDLE + // So it might be that the poweron event got lost, or that a couple of runstate events got lost, which + // caused our cron.inc.php to time out the client and reset it to OFFLINE + if ($NOW - $old['lastseen'] > 900) { + $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; + } + // 1) Log last session length if we didn't see the machine for a while + if ($NOW - $old['lastseen'] > 900 && $old['lastseen'] !== 0) { + // Old session timed out - might be caused by hard reboot + if ($old['logintime'] !== 0) { + if ($old['lastseen'] > $old['logintime']) { + $sessionLength = $old['lastseen'] - $old['logintime']; + } + } + } + } + foreach (['memsize', 'tmpsize', 'swapsize', 'memfree', 'tmpfree', 'swapfree'] as $item) { + $strUpdateBoottime .= ' live_' . $item . ' = :_' . $item . ', '; + $params['_' . $item] = ceil(Request::post($item, 0, 'int') / 1024); + } + // Figure out what's happening - state changes if ($used === 0 && $old['state'] !== 'IDLE') { - // Is not in use, was in use before - $sessionLength = $NOW - $old['logintime']; + if ($old['state'] === 'OCCUPIED' && $sessionLength === 0) { + // Is not in use, was in use before + $sessionLength = $NOW - $old['logintime']; + } $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime . " logintime = 0, currentuser = NULL, state = 'IDLE' " @@ -227,13 +234,7 @@ if ($type{0} === '~') { } // 9) Log last session length if applicable if ($mode === false && $sessionLength > 0 && $sessionLength < 86400*2 && $old['logintime'] !== 0) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } elseif ($type === '~poweroff') { if ($old === false) die("Unknown machine.\n"); @@ -241,16 +242,10 @@ if ($type{0} === '~') { EventLog::warning("[poweroff] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); die("Address changed.\n"); } - if ($mode === false && $old['logintime'] !== 0) { + if ($mode === false && $old['state'] === 'OCCUPIED' && $old['logintime'] !== 0) { $sessionLength = $old['lastseen'] - $old['logintime']; if ($sessionLength > 0 && $sessionLength < 86400*2) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:start, '~session-length', :uuid, :clientip, '', :length)", array( - 'start' => $old['logintime'], - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $sessionLength - )); + Statistics::logMachineState($uuid, $ip, Statistics::SESSION_LENGTH, $old['logintime'], $sessionLength); } } Database::exec("UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), state = 'OFFLINE' @@ -267,6 +262,10 @@ if ($type{0} === '~') { foreach ($screens as $port => $screen) { if (!array_key_exists('name', $screen)) continue; + // Filter bogus data + $screen['name'] = iconv('UTF-8', 'UTF-8//IGNORE', $screen['name']); + if (empty($screen['name'])) + continue; if (array_key_exists($screen['name'], $hwids)) { $hwid = $hwids[$screen['name']]; } else { @@ -359,13 +358,7 @@ if ($type{0} === '~') { $lastSeen = $old['lastseen']; $duration = $NOW - $lastSeen; if ($duration > 500 && $duration < 86400 * 14) { - Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' - . " VALUES (:suspend, '~suspend-length', :uuid, :clientip, '', :length)", array( - 'suspend' => $lastSeen, - 'uuid' => $uuid, - 'clientip' => $ip, - 'length' => $duration - )); + Statistics::logMachineState($uuid, $ip, Statistics::SUSPEND_LENGTH, $lastSeen, $duration); } } } else { @@ -399,6 +392,18 @@ function writeStatisticLog($type, $username, $data) )); } +function writeClientLog($type, $description) +{ + global $ip, $uuid; + Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( + 'type' => $type, + 'client' => $ip, + 'description' => $description, + 'longdesc' => '', + 'uuid' => $uuid, + )); +} + // For backwards compat, we require the . prefix if ($type{0} === '.') { @@ -428,10 +433,16 @@ if ($type{0} === '.') { function checkHardwareChange($old, $new) { if ($new['mbram'] !== 0) { - if ($new['mbram'] + 1000 < $old['mbram']) { - $ram1 = round($old['mbram'] / 512) / 2; - $ram2 = round($new['mbram'] / 512) / 2; - EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM decreased from {$ram1}GB to {$ram2}GB"); + if ($new['mbram'] < 6200) { + $ram1 = ceil($old['mbram'] / 512) / 2; + $ram2 = ceil($new['mbram'] / 512) / 2; + } else { + $ram1 = ceil($old['mbram'] / 1024); + $ram2 = ceil($new['mbram'] / 1024); + } + if ($ram1 !== $ram2) { + $word = $ram1 > $ram2 ? 'decreased' : 'increased'; + EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): RAM $word from {$ram1}GB to {$ram2}GB"); } if (!empty($old['cpumodel']) && !empty($new['cpumodel']) && $new['cpumodel'] !== $old['cpumodel']) { EventLog::warning('[poweron] Client ' . $new['uuid'] . ' (' . $new['clientip'] . "): CPU changed from '{$old['cpumodel']}' to '{$new['cpumodel']}'"); diff --git a/modules-available/statistics/config.json b/modules-available/statistics/config.json index 333f881a..412dc3cb 100644 --- a/modules-available/statistics/config.json +++ b/modules-available/statistics/config.json @@ -1,5 +1,9 @@ { - "category":"main.status", - "dependencies": [ "js_chart", "js_selectize", "bootstrap_datepicker"], - "permission":"0" -} + "category": "main.status", + "dependencies": [ + "js_chart", + "js_selectize", + "bootstrap_datepicker" + ], + "permission": "0" +}
\ No newline at end of file diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index 4df7b0d4..6393b2c6 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -21,9 +21,37 @@ function logstats() function state_cleanup() { // Fix online state of machines that crashed - $standby = time() - 86400 * 2; // Reset standby machines after two days + $standby = time() - 86400 * 4; // Reset standby machines after four days $on = time() - 610; // Reset others after ~10 minutes - Database::exec("UPDATE machine SET state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + // Query for logging + $res = Database::simpleQuery("SELECT machineuuid, clientip, state, logintime, lastseen, live_memfree, live_swapfree, live_tmpfree + FROM machine WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + Database::exec('INSERT INTO clientlog (dateline, logtypeid, clientip, machineuuid, description, extra) + VALUES (UNIX_TIMESTAMP(), :type, :client, :uuid, :description, :longdesc)', array( + 'type' => 'machine-mismatch-cron', + 'client' => $row['clientip'], + 'description' => 'Client timed out, last known state is ' . $row['state'] + . '. RAM: ' . Util::readableFileSize($row['live_memfree'], -1, 2) + . ', Swap: ' . Util::readableFileSize($row['live_swapfree'], -1, 2) + . ', ID44: ' . Util::readableFileSize($row['live_tmpfree'], -1, 2), + 'longdesc' => '', + 'uuid' => $row['machineuuid'], + )); + if ($row['state'] === 'OCCUPIED') { + $length = $row['lastseen'] - $row['logintime']; + if ($length > 0 && $length < 86400 * 7) { + Statistics::logMachineState($row['machineuuid'], $row['clientip'], Statistics::SESSION_LENGTH, $row['logintime'], $length); + } + } elseif ($row['state'] === 'STANDBY') { + $length = time() - $row['lastseen']; + if ($length > 0 && $length < 86400 * 7) { + Statistics::logMachineState($row['machineuuid'], $row['clientip'], Statistics::SUSPEND_LENGTH, $row['lastseen'], $length); + } + } + } + // Update -- yes this is not atomic. Should be sufficient for simple warnings and bookkeeping though. + Database::exec("UPDATE machine SET logintime = 0, state = 'OFFLINE' WHERE lastseen < If(state = 'STANDBY', $standby, $on) AND state <> 'OFFLINE'"); } state_cleanup(); diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php index f6765059..46de467b 100644 --- a/modules-available/statistics/inc/filter.inc.php +++ b/modules-available/statistics/inc/filter.inc.php @@ -14,18 +14,24 @@ class Filter public $operator; public $argument; + private static $keyCounter = 0; + + public static function getNewKey($colname) + { + return $colname . '_' . (self::$keyCounter++); + } + public function __construct($column, $operator, $argument = null) { $this->column = trim($column); $this->operator = trim($operator); - $this->argument = trim($argument); + $this->argument = is_array($argument) ? $argument : trim($argument); } /* returns a where clause and adds needed operators to the passed array */ public function whereClause(&$args, &$joins) { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); $addendum = ''; /* check if we have to do some parsing*/ @@ -187,20 +193,24 @@ class Id44Filter extends Filter public function whereClause(&$args, &$joins) { global $SIZE_ID44; - $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100); - $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100); + if ($this->operator === '=' || $this->operator === '!=') { + $lower = floor(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, false) * 1024 - 100); + $upper = ceil(Page_Statistics::findBestValue($SIZE_ID44, $this->argument, true) * 1024 + 100); + } else { + $lower = $upper = round($this->argument * 1024); + } - if ($this->operator == '=') { + if ($this->operator === '=') { return " id44mb BETWEEN $lower AND $upper"; - } elseif ($this->operator == '!=') { + } elseif ($this->operator === '!=') { return " id44mb < $lower OR id44mb > $upper"; - } elseif ($this->operator == '<=') { - return " id44mb < $upper"; - } elseif ($this->operator == '>=') { - return " id44mb > $lower"; - } elseif ($this->operator == '<') { + } elseif ($this->operator === '<=') { + return " id44mb <= $upper"; + } elseif ($this->operator === '>=') { + return " id44mb >= $lower"; + } elseif ($this->operator === '<') { return " id44mb < $lower"; - } elseif ($this->operator == '>') { + } elseif ($this->operator === '>') { return " id44mb > $upper"; } else { error_log("unimplemented operator in Id44Filter: $this->operator"); @@ -222,8 +232,7 @@ class StateFilter extends Filter $map = [ 'on' => ['IDLE', 'OCCUPIED'], 'off' => ['OFFLINE'], 'idle' => ['IDLE'], 'occupied' => ['OCCUPIED'], 'standby' => ['STANDBY'] ]; $neg = $this->operator == '!=' ? 'NOT ' : ''; if (array_key_exists($this->argument, $map)) { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); $args[$key] = $map[$this->argument]; return " machine.state $neg IN ( :$key ) "; } else { @@ -245,13 +254,17 @@ class LocationFilter extends Filter $recursive = (substr($this->operator, -1) === '~'); $this->operator = str_replace('~', '=', $this->operator); - settype($this->argument, 'int'); + if (is_array($this->argument)) { + if ($recursive) + Util::traceError('Cannot use ~ operator for location with array'); + } else { + settype($this->argument, 'int'); + } $neg = $this->operator === '=' ? '' : 'NOT'; if ($this->argument === 0) { return "machine.locationid IS $neg NULL"; } else { - global $unique_key; - $key = $this->column . '_arg' . ($unique_key++); + $key = Filter::getNewKey($this->column); if ($recursive) { $args[$key] = array_keys(Location::getRecursiveFlat($this->argument)); } else { diff --git a/modules-available/statistics/inc/filterset.inc.php b/modules-available/statistics/inc/filterset.inc.php index 25c5c8fa..774bfd18 100644 --- a/modules-available/statistics/inc/filterset.inc.php +++ b/modules-available/statistics/inc/filterset.inc.php @@ -9,6 +9,8 @@ class FilterSet private $sortDirection; private $sortColumn; + private $cache = false; + public function __construct($filters) { $this->filters = $filters; @@ -16,19 +18,28 @@ class FilterSet public function setSort($col, $direction) { - $this->sortDirection = $direction === 'DESC' ? 'DESC' : 'ASC'; + $direction = ($direction === 'DESC' ? 'DESC' : 'ASC'); - if (is_string($col) && array_key_exists($col, Page_Statistics::$columns)) { - $this->sortColumn = $col; - } else { + if (!is_string($col) || !array_key_exists($col, Page_Statistics::$columns)) { /* default sorting column is clientip */ - $this->sortColumn = 'clientip'; + $col = 'clientip'; } - + if ($col === $this->sortColumn && $direction === $this->sortDirection) + return; + $this->cache = false; + $this->sortDirection = $direction; + $this->sortColumn = $col; } public function makeFragments(&$where, &$join, &$sort, &$args) { + if ($this->cache !== false) { + $where = $this->cache['where']; + $join = $this->cache['join']; + $sort = $this->cache['sort']; + $args = $this->cache['args']; + return; + } /* generate where clause & arguments */ $where = ''; $joins = []; @@ -54,16 +65,13 @@ class FilterSet $sort = " ORDER BY " . $concreteCol . " " . $this->sortDirection . ", machineuuid ASC"; + $this->cache = compact('where', 'join', 'sort', 'args'); } public function isNoId44Filter() { - foreach ($this->filters as $filter) { - if (get_class($filter) === 'Id44Filter' && $filter->argument == 0) { - return true; - } - } - return false; + $filter = $this->hasFilter('Id44Filter'); + return $filter !== false && $filter->argument == 0; } public function getSortDirection() @@ -78,10 +86,58 @@ class FilterSet public function filterNonClients() { - if (Module::get('runmode') === false) + if (Module::get('runmode') === false || $this->hasFilter('IsClientFilter') !== false) return; + $this->cache = false; // Runmode module exists, add filter $this->filters[] = new IsClientFilter(true); } + /** + * @param string $type filter type (class name) + * @return false|Filter The filter, false if not found + */ + public function hasFilter($type) + { + foreach ($this->filters as $filter) { + if (get_class($filter) === $type) { + return $filter; + } + } + return false; + } + + /** + * Add a location filter based on the allowed permissions for the given permission. + * Returns false if the user doesn't have the given permission for any location. + * + * @param string $permission permission to use + * @return bool false if no permission for any location, true otherwise + */ + public function setAllowedLocationsFromPermission($permission) + { + $locs = User::getAllowedLocations($permission); + if (empty($locs)) + return false; + if (in_array(0, $locs)) { + if (!isset($this->filters['permissions'])) + return true; + unset($this->filters['permissions']); + } else { + $this->filters['permissions'] = new LocationFilter('=', $locs); + } + $this->cache = false; + return true; + } + + /** + * @return false|array + */ + public function getAllowedLocations() + { + if (isset($this->filters['permissions']->argument) && is_array($this->filters['permissions']->argument)) + return $this->filters['permissions']->argument; + return false; + } + } diff --git a/modules-available/statistics/inc/parser.inc.php b/modules-available/statistics/inc/parser.inc.php index 679055a7..0d39079d 100644 --- a/modules-available/statistics/inc/parser.inc.php +++ b/modules-available/statistics/inc/parser.inc.php @@ -104,10 +104,12 @@ class Parser { foreach ($lines as $line) { if (preg_match('/^Disk (\S+):.* (\d+) bytes/i', $line, $out)) { // --- Beginning of MBR disk --- + unset($hdd); if ($out[2] < 10000) // sometimes vmware reports lots of 512byte disks continue; + if (substr($out[1], 0, 8) === '/dev/dm-') // Ignore device mapper + continue; // disk total size and name - unset($hdd); $mbrToMbFactor = 0; // This is != 0 for mbr $sectorToMbFactor = 0; // This is != for gpt $hdd = array( @@ -122,10 +124,12 @@ class Parser { $hdds[] = &$hdd; } elseif (preg_match('/^Disk (\S+):\s+(\d+)\s+sectors,/i', $line, $out)) { // --- Beginning of GPT disk --- + unset($hdd); if ($out[2] < 1000) // sometimes vmware reports lots of 512byte disks continue; + if (substr($out[1], 0, 8) === '/dev/dm-') // Ignore device mapper + continue; // disk total size and name - unset($hdd); $mbrToMbFactor = 0; // This is != 0 for mbr $sectorToMbFactor = 0; // This is != for gpt $hdd = array( @@ -151,24 +155,44 @@ class Parser { $type = strtolower($out[4]); if ($type === '5' || $type === 'f' || $type === '85') { continue; + } elseif ($type === '44') { + $out[5] = 'OpenSLX-ID44'; + $color = '#5c1'; + } elseif ($type === '45') { + $out[5] = 'OpenSLX-ID45'; + $color = '#0d7'; + } elseif ($type === '82') { + $color = '#48f'; + } else { + $color = '#e55'; } + $partsize = round(($out[3] - $out[2]) * $mbrToMbFactor); $hdd['partitions'][] = array( 'id' => $out[1], 'name' => $out[1], 'size' => round($partsize / 1024, $partsize < 1024 ? 1 : 0), - 'type' => ($type === '44' ? 'OpenSLX' : $out[5]), + 'type' => $out[5], ); $hdd['json'][] = array( 'label' => $out[1], 'value' => $partsize, - 'color' => ($type === '44' ? '#4d4' : ($type === '82' ? '#48f' : '#e55')), + 'color' => $color, ); $hdd['used'] += $partsize; } elseif (isset($hdd) && $sectorToMbFactor !== 0 && preg_match(',^\s*(\d+)\s+(\d+)[\+\-]?\s+(\d+)[\+\-]?\s+\S+\s+([0-9a-f]+)\s+(.*)$,i', $line, $out)) { // --- GPT: Partition entry --- // Some partition $type = $out[5]; + if ($type === 'OpenSLX-ID44') { + $color = '#5c1'; + } elseif ($type === 'OpenSLX-ID45') { + $color = '#0d7'; + } elseif ($type === 'Linux swap') { + $color = '#48f'; + } else { + $color = '#e55'; + } $id = $hdd['devid'] . '-' . $out[1]; $partsize = round(($out[3] - $out[2]) * $sectorToMbFactor); $hdd['partitions'][] = array( @@ -180,7 +204,7 @@ class Parser { $hdd['json'][] = array( 'label' => $id, 'value' => $partsize, - 'color' => ($type === 'OpenSLX-ID44' ? '#4d4' : ($type === 'Linux swap' ? '#48f' : '#e55')), + 'color' => $color, ); $hdd['used'] += $partsize; } @@ -193,7 +217,7 @@ class Parser { $hdd['size'] = round(($hdd['sectors'] * $sectorToMbFactor) / 1024); } $free = $hdd['size'] - $hdd['used']; - if ($free > 5 || ($free / $hdd['size']) > 0.1) { + if ($hdd['size'] > 0 && ($free > 5 || ($free / $hdd['size']) > 0.1)) { $hdd['partitions'][] = array( 'id' => 'free-id-' . $i, 'name' => Dictionary::translate('unused'), diff --git a/modules-available/statistics/inc/statistics.inc.php b/modules-available/statistics/inc/statistics.inc.php index 2500f16f..1f8a081a 100644 --- a/modules-available/statistics/inc/statistics.inc.php +++ b/modules-available/statistics/inc/statistics.inc.php @@ -70,4 +70,21 @@ class Statistics return $list; } + const SESSION_LENGTH = '~session-length'; + const OFFLINE_LENGTH = '~offline-length'; + const SUSPEND_LENGTH = '~suspend-length'; + + public static function logMachineState($uuid, $ip, $type, $start, $length, $username = '') + { + return Database::exec('INSERT INTO statistic (dateline, typeid, machineuuid, clientip, username, data)' + . " VALUES (:start, :type, :uuid, :clientip, :username, :length)", array( + 'start' => $start, + 'type' => $type, + 'uuid' => $uuid, + 'clientip' => $ip, + 'username' => $username, + 'length' => $length, + )); + } + } diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php index 4e2dfcca..84e038a4 100644 --- a/modules-available/statistics/install.inc.php +++ b/modules-available/statistics/install.inc.php @@ -234,5 +234,22 @@ if (!tableHasColumn('machine', 'state')) { $res[] = UPDATE_DONE; } +// 2019-01-25: Add memory/temp stats column +if (!tableHasColumn('machine', 'live_tmpsize')) { + $ret = Database::exec("ALTER TABLE `machine` + ADD COLUMN `live_tmpsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id44mb`, + ADD COLUMN `live_tmpfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_tmpsize`, + ADD COLUMN `live_swapsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_tmpfree`, + ADD COLUMN `live_swapfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_swapsize`, + ADD COLUMN `live_memsize` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_swapfree`, + ADD COLUMN `live_memfree` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `live_memsize`, + ADD INDEX `live_tmpfree` (`live_tmpfree`), + ADD INDEX `live_memfree` (`live_memfree`)"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, 'Adding state column to machine table failed: ' . Database::lastError()); + } + $res[] = UPDATE_DONE; +} + // Create response responseFromArray($res); diff --git a/modules-available/statistics/lang/de/permissions.json b/modules-available/statistics/lang/de/permissions.json index 15303993..8579b28f 100644 --- a/modules-available/statistics/lang/de/permissions.json +++ b/modules-available/statistics/lang/de/permissions.json @@ -1,5 +1,12 @@ { - "view": "Client Statistiken anschauen.", - "note": "Client Notizen speichern.", - "delete": "Client löschen." + "hardware.projectors.edit": "Beamerzuweisung bearbeiten", + "hardware.projectors.view": "Beamerzuweisung anzeigen", + "machine.delete": "Rechner l\u00f6schen.", + "machine.note": "Anmerkungen zu einem Rechner speichern.", + "machine.note.edit": "Anmerkungen bearbeiten", + "machine.note.view": "Anmerkungen anzeigen", + "machine.view-details": "Clientinformationen anzeigen", + "view": "Statistiken anschauen.", + "view.list": "Clientliste anzeigen", + "view.summary": "Visualisierung anzeigen" }
\ No newline at end of file diff --git a/modules-available/statistics/lang/de/template-tags.json b/modules-available/statistics/lang/de/template-tags.json index 84c4690c..2567eea1 100644 --- a/modules-available/statistics/lang/de/template-tags.json +++ b/modules-available/statistics/lang/de/template-tags.json @@ -14,6 +14,7 @@ "lang_event": "Ereignis", "lang_eventType": "Typ", "lang_firstSeen": "Erste Aktivit\u00e4t", + "lang_free": "frei", "lang_gbRam": "RAM", "lang_hardwareSummary": "Hardware", "lang_hdds": "Festplatten", @@ -40,6 +41,7 @@ "lang_machineStandby": "Im Standby", "lang_machineSummary": "Zusammenfassung", "lang_maximumAbbrev": "Max.", + "lang_memFree": "RAM frei (MB)", "lang_memoryStats": "Arbeitsspeicher", "lang_model": "Modell", "lang_modelCount": "Anzahl", @@ -82,10 +84,12 @@ "lang_subnet": "Subnetz", "lang_sureDeletePermanent": "M\u00f6chten Sie diese(n) Rechner wirklich unwiderruflich aus der Datenbank entfernen?\r\n\r\nWichtig: L\u00f6schen verhindert nicht, dass ein Rechner nach erneutem Starten von bwLehrpool wieder in die Datenbank aufgenommen wird.", "lang_sureReplaceNoUndo": "Wollen Sie die Daten ausgew\u00e4hlten Rechner \u00fcbertragen? Diese Aktion kann nicht r\u00fcckg\u00e4ngig gemacht werden.", + "lang_swapFree": "swap frei (MB)", "lang_tempPart": "Temp. Partition", "lang_tempPartStats": "Tempor\u00e4re Partition", "lang_thoseAreProjectors": "Diese Modellnamen werden als Beamer behandelt, auch wenn die EDID-Informationen des Ger\u00e4tes anderes berichten.", "lang_timebarDesc": "Visuelle Darstellung der letzten Tage. Rote Abschnitte zeigen, wann der Rechner belegt war, gr\u00fcne, wann er nicht verwendet wurde, aber eingeschaltet war. Die leicht abgedunkelten Abschnitte markieren N\u00e4chte (22 bis 8 Uhr).", + "lang_tmpFree": "ID44 frei (MB)", "lang_tmpGb": "Temp-HDD", "lang_total": "Gesamt", "lang_usageDetails": "Nutzungsdetails", diff --git a/modules-available/statistics/lang/en/permissions.json b/modules-available/statistics/lang/en/permissions.json index 7be32f22..445f96b8 100644 --- a/modules-available/statistics/lang/en/permissions.json +++ b/modules-available/statistics/lang/en/permissions.json @@ -1,5 +1,12 @@ { - "view": "View client statistics.", - "note": "Save client notes.", - "delete": "Delete client." + "hardware.projectors.edit": "Edit beamer assignment", + "hardware.projectors.view": "Show beamer assignment", + "machine.delete": "Delete clients.", + "machine.note": "Save client notes.", + "machine.note.edit": "Edit notes", + "machine.note.view": "Show notes", + "machine.view-details": "Show client details", + "view": "View statistics.", + "view.list": "Show client list", + "view.summary": "Show visualization" }
\ No newline at end of file diff --git a/modules-available/statistics/lang/en/template-tags.json b/modules-available/statistics/lang/en/template-tags.json index b064ee50..1d9cd4da 100644 --- a/modules-available/statistics/lang/en/template-tags.json +++ b/modules-available/statistics/lang/en/template-tags.json @@ -14,6 +14,7 @@ "lang_event": "Event", "lang_eventType": "Type", "lang_firstSeen": "First seen", + "lang_free": "free", "lang_gbRam": "RAM", "lang_hardwareSummary": "Hardware", "lang_hdds": "Hard disk drives", @@ -40,6 +41,7 @@ "lang_machineStandby": "In standby mode", "lang_machineSummary": "Summary", "lang_maximumAbbrev": "max.", + "lang_memFree": "RAM free (MB)", "lang_memoryStats": "Memory", "lang_model": "Model", "lang_modelCount": "Count", @@ -82,10 +84,12 @@ "lang_subnet": "Subnet", "lang_sureDeletePermanent": "Are your sure you want to delete the selected machine(s) from the database? This cannot be undone.\r\n\r\nNote: Deleting machines from the database does not prevent booting up bwLehrpool again, which would recreate their respective database entries.", "lang_sureReplaceNoUndo": "Are you sure you want to replace the selected machine pairs? This action cannot be undone.", + "lang_swapFree": "swap free (MB)", "lang_tempPart": "Temp. partition", "lang_tempPartStats": "Temporary partition", "lang_thoseAreProjectors": "These model names will always be treated as beamers, even if the device's EDID data says otherwise.", "lang_timebarDesc": "Visual representation of the last few days. Red parts mark periods where the client was occupied, green parts where the client was idle. Dimmed parts mark nights (10pm to 8am).", + "lang_tmpFree": "ID44 free (MB)", "lang_tmpGb": "Temp HDD", "lang_total": "Total", "lang_usageDetails": "Detailed usage", diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index 5fe4ebfa..a9cde6fb 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -1,165 +1,177 @@ <?php global $STATS_COLORS, $SIZE_ID44, $SIZE_RAM; -global $unique_key; $STATS_COLORS = array(); for ($i = 0; $i < 10; ++$i) { $STATS_COLORS[] = '#55' . sprintf('%02s%02s', dechex((($i + 1) * ($i + 1)) / .3922), dechex(abs((5 - $i) * 51))); } //$STATS_COLORS = array('#57e', '#ee8', '#5ae', '#fb7', '#6d7', '#e77', '#3af', '#666', '#e0e', '#999'); -$SIZE_ID44 = array(0, 8, 16, 24, 30, 40, 50, 60, 80, 100, 120, 150, 180, 250, 300, 350, 400, 450, 500); +$SIZE_ID44 = array(0, 8, 16, 24, 30, 40, 50, 60, 80, 100, 120, 150, 180, 250, 300, 400, 500, 1000, 2000, 4000); $SIZE_RAM = array(1, 2, 3, 4, 6, 8, 10, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 320, 480, 512, 768, 1024); class Page_Statistics extends Page { /* some constants, TODO: Find a better place */ - public static $op_nominal; - public static $op_ordinal; - public static $op_stringcmp; + const OP_NOMINAL = ['!=', '=']; + const OP_ORDINAL = ['!=', '<=', '>=', '=', '<', '>']; + const OP_STRCMP = ['!~', '~', '=', '!=']; public static $columns; private $query; - - private $locationsAllowedToView; + private $show; /** * @var bool whether we have a SubPage from the pages/ subdir */ private $haveSubpage; - /* PHP sucks, no static, const array definitions... Or am I missing something? */ - public function initConstants() + /** + * Do this here instead of const since we need to check for available modules while building array. + */ + public static function initConstants() { - Page_Statistics::$op_nominal = ['!=', '=']; - Page_Statistics::$op_ordinal = ['!=', '<=', '>=', '=', '<', '>']; - Page_Statistics::$op_stringcmp = ['!~', '~', '=', '!=']; Page_Statistics::$columns = [ 'machineuuid' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true, ], 'macaddr' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true, ], 'firstseen' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'lastseen' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'logintime' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'date', 'column' => true, ], 'realcores' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true, ], 'systemmodel' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true, ], 'cpumodel' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true, ], 'hddgb' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => false, 'map_sort' => 'id44mb' ], 'gbram' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'map_sort' => 'mbram', 'column' => false, ], 'kvmstate' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'enum', 'column' => true, 'values' => ['ENABLED', 'DISABLED', 'UNSUPPORTED'] ], 'badsectors' => [ - 'op' => Page_Statistics::$op_ordinal, + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true ], 'clientip' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true ], 'hostname' => [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'string', 'column' => true ], 'subnet' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => false ], 'currentuser' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'string', 'column' => true ], 'state' => [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::OP_NOMINAL, 'type' => 'enum', 'column' => true, 'values' => ['occupied', 'on', 'off', 'idle', 'standby'] ], - 'runtime' => [ - 'op' => Page_Statistics::$op_ordinal, + 'live_swapfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, + 'type' => 'int', + 'column' => true + ], + 'live_memfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, + 'type' => 'int', + 'column' => true + ], + 'live_tmpfree' => [ + 'op' => Page_Statistics::OP_ORDINAL, 'type' => 'int', 'column' => true ], ]; if (Module::isAvailable('locations')) { Page_Statistics::$columns['location'] = [ - 'op' => Page_Statistics::$op_stringcmp, + 'op' => Page_Statistics::OP_STRCMP, 'type' => 'enum', 'column' => false, 'values' => array_keys(Location::getLocationsAssoc()), ]; } - /* TODO ... */ } protected function doPreprocess() { - $this->initConstants(); User::load(); if (!User::isLoggedIn()) { Message::addError('main.no-permission'); Util::redirect('?do=Main'); } - $this->locationsAllowedToView = User::getAllowedLocations("view"); - - - $show = Request::any('show', 'stat', 'string'); - $show = preg_replace('/[^a-z0-9_\-]/', '', $show); + $this->show = Request::any('show', false, 'string'); + if ($this->show === false) { + if (User::hasPermission('view.summary')) { + $this->show = 'summary'; + } elseif (User::hasPermission('view.list')) { + $this->show = 'list'; + } else { + User::assertPermission('view.summary'); + } + } else { + $this->show = preg_replace('/[^a-z0-9_\-]/', '', $this->show); + } - if (file_exists('modules/statistics/pages/' . $show . '.inc.php')) { + if (file_exists('modules/statistics/pages/' . $this->show . '.inc.php')) { - require_once 'modules/statistics/pages/' . $show . '.inc.php'; + require_once 'modules/statistics/pages/' . $this->show . '.inc.php'; $this->haveSubpage = true; SubPage::doPreprocess(); @@ -168,20 +180,23 @@ class Page_Statistics extends Page $action = Request::post('action'); if ($action === 'setnotes') { $uuid = Request::post('uuid', '', 'string'); - $locationid = Database::queryFirst('SELECT locationid FROM machine WHERE machineuuid = :uuid', - array('uuid' => $uuid))['locationid']; - if (User::hasPermission("note", $locationid)) { - $text = Request::post('content', '', 'string'); - if (empty($text)) { - $text = null; - } - Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array( - 'uuid' => $uuid, - 'text' => $text, - )); - Message::addSuccess('notes-saved'); - Util::redirect('?do=Statistics&uuid=' . $uuid); + $res = Database::queryFirst('SELECT locationid FROM machine WHERE machineuuid = :uuid', + array('uuid' => $uuid)); + if ($res === false) { + Message::addError('unknown-machine', $uuid); + Util::redirect('?do=statistics'); } + User::assertPermission("machine.note.edit", (int)$res['locationid']); + $text = Request::post('content', null, 'string'); + if (empty($text)) { + $text = null; + } + Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array( + 'uuid' => $uuid, + 'text' => $text, + )); + Message::addSuccess('notes-saved'); + Util::redirect('?do=statistics&uuid=' . $uuid); } elseif ($action === 'delmachines') { $this->deleteMachines(); Util::redirect('?do=statistics', true); @@ -204,14 +219,20 @@ class Page_Statistics extends Page Message::addError('main.parameter-empty', 'uuid'); return; } + $allowedLocations = User::getAllowedLocations("machine.delete"); + if (empty($allowedLocations)) { + Message::addError('main.no-permission'); + Util::redirect('?do=statistics'); + } $res = Database::simpleQuery('SELECT machineuuid, locationid FROM machine WHERE machineuuid IN (:ids)', compact('ids')); $ids = array_flip($ids); $delete = []; - $allowedLocations = User::getAllowedLocations("delete"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + unset($ids[$row['machineuuid']]); if (in_array($row['locationid'], $allowedLocations)) { - unset($ids[$row['machineuuid']]); $delete[] = $row['machineuuid']; + } else { + Message::addError('locations.no-permission-location', $row['locationid']); } } if (!empty($delete)) { @@ -219,7 +240,6 @@ class Page_Statistics extends Page Message::addSuccess('deleted-n-machines', count($delete)); } if (!empty($ids)) { - // TODO: Warn permissions Message::addWarning('unknown-machine', implode(', ', array_keys($ids))); } } @@ -237,8 +257,6 @@ class Page_Statistics extends Page return; } - $show = Request::get('show', 'stat', 'string'); - /* read filter */ $this->query = Request::any('filters', false); if ($this->query === false) { @@ -251,23 +269,31 @@ class Page_Statistics extends Page $filterSet = new FilterSet($filters); $filterSet->setSort($sortColumn, $sortDirection); - if ($show == 'list') { + if (!$filterSet->setAllowedLocationsFromPermission('view.' . $this->show)) { + Message::addError('main.no-permission'); + Util::redirect('?do=main'); + } + + if ($this->show === 'list') { Render::openTag('div', array('class' => 'row')); $this->showFilter('list', $filterSet); Render::closeTag('div'); $this->showMachineList($filterSet); return; + } elseif ($this->show === 'summary') { + $filterSet->filterNonClients(); + Render::openTag('div', array('class' => 'row')); + $this->showFilter('summary', $filterSet); + $this->showSummary($filterSet); + $this->showMemory($filterSet); + $this->showId44($filterSet); + $this->showKvmState($filterSet); + $this->showLatestMachines($filterSet); + $this->showSystemModels($filterSet); + Render::closeTag('div'); + } else { + Message::addError('main.value-invalid', 'show', $this->show); } - $filterSet->filterNonClients(); - Render::openTag('div', array('class' => 'row')); - $this->showFilter('stat', $filterSet); - $this->showSummary($filterSet); - $this->showMemory($filterSet); - $this->showId44($filterSet); - $this->showKvmState($filterSet); - $this->showLatestMachines($filterSet); - $this->showSystemModels($filterSet); - Render::closeTag('div'); } /** @@ -295,15 +321,17 @@ class Page_Statistics extends Page $locsFlat = array(); if (Module::isAvailable('locations')) { + $allowed = $filterSet->getAllowedLocations(); foreach (Location::getLocations() as $loc) { $locsFlat['L' . $loc['locationid']] = array( 'pad' => $loc['locationpad'], 'name' => $loc['locationname'], - 'disabled' => !in_array($loc['locationid'], $this->locationsAllowedToView) + 'disabled' => $allowed !== false && !in_array($loc['locationid'], $allowed), ); } } + Permission::addGlobalTags($data['perms'], null, ['view.summary', 'view.list']); $data['locations'] = json_encode($locsFlat); Render::addTemplate('filterbox', $data); @@ -355,8 +383,6 @@ class Page_Statistics extends Page private function showSummary($filterSet) { $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE $where", $args); // If we only have one machine, redirect to machine details if ($known['val'] == 1) { @@ -417,8 +443,6 @@ class Page_Statistics extends Page global $STATS_COLORS; $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $res = Database::simpleQuery('SELECT systemmodel, Round(AVG(realcores)) AS cores, Count(*) AS `count` FROM machine' . " $join WHERE $where GROUP BY systemmodel ORDER BY `count` DESC, systemmodel ASC", $args); $lines = array(); @@ -451,8 +475,6 @@ class Page_Statistics extends Page global $STATS_COLORS, $SIZE_RAM; $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $res = Database::simpleQuery("SELECT mbram, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY mbram", $args); $lines = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { @@ -498,8 +520,6 @@ class Page_Statistics extends Page private function showKvmState($filterSet) { $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6'); $res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY kvmstate ORDER BY `count` DESC", $args); $lines = array(); @@ -523,8 +543,6 @@ class Page_Statistics extends Page global $STATS_COLORS, $SIZE_ID44; $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $res = Database::simpleQuery("SELECT id44mb, Count(*) AS `count` FROM machine $join WHERE $where GROUP BY id44mb", $args); $lines = array(); $total = 0; @@ -576,8 +594,6 @@ class Page_Statistics extends Page private function showLatestMachines($filterSet) { $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $args['cutoff'] = ceil(time() / 3600) * 3600 - 86400 * 10; $res = Database::simpleQuery("SELECT machineuuid, clientip, hostname, firstseen, mbram, kvmstate, id44mb FROM machine $join" @@ -611,8 +627,6 @@ class Page_Statistics extends Page { Module::isAvailable('js_stupidtable'); $filterSet->makeFragments($where, $join, $sort, $args); - $args['allowedLocations'] = $this->locationsAllowedToView; - $where = "locationid IN (:allowedLocations) AND ($where)"; $xtra = ''; if ($filterSet->isNoId44Filter()) { $xtra .= ', data'; @@ -629,14 +643,17 @@ class Page_Statistics extends Page . " $join WHERE $where $sort", $args); $rows = array(); $singleMachine = 'none'; - $deleteAllowedLocations = User::getAllowedLocations("delete"); + $deleteAllowedLocations = User::getAllowedLocations("machine.delete"); + $detailsAllowedLocations = User::getAllowedLocations("machine.view-details"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($singleMachine === 'none') { $singleMachine = $row['machineuuid']; } else { $singleMachine = false; } - $row['deleteAllowed'] = in_array($row['locationid'], $deleteAllowedLocations); + // TODO: This only makes sense as long as there is only one action to perform on selected clients; reboot/shutdown is planned + $row['delete_disabled'] = in_array($row['locationid'], $deleteAllowedLocations) ? '' : 'disabled'; + $row['link_details'] = in_array($row['locationid'], $detailsAllowedLocations); $row['state_' . $row['state']] = true; //$row['firstseen'] = Util::prettyTime($row['firstseen']); $row['lastseen_int'] = $row['lastseen']; @@ -668,12 +685,13 @@ class Page_Statistics extends Page $row['modeName'] = $data['modeName']; } } + $row['locationname'] = Location::getName($row['locationid']); $rows[] = $row; } if ($singleMachine !== false && $singleMachine !== 'none') { Util::redirect('?do=statistics&uuid=' . $singleMachine); } - Render::addTemplate('clientlist', array( + $data = array( 'rowCount' => count($rows), 'rows' => $rows, 'query' => $this->query, @@ -684,7 +702,8 @@ class Page_Statistics extends Page 'showList' => 1, 'show' => 'list', 'redirect' => $_SERVER['QUERY_STRING'] - )); + ); + Render::addTemplate('clientlist', $data); } private function ramColorClass($mb) @@ -755,9 +774,9 @@ class Page_Statistics extends Page $row['currentsession'] = $lecture['displayname']; $row['lectureid'] = $lecture['lectureid']; } + $row['session'] = $row['currentsession']; + return; } - $row['session'] = $row['currentsession']; - return; } $res = Database::simpleQuery('SELECT dateline, username, data FROM statistic' . " WHERE clientip = :ip AND typeid = '.vmchooser-session-name'" @@ -774,23 +793,23 @@ class Page_Statistics extends Page } if ($session !== false) { $row['session'] = $session['data']; - $row['username'] = $session['username']; + if (empty($row['currentuser'])) { + $row['username'] = $session['username']; + } } } private function showMachine($uuid) { - $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state,' - . ' mbram, kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', + $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot, state, + mbram, live_tmpsize, live_tmpfree, live_swapsize, live_swapfree, live_memsize, live_memfree, + kvmstate, cpumodel, id44mb, data, hostname, currentuser, currentsession, notes FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($client === false) { Message::addError('unknown-machine', $uuid); return; } - if (!in_array($client['locationid'], $this->locationsAllowedToView)) { - Message::addError('main.no-permission'); - return; - } + User::assertPermission('machine.view-details', (int)$client['locationid']); // Hack: Get raw collected data if (Request::get('raw', false)) { Header('Content-Type: text/plain; charset=utf-8'); @@ -820,6 +839,7 @@ class Page_Statistics extends Page $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 { @@ -829,9 +849,14 @@ class Page_Statistics extends Page $client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')'; } } - $client['logintime_s'] = date('d.m.Y H:i', $client['logintime']); - $client['gbram'] = round(round($client['mbram'] / 500) / 2, 1); + $client['gbram'] = round(ceil($client['mbram'] / 512) / 2, 1); $client['gbtmp'] = round($client['id44mb'] / 1024); + foreach (['tmp', '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); + } $client['ramclass'] = $this->ramColorClass($client['mbram']); $client['kvmclass'] = $this->kvmColorClass($client['kvmstate']); $client['hddclass'] = $this->hddColorClass($client['gbtmp']); @@ -856,7 +881,7 @@ class Page_Statistics extends Page Parser::parsePci($client['lspci1'], $client['lspci2'], $section[2]); } if (isset($hdds['hdds']) && $section[1] === 'smartctl') { - // This currently required that the partition table section comes first... + // This currently requires that the partition table section comes first... Parser::parseSmartctl($hdds['hdds'], $section[2]); } } @@ -891,6 +916,7 @@ class Page_Statistics extends Page $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); // Sessions @@ -899,16 +925,18 @@ class Page_Statistics extends Page //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 ('~session-length', '~offline-length') AND machineuuid = :uuid ORDER BY dateline ASC", array( + . " WHERE dateline > :cutoff AND typeid IN (:sessionLength, :offlineLength) AND machineuuid = :uuid ORDER BY dateline ASC", array( 'cutoff' => $cutoff - 86400 * 14, 'uuid' => $uuid, + 'sessionLength' => Statistics::SESSION_LENGTH, + 'offlineLength' => Statistics::OFFLINE_LENGTH, )); $spans['rows'] = array(); $spans['graph'] = ''; $last = false; $first = true; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if (!$client['isclient'] && $row['typeid'] === '~session-length') + 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 @@ -934,9 +962,12 @@ class Page_Statistics extends Page } $row['from'] = Util::prettyTime($row['dateline']); $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']); - if ($row['typeid'] === '~offline-length') { + if ($row['typeid'] === Statistics::OFFLINE_LENGTH) { $row['glyph'] = 'off'; $color = '#444'; + } elseif ($row['typeid'] === Statistics::SUSPEND_LENGTH) { + $row['glyph'] = 'pause'; + $color = '#686'; } else { $row['glyph'] = 'user'; $color = '#e77'; @@ -956,8 +987,26 @@ class Page_Statistics extends Page } if ($client['state'] === 'OCCUPIED') { $spans['graph'] .= '<div style="background:#e99;left:' . round(($client['logintime'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['logintime'] + 900) * $scale, 2) . '%"> </div>'; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['logintime']), + 'duration' => '-', + 'glyph' => 'user', + ]; + $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']); } elseif ($client['state'] === 'OFFLINE') { $spans['graph'] .= '<div style="background:#444;left:' . round(($client['lastseen'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['lastseen'] + 900) * $scale, 2) . '%"> </div>'; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['lastseen']), + 'duration' => '-', + 'glyph' => 'off', + ]; + } elseif ($client['state'] === 'STANDBY') { + $spans['graph'] .= '<div style="background:#686;left:' . round(($client['lastseen'] - $cutoff) * $scale, 2) . '%;width:' . round(($NOW - $client['lastseen'] + 900) * $scale, 2) . '%"> </div>'; + $spans['rows'][] = [ + 'from' => Util::prettyTime($client['lastseen']), + 'duration' => '-', + 'glyph' => 'pause', + ]; } $t = explode('-', date('Y-n-j-G', $cutoff)); if ($t[3] >= 8 && $t[3] <= 22) { @@ -1002,8 +1051,10 @@ class Page_Statistics extends Page )); } // Notes - $client["notesAllowed"] = User::hasPermission("note", $client["locationid"]); - Render::addTemplate('machine-notes', $client); + if (User::hasPermission('machine.note.*', (int)$client['locationid'])) { + Permission::addGlobalTags($client['perms'], (int)$client['locationid'], ['machine.note.edit']); + Render::addTemplate('machine-notes', $client); + } } private function eventToIconName($event) @@ -1026,6 +1077,8 @@ class Page_Statistics extends Page protected function doAjax() { + if (!User::load()) + return; $param = Request::any('lookup', false, 'string'); if ($param === false) { die('No lookup given'); @@ -1089,3 +1142,5 @@ class Page_Statistics extends Page ), true); } } + +Page_Statistics::initConstants(); diff --git a/modules-available/statistics/pages/projectors.inc.php b/modules-available/statistics/pages/projectors.inc.php index cde542c6..cc808cf0 100644 --- a/modules-available/statistics/pages/projectors.inc.php +++ b/modules-available/statistics/pages/projectors.inc.php @@ -13,12 +13,13 @@ class SubPage private static function handleProjector($action) { + User::assertPermission('hardware.projectors.edit'); $hwid = Request::post('hwid', false, 'int'); if ($hwid === false) { Util::traceError('Param hwid missing'); } if ($action === 'addprojector') { - Database::exec('INSERT INTO statistic_hw_prop (hwid, prop, value)' + Database::exec('INSERT IGNORE INTO statistic_hw_prop (hwid, prop, value)' . ' VALUES (:hwid, :prop, :value)', array( 'hwid' => $hwid, 'prop' => 'projector', @@ -43,6 +44,7 @@ class SubPage private static function showProjectors() { + User::assertPermission('hardware.projectors.*'); $res = Database::simpleQuery('SELECT h.hwname, h.hwid FROM statistic_hw h' . " INNER JOIN statistic_hw_prop p ON (h.hwid = p.hwid AND p.prop = :projector)" . " WHERE h.hwtype = :screen ORDER BY h.hwname ASC", array( diff --git a/modules-available/statistics/permissions/permissions.json b/modules-available/statistics/permissions/permissions.json index 97a49036..663a8dc4 100644 --- a/modules-available/statistics/permissions/permissions.json +++ b/modules-available/statistics/permissions/permissions.json @@ -1,5 +1,26 @@ -[ - "view", - "note", - "delete" -]
\ No newline at end of file +{ + "machine.delete": { + "location-aware": true + }, + "machine.note.view": { + "location-aware": true + }, + "machine.note.edit": { + "location-aware": true + }, + "hardware.projectors.view": { + "location-aware": false + }, + "hardware.projectors.edit": { + "location-aware": false + }, + "machine.view-details": { + "location-aware": true + }, + "view.summary": { + "location-aware": true + }, + "view.list": { + "location-aware": true + } +}
\ No newline at end of file diff --git a/modules-available/statistics/style.css b/modules-available/statistics/style.css index 1496ac87..c48275ba 100644 --- a/modules-available/statistics/style.css +++ b/modules-available/statistics/style.css @@ -8,4 +8,36 @@ border-radius: 25px; font-size: 20px; line-height: 40px; +} + +.meter { + position: relative; + border-radius: 5px; + border: 1px solid #999; + background: linear-gradient(to right, #9e6 0%, #fb8 66%, #f32 100%); + height: 1.25em; + padding: 0; + width: 100%; + overflow: hidden; +} + +.meter .bar { + position: absolute; + top: 0; + height: 100%; + right: 0; + background: #efe; + display: inline-block; + padding: 0; + margin: 0 0 0 auto; +} + +.meter .text { + position: absolute; + right: 5px; + overflow: visible; + font-size: 8pt; + white-space: nowrap; + z-index: 1000; + text-shadow: #fff 1px 1px; }
\ No newline at end of file diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html index d06eb4f7..18a5d10a 100644 --- a/modules-available/statistics/templates/clientlist.html +++ b/modules-available/statistics/templates/clientlist.html @@ -34,6 +34,11 @@ <span class="glyphicon glyphicon-filter"></span> </button> </td> + <td> + <button type="button" class="btn btn-default btn-xs" onclick="popupFilter('location')"> + <span class="glyphicon glyphicon-filter"></span> + </button> + </td> </tr> <tr> <th data-sort="string">{{lang_machine}}</th> @@ -43,18 +48,17 @@ <th data-sort="int" class="text-right">{{lang_gbRam}}</th> <th data-sort="int" class="text-right">{{lang_tmpGb}}</th> <th data-sort="int">{{lang_cpuModel}}</th> + <th data-sort="string">{{lang_location}}</th> </tr> </thead> <tbody> {{#rows}} <tr> <td data-sort-value="{{hostname}}" class="text-nowrap"> - {{#deleteAllowed}} - <div class="checkbox checkbox-inline"> - <input type="checkbox" name="uuid[]" value="{{machineuuid}}" class="deleteCheckboxes"> - <label></label> - </div> - {{/deleteAllowed}} + <div class="checkbox checkbox-inline"> + <input type="checkbox" name="uuid[]" value="{{machineuuid}}" class="deleteCheckboxes" {{delete_disabled}}> + <label></label> + </div> {{#hasnotes}} <span class="glyphicon glyphicon-exclamation-sign pull-right"></span> {{/hasnotes}} @@ -70,7 +74,13 @@ {{#state_STANDBY}} <span class="glyphicon glyphicon-off green" title="{{lang_machineStandby}}"></span> {{/state_STANDBY}} - <a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a> + {{#link_details}} + <a href="?do=Statistics&uuid={{machineuuid}}"> + {{/link_details}} + <b>{{hostname}}</b> + {{#link_details}} + </a> + {{/link_details}} <div class="small">{{machineuuid}}</div> {{#rmmodule}} <div class="small">{{lang_runMode}}: @@ -93,6 +103,7 @@ </div>{{/nohdd}} </td> <td data-sort-value="{{realcores}}">{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td> + <td data-sort-value="{{locationname}}">{{locationname}}</td> </tr> {{/rows}} </tbody> diff --git a/modules-available/statistics/templates/cpumodels.html b/modules-available/statistics/templates/cpumodels.html index d684c914..d89a5b2f 100644 --- a/modules-available/statistics/templates/cpumodels.html +++ b/modules-available/statistics/templates/cpumodels.html @@ -19,11 +19,11 @@ <tr id="{{id}}" class="{{collapse}}"> <td data-sort-value="{{systemmodel}}" class="text-left text-nowrap filter-col" data-filter-col="systemmodel"> <table style="width:100%; table-layout: fixed;"><tr><td style="overflow:hidden;text-overflow: ellipsis;"> - <a class="filter-val" data-filter-val="{{systemmodel}}" href="?do=Statistics&show=stat&filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a> + <a class="filter-val" data-filter-val="{{systemmodel}}" href="?do=Statistics&show=summary&filters={{query}}~,~systemmodel={{urlsystemmodel}}">{{systemmodel}}</a> </td></tr></table> </td> <td data-sort-value="{{cores}}" class="text-right filter-col" data-filter-col="realcores"> - <a class="filter-val" data-filter-val="{{cores}}" href="?do=Statistics&show=stat&filters={{query}}~,~realcores={{cores}}">{{cores}}</a> + <a class="filter-val" data-filter-val="{{cores}}" href="?do=Statistics&show=summary&filters={{query}}~,~realcores={{cores}}">{{cores}}</a> </td> <td class="text-right">{{count}}</td> </tr> diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html index 58b66a75..07aa7320 100644 --- a/modules-available/statistics/templates/filterbox.html +++ b/modules-available/statistics/templates/filterbox.html @@ -13,7 +13,7 @@ <select id="operatorSelect" name="operator" class="form-control col-4-xs"> </select> </div> <div class="form-group"> - <input name="argument" id="argumentInput" class="form-control col-4-xs"> </input> + <input name="argument" id="argumentInput" class="form-control col-4-xs"> <select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select> </div> @@ -41,11 +41,11 @@ <div class="btn-group pull-right"> - <button class="btn btn-default {{statButtonClass}}" type="submit" name="show" value="stat"> + <button class="btn btn-default {{statButtonClass}}" type="submit" name="show" value="summary" {{perms.view.summary.disabled}}> <span class="glyphicon glyphicon-stats"></span> {{lang_showVisualization}} </button> - <button class="btn btn-default {{listButtonClass}}" type="submit" name="show" value="list"> + <button class="btn btn-default {{listButtonClass}}" type="submit" name="show" value="list" {{perms.view.list.disabled}}> <span class="glyphicon glyphicon-list"></span> {{lang_showList}} </button> @@ -101,7 +101,10 @@ var slxFilterNames = { currentuser: '{{lang_currentUser}}', subnet: '{{lang_subnet}}', runtime: '{{lang_runtimeHours}}', - hostname: '{{lang_hostname}}' + hostname: '{{lang_hostname}}', + live_swapfree: '{{lang_swapFree}}', + live_memfree: '{{lang_memFree}}', + live_tmpfree: '{{lang_tmpFree}}' }; slxLocations = {{{locations}}}; @@ -169,16 +172,16 @@ document.addEventListener("DOMContentLoaded", function () { })); }); /* also set the type of the input */ - if (columns[col]['type'] == 'date') { + if (columns[col]['type'] === 'date') { $('#argumentInput').datepicker({format : 'yyyy-mm-dd'}); $('#argumentSelect').hide(); - } else if(columns[col]['type'] == 'enum') { + } else if(columns[col]['type'] === 'enum') { $('#argumentSelect').empty(); $('#argumentInput').hide(); $('#argumentSelect').show(); columns[col]['values'].forEach(function (v) { var t = v; - var disabled = true; + var disabled = (col === 'location'); if (col === 'location' && slxLocations['L' + v]) { t = slxLocations['L' + v].pad + ' ' + slxLocations['L' + v].name; disabled = slxLocations['L' + v].disabled; diff --git a/modules-available/statistics/templates/id44.html b/modules-available/statistics/templates/id44.html index d3b1ab1c..de1c71ad 100644 --- a/modules-available/statistics/templates/id44.html +++ b/modules-available/statistics/templates/id44.html @@ -17,7 +17,7 @@ {{#rows}} <tr id="tmpid{{gb}}" class="{{class}} {{collapse}}"> <td data-sort-value="{{gb}}" class="text-left text-nowrap"> - <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&show=stat&filters={{query}}~,~hddgb={{gb}}">{{gb}} GiB</a> + <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&show=summary&filters={{query}}~,~hddgb={{gb}}">{{gb}} GiB</a> </td> <td class="text-right">{{count}}</td> </tr> diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html index 3704eda0..efa3bad3 100644 --- a/modules-available/statistics/templates/kvmstate.html +++ b/modules-available/statistics/templates/kvmstate.html @@ -17,7 +17,7 @@ {{#rows}} <tr id="kvm{{kvmstate}}"> <td data-sort-value="{{kvmstate}}" class="text-left text-nowrap"> - <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&show=stat&filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a> + <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&show=summary&filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a> </td> <td class="text-right">{{count}}</td> </tr> diff --git a/modules-available/statistics/templates/machine-hdds.html b/modules-available/statistics/templates/machine-hdds.html index fd6cf1be..b839dfca 100644 --- a/modules-available/statistics/templates/machine-hdds.html +++ b/modules-available/statistics/templates/machine-hdds.html @@ -20,7 +20,7 @@ <div>{{lang_powerOnTime}}: {{s_PowerOnHours}} {{lang_hours}} ({{PowerOnTime}})</div> {{/s_PowerOnHours}} <div class="row"> - <div class="col-sm-6"> + <div class="col-sm-7"> <table class="table table-condensed table-striped table-responsive"> <tr> <th>{{lang_partName}}</th> @@ -30,14 +30,14 @@ {{#partitions}} <tr id="{{id}}"> <td>{{name}}</td> - <td class="text-right">{{size}} GiB</td> + <td class="text-right text-nowrap">{{size}} GiB</td> <td>{{type}}</td> </tr> {{/partitions}} </table> <div class="slx-bold">{{lang_total}}: {{size}} GiB</div> </div> - <div class="col-sm-6"> + <div class="col-sm-5"> <canvas id="{{devid}}-chart" style="width:100%;height:250px"></canvas> <script type="text/javascript"> document.addEventListener("DOMContentLoaded", function() { diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html index d8f2c521..44f03a99 100644 --- a/modules-available/statistics/templates/machine-main.html +++ b/modules-available/statistics/templates/machine-main.html @@ -117,9 +117,23 @@ <tr class="{{ramclass}}"> <td class="text-nowrap">{{lang_ram}}</td> <td> - {{gbram}} GiB - {{#maxram}}({{lang_maximumAbbrev}} {{maxram}}){{/maxram}} - {{ramtype}} + <div> + {{gbram}} GiB + {{#maxram}}({{lang_maximumAbbrev}} {{maxram}}){{/maxram}} + {{ramtype}} + </div> + {{#live_memsize}} + <div class="meter"> + <div class="text">{{live_memfree_s}} {{lang_free}}</div> + <div class="bar" style="width:{{live_mempercent}}%"></div> + </div> + {{/live_memsize}} + {{#live_swapsize}} + <div class="meter"> + <div class="text">{{live_swapfree_s}} {{lang_free}}</div> + <div class="bar" style="width:{{live_swappercent}}%"></div> + </div> + {{/live_swapsize}} </td> </tr> {{#extram}} @@ -135,7 +149,17 @@ {{/extram}} <tr class="{{hddclass}}"> <td class="text-nowrap">{{lang_tempPart}}</td> - <td>{{gbtmp}} GiB</td> + <td> + <div> + {{gbtmp}} GiB + </div> + {{#live_tmpsize}} + <div class="meter"> + <div class="text">{{live_tmpfree_s}} {{lang_free}}</div> + <div class="bar" style="width:{{live_tmppercent}}%"></div> + </div> + {{/live_tmpsize}} + </td> </tr> <tr class="{{kvmclass}}"> <td class="text-nowrap">{{lang_64bitSupport}}</td> @@ -157,13 +181,19 @@ {{#hwname}} <div class="pull-right btn-group btn-group-xs"> {{#projector}} - <a href="?do=statistics&show=projectors" class="btn btn-default">{{lang_projector}}</a> + <a href="?do=statistics&show=projectors" class="btn btn-default {{perms.hardware.projectors.view.disabled}}"> + {{lang_projector}} + </a> <button form="delprojector" type="submit" name="hwid" value="{{hwid}}" - class="btn btn-danger"><span class="glyphicon glyphicon-remove"></span></button> + class="btn btn-danger" {{perms.hardware.projectors.edit.disabled}}> + <span class="glyphicon glyphicon-remove"></span> + </button> {{/projector}} {{^projector}} <button form="addprojector" type="submit" name="hwid" value="{{hwid}}" - class="btn btn-success"><span class="glyphicon glyphicon-plus"></span> {{lang_projector}}</button> + class="btn btn-success" {{perms.hardware.projectors.edit.disabled}}> + <span class="glyphicon glyphicon-plus"></span> {{lang_projector}} + </button> {{/projector}} </div> {{/hwname}} diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html index 66e44da4..c352580f 100644 --- a/modules-available/statistics/templates/machine-notes.html +++ b/modules-available/statistics/templates/machine-notes.html @@ -8,9 +8,12 @@ <input type="hidden" name="token" value="{{token}}"> <input type="hidden" name="action" value="setnotes"> <input type="hidden" name="uuid" value="{{machineuuid}}"> - <textarea name="content" class="form-control" cols="101" rows="10" {{^notesAllowed}}disabled{{/notesAllowed}}>{{notes}}</textarea> + <textarea name="content" class="form-control" cols="101" rows="10" {{perms.machine.note.edit.disabled}}>{{notes}}</textarea> <br/> - <button type="submit" class="btn btn-primary pull-right" {{^notesAllowed}}disabled{{/notesAllowed}}><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button> + <button type="submit" class="btn btn-primary pull-right" {{perms.machine.note.edit.disabled}}> + <span class="glyphicon glyphicon-floppy-disk"></span> + {{lang_save}} + </button> </form> </div> </div> diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html index 6bc13980..cfb86062 100644 --- a/modules-available/statistics/templates/memory.html +++ b/modules-available/statistics/templates/memory.html @@ -17,7 +17,7 @@ {{#rows}} <tr id="ramid{{gb}}" class="{{class}} {{collapse}}"> <td class="text-left text-nowrap" data-sort-value="{{gb}}"> - <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&show=stat&filters={{query}}~,~gbram={{gb}}">{{gb}} GiB</a> + <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&show=summary&filters={{query}}~,~gbram={{gb}}">{{gb}} GiB</a> </td> <td class="text-right">{{count}}</td> </tr> diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html index fe9559ed..3ede7bc5 100644 --- a/modules-available/statistics/templates/summary.html +++ b/modules-available/statistics/templates/summary.html @@ -8,8 +8,8 @@ {{/runmode}} <div> {{lang_knownMachines}}: <b>{{known}}</b>  - <a href="?do=Statistics&show=stat&filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>  - <a href="?do=Statistics&show=stat&filters={{query}}~,~state=occupied">{{lang_inUseMachines}}</a>: <b>{{used}}</b> (<b>{{usedpercent}}%</b>) + <a href="?do=Statistics&show=summary&filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>  + <a href="?do=Statistics&show=summary&filters={{query}}~,~state=occupied">{{lang_inUseMachines}}</a>: <b>{{used}}</b> (<b>{{usedpercent}}%</b>) </div> {{#badhdd}} <div> |