summaryrefslogtreecommitdiffstats
path: root/modules-available/dnbd3
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/dnbd3')
-rw-r--r--modules-available/dnbd3/api.inc.php7
-rw-r--r--modules-available/dnbd3/baseconfig/getconfig.inc.php53
-rw-r--r--modules-available/dnbd3/config.json4
-rw-r--r--modules-available/dnbd3/hooks/cron.inc.php3
-rw-r--r--modules-available/dnbd3/hooks/main-warning.inc.php25
-rw-r--r--modules-available/dnbd3/hooks/runmode/config.json6
-rw-r--r--modules-available/dnbd3/inc/dnbd3.inc.php38
-rw-r--r--modules-available/dnbd3/inc/dnbd3rpc.inc.php58
-rw-r--r--modules-available/dnbd3/inc/dnbd3util.inc.php202
-rw-r--r--modules-available/dnbd3/lang/de/messages.json11
-rw-r--r--modules-available/dnbd3/lang/de/module.json4
-rw-r--r--modules-available/dnbd3/lang/de/template-tags.json57
-rw-r--r--modules-available/dnbd3/lang/en/messages.json3
-rw-r--r--modules-available/dnbd3/lang/en/module.json4
-rw-r--r--modules-available/dnbd3/lang/pt/module.json3
-rw-r--r--modules-available/dnbd3/page.inc.php417
-rw-r--r--modules-available/dnbd3/templates/fragment-server-settings.html14
-rw-r--r--modules-available/dnbd3/templates/page-proxy-altservers.html36
-rw-r--r--modules-available/dnbd3/templates/page-proxy-clients.html20
-rw-r--r--modules-available/dnbd3/templates/page-proxy-config.html4
-rw-r--r--modules-available/dnbd3/templates/page-proxy-header.html1
-rw-r--r--modules-available/dnbd3/templates/page-proxy-loclist.html27
-rw-r--r--modules-available/dnbd3/templates/page-proxy-stats.html9
-rw-r--r--modules-available/dnbd3/templates/page-server-locations.html96
-rw-r--r--modules-available/dnbd3/templates/page-serverlist.html404
25 files changed, 1506 insertions, 0 deletions
diff --git a/modules-available/dnbd3/api.inc.php b/modules-available/dnbd3/api.inc.php
new file mode 100644
index 00000000..68f8007c
--- /dev/null
+++ b/modules-available/dnbd3/api.inc.php
@@ -0,0 +1,7 @@
+<?php
+
+if (Dnbd3::isEnabled()) {
+ die('YES');
+} else {
+ die('NO');
+}
diff --git a/modules-available/dnbd3/baseconfig/getconfig.inc.php b/modules-available/dnbd3/baseconfig/getconfig.inc.php
new file mode 100644
index 00000000..e0389c71
--- /dev/null
+++ b/modules-available/dnbd3/baseconfig/getconfig.inc.php
@@ -0,0 +1,53 @@
+<?php
+
+if (!Dnbd3::isEnabled()) return;
+
+if (!Dnbd3::hasNfsFallback()) {
+ ConfigHolder::add("SLX_VM_NFS", false, 1000);
+ ConfigHolder::add("SLX_VM_NFS_USER", false, 1000);
+ ConfigHolder::add("SLX_VM_NFS_PASSWD", false, 1000);
+}
+
+// Locations from closest to furthest (order)
+$locations = ConfigHolder::get('SLX_LOCATIONS');
+if ($locations === false) {
+ $locationIds = [0];
+} else {
+ $locationIds = explode(' ', $locations);
+ if (empty($locationIds)) {
+ $locationIds[] = 0;
+ }
+}
+
+$res = Database::simpleQuery('SELECT s.fixedip, m.clientip, sxl.locationid FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ LEFT JOIN dnbd3_server_x_location sxl USING (serverid)
+ WHERE sxl.locationid IS NULL OR sxl.locationid IN (:lids)', array('lids' => $locationIds));
+// Lookup of priority - first index (0) will be closest location in chain
+$locationsAssoc = array_flip($locationIds);
+$servers = array();
+while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['fixedip'] === '<self>') {
+ $row['fixedip'] = Property::getServerIp();
+ $defPrio = 2000;
+ } else {
+ $defPrio = 1000;
+ }
+ $ip = $row['clientip'] ? $row['clientip'] : $row['fixedip'];
+ if ($defPrio === 1000 && is_null($row['locationid'])) {
+ $serverLoc = Location::getFromIp($ip);
+ if ($serverLoc !== false) {
+ $row['locationid'] = $serverLoc;
+ }
+ }
+ $old = isset($servers[$ip]) ? $servers[$ip] : $defPrio;
+ if (is_null($row['locationid']) || !isset($locationsAssoc[$row['locationid']])) {
+ $servers[$ip] = min($defPrio, $old) . '.' . mt_rand();
+ } else {
+ $servers[$ip] = min($locationsAssoc[$row['locationid']], $old) . '.' . mt_rand();
+ }
+}
+
+asort($servers, SORT_NUMERIC | SORT_ASC);
+ConfigHolder::add('SLX_DNBD3_SERVERS', implode(' ', array_keys($servers)));
+ConfigHolder::add('SLX_VM_DNBD3', 'yes');
diff --git a/modules-available/dnbd3/config.json b/modules-available/dnbd3/config.json
new file mode 100644
index 00000000..f84a4170
--- /dev/null
+++ b/modules-available/dnbd3/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"main.settings-server",
+ "dependencies":["locations","runmode"]
+}
diff --git a/modules-available/dnbd3/hooks/cron.inc.php b/modules-available/dnbd3/hooks/cron.inc.php
new file mode 100644
index 00000000..3da4cae4
--- /dev/null
+++ b/modules-available/dnbd3/hooks/cron.inc.php
@@ -0,0 +1,3 @@
+<?php
+
+Dnbd3Util::updateServerStatus();
diff --git a/modules-available/dnbd3/hooks/main-warning.inc.php b/modules-available/dnbd3/hooks/main-warning.inc.php
new file mode 100644
index 00000000..258d03d0
--- /dev/null
+++ b/modules-available/dnbd3/hooks/main-warning.inc.php
@@ -0,0 +1,25 @@
+<?php
+
+if (Dnbd3::isEnabled()) {
+ $res = Database::simpleQuery('SELECT s.fixedip, s.lastseen AS dnbd3lastseen, s.errormsg, m.clientip, m.hostname
+ FROM dnbd3_server s
+ 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>';
+ $lastSeen = date('d.m.Y H:i', $row['dnbd3lastseen']);
+ if ($row['fixedip'] === '<self>') {
+ Message::addError('dnbd3.main-dnbd3-unreachable', true, $error, $lastSeen);
+ continue;
+ }
+ if (!is_null($row['fixedip'])) {
+ $ip = $row['fixedip'];
+ } else {
+ $ip = $row['clientip'] . '/' . $row['hostname'];
+ }
+ Message::addWarning('dnbd3.dnbd3-proxy-unreachable', true, $ip, $error, $lastSeen);
+ }
+
+ unset($res);
+}
diff --git a/modules-available/dnbd3/hooks/runmode/config.json b/modules-available/dnbd3/hooks/runmode/config.json
new file mode 100644
index 00000000..095cb42f
--- /dev/null
+++ b/modules-available/dnbd3/hooks/runmode/config.json
@@ -0,0 +1,6 @@
+{
+ "isClient": false,
+ "configHook": "Dnbd3Util::runmodeConfigHook",
+ "noSysconfig": true,
+ "systemdDefaultTarget": "dnbd3-proxy"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/inc/dnbd3.inc.php b/modules-available/dnbd3/inc/dnbd3.inc.php
new file mode 100644
index 00000000..9607c544
--- /dev/null
+++ b/modules-available/dnbd3/inc/dnbd3.inc.php
@@ -0,0 +1,38 @@
+<?php
+
+class Dnbd3 {
+
+ const PROP_ENABLED = 'dnbd3.enabled';
+ const PROP_NFS_FALLBACK = 'dnbd3.nfs-fallback';
+
+ public static function isEnabled()
+ {
+ return Property::get(self::PROP_ENABLED, 0) ? true : false;
+ }
+
+ public static function setEnabled($bool)
+ {
+ Property::set(self::PROP_ENABLED, $bool ? 1 : 0);
+ $task = Taskmanager::submit('Systemctl', array(
+ 'operation' => ($bool ? 'start' : 'stop'),
+ 'service' => 'dnbd3-server'
+ ));
+ return $task;
+ }
+
+ public static function hasNfsFallback()
+ {
+ return Property::get(self::PROP_NFS_FALLBACK, 0) ? true : false;
+ }
+
+ public static function setNfsFallback($bool)
+ {
+ Property::set(self::PROP_NFS_FALLBACK, $bool ? 1 : 0);
+ }
+
+ public static function getLocalStatus()
+ {
+
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/inc/dnbd3rpc.inc.php b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
new file mode 100644
index 00000000..cdcda508
--- /dev/null
+++ b/modules-available/dnbd3/inc/dnbd3rpc.inc.php
@@ -0,0 +1,58 @@
+<?php
+
+class Dnbd3Rpc {
+
+ const QUERY_UNREACHABLE = 1;
+ const QUERY_NOT_200 = 2;
+ const QUERY_NOT_JSON = 3;
+
+ /**
+ * Query given DNBD3 server for status information.
+ *
+ * @param string $server server address
+ * @param int $port server port
+ * @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
+ */
+ public static function query($server, $port, $stats, $clients, $images, $diskSpace = false, $config = false, $altservers = false)
+ {
+ // Special case - local server
+ if ($server === '<self>') {
+ $server = '127.0.0.1';
+ }
+ $url = 'http://' . $server . ':' . $port . '/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&';
+ }
+ $str = Download::asString($url, 3, $code);
+ if ($str === false)
+ return self::QUERY_UNREACHABLE;
+ if ($code !== 200)
+ return self::QUERY_NOT_200;
+ $ret = json_decode($str, true);
+ if (!is_array($ret))
+ return self::QUERY_NOT_JSON;
+ return $ret;
+ }
+
+}
diff --git a/modules-available/dnbd3/inc/dnbd3util.inc.php b/modules-available/dnbd3/inc/dnbd3util.inc.php
new file mode 100644
index 00000000..a9b9241e
--- /dev/null
+++ b/modules-available/dnbd3/inc/dnbd3util.inc.php
@@ -0,0 +1,202 @@
+<?php
+
+class Dnbd3Util {
+
+ public static function updateServerStatus()
+ {
+ $dynClients = RunMode::getForMode('dnbd3', 'proxy', false, true);
+ $satServerIp = Property::getServerIp();
+ $servers = array();
+ $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)) {
+ if (!is_null($row['clientip'])) {
+ $ip = $row['clientip'];
+ } elseif (!is_null($row['fixedip'])) {
+ $ip = $row['fixedip'];
+ } else {
+ continue; // Huh?
+ }
+ if (!is_null($row['machineuuid'])) {
+ unset($dynClients[$row['machineuuid']]);
+ if ($row['clientip'] === $satServerIp) {
+ // Lolwut, sat server is openslx client configured for proxy mode!? baleeted.
+ RunMode::setRunMode($row['machineuuid'], 'dnbd3', null, null, null);
+ continue;
+ }
+ }
+ $server = array(
+ 'serverid' => $row['serverid'],
+ 'addr' => $ip,
+ );
+ $servers[] = $server;
+ }
+ // See if any clients are in dnbd3 proxy mode but don't have a matching row in the dnbd3_server table
+ foreach ($dynClients as $client) {
+ Database::exec('INSERT IGNORE INTO dnbd3_server (machineuuid) VALUES (:machineuuid)',
+ array('machineuuid' => $client['machineuuid']));
+ // Missing from $servers now but we'll handle them in the next run, so don't bother
+ }
+ // Same for this server - we use the special fixedip '<self>' for it and need to make surecx we don't have the
+ // IP address of the server itself in the list.
+ Database::exec('DELETE FROM dnbd3_server WHERE fixedip = :serverip', array('serverip' => $satServerIp));
+ Database::exec("INSERT IGNORE INTO dnbd3_server (fixedip) VALUES ('<self>')");
+ // Delete orphaned entires with machineuuid from dnbd3_server where we don't have a runmode entry
+ Database::exec('DELETE s FROM dnbd3_server s
+ LEFT JOIN runmode r USING (machineuuid)
+ WHERE s.machineuuid IS NOT NULL AND r.module IS NULL');
+ // Now query them all
+ $NOW = time();
+ foreach ($servers as $server) {
+ $port = 5003;
+ $data = Dnbd3Rpc::query($server['addr'], $port, true, false, false, true);
+ if ($data === Dnbd3Rpc::QUERY_UNREACHABLE) {
+ $error = 'No (HTTP) reply on port ' . $port;
+ } elseif ($data === Dnbd3Rpc::QUERY_NOT_200) {
+ $error = 'No HTTP 200 OK on port ' . $port;
+ } elseif ($data === Dnbd3Rpc::QUERY_NOT_JSON) {
+ $error = 'Reply to status query is not JSON';
+ } elseif (!is_array($data) || !isset($data['runId'])) {
+ if (is_array($data) && isset($data['errorMsg'])) {
+ $error = 'DNBD3: ' . $data['errorMsg'];
+ } else {
+ $error = 'Reply to status query has unexpected format';
+ }
+ } else {
+ $error = false;
+ }
+ if ($error !== false) {
+ Database::exec('UPDATE dnbd3_server SET uptime = 0, clientcount = 0, errormsg = :errormsg WHERE serverid = :serverid',
+ array('serverid' => $server['serverid'], 'errormsg' => $error));
+ continue;
+ }
+ // Seems up - since we only get absolute rx/tx values from the server, we have to prevent update race conditions
+ // and make sure the server was not restarted in the meantime (use runid and uptime for this)
+ Database::exec('UPDATE dnbd3_server SET runid = :runid, lastseen = :now, uptime = :uptime,
+ totalup = totalup + If(runid = :runid AND uptime <= :uptime, If(lastup < :up, :up - lastup, 0), If(:uptime < 1800, :up, 0)),
+ totaldown = totaldown + If(runid = :runid AND uptime <= :uptime, If(lastdown < :down, :down - lastdown, 0), If(:uptime < 1800, :up, 0)),
+ lastup = :up, lastdown = :down, clientcount = :clientcount, disktotal = :disktotal, diskfree = :diskfree, errormsg = NULL
+ WHERE serverid = :serverid', array(
+ 'runid' => $data['runId'],
+ 'now' => $NOW,
+ 'uptime' => $data['uptime'],
+ 'up' => $data['bytesSent'],
+ 'down' => $data['bytesReceived'],
+ 'clientcount' => $data['clientCount'],
+ 'serverid' => $server['serverid'],
+ 'disktotal' => $data['spaceTotal'],
+ 'diskfree' => $data['spaceFree'],
+ ));
+ }
+ }
+
+ /**
+ * 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)
+ {
+ // Get all directly assigned locations
+ $res = Database::simpleQuery('SELECT locationid FROM dnbd3_server
+ INNER JOIN dnbd3_server_x_location USING (serverid)
+ WHERE machineuuid = :uuid',
+ array('uuid' => $machineUuid));
+ $assignedLocs = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $assignedLocs[] = $row['locationid'];
+ }
+ $modeData = (array)json_decode($modeData, true) + self::defaultRunmodeConfig();
+ if (!empty($assignedLocs) && isset($modeData['firewall']) && $modeData['firewall']) {
+ // Get all sub-locations too
+ $recursiveLocs = $assignedLocs;
+ $locations = Location::getLocationsAssoc();
+ foreach ($assignedLocs as $l) {
+ if (isset($locations[$l])) {
+ $recursiveLocs = array_merge($recursiveLocs, $locations[$l]['children']);
+ }
+ }
+ $res = Database::simpleQuery('SELECT startaddr, endaddr FROM subnet WHERE locationid IN (:locs)',
+ array('locs' => array_values($recursiveLocs)));
+ // Got subnets, build whitelist
+ // TODO: Coalesce overlapping ranges
+ $opt = '';
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $opt .= ' ' . self::range2Cidr($row['startaddr'], $row['endaddr']);
+ }
+ if (!empty($opt)) {
+ ConfigHolder::add('SLX_DNBD3_WHITELIST', $opt, 1000);
+ }
+ }
+ // Send list of other proxy servers
+ $res = Database::simpleQuery('SELECT s.fixedip, m.clientip, sxl.locationid FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ LEFT JOIN dnbd3_server_x_location sxl USING (serverid)
+ WHERE s.machineuuid <> :uuid OR s.machineuuid IS NULL', array('uuid' => $machineUuid));
+ $public = array();
+ $private = array();
+ $self = Property::getServerIp();
+ $public[$self] = $self;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $ip = $row['clientip'] ? $row['clientip'] : $row['fixedip'];
+ if ($ip === '<self>') {
+ continue;
+ }
+ if (is_null($row['locationid'])) {
+ if (!array_key_exists($ip, $private)) {
+ $public[$ip] = $ip;
+ }
+ } else {
+ unset($public[$ip]);
+ $private[$ip] = $ip;
+ }
+ }
+ if (!empty($public)) {
+ shuffle($public);
+ ConfigHolder::add('SLX_DNBD3_PUBLIC', implode(' ', $public));
+ }
+ if (!empty($private)) {
+ shuffle($private);
+ ConfigHolder::add('SLX_DNBD3_PRIVATE', implode(' ', $private));
+ }
+ if (isset($modeData['bgr']) && $modeData['bgr']) {
+ // Background replication
+ ConfigHolder::add('SLX_DNBD3_BGR', '1');
+ }
+ ConfigHolder::add('SLX_ADDONS', '', 1000);
+ ConfigHolder::add('SLX_SHUTDOWN_TIMEOUT', '', 1000);
+ ConfigHolder::add('SLX_SHUTDOWN_SCHEDULE', '', 1000);
+ ConfigHolder::add('SLX_REBOOT_TIMEOUT', '', 1000);
+ ConfigHolder::add('SLX_REBOOT_SCHEDULE', '', 1000);
+ }
+
+ /**
+ * Get smallest subnet in CIDR notation that covers the given range.
+ * The subnet denoted by the CIDR notation might actually be larger
+ * than the range described by $start and $end.
+ *
+ * @param int $start start address
+ * @param int $end end address
+ * @return string CIDR notation
+ */
+ private static function range2Cidr($start, $end)
+ {
+ $bin = decbin((int)$start ^ (int)$end);
+ if ($bin === '0')
+ return long2ip($start);
+ $mask = 32 - strlen($bin);
+ return long2ip($start) . '/' . $mask;
+ }
+
+ public static function defaultRunmodeConfig()
+ {
+ return array(
+ 'bgr' => true,
+ 'firewall' => false
+ );
+ }
+
+}
diff --git a/modules-available/dnbd3/lang/de/messages.json b/modules-available/dnbd3/lang/de/messages.json
new file mode 100644
index 00000000..05b8f968
--- /dev/null
+++ b/modules-available/dnbd3/lang/de/messages.json
@@ -0,0 +1,11 @@
+{
+ "dnbd3-proxy-unreachable": "DNBD3-Proxy {{0}} ist offline seit {{2}}. ({{1}})",
+ "invalid-ipv4": "{{0}} ist keine g\u00fcltige IPv4-Adresse",
+ "main-dnbd3-unreachable": "Zentraler DNBD3-Server des Satelliten ist offline seit {{1}}. ({{0}})",
+ "not-automatic-server": "{{0}} wird nicht \u00fcber den Satelliten verwaltet",
+ "server-added": "Server {{0}} hinzugef\u00fcgt",
+ "server-already-exists": "Server {{0}} existiert bereits",
+ "server-deleted": "Server {{0}} gel\u00f6scht",
+ "server-non-existent": "Server {{0}} existiert nicht",
+ "server-unreachable": "Server nicht erreichbar"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/de/module.json b/modules-available/dnbd3/lang/de/module.json
new file mode 100644
index 00000000..cf748a49
--- /dev/null
+++ b/modules-available/dnbd3/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "DNBD3",
+ "page_title": "DNBD3-Verwaltung"
+} \ 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
new file mode 100644
index 00000000..0573a68e
--- /dev/null
+++ b/modules-available/dnbd3/lang/de/template-tags.json
@@ -0,0 +1,57 @@
+{
+ "lang_addServer": "Server hinzuf\u00fcgen",
+ "lang_allowedSubnets": "Zum Zugriff 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.",
+ "lang_bytesSent": "Gesendet",
+ "lang_changeDnbd3Status": "DNBD3 ein-\/ausschalten",
+ "lang_client": "Client",
+ "lang_clientCount": "Clients",
+ "lang_clientList": "Liste der Clients",
+ "lang_clientsByLocation": "Clients nach Raum\/Ort",
+ "lang_comment": "Kommentar",
+ "lang_count": "Anzahl",
+ "lang_disabled": "Deaktiviert",
+ "lang_diskFree": "Freier Speicher",
+ "lang_dnbd3IntroText": "DNBD3 ist ganz toll. Einfach einschalten und los gehts, zu beachten gibt es rein gar nichts!",
+ "lang_dnbd3Management": "DNBD3 Verwaltung",
+ "lang_dnbd3Status": "DNBD3 Status",
+ "lang_editProxyHeading": "Proxy-Einstellungen bearbeiten",
+ "lang_enableDnbd3": "DNBD3 aktivieren",
+ "lang_enabled": "Aktiviert",
+ "lang_enterIpOfServer": "Bitte geben Sie die IP-Adresse des hinzuzuf\u00fcgenden Servers ein",
+ "lang_externalServer": "Externer DNBD3-Server",
+ "lang_externalServerAdd": "Externen Server hinzuf\u00fcgen",
+ "lang_externalServerHelp": "Ein externer Server wird nicht \u00fcber den Satellitenserver konfiguriert und verwaltet. Das Installieren, Einrichten und ggf. Aktualisieren der DNBD3-Serversoftware muss manuell durchgef\u00fchrt werden.\r\nDies bietet mehr Flexibilit\u00e4t bei der Konfiguration und Anpassung, z.B. bei der Verwendung von RAID- oder bcache-Setups, oder wenn der DNBD3-Server auf einer Maschine laufen soll, die noch andere Services bereitstellt.\r\nWeitere Informationen dazu finden Sie im Wiki.",
+ "lang_firewallInfo": "Wird ein Proxy auf einen oder mehrere R\u00e4ume beschr\u00e4nkt, werden Clients aus anderen R\u00e4umen diesen Proxy nicht verwenden. Technisch ist der Zugriff aus anderen R\u00e4umen jedoch trotzdem noch m\u00f6glich. Mit aktivieren dieser Option wird der Zugriff aus anderen R\u00e4umen per Firewall verhindert.",
+ "lang_firewalled": "Zugriff auf zugewiesene R\u00e4ume beschr\u00e4nken",
+ "lang_flags": "Flags",
+ "lang_global": "Global",
+ "lang_lastSeen": "Letzte Aktivit\u00e4t",
+ "lang_latency": "Latenz",
+ "lang_location": "Ort",
+ "lang_locations": "Orte",
+ "lang_manageAccessTo": "Zugriff auf Server 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 Satelliten-Server. 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.\r\nDer Vorteil ist, dass die Konfiguration automatisiert erfolgt, und durch w\u00f6chentliche Reboots sichergestellt wird, dass eventuelle Updates des MiniLinux angewendet werden.\r\nIn 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.\r\nWeitere Informationen dazu finden Sie im Wiki.",
+ "lang_numFails": "Fehler",
+ "lang_proxyConfig": "Konfiguration",
+ "lang_proxyLocationText": "Bla bla bla BLA!!!!",
+ "lang_proxyServerTHead": "Server\/Proxy",
+ "lang_reboot": "Reboot",
+ "lang_rebootProxyHeading": "Proxy neustarten",
+ "lang_rebootProxyText": "Bei \u00c4nderungen an der Konfiguration muss der Server neugestartet werden, damit die \u00c4nderungen wirksam werden.",
+ "lang_recursiveCount": "Rekursiv",
+ "lang_rxTotal": "Gesamt empfangen",
+ "lang_serverList": "Serverliste",
+ "lang_sessionRx": "Seit Neustart empfangen",
+ "lang_sessionTx": "Seit Neustart gesendet",
+ "lang_settings": "Einstellungen",
+ "lang_storageSize": "Speichergr\u00f6\u00dfe",
+ "lang_test": "Testen",
+ "lang_txTotal": "Gesamt gesendet",
+ "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/messages.json b/modules-available/dnbd3/lang/en/messages.json
new file mode 100644
index 00000000..4c658e11
--- /dev/null
+++ b/modules-available/dnbd3/lang/en/messages.json
@@ -0,0 +1,3 @@
+{
+ "server-unreachable": "Server not reachable"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/en/module.json b/modules-available/dnbd3/lang/en/module.json
new file mode 100644
index 00000000..d2bb6fd3
--- /dev/null
+++ b/modules-available/dnbd3/lang/en/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "DNBD3",
+ "page_title": "DNBD3 management"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/lang/pt/module.json b/modules-available/dnbd3/lang/pt/module.json
new file mode 100644
index 00000000..8cc7f8e4
--- /dev/null
+++ b/modules-available/dnbd3/lang/pt/module.json
@@ -0,0 +1,3 @@
+{
+ "module_name": "Log do Servidor"
+} \ No newline at end of file
diff --git a/modules-available/dnbd3/page.inc.php b/modules-available/dnbd3/page.inc.php
new file mode 100644
index 00000000..2836cbf4
--- /dev/null
+++ b/modules-available/dnbd3/page.inc.php
@@ -0,0 +1,417 @@
+<?php
+
+class Page_Dnbd3 extends Page
+{
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::isLoggedIn()) {
+ Message::addError('main.no-permission');
+ Util::redirect('?do=Main');
+ }
+ $action = Request::post('action', false, 'string');
+ if ($action === 'refresh') {
+ Dnbd3Util::updateServerStatus();
+ } elseif ($action === 'delserver') {
+ $this->deleteServer();
+ } elseif ($action === 'addserver') {
+ $this->addServer();
+ } elseif ($action === 'editserver') {
+ $this->editServer();
+ } elseif ($action === 'savelocations') {
+ $this->saveServerLocations();
+ } elseif ($action === 'toggle-usage') {
+ $this->toggleUsage();
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=dnbd3');
+ }
+ }
+
+ private function editServer()
+ {
+ $server = $this->getServerById();
+ if (!isset($server['machineuuid'])) {
+ Message::addError('not-automatic-server', $server['ip']);
+ return;
+ }
+ $bgr = Request::post('bgr', false, 'bool');
+ $firewall = Request::post('firewall', false, 'bool');
+ RunMode::setRunMode($server['machineuuid'], 'dnbd3', 'proxy',
+ json_encode(compact('bgr', 'firewall')), false);
+ }
+
+ private function toggleUsage()
+ {
+ $enabled = Request::post('enabled', false, 'bool');
+ $nfs = Request::post('with-nfs', false, 'bool');
+ $task = Dnbd3::setEnabled($enabled);
+ Dnbd3::setNfsFallback($nfs);
+ Taskmanager::waitComplete($task, 5000);
+ }
+
+ private function saveServerLocations()
+ {
+ $server = $this->getServerById();
+ $locids = Request::post('location', [], 'array');
+ if (empty($locids)) {
+ Database::exec('DELETE FROM dnbd3_server_x_location WHERE serverid = :serverid',
+ array('serverid' => $server['serverid']));
+ } else {
+ Database::exec('DELETE FROM dnbd3_server_x_location WHERE serverid = :serverid AND locationid NOT IN (:lids)',
+ array('serverid' => $server['serverid'], 'lids' => $locids));
+ foreach ($locids as $lid) {
+ Database::exec('INSERT IGNORE INTO dnbd3_server_x_location (serverid, locationid) VALUES (:serverid, :lid)',
+ array('serverid' => $server['serverid'], 'lid' => $lid));
+ }
+ }
+ }
+
+ private function addServer()
+ {
+ $ip = Request::post('newip', false, 'string');
+ if ($ip === false) {
+ Message::addError('main.parameter-missing', 'ip');
+ return;
+ }
+ $ip = ip2long(trim($ip));
+ if ($ip !== false) {
+ $ip = long2ip($ip);
+ }
+ if ($ip === false) {
+ Message::addError('invalid-ipv4', $ip);
+ return;
+ }
+ $res = Database::queryFirst('SELECT serverid FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ WHERE s.fixedip = :ip OR m.clientip = :ip', compact('ip'));
+ if ($res !== false) {
+ Message::addError('server-already-exists', $ip);
+ return;
+ }
+ Database::exec('INSERT INTO dnbd3_server (fixedip) VALUES (:ip)', compact('ip'));
+ Message::addSuccess('server-added', $ip);
+ }
+
+ private function deleteServer()
+ {
+ $server = $this->getServerById();
+ if ($server['fixedip'] === '<self>')
+ return;
+ if (!is_null($server['machineuuid'])) {
+ RunMode::setRunMode($server['machineuuid'], 'dnbd3', null, null, null);
+ }
+ Database::exec('DELETE FROM dnbd3_server WHERE serverid = :serverid',
+ array('serverid' => $server['serverid']));
+ Message::addSuccess('server-deleted', $server['ip']);
+ }
+
+ /*
+ * RENDER
+ */
+
+ protected function doRender()
+ {
+ $show = Request::get('show', false, 'string');
+ if ($show === 'proxy') {
+ $this->showProxyDetails();
+ } elseif ($show === 'locations') {
+ $this->showServerLocationEdit();
+ } elseif ($show === false) {
+ $this->showServerList();
+ } else {
+ Util::redirect('?do=dnbd3');
+ }
+ }
+
+ private function showServerList()
+ {
+ $dynClients = RunMode::getForMode(Page::getModule(), 'proxy', true, true);
+ $res = Database::simpleQuery('SELECT s.serverid, s.machineuuid, s.fixedip, s.lastseen AS dnbd3lastseen,
+ s.uptime, s.totalup, s.totaldown, s.clientcount, s.disktotal, s.diskfree, Count(sxl.locationid) AS locations,
+ s.errormsg
+ FROM dnbd3_server s
+ LEFT JOIN dnbd3_server_x_location sxl USING (serverid)
+ GROUP BY s.serverid');
+ $servers = array();
+ $sort = array();
+ $NOW = time();
+ while ($server = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (isset($dynClients[$server['machineuuid']])) {
+ $server += $dynClients[$server['machineuuid']];
+ unset($dynClients[$server['machineuuid']]);
+ }
+ if ($server['uptime'] != 0) {
+ $server['uptime'] += ($NOW - $server['dnbd3lastseen']);
+ }
+ $server['dnbd3lastseen_s'] = $server['dnbd3lastseen'] ? date('d.m.Y H:i', $server['dnbd3lastseen']) : '-';
+ $server['uptime_s'] = $server['uptime'] ? floor($server['uptime'] / 86400) . 'd ' . gmdate('H:i', $server['uptime']) : '-';
+ $server['totalup_s'] = Util::readableFileSize($server['totalup']);
+ $server['totaldown_s'] = Util::readableFileSize($server['totaldown']);
+ if ($server['disktotal'] > 0) {
+ $server['disktotal_s'] = Util::readableFileSize($server['disktotal']);
+ $server['diskfree_s'] = Util::readableFileSize($server['diskfree']);
+ $server['diskUsePercent'] = floor(100 - 100 * $server['diskfree'] / $server['disktotal']);
+ } else {
+ $server['disktotal_s'] = '?';
+ $server['diskfree_s'] = '?';
+ $server['diskUsePercent'] = 0;
+ }
+ $server['self'] = ($server['fixedip'] === '<self>');
+ if (isset($server['clientip']) && !is_null($server['clientip'])) {
+ if ($NOW - $server['lastseen'] > 360) {
+ $server['slxDown'] = true;
+ } else {
+ $server['slxOk'] = true;
+ }
+ }
+ if ($server['self']) {
+ $sort[] = '---';
+ } else {
+ $sort[] = $server['fixedip'] . '.' . $server['machineuuid'];
+ }
+ $servers[] = $server;
+ }
+ foreach ($dynClients as $server) {
+ $servers[] = $server;
+ $sort[] = '-' . $server['machineuuid'];
+ Database::exec('INSERT INTO dnbd3_server (machineuuid) VALUES (:uuid)', array('uuid' => $server['machineuuid']));
+ }
+ array_multisort($sort, SORT_ASC, $servers);
+ Render::addTemplate('page-serverlist', array(
+ 'list' => $servers,
+ 'enabled' => Dnbd3::isEnabled(),
+ 'enabled_checked_s' => Dnbd3::isEnabled() ? 'checked' : '',
+ 'nfs_checked_s' => Dnbd3::hasNfsFallback() ? 'checked' : '',
+ 'rebootcontrol' => Module::isAvailable('rebootcontrol', false)
+ ));
+ }
+
+ private function showProxyDetails()
+ {
+ $server = $this->getServerById();
+ Render::addTemplate('page-proxy-header', $server);
+ $stats = Dnbd3Rpc::query($server['ip'], 5003,true, true, false, true);
+ if (!is_array($stats) || !isset($stats['runId'])) {
+ Message::addError('server-unreachable');
+ return;
+ }
+ $stats['bytesSent_s'] = Util::readableFileSize($stats['bytesSent']);
+ $stats['bytesReceived_s'] = Util::readableFileSize($stats['bytesReceived']);
+ $stats['uptime_s'] = floor($stats['uptime'] / 86400) . 'd ' . gmdate('H:i:s', $stats['uptime']);
+ Render::addTemplate('page-proxy-stats', $stats);
+ $images = Dnbd3Rpc::query($server['ip'], 5003,false, false, true);
+ $confAlts = Dnbd3Rpc::query($server['ip'], 5003,false, false, false, false, true, true);
+ $ips = array();
+ $sort = array();
+ foreach ($stats['clients'] as &$c) {
+ $c['bytesSent_s'] = Util::readableFileSize($c['bytesSent']);
+ $sort[] = $c['bytesSent'];
+ $ips[] = preg_replace('/:\d+$/', '', $c['address']);
+ }
+ array_multisort($sort, SORT_DESC, $stats['clients']);
+ Render::openTag('div', ['class' => 'row']);
+ // Config
+ if (is_string($confAlts['config'])) {
+ Render::addTemplate('page-proxy-config', $confAlts);
+ }
+ if (is_array($confAlts['altservers'])) {
+ foreach ($confAlts['altservers'] as &$as) {
+ $as['rtt'] = round(array_sum($as['rtt']) / count($as['rtt']) / 1000, 2);
+ }
+ unset($as);
+ Render::addTemplate('page-proxy-altservers', $confAlts);
+ }
+ Render::closeTag('div');
+ Render::openTag('div', ['class' => 'row']);
+ // Count locations
+ $res = Database::simpleQuery('SELECT locationid, Count(*) AS cnt FROM machine WHERE clientip IN (:ips) GROUP BY locationid', compact('ips'));
+ $locCount = Location::getLocationsAssoc();
+ $locCount[0] = array(
+ 'locationname' => '/',
+ 'depth' => 0,
+ 'recCount' => 0,
+ );
+ foreach ($locCount as &$loc) {
+ $loc['recCount'] = 0;
+ }
+ $showLocs = false;
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ settype($row['locationid'], 'int');
+ $loc =& $locCount[$row['locationid']];
+ $loc['clientCount'] = $row['cnt'];
+ $loc['recCount'] += $row['cnt'];
+ if ($row['locationid'] !== 0) {
+ $showLocs = true;
+ }
+ $loc['keep'] = true;
+ if (isset($loc['parents'])) {
+ foreach ($loc['parents'] as $p) {
+ $locCount[$p]['keep'] = true;
+ $locCount[$p]['recCount'] += $row['cnt'];
+ }
+ }
+ }
+ if ($showLocs) {
+ $locCount = array_filter($locCount, function ($v) { return isset($v['keep']); });
+ Render::addTemplate('page-proxy-loclist', array('list' => array_values($locCount)));
+ }
+ Render::addTemplate('page-proxy-clients', $stats);
+ Render::closeTag('div');
+ }
+
+ private function showServerLocationEdit()
+ {
+ $server = $this->getServerById();
+ // Get selected ones
+ $res = Database::simpleQuery('SELECT locationid FROM dnbd3_server_x_location WHERE serverid = :serverid',
+ array('serverid' => $server['serverid']));
+ $selectedLocations = array();
+ while ($loc = $res->fetchColumn(0)) {
+ $selectedLocations[$loc] = true;
+ }
+ // Build location list
+ $server['locations'] = array_values(Location::getSubnetsByLocation());
+ $filtered = array();
+ foreach ($server['locations'] as &$loc) {
+ $filtered['l'.$loc['locationid']] = array(
+ 'children' => $loc['children'],
+ 'subnets' => $loc['subnets']
+ );
+ if (isset($selectedLocations[$loc['locationid']])) {
+ $loc['checked_s'] = 'checked';
+ }
+ }
+ unset($loc);
+ $server['jsonLocations'] = json_encode($filtered);
+ Render::addTemplate('page-server-locations', $server);
+ }
+
+ private function getServerById($serverId = false)
+ {
+ if ($serverId === false) {
+ $serverId = Request::any('server', false, 'int');
+ }
+ if ($serverId === false) {
+ if (AJAX)
+ die('Missing parameter');
+ Message::addError('main.parameter-missing', 'server');
+ Util::redirect('?do=dnbd3');
+ }
+ $server = Database::queryFirst('SELECT s.serverid, s.machineuuid, s.fixedip, m.clientip, m.hostname
+ FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ WHERE s.serverid = :serverId', compact('serverId'));
+ if ($server === false) {
+ if (AJAX)
+ die('Invalid server id');
+ Message::addError('server-non-existent', 'server');
+ Util::redirect('?do=dnbd3');
+ }
+ if (!is_null($server['clientip'])) {
+ $server['ip'] = $server['clientip'];
+ } elseif (!is_null($server['fixedip'])) {
+ $server['ip'] = $server['fixedip'];
+ } else {
+ $server['ip'] = '127.0.0.1';
+ }
+ return $server;
+ }
+
+ /*
+ * AJAX
+ */
+
+ protected function doAjax()
+ {
+ User::load();
+ if (!User::isLoggedIn())
+ die('No');
+ $action = Request::any('action', false, 'string');
+ if ($action === 'servertest') {
+ $this->ajaxServerTest();
+ } elseif ($action === 'editserver') {
+ $this->ajaxEditServer();
+ } elseif ($action === 'reboot') {
+ $this->ajaxReboot();
+ } else {
+ die($action . '???');
+ }
+ }
+
+ private function ajaxServerTest()
+ {
+ Header('Content-Type: application/json; charset=utf-8');
+ $ip = Request::post('ip', false, 'string');
+ if ($ip === false)
+ die('{"error": "Missing parameter", "fatal": true}');
+ $ip = ip2long(trim($ip));
+ if ($ip !== false) {
+ $ip = long2ip($ip);
+ }
+ if ($ip === false)
+ die('{"error": "Supports IPv4 only", "fatal": true}');
+ // Dup?
+ $res = Database::queryFirst('SELECT serverid FROM dnbd3_server s
+ LEFT JOIN machine m USING (machineuuid)
+ WHERE s.fixedip = :ip OR m.clientip = :ip', compact('ip'));
+ if ($res !== false)
+ die('{"error": "Server with this IP already exists", "fatal": true}');
+ // Query
+ $reply = Dnbd3Rpc::query($ip, 5003,true, false, false, true);
+ if ($reply === Dnbd3Rpc::QUERY_UNREACHABLE)
+ die('{"error": "Could not reach server"}');
+ if ($reply === Dnbd3Rpc::QUERY_NOT_200)
+ die('{"error": "Server did not reply with 200 OK"}');
+ if ($reply === Dnbd3Rpc::QUERY_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"}');
+ echo json_encode($reply);
+ }
+
+ private function ajaxEditServer()
+ {
+ $server = $this->getServerById();
+ if (!isset($server['machineuuid'])) {
+ echo 'Not automatic server.';
+ return;
+ }
+ $rm = RunMode::getForMode(Page::getModule(), 'proxy', false, true);
+ if (!isset($rm[$server['machineuuid']])) {
+ echo 'Error: RunMode entry missing.';
+ return;
+ }
+ $modeData = (array)json_decode($rm[$server['machineuuid']]['modedata'], true);
+ $server += $modeData + Dnbd3Util::defaultRunmodeConfig();
+ echo Render::parse('fragment-server-settings', $server);
+ }
+
+ private function ajaxReboot()
+ {
+ $server = $this->getServerById();
+ if (!isset($server['machineuuid'])) {
+ die('Not automatic server.');
+ }
+ if (!Module::isAvailable('rebootcontrol')) {
+ die('No rebootcontrol');
+ }
+ $uuid = $server['machineuuid'];
+ $task = RebootControl::reboot([ $uuid ]);
+ if ($task === false) {
+ die('Taskmanager unreachable');
+ }
+ $task = Taskmanager::waitComplete($task, 2000);
+ if (is_array($task) && isset($task['data']) && isset($task['data']['clientStatus']) && isset($task['data']['clientStatus'][$uuid])) {
+ $status = $task['data']['clientStatus'][$uuid];
+ if (!empty($task['data']['error'])) {
+ $status .= "\n --- \n" . $task['data']['error'];
+ }
+ die($status);
+ }
+ die('Unknown :-(');
+ }
+
+}
diff --git a/modules-available/dnbd3/templates/fragment-server-settings.html b/modules-available/dnbd3/templates/fragment-server-settings.html
new file mode 100644
index 00000000..61b0626d
--- /dev/null
+++ b/modules-available/dnbd3/templates/fragment-server-settings.html
@@ -0,0 +1,14 @@
+<input type="hidden" name="server" value="{{serverid}}">
+
+<div class="checkbox">
+ <input type="checkbox" name="bgr" id="bgr" {{#bgr}}checked{{/bgr}}>
+ <label for="bgr">{{lang_backgroundReplication}}</label>
+</div>
+<i>{{lang_backgroundReplicationInfo}}</i>
+<br>
+<div class="checkbox">
+ <input type="checkbox" name="firewall" id="firewall" {{#firewall}}checked{{/firewall}}>
+ <label for="firewall">{{lang_firewalled}}</label>
+</div>
+<i>{{lang_firewallInfo}}</i>
+<br>
diff --git a/modules-available/dnbd3/templates/page-proxy-altservers.html b/modules-available/dnbd3/templates/page-proxy-altservers.html
new file mode 100644
index 00000000..00a884cc
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-altservers.html
@@ -0,0 +1,36 @@
+<div class="col-md-6">
+ <h2>{{lang_altservers}}</h2>
+ <table class="table table-condensed">
+ <tr>
+ <th>{{lang_proxyServerTHead}}</th>
+ <th>{{lang_numFails}}</th>
+ <th class="text-right">{{lang_latency}}</th>
+ <th>{{lang_flags}}</th>
+ <th>{{lang_comment}}</th>
+ </tr>
+ {{#altservers}}
+ <tr>
+ <td class="text-nowrap">{{host}}</td>
+ <td>
+ {{^isClientOnly}}
+ {{numFails}}
+ {{/isClientOnly}}
+ </td>
+ <td class="text-right text-nowrap">
+ {{^isClientOnly}}
+ {{rtt}}&thinsp;ms
+ {{/isClientOnly}}
+ </td>
+ <td>
+ {{#isClientOnly}}
+ [CO]
+ {{/isClientOnly}}
+ {{#isPrivate}}
+ [PO]
+ {{/isPrivate}}
+ </td>
+ <td><table class="slx-ellipsis"><tr><td>{{comment}}</td></tr></table></td>
+ </tr>
+ {{/altservers}}
+ </table>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-proxy-clients.html b/modules-available/dnbd3/templates/page-proxy-clients.html
new file mode 100644
index 00000000..9e7cec4c
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-clients.html
@@ -0,0 +1,20 @@
+<div class="col-md-6">
+ <h2>{{lang_clientList}}</h2>
+
+ <table class="table table-condensed">
+ <tr>
+ <th>{{lang_client}}</th>
+ <th class="text-right">{{lang_bytesSent}}</th>
+ </tr>
+ {{#clients}}
+ <tr>
+ <td>
+ {{address}}
+ </td>
+ <td data-sort="int" data-sort-value="{{bytesSent}}" class="text-right">
+ {{bytesSent_s}}
+ </td>
+ </tr>
+ {{/clients}}
+ </table>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-proxy-config.html b/modules-available/dnbd3/templates/page-proxy-config.html
new file mode 100644
index 00000000..adc73a57
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-config.html
@@ -0,0 +1,4 @@
+<div class="col-md-6">
+ <h2>{{lang_proxyConfig}}</h2>
+ <pre>{{config}}</pre>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-proxy-header.html b/modules-available/dnbd3/templates/page-proxy-header.html
new file mode 100644
index 00000000..6f3f1b7f
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-header.html
@@ -0,0 +1 @@
+<h1>{{ip}}</h1> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-proxy-loclist.html b/modules-available/dnbd3/templates/page-proxy-loclist.html
new file mode 100644
index 00000000..67c90683
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-loclist.html
@@ -0,0 +1,27 @@
+<div class="col-md-6">
+ <h2>{{lang_clientsByLocation}}</h2>
+
+ <table class="table table-condensed">
+ <tr>
+ <th>{{lang_location}}</th>
+ <th class="text-right">{{lang_count}}</th>
+ <th class="text-right">{{lang_recursiveCount}}</th>
+ </tr>
+ {{#list}}
+ <tr>
+ <td>
+ {{#depth}}
+ <div style="display:inline-block;width:{{depth}}em"></div>
+ {{/depth}}
+ {{locationname}}
+ </td>
+ <td class="text-right">
+ {{clientCount}}
+ </td>
+ <td class="text-right">
+ {{recCount}}
+ </td>
+ </tr>
+ {{/list}}
+ </table>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-proxy-stats.html b/modules-available/dnbd3/templates/page-proxy-stats.html
new file mode 100644
index 00000000..e7811028
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-proxy-stats.html
@@ -0,0 +1,9 @@
+<div class="panel panel-default">
+ <div class="panel-body">
+ {{lang_sessionTx}}: <b>{{bytesSent_s}}</b>
+ ––
+ {{lang_sessionRx}}: <b>{{bytesReceived_s}}</b>
+ ––
+ {{lang_uptime}}: <b>{{uptime_s}}</b>
+ </div>
+</div> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-server-locations.html b/modules-available/dnbd3/templates/page-server-locations.html
new file mode 100644
index 00000000..20dddaac
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-server-locations.html
@@ -0,0 +1,96 @@
+<h1>{{lang_manageAccessTo}} {{ip}}</h1>
+
+<p><i>{{lang_proxyLocationText}}</i></p>
+
+<form method="post" action="?do=dnbd3">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="server" value="{{serverid}}">
+
+ <div class="buttonbar text-right">
+ <button type="submit" class="btn btn-primary" name="action" value="savelocations">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6">
+ <h3>{{lang_allowedSubnets}}</h3>
+ <div id="subnet-list">
+
+ </div>
+ </div>
+ <div class="col-md-6">
+ <h3>{{lang_locations}}</h3>
+ {{#locations}}
+ <div class="checkbox">
+ {{#depth}}
+ <div style="display:inline-block;width:{{depth}}em"></div>
+ {{/depth}}
+ <input id="cb-{{locationid}}" class="loc-check" type="checkbox" name="location[]" value="{{locationid}}" {{checked_s}}>
+ <label for="cb-{{locationid}}">{{locationname}}</label>
+ </div>
+ {{/locations}}
+ </div>
+ </div>
+
+ <div class="buttonbar text-right">
+ <button type="submit" class="btn btn-success" name="action" value="savelocations">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+</form>
+
+<script type="application/javascript"><!--
+document.addEventListener('DOMContentLoaded', function() {
+
+ const locData = {{{jsonLocations}}};
+ const $snList = $('#subnet-list');
+
+ function updateLocations() {
+ var allLocs = [];
+ $('.loc-check:checked').each(function () {
+ var lid = parseInt(this.value);
+ var locs = getAllLocs(lid);
+ for (var i = 0; i < locs.length; ++i) {
+ if (allLocs.indexOf(locs[i]) === -1) allLocs.push(locs[i]);
+ }
+ });
+ var subnets = [];
+ $snList.empty();
+ for (var i = 0; i < allLocs.length; ++i) {
+ var loc = locData['l'+allLocs[i]];
+ if (!loc || !loc.subnets) continue;
+ for (var j = 0; j < loc.subnets.length; ++j) {
+ var line = long2ip(loc.subnets[j].startaddr) + ' - ' + long2ip(loc.subnets[j].endaddr);
+ if (subnets.indexOf(line) === -1) {
+ subnets.push(line);
+ $snList.append($('<div>').text(line));
+ }
+ }
+ }
+ if (subnets.length === 0) {
+ $snList.text('{{lang_global}}');
+ }
+ }
+
+ function getAllLocs(lid) {
+ var e = locData['l'+lid];
+ if (!e || !e.children) return [];
+ var ret = e.children;
+ ret.push(lid);
+ return ret;
+ }
+
+ function long2ip(ip) {
+ // discuss at: http://locutus.io/php/long2ip/
+ // original by: Waldo Malqui Silva (http://waldo.malqui.info)
+ if (!isFinite(ip)) return false;
+ return [ip >>> 24, ip >>> 16 & 0xFF, ip >>> 8 & 0xFF, ip & 0xFF].join('.');
+ }
+
+ $('.loc-check').change(updateLocations);
+ updateLocations();
+});
+//--></script> \ No newline at end of file
diff --git a/modules-available/dnbd3/templates/page-serverlist.html b/modules-available/dnbd3/templates/page-serverlist.html
new file mode 100644
index 00000000..c5905dcd
--- /dev/null
+++ b/modules-available/dnbd3/templates/page-serverlist.html
@@ -0,0 +1,404 @@
+<h1>{{lang_dnbd3Management}}</h1>
+<p><i>{{lang_dnbd3IntroText}}</i></p>
+
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_dnbd3Status}}:
+ <b>
+ {{#enabled}}{{lang_enabled}}{{/enabled}}
+ {{^enabled}}{{lang_disabled}} (NFS/CIFS){{/enabled}}
+ </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-body">
+ <form method="post" action="?do=dnbd3">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="checkbox">
+ <input id="enable-dnbd3" type="checkbox" name="enabled" {{enabled_checked_s}}>
+ <label for="enable-dnbd3">{{lang_enableDnbd3}}</label>
+ </div>
+ <div class="checkbox">
+ <input id="allow-nfs" type="checkbox" name="with-nfs" {{nfs_checked_s}}>
+ <label for="allow-nfs">{{lang_allowNfsFallback}}</label>
+ </div>
+ <button type="submit" name="action" value="toggle-usage" class="btn btn-success">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </form>
+ </div>
+ </div>
+</div>
+
+<form method="post" onsubmit="$('#refbtn').prop('disabled', true).find('span').addClass('slx-rotation')" action="?do=dnbd3">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="refresh">
+ <h2>
+ {{lang_serverList}}
+ <button id="refbtn" type="submit" class="btn btn-default"><span class="glyphicon glyphicon-refresh"></span></button>
+ </h2>
+</form>
+
+<form method="post" action="?do=dnbd3">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="delserver">
+ <table class="table">
+ <thead>
+ <tr>
+ <th></th>
+ <th>{{lang_proxyServerTHead}}</th>
+ <th class="text-right">{{lang_storageSize}}</th>
+ <th class="text-right">{{lang_clientCount}}</th>
+ <th class="text-right">{{lang_lastSeen}}</th>
+ <th class="text-right">{{lang_uptime}}</th>
+ <th class="text-right">{{lang_txTotal}}</th>
+ <th class="text-right">{{lang_rxTotal}}</th>
+ <th class="text-right">{{lang_locations}}</th>
+ <th></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ {{#list}}
+ <tr>
+ <td class="text-right text-nowrap">
+ {{#slxOk}}
+ <span class="glyphicon glyphicon-ok text-success"></span>
+ {{/slxOk}}
+ {{#slxDown}}
+ <span class="glyphicon glyphicon-off"></span>
+ {{/slxDown}}
+ {{#uptime}}
+ <span class="glyphicon glyphicon-ok text-success"></span>
+ {{/uptime}}
+ {{^uptime}}
+ <span class="glyphicon glyphicon-remove text-danger"></span>
+ {{/uptime}}
+ </td>
+ <td class="{{#self}}slx-bold{{/self}}">
+ {{#machineuuid}}
+ <a class="pull-right btn btn-default btn-xs" href="?do=Statistics&uuid={{machineuuid}}">
+ <span class="glyphicon glyphicon-eye-open"></span>
+ </a>
+ {{/machineuuid}}
+ <a href="?do=dnbd3&amp;show=proxy&amp;server={{serverid}}">
+ {{fixedip}}
+ <span class="small">{{clientip}}</span>
+ </a>
+ <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"
+ title="{{lang_diskFree}}: {{diskfree_s}}">
+ {{disktotal_s}}
+ </div>
+ {{#errormsg}}
+ <div class="small text-nowrap text-danger" style="margin-right:-500px">
+ {{errormsg}}
+ </div>
+ {{/errormsg}}
+ </td>
+ <td data-sort="int" data-sort-default="desc" class="text-right">
+ {{clientcount}}
+ </td>
+ <td data-sort="int" data-sort-default="desc" data-sort-value="{{dnbd3lastseen}}" class="text-right text-nowrap">
+ {{dnbd3lastseen_s}}
+ </td>
+ <td data-sort="int" data-sort-default="desc" data-sort-value="{{uptime}}" class="text-right text-nowrap">
+ {{uptime_s}}
+ </td>
+ <td data-sort="int" data-sort-default="desc" data-sort-value="{{totalup}}" class="text-right text-nowrap">
+ {{totalup_s}}
+ </td>
+ <td data-sort="int" data-sort-default="desc" data-sort-value="{{totaldown}}" class="text-right text-nowrap">
+ {{totaldown_s}}
+ </td>
+ <td class="text-right text-nowrap">
+ {{^self}}
+ {{^locations}}
+ <i>{{lang_global}}</i>
+ {{/locations}}
+ {{#locations}}
+ {{locations}}
+ {{/locations}}
+ <a href="?do=dnbd3&amp;show=locations&amp;server={{serverid}}" class="btn btn-default btn-xs">
+ <span class="glyphicon glyphicon-map-marker"></span>
+ </a>
+ {{/self}}
+ </td>
+ <td class="text-right text-nowrap">
+ {{#machineuuid}}
+ {{#rebootcontrol}}
+ <button class="btn btn-warning btn-xs reboot-btn" type="button" data-id="{{serverid}}"
+ data-toggle="modal" data-target="#server-reboot-modal" title="{{lang_reboot}}">
+ <span class="glyphicon glyphicon-repeat"></span>
+ </button>
+ {{/rebootcontrol}}
+ <button class="btn btn-default btn-xs edit-btn" type="button" data-id="{{serverid}}"
+ data-toggle="modal" data-target="#server-edit-modal" title="{{lang_settings}}">
+ <span class="glyphicon glyphicon-cog"></span>
+ </button>
+ {{/machineuuid}}
+ {{^self}}
+ <button class="btn btn-danger btn-xs" name="server" value="{{serverid}}"
+ onclick="return confirm('{{lang_wantToDelete}}')" title="{{lang_delete}}">
+ <span class="glyphicon glyphicon-trash"></span>
+ </button>
+ {{/self}}
+ </td>
+ </tr>
+ {{/list}}
+ </tbody>
+ </table>
+</form>
+
+<div class="btn-toolbar pull-right">
+ <div class="btn-group">
+ <button type="button" class="btn btn-success" data-toggle="modal" data-target="#add-modal">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_externalServerAdd}}
+ </button>
+ <button type="button" class="btn btn-default" data-toggle="modal" data-target="#help-external">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </button>
+ </div>
+ <div class="btn-group">
+ <a class="btn btn-success" href="?do=runmode&amp;module=dnbd3&amp;modeid=proxy&amp;redirect=?do=dnbd3">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_managedServerAdd}}
+ </a>
+ <button type="button" class="btn btn-default" data-toggle="modal" data-target="#help-automatic">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ </button>
+ </div>
+</div>
+
+<div id="help-external" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_externalServer}}</b></h4>
+ </div>
+ <div class="modal-body">
+ {{lang_externalServerHelp}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary pull-right" data-dismiss="modal">
+ {{lang_close}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="help-automatic" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_managedServer}}</b></h4>
+ </div>
+ <div class="modal-body">
+ {{lang_managedServerHelp}}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary pull-right" data-dismiss="modal">
+ {{lang_close}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="server-edit-modal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <form method="post" action="?do=dnbd3">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="editserver">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_editProxyHeading}}</b></h4>
+ </div>
+ <div class="modal-body" id="server-edit-body">
+ .
+ </div>
+ <div class="modal-footer">
+ <button type="submit" class="btn btn-primary pull-right">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+
+<div id="server-reboot-modal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_rebootProxyHeading}}</b></h4>
+ </div>
+ <div class="modal-body">
+ <p>{{lang_rebootProxyText}}</p>
+ <p id="reboot-status"></p>
+ </div>
+ <div class="modal-footer text-right">
+ <button type="button" class="btn btn-default" data-dismiss="modal">{{lang_close}}</button>
+ <button id="btn-exec-reboot" type="button" class="btn btn-primary">
+ <span class="glyphicon glyphicon-repeat"></span>
+ {{lang_reboot}}
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="add-modal" class="modal fade" role="dialog">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal">&times;</button>
+ <h4 class="modal-title"><b>{{lang_addServer}}</b></h4>
+ </div>
+ <form id="addform" method="post" action="?do=dnbd3">
+ <div class="modal-body">
+ <p>{{lang_enterIpOfServer}}</p>
+
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="text" class="form-control" name="newip" id="newip">
+ <div id="addtest" class="text-danger"></div>
+ </div>
+ <div class="modal-footer">
+ <div class="btn-toolbar pull-right">
+ <button id="testbtn" type="submit" class="btn btn-warning" name="action" value="addserver">
+ <span class="glyphicon glyphicon-question-sign"></span>
+ {{lang_test}}
+ </button>
+ <button id="savebtn" type="submit" class="btn btn-primary" name="action" value="addserver" disabled>
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</div>
+
+<div class="clearfix"></div>
+
+<script type="application/javascript"><!--
+document.addEventListener('DOMContentLoaded', function () {
+ var slxWorking = false;
+ var testedIp;
+ const $form = $('#addform');
+ const $inputs = $form.find(':input');
+ const $result =$('#addtest');
+ const $ip = $('#newip');
+ const $save = $('#savebtn');
+
+ const changeFunc = function() {
+ $save.prop('disabled', $ip.val() !== testedIp);
+ };
+ $ip.change(changeFunc).keypress(function () { setTimeout(changeFunc, 1); });
+
+ $form.submit(function (event) {
+ console.log('pre-sub');
+ console.log($save.prop('disabled'));
+ if (!$save.prop('disabled')) return;
+ console.log('post-sub');
+ event.preventDefault();
+ runTest();
+ });
+
+ $('#testbtn').click(function (event) {
+ console.log('pre-focus');
+ if ($ip.is(':focus')) return;
+ console.log('post-focus');
+ event.preventDefault();
+ runTest();
+ });
+
+ function runTest() {
+ if (slxWorking === false) {
+ var ip = $ip.val();
+ var form = $('#addform');
+ slxWorking = Math.random();
+ var workCopy = slxWorking;
+ $inputs.prop('disabled', true);
+ $result.empty().removeClass('text-danger').text('...working...');
+ var resetFunc = function(ok) {
+ if (slxWorking === workCopy) {
+ slxWorking = false;
+ $inputs.prop('disabled', false);
+ if (!ok) $result.empty().addClass('text-danger').text('Timeout.');
+ }
+ };
+ setTimeout(resetFunc, 3000);
+ testedIp = ip;
+ $.post('?do=dnbd3', {action:'servertest', ip:ip, token:TOKEN}, function (data) {
+ if (workCopy !== slxWorking) return;
+ resetFunc(true);
+ if (!data || data.fatal) {
+ $save.prop('disabled', true);
+ }
+ if (data && data.error) {
+ $result.empty().addClass('text-danger').text(data.error);
+ } else {
+ $result.empty().removeClass('text-danger').text('OK, Uptime: ' + data.uptime + ', Clients: ' + data.clientCount);
+ }
+ }, 'json').fail(function(oh, what) {
+ resetFunc(true);
+ $result.empty().addClass('text-danger').text('Fail ' + what);
+ });
+ }
+ }
+
+ $('.edit-btn').click(function() {
+ var id = $(this).data('id');
+ $('#server-edit-body').text('loading').load('?do=dnbd3&action=editserver&server=' + id);
+ });
+
+ var rebootServerId = 0;
+ $('.reboot-btn').click(function() {
+ rebootServerId = $(this).data('id');
+ $('#btn-exec-reboot').prop('disabled', false);
+ $('#reboot-status').empty();
+ });
+ $('#btn-exec-reboot').click(function() {
+ $(this).prop('disabled', true);
+ var $t = $('#reboot-status');
+ if (rebootServerId === 0) {
+ $t.text('No ID!?');
+ return;
+ }
+ $t.html('<span class="glyphicon glyphicon-refresh slx-rotation"></span>');
+ var sid = rebootServerId;
+ var query = function() {
+ $.ajax({
+ "data": {"token": TOKEN, "action": "reboot", "server": sid},
+ "method": "POST",
+ "dataType": "text",
+ "url": "?do=dnbd3"
+ }).done(function (data) {
+ $t.text(data);
+ if (data.indexOf('REBOOTING') !== -1 || data.indexOf('CONNECTING') !== -1) {
+ setTimeout(query, 5000);
+ $t.append($('<span class="glyphicon glyphicon-refresh slx-rotation"></span>'));
+ }
+ }).fail(function () {
+ $.text('Failed');
+ });
+ };
+ query();
+ rebootServerId = 0;
+ });
+});
+
+//--></script> \ No newline at end of file