diff options
22 files changed, 206 insertions, 37 deletions
diff --git a/lang/de/messages-hardcoded.json b/lang/de/messages-hardcoded.json index 76d13e30..f785f042 100644 --- a/lang/de/messages-hardcoded.json +++ b/lang/de/messages-hardcoded.json @@ -11,9 +11,11 @@ "lang_noModuleFromThisGroup": "(Kein Modul dieser Gruppe)", "lang_serverConfiguration": "Serverseitige Konfiguration", "lang_titleBackup": "Sichern und Wiederherstellen", + "lang_titleClientStatistics": "Client-Statistiken", "lang_titleEventLog": "Ereignisprotokoll", "lang_titleWebinterface": "Webschnittstelle", "lang_unknwonTaskManager": "Unbekannter Taskmanager-Fehler", "today": "Heute", + "unused": "Ungenutzt", "yesterday": "Gestern" }
\ No newline at end of file diff --git a/lang/de/messages.json b/lang/de/messages.json index 13b38c59..4d2859b2 100644 --- a/lang/de/messages.json +++ b/lang/de/messages.json @@ -21,6 +21,7 @@ "i18n-invalid-lang": "Ung\u00fcltige Sprache: {{0}}", "invalid-action": "Ung\u00fcltige Aktion: {{0}}", "invalid-file": "Die Datei {{0}} existiert nicht!", + "invalid-filter": "Ung\u00fcltiger Filter", "invalid-ip": "Kein Interface ist auf die Adresse {{0}} konfiguriert", "invalid-path": "Ung\u00fcltiger Pfad.", "invalid-template": "Ausgew\u00e4hlte Template ist nicht g\u00fcltig", @@ -40,6 +41,7 @@ "news-save-success": "News erfolgreich aktualisiert", "no-image": "Unter der angegebenen URL konnte kein SVG-Bild gefunden werden", "no-permission": "Keine ausreichenden Rechte, um auf diese Seite zuzugreifen", + "notes-saved": "Anmerkungen gespeichert", "password-mismatch": "Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein", "reboot-unconfirmed": "Sicherheitsabfrage zum Reboot nicht best\u00e4tigt", "remote-parse-failed": "Parsen der empfangenen Daten fehlgeschlagen ({{0}})", diff --git a/lang/de/templates/main-menu.json b/lang/de/templates/main-menu.json index efd918a3..3ed43eff 100644 --- a/lang/de/templates/main-menu.json +++ b/lang/de/templates/main-menu.json @@ -2,6 +2,7 @@ "lang_backup": "Backup\/Restore", "lang_client": "Client", "lang_clientLog": "Client Log", + "lang_clientStats": "Client-Statistiken", "lang_configurationBasic": "PXE\/Boot", "lang_configurationVariables": "KonfigurationsVariablen", "lang_dozmod": "Dozentenmodul", diff --git a/lang/de/templates/statistics/machine-notes.json b/lang/de/templates/statistics/machine-notes.json new file mode 100644 index 00000000..f9df1b92 --- /dev/null +++ b/lang/de/templates/statistics/machine-notes.json @@ -0,0 +1,4 @@ +{ + "lang_notes": "Anmerkungen", + "lang_save": "Speichern" +}
\ No newline at end of file diff --git a/lang/de/templates/statistics/newclients.json b/lang/de/templates/statistics/newclients.json new file mode 100644 index 00000000..f1353389 --- /dev/null +++ b/lang/de/templates/statistics/newclients.json @@ -0,0 +1,4 @@ +{ + "lang_machine": "Client", + "lang_newMachines": "Neue Ger\u00e4te" +}
\ No newline at end of file diff --git a/lang/en/messages-hardcoded.json b/lang/en/messages-hardcoded.json index 325f04ef..b19ef60a 100644 --- a/lang/en/messages-hardcoded.json +++ b/lang/en/messages-hardcoded.json @@ -11,9 +11,11 @@ "lang_noModuleFromThisGroup": "(No module from this group)", "lang_serverConfiguration": "Server-side Configuration", "lang_titleBackup": "Save and Restore", + "lang_titleClientStatistics": "Client statistics", "lang_titleEventLog": "Event log", "lang_titleWebinterface": "Web Interface", "lang_unknwonTaskManager": "Unknown Task Manager error", "today": "Today", + "unused": "Unused", "yesterday": "Yesterday" }
\ No newline at end of file diff --git a/lang/en/messages.json b/lang/en/messages.json index bd8ffb18..aed10cce 100644 --- a/lang/en/messages.json +++ b/lang/en/messages.json @@ -21,6 +21,7 @@ "i18n-invalid-lang": "Invalid language: {{0}}", "invalid-action": "Invalid action: {{0}}", "invalid-file": "The file {{0}} does not exist!", + "invalid-filter": "Invalid filter", "invalid-ip": "No interface is configured with the address {{0}}", "invalid-path": "Invalid path.", "invalid-template": "Selected template is not valid", @@ -40,6 +41,7 @@ "news-save-success": "News updated successfully", "no-image": "Could not find an SVG-image at the given URL", "no-permission": "No sufficient privileges to access this page", + "notes-saved": "Notes have been saved", "password-mismatch": "Password and password confirmation do not match", "reboot-unconfirmed": "Confirmation prompt to reboot not confirmed", "remote-parse-failed": "Parsing the received data failed ({{0}})", diff --git a/lang/en/templates/main-menu.json b/lang/en/templates/main-menu.json index 839d65b1..be9cefed 100644 --- a/lang/en/templates/main-menu.json +++ b/lang/en/templates/main-menu.json @@ -2,6 +2,7 @@ "lang_backup": "Backup\/Restore", "lang_client": "Client", "lang_clientLog": "Client Log", + "lang_clientStats": "Client statistics", "lang_configurationBasic": "PXE\/Boot", "lang_configurationVariables": "Configuration Variables", "lang_dozmod": "Tutor module", diff --git a/lang/en/templates/statistics/machine-notes.json b/lang/en/templates/statistics/machine-notes.json new file mode 100644 index 00000000..7a13f28a --- /dev/null +++ b/lang/en/templates/statistics/machine-notes.json @@ -0,0 +1,4 @@ +{ + "lang_notes": "Notes", + "lang_save": "Save" +}
\ No newline at end of file diff --git a/lang/en/templates/statistics/newclients.json b/lang/en/templates/statistics/newclients.json new file mode 100644 index 00000000..f7e55f3f --- /dev/null +++ b/lang/en/templates/statistics/newclients.json @@ -0,0 +1,4 @@ +{ + "lang_machine": "Client", + "lang_newMachines": "New machines" +}
\ No newline at end of file diff --git a/lang/pt/templates/statistics/machine-notes.json b/lang/pt/templates/statistics/machine-notes.json new file mode 100644 index 00000000..c44dc44f --- /dev/null +++ b/lang/pt/templates/statistics/machine-notes.json @@ -0,0 +1,3 @@ +[ + +]
\ No newline at end of file diff --git a/lang/pt/templates/statistics/newclients.json b/lang/pt/templates/statistics/newclients.json new file mode 100644 index 00000000..c44dc44f --- /dev/null +++ b/lang/pt/templates/statistics/newclients.json @@ -0,0 +1,3 @@ +[ + +]
\ No newline at end of file diff --git a/modules/statistics.inc.php b/modules/statistics.inc.php index c459528f..c8c869b9 100644 --- a/modules/statistics.inc.php +++ b/modules/statistics.inc.php @@ -1,7 +1,11 @@ <?php global $STATS_COLORS, $SIZE_ID44, $SIZE_RAM; -$STATS_COLORS = array('#e55', '#ee6', '#4d4', '#44f', '#e83', '#0de', '#08f', '#666', '#e0e', '#999'); +$STATS_COLORS = array(); +for ($i = 0; $i < 10; ++$i) { + $STATS_COLORS[] = '#55' . sprintf('%02s%02s', dechex((($i+1)*($i+1)) / .3922), dechex(abs((5-$i) * 51))); +} +//$STATS_COLORS = array('#57e', '#ee8', '#5ae', '#fb7', '#6d7', '#e77', '#3af', '#666', '#e0e', '#999'); $SIZE_ID44 = array(0, 8, 16, 24, 30, 40, 50, 60, 80, 100, 120, 150, 180, 250, 300, 350, 400, 450, 500); $SIZE_RAM = array(1, 2, 3, 4, 6, 8, 10, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 320, 480, 512, 768, 1024); @@ -15,6 +19,20 @@ class Page_Statistics extends Page Message::addError('no-permission'); Util::redirect('?do=Main'); } + $action = Request::post('action'); + if ($action === 'setnotes') { + $uuid = Request::post('uuid', '', 'string'); + $text = Request::post('content', '', 'string'); + if (empty($text)) { + $text = null; + } + Database::exec('UPDATE machine SET notes = :text WHERE machineuuid = :uuid', array( + 'uuid' => $uuid, + 'text' => $text + )); + Message::addSuccess('notes-saved'); + Util::redirect('?do=Statistics&uuid=' . $uuid); + } } protected function doRender() @@ -33,12 +51,38 @@ class Page_Statistics extends Page } Render::addScriptBottom('chart.min'); Render::openTag('div', array('class' => 'row')); - $this->showCpuModels(); $this->showMemory(); - $this->showKvmState(); $this->showId44(); + $this->showKvmState(); + $this->showLatestMachines(); + $this->showCpuModels(); Render::closeTag('div'); } + + private function capChart(&$json, $cutoff) + { + $total = 0; + foreach ($json as $entry) { + $total += $entry['value']; + } + $cap = ceil($total * $cutoff); + $accounted = 0; + $id = 0; + foreach ($json as $entry) { + if ($accounted < $cap || $id < 3) { + $id++; + $accounted += $entry['value']; + } + } + $json = array_slice($json, 0, $id); + if ($accounted / $total < 0.99) { + $json[] = array( + 'color' => '#eee', + 'label' => 'invalid', + 'value' => ($total - $accounted) + ); + } + } private function showCpuModels() { @@ -47,10 +91,8 @@ class Page_Statistics extends Page $lines = array(); $json = array(); $id = 0; - $total = 0; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { settype($row['count'], 'integer'); - $total += $row['count']; $row['id'] = 'cpuid' . $id; $row['urlcpumodel'] = urlencode($row['cpumodel']); $lines[] = $row; @@ -61,14 +103,7 @@ class Page_Statistics extends Page ); ++$id; } - $cap = ceil($total * 0.95); - $total = 0; - $id = 0; - foreach ($json as $entry) { - $total += $entry['value']; - if ($total <= $cap) $id++; - } - $json = array_slice($json, 0, $id); + $this->capChart($json, 0.92); Render::addTemplate('statistics/cpumodels', array('rows' => $lines, 'json' => json_encode($json))); } @@ -96,7 +131,7 @@ class Page_Statistics extends Page $json = array(); $id = 0; foreach (array_reverse($lines, true) as $k => $v) { - $data['rows'][] = array('gb' => $k, 'count' => $v); + $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => $this->ramColorClass($k * 1024)); $json[] = array( 'color' => $STATS_COLORS[$id % count($STATS_COLORS)], 'label' => (string)$k, @@ -104,13 +139,14 @@ class Page_Statistics extends Page ); ++$id; } + $this->capChart($json, 0.92); $data['json'] = json_encode($json); Render::addTemplate('statistics/memory', $data); } private function showKvmState() { - $colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#4d4'); + $colors = array('UNKNOWN' => '#666', 'UNSUPPORTED' => '#ea5', 'DISABLED' => '#e55', 'ENABLED' => '#6d6'); $res = Database::simpleQuery("SELECT kvmstate, Count(*) AS `count` FROM machine GROUP BY kvmstate ORDER BY `count` DESC"); $lines = array(); $json = array(); @@ -149,30 +185,62 @@ class Page_Statistics extends Page asort($lines); $data = array('rows' => array()); $json = array(); - $id = 1; + $id = 0; $cap = ceil($total * 0.95); - $total = 0; + $accounted = 0; foreach (array_reverse($lines, true) as $k => $v) { - $data['rows'][] = array('gb' => $k, 'count' => $v); - $total += $v; - if ($total <= $cap) { + $data['rows'][] = array('gb' => $k, 'count' => $v, 'class' => $this->hddColorClass($k)); + if ($accounted <= $cap) { if ($k === 0) { - $color = $STATS_COLORS[0]; + $color = '#e55'; } else { - $color = $STATS_COLORS[$id % count($STATS_COLORS)]; + $color = $STATS_COLORS[$id++ % count($STATS_COLORS)]; } $json[] = array( 'color' => $color, 'label' => (string)$k, 'value' => $v ); - ++$id; } + $accounted += $v; + } + if ($accounted / $total < 0.99) { + $json[] = array( + 'color' => '#eee', + 'label' => 'invalid', + 'value' => ($total - $accounted) + ); } $data['json'] = json_encode($json); Render::addTemplate('statistics/id44', $data); } + private function showLatestMachines() + { + $data = array('cutoff' => ceil(time() / 3600) * 3600 - 86400 * 7); + $res = Database::simpleQuery("SELECT machineuuid, clientip, hostname, firstseen, mbram, kvmstate, id44mb FROM machine" + . " WHERE firstseen > :cutoff ORDER BY firstseen DESC LIMIT 32", $data); + $rows = array(); + $count = 0; + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (empty($row['hostname'])) { + $row['hostname'] = $row['clientip']; + } + $row['firstseen'] = date('d.m. H:i', $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']); + $row['kvmclass'] = $this->kvmColorClass($row['kvmstate']); + $row['hddclass'] = $this->hddColorClass($row['gbtmp']); + $row['kvmicon'] = $row['kvmstate'] === 'ENABLED' ? '✓' : '✗'; + if (++$count > 5) { + $row['style'] = 'display:none'; + } + $rows[] = $row; + } + Render::addTemplate('statistics/newclients', array('rows' => $rows, 'openbutton' => $count > 5)); + } + private function showMachineList($filter, $argument) { global $SIZE_RAM, $SIZE_ID44; @@ -202,7 +270,7 @@ class Page_Statistics extends Page return; } $res = Database::simpleQuery("SELECT machineuuid, macaddr, clientip, firstseen, lastseen," - . " logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname FROM machine" + . " logintime, lastboot, realcores, mbram, kvmstate, cpumodel, id44mb, hostname, notes IS NOT NULL AS hasnotes FROM machine" . " WHERE $where ORDER BY lastseen DESC, clientip ASC", $args); $rows = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { @@ -275,7 +343,7 @@ class Page_Statistics extends Page private function showMachine($uuid) { $row = Database::queryFirst("SELECT machineuuid, macaddr, clientip, firstseen, lastseen, logintime, lastboot," - . " mbram, kvmstate, cpumodel, id44mb, data, hostname FROM machine WHERE machineuuid = :uuid", + . " mbram, kvmstate, cpumodel, id44mb, data, hostname, notes FROM machine WHERE machineuuid = :uuid", array('uuid' => $uuid)); // Mangle fields $row['firstseen'] = date('d.m.Y H:i', $row['firstseen']); @@ -305,6 +373,7 @@ class Page_Statistics extends Page Render::addScriptBottom('chart.min'); Render::addTemplate('statistics/machine-hdds', $hdds); } + Render::addTemplate('statistics/machine-notes', $row); } private function parseCpu(&$row, $data) @@ -341,13 +410,13 @@ class Page_Statistics extends Page $unit = $out[1] / (1024 * 1024); // Convert so that multiplying by unit yields MiB } else if (isset($hdd) && $unit !== 0 && preg_match(',^/dev/(\S+)\s+.*\s(\d+)\s+(\d+)\s+\d+\s+([0-9a-f]+)\s+(.*)$,i', $line, $out)) { // Some partition - $type = (string)$out[4]; - if ($type === '5') continue; + $type = strtolower($out[4]); + if ($type === '5' || $type === 'f' || $type === '85') continue; $partsize = round(($out[3] - $out[2]) * $unit); $hdd['partitions'][] = array( 'id' => $out[1], 'name' => $out[1], - 'size' => round($partsize / 1024), + 'size' => round($partsize / 1024, $partsize < 1024 ? 1 : 0), 'type' => ($type === '44' ? 'OpenSLX' : $out[5]), ); $hdd['json'][] = array( diff --git a/templates/statistics/clientlist.html b/templates/statistics/clientlist.html index e5f91493..879e27c4 100644 --- a/templates/statistics/clientlist.html +++ b/templates/statistics/clientlist.html @@ -14,7 +14,11 @@ </tr> {{#rows}} <tr> - <td class="slx-nowrap"><a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a><div class="small">{{machineuuid}}</div></td> + <td class="slx-nowrap"> + {{#hasnotes}}<span class="glyphicon glyphicon-exclamation-sign pull-right"></span>{{/hasnotes}} + <a href="?do=Statistics&uuid={{machineuuid}}"><b>{{hostname}}</b></a> + <div class="small">{{machineuuid}}</div> + </td> <td><b><a href="?do=Statistics&filter=subnet&argument={{subnet}}">{{subnet}}</a>{{lastoctet}}</b><br>{{macaddr}}</td> <td class="text-right">{{lastboot}}</td> <td class="{{kvmclass}}">{{kvmstate}}</td> diff --git a/templates/statistics/cpumodels.html b/templates/statistics/cpumodels.html index c3d43aa4..f98b89db 100644 --- a/templates/statistics/cpumodels.html +++ b/templates/statistics/cpumodels.html @@ -31,8 +31,8 @@ animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { + if (sel !== false) sel.removeClass('info'); if (!tooltip) { - if (sel !== false) sel.removeClass('info'); sel = false; return; } diff --git a/templates/statistics/id44.html b/templates/statistics/id44.html index cd4e32d7..d9c92c47 100644 --- a/templates/statistics/id44.html +++ b/templates/statistics/id44.html @@ -12,7 +12,7 @@ <th class="text-right">{{lang_machineCount}}</th> </tr> {{#rows}} - <tr id="tmpid{{gb}}"> + <tr id="tmpid{{gb}}" class="{{class}}"> <td class="text-left slx-nowrap"><a href="?do=Statistics&filter=hddgb&argument={{gb}}">{{gb}} GiB</td> <td class="text-right">{{count}}</td> </tr> @@ -29,8 +29,8 @@ animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { + if (sel !== false) sel.removeClass('info'); if (!tooltip) { - if (sel !== false) sel.removeClass('info'); sel = false; return; } diff --git a/templates/statistics/kvmstate.html b/templates/statistics/kvmstate.html index ea95df4e..5431990c 100644 --- a/templates/statistics/kvmstate.html +++ b/templates/statistics/kvmstate.html @@ -29,8 +29,8 @@ animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { + if (sel !== false) sel.removeClass('info'); if (!tooltip) { - if (sel !== false) sel.removeClass('info'); sel = false; return; } diff --git a/templates/statistics/machine-hdds.html b/templates/statistics/machine-hdds.html index 98cf65c5..cb3cbffc 100644 --- a/templates/statistics/machine-hdds.html +++ b/templates/statistics/machine-hdds.html @@ -35,8 +35,8 @@ animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { + if (sel !== false) sel.removeClass('info'); if (!tooltip) { - if (sel !== false) sel.removeClass('info'); sel = false; return; } diff --git a/templates/statistics/machine-main.html b/templates/statistics/machine-main.html index ac8b8001..985194bf 100644 --- a/templates/statistics/machine-main.html +++ b/templates/statistics/machine-main.html @@ -1,4 +1,7 @@ -<h1>{{hostname}} {{#hostname}}–{{/hostname}} {{clientip}}</h1> +<h1> + {{hostname}} {{#hostname}}–{{/hostname}} {{clientip}} + {{#notes}}<a href="#usernotes"><span class="glyphicon glyphicon-exclamation-sign"></span></a>{{/notes}} +</h1> <div class="row"> <div class="col-md-6"> diff --git a/templates/statistics/machine-notes.html b/templates/statistics/machine-notes.html new file mode 100644 index 00000000..c4f97543 --- /dev/null +++ b/templates/statistics/machine-notes.html @@ -0,0 +1,17 @@ +<a name="usernotes"></a> +<h3>{{lang_notes}}</h3> +<div class="row"> + <div class="col-md-12"> + <div class="panel panel-default"> + <div class="panel-body"> + <form action="?do=Statistics" method="post"> + <input type="hidden" name="token" value="{{token}}"> + <input type="hidden" name="action" value="setnotes"> + <input type="hidden" name="uuid" value="{{machineuuid}}"> + <textarea name="content" class="form-control" cols="101" rows="10">{{notes}}</textarea> + <button type="submit" class="btn btn-primary">{{lang_save}}</button> + </form> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/templates/statistics/memory.html b/templates/statistics/memory.html index b9ee77fb..1ebfbae0 100644 --- a/templates/statistics/memory.html +++ b/templates/statistics/memory.html @@ -12,7 +12,7 @@ <th class="text-right">{{lang_machineCount}}</th> </tr> {{#rows}} - <tr id="ramid{{gb}}"> + <tr id="ramid{{gb}}" class="{{class}}"> <td class="text-left slx-nowrap"><a href="?do=Statistics&filter=gbram&argument={{gb}}">{{gb}} GiB</a></td> <td class="text-right">{{count}}</td> </tr> @@ -29,8 +29,8 @@ animation: false, tooltipTemplate: "<%if (label){%><%=label%><%}%>", customTooltips: function(tooltip) { + if (sel !== false) sel.removeClass('info'); if (!tooltip) { - if (sel !== false) sel.removeClass('info'); sel = false; return; } diff --git a/templates/statistics/newclients.html b/templates/statistics/newclients.html new file mode 100644 index 00000000..0d9c74df --- /dev/null +++ b/templates/statistics/newclients.html @@ -0,0 +1,44 @@ +<div class="col-md-6"> + <div class="panel panel-default"> + <div class="panel-heading"> + {{lang_newMachines}} + </div> + <div class="panel-body"> + <table class="table table-condensed table-striped" id="newclienttable"> + <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 style="{{style}}"> + <td class="slx-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}} + {{#openbutton}} + <tr> + <td colspan="5" onclick="slxExpandNew(this)"> + <span class="btn-group btn-group-justified"> + <span class="btn btn-default"> + <span class="glyphicon glyphicon-menu-down"></span> + </span> + </span> + <script type="text/javascript"> + function slxExpandNew(b) { + $('#newclienttable').find('tr').show(); + $(b).hide(); + } + </script> + </td> + </tr> + {{/openbutton}} + </table> + </div> + </div> +</div>
\ No newline at end of file |