summaryrefslogtreecommitdiffstats
path: root/modules-available/dnbd3
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/dnbd3')
-rw-r--r--modules-available/dnbd3/baseconfig/getconfig.inc.php13
-rw-r--r--modules-available/dnbd3/hooks/main-warning.inc.php4
-rw-r--r--modules-available/dnbd3/hooks/translation.inc.php4
-rw-r--r--modules-available/dnbd3/inc/dnbd3.inc.php39
-rw-r--r--modules-available/dnbd3/inc/dnbd3rpc.inc.php147
-rw-r--r--modules-available/dnbd3/inc/dnbd3util.inc.php52
-rw-r--r--modules-available/dnbd3/lang/de/config-variables.json4
-rw-r--r--modules-available/dnbd3/lang/de/template-tags.json9
-rw-r--r--modules-available/dnbd3/lang/en/config-variables.json4
-rw-r--r--modules-available/dnbd3/lang/en/template-tags.json7
-rw-r--r--modules-available/dnbd3/page.inc.php94
-rw-r--r--modules-available/dnbd3/templates/fragment-server-settings.html2
-rw-r--r--modules-available/dnbd3/templates/page-proxy-images.html4
-rw-r--r--modules-available/dnbd3/templates/page-proxy-stats.html7
-rw-r--r--modules-available/dnbd3/templates/page-serverlist.html156
15 files changed, 390 insertions, 156 deletions
diff --git a/modules-available/dnbd3/baseconfig/getconfig.inc.php b/modules-available/dnbd3/baseconfig/getconfig.inc.php
index e4f84d81..eff821fc 100644
--- a/modules-available/dnbd3/baseconfig/getconfig.inc.php
+++ b/modules-available/dnbd3/baseconfig/getconfig.inc.php
@@ -1,5 +1,8 @@
<?php
+/** @var ?string $uuid */
+/** @var ?string $ip */
+
if (Dnbd3::isEnabled()) {
if (!Dnbd3::hasNfsFallback()) {
ConfigHolder::add("SLX_VM_NFS", false, 1000);
@@ -12,7 +15,7 @@ if (Dnbd3::isEnabled()) {
// Locations from closest to furthest (order)
$locations = ConfigHolder::get('SLX_LOCATIONS');
-if ($locations === false) {
+if ($locations === null) {
$locationIds = [0];
} else {
$locationIds = explode(' ', $locations);
@@ -29,14 +32,14 @@ $res = Database::simpleQuery('SELECT s.fixedip, m.clientip, sxl.locationid FROM
$locationsAssoc = array_flip($locationIds);
$servers = array();
$fallback = array();
-while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+foreach ($res as $row) {
if ($row['fixedip'] === '<self>') {
$row['fixedip'] = Property::getServerIp();
- $defPrio = 2000;
+ $defPrio = Dnbd3::preferLocal() ? 500 : 2000;
} else {
$defPrio = 1000;
}
- $ip = $row['fixedip'] ? $row['fixedip'] : $row['clientip'];
+ $ip = $row['fixedip'] ?: $row['clientip'];
// See if this server is meant for the client at all
if (!is_null($row['locationid']) && !isset($locationsAssoc[$row['locationid']])) {
$fallback[$ip] = true;
@@ -50,7 +53,7 @@ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$row['locationid'] = $serverLoc;
}
}
- $old = isset($servers[$ip]) ? $servers[$ip] : PHP_INT_MAX;
+ $old = $servers[$ip] ?? PHP_INT_MAX;
if (is_null($row['locationid']) || !isset($locationsAssoc[$row['locationid']])) {
$servers[$ip] = min($defPrio . '.' . mt_rand(), $old);
} else {
diff --git a/modules-available/dnbd3/hooks/main-warning.inc.php b/modules-available/dnbd3/hooks/main-warning.inc.php
index 5f8a844f..ead0a259 100644
--- a/modules-available/dnbd3/hooks/main-warning.inc.php
+++ b/modules-available/dnbd3/hooks/main-warning.inc.php
@@ -6,8 +6,8 @@ if (Dnbd3::isEnabled() && User::hasPermission('.dnbd3.access-page')) {
LEFT JOIN machine m USING (machineuuid)
WHERE errormsg IS NOT NULL');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $error = $row['errormsg'] ? $row['errormsg'] : '<unknown error>';
+ foreach ($res as $row) {
+ $error = $row['errormsg'] ?: '<unknown error>';
$lastSeen = Util::prettyTime($row['dnbd3lastseen']);
if ($row['fixedip'] === '<self>') {
Message::addError('dnbd3.main-dnbd3-unreachable', true, $error, $lastSeen);
diff --git a/modules-available/dnbd3/hooks/translation.inc.php b/modules-available/dnbd3/hooks/translation.inc.php
index cb1854b4..9ff593cf 100644
--- a/modules-available/dnbd3/hooks/translation.inc.php
+++ b/modules-available/dnbd3/hooks/translation.inc.php
@@ -16,10 +16,8 @@ $HANDLER['subsections'] = array(
/**
* Configuration variables.
- * @param \Module $module
- * @return array
*/
-$HANDLER['grep_config-variables'] = function($module) {
+$HANDLER['grep_config-variables'] = function(Module $module): array {
if (!$module->activate(1, false) || !Module::isAvailable('baseconfig'))
return array();
$want = BaseConfigUtil::getVariables($module);
diff --git a/modules-available/dnbd3/inc/dnbd3.inc.php b/modules-available/dnbd3/inc/dnbd3.inc.php
index ccd783d9..def4e062 100644
--- a/modules-available/dnbd3/inc/dnbd3.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3.inc.php
@@ -4,10 +4,11 @@ class Dnbd3 {
const PROP_ENABLED = 'dnbd3.enabled';
const PROP_NFS_FALLBACK = 'dnbd3.nfs-fallback';
+ const PROP_PREFER_LOCAL = 'dnbd3.prefer-local';
- public static function isEnabled()
+ public static function isEnabled(): bool
{
- return Property::get(self::PROP_ENABLED, 0) ? true : false;
+ return (bool)Property::get(self::PROP_ENABLED, 0);
}
public static function setEnabled($bool)
@@ -16,14 +17,44 @@ class Dnbd3 {
Trigger::mount(false, true);
}
- public static function hasNfsFallback()
+ public static function hasNfsFallback(): bool
{
- return Property::get(self::PROP_NFS_FALLBACK, 0) ? true : false;
+ return (bool)Property::get(self::PROP_NFS_FALLBACK, 0);
}
public static function setNfsFallback($bool)
{
Property::set(self::PROP_NFS_FALLBACK, $bool ? 1 : 0);
}
+ public static function preferLocal(): bool
+ {
+ return (bool)Property::get(self::PROP_PREFER_LOCAL, 0);
+ }
+
+ public static function setPreferLocal($bool)
+ {
+ Property::set(self::PROP_PREFER_LOCAL, $bool ? 1 : 0);
+ }
+
+ public static function getActiveServers(): array
+ {
+ $res = Database::simpleQuery('SELECT s.serverid, m.clientip, s.fixedip
+ FROM dnbd3_server s
+ LEFT JOIN machine m ON (s.machineuuid = m.machineuuid)
+ WHERE s.lastseen > :cutoff', ['cutoff' => CONFIG_DEBUG ? 0 : time() - 310]);
+ $lookup = [];
+ foreach ($res as $row) {
+ $lookup[$row['fixedip'] ?? $row['clientip'] ?? ''] = $row['serverid'];
+ }
+ return $lookup;
+ }
+
+ public static function getServer(string $serverId)
+ {
+ return Database::queryFirst('SELECT s.serverid, IFNULL(s.fixedip, m.clientip) AS clientip
+ FROM dnbd3_server s
+ LEFT JOIN machine m ON (s.machineuuid = m.machineuuid)
+ WHERE s.serverid = :id', ['id' => $serverId]);
+ }
}
diff --git a/modules-available/dnbd3/inc/dnbd3rpc.inc.php b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
index 6e7480c0..f6bbf0ca 100644
--- a/modules-available/dnbd3/inc/dnbd3rpc.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
@@ -2,26 +2,25 @@
class Dnbd3Rpc {
- const QUERY_UNREACHABLE = 1;
- const QUERY_NOT_200 = 2;
- const QUERY_NOT_JSON = 3;
+ const ERROR_UNREACHABLE = 1;
+ const ERROR_NOT_200 = 2;
+ const ERROR_NOT_JSON = 3;
- private static function translateServer($server)
+ const QUERY_STATS = 'stats';
+ const QUERY_CLIENTS = 'clients';
+ const QUERY_IMAGES = 'images';
+ const QUERY_SPACE = 'space';
+ const QUERY_CONFIG = 'config';
+ const QUERY_ALTSERVERS = 'altservers';
+
+ private static function translateServer(string $server): string
{
// Special case - local server
if ($server === '<self>') {
$server = '127.0.0.1:5003';
- } elseif (($out = Dnbd3Util::matchAddress($server))) {
- if (isset($out['v4'])) {
- $server = $out['v4'];
- } else {
- $server = '[' . $out['v6'] . ']';
- }
- if (isset($out['port'])) {
- $server .= $out['port'];
- } else {
- $server .= ':5003';
- }
+ } elseif (($out = Dnbd3Util::matchAddress($server)) !== false) {
+ $server = $out['v4'] ?? '[' . $out['v6'] . ']';
+ $server .= $out['port'] ?? ':5003';
}
return $server;
}
@@ -30,44 +29,24 @@ class Dnbd3Rpc {
* Query given DNBD3 server for status information.
*
* @param string $server server address
- * @param bool $stats include general stats
- * @param bool $clients include client list
- * @param bool $images include image list
- * @param bool $diskSpace include disk space stats
- * @param bool $config get config
- * @param bool $altservers list of alt servers with status
- * @return int|array the queried data as an array, or false on error
+ * @param array $queryOptions Options to query, self::QUERY_*
+ * @return int|array the queried data as an array, or error code (self::ERROR_*) on error
*/
- public static function query($server, $stats, $clients, $images, $diskSpace = false, $config = false, $altservers = false)
+ public static function query(string $server, array $queryOptions)
{
$server = self::translateServer($server);
- $url = 'http://' . $server . '/query?';
- if ($stats) {
- $url .= 'q=stats&';
- }
- if ($clients) {
- $url .= 'q=clients&';
- }
- if ($images) {
- $url .= 'q=images&';
- }
- if ($diskSpace) {
- $url .= 'q=space&';
- }
- if ($config) {
- $url .= 'q=config&';
- }
- if ($altservers) {
- $url .= 'q=altservers&';
+ $url = 'http://' . $server . '/query?q=version';
+ if (!empty($queryOptions)) {
+ $url .= '&q=' . implode('&q=', $queryOptions);
}
$str = Download::asString($url, 3, $code);
if ($str === false)
- return self::QUERY_UNREACHABLE;
+ return self::ERROR_UNREACHABLE;
if ($code !== 200)
- return self::QUERY_NOT_200;
+ return self::ERROR_NOT_200;
$ret = json_decode($str, true);
if (!is_array($ret))
- return self::QUERY_NOT_JSON;
+ return self::ERROR_NOT_JSON;
return $ret;
}
@@ -76,10 +55,86 @@ class Dnbd3Rpc {
$server = self::translateServer($server);
$str = Download::asString('http://' . $server . '/cachemap?id=' . $imgId, 3, $code);
if ($str === false)
- return self::QUERY_UNREACHABLE;
+ return self::ERROR_UNREACHABLE;
if ($code !== 200)
- return self::QUERY_NOT_200;
+ return self::ERROR_NOT_200;
return $str;
}
+ /**
+ * Get statistics for multiple servers at once.
+ * @param string[] $servers
+ */
+ public static function getStatsMulti(array $servers, array $queryOptions = [], int $timeout = 2): array
+ {
+ if (empty($servers))
+ return [];
+ $extra = '';
+ if (!empty($queryOptions)) {
+ $extra = '&q=' . implode('&q=', $queryOptions);
+ }
+ $active = [];
+ $mh = curl_multi_init();
+ curl_multi_setopt($mh, CURLMOPT_MAXCONNECTS, 8);
+ curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 2);
+ curl_multi_setopt($mh, CURLMOPT_MAX_TOTAL_CONNECTIONS, 8);
+ foreach ($servers as $server) {
+ $url = 'http://' . self::translateServer($server) . '/query?q=version' . $extra;
+ $res = curl_init($url);
+ if ($res === false) {
+ error_log("curl_init($url) failed");
+ continue;
+ }
+ curl_setopt_array($res, [
+ CURLOPT_CONNECTTIMEOUT => $timeout,
+ CURLOPT_TIMEOUT => $timeout,
+ CURLOPT_FOLLOWLOCATION => 0,
+ CURLOPT_ACCEPT_ENCODING => '', // Use everything libcurl supports
+ CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
+ CURLOPT_RETURNTRANSFER => 1,
+ ]);
+ $err = curl_multi_add_handle($mh, $res);
+ if ($err !== 0) {
+ error_log("curl_multi_add_handle() failed with $err");
+ curl_close($res);
+ } else {
+ $active[(int)$res] = $server;
+ }
+ }
+ // Wait
+ $running = 1;
+ $result = [];
+ $startTime = microtime(true);
+ for (;;) {
+ $ret = curl_multi_exec($mh, $running);
+ while (($info = curl_multi_info_read($mh)) !== false) {
+ if ($info['msg'] === CURLMSG_DONE) {
+ if (isset($active[(int)$info['handle']])) {
+ $server = $active[(int)$info['handle']];
+ unset($active[(int)$info['handle']]);
+ if ($info['result'] === CURLE_OK) {
+ $data = json_decode(curl_multi_getcontent($info['handle']), true);
+ if (is_array($data)) {
+ $data['ts'] = microtime(true);
+ $result[$server] = $data;
+ }
+ }
+ }
+ curl_multi_remove_handle($mh, $info['handle']);
+ curl_close($info['handle']);
+ }
+ }
+ $delay = ($startTime + $timeout) - microtime(true);
+ if ($ret !== CURLM_OK || !$running || $delay <= 0)
+ break;
+ $sret = curl_multi_select($mh, $delay);
+ if ($sret < 0) {
+ error_log("curl_multi_select returned $sret");
+ break;
+ }
+ }
+ curl_multi_close($mh);
+ return $result;
+ }
+
}
diff --git a/modules-available/dnbd3/inc/dnbd3util.inc.php b/modules-available/dnbd3/inc/dnbd3util.inc.php
index 8e355370..314c44fe 100644
--- a/modules-available/dnbd3/inc/dnbd3util.inc.php
+++ b/modules-available/dnbd3/inc/dnbd3util.inc.php
@@ -12,7 +12,7 @@ class Dnbd3Util {
$res = Database::simpleQuery('SELECT s.serverid, s.machineuuid, s.fixedip, s.lastup, s.lastdown, m.clientip
FROM dnbd3_server s
LEFT JOIN machine m USING (machineuuid)');
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
if (!empty($row['machineuuid'])) {
$allUuids[$row['machineuuid']] = true;
}
@@ -64,12 +64,12 @@ class Dnbd3Util {
// Now query them all
$NOW = time();
foreach ($servers as $server) {
- $data = Dnbd3Rpc::query($server['addr'], true, false, false, true);
- if ($data === Dnbd3Rpc::QUERY_UNREACHABLE) {
+ $data = Dnbd3Rpc::query($server['addr'], [Dnbd3Rpc::QUERY_STATS, Dnbd3Rpc::QUERY_SPACE]);
+ if ($data === Dnbd3Rpc::ERROR_UNREACHABLE) {
$error = 'No (HTTP) reply from ' . $server['addr'];
- } elseif ($data === Dnbd3Rpc::QUERY_NOT_200) {
+ } elseif ($data === Dnbd3Rpc::ERROR_NOT_200) {
$error = 'No HTTP 200 OK from ' . $server['addr'];
- } elseif ($data === Dnbd3Rpc::QUERY_NOT_JSON) {
+ } elseif ($data === Dnbd3Rpc::ERROR_NOT_JSON) {
$error = 'Reply to status query is not JSON';
} elseif (!is_array($data) || !isset($data['runId'])) {
if (is_array($data) && isset($data['errorMsg'])) {
@@ -108,11 +108,9 @@ class Dnbd3Util {
/**
* A client is booting that has runmode dnbd3 proxy - set config vars accordingly.
*
- * @param string $machineUuid
* @param string $mode always 'proxy'
- * @param string $modeData
*/
- public static function runmodeConfigHook($machineUuid, $mode, $modeData)
+ public static function runmodeConfigHook(string $machineUuid, string $mode, ?string $modeData)
{
$self = Property::getServerIp();
// Get all directly assigned locations
@@ -121,11 +119,11 @@ class Dnbd3Util {
WHERE machineuuid = :uuid',
array('uuid' => $machineUuid));
$assignedLocs = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$assignedLocs[] = $row['locationid'];
}
- $modeData = (array)json_decode($modeData, true) + self::defaultRunmodeConfig();
- if (!empty($assignedLocs) && isset($modeData['firewall']) && $modeData['firewall']) {
+ $modeData = (array)json_decode($modeData ?? '{}', true) + self::defaultRunmodeConfig();
+ if (!empty($assignedLocs) && ($modeData['firewall'] ?? false)) {
// Get all sub-locations too
$recursiveLocs = $assignedLocs;
$locations = Location::getLocationsAssoc();
@@ -138,13 +136,10 @@ class Dnbd3Util {
array('locs' => array_values($recursiveLocs)));
// Coalesce overlapping ranges
$floatIp = ip2long($self); // Float for 32bit php :/
- if (PHP_INT_SIZE === 4) {
- $floatIp = (float)sprintf('%u', $floatIp); // Float for 32bit php :/
- }
$ranges = [['startaddr' => $floatIp, 'endaddr' => $floatIp]];
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- settype($row['startaddr'], PHP_INT_SIZE === 4 ? 'float' : 'int');
- settype($row['endaddr'], PHP_INT_SIZE === 4 ? 'float' : 'int');
+ foreach ($res as $row) {
+ settype($row['startaddr'], 'int');
+ settype($row['endaddr'], 'int');
self::mergeRanges($ranges, $row);
}
// Got subnets, build whitelist
@@ -164,8 +159,8 @@ class Dnbd3Util {
$public = array();
$private = array();
$public[$self] = $self;
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $ip = $row['fixedip'] ? $row['fixedip'] : $row['clientip'];
+ foreach ($res as $row) {
+ $ip = $row['fixedip'] ?: $row['clientip'];
if ($ip === '<self>') {
continue;
}
@@ -215,13 +210,9 @@ class Dnbd3Util {
* @param int $end end address
* @return string CIDR notation
*/
- private static function range2Cidr($start, $end)
+ private static function range2Cidr(int $start, int $end): string
{
- if (PHP_INT_SIZE > 4) {
- $bin = decbin((int)$start ^ (int)$end);
- } else {
- $bin = decbin((int)(float)$start ^ (int)(float)$end);
- }
+ $bin = decbin($start ^ $end);
if ($bin === '0')
return long2ip($start);
$mask = 32 - strlen($bin);
@@ -252,18 +243,21 @@ class Dnbd3Util {
// $row['startaddr'] must lie before range start, otherwise we'd have hit the case above
$row['endaddr'] = $ranges[$key]['endaddr'];
unset($ranges[$key]);
- continue;
+ //continue;
}
}
$ranges[] = $row;
}
- public static function defaultRunmodeConfig()
+ /**
+ * @return array{bgr: bool, firewall: bool}
+ */
+ public static function defaultRunmodeConfig(): array
{
- return array(
+ return [
'bgr' => true,
'firewall' => false
- );
+ ];
}
public static function matchAddress($server)
diff --git a/modules-available/dnbd3/lang/de/config-variables.json b/modules-available/dnbd3/lang/de/config-variables.json
index 75020efc..f41cf517 100644
--- a/modules-available/dnbd3/lang/de/config-variables.json
+++ b/modules-available/dnbd3/lang/de/config-variables.json
@@ -1,4 +1,4 @@
{
- "SLX_DNBD3_MIN_GB": "Experimentell!",
- "SLX_DNBD3_MIN_GB_HASH": "Experimentell!"
+ "SLX_DNBD3_MIN_GB": "Mindestgr\u00f6\u00dfe der *ID45-Partition* in Gigabyte, damit lokales Caching aktiviert wird.\r\n\r\n*Hinweis:* Ein leerer Wert oder Werte kleiner 10 deaktivieren die Funktion.",
+ "SLX_DNBD3_MIN_GB_HASH": "Mindestgr\u00f6\u00dfe der *ID45-Partition* in Gigabyte, bei der durch lokales Caching angefragte Daten immer auf 16MB Bl\u00f6cke aufgef\u00fcllt werden (Hashblock-Verfahren). Diese Funktion belegt i.d.R. deutlich mehr freien Speicher im lokalen Cache als regul\u00e4res Caching.\r\n\r\nNur in Kombination mit *SLX_DNBD3_MIN_GB* sinnvoll."
} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/de/template-tags.json b/modules-available/dnbd3/lang/de/template-tags.json
index 532d1c32..d58c033f 100644
--- a/modules-available/dnbd3/lang/de/template-tags.json
+++ b/modules-available/dnbd3/lang/de/template-tags.json
@@ -3,7 +3,7 @@
"lang_advancedConfigDesc": "Hier handelt es sich um Optionen, die normalerweise keiner weiteren Anpassung bed\u00fcrfen, im Einzelfall jedoch die Performance verbessern k\u00f6nnen, bzw. bessere Anpassung an lokale Gegebenheiten erm\u00f6glichen. Unpassende Werte k\u00f6nnen einen Proxy-Server jedoch effektiv unbrauchbar machen. \u00c4nderungen dieser Werte erfordern ebenfalls einen Neustart des Proxies.",
"lang_advancedProxyConfig": "Erweiterte Konfiguration",
"lang_allowNfsFallback": "NFS-Fallback aktivieren",
- "lang_allowedSubnets": "Zum Zugriff freigegebene Subnets",
+ "lang_allowedSubnets": "Freigegebene Subnets",
"lang_altservers": "Uplinks",
"lang_backgroundReplication": "Replikation im Hintergrund",
"lang_backgroundReplicationInfo": "Sobald eine VM \u00fcber den Proxy angefragt wird, spiegelt der Proxy im Hintergrund den vollst\u00e4ndigen Inhalt des VM-Abbildes, nicht nur die angefragten Bl\u00f6cke.",
@@ -24,6 +24,7 @@
"lang_dnbd3Status": "DNBD3 Status",
"lang_editProxyHeading": "Proxy-Einstellungen bearbeiten",
"lang_enableDnbd3": "DNBD3 f\u00fcr VMs aktivieren",
+ "lang_enableDnbd3Hint": "Sie haben Proxy-Server angelegt, aber DNBD3 nicht f\u00fcr VMs aktiviert. Aktuell wird nur der Bootvorgang des Grundsystems \u00fcber DNBD3 durchgef\u00fchrt, die VM-Images werden weiterhin direkt vom NFS\/CIFS-Server geladen, und profitieren nicht von Lastverteilung und Failover.",
"lang_enabled": "Aktiviert",
"lang_enterIpOfServer": "Bitte geben Sie die IP-Adresse des hinzuzuf\u00fcgenden Servers ein",
"lang_externalServer": "Externer DNBD3-Server",
@@ -35,12 +36,12 @@
"lang_global": "Global",
"lang_image": "Image",
"lang_imageList": "Image-Liste",
- "lang_isProxy": "DNBD3 Proxy",
+ "lang_isProxy": "DNBD3-Proxy",
"lang_lastSeen": "Letzte Aktivit\u00e4t",
"lang_latency": "Latenz",
"lang_location": "Ort",
"lang_locations": "Orte",
- "lang_manageAccessTo": "Zugriff auf Server festlegen:",
+ "lang_manageAccessTo": "Verwendung festlegen:",
"lang_managedServer": "Automatisch konfigurierter DNBD3-Proxy",
"lang_managedServerAdd": "Automatisch konfigurierten Proxy hinzuf\u00fcgen",
"lang_managedServerHelp": "Automatisch konfigurierte DNBD3-Proxies booten wie gew\u00f6hnliche bwLehrpool-Clients via PXE \u00fcber den Satellitenserver. Sobald ein bwLehrpool-Client als DNBD3-Proxy konfiguriert wird, erh\u00e4lt er beim Booten eine gesonderte Konfiguration, sodass er fortan exklusiv als DNBD3-Proxy arbeitet, und nicht mehr als Arbeitsstation zur Verf\u00fcgung steht. Der Vorteil ist, dass die Konfiguration automatisiert erfolgt, und durch w\u00f6chentliche Reboots sichergestellt wird, dass eventuelle Updates des MiniLinux angewendet werden. In diesem Fall legen Sie bitte eine Partition mit der ID 45 auf der Festplatte des Proxy-Servers an; diese wird persistent Behandelt und im Gegensatz zur ID44-Partition nicht beim Booten formatiert. Generell sollte diese Partition so gro\u00df wie m\u00f6glich sein, abh\u00e4ngig von der Anzahl der genutzten VMs. Bei Platzmangel l\u00f6scht der Proxy automatisch die VM, die am l\u00e4ngsten nicht verwendet wurde, um neuen VMs Platz zu machen. Weitere Informationen dazu finden Sie im Wiki.",
@@ -49,6 +50,7 @@
"lang_numFails": "Fehler",
"lang_overrideIp": "Zu verwendende IP-Adresse",
"lang_overrideIpInfo": "Normalerweise wird die automatisch per DHCP zugewiesene Adresse auf dem Boot-Interface verwendet. Falls der Proxy mit weiteren Netzwerkkarten ausgestattet ist (die ebenfalls per DHCP konfiguriert werden) kann durch Angabe einer solchen Alternativadresse hier die Verwendung der entsprechenden Karte erzwungen werden.",
+ "lang_preferSatDnbd3": "Bevorzuge Sat-Server f\u00fcr initiale Verbindung",
"lang_proxyConfig": "Konfiguration",
"lang_proxyLocationText": "Hier k\u00f6nnen Sie festlegen, dass nur Clients aus bestimmten R\u00e4umen\/Orten diesen Proxy verwenden. Damit vermeiden Sie die Metrikmessung zwischen Client und Proxy, wenn aufgrund der Infrastruktur bereits bekannt ist, dass dieser Proxy nur f\u00fcr bestimmte R\u00e4ume sinnvoll ist. ",
"lang_proxyServerTHead": "Server\/Proxy",
@@ -68,6 +70,7 @@
"lang_txTotal": "Gesamt gesendet",
"lang_unusedFor": "Ungenutzt",
"lang_uplink": "Uplink",
+ "lang_uploadSpeed": "Ausgehend",
"lang_uptime": "Aktuelle Laufzeit",
"lang_wantToDelete": "Wollen Sie diesen Server wirklich entfernen? (Rebooten\/Ausschalten muss in diesem Fall manuell vorgenommen werden)"
} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/en/config-variables.json b/modules-available/dnbd3/lang/en/config-variables.json
index 726ce821..d7d4da66 100644
--- a/modules-available/dnbd3/lang/en/config-variables.json
+++ b/modules-available/dnbd3/lang/en/config-variables.json
@@ -1,4 +1,4 @@
{
- "SLX_DNBD3_MIN_GB": "Experimental",
- "SLX_DNBD3_MIN_GB_HASH": "Experimental"
+ "SLX_DNBD3_MIN_GB": "Minimum size of the *ID45 partition* in gigabytes for local caching to be enabled.\r\n\r\n*Note:* An empty value or values smaller than 10 disable the feature.",
+ "SLX_DNBD3_MIN_GB_HASH": "Minimum size of the *ID45 partition* in gigabytes, where data requested by local caching is always padded to 16MB blocks (hashblock method). This function usually occupies significantly more free memory in the local cache than regular caching. \r\n\r\nOnly useful in combination with *SLX_DNBD3_MIN_GB*."
} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/en/template-tags.json b/modules-available/dnbd3/lang/en/template-tags.json
index 483991be..890aa0c2 100644
--- a/modules-available/dnbd3/lang/en/template-tags.json
+++ b/modules-available/dnbd3/lang/en/template-tags.json
@@ -24,6 +24,7 @@
"lang_dnbd3Status": "DNBD3 status",
"lang_editProxyHeading": "Edit proxy settings",
"lang_enableDnbd3": "Enable DNBD3 for VMs",
+ "lang_enableDnbd3Hint": "You have created proxy servers, but did not enable DNBD3 for VMs. Currently, only the boot process of the client is done via DNBD3, the VM images are still loaded directly from the NFS\/CIFS server and do not benefit from load balancing and failover.",
"lang_enabled": "Enabled",
"lang_enterIpOfServer": "Please enter the ip address ot the server",
"lang_externalServer": "External DNBD3-Server",
@@ -35,12 +36,12 @@
"lang_global": "Global",
"lang_image": "Image",
"lang_imageList": "Image list",
- "lang_isProxy": "DNBD3 proxy",
+ "lang_isProxy": "DNBD3-Proxy",
"lang_lastSeen": "Last seen",
"lang_latency": "Latency",
"lang_location": "Location",
"lang_locations": "Locations",
- "lang_manageAccessTo": "Manage access to server:",
+ "lang_manageAccessTo": "Manage usage of server:",
"lang_managedServer": "Automatically configured DNBD3-Proxy",
"lang_managedServerAdd": "Add automatically configured proxy",
"lang_managedServerHelp": "Automatically configured DNBD3-Proxies will boot like normal bwLehrpool-Clients over PXE and the satellite server. If a client is configured as proxy it will boot with a different configuration and acts exclusively as proxy. The client can therefore not be used as a normal working station.\r\nThe advantage is that you don't need to install or configure anything else. The client will reboot every week to get possible updates ot the minilinux.\r\nIf you want to use this feature, please create a partition with ID 45 on the local hard disk of the proxy server. In contrast to the ID 44 partition which is formatted after every reboot, this partition is persistent. As a rule of thumb the partition should be as big as possible. If there is no space left the proxy will delete the VM which hasn't be used for the longest time. More information in the wiki.",
@@ -49,6 +50,7 @@
"lang_numFails": "Errors",
"lang_overrideIp": "IP address to use",
"lang_overrideIpInfo": "Usually the address that the DHCP server assigns to the boot interface of the proxy will be used. If the proxy has multiple interfaces (that also get an address assigned via DHCP) you can specify that address here to enforce their usage instead.",
+ "lang_preferSatDnbd3": "Prefer satellite server for initial connection",
"lang_proxyConfig": "Configuration",
"lang_proxyLocationText": "Here you can restrict the usage of this proxy to certain locations. This can be useful if the usage is only reasonable from some locations. That may be because of the network infrastructure.",
"lang_proxyServerTHead": "Server\/Proxy",
@@ -68,6 +70,7 @@
"lang_txTotal": "Total sent",
"lang_unusedFor": "Unused",
"lang_uplink": "Uplink",
+ "lang_uploadSpeed": "Egress",
"lang_uptime": "Uptime",
"lang_wantToDelete": "Do you really want to delete this server? (Reboot\/Shutdown has to be done manually)"
} \ No newline at end of file
diff --git a/modules-available/dnbd3/page.inc.php b/modules-available/dnbd3/page.inc.php
index d0842c23..6b0df8e4 100644
--- a/modules-available/dnbd3/page.inc.php
+++ b/modules-available/dnbd3/page.inc.php
@@ -25,7 +25,7 @@ class Page_Dnbd3 extends Page
} elseif ($action === 'savelocations') {
$this->saveServerLocations();
} elseif ($action === 'toggle-usage') {
- $this->toggleUsage();
+ $this->saveGenericSettings();
}
if (Request::isPost()) {
Util::redirect('?do=dnbd3');
@@ -34,7 +34,7 @@ class Page_Dnbd3 extends Page
private function editServer()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
if (!isset($server['machineuuid'])) {
Message::addError('not-automatic-server', $server['ip']);
return;
@@ -45,12 +45,15 @@ class Page_Dnbd3 extends Page
$overrideIp = false;
$sip = Request::post('fixedip', null, 'string');
if (empty($sip)) {
+ // Reset IP override
$overrideIp = null;
- } elseif ($server['fixedip'] !== $overrideIp) {
+ } elseif ($server['fixedip'] !== $sip) {
+ // IP override is set and different from current value
if (Dnbd3Util::matchAddress($sip) === false) {
Message::addError('invalid-ip', $sip);
return;
}
+ // Dupcheck
$res = Database::queryFirst('SELECT serverid FROM dnbd3_server s
LEFT JOIN machine m USING (machineuuid)
WHERE s.fixedip = :ip OR m.clientip = :ip', ['ip' => $sip]);
@@ -68,7 +71,7 @@ class Page_Dnbd3 extends Page
}
$advancedSettings = [];
foreach (Request::post('extra', [], 'array') as $name => $value) {
- $value = preg_replace('/[^0-9KMGTmhdBb]/', '', $value);
+ $value = preg_replace('/[^0-9KMGTmhdBbtruefals]/', '', $value);
if ($value === '')
continue;
$advancedSettings[$name] = $value;
@@ -77,18 +80,20 @@ class Page_Dnbd3 extends Page
json_encode(compact('bgr', 'firewall', 'advancedSettings')), false);
}
- private function toggleUsage()
+ private function saveGenericSettings()
{
User::assertPermission('toggle-usage');
$enabled = Request::post('enabled', false, 'bool');
$nfs = Request::post('with-nfs', false, 'bool');
+ $preferLocal = Request::post('prefer-local', false, 'bool');
Dnbd3::setEnabled($enabled);
Dnbd3::setNfsFallback($nfs);
+ Dnbd3::setPreferLocal($preferLocal);
}
private function saveServerLocations()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
$this->assertPermission($server);
$locids = Request::post('location', [], 'array');
if (empty($locids)) {
@@ -129,7 +134,7 @@ class Page_Dnbd3 extends Page
private function deleteServer()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
$this->assertPermission($server);
if ($server['fixedip'] === '<self>')
return;
@@ -174,7 +179,7 @@ class Page_Dnbd3 extends Page
$NOW = time();
$externalAllowed = User::hasPermission('configure.external');
$locsRunmode = User::getAllowedLocations('configure.proxy');
- while ($server = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $server) {
if (!is_null($server['machineuuid'])) {
// Auto proxy
if (!isset($dynClients[$server['machineuuid']])) {
@@ -208,7 +213,7 @@ class Page_Dnbd3 extends Page
$server['diskUsePercent'] = 0;
}
$server['self'] = ($server['fixedip'] === '<self>');
- if (isset($server['clientip']) && !is_null($server['clientip'])) {
+ if (isset($server['clientip'])) {
if ($NOW - $server['lastseen'] > 360) {
$server['slxDown'] = true;
} else {
@@ -251,7 +256,9 @@ class Page_Dnbd3 extends Page
'enabled' => Dnbd3::isEnabled(),
'enabled_checked_s' => Dnbd3::isEnabled() ? 'checked' : '',
'nfs_checked_s' => Dnbd3::hasNfsFallback() ? 'checked' : '',
- 'rebootcontrol' => Module::isAvailable('rebootcontrol', false)
+ 'local_checked_s' => Dnbd3::preferLocal() ? 'checked' : '',
+ 'rebootcontrol' => Module::isAvailable('rebootcontrol', false),
+ 'show_enable_warning' => count($servers) > 1 && !Dnbd3::isEnabled(),
);
Permission::addGlobalTags($data['perms'], null, ['view.details', 'refresh', 'toggle-usage', 'configure.proxy', 'configure.external']);
Render::addTemplate('page-serverlist', $data);
@@ -261,9 +268,10 @@ class Page_Dnbd3 extends Page
{
User::assertPermission('view.details');
Module::isAvailable('js_stupidtable');
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
Render::addTemplate('page-proxy-header', $server);
- $stats = Dnbd3Rpc::query($server['ip'], true, true, true, true, true, true);
+ $stats = Dnbd3Rpc::query($server['ip'], [Dnbd3Rpc::QUERY_STATS, Dnbd3Rpc::QUERY_CLIENTS,
+ Dnbd3Rpc::QUERY_IMAGES, Dnbd3Rpc::QUERY_SPACE, Dnbd3Rpc::QUERY_CONFIG, Dnbd3Rpc::QUERY_ALTSERVERS]);
if (!is_array($stats) || !isset($stats['runId'])) {
Message::addError('server-unreachable');
return;
@@ -354,8 +362,9 @@ class Page_Dnbd3 extends Page
$loc['clientCount'] = 0;
$loc['recCount'] = 0;
}
+ unset($loc);
$showLocs = false;
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
settype($row['locationid'], 'int');
$loc =& $locCount[$row['locationid']];
$loc['clientCount'] = $row['cnt'];
@@ -407,7 +416,7 @@ class Page_Dnbd3 extends Page
private function showServerLocationEdit()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
$this->assertPermission($server);
// Get selected ones
$res = Database::simpleQuery('SELECT locationid FROM dnbd3_server_x_location WHERE serverid = :serverid',
@@ -433,11 +442,9 @@ class Page_Dnbd3 extends Page
Render::addTemplate('page-server-locations', $server);
}
- private function getServerById($serverId = false)
+ private function getServerFromQuery(): array
{
- if ($serverId === false) {
- $serverId = Request::any('server', false, 'int');
- }
+ $serverId = Request::any('server', false, 'int');
if ($serverId === false) {
if (AJAX)
die('Missing parameter');
@@ -492,6 +499,8 @@ class Page_Dnbd3 extends Page
$this->ajaxReboot();
} elseif ($action === 'cachemap') {
$this->ajaxCacheMap();
+ } elseif ($action === 'stats') {
+ $this->ajaxStats();
} else {
die($action . '???');
}
@@ -513,12 +522,12 @@ class Page_Dnbd3 extends Page
if ($res !== false)
die('{"error": "Server with this IP already exists", "fatal": true}');
// Query
- $reply = Dnbd3Rpc::query($ip,true, false, false, true);
- if ($reply === Dnbd3Rpc::QUERY_UNREACHABLE)
+ $reply = Dnbd3Rpc::query($ip, [Dnbd3Rpc::QUERY_STATS, Dnbd3Rpc::QUERY_SPACE]);
+ if ($reply === Dnbd3Rpc::ERROR_UNREACHABLE)
die('{"error": "Could not reach server"}');
- if ($reply === Dnbd3Rpc::QUERY_NOT_200)
+ if ($reply === Dnbd3Rpc::ERROR_NOT_200)
die('{"error": "Server did not reply with 200 OK"}');
- if ($reply === Dnbd3Rpc::QUERY_NOT_JSON)
+ if ($reply === Dnbd3Rpc::ERROR_NOT_JSON)
die('{"error": "No JSON received from server"}');
if (!is_array($reply) || !isset($reply['uptime']) || !isset($reply['clientCount']))
die('{"error": "Reply does not suggest this is a dnbd3 server"}');
@@ -527,7 +536,7 @@ class Page_Dnbd3 extends Page
private function ajaxEditServer()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
if (!isset($server['machineuuid'])) {
echo 'Not automatic server.';
return;
@@ -547,6 +556,7 @@ class Page_Dnbd3 extends Page
'dnbd3.bgrMinClients',
'dnbd3.bgrWindowSize',
'dnbd3.autoFreeDiskSpaceDelay',
+ 'dnbd3.sparseFiles',
'limits.maxClients',
'limits.maxImages',
'limits.maxPayload',
@@ -558,7 +568,7 @@ class Page_Dnbd3 extends Page
private function ajaxReboot()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
if (!isset($server['machineuuid'])) {
die('Not automatic server.');
}
@@ -597,18 +607,18 @@ class Page_Dnbd3 extends Page
private function ajaxCacheMap()
{
- $server = $this->getServerById();
+ $server = $this->getServerFromQuery();
$imgId = Request::any('id', 0, 'int');
if ($imgId <= 0) {
Header('HTTP/1.1 400 Bad Request');
die('Invalid/no image id');
}
$data = Dnbd3Rpc::getCacheMap($server['ip'], $imgId);
- if ($data === Dnbd3Rpc::QUERY_UNREACHABLE) {
+ if ($data === Dnbd3Rpc::ERROR_UNREACHABLE) {
Header('HTTP/1.1 504 Gateway Timeout');
die('Proxy not reachable');
}
- if ($data === Dnbd3Rpc::QUERY_NOT_200) {
+ if ($data === Dnbd3Rpc::ERROR_NOT_200) {
Header('HTTP/1.1 503 Service Unavailable');
die("Proxy didn't reply with 200 OK");
}
@@ -616,30 +626,16 @@ class Page_Dnbd3 extends Page
die($data);
}
- private function genChunk($acc)
+ private function ajaxStats()
{
- static $last = -1;
- static $count = 0;
- if ($acc !== false) {
- if ($acc > 15) {
- $acc = 15;
- }
- $acc = round($acc);
- if ($last === $acc) {
- $count++;
- return '';
- }
- }
- if ($last !== -1) {
- if ($count === 1)
- return '<b style="background:#0' . dechex($acc) . '0"></b>';
- $line = '<b style="background:#0' . dechex($last) . '0;flex-grow:' . $count . '"></b>';
- } else {
- $line = '';
+ $lookup = Dnbd3::getActiveServers();
+ $result = Dnbd3Rpc::getStatsMulti(array_keys($lookup), [Dnbd3Rpc::QUERY_STATS]);
+ $return = [];
+ foreach ($result as $ip => $data) {
+ $return[$lookup[$ip]] = $data;
}
- $last = $acc;
- $count = 1;
- return $line;
+ Header('Content-Type: application/json; charset=utf-8');
+ die(json_encode($return));
}
}
diff --git a/modules-available/dnbd3/templates/fragment-server-settings.html b/modules-available/dnbd3/templates/fragment-server-settings.html
index 316b883c..794c5fd9 100644
--- a/modules-available/dnbd3/templates/fragment-server-settings.html
+++ b/modules-available/dnbd3/templates/fragment-server-settings.html
@@ -33,7 +33,7 @@
<label for="ex-{{name}}">{{name}}</label>
</div>
<div class="col-sm-4">
- <input id="ex-{{name}}" class="form-control" type="text" pattern="[0-9]*[KMGTmhd]?[Bb]?" value="{{value}}"
+ <input id="ex-{{name}}" class="form-control" type="text" pattern="[0-9KMGTmhdtruefals]*" value="{{value}}"
name="extra[{{name}}]">
</div>
</div>
diff --git a/modules-available/dnbd3/templates/page-proxy-images.html b/modules-available/dnbd3/templates/page-proxy-images.html
index e7fc2b3c..0dd06801 100644
--- a/modules-available/dnbd3/templates/page-proxy-images.html
+++ b/modules-available/dnbd3/templates/page-proxy-images.html
@@ -4,8 +4,8 @@
<thead>
<tr>
<th data-sort="string">{{lang_image}}</th>
- <th class="text-right slx-smallcol" data-sort="int">{{lang_clients}}</th>
- <th class="text-right slx-smallcol" data-sort="int">{{lang_size}}</th>
+ <th class="text-right slx-smallcol" data-sort="int" data-sort-default="desc">{{lang_clients}}</th>
+ <th class="text-right slx-smallcol" data-sort="int" data-sort-default="desc">{{lang_size}}</th>
<th class="text-right slx-smallcol" data-sort="int">{{lang_complete}}</th>
<th class="text-right slx-smallcol" data-sort="int">{{lang_unusedFor}}</th>
<th class="slx-smallcol" data-sort="string">{{lang_uplink}}</th>
diff --git a/modules-available/dnbd3/templates/page-proxy-stats.html b/modules-available/dnbd3/templates/page-proxy-stats.html
index 9c3a4a84..4bc411d1 100644
--- a/modules-available/dnbd3/templates/page-proxy-stats.html
+++ b/modules-available/dnbd3/templates/page-proxy-stats.html
@@ -1,7 +1,10 @@
<div class="panel panel-default">
<div class="panel-body">
- <div class="pull-right">
- {{lang_uptime}}: <b>{{uptime_s}}</b>
+ <div class="pull-right text-right">
+ {{#version}}
+ <div>{{version}}</div>
+ {{/version}}
+ <div>{{lang_uptime}}: <b>{{uptime_s}}</b></div>
</div>
<div>
{{lang_sessionTx}}: <b>{{bytesSent_s}}</b>
diff --git a/modules-available/dnbd3/templates/page-serverlist.html b/modules-available/dnbd3/templates/page-serverlist.html
index cdbd0789..bcb0d766 100644
--- a/modules-available/dnbd3/templates/page-serverlist.html
+++ b/modules-available/dnbd3/templates/page-serverlist.html
@@ -1,6 +1,12 @@
<h1>{{lang_dnbd3Management}}</h1>
<p><i>{{lang_dnbd3IntroText}}</i></p>
+<style>
+ .shd { text-shadow: #fff 1px 1px 2px; border:1px solid #ddd; min-width:100px; }
+ .shd:empty { display: none; }
+ #speed-graph { width: 100%; height: 100px; margin: 3px; border-radius: 3px; }
+</style>
+
<div class="panel panel-default">
<div class="panel-heading">
{{lang_dnbd3Status}}:
@@ -10,7 +16,7 @@
</b>
– <a href="#" data-toggle="collapse" data-target="#toggle-div">{{lang_changeDnbd3Status}}</a>
</div>
- <div class="panel-collapse collapse" id="toggle-div">
+ <div class="panel-collapse {{^show_enable_warning}}collapse{{/show_enable_warning}}" id="toggle-div">
<div class="panel-body">
<form method="post" action="?do=dnbd3">
<input type="hidden" name="token" value="{{token}}">
@@ -18,10 +24,19 @@
<input id="enable-dnbd3" type="checkbox" name="enabled" {{enabled_checked_s}} {{perms.toggle-usage.disabled}}>
<label for="enable-dnbd3">{{lang_enableDnbd3}}</label>
</div>
+ {{#show_enable_warning}}
+ <div class="text-warning">
+ {{lang_enableDnbd3Hint}}
+ </div>
+ {{/show_enable_warning}}
<div class="checkbox">
<input id="allow-nfs" type="checkbox" name="with-nfs" {{nfs_checked_s}} {{perms.toggle-usage.disabled}}>
<label for="allow-nfs">{{lang_allowNfsFallback}}</label>
</div>
+ <div class="checkbox">
+ <input id="prefer-local" type="checkbox" name="prefer-local" {{local_checked_s}} {{perms.toggle-usage.disabled}}>
+ <label for="prefer-local">{{lang_preferSatDnbd3}}</label>
+ </div>
<button type="submit" name="action" value="toggle-usage" class="btn btn-success" {{perms.toggle-usage.disabled}}>
<span class="glyphicon glyphicon-floppy-disk"></span>
{{lang_save}}
@@ -53,6 +68,7 @@
<th>{{lang_proxyServerTHead}}</th>
<th class="text-right">{{lang_storageSize}}</th>
<th class="text-right">{{lang_clientCount}}</th>
+ <th style="min-width:116px">{{lang_uploadSpeed}}</th>
<th class="text-right">{{lang_lastSeen}}</th>
<th class="text-right">{{lang_uptime}}</th>
<th class="text-right">{{lang_txTotal}}</th>
@@ -96,8 +112,8 @@
<div class="small">{{hostname}}</div>
</td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{disktotal}}">
- <div style="border:1px solid #ddd;background:linear-gradient(to right, #f85 {{diskUsePercent}}%, transparent {{diskUsePercent}}%)"
- class="text-center text-nowrap"
+ <div style="background:linear-gradient(to right, #f85 {{diskUsePercent}}%, transparent {{diskUsePercent}}%)"
+ class="text-center text-nowrap shd"
title="{{lang_diskFree}}: {{diskfree_s}}">
{{disktotal_s}}
</div>
@@ -107,9 +123,12 @@
</div>
{{/errormsg}}
</td>
- <td data-sort="int" data-sort-default="desc" class="text-right">
+ <td data-sort="int" data-sort-default="desc" class="text-right text-nowrap" id="clientcount-{{serverid}}">
{{clientcount}}
</td>
+ <td data-sort="int" data-sort-default="desc" class="text-right text-nowrap">
+ <div id="upspeed-{{serverid}}" class="text-center text-nowrap shd"></div>
+ </td>
<td data-sort="int" data-sort-default="desc" data-sort-value="{{dnbd3lastseen}}" class="text-right text-nowrap">
{{dnbd3lastseen_s}}
</td>
@@ -302,6 +321,8 @@
</div>
<div class="clearfix"></div>
+<div class="slx-space"></div>
+<canvas id="speed-graph"></canvas>
<script type="application/javascript"><!--
document.addEventListener('DOMContentLoaded', function () {
@@ -421,6 +442,133 @@ document.addEventListener('DOMContentLoaded', function () {
query();
rebootServerId = 0;
});
+ // live speed
+ var hiddenProp;
+ if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
+ hiddenProp = "hidden";
+ } else if (typeof document.msHidden !== "undefined") {
+ hiddenProp = "msHidden";
+ } else if (typeof document.webkitHidden !== "undefined") {
+ hiddenProp = "webkitHidden";
+ } else {
+ hiddenProp = null;
+ }
+ var formatBytes = function(bytes) {
+ if (bytes < 1024) return bytes.toFixed(0) + '\u2009B';
+ if (bytes < 1048576) return (bytes / 1024).toFixed(0) + '\u2009KiB';
+ if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + '\u2009MiB';
+ if (bytes < 1099511627776) return (bytes / 1073741824).toFixed(2) + '\u2009GiB';
+ return (bytes / 1099511627776).toFixed(2) + '\u2009TiB';
+ };
+ var calcBackgroundStyle = function(speed) {
+ const colors = ['#eee', '#cfc', '#6f6', '#bc3', '#f00', '#f88'];
+ const limits = [1048576, 10485760, 104857600, 1073741824, 10737418240];
+ for (var i = 0; i < 4; ++i) {
+ if (speed < limits[i]) break;
+ }
+ const percent = Math.round(Math.max(0, Math.min(100, speed / limits[i] * 100)));
+ return { background: 'linear-gradient(90deg, ' + colors[i+1] + ' ' + percent + '%, ' + colors[i] + ' ' + percent + '%)' };
+ };
+ var lastSpeedList = {};
+ var history = [];
+ var inactiveCount = 0;
+ var updateSpeed = function() {
+ if (hiddenProp && document[hiddenProp]) {
+ if (++inactiveCount > 300)
+ return;
+ } else {
+ if (inactiveCount > 300) {
+ history.push(-1);
+ }
+ inactiveCount = 0;
+ }
+ $.ajax('?do=dnbd3&action=stats').done(function(elist) {
+ var speedSum = 0;
+ for (var k in elist) {
+ var e = elist[k];
+ if (lastSpeedList[k]) {
+ var lastSpeed = lastSpeedList[k];
+ if (lastSpeed['ts'] < e['ts']) {
+ var $speed = $('#upspeed-' + k);
+ var s = (e['bytesSent'] - lastSpeed['bytesSent']) / (e['ts'] - lastSpeed['ts']);
+ $speed.text(formatBytes(s) + "/s").css(calcBackgroundStyle(s));
+ speedSum += s;
+ }
+ }
+ var $clients = $('#clientcount-' + k);
+ $clients.text(e['clientCount'] + e['serverCount']);
+ }
+ history.push(speedSum);
+ while (history.length > 500) history.shift();
+ for (k in lastSpeedList) {
+ if (!elist[k]) {
+ $('#upspeed-' + k).text('???').css('background', '#aaa');
+ $('#clientcount-' + k).text('-');
+ }
+ }
+ lastSpeedList = elist;
+ updateGraph();
+ });
+ };
+ updateSpeed();
+ setInterval(updateSpeed, 2500);
+ var graph = document.getElementById('speed-graph');
+ var updateGraph = function() {
+ var i;
+ var gctx = graph.getContext('2d');
+ graph.width = Math.floor(graph.clientWidth / graph.clientHeight * 100);
+ graph.height = 100;
+ gctx.fillStyle = '#eee';
+ gctx.fillRect(0, 0, graph.width, graph.height);
+ var part = history.slice(-Math.floor(graph.width / 10));
+ var max = 1;
+ var peakIdx = -1;
+ var peakCount = 0;
+ var peakList = {};
+ for (i = 0; i < part.length; ++i) {
+ if (part[i] > max) max = part[i];
+ }
+ for (i = 0; i < part.length; ++i) {
+ if (peakIdx === -1 || part[i] > part[peakIdx]) {
+ peakIdx = i;
+ peakCount = 0;
+ } else if ((part[peakIdx] - part[i]) / max > 0.1) {
+ if (peakCount > 3) {
+ peakList[part.length - peakIdx - 1] = 1;
+ peakIdx = -1;
+ } else {
+ peakCount++;
+ }
+ } else {
+ peakIdx = i;
+ }
+ }
+ if (peakCount > 1) {
+ peakList[part.length - peakIdx - 1] = 1;
+ }
+ const BAR_COLOR = '#999';
+ part.reverse();
+ gctx.fillStyle = BAR_COLOR;
+ gctx.font = "9pt Arial";
+ gctx.textBaseline = 'top';
+ for (i = 0; i < part.length; ++i) {
+ var x = graph.width - i*10;
+ if (part[i] === -1) {
+ gctx.fillStyle = '#bbb';
+ gctx.fillRect(x - 5, 0, 1, 100);
+ gctx.fillStyle = BAR_COLOR;
+ } else {
+ var v = Math.round((1 - part[i] / max) * 100);
+ gctx.fillRect(x - 10, v, 9, 100);
+ if (peakList[i]) {
+ gctx.fillStyle = '#333';
+ gctx.fillText(formatBytes(part[i]) + "/s", x + 1, v);
+ gctx.fillStyle = BAR_COLOR;
+ }
+ }
+ }
+ gctx.stroke();
+ };
});
//--></script> \ No newline at end of file