diff options
author | Jannik Schönartz | 2017-12-14 13:03:44 +0100 |
---|---|---|
committer | Jannik Schönartz | 2017-12-14 13:03:44 +0100 |
commit | 5d5c2f27bee5d4fbd3747555efbf2ac9f337805b (patch) | |
tree | c65898e1b3d6f0f46366a280bbbaf4c6ccbc477c /modules-available/statistics | |
parent | [usb-lock-off] Design changes to fit the design_guidelines. TODO: lang_discar... (diff) | |
parent | [sysconfig] Update translations (diff) | |
download | slx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.tar.gz slx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.tar.xz slx-admin-5d5c2f27bee5d4fbd3747555efbf2ac9f337805b.zip |
Merge branch 'master' into usb-lock-off
Diffstat (limited to 'modules-available/statistics')
25 files changed, 841 insertions, 424 deletions
diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 566b1d69..a614658a 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -16,7 +16,7 @@ if ($type{0} === '~') { $uuid = Request::post('uuid', '', 'string'); if (strlen($uuid) !== 36) die("Invalid UUID.\n"); $macaddr = Request::post('macaddr', '', 'string'); - if (!empty($macaddr) && substr($uuid, 0, 16) === '000000000000000-') { + if (!empty($macaddr) && substr($uuid, 0, 16) === '000000000000001-') { // Override uuid if the mac is known and unique $res = Database::simpleQuery('SELECT machineuuid FROM machine WHERE macaddr = :macaddr AND machineuuid <> :uuid', compact('macaddr', 'uuid')); $override = false; @@ -34,7 +34,7 @@ if ($type{0} === '~') { // External mode of operation? $mode = Request::post('mode', false, 'string'); $NOW = time(); - $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); + $old = Database::queryFirst('SELECT clientip, logintime, lastseen, lastboot, state, mbram, cpumodel FROM machine WHERE machineuuid = :uuid', array('uuid' => $uuid)); if ($old !== false) { settype($old['logintime'], 'integer'); settype($old['lastseen'], 'integer'); @@ -43,7 +43,7 @@ if ($type{0} === '~') { // Handle event type if ($mode === false && $type === '~poweron') { // Poweron & hw stats - $uptime = Request::post('uptime', '', 'integer'); + $uptime = Request::post('uptime', 0, 'integer'); if (strlen($macaddr) > 17) die("Invalid MAC.\n"); if ($uptime < 0 || $uptime > 4000000) die("Implausible uptime.\n"); $realcores = Request::post('realcores', 0, 'integer'); @@ -64,6 +64,65 @@ if ($type{0} === '~') { $hostname = ''; } $data = Request::post('data', '', 'string'); + // Prepare insert/update to machine table + $new = array( + 'uuid' => $uuid, + 'macaddr' => $macaddr, + 'clientip' => $ip, + 'lastseen' => $NOW, + 'lastboot' => $NOW - $uptime, + 'realcores' => $realcores, + 'mbram' => $mbram, + 'kvmstate' => $kvmstate, + 'cpumodel' => $cpumodel, + 'systemmodel'=> $systemmodel, + 'id44mb' => $id44mb, + 'badsectors' => $badsectors, + 'data' => $data, + 'state' => 'IDLE', + ); + // Create/update machine entry + if ($old === false) { + $new['firstseen'] = $NOW; + $new['hostname'] = $hostname; + $res = Database::exec('INSERT INTO machine ' + . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,' + . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname, state) VALUES ' + . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram," + . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname, :state)', $new, true); + if ($res === false) { + die("Concurrent insert, ignored. (RESULT=0)\n"); + } + } else { + // Update + $moresql = ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : ''); + if (!empty($hostname)) { + $new['hostname'] = $hostname; + $moresql .= ' hostname = :hostname,'; + } + $new['oldstate'] = $old['state']; + $new['oldlastseen'] = $old['lastseen']; + $res = Database::exec('UPDATE machine SET ' + . ' macaddr = :macaddr,' + . ' clientip = :clientip,' + . ' lastseen = :lastseen,' + . ' lastboot = :lastboot,' + . $moresql + . ' realcores = :realcores,' + . ' mbram = :mbram,' + . ' kvmstate = :kvmstate,' + . ' cpumodel = :cpumodel,' + . ' systemmodel = :systemmodel,' + . ' id44mb = :id44mb,' + . ' badsectors = :badsectors,' + . ' data = :data,' + . ' state = :state ' + . " WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", $new); + if ($res === 0) { + die("Concurrent update, ignored. (RESULT=0)\n"); + } + } + // Maybe log old crashed session if ($uptime < 120) { // See if we have a lingering session, create statistic entry if so if ($old !== false && $old['logintime'] !== 0) { @@ -93,49 +152,17 @@ if ($type{0} === '~') { } } } - // Create/update machine entry - Database::exec('INSERT INTO machine ' - . '(machineuuid, macaddr, clientip, firstseen, lastseen, logintime, position, lastboot, realcores, mbram,' - . ' kvmstate, cpumodel, systemmodel, id44mb, badsectors, data, hostname) VALUES ' - . "(:uuid, :macaddr, :clientip, :firstseen, :lastseen, 0, '', :lastboot, :realcores, :mbram," - . ' :kvmstate, :cpumodel, :systemmodel, :id44mb, :badsectors, :data, :hostname)' - . ' ON DUPLICATE KEY UPDATE' - . ' macaddr = VALUES(macaddr),' - . ' clientip = VALUES(clientip),' - . ' lastseen = VALUES(lastseen),' - . ($uptime < 180 ? ' logintime = 0, currentuser = NULL, currentsession = NULL,' : '') - . ' lastboot = VALUES(lastboot),' - . ' realcores = VALUES(realcores),' - . ' mbram = VALUES(mbram),' - . ' kvmstate = VALUES(kvmstate),' - . ' cpumodel = VALUES(cpumodel),' - . ' systemmodel = VALUES(systemmodel),' - . ' id44mb = VALUES(id44mb),' - . ' badsectors = VALUES(badsectors),' - . ' data = VALUES(data),' - . " hostname = If(VALUES(hostname) = '', hostname, VALUES(hostname))", array( - 'uuid' => $uuid, - 'macaddr' => $macaddr, - 'clientip' => $ip, - 'firstseen' => $NOW, - 'lastseen' => $NOW, - 'lastboot' => $NOW - $uptime, - 'realcores' => $realcores, - 'mbram' => $mbram, - 'kvmstate' => $kvmstate, - 'cpumodel' => $cpumodel, - 'systemmodel'=> $systemmodel, - 'id44mb' => $id44mb, - 'badsectors' => $badsectors, - 'data' => $data, - 'hostname' => $hostname, - )); if (($old === false || $old['clientip'] !== $ip) && Module::isAvailable('locations')) { // New, or ip changed (dynamic pool?), update subnetlicationid Location::updateMapIpToLocation($uuid, $ip); } + // Check for suspicious hardware changes + if ($old !== false) { + checkHardwareChange($old, $new); + } + // Write statistics data } else if ($type === '~runstate') { @@ -147,7 +174,7 @@ if ($type{0} === '~') { die("Address changed.\n"); } $used = Request::post('used', 0, 'integer'); - if ($old['lastboot'] === 0 && $NOW - $old['lastseen'] > 300) { + if ($old['state'] === 'OFFLINE' && $NOW - $old['lastseen'] > 600) { $strUpdateBoottime = ' lastboot = UNIX_TIMESTAMP(), '; } else { $strUpdateBoottime = ''; @@ -161,31 +188,42 @@ if ($type{0} === '~') { } $old['logintime'] = 0; } - $old['lastboot'] = 0; } // Figure out what's happening - state changes - if ($used === 0 && $old['logintime'] !== 0) { + $params = array( + 'uuid' => $uuid, + 'oldlastseen' => $old['lastseen'], + 'oldstate' => $old['state'], + ); + if ($used === 0 && $old['state'] !== 'IDLE') { // Is not in use, was in use before $sessionLength = $NOW - $old['logintime']; - Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' + $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime - . ' logintime = 0, currentuser = NULL WHERE machineuuid = :uuid', array('uuid' => $uuid)); - } elseif ($used === 1 && $old['logintime'] === 0) { + . " logintime = 0, currentuser = NULL, state = 'IDLE' " + . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate", + $params); + } elseif ($used === 1 && $old['state'] !== 'OCCUPIED') { // Machine is in use, was free before if ($sessionLength !== 0 || $old['logintime'] === 0) { // This event is a start of a new session, rather than an update - Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' + $params['user'] = Request::post('user', null, 'string'); + $res = Database::exec('UPDATE machine SET lastseen = UNIX_TIMESTAMP(),' . $strUpdateBoottime - . ' logintime = UNIX_TIMESTAMP(), currentuser = :user WHERE machineuuid = :uuid', array( - 'uuid' => $uuid, - 'user' => Request::post('user', null, 'string'), - )); + . " logintime = UNIX_TIMESTAMP(), currentuser = :user, currentsession = NULL, state = 'OCCUPIED' " + . " WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate", $params); + } else { + $res = 0; } } else { // Nothing changed, simple lastseen update - Database::exec('UPDATE machine SET ' + $res = Database::exec('UPDATE machine SET ' . $strUpdateBoottime - . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid', array('uuid' => $uuid)); + . ' lastseen = UNIX_TIMESTAMP() WHERE machineuuid = :uuid AND lastseen = :oldlastseen AND state = :oldstate', $params); + } + // Did we update, or was there a concurrent update? + if ($res === 0) { + die("Concurrent update, ignored. (RESULT=0)\n"); } // 9) Log last session length if applicable if ($mode === false && $sessionLength > 0 && $sessionLength < 86400*2 && $old['logintime'] !== 0) { @@ -215,8 +253,11 @@ if ($type{0} === '~') { )); } } - Database::exec('UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), lastboot = 0 WHERE machineuuid = :uuid', array('uuid' => $uuid)); + Database::exec("UPDATE machine SET logintime = 0, lastseen = UNIX_TIMESTAMP(), state = 'OFFLINE' + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); } elseif ($mode === false && $type === '~screens') { + if ($old === false) die("Unknown machine.\n"); $screens = Request::post('screen', false, 'array'); if (is_array($screens)) { // `devicetype`, `devicename`, `subid`, `machineuuid` @@ -287,6 +328,47 @@ if ($type{0} === '~') { } } + } else if ($type === '~suspend') { + // Client entering suspend + if ($old === false) die("Unknown machine.\n"); + if ($old['clientip'] !== $ip) { + EventLog::warning("[suspend] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip)"); + die("Address changed.\n"); + } + if ($NOW - $old['lastseen'] < 610 && $old['state'] !== 'OFFLINE') { + Database::exec("UPDATE machine SET lastseen = UNIX_TIMESTAMP(), state = 'STANDBY' + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); + } else { + EventLog::info("[suspend] Client $uuid reported switch to standby when it wasn't powered on first. Was: " . $old['state']); + } + } else if ($type === '~resume') { + // Waking up from suspend + if ($old === false) die("Unknown machine.\n"); + if ($old['clientip'] !== $ip) { + EventLog::info("[resume] IP address of client $uuid seems to have changed ({$old['clientip']} -> $ip), allowed on resume."); + } + if ($old['state'] === 'STANDBY') { + $res = Database::exec("UPDATE machine SET state = 'IDLE', clientip = :ip, lastseen = UNIX_TIMESTAMP() + WHERE machineuuid = :uuid AND state = :oldstate AND lastseen = :oldlastseen", + array('uuid' => $uuid, 'ip' => $ip, 'oldlastseen' => $old['lastseen'], 'oldstate' => $old['state'])); + // Write standby period length to statistic table + if ($mode === false && $res > 0 && $old['lastseen'] !== 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 + )); + } + } + } else { + EventLog::info("[resume] Client $uuid reported wakeup from standby when it wasn't logged as being in standby. Was: " . $old['state']); + } } else { die("INVALID ACTION '$type'\n"); } @@ -315,6 +397,7 @@ function writeStatisticLog($type, $username, $data) )); } + // For backwards compat, we require the . prefix if ($type{0} === '.') { if ($type === '.vmchooser-session') { @@ -336,4 +419,22 @@ if ($type{0} === '.') { } } +/** + * @param array $old row from DB with client's old data + * @param array $new new data to be written + */ +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 (!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']}'"); + } + } +} + echo "OK.\n"; diff --git a/modules-available/statistics/hooks/cron.inc.php b/modules-available/statistics/hooks/cron.inc.php index 94c65248..4df7b0d4 100644 --- a/modules-available/statistics/hooks/cron.inc.php +++ b/modules-available/statistics/hooks/cron.inc.php @@ -1,18 +1,44 @@ <?php -Database::exec("DELETE FROM statistic WHERE (UNIX_TIMESTAMP() - dateline) > 86400 * 190"); -Database::exec("DELETE FROM machine WHERE (UNIX_TIMESTAMP() - lastseen) > 86400 * 365"); - -function logstats() { +function logstats() +{ $NOW = time(); $cutoff = $NOW - 86400 * 30; - $online = $NOW - 610; - $known = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $cutoff"); - $on = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online"); - $used = Database::queryFirst("SELECT Count(*) AS val FROM machine WHERE lastseen > $online AND logintime <> 0"); + $join = $where = ''; + if (Module::get('runmode') !== false) { + $join = 'LEFT JOIN runmode r USING (machineuuid)'; + $where = 'AND (r.isclient IS NULL OR r.isclient <> 0)'; + } + $known = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.lastseen > $cutoff $where"); + $on = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state IN ('IDLE', 'OCCUPIED') $where"); + $used = Database::queryFirst("SELECT Count(*) AS val FROM machine m $join WHERE m.state = 'OCCUPIED' $where"); Database::exec("INSERT INTO statistic (dateline, typeid, clientip, username, data) VALUES (:now, '~stats', '', '', :vals)", array( 'now' => $NOW, 'vals' => $known['val'] . '#' . $on['val'] . '#' . $used['val'], )); } + +function state_cleanup() +{ + // Fix online state of machines that crashed + $standby = time() - 86400 * 2; // Reset standby machines after two 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'"); +} + +state_cleanup(); + logstats(); + +if (mt_rand(1, 10) === 1) { + Database::exec("DELETE FROM statistic WHERE (UNIX_TIMESTAMP() - 86400 * 190) > dateline"); + if (mt_rand(1, 100) === 1) { + Database::exec("OPTIMIZE TABLE statistic"); + } +} +if (mt_rand(1, 10) === 1) { + Database::exec("DELETE FROM machine WHERE (UNIX_TIMESTAMP() - 86400 * 365) > lastseen"); + if (mt_rand(1, 100) === 1) { + Database::exec("OPTIMIZE TABLE machine"); + } +} diff --git a/modules-available/statistics/inc/filter.inc.php b/modules-available/statistics/inc/filter.inc.php index 0afce572..be6df752 100644 --- a/modules-available/statistics/inc/filter.inc.php +++ b/modules-available/statistics/inc/filter.inc.php @@ -29,7 +29,7 @@ class Filter $addendum = ''; /* check if we have to do some parsing*/ - if (Page_Statistics::$columns[$this->column]['type'] == 'date') { + if (Page_Statistics::$columns[$this->column]['type'] === 'date') { $args[$key] = strtotime($this->argument); } else { $args[$key] = $this->argument; @@ -180,21 +180,18 @@ class StateFilter extends Filter { public function __construct($operator, $argument) { - $this->operator = $operator; - $this->argument = $argument; + parent::__construct(null, $operator, $argument); } public function whereClause(&$args, &$joins) { + $map = [ 'on' => ['IDLE', 'OCCUPIED'], 'off' => ['OFFLINE'], 'idle' => ['IDLE'], 'occupied' => ['OCCUPIED'], 'standby' => ['STANDBY'] ]; $neg = $this->operator == '!=' ? 'NOT ' : ''; - if ($this->argument === 'on') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() ) "; - } elseif ($this->argument === 'off') { - return " $neg (lastseen + 600 < UNIX_TIMESTAMP() ) "; - } elseif ($this->argument === 'idle') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime = 0 ) "; - } elseif ($this->argument === 'occupied') { - return " $neg (lastseen + 600 > UNIX_TIMESTAMP() AND logintime <> 0 ) "; + if (array_key_exists($this->argument, $map)) { + global $unique_key; + $key = $this->column . '_arg' . ($unique_key++); + $args[$key] = $map[$this->argument]; + return " machine.state $neg IN ( :$key ) "; } else { Message::addError('invalid-filter-argument', 'state', $this->argument); return ' 1'; @@ -211,13 +208,22 @@ class LocationFilter extends Filter public function whereClause(&$args, &$joins) { + $recursive = (substr($this->operator, -1) === '~'); + $this->operator = str_replace('~', '=', $this->operator); + settype($this->argument, 'int'); + $neg = $this->operator === '=' ? '' : 'NOT'; if ($this->argument === 0) { - $neg = $this->operator === '=' ? '' : 'NOT'; return "machine.locationid IS $neg NULL"; } else { - $args['lid'] = $this->argument; - return "machine.locationid {$this->operator} :lid"; + global $unique_key; + $key = $this->column . '_arg' . ($unique_key++); + if ($recursive) { + $args[$key] = array_keys(Location::getRecursiveFlat($this->argument)); + } else { + $args[$key] = $this->argument; + } + return "machine.locationid $neg IN (:$key)"; } } } @@ -236,3 +242,20 @@ class SubnetFilter extends Filter } } +class IsClientFilter extends Filter +{ + public function __construct($argument) + { + parent::__construct(null, null, $argument); + } + + public function whereClause(&$args, &$joins) + { + if ($this->argument) { + $joins[] = ' LEFT JOIN runmode USING (machineuuid)'; + return "(runmode.isclient <> 0 OR runmode.isclient IS NULL)"; + } + $joins[] = ' INNER JOIN runmode USING (machineuuid)'; + return "runmode.isclient = 0"; + } +} diff --git a/modules-available/statistics/inc/filterset.inc.php b/modules-available/statistics/inc/filterset.inc.php index c73feeef..25c5c8fa 100644 --- a/modules-available/statistics/inc/filterset.inc.php +++ b/modules-available/statistics/inc/filterset.inc.php @@ -2,6 +2,9 @@ class FilterSet { + /** + * @var \Filter[] + */ private $filters; private $sortDirection; private $sortColumn; @@ -39,7 +42,7 @@ class FilterSet $where .= $sep . $filter->whereClause($args, $joins); } } - $join = implode('', array_unique($joins)); + $join = implode(' ', array_unique($joins)); $col = $this->sortColumn; $isMapped = array_key_exists('map_sort', Page_Statistics::$columns[$col]); @@ -72,4 +75,13 @@ class FilterSet { return $this->sortColumn; } + + public function filterNonClients() + { + if (Module::get('runmode') === false) + return; + // Runmode module exists, add filter + $this->filters[] = new IsClientFilter(true); + } + } diff --git a/modules-available/statistics/inc/machine.inc.php b/modules-available/statistics/inc/machine.inc.php index 8cb5e884..8605749b 100644 --- a/modules-available/statistics/inc/machine.inc.php +++ b/modules-available/statistics/inc/machine.inc.php @@ -51,6 +51,11 @@ class Machine public $logintime; /** + * @var string state of machine (OFFLINE, IDLE, OCCUPIED, STANDBY) + */ + public $state; + + /** * @var string json data of position inside room (if any), null/empty otherwise */ public $position; diff --git a/modules-available/statistics/inc/statistics.inc.php b/modules-available/statistics/inc/statistics.inc.php index 1c9ebf07..2500f16f 100644 --- a/modules-available/statistics/inc/statistics.inc.php +++ b/modules-available/statistics/inc/statistics.inc.php @@ -7,17 +7,12 @@ class Statistics private static $machineFields = false; - /** - * @param string $machineuuid - * @param int $returnData - * @return \Machine|false - */ - public static function getMachine($machineuuid, $returnData) + private static function initFields($returnData) { if (self::$machineFields === false) { $r = new ReflectionClass('Machine'); $props = $r->getProperties(ReflectionProperty::IS_PUBLIC); - self::$machineFields = array_flip(array_map(function($e) { return $e->getName(); }, $props)); + self::$machineFields = array_flip(array_map(function(/* @var ReflectionProperty $e */ $e) { return $e->getName(); }, $props)); } if ($returnData === Machine::NO_DATA) { unset(self::$machineFields['data']); @@ -26,8 +21,19 @@ class Statistics } else { Util::traceError('Invalid $returnData option passed'); } - $fields = implode(',', array_keys(self::$machineFields)); - $row = Database::queryFirst("SELECT * FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid')); + return implode(',', array_keys(self::$machineFields)); + } + + /** + * @param string $machineuuid + * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ... + * @return \Machine|false + */ + public static function getMachine($machineuuid, $returnData) + { + $fields = self::initFields($returnData); + + $row = Database::queryFirst("SELECT $fields FROM machine WHERE machineuuid = :machineuuid", compact('machineuuid')); if ($row === false) return false; $m = new Machine(); @@ -37,4 +43,31 @@ class Statistics return $m; } + /** + * @param string $ip + * @param int $returnData What kind of data to return Machine::NO_DATA, Machine::RAW_DATA, ... + * @param string $sort something like 'lastseen ASC' - not sanitized, don't pass user input! + * @return \Machine[] list of matches + */ + public static function getMachinesByIp($ip, $returnData, $sort = false) + { + $fields = self::initFields($returnData); + + if ($sort === false) { + $sort = ''; + } else { + $sort = "ORDER BY $sort"; + } + $res = Database::simpleQuery("SELECT $fields FROM machine WHERE clientip = :ip $sort", compact('ip')); + $list = array(); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $m = new Machine(); + foreach ($row as $key => $val) { + $m->{$key} = $val; + } + $list[] = $m; + } + return $list; + } + } diff --git a/modules-available/statistics/install.inc.php b/modules-available/statistics/install.inc.php index bfa342c4..4e2dfcca 100644 --- a/modules-available/statistics/install.inc.php +++ b/modules-available/statistics/install.inc.php @@ -36,6 +36,7 @@ $res[] = $machineCreate = tableCreate('machine', " `logintime` int(10) unsigned NOT NULL, `position` varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, `lastboot` int(10) unsigned NOT NULL, + `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE', `realcores` smallint(5) unsigned NOT NULL, `mbram` int(10) unsigned NOT NULL, `kvmstate` enum('UNKNOWN','UNSUPPORTED','DISABLED','ENABLED') NOT NULL, @@ -51,6 +52,7 @@ $res[] = $machineCreate = tableCreate('machine', " PRIMARY KEY (`machineuuid`), KEY `macaddr` (`macaddr`), KEY `clientip` (`clientip`), + KEY `state` (`state`), KEY `realcores` (`realcores`), KEY `mbram` (`mbram`), KEY `kvmstate` (`kvmstate`), @@ -198,6 +200,7 @@ if ($addTrigger) { finalResponse(UPDATE_RETRY, 'Locations module not installed yet, retry later'); } } + $res[] = UPDATE_DONE; } if ($machineHwCreate === UPDATE_DONE) { @@ -217,12 +220,19 @@ if ($machineHwCreate === UPDATE_DONE) { if ($ret === false) { finalResponse(UPDATE_FAILED, 'Adding constraint to statistic_hw_prop failed: ' . Database::lastError()); } + $res[] = UPDATE_DONE; } -// Create response - -if (in_array(UPDATE_DONE, $res)) { - finalResponse(UPDATE_DONE, 'Tables created successfully'); +// 2017-11-27: Add state column +if (!tableHasColumn('machine', 'state')) { + $ret = Database::exec("ALTER TABLE `machine` + ADD COLUMN `state` enum('OFFLINE', 'IDLE', 'OCCUPIED', 'STANDBY', 'IGNORED') NOT NULL DEFAULT 'OFFLINE' AFTER `lastboot`, + ADD INDEX `state` (`state`)"); + if ($ret === false) { + finalResponse(UPDATE_FAILED, 'Adding state column to machine table failed: ' . Database::lastError()); + } + $res[] = UPDATE_DONE; } -finalResponse(UPDATE_NOOP, 'Everything already up to date'); +// Create response +responseFromArray($res); diff --git a/modules-available/statistics/lang/de/messages.json b/modules-available/statistics/lang/de/messages.json index 8bdf9cfc..c9667f7b 100644 --- a/modules-available/statistics/lang/de/messages.json +++ b/modules-available/statistics/lang/de/messages.json @@ -1,6 +1,7 @@ { - "invalid-filter": "Ung\u00fcltiger Filter", + "deleted-n-machines": "{{0}} Clients gel\u00f6scht", "invalid-filter-argument": "Das Argument {{1}} ist nicht g\u00fcltig f\u00fcr den Filter {{0}}", "invalid-filter-key": "{{0}} ist kein g\u00fcltiges Filterkriterium", - "notes-saved": "Anmerkungen gespeichert" + "notes-saved": "Anmerkungen gespeichert", + "unknown-machine": "Unbekannte Rechner-ID {{0}}" }
\ 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 02c3e4d6..3cdde813 100644 --- a/modules-available/statistics/lang/de/template-tags.json +++ b/modules-available/statistics/lang/de/template-tags.json @@ -37,6 +37,7 @@ "lang_machineOccupied": "Der Rechner ist eingeschaltet und wird benutzt", "lang_machineOccupiedBy": "In Verwendung durch", "lang_machineOff": "Der Rechner ist ausgeschaltet, oder hat kein bwLehrpool gebootet", + "lang_machineStandby": "Im Standby", "lang_machineSummary": "Zusammenfassung", "lang_maximumAbbrev": "Max.", "lang_memoryStats": "Arbeitsspeicher", @@ -45,6 +46,7 @@ "lang_modelName": "Modellname", "lang_modelNo": "Modell", "lang_modelStats": "PC-Modelle", + "lang_moduleHeading": "Client-Statistiken", "lang_more": "Mehr", "lang_newMachines": "Neue Ger\u00e4te", "lang_noEdid": "Kein EDID", @@ -64,17 +66,20 @@ "lang_ramSlots": "Speicher-Slots", "lang_realCores": "Kerne", "lang_reallocatedSectors": "Defekte Sektoren", + "lang_runMode": "Betriebsmodus", + "lang_runmodeMachines": "Mit besonderem Betriebsmodus", "lang_screens": "Bildschirme", "lang_serialNo": "Serien-Nr", "lang_showList": "Liste", "lang_showVisualization": "Visualisierung", "lang_sockets": "Sockel", "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_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_tmpGb": "HDD-Temp", + "lang_tmpGb": "Temp-HDD", "lang_total": "Gesamt", "lang_usageDetails": "Nutzungsdetails", "lang_usageState": "Zustand", @@ -82,4 +87,4 @@ "lang_virtualCores": "Virtuelle Kerne", "lang_when": "Wann", "lang_withBadSectors": "Clients mit potentiell defekten Festplatten (mehr als 10 defekte Sektoren)" -} +}
\ No newline at end of file diff --git a/modules-available/statistics/lang/en/messages.json b/modules-available/statistics/lang/en/messages.json index 40eac8c9..3471c472 100644 --- a/modules-available/statistics/lang/en/messages.json +++ b/modules-available/statistics/lang/en/messages.json @@ -1,6 +1,7 @@ { - "invalid-filter": "Invalid filter", + "deleted-n-machines": "Deleted {{0}} clients", "invalid-filter-argument": "{{1}} is not a vald argument for filter {{0}}", "invalid-filter-key": "{{0}} is not a valid filter", - "notes-saved": "Notes have been saved" + "notes-saved": "Notes have been saved", + "unknown-machine": "Unknown machine uuid {{0}}" }
\ 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 c097ce37..35c4e68a 100644 --- a/modules-available/statistics/lang/en/template-tags.json +++ b/modules-available/statistics/lang/en/template-tags.json @@ -37,6 +37,7 @@ "lang_machineOccupied": "Machine is powered on and in use", "lang_machineOccupiedBy": "In use by", "lang_machineOff": "Machine is powered down, or is not running bwLehrpool", + "lang_machineStandby": "In standby mode", "lang_machineSummary": "Summary", "lang_maximumAbbrev": "max.", "lang_memoryStats": "Memory", @@ -45,6 +46,7 @@ "lang_modelName": "Model name", "lang_modelNo": "Model", "lang_modelStats": "PC models", + "lang_moduleHeading": "Client Statistics", "lang_more": "More", "lang_newMachines": "New machines", "lang_noEdid": "No EDID", @@ -64,17 +66,20 @@ "lang_ramSlots": "Memory slots", "lang_realCores": "Cores", "lang_reallocatedSectors": "Bad sectors", + "lang_runMode": "Mode of operation", + "lang_runmodeMachines": "With special mode of operation", "lang_screens": "Screens", "lang_serialNo": "Serial no", - "lang_showList": "Show list", - "lang_showVisualization": "Show visualization", + "lang_showList": "List", + "lang_showVisualization": "Visualization", "lang_sockets": "Sockets", "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_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_tmpGb": "HDD temp", + "lang_tmpGb": "Temp HDD", "lang_total": "Total", "lang_usageDetails": "Detailed usage", "lang_usageState": "State", @@ -82,4 +87,4 @@ "lang_virtualCores": "Virtual cores", "lang_when": "When", "lang_withBadSectors": "Clients with potentially bad HDDs (more than 10 reallocated sectors)" -} +}
\ No newline at end of file diff --git a/modules-available/statistics/page.inc.php b/modules-available/statistics/page.inc.php index 2b12c69f..c3ecf52b 100644 --- a/modules-available/statistics/page.inc.php +++ b/modules-available/statistics/page.inc.php @@ -106,11 +106,17 @@ class Page_Statistics extends Page 'op' => Page_Statistics::$op_nominal, 'type' => 'string', 'column' => true + ], + 'state' => [ + 'op' => Page_Statistics::$op_nominal, + 'type' => 'enum', + 'column' => true, + 'values' => ['occupied', 'on', 'off', 'idle', 'standby'] ] ]; if (Module::isAvailable('locations')) { Page_Statistics::$columns['location'] = [ - 'op' => Page_Statistics::$op_nominal, + 'op' => Page_Statistics::$op_stringcmp, 'type' => 'enum', 'column' => false, 'values' => array_keys(Location::getLocationsAssoc()), @@ -189,9 +195,36 @@ class Page_Statistics extends Page Util::redirect('?do=Statistics&uuid=' . $uuid); } elseif ($action === 'addprojector' || $action === 'delprojector') { $this->handleProjector($action); + } elseif ($action === 'delmachines') { + $this->deleteMachines(); + Util::redirect('?do=statistics', true); + } + } + + private function deleteMachines() + { + $ids = Request::post('uuid', [], 'array'); + $ids = array_values($ids); + if (empty($ids)) { + Message::addError('main.parameter-empty', 'uuid'); + return; + } + $res = Database::simpleQuery('SELECT machineuuid, locationid FROM machine WHERE machineuuid IN (:ids)', compact('ids')); + $ids = array_flip($ids); + $delete = []; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + // TODO: Check locationid permissions + unset($ids[$row['machineuuid']]); + $delete[] = $row['machineuuid']; + } + if (!empty($delete)) { + Database::exec('DELETE FROM machine WHERE machineuuid IN (:delete)', compact('delete')); + Message::addSuccess('deleted-n-machines', count($delete)); + } + if (!empty($ids)) { + // TODO: Warn permissions + Message::addWarning('unknown-machine', implode(', ', array_keys($ids))); } - // Fix online state of machines that crashed -- TODO: Make cronjob for this - Database::exec("UPDATE machine SET lastboot = 0 WHERE lastseen < UNIX_TIMESTAMP() - 610"); } protected function doRender() @@ -199,7 +232,12 @@ class Page_Statistics extends Page $uuid = Request::get('uuid', false, 'string'); if ($uuid !== false) { $this->showMachine($uuid); + return; + } + $show = Request::get('show', 'stat', 'string'); + if ($show === 'projectors') { + $this->showProjectors(); return; } @@ -210,23 +248,19 @@ class Page_Statistics extends Page } $sortColumn = Request::any('sortColumn'); $sortDirection = Request::any('sortDirection'); - $filters = Filter::parseQuery($this->query); + $filters = Filter::parseQuery($this->query); $filterSet = new FilterSet($filters); $filterSet->setSort($sortColumn, $sortDirection); - - $show = Request::get('show', 'stat', 'string'); if ($show == 'list') { Render::openTag('div', array('class' => 'row')); $this->showFilter('list', $filterSet); Render::closeTag('div'); $this->showMachineList($filterSet); return; - } elseif ($show === 'projectors') { - $this->showProjectors(); - return; } + $filterSet->filterNonClients(); Render::openTag('div', array('class' => 'row')); $this->showFilter('stat', $filterSet); $this->showSummary($filterSet); @@ -253,11 +287,11 @@ class Page_Statistics extends Page ); if ($show === 'list') { - $data['listButtonClass'] = 'btn-primary'; - $data['statButtonClass'] = 'btn-default'; + $data['listButtonClass'] = 'active'; + $data['statButtonClass'] = ''; } else { - $data['listButtonClass'] = 'btn-default'; - $data['statButtonClass'] = 'btn-primary'; + $data['listButtonClass'] = ''; + $data['statButtonClass'] = 'active'; } @@ -308,6 +342,14 @@ class Page_Statistics extends Page } } + private function redirectFirst($where, $join, $args) + { + $res = Database::queryFirst("SELECT machineuuid FROM machine $join WHERE ($where) LIMIT 1", $args); + if ($res !== false) { + Util::redirect('?do=statistics&uuid=' . $res['machineuuid']); + } + } + /** * @param \FilterSet $filterSet */ @@ -316,8 +358,12 @@ class Page_Statistics extends Page $filterSet->makeFragments($where, $join, $sort, $args); $known = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE ($where)", $args); - $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND ($where)", $args); - $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE lastboot <> 0 AND logintime <> 0 AND ($where)", $args); + // If we only have one machine, redirect to machine details + if ($known['val'] == 1) { + $this->redirectFirst($where, $join, $args); + } + $on = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state IN ('IDLE', 'OCCUPIED') AND ($where)", $args); + $used = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE state = 'OCCUPIED' AND ($where)", $args); $hdd = Database::queryFirst("SELECT Count(*) AS val FROM machine $join WHERE badsectors >= 10 AND ($where)", $args); if ($on['val'] != 0) { $usedpercent = round($used['val'] / $on['val'] * 100); @@ -355,6 +401,10 @@ class Page_Statistics extends Page } $data['json'] = json_encode(array('labels' => $labels, 'datasets' => array($points1, $points2))); $data['query'] = $this->query; + if (Module::get('runmode') !== false) { + $res = Database::queryFirst('SELECT Count(*) AS cnt FROM runmode'); + $data['runmode'] = $res['cnt']; + } // Draw Render::addTemplate('summary', $data); } @@ -533,7 +583,8 @@ class Page_Statistics extends Page if (empty($row['hostname'])) { $row['hostname'] = $row['clientip']; } - $row['firstseen'] = date('d.m. H:i', $row['firstseen']); + $row['firstseen_int'] = $row['firstseen']; + $row['firstseen'] = Util::prettyTime($row['firstseen']); $row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding.. $row['gbtmp'] = round($row['id44mb'] / 1024); $row['ramclass'] = $this->ramColorClass($row['mbram']); @@ -553,29 +604,37 @@ class Page_Statistics extends Page */ private function showMachineList($filterSet) { + Module::isAvailable('js_stupidtable'); $filterSet->makeFragments($where, $join, $sort, $args); $xtra = ''; if ($filterSet->isNoId44Filter()) { - $xtra = ', data'; + $xtra .= ', data'; + } + if (Module::isAvailable('runmode')) { + $xtra .= ', runmode.module AS rmmodule'; + if (strpos($join, 'runmode') === false) { + $join .= ' LEFT JOIN runmode USING (machineuuid) '; + } } - $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, firstseen, lastseen,' - . ' logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,' + $res = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, lastseen,' + . ' logintime, state, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes,' . ' badsectors ' . $xtra . ' FROM machine' . " $join WHERE $where $sort", $args); $rows = array(); $NOW = time(); + $singleMachine = 'none'; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if ($row['lastboot'] == 0) { - $row['state_off'] = true; - } elseif ($row['logintime'] == 0) { - $row['state_idle'] = true; + if ($singleMachine === 'none') { + $singleMachine = $row['machineuuid']; } else { - $row['state_occupied'] = true; + $singleMachine = false; } - //$row['firstseen'] = date('d.m.Y H:i', $row['firstseen']); - $row['lastseen'] = date('d.m. H:i', $row['lastseen']); - //$row['lastboot'] = date('d.m. H:i', $row['lastboot']); + $row['state_' . $row['state']] = true; + //$row['firstseen'] = Util::prettyTime($row['firstseen']); + $row['lastseen_int'] = $row['lastseen']; + $row['lastseen'] = Util::prettyTime($row['lastseen']); + //$row['lastboot'] = Util::prettyTime($row['lastboot']); $row['gbram'] = round(round($row['mbram'] / 500) / 2, 1); // Trial and error until we got "expected" rounding.. $row['gbtmp'] = round($row['id44mb'] / 1024); $octets = explode('.', $row['clientip']); @@ -594,8 +653,12 @@ class Page_Statistics extends Page $row['nohdd'] = true; } } + $row['cpumodel'] = preg_replace('/\(R\)|\(TM\)|\bintel\b|\bamd\b|\bcpu\b|dual-core|\bdual\s+core\b|\bdual\b|\bprocessor\b/i', ' ', $row['cpumodel']); $rows[] = $row; } + if ($singleMachine !== false && $singleMachine !== 'none') { + Util::redirect('?do=statistics&uuid=' . $singleMachine); + } Render::addTemplate('clientlist', array( 'rowCount' => count($rows), 'rows' => $rows, @@ -605,7 +668,8 @@ class Page_Statistics extends Page 'sortColumn' => $filterSet->getSortColumn(), 'columns' => json_encode(Page_Statistics::$columns), 'showList' => 1, - 'show' => 'list' + 'show' => 'list', + 'redirect' => $_SERVER['QUERY_STRING'] )); } @@ -702,24 +766,40 @@ class Page_Statistics extends Page private function showMachine($uuid) { - $client = Database::queryFirst('SELECT machineuuid, locationid, macaddr, clientip, firstseen, lastseen, logintime, lastboot,' + $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', array('uuid' => $uuid)); + if ($client === false) { + Message::addError('unknown-machine', $uuid); + return; + } // Hack: Get raw collected data if (Request::get('raw', false)) { Header('Content-Type: text/plain; charset=utf-8'); die($client['data']); } + // Runmode + if (Module::isAvailable('runmode')) { + $data = RunMode::getRunMode($uuid, RunMode::DATA_STRINGS); + if ($data !== false) { + $client += $data; + } + } + if (!isset($client['isclient'])) { + $client['isclient'] = true; + } // Mangle fields $NOW = time(); - if ($client['lastboot'] == 0) { - $client['state_off'] = true; - } elseif ($client['logintime'] == 0) { - $client['state_idle'] = true; + if (!$client['isclient']) { + if ($client['state'] === 'IDLE') { + $client['state'] = 'OCCUPIED'; + } } else { - $client['state_occupied'] = true; - $this->fillSessionInfo($client); + if ($client['state'] === 'OCCUPIED') { + $this->fillSessionInfo($client); + } } + $client['state_' . $client['state']] = true; $client['firstseen_s'] = date('d.m.Y H:i', $client['firstseen']); $client['lastseen_s'] = date('d.m.Y H:i', $client['lastseen']); if ($client['lastboot'] == 0) { @@ -727,7 +807,7 @@ class Page_Statistics extends Page } else { $uptime = $NOW - $client['lastboot']; $client['lastboot_s'] = date('d.m.Y H:i', $client['lastboot']); - if (!isset($client['state_off']) || !$client['state_off']) { + if ($client['state'] === 'IDLE' || $client['state'] === 'OCCUPIED') { $client['lastboot_s'] .= ' (Up ' . floor($uptime / 86400) . 'd ' . gmdate('H:i', $uptime) . ')'; } } @@ -810,6 +890,8 @@ class Page_Statistics extends Page $last = false; $first = true; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (!$client['isclient'] && $row['typeid'] === '~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 $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round((min($row['dateline'], $client['lastboot']) - $cutoff) * $scale, 2) . '%"> </div>'; @@ -832,7 +914,7 @@ class Page_Statistics extends Page $row['data'] -= ($cutoff - $row['dateline']); $row['dateline'] = $cutoff; } - $row['from'] = date('d.m. H:i', $row['dateline']); + $row['from'] = Util::prettyTime($row['dateline']); $row['duration'] = floor($row['data'] / 86400) . 'd ' . gmdate('H:i', $row['data']); if ($row['typeid'] === '~offline-length') { $row['glyph'] = 'off'; @@ -842,12 +924,17 @@ class Page_Statistics extends Page $color = '#e77'; } $spans['graph'] .= '<div style="background:' . $color . ';left:' . round(($row['dateline'] - $cutoff) * $scale, 2) . '%;width:' . round(($row['data']) * $scale, 2) . '%"> </div>'; - $spans['rows'][] = $row; + if ($client['isclient']) { + $spans['rows'][] = $row; + } $last = $row; } if ($first && $client['lastboot'] > $cutoff) { // Special case: offline before $spans['graph'] .= '<div style="background:#444;left:0%;width:' . round(($client['lastboot'] - $cutoff) * $scale, 2) . '%"> </div>'; + } elseif ($first) { + // Not seen in last two weeks + $spans['graph'] .= '<div style="background:#444;left:0%;width:100%"> </div>'; } if (isset($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>'; @@ -868,6 +955,7 @@ class Page_Statistics extends Page $spans['rows2'] = array_slice($spans['rows'], ceil(count($spans['rows']) / 2)); $spans['rows'] = array_slice($spans['rows'], 0, ceil(count($spans['rows']) / 2)); } + $spans['isclient'] = $client['isclient']; Render::addTemplate('machine-usage', $spans); // Any hdds? if (!empty($hdds['hdds'])) { @@ -877,21 +965,13 @@ class Page_Statistics extends Page if (Module::get('syslog') !== false) { $lres = Database::simpleQuery('SELECT logid, dateline, logtypeid, clientip, description, extra FROM clientlog' . ' WHERE machineuuid = :uuid ORDER BY logid DESC LIMIT 25', array('uuid' => $client['machineuuid'])); - $today = date('d.m.Y'); - $yesterday = date('d.m.Y', time() - 86400); $count = 0; $log = array(); while ($row = $lres->fetch(PDO::FETCH_ASSOC)) { if (substr($row['description'], -5) === 'on :0' && strpos($row['description'], 'root logged') === false) { continue; } - $day = date('d.m.Y', $row['dateline']); - if ($day === $today) { - $day = Dictionary::translate('lang_today'); - } elseif ($day === $yesterday) { - $day = Dictionary::translate('lang_yesterday'); - } - $row['date'] = $day . date(' H:i', $row['dateline']); + $row['date'] = Util::prettyTime($row['dateline']); $row['icon'] = $this->eventToIconName($row['logtypeid']); $log[] = $row; if (++$count === 10) { @@ -899,7 +979,7 @@ class Page_Statistics extends Page } } Render::addTemplate('syslog', array( - 'clientip' => $client['clientip'], + 'machineuuid' => $client['machineuuid'], 'list' => $log, )); } diff --git a/modules-available/statistics/style.css b/modules-available/statistics/style.css new file mode 100644 index 00000000..1496ac87 --- /dev/null +++ b/modules-available/statistics/style.css @@ -0,0 +1,11 @@ +.to-top-btn { + position: fixed; + right: 10px; + bottom: 10px; + z-index: 100; + width: 50px; + height: 50px; + border-radius: 25px; + font-size: 20px; + line-height: 40px; +}
\ No newline at end of file diff --git a/modules-available/statistics/templates/clientlist.html b/modules-available/statistics/templates/clientlist.html index 73148eb8..13e148fa 100644 --- a/modules-available/statistics/templates/clientlist.html +++ b/modules-available/statistics/templates/clientlist.html @@ -1,95 +1,125 @@ +<h2>{{lang_clientList}} ({{rowCount}})</h2> +<form method="post" action="?do=statistics"> +<input type="hidden" name="token" value="{{token}}"> +<input type="hidden" name="redirect" value="?{{redirect}}"> - -<h1>{{lang_clientList}} ({{rowCount}})</h1> - -<table class="table table-condensed table-striped"> - <tr> - <th>{{lang_machine}}</th> - <th>{{lang_address}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-clientip"></button> - </div> - - </th> - <th class="text-right">{{lang_lastSeen}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-lastseen"></button> +<table class="stupidtable table table-condensed table-striped"> + <thead> + <tr> + <td></td> + <td></td> + <td class="text-right"> <button class="btn btn-default btn-xs" onclick="popupFilter('lastseen')"> <span id="btn_filter_lastseen" class="glyphicon glyphicon-filter"></span> </button> - </div> - </th> - <th> - {{lang_kvmSupport}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-kvmstate"></button> - <button class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')"> - <span id="btn_filter_kvmstate" class="glyphicon glyphicon-filter"></span> - </button> - </div> - </th> - <th class="text-right"> - {{lang_gbRam}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-gbram"></button> + </td> + <td> + <button class="btn btn-default btn-xs" onclick="popupFilter('kvmstate')"> + <span id="btn_filter_kvmstate" class="glyphicon glyphicon-filter"></span> + </button> + </td> + <td class="text-right"> <button class="btn btn-default btn-xs" onclick="popupFilter('gbram')"> <span id="btn_filter_gbram" class="glyphicon glyphicon-filter"></span> </button> - </div> - </th> - <th class="text-right"> - {{lang_tmpGb}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-hddgb"></button> - <button class="btn btn-default btn-xs" onclick="popupFilter('hddgb')"> - <span id="btn_filter_hddgb" class="glyphicon glyphicon-filter"></span> - </button> - </div> - </th> - <th>{{lang_cpuModel}} - <div class="btn-group pull-right"> - <button class="btn btn-default btn-xs" id="sortButton-realcores"></button> - <button class="btn btn-default btn-xs" onclick="popupFilter('realcores')"> - <span id="btn_filter_cpu" class="glyphicon glyphicon-filter"></span> - </button> - </div> - </th> - </tr> - {{#rows}} - <tr> - <td class="text-nowrap"> - {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}} - {{#state_off}} - <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span> - {{/state_off}} - {{#state_idle}} - <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span> - {{/state_idle}} - {{#state_occupied}} - <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span> - {{/state_occupied}} - <a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a> - <div class="small">{{machineuuid}}</div> - </td> - <td><b><a href="?do=Statistics&show=list&filters=subnet={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td> - <td class="text-right">{{lastseen}}</td> - <td class="{{kvmclass}}">{{kvmstate}}</td> - <td class="text-right {{ramclass}}">{{gbram}} GiB</td> - <td class="text-right {{hddclass}}"> - {{gbtmp}} GiB - {{#badsectors}}<div> - <span class="glyphicon glyphicon-exclamation-sign"></span> - {{badsectors}} - </div>{{/badsectors}} - {{#nohdd}}<div> - <span class="glyphicon glyphicon-hdd red"></span> - </div>{{/nohdd}} - </td> - <td>{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td> - </tr> - {{/rows}} + </td> + <td class="text-right"> + <button class="btn btn-default btn-xs" onclick="popupFilter('hddgb')"> + <span id="btn_filter_hddgb" class="glyphicon glyphicon-filter"></span> + </button> + </td> + <td> + <button class="btn btn-default btn-xs" onclick="popupFilter('realcores')"> + <span id="btn_filter_cpu" class="glyphicon glyphicon-filter"></span> + </button> + </td> + </tr> + <tr> + <th data-sort="string">{{lang_machine}}</th> + <th data-sort="ipv4">{{lang_address}}</th> + <th data-sort="int" class="text-right">{{lang_lastSeen}}</th> + <th data-sort="string">{{lang_kvmSupport}}</th> + <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> + </tr> + </thead> + <tbody> + {{#rows}} + <tr> + <td data-sort-value="{{hostname}}" class="text-nowrap"> + <div class="checkbox checkbox-inline"> + <input type="checkbox" name="uuid[]" value="{{machineuuid}}"> + <label></label> + </div> + {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}} + {{#state_OFFLINE}} + <span class="glyphicon glyphicon-off" title="{{lang_machineOff}}"></span> + {{/state_OFFLINE}} + {{#state_IDLE}} + <span class="glyphicon glyphicon-ok green" title="{{lang_machineIdle}}"></span> + {{/state_IDLE}} + {{#state_OCCUPIED}} + <span class="glyphicon glyphicon-user red" title="{{lang_machineOccupied}}"></span> + {{/state_OCCUPIED}} + {{#state_STANDBY}} + <span class="glyphicon glyphicon-off green" title="{{lang_machineStandby}}"></span> + {{/state_STANDBY}} + <a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a> + <div class="small">{{machineuuid}}</div> + {{#rmmodule}}<div class="small">{{lang_runMode}}: <a class="slx-bold" href="?do=runmode&module={{rmmodule}}">{{rmmodule}}</a></div>{{/rmmodule}} + </td> + <td data-sort-value="{{clientip}}"><b><a href="?do=Statistics&show=list&filters=subnet={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td> + <td data-sort-value="{{lastseen_int}}" class="text-right text-nowrap">{{lastseen}}</td> + <td class="{{kvmclass}}">{{kvmstate}}</td> + <td data-sort-value="{{gbram}}" class="text-right {{ramclass}}">{{gbram}} GiB</td> + <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}"> + {{gbtmp}} GiB + {{#badsectors}}<div><span data-toggle="tooltip" title="{{lang_reallocatedSectors}}" data-placement="left"> + <span class="glyphicon glyphicon-exclamation-sign"></span> + {{badsectors}} + </span></div>{{/badsectors}} + {{#nohdd}}<div> + <span class="glyphicon glyphicon-hdd red"></span> + </div>{{/nohdd}} + </td> + <td data-sort-value="{{realcores}}">{{lang_realCores}}: {{realcores}}<div class="small">{{cpumodel}}</div></td> + </tr> + {{/rows}} + </tbody> </table> + <div class="text-right buttonbar"> + <button type="reset" class="btn btn-default"> + <span class="glyphicon glyphicon-remove"></span> + {{lang_reset}} + </button> + <button type="button" class="btn btn-danger" onclick="$('#del-confirm').modal()"> + <span class="glyphicon glyphicon-trash"></span> + {{lang_delete}} + </button> + </div> + <div class="modal fade" id="del-confirm" tabindex="-1" role="dialog"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal">×</button> + <b>{{lang_delete}}</b> + </div> + <div class="modal-body"> + {{lang_sureDeletePermanent}} + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button type="submit" class="btn btn-danger" name="action" value="delmachines"> + <span class="glyphicon glyphicon-trash"></span> + {{lang_delete}} + </button> + </div> + </div> + </div> + </div> +</form> <script type="application/javascript"><!-- document.addEventListener("DOMContentLoaded", function () { @@ -103,6 +133,11 @@ document.addEventListener("DOMContentLoaded", function () { $sortBtn.html('<span class="glyphicon glyphicon-arrow-' + order + '"></span>'); $sortBtn.attr('onclick', 'toggleButton(\'' + v + '\');'); }); + + $('[data-toggle="tooltip"]').tooltip({ + container: 'body', + trigger : 'hover' + }); }); function toggleButton(v) { @@ -126,4 +161,4 @@ function toggleButton(v) { $queryForm.submit(); } -//--></script>
\ No newline at end of file +//--></script> diff --git a/modules-available/statistics/templates/cpumodels.html b/modules-available/statistics/templates/cpumodels.html index d9b0298b..d684c914 100644 --- a/modules-available/statistics/templates/cpumodels.html +++ b/modules-available/statistics/templates/cpumodels.html @@ -6,34 +6,38 @@ <div class="panel-body"> <div class="row"> <div class="col-md-8"> - <table class="table table-condensed table-striped table-responsive"> - <tr> - <th>{{lang_modelName}}</th> - <th class="text-right text-nowrap">{{lang_cpuCores}}</th> - <th class="text-right text-nowrap">{{lang_modelCount}}</th> - </tr> - {{#rows}} - <tr id="{{id}}" class="{{collapse}}"> - <td 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> - </td></tr></table> - </td> - <td 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> - </td> - <td class="text-right">{{count}}</td> - </tr> - {{/rows}} - <tr class="slx-decollapse"> - <td colspan="3"> - <span class="btn-group btn-group-justified"> - <span class="btn btn-default btn-sm"> - <span class="glyphicon glyphicon-menu-down"></span> + <table class="stupidtable table table-condensed table-striped table-responsive"> + <thead> + <tr> + <th data-sort="string">{{lang_modelName}}</th> + <th data-sort="int" class="text-right text-nowrap">{{lang_cpuCores}}</th> + <th data-sort="int" class="text-right text-nowrap">{{lang_modelCount}}</th> + </tr> + </thead> + <tbody> + {{#rows}} + <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> + </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> + </td> + <td class="text-right">{{count}}</td> + </tr> + {{/rows}} + <tr class="slx-decollapse"> + <td colspan="3"> + <span class="btn-group btn-group-justified"> + <span class="btn btn-default btn-sm"> + <span class="glyphicon glyphicon-menu-down"></span> + </span> </span> - </span> - </td> - </tr> + </td> + </tr> + </tbody> </table> </div> <div class="col-md-4"> diff --git a/modules-available/statistics/templates/filterbox.html b/modules-available/statistics/templates/filterbox.html index 7cd0f617..31daabc6 100644 --- a/modules-available/statistics/templates/filterbox.html +++ b/modules-available/statistics/templates/filterbox.html @@ -1,59 +1,82 @@ -<div id="modal-add-filter" class="modal modal-sm fade" role="dialog" - style="position:absolute; min-width:600px; min-height: 400px;margin:auto"> - <div class="modal-content"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span - class="sr-only">Close</span></button> - {{lang_add_filter}} - </div> - <form class="modal-body form-inline center" onsubmit="$('#add-btn').click(); return false"> - <div class="form-group"> - <select id="columnSelect" name="column" class="form-control col-4-xs"> </select> - </div> - <div class="form-group"> - <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> - <select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select> +<div id="modal-add-filter" class="modal fade" role="dialog" style="position: absolute"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <b>{{lang_add_filter}}</b> </div> - <button id="add-btn" type="button" class="btn btn-primary" onclick="addFilterFromForm()"> - <span class="glyphicon glyphicon-plus"></span> - {{lang_add}} - </button> - </form> + <form class="form-inline center" onsubmit="$('#add-btn').click(); return false"> + <div class="modal-body"> + <div class="form-group"> + <select id="columnSelect" name="column" class="form-control col-4-xs"> </select> + </div> + <div class="form-group"> + <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> + <select name="argument" id="argumentSelect" class="form-control col-4-xs"> </select> + </div> + + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_cancel}}</button> + <button id="add-btn" type="button" class="btn btn-success" onclick="addFilterFromForm()"> + <span class="glyphicon glyphicon-plus"></span> + {{lang_add}} + </button> + </div> + </form> + </div> </div> </div> +<a href="#top" class="btn btn-default to-top-btn"><span class="glyphicon glyphicon-menu-up"></span></a> -<div style="height:120px" class="col-xs-12"> + +<div class="col-md-12"> <!-- use GET here, to avoid the "resend form?" confirmation, and anyway this is stateless, so GET makes more sense --> <form id="queryForm" method="GET" action="?do=Statistics" class="" role="form"> - <input type="hidden" name="do" value="statistics"> <input type="hidden" name="show" value="{{show}}"> - <label for="filterInput">{{lang_labelFilter}}</label> - <input type="text" name="filters" class="" id="filterInput"/> - <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/> - <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/> + <button type="submit" hidden></button> + - <button type="button" class="btn btn-success pull-left" onclick="popupFilter(null)"> - <span class="glyphicon glyphicon-plus"></span> - {{lang_add_filter}} - </button> <div class="btn-group pull-right"> - <button class="btn {{statButtonClass}}" type="submit" name="show" value="stat"> + <button class="btn btn-default {{statButtonClass}}" type="submit" name="show" value="stat"> <span class="glyphicon glyphicon-stats"></span> {{lang_showVisualization}} </button> - <button class="btn {{listButtonClass}}" type="submit" name="show" value="list"> + <button class="btn btn-default {{listButtonClass}}" type="submit" name="show" value="list"> <span class="glyphicon glyphicon-list"></span> {{lang_showList}} </button> </div> + <h1>{{lang_moduleHeading}}</h1> + + <br/> + + <input type="hidden" name="do" value="statistics"> + <input type="hidden" name="sortColumn" id="sortColumn" value="{{sortColumn}}"/> + <input type="hidden" name="sortDirection" id="sortDirection" value="{{sortDirection}}"/> + + <label for="filterInput">{{lang_labelFilter}}</label> + <div class="row"> + <div class="col-md-12"> + <div class="input-group"> + <input type="text" name="filters" class="" id="filterInput"/> + <span class="input-group-btn" style=" width: 1%; padding-bottom: 5px;"> + <button type="button" class="btn btn-success" onclick="popupFilter(null)"> + <span class="glyphicon glyphicon-plus"></span> + {{lang_add_filter}} + </button> + </span> + </div> + </div> + </div> + + <br/> </form> - <br/> - <br/> </div> + <script type="application/javascript"><!-- var filterSelectize; @@ -112,6 +135,9 @@ document.addEventListener("DOMContentLoaded", function () { // if (initComplete && !$('#filterInput').is(':focus')) { // reload(); // } + }, + onItemRemove: function(value) { + refresh(); } })[0].selectize; /* add query */ @@ -144,6 +170,7 @@ document.addEventListener("DOMContentLoaded", function () { $('#argumentInput').datepicker({format : 'yyyy-mm-dd'}); $('#argumentSelect').hide(); } else if(columns[col]['type'] == 'enum') { + $('#argumentSelect').empty(); $('#argumentInput').hide(); $('#argumentSelect').show(); columns[col]['values'].forEach(function (v) { diff --git a/modules-available/statistics/templates/id44.html b/modules-available/statistics/templates/id44.html index 38cf028f..d3b1ab1c 100644 --- a/modules-available/statistics/templates/id44.html +++ b/modules-available/statistics/templates/id44.html @@ -6,28 +6,32 @@ <div class="panel-body"> <div class="row"> <div class="col-sm-6"> - <table class="filter-col table table-condensed table-striped" data-filter-col="hddgb"> - <tr> - <th>{{lang_partitionSize}}</th> - <th class="text-right">{{lang_machineCount}}</th> - </tr> - {{#rows}} - <tr id="tmpid{{gb}}" class="{{class}} {{collapse}}"> - <td 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> - </td> - <td class="text-right">{{count}}</td> - </tr> - {{/rows}} - <tr class="slx-decollapse"> - <td colspan="2"> - <span class="btn-group btn-group-justified"> - <span class="btn btn-default btn-sm"> - <span class="glyphicon glyphicon-menu-down"></span> + <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="hddgb"> + <thead> + <tr> + <th data-sort="int">{{lang_partitionSize}}</th> + <th data-sort="int" class="text-right">{{lang_machineCount}}</th> + </tr> + </thead> + <tbody> + {{#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> + </td> + <td class="text-right">{{count}}</td> + </tr> + {{/rows}} + <tr class="slx-decollapse"> + <td colspan="2"> + <span class="btn-group btn-group-justified"> + <span class="btn btn-default btn-sm"> + <span class="glyphicon glyphicon-menu-down"></span> + </span> </span> - </span> - </td> - </tr> + </td> + </tr> + </tbody> </table> </div> <div class="col-sm-6"> diff --git a/modules-available/statistics/templates/kvmstate.html b/modules-available/statistics/templates/kvmstate.html index 33a00d38..3704eda0 100644 --- a/modules-available/statistics/templates/kvmstate.html +++ b/modules-available/statistics/templates/kvmstate.html @@ -6,19 +6,23 @@ <div class="panel-body"> <div class="row"> <div class="col-sm-6"> - <table class="filter-col table table-condensed table-striped" data-filter-col="kvmstate"> - <tr> - <th>{{lang_kvmState}}</th> - <th class="text-right">{{lang_machineCount}}</th> - </tr> - {{#rows}} - <tr id="kvm{{kvmstate}}"> - <td class="text-left text-nowrap"> - <a class="filter-val" data-filter-val="{{kvmstate}}" href="?do=Statistics&show=stat&filters={{query}}~,~kvmstate={{kvmstate}}">{{kvmstate}}</a> - </td> - <td class="text-right">{{count}}</td> - </tr> - {{/rows}} + <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="kvmstate"> + <thead> + <tr> + <th data-sort="string">{{lang_kvmState}}</th> + <th data-sort="int" class="text-right">{{lang_machineCount}}</th> + </tr> + </thead> + <tbody> + {{#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> + </td> + <td class="text-right">{{count}}</td> + </tr> + {{/rows}} + </tbody> </table> </div> <div class="col-sm-6"> diff --git a/modules-available/statistics/templates/machine-main.html b/modules-available/statistics/templates/machine-main.html index 74df80c4..19deb8b3 100644 --- a/modules-available/statistics/templates/machine-main.html +++ b/modules-available/statistics/templates/machine-main.html @@ -50,13 +50,13 @@ <tr> <td class="text-nowrap">{{lang_usageState}}</td> <td> - {{#state_off}} + {{#state_OFFLINE}} <span class="glyphicon glyphicon-off"></span> {{lang_machineOff}} - {{/state_off}} - {{#state_idle}} + {{/state_OFFLINE}} + {{#state_IDLE}} <span class="glyphicon glyphicon-ok green"></span> {{lang_machineIdle}} - {{/state_idle}} - {{#state_occupied}} + {{/state_IDLE}} + {{#state_OCCUPIED}} {{#username}} <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupiedBy}} <b>{{username}}</b> {{/username}} @@ -64,7 +64,10 @@ <span class="glyphicon glyphicon-user red"></span> {{lang_machineOccupied}} {{/username}} <div>{{logintime_s}}</div> - {{/state_occupied}} + {{/state_OCCUPIED}} + {{#state_STANDBY}} + <span class="glyphicon glyphicon-off green"></span> {{lang_machineStandby}} + {{/state_STANDBY}} {{#session}} <div> {{#lectureid}} @@ -77,6 +80,14 @@ {{/session}} </td> </tr> + {{#modeid}} + <tr> + <td class="text-nowrap">{{lang_runMode}}</td> + <td> + <a href="?do=runmode&module={{module}}">{{moduleName}}</a> – {{modeName}} + </td> + </tr> + {{/modeid}} </table> </div> </div> diff --git a/modules-available/statistics/templates/machine-notes.html b/modules-available/statistics/templates/machine-notes.html index c4f97543..22ed96e9 100644 --- a/modules-available/statistics/templates/machine-notes.html +++ b/modules-available/statistics/templates/machine-notes.html @@ -9,7 +9,8 @@ <input type="hidden" name="action" value="setnotes"> <input type="hidden" name="uuid" value="{{machineuuid}}"> <textarea name="content" class="form-control" cols="101" rows="10">{{notes}}</textarea> - <button type="submit" class="btn btn-primary">{{lang_save}}</button> + <br/> + <button type="submit" class="btn btn-primary pull-right"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button> </form> </div> </div> diff --git a/modules-available/statistics/templates/machine-usage.html b/modules-available/statistics/templates/machine-usage.html index ef969fc6..be435ee5 100644 --- a/modules-available/statistics/templates/machine-usage.html +++ b/modules-available/statistics/templates/machine-usage.html @@ -5,46 +5,50 @@ {{lang_usageDetails}} </div> <div class="panel-body"> - <div class="row"> - <div class="col-sm-6"> - <table class="table table-condensed"> - <tr> - <th>{{lang_eventType}}</th> - <th>{{lang_when}}</th> - <th>{{lang_duration}}</th> - </tr> - {{#rows}} - <tr> - <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> - <td>{{from}}</td> - <td>{{duration}}</td> - </tr> - {{/rows}} - </table> + {{#isclient}} + <div class="row"> + <div class="col-sm-6"> + <table class="table table-condensed"> + <tr> + <th>{{lang_eventType}}</th> + <th>{{lang_when}}</th> + <th>{{lang_duration}}</th> + </tr> + {{#rows}} + <tr> + <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> + <td>{{from}}</td> + <td>{{duration}}</td> + </tr> + {{/rows}} + </table> + </div> + <div class="col-sm-6"> + <table class="table table-condensed"> + {{#hasrows2}} + <tr> + <th>{{lang_eventType}}</th> + <th>{{lang_when}}</th> + <th>{{lang_duration}}</th> + </tr> + {{/hasrows2}} + {{#rows2}} + <tr> + <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> + <td>{{from}}</td> + <td>{{duration}}</td> + </tr> + {{/rows2}} + </table> + </div> </div> - <div class="col-sm-6"> - <table class="table table-condensed"> - {{#hasrows2}} - <tr> - <th>{{lang_eventType}}</th> - <th>{{lang_when}}</th> - <th>{{lang_duration}}</th> - </tr> - {{/hasrows2}} - {{#rows2}} - <tr> - <td><span class="glyphicon glyphicon-{{glyph}}"></span></td> - <td>{{from}}</td> - <td>{{duration}}</td> - </tr> - {{/rows2}} - </table> - </div> - </div> + {{/isclient}} <div class="timebar"> {{{graph}}}</div> - <div> - {{lang_timebarDesc}} - </div> + {{#isclient}} + <div> + {{lang_timebarDesc}} + </div> + {{/isclient}} </div> </div> </div> diff --git a/modules-available/statistics/templates/memory.html b/modules-available/statistics/templates/memory.html index f17f55ca..6bc13980 100644 --- a/modules-available/statistics/templates/memory.html +++ b/modules-available/statistics/templates/memory.html @@ -6,28 +6,32 @@ <div class="panel-body"> <div class="row"> <div class="col-sm-6"> - <table class="filter-col table table-condensed table-striped" data-filter-col="gbram"> - <tr> - <th>{{lang_ramSize}}</th> - <th class="text-right">{{lang_machineCount}}</th> - </tr> - {{#rows}} - <tr id="ramid{{gb}}" class="{{class}} {{collapse}}"> - <td class="text-left text-nowrap"> - <a class="filter-val" data-filter-val="{{gb}}" href="?do=Statistics&show=stat&filters={{query}}~,~gbram={{gb}}">{{gb}} GiB</a> - </td> - <td class="text-right">{{count}}</td> - </tr> - {{/rows}} - <tr class="slx-decollapse"> - <td colspan="2"> - <span class="btn-group btn-group-justified"> - <span class="btn btn-default btn-sm"> - <span class="glyphicon glyphicon-menu-down"></span> + <table class="stupidtable filter-col table table-condensed table-striped" data-filter-col="gbram"> + <thead> + <tr> + <th data-sort="int">{{lang_ramSize}}</th> + <th data-sort="int" class="text-right">{{lang_machineCount}}</th> + </tr> + </thead> + <tbody> + {{#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> + </td> + <td class="text-right">{{count}}</td> + </tr> + {{/rows}} + <tr class="slx-decollapse"> + <td colspan="2"> + <span class="btn-group btn-group-justified"> + <span class="btn btn-default btn-sm"> + <span class="glyphicon glyphicon-menu-down"></span> + </span> </span> - </span> - </td> - </tr> + </td> + </tr> + </tbody> </table> </div> <div class="col-sm-6"> diff --git a/modules-available/statistics/templates/newclients.html b/modules-available/statistics/templates/newclients.html index c5c704d1..6dc04144 100644 --- a/modules-available/statistics/templates/newclients.html +++ b/modules-available/statistics/templates/newclients.html @@ -4,32 +4,36 @@ {{lang_newMachines}} </div> <div class="panel-body"> - <table class="table table-condensed table-striped"> - <tr> - <th>{{lang_machine}}</th> - <th class="text-right"></th> - <th>64Bit</th> - <th class="text-right">RAM</th> - <th class="text-right">HDD</th> - </tr> - {{#rows}} - <tr class="{{collapse}}"> - <td class="text-nowrap"><a href="?do=Statistics&uuid={{machineuuid}}">{{hostname}}</a></td> - <td class="text-right">{{firstseen}}</td> - <td class="{{kvmclass}}">{{kvmicon}}</td> - <td class="text-right {{ramclass}}">{{gbram}} GiB</td> - <td class="text-right {{hddclass}}">{{gbtmp}} GiB</td> - </tr> - {{/rows}} - <tr class="slx-decollapse"> - <td colspan="5"> - <span class="btn-group btn-group-justified"> - <span class="btn btn-default btn-sm"> - <span class="glyphicon glyphicon-menu-down"></span> + <table class="stupidtable table table-condensed table-striped"> + <thead> + <tr> + <th data-sort="string">{{lang_machine}}</th> + <th data-sort="int" class="text-right" style="min-width: 80px;">{{lang_firstSeen}}</th> + <th data-sort="string" class="text-center">64Bit</th> + <th data-sort="int" class="text-right">RAM</th> + <th data-sort="int" class="text-right">HDD</th> + </tr> + </thead> + <tbody> + {{#rows}} + <tr class="{{collapse}}"> + <td data-sort-value="{{hostname}}" class="text-nowrap"><a href="?do=Statistics&uuid={{machineuuid}}">{{hostname}}</a></td> + <td data-sort-value="{{firstseen_int}}" class="text-right">{{firstseen}}</td> + <td class="text-center {{kvmclass}}">{{kvmicon}}</td> + <td data-sort-value="{{gbram}}" class="text-right {{ramclass}}">{{gbram}} GiB</td> + <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}">{{gbtmp}} GiB</td> + </tr> + {{/rows}} + <tr class="slx-decollapse"> + <td colspan="5"> + <span class="btn-group btn-group-justified"> + <span class="btn btn-default btn-sm"> + <span class="glyphicon glyphicon-menu-down"></span> + </span> </span> - </span> - </td> - </tr> + </td> + </tr> + </tbody> </table> </div> </div> diff --git a/modules-available/statistics/templates/summary.html b/modules-available/statistics/templates/summary.html index 642c48fc..fe9559ed 100644 --- a/modules-available/statistics/templates/summary.html +++ b/modules-available/statistics/templates/summary.html @@ -1,6 +1,11 @@ <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-body"> + {{#runmode}} + <div class="pull-right"> + <a href="?do=runmode">{{lang_runmodeMachines}}</a>: <b>{{runmode}}</b> + </div> + {{/runmode}} <div> {{lang_knownMachines}}: <b>{{known}}</b>  <a href="?do=Statistics&show=stat&filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>  @@ -10,8 +15,9 @@ <div> <span class="glyphicon glyphicon-exclamation-sign red"></span> <a href="?do=Statistics&show=list&filters={{query}}~,~badsectors>=10"> - {{lang_withBadSectors}}: <b>{{badhdd}}</b> + {{lang_withBadSectors}}: </a> + <b>{{badhdd}}</b> </div> {{/badhdd}} </div> diff --git a/modules-available/statistics/templates/syslog.html b/modules-available/statistics/templates/syslog.html index c82cb8ac..968d32ab 100644 --- a/modules-available/statistics/templates/syslog.html +++ b/modules-available/statistics/templates/syslog.html @@ -20,7 +20,7 @@ {{/list}} </tbody> </table> -<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&ip={{clientip}}">{{lang_more}} »</a></div> +<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&machineuuid={{machineuuid}}">{{lang_more}} »</a></div> <div class="clearfix"></div> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> |