summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2020-05-15 17:24:05 +0200
committerSimon Rettberg2020-05-15 17:24:05 +0200
commit2faaf8383c9c8a1a557518caa9f2284158df523b (patch)
tree74daaa3fa45f0808df369657d8f6efd54d19ed40
parent[rebootcontrol] Wait until tasks finish (diff)
downloadslx-admin-2faaf8383c9c8a1a557518caa9f2284158df523b.tar.gz
slx-admin-2faaf8383c9c8a1a557518caa9f2284158df523b.tar.xz
slx-admin-2faaf8383c9c8a1a557518caa9f2284158df523b.zip
[remoteaccess] New module
-rw-r--r--modules-available/remoteaccess/api.inc.php77
-rw-r--r--modules-available/remoteaccess/baseconfig/getconfig.inc.php18
-rw-r--r--modules-available/remoteaccess/config.json7
-rw-r--r--modules-available/remoteaccess/hooks/client-update.inc.php9
-rw-r--r--modules-available/remoteaccess/hooks/cron.inc.php6
-rw-r--r--modules-available/remoteaccess/inc/remoteaccess.inc.php78
-rw-r--r--modules-available/remoteaccess/install.inc.php60
-rw-r--r--modules-available/remoteaccess/lang/de/messages.json6
-rw-r--r--modules-available/remoteaccess/lang/de/module.json4
-rw-r--r--modules-available/remoteaccess/lang/de/template-tags.json16
-rw-r--r--modules-available/remoteaccess/page.inc.php100
-rw-r--r--modules-available/remoteaccess/templates/edit-group.html43
-rw-r--r--modules-available/remoteaccess/templates/edit-settings.html72
13 files changed, 496 insertions, 0 deletions
diff --git a/modules-available/remoteaccess/api.inc.php b/modules-available/remoteaccess/api.inc.php
new file mode 100644
index 00000000..2e1e4bf9
--- /dev/null
+++ b/modules-available/remoteaccess/api.inc.php
@@ -0,0 +1,77 @@
+<?php
+
+$ip = $_SERVER['REMOTE_ADDR'];
+if (substr($ip, 0, 7) === '::ffff:') $ip = substr($ip, 7);
+
+$password = Request::post('password', false, 'string');
+if ($password !== false) {
+ $c = Database::queryFirst("SELECT machineuuid FROM machine WHERE clientip = :ip", ['ip' => $ip]);
+ if ($c !== false) {
+ Database::exec("INSERT INTO remoteaccess_machine (machineuuid, password)
+ VALUES (:uuid, :passwd)
+ ON DUPLICATE KEY UPDATE password = VALUES(password)", ['uuid' => $c['machineuuid'], 'passwd' => $password]);
+ }
+ exit;
+}
+
+$range = IpUtil::parseCidr(Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET));
+if ($range === false) {
+ die('No allowed IP defined');
+}
+$iplong = ip2long($ip);
+if (PHP_INT_SIZE === 4) {
+ $iplong = sprintf('%u', $iplong);
+}
+if ($iplong < $range['start'] || $iplong > $range['end']) {
+ die('Access denied');
+}
+
+Header('Content-Type: application/json');
+
+$remoteLocations = RemoteAccess::getEnabledLocations();
+
+if (empty($remoteLocations)) {
+ $rows = [];
+} else {
+// TODO fail-counter for WOL, so we can ignore machines that apparently can't be woken up
+// -> Reset counter in our ~poweron hook, but only if the time roughly matches a WOL attempt (within ~5 minutes)
+ $rows = Database::queryAll("SELECT m.clientip, m.locationid, m.state, ram.password, ram.woltime FROM machine m
+ LEFT JOIN remoteaccess_machine ram ON (ram.machineuuid = m.machineuuid AND (ram.password IS NOT NULL OR m.state <> 'IDLE'))
+ LEFT JOIN runmode r ON (r.machineuuid = m.machineuuid)
+ WHERE m.locationid IN (:locs)
+ AND r.machineuuid IS NULL",
+ ['locs' => $remoteLocations]);
+
+ $wolCut = time() - 90;
+ foreach ($rows as &$row) {
+ if (($row['state'] === 'OFFLINE' || $row['state'] === 'STANDBY') && $row['woltime'] > $wolCut) {
+ $row['wol_in_progress'] = true;
+ }
+ settype($row['locationid'], 'int');
+ unset($row['woltime']);
+ }
+}
+
+$groups = Database::queryAll("SELECT g.groupid AS id, g.groupname AS name,
+ GROUP_CONCAT(l.locationid) AS locationids, g.passwd AS password
+ FROM remoteaccess_group g INNER JOIN remoteaccess_x_location l USING (groupid)
+ WHERE g.active = 1
+ GROUP BY g.groupid");
+foreach ($groups as &$group) {
+ $group['locationids'] = explode(',', $group['locationids']);
+ if (empty($group['password'])) {
+ unset($group['password']);
+ }
+ settype($group['id'], 'int');
+ foreach ($group['locationids'] as &$lid) {
+ settype($lid, 'int');
+ }
+}
+
+$fakeid = 100000;
+echo json_encode(['clients' => $rows, 'locations' => $groups]);
+
+// WTF, this makes the server return a 500 -.-
+//fastcgi_finish_request();
+
+RemoteAccess::ensureMachinesRunning();
diff --git a/modules-available/remoteaccess/baseconfig/getconfig.inc.php b/modules-available/remoteaccess/baseconfig/getconfig.inc.php
new file mode 100644
index 00000000..2ebc97cb
--- /dev/null
+++ b/modules-available/remoteaccess/baseconfig/getconfig.inc.php
@@ -0,0 +1,18 @@
+<?php
+
+// Locations from closest to furthest (order)
+$locationId = ConfigHolder::get('SLX_LOCATIONS');
+if ($locationId !== false) {
+ $locationId = (int)$locationId;
+ $ret = Database::queryFirst("SELECT locationid FROM remoteaccess_x_location WHERE locationid = :lid",
+ ['lid' => $locationId], true); // TODO Remove true after next point release (2020-05-12)
+ if ($ret !== false) {
+ // TODO Properly merge
+ if (Property::get(RemoteAccess::PROP_TRY_VIRT_HANDOVER)) {
+ ConfigHolder::add("SLX_REMOTE_VNC", 'vmware virtualbox');
+ } else {
+ ConfigHolder::add("SLX_REMOTE_VNC", 'x11vnc');
+ }
+ ConfigHolder::add("SLX_REMOTE_HOST_ACCESS", Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET));
+ }
+}
diff --git a/modules-available/remoteaccess/config.json b/modules-available/remoteaccess/config.json
new file mode 100644
index 00000000..1530df87
--- /dev/null
+++ b/modules-available/remoteaccess/config.json
@@ -0,0 +1,7 @@
+{
+ "category": "main.settings-client",
+ "dependencies" : [
+ "locations",
+ "rebootcontrol"
+ ]
+} \ No newline at end of file
diff --git a/modules-available/remoteaccess/hooks/client-update.inc.php b/modules-available/remoteaccess/hooks/client-update.inc.php
new file mode 100644
index 00000000..ecf5d91c
--- /dev/null
+++ b/modules-available/remoteaccess/hooks/client-update.inc.php
@@ -0,0 +1,9 @@
+<?php
+
+if ($type === '~poweron') {
+ Database::exec("UPDATE remoteaccess_machine SET password = NULL WHERE machineuuid = :uuid",
+ ['uuid' => $uuid]);
+} elseif ($type === '~poweroff') {
+ Database::exec("UPDATE remoteaccess_machine SET woltime = 0 WHERE machineuuid = :uuid",
+ ['uuid' => $uuid]);
+}
diff --git a/modules-available/remoteaccess/hooks/cron.inc.php b/modules-available/remoteaccess/hooks/cron.inc.php
new file mode 100644
index 00000000..3e0e130b
--- /dev/null
+++ b/modules-available/remoteaccess/hooks/cron.inc.php
@@ -0,0 +1,6 @@
+<?php
+
+if (!Module::isAvailable('rebootcontrol'))
+ return;
+
+RemoteAccess::ensureMachinesRunning();
diff --git a/modules-available/remoteaccess/inc/remoteaccess.inc.php b/modules-available/remoteaccess/inc/remoteaccess.inc.php
new file mode 100644
index 00000000..1dbe02c3
--- /dev/null
+++ b/modules-available/remoteaccess/inc/remoteaccess.inc.php
@@ -0,0 +1,78 @@
+<?php
+
+class RemoteAccess
+{
+
+ const PROP_ALLOWED_VNC_NET = 'remoteaccess.allowedvncaccess';
+
+ const PROP_TRY_VIRT_HANDOVER = 'remoteaccess.virthandover';
+
+ public static function getEnabledLocations($group = 0)
+ {
+ if ($group === 0) {
+ return Database::queryColumnArray("SELECT DISTINCT rxl.locationid FROM remoteaccess_x_location rxl
+ INNER JOIN remoteaccess_group g ON (g.groupid = rxl.groupid AND g.active = 1)");
+ }
+ return Database::queryColumnArray("SELECT DISTINCT locationid FROM remoteaccess_x_location
+ WHERE groupid = :gid", ['gid' => $group]);
+ }
+
+ public static function ensureMachinesRunning()
+ {
+ $res = Database::simpleQuery("SELECT rg.groupid, rg.wolcount, GROUP_CONCAT(rxl.locationid) AS locs FROM remoteaccess_group rg
+ INNER JOIN remoteaccess_x_location rxl USING (groupid)
+ WHERE rg.active = 1
+ GROUP BY groupid");
+
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if ($row['wolcount'] <= 0)
+ continue;
+ // This can't really be anything but a CSV list, but better be safe
+ $locs = preg_replace('/[^0-9,]/', '', $row['locs']);
+ if (empty($locs))
+ continue;
+ $active = Database::queryFirst("SELECT Count(*) AS cnt FROM machine m
+ INNER JOIN remoteaccess_machine rm USING (machineuuid)
+ WHERE m.locationid IN ($locs) AND m.state = 'IDLE'");
+ $wantNum = $row['wolcount'] - (isset($active['cnt']) ? $active['cnt'] : 0);
+ if ($wantNum <= 0)
+ continue;
+ self::tryWakeMachines($locs, $wantNum);
+ }
+ }
+
+ private static function tryWakeMachines($locs, $num)
+ {
+ $res = Database::simpleQuery("SELECT m.machineuuid, m.macaddr, m.clientip FROM machine m
+ LEFT JOIN remoteaccess_machine rm USING (machineuuid)
+ WHERE m.locationid IN ($locs) AND m.state IN ('OFFLINE', 'STANDBY')
+ ORDER BY rm.woltime ASC");
+ $NOW = time();
+ while ($num > 0) {
+ $list = [];
+ for ($i = 0; $i < $num && $row = $res->fetch(PDO::FETCH_ASSOC); ++$i) {
+ $list[] = $row;
+ Database::exec("INSERT INTO remoteaccess_machine (machineuuid, password, woltime)
+ VALUES (:uuid, NULL, :now)
+ ON DUPLICATE KEY UPDATE woltime = VALUES(woltime)",
+ ['uuid' => $row['machineuuid'], 'now' => $NOW]);
+ }
+ if (empty($list))
+ break; // No more clients in this location
+ error_log('Trying to wake ' . count($list) . " in $locs");
+ RebootControl::wakeMachines($list, $fails);
+ $num -= count($list) - count($fails);
+ if (!empty($fails)) {
+ error_log(count($fails) . ' failed');
+ $failIds = ArrayUtil::flattenByKey($fails, 'machineuuid');
+ // Reduce time so they won't be marked as wol_in_progress
+ Database::exec('UPDATE remoteaccess_machine SET woltime = :faketime WHERE machineuuid IN (:fails)',
+ ['faketime' => $NOW - 95, 'fails' => $failIds]);
+ }
+ }
+ if ($num > 0) {
+ error_log("Could not wake $num clients...");
+ }
+ }
+
+}
diff --git a/modules-available/remoteaccess/install.inc.php b/modules-available/remoteaccess/install.inc.php
new file mode 100644
index 00000000..11656218
--- /dev/null
+++ b/modules-available/remoteaccess/install.inc.php
@@ -0,0 +1,60 @@
+<?php
+
+$dbret = [];
+
+$dbret[] = tableCreate('remoteaccess_group', "
+ `groupid` int(11) NOT NULL AUTO_INCREMENT,
+ `groupname` varchar(100) NOT NULL,
+ `wolcount` smallint(11) NOT NULL,
+ `passwd` varchar(100) NOT NULL,
+ `active` tinyint(1) UNSIGNED NOT NULL DEFAULT '1',
+ PRIMARY KEY (`groupid`)
+");
+
+$dbret[] = tableCreate('remoteaccess_x_location', "
+ `groupid` int(11) NOT NULL,
+ `locationid` int(11) NOT NULL,
+ PRIMARY KEY (`groupid`, `locationid`)
+");
+
+$dbret[] = tableCreate('remoteaccess_machine', "
+ `machineuuid` char(36) CHARACTER SET ascii NOT NULL,
+ `password` char(8) CHARACTER SET ascii NULL DEFAULT NULL,
+ `woltime` int(10) UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (`machineuuid`)
+");
+
+$dbret[] = tableAddConstraint('remoteaccess_x_location', 'locationid', 'location', 'locationid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$dbret[] = tableAddConstraint('remoteaccess_x_location', 'groupid', 'remoteaccess_group', 'groupid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+$dbret[] = tableAddConstraint('remoteaccess_machine', 'machineuuid', 'machine', 'machineuuid',
+ 'ON UPDATE CASCADE ON DELETE CASCADE');
+
+if (tableExists('remoteaccess_location')
+ && tableExists('remoteaccess_x_location')
+ && tableExists('remoteaccess_group')) {
+ // Migrate old version
+ $wantedIdleCount = (int)Property::get('remoteaccess.wantedclients', 0);
+ $ret = Database::exec("INSERT IGNORE INTO remoteaccess_group (groupid, groupname, wolcount, passwd)
+ SELECT l.locationid, l.locationname, $wantedIdleCount AS blu, '' AS bla FROM location l
+ INNER JOIN remoteaccess_location rl USING (locationid)");
+ if ($ret === false) {
+ finalResponse(UPDATE_FAILED, Database::lastError());
+ }
+ Property::set('remoteaccess.wantedclients', 0, 1);
+ $max = Database::queryFirst("SELECT groupid FROM remoteaccess_group ORDER BY groupid DESC LIMIT 1");
+ if ($max !== false) {
+ Database::exec("ALTER TABLE remoteaccess_group AUTO_INCREMENT = :next", ['next' => $max['groupid'] + 1]);
+ }
+ $ret = Database::exec("INSERT IGNORE INTO remoteaccess_x_location (groupid, locationid)
+ SELECT locationid, locationid FROM remoteaccess_location");
+ if ($ret === false) {
+ finalResponse(UPDATE_FAILED, Database::lastError());
+ }
+ Database::exec("DROP TABLE remoteaccess_location");
+}
+
+responseFromArray($dbret);
diff --git a/modules-available/remoteaccess/lang/de/messages.json b/modules-available/remoteaccess/lang/de/messages.json
new file mode 100644
index 00000000..fbdefd8f
--- /dev/null
+++ b/modules-available/remoteaccess/lang/de/messages.json
@@ -0,0 +1,6 @@
+{
+ "group-added": "Gruppe hinzugef\u00fcgt",
+ "group-not-found": "Gruppe {{0}} existiert nicht",
+ "group-updated": "Gruppe {{0}} wurde aktualisiert",
+ "settings-saved": "Einstellungen gespeichert"
+} \ No newline at end of file
diff --git a/modules-available/remoteaccess/lang/de/module.json b/modules-available/remoteaccess/lang/de/module.json
new file mode 100644
index 00000000..21d8ca69
--- /dev/null
+++ b/modules-available/remoteaccess/lang/de/module.json
@@ -0,0 +1,4 @@
+{
+ "module_name": "Fernzugriff",
+ "page_title": "Fernzugriff"
+} \ No newline at end of file
diff --git a/modules-available/remoteaccess/lang/de/template-tags.json b/modules-available/remoteaccess/lang/de/template-tags.json
new file mode 100644
index 00000000..b44849d6
--- /dev/null
+++ b/modules-available/remoteaccess/lang/de/template-tags.json
@@ -0,0 +1,16 @@
+{
+ "lang_add": "Hinzuf\u00fcgen",
+ "lang_allowAccessText": "IP-Adresse oder Netz in CIDR Notation, welches auf den VNC-Port des Clients zugreifen darf. (I.d.R. nur der Guacamole-Server)",
+ "lang_allowedAccessToVncPort": "Erlaubte Quelle f\u00fcr VNC-Zugriff",
+ "lang_assignLocations": "R\u00e4ume zuweisen",
+ "lang_group": "Gruppe",
+ "lang_groupListText": "Liste verf\u00fcgbarer Gruppen (\"virtuelle R\u00e4ume\")",
+ "lang_keepAvailableWol": "WoL#",
+ "lang_locationSelectionText": "Ausgew\u00e4hlte Orte werden in den Remote-Modus geschaltet (beim n\u00e4chsten Boot des Clients) und sind damit im Pool f\u00fcr den Fernzugriff.",
+ "lang_numLocs": "R\u00e4ume",
+ "lang_numberOfAvailableClients": "Anzahl bereit zu haltender Rechner",
+ "lang_numberOfAvailableText": "Wir hier eine Zahl > 0 angegeben, wird versucht mittels WOL mindestens diese Anzahl an Rechnern am Loginbildschirm bereit zu halten, um sofortigen Zugriff zu gew\u00e4hrleisten. Diese Einstellung deaktiviert keine eventuell gesetzten Reboot\/Shutdown Timeouts oder Zeitpl\u00e4ne, diese sollten also ggf. f\u00fcr die unten ausgew\u00e4hlten R\u00e4ume angepasst werden.",
+ "lang_remoteAccessSettings": "Einstellungen f\u00fcr den Fernzugriff",
+ "lang_tryVirtualizerHandover": "Versuche, VNC-Server des Virtualisierers zu verwenden",
+ "lang_tryVirtualizerText": "Wenn aktiviert wird versucht, nach dem Start einer VM die Verbindung auf den VNC-Server des Virtualisierers umzubuchen. Zumindest f\u00fcr VMware haben wir hier allerdings eher eine Verschlechterung der Performance beobachten k\u00f6nnen; au\u00dferdem bricht die Verbindung beim Handover manchmal ab -> Nur experimentell!"
+} \ No newline at end of file
diff --git a/modules-available/remoteaccess/page.inc.php b/modules-available/remoteaccess/page.inc.php
new file mode 100644
index 00000000..edbe0ff8
--- /dev/null
+++ b/modules-available/remoteaccess/page.inc.php
@@ -0,0 +1,100 @@
+<?php
+
+/*
+ * TODO TODO TODO TODO
+ * ### PERMISSIONS ###
+ * TODO TODO TODO TODO
+ */
+
+class Page_RemoteAccess 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');
+ // Add group adds a DB row and then falls through to regular saving
+ if ($action === 'add-group') {
+ Database::exec("INSERT INTO remoteaccess_group (groupname, wolcount, passwd, active)
+ VALUES ('.new', 0, '', 0)");
+ $action = 'save-settings';
+ Message::addSuccess('group-added');
+ }
+ if ($action === 'save-settings') {
+ $groups = Request::post('group', [], 'array');
+ foreach ($groups as $id => $group) {
+ Database::exec("UPDATE remoteaccess_group SET groupname = :name, wolcount = :wol,
+ passwd = :passwd, active = :active WHERE groupid = :id", [
+ 'id' => $id,
+ 'name' => isset($group['groupname']) ? $group['groupname'] : $id,
+ 'wol' => isset($group['wolcount']) ? $group['wolcount'] : 0,
+ 'passwd' => isset($group['passwd']) ? $group['passwd'] : 0,
+ 'active' => isset($group['active']) && $group['active'] ? 1 : 0,
+ ]);
+ }
+ Property::set(RemoteAccess::PROP_ALLOWED_VNC_NET, Request::post('allowed-source', '', 'string'));
+ Property::set(RemoteAccess::PROP_TRY_VIRT_HANDOVER, Request::post('virt-handover', false, 'int'));
+ Message::addSuccess('settings-saved');
+ } elseif ($action === 'set-locations') {
+ $groupid = Request::post('groupid', Request::REQUIRED, 'int');
+ $group = Database::queryFirst("SELECT groupname FROM remoteaccess_group WHERE groupid = :groupid");
+ if ($group === false) {
+ Message::addError('group-not-found', $groupid);
+ Util::redirect('?do=remoteaccess');
+ }
+ $locations = array_values(Request::post('location', [], 'array'));
+ if (empty($locations)) {
+ Database::exec("DELETE FROM remoteaccess_x_location WHERE groupid = :id", ['id' => $groupid]);
+ } else {
+ Database::exec("INSERT IGNORE INTO remoteaccess_x_location (groupid, locationid)
+ VALUES :values", ['values' => array_map(function($item) use ($groupid) { return [$groupid, $item]; }, $locations)]);
+ Database::exec("DELETE FROM remoteaccess_x_location WHERE groupid = :id AND locationid NOT IN (:locations)",
+ ['id' => $groupid, 'locations' => $locations]);
+ }
+ Message::addSuccess('group-updated', $group['groupname']);
+ }
+ if (Request::isPost()) {
+ Util::redirect('?do=remoteaccess');
+ }
+ }
+
+ protected function doRender()
+ {
+ $groupid = Request::get('groupid', false, 'int');
+ if ($groupid === false) {
+ // Edit list of groups and their settings
+ $groups = Database::queryAll("SELECT g.groupid, g.groupname, g.wolcount, g.passwd,
+ Count(l.locationid) AS locs, If(g.active, 'checked', '') AS checked
+ FROM remoteaccess_group g LEFT JOIN remoteaccess_x_location l USING (groupid)
+ GROUP BY g.groupid, g.groupname
+ ORDER BY g.groupname ASC");
+ $data = [
+ 'allowed-source' => Property::get(RemoteAccess::PROP_ALLOWED_VNC_NET),
+ 'virt-handover_checked' => Property::get(RemoteAccess::PROP_TRY_VIRT_HANDOVER) ? 'checked' : '',
+ 'groups' => $groups,
+ ];
+ Render::addTemplate('edit-settings', $data);
+ } else {
+ // Edit locations for group
+ $group = Database::queryFirst("SELECT groupid, groupname FROM remoteaccess_group WHERE groupid = :id",
+ ['id' => $groupid]);
+ if ($group === false) {
+ Message::addError('group-not-found', $groupid);
+ return;
+ }
+ $locationList = Location::getLocationsAssoc();
+ $enabled = RemoteAccess::getEnabledLocations($groupid);
+ foreach ($enabled as $lid) {
+ if (isset($locationList[$lid])) {
+ $locationList[$lid]['checked'] = 'checked';
+ }
+ }
+ Render::addTemplate('edit-group', $group + ['locations' => array_values($locationList)]);
+ }
+ }
+
+}
diff --git a/modules-available/remoteaccess/templates/edit-group.html b/modules-available/remoteaccess/templates/edit-group.html
new file mode 100644
index 00000000..2c207ca5
--- /dev/null
+++ b/modules-available/remoteaccess/templates/edit-group.html
@@ -0,0 +1,43 @@
+<h2>{{lang_assignLocations}}</h2>
+<h3>{{groupname}}</h3>
+
+<form method="post" action="?do=remoteaccess">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="groupid" value="{{groupid}}">
+
+ <div class="buttonbar pull-right">
+ <button type="submit" class="btn btn-primary" name="action" value="set-locations">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ <div class="clearfix"></div>
+
+ <div class="form-group">
+ <p>{{lang_locationSelectionText}}</p>
+ <table class="table table-condensed table-hover">
+ {{#locations}}
+ <tr>
+ <td class="slx-smallcol">
+ <div class="checkbox checkbox-inline">
+ <input type="checkbox" name="location[]" value="{{locationid}}" id="loc-check-{{locationid}}"
+ {{checked}}>
+ <label></label>
+ </div>
+ </td>
+ <td class="text-nowrap">
+ <div style="display:inline-block;width:{{depth}}em"></div>
+ <label for="loc-check-{{locationid}}">{{locationname}}</label>
+ </td>
+ </tr>
+ {{/locations}}
+ </table>
+ </div>
+ <div class="buttonbar pull-right">
+ <button type="submit" class="btn btn-primary" name="action" value="set-locations">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ <div class="clearfix"></div>
+</form>
diff --git a/modules-available/remoteaccess/templates/edit-settings.html b/modules-available/remoteaccess/templates/edit-settings.html
new file mode 100644
index 00000000..2712cf04
--- /dev/null
+++ b/modules-available/remoteaccess/templates/edit-settings.html
@@ -0,0 +1,72 @@
+<h2>{{lang_remoteAccessSettings}}</h2>
+
+<form method="post" action="?do=remoteaccess">
+ <input type="hidden" name="token" value="{{token}}">
+ <div class="form-group">
+ <label>
+ {{lang_allowedAccessToVncPort}}
+ <input type="text" class="form-control" name="allowed-source" value="{{allowed-source}}" required>
+ </label>
+ <p>{{lang_allowAccessText}}</p>
+ </div>
+ <div class="form-group">
+ <div class="checkbox">
+ <input type="checkbox" name="virt-handover" value="1"
+ id="virt-handover" {{virt-handover_checked}}>
+ <label for="virt-handover">{{lang_tryVirtualizerHandover}}</label>
+ </div>
+ <p>{{lang_tryVirtualizerText}}</p>
+ </div>
+
+ <div class="form-group">
+ <p>{{lang_groupListText}}</p>
+ <table class="table table-condensed table-hover">
+ <thead>
+ <tr>
+ <th></th>
+ <th>{{lang_group}}</th>
+ <th class="text-nowrap" width="10%">{{lang_numLocs}}</th>
+ <th class="text-nowrap" width="10%">{{lang_keepAvailableWol}}</th>
+ <th class="text-nowrap" width="13%">{{lang_password}}</th>
+ </tr>
+ </thead>
+ {{#groups}}
+ <tr>
+ <td class="slx-smallcol">
+ <div class="checkbox checkbox-inline">
+ <input type="checkbox" name="group[{{groupid}}][active]" value="1" id="group-check-{{groupid}}"
+ {{checked}}>
+ <label for="group-check-{{groupid}}"></label>
+ </div>
+ </td>
+ <td class="text-nowrap">
+ <input type="text" class="form-control" name="group[{{groupid}}][groupname]" value="{{groupname}}">
+ </td>
+ <td class="text-right text-nowrap">
+ <span class="badge">{{locs}}</span>
+ <a href="?do=remoteaccess&amp;groupid={{groupid}}" class="btn btn-xs btn-default">
+ <span class="glyphicon glyphicon-edit"></span>
+ </a>
+ </td>
+ <td>
+ <input type="number" class="form-control" name="group[{{groupid}}][wolcount]" value="{{wolcount}}">
+ </td>
+ <td>
+ <input type="text" class="form-control" name="group[{{groupid}}][passwd]" value="{{passwd}}">
+ </td>
+ </tr>
+ {{/groups}}
+ </table>
+ </div>
+ <div class="buttonbar pull-right">
+ <button type="submit" class="btn btn-success" name="action" value="add-group">
+ <span class="glyphicon glyphicon-plus"></span>
+ {{lang_add}}
+ </button>
+ <button type="submit" class="btn btn-primary" name="action" value="save-settings">
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ <div class="clearfix"></div>
+</form> \ No newline at end of file