summaryrefslogtreecommitdiffstats
path: root/modules-available/statistics
diff options
context:
space:
mode:
authorJannik Schönartz2017-12-14 13:03:44 +0100
committerJannik Schönartz2017-12-14 13:03:44 +0100
commit5d5c2f27bee5d4fbd3747555efbf2ac9f337805b (patch)
treec65898e1b3d6f0f46366a280bbbaf4c6ccbc477c /modules-available/statistics
parent[usb-lock-off] Design changes to fit the design_guidelines. TODO: lang_discar... (diff)
parent[sysconfig] Update translations (diff)
downloadslx-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')
-rw-r--r--modules-available/statistics/api.inc.php209
-rw-r--r--modules-available/statistics/hooks/cron.inc.php42
-rw-r--r--modules-available/statistics/inc/filter.inc.php51
-rw-r--r--modules-available/statistics/inc/filterset.inc.php14
-rw-r--r--modules-available/statistics/inc/machine.inc.php5
-rw-r--r--modules-available/statistics/inc/statistics.inc.php51
-rw-r--r--modules-available/statistics/install.inc.php20
-rw-r--r--modules-available/statistics/lang/de/messages.json5
-rw-r--r--modules-available/statistics/lang/de/template-tags.json9
-rw-r--r--modules-available/statistics/lang/en/messages.json5
-rw-r--r--modules-available/statistics/lang/en/template-tags.json13
-rw-r--r--modules-available/statistics/page.inc.php176
-rw-r--r--modules-available/statistics/style.css11
-rw-r--r--modules-available/statistics/templates/clientlist.html203
-rw-r--r--modules-available/statistics/templates/cpumodels.html58
-rw-r--r--modules-available/statistics/templates/filterbox.html101
-rw-r--r--modules-available/statistics/templates/id44.html46
-rw-r--r--modules-available/statistics/templates/kvmstate.html30
-rw-r--r--modules-available/statistics/templates/machine-main.html23
-rw-r--r--modules-available/statistics/templates/machine-notes.html3
-rw-r--r--modules-available/statistics/templates/machine-usage.html80
-rw-r--r--modules-available/statistics/templates/memory.html46
-rw-r--r--modules-available/statistics/templates/newclients.html54
-rw-r--r--modules-available/statistics/templates/summary.html8
-rw-r--r--modules-available/statistics/templates/syslog.html2
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) . '%">&nbsp;</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) . '%">&nbsp;</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) . '%">&nbsp;</div>';
+ } elseif ($first) {
+ // Not seen in last two weeks
+ $spans['graph'] .= '<div style="background:#444;left:0%;width:100%">&nbsp;</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) . '%">&nbsp;</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&amp;uuid={{machineuuid}}"><b>{{hostname}}</b></a>
- <div class="small">{{machineuuid}}</div>
- </td>
- <td><b><a href="?do=Statistics&amp;show=list&amp;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}}&thinsp;GiB</td>
- <td class="text-right {{hddclass}}">
- {{gbtmp}}&thinsp;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&amp;uuid={{machineuuid}}"><b>{{hostname}}</b></a>
+ <div class="small">{{machineuuid}}</div>
+ {{#rmmodule}}<div class="small">{{lang_runMode}}: <a class="slx-bold" href="?do=runmode&amp;module={{rmmodule}}">{{rmmodule}}</a></div>{{/rmmodule}}
+ </td>
+ <td data-sort-value="{{clientip}}"><b><a href="?do=Statistics&amp;show=list&amp;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}}&thinsp;GiB</td>
+ <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}">
+ {{gbtmp}}&thinsp;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">&times;</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&amp;show=stat&amp;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&amp;show=stat&amp;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&amp;show=stat&amp;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&amp;show=stat&amp;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">&times;</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&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;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&amp;show=stat&amp;filters={{query}}~,~hddgb={{gb}}">{{gb}}&thinsp;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&amp;show=stat&amp;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&amp;show=stat&amp;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&amp;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">&nbsp;{{{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&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;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&amp;show=stat&amp;filters={{query}}~,~gbram={{gb}}">{{gb}}&thinsp;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&amp;uuid={{machineuuid}}">{{hostname}}</a></td>
- <td class="text-right">{{firstseen}}</td>
- <td class="{{kvmclass}}">{{kvmicon}}</td>
- <td class="text-right {{ramclass}}">{{gbram}}&thinsp;GiB</td>
- <td class="text-right {{hddclass}}">{{gbtmp}}&thinsp;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&amp;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}}&thinsp;GiB</td>
+ <td data-sort-value="{{gbtmp}}" class="text-right {{hddclass}}">{{gbtmp}}&thinsp;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>&emsp;
<a href="?do=Statistics&amp;show=stat&amp;filters={{query}}~,~state=on">{{lang_onlineMachines}}</a>: <b>{{online}}</b>&emsp;
@@ -10,8 +15,9 @@
<div>
<span class="glyphicon glyphicon-exclamation-sign red"></span>
<a href="?do=Statistics&amp;show=list&amp;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&amp;ip={{clientip}}">{{lang_more}} &raquo;</a></div>
+<div class="pull-right"><a class="btn btn-default btn-sm" href="?do=SysLog&machineuuid={{machineuuid}}">{{lang_more}} &raquo;</a></div>
<div class="clearfix"></div>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">