summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--inc/util.inc.php14
-rw-r--r--modules-available/locations/inc/openingtimes.inc.php33
-rw-r--r--modules-available/locations/pages/details.inc.php21
-rw-r--r--modules-available/locations/templates/ajax-opening-location.html29
-rw-r--r--modules-available/locations/templates/location-subnets.html2
-rw-r--r--modules-available/rebootcontrol/inc/rebootcontrol.inc.php1
-rw-r--r--modules-available/rebootcontrol/inc/scheduler.inc.php131
-rw-r--r--modules-available/remoteaccess/baseconfig/getconfig.inc.php9
8 files changed, 179 insertions, 61 deletions
diff --git a/inc/util.inc.php b/inc/util.inc.php
index 81c7d807..c3e70f89 100644
--- a/inc/util.inc.php
+++ b/inc/util.inc.php
@@ -590,11 +590,23 @@ SADFACE;
{
$regex = '/
(
- (?: [\x20-\xFF] ){1,100} # ignore lower non-printable range
+ [\x20-\xFF]{1,100} # ignore lower non-printable range
)
| . # anything else
/x';
return iconv('MS-ANSI', 'UTF-8', preg_replace($regex, '$1', $string));
}
+ /**
+ * Clamp given value into [min, max] range.
+ */
+ public static function clamp(int &$value, int $min, int $max)
+ {
+ if ($value < $min) {
+ $value = $min;
+ } elseif ($value > $max) {
+ $value = $max;
+ }
+ }
+
}
diff --git a/modules-available/locations/inc/openingtimes.inc.php b/modules-available/locations/inc/openingtimes.inc.php
index 72ce92f4..18e25063 100644
--- a/modules-available/locations/inc/openingtimes.inc.php
+++ b/modules-available/locations/inc/openingtimes.inc.php
@@ -6,11 +6,12 @@ class OpeningTimes
/**
* Get opening times for given location.
* Format is the decoded JSON from DB column, i.e. currently a list of entries:
- * {
+ * <pre>{
* "days": ["Monday", "Tuesday", ...],
* "openingtime": "8:00",
* "closingtime": "20:00"
- * }
+ * }</pre>
+ * @return array|null
*/
public static function forLocation(int $locationId)
{
@@ -31,4 +32,32 @@ class OpeningTimes
return null;
}
+ /**
+ * Check whether given location is open according to openingtimes.
+ * @param int $locationId location
+ * @param int $openOffsetMin offset to apply to opening times when checking. this is subtracted from opening time
+ * @param int $closeOffsetMin offset to apply to closing times when checking. this is added to closing time
+ */
+ public static function isRoomOpen(int $locationId, int $openOffsetMin = 0, int $closeOffsetMin = 0): bool
+ {
+ $openingTimes = self::forLocation($locationId);
+ if ($openingTimes === null)
+ return true; // No opening times should mean room is always open
+ $now = time();
+ $today = date('l', $now);
+ foreach ($openingTimes as $row) {
+ foreach ($row['days'] as $day) {
+ if ($day !== $today)
+ continue; // Not today!
+ if (strtotime("today {$row['openingtime']} -$openOffsetMin minutes") > $now)
+ continue;
+ if (strtotime("today {$row['closingtime']} +$closeOffsetMin minutes") < $now)
+ continue;
+ // Bingo!
+ return true;
+ }
+ }
+ return false;
+ }
+
} \ No newline at end of file
diff --git a/modules-available/locations/pages/details.inc.php b/modules-available/locations/pages/details.inc.php
index 86bfebd6..d2ec7b24 100644
--- a/modules-available/locations/pages/details.inc.php
+++ b/modules-available/locations/pages/details.inc.php
@@ -39,9 +39,10 @@ class SubPage
$openingTimes = Request::post('openingtimes', Request::REQUIRED, 'string');
$locationid = Request::post('locationid', Request::REQUIRED, 'int');
$wol = Request::post('wol', false, 'bool');
- $woloffset = Request::post('wol-offset', 0, 'int');
+ $wolOffset = Request::post('wol-offset', 0, 'int');
$sd = Request::post('sd', false, 'bool');
- $sdoffset = Request::post('sd-offset', 0, 'int');
+ $sdOffset = Request::post('sd-offset', 0, 'int');
+ $raMode = Request::post('ra-mode', 'ALWAYS', 'string');
User::assertPermission('location.edit.openingtimes', $locationid);
@@ -93,7 +94,16 @@ class SubPage
if (Module::isAvailable('rebootcontrol')) {
// Set options
- Scheduler::setLocationOptions($locationid, $wol, $sd, $woloffset, $sdoffset);
+ if (!Scheduler::isValidRaMode($raMode)) {
+ $raMode = Scheduler::RA_ALWAYS;
+ }
+ Scheduler::setLocationOptions($locationid, [
+ 'wol' => $wol,
+ 'sd' => $sd,
+ 'wol-offset' => $wolOffset,
+ 'sd-offset' => $sdOffset,
+ 'ra-mode' => $raMode,
+ ]);
}
}
@@ -396,7 +406,7 @@ class SubPage
if (Module::get('rebootcontrol') !== false) {
$res = Database::queryFirst("SELECT action, nextexecution FROM `reboot_scheduler`
WHERE locationid = :id", ['id' => $locationId]);
- if ($res !== false) {
+ if ($res !== false && $res['nextexecution'] > 0) {
$data['next_action'] = $res['action'];
$data['next_time'] = Util::prettyTime($res['nextexecution']);
}
@@ -431,12 +441,13 @@ class SubPage
$data['rebootcontrol'] = $rebootcontrol;
if ($rebootcontrol) {
$data['scheduler-options'] = Scheduler::getLocationOptions($id);
+ $data['scheduler_' . $data['scheduler-options']['ra-mode'] . '_checked'] = 'checked';
}
echo Render::parse('ajax-opening-location', $data);
}
- private static function isSimpleMode(&$array)
+ private static function isSimpleMode(&$array): bool
{
if (empty($array))
return true;
diff --git a/modules-available/locations/templates/ajax-opening-location.html b/modules-available/locations/templates/ajax-opening-location.html
index 967e111c..861bef65 100644
--- a/modules-available/locations/templates/ajax-opening-location.html
+++ b/modules-available/locations/templates/ajax-opening-location.html
@@ -1,5 +1,5 @@
<div>
- <h3>{{lang_openingTime}}</h3>
+ <h4>{{lang_openingTime}}</h4>
<div class="checkbox">
<input id="oi{{id}}" class="openingtimes-inherited"
type="checkbox" name="openingtimes-inherited" value="1" {{openingtimes_inherited}}>
@@ -129,7 +129,7 @@
</div>
{{#rebootcontrol}}
-<hr>
+<h4>{{lang_automatedMachineActions}}</h4>
<div class="row wol">
<div class="col-sm-4">
<div class="checkbox checkbox-inline">
@@ -164,9 +164,32 @@
</div>
</div>
</div>
+<h4>{{lang_remoteAccessConstraints}}</h4>
+<div class="slx-smallspace">
+ <div class="radio">
+ <input id="ra-ALWAYS-check-{{id}}" name="ra-mode" value="ALWAYS" type="radio"
+ {{scheduler_ALWAYS_checked}}>
+ <label for="ra-ALWAYS-check-{{id}}">{{lang_remoteAccessNoRestriction}}</label>
+ </div>
+</div>
+<div class="slx-smallspace">
+ <div class="radio">
+ <input id="ra-SELECTIVE-check-{{id}}" name="ra-mode" value="SELECTIVE" type="radio"
+ {{scheduler_SELECTIVE_checked}}>
+ <label for="ra-SELECTIVE-check-{{id}}">{{lang_remoteAccessOnlyWhenClosed}}</label>
+ </div>
+</div>
+<div class="slx-smallspace">
+ <div class="radio">
+ <input id="ra-NEVER-check-{{id}}" name="ra-mode" value="NEVER" type="radio"
+ {{scheduler_NEVER_checked}}>
+ <label for="ra-NEVER-check-{{id}}">{{lang_remoteAccessNever}}</label>
+ </div>
+</div>
+<p><i>{{lang_remoteAccessHelp}}</i></p>
{{/rebootcontrol}}
-<script type="application/javascript">
+<script>
(function() {
var $loc = $('#openingTimesModal{{id}}');
diff --git a/modules-available/locations/templates/location-subnets.html b/modules-available/locations/templates/location-subnets.html
index 80e63abf..85c5a744 100644
--- a/modules-available/locations/templates/location-subnets.html
+++ b/modules-available/locations/templates/location-subnets.html
@@ -150,7 +150,7 @@
<input type="hidden" name="openingtimes" value="">
<input type="hidden" name="locationid" value="{{locationid}}">
- <div class="modal-header">{{locationname}}</div>
+ <div class="modal-header"><h3>{{locationname}}</h3></div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{lang_close}}</button>
diff --git a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
index b5f47c1d..d68e4dea 100644
--- a/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
+++ b/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
@@ -37,7 +37,6 @@ class RebootControl
* @param array $list list of clients containing each keys 'machineuuid', 'clientip' and 'locationid'
* @param string $mode reboot mode: RebootControl::REBOOT ::KEXEC_REBOOT or ::SHUTDOWN
* @param int $minutes delay in minutes for action
- * @param int $locationId meta data only: locationId of clients
* @return array|false the task, or false if it could not be started
*/
public static function execute($list, $mode, $minutes)
diff --git a/modules-available/rebootcontrol/inc/scheduler.inc.php b/modules-available/rebootcontrol/inc/scheduler.inc.php
index 7da4b46b..9e91df25 100644
--- a/modules-available/rebootcontrol/inc/scheduler.inc.php
+++ b/modules-available/rebootcontrol/inc/scheduler.inc.php
@@ -3,9 +3,13 @@
class Scheduler
{
- const SHUTDOWN = 'SHUTDOWN';
- const REBOOT = 'REBOOT';
- const WOL = 'WOL';
+ const ACTION_SHUTDOWN = 'SHUTDOWN';
+ const ACTION_REBOOT = 'REBOOT';
+ const ACTION_WOL = 'WOL';
+ const RA_NEVER = 'NEVER';
+ const RA_SELECTIVE = 'SELECTIVE';
+ const RA_ALWAYS = 'ALWAYS';
+ const SCHEDULE_OPTIONS_DEFAULT = ['wol' => false, 'sd' => false, 'wol-offset' => 0, 'sd-offset' => 0, 'ra-mode' => self::RA_ALWAYS];
/**
* @param int $locationid ID of location to delete WOL/shutdown settings for
@@ -17,7 +21,7 @@ class Scheduler
}
/**
- * Calculate next time the given time description is reached
+ * Calculate next time the given time description is reached.
* @param int $now unix timestamp representing now
* @param string $day Name of weekday
* @param string $time Time, fi. 13:45
@@ -42,18 +46,23 @@ class Scheduler
*/
private static function calculateNext(array $options, array $openingTimes)
{
- if ((!$options['wol'] && !$options['sd']) || empty($openingTimes))
+ // If ra-mode is selective, still execute even if wol and shutdown is disabled,
+ // because we still want to shutdown any sessions in the wrong runmode then
+ $selectiveRa = ($options['ra-mode'] === self::RA_SELECTIVE);
+ if ((!$options['wol'] && !$options['sd'] && !$selectiveRa) || empty($openingTimes))
return false;
$now = time();
$events = [];
+ $findWol = $options['wol'] || $options['ra-mode'] === self::RA_SELECTIVE;
+ $findSd = $options['sd'] || $options['ra-mode'] === self::RA_SELECTIVE;
foreach ($openingTimes as $row) {
foreach ($row['days'] as $day) {
- if ($options['wol']) {
- $events[] = ['action' => self::WOL,
+ if ($findWol) {
+ $events[] = ['action' => self::ACTION_WOL,
'time' => self::calculateTimestamp($now, $day, $row['openingtime'])];
}
- if ($options['sd']) {
- $events[] = ['action' => self::SHUTDOWN,
+ if ($findSd) {
+ $events[] = ['action' => self::ACTION_SHUTDOWN,
'time' => self::calculateTimestamp($now, $day, $row['closingtime'])];
}
}
@@ -67,9 +76,9 @@ class Scheduler
$prev = PHP_INT_MAX;
for ($i = count($events) - 1; $i >= 0; --$i) {
$event =& $events[$i];
- if ($event['action'] === self::WOL) {
+ if ($event['action'] === self::ACTION_WOL) {
$event['time'] -= $wolOffset;
- } elseif ($event['action'] === self::SHUTDOWN) {
+ } elseif ($event['action'] === self::ACTION_SHUTDOWN) {
$event['time'] += $sdOffset;
} else {
error_log('BUG Unhandled event type ' . $event['action']);
@@ -94,9 +103,9 @@ class Scheduler
// If difference to next event is < 5 min, ignore.
continue;
}
- if ($diff < 900 && $event['action'] === self::SHUTDOWN && $events[$i + 1]['action'] === self::WOL) {
+ if ($diff < 900 && $event['action'] === self::ACTION_SHUTDOWN && $events[$i + 1]['action'] === self::ACTION_WOL) {
// If difference to next WOL is < 15 min and this is a shutdown, reboot instead.
- $res['action'] = self::REBOOT;
+ $res['action'] = self::ACTION_REBOOT;
$res['time'] = $event['time'];
} else {
// Use first event.
@@ -119,7 +128,7 @@ class Scheduler
WHERE s.nextexecution < :now AND s.nextexecution > 0", ['now' => $now]);
foreach ($res as $row) {
// Calculate next_execution for the event and update DB.
- $options = json_decode($row['options'], true);
+ $options = json_decode($row['options'], true) + self::SCHEDULE_OPTIONS_DEFAULT;
// Determine proper opening times by waling up tree
$openingTimes = OpeningTimes::forLocation($row['locationid']);
if ($openingTimes !== null) {
@@ -128,24 +137,61 @@ class Scheduler
// Weird clock drift? Server offline for a while? Do nothing.
if ($row['nextexecution'] + 900 < $now)
continue;
- self::executeCronForLocation($row['locationid'], $row['action']);
+ $selectiveRa = ($options['ra-mode'] === self::RA_SELECTIVE);
+ // Now, if selective remote access is active, we might modify the actual event:
+ if ($selectiveRa) {
+ // If this is WOL, and WOL is actually enabled, then reboot any running machines
+ // in remoteaccess mode, in addition to waking the others, so they exit remote access mode.
+ if ($row['action'] === Scheduler::ACTION_WOL && $options['wol']) {
+ self::executeCronForLocation($row['locationid'], Scheduler::ACTION_REBOOT, 'remoteaccess');
+ self::executeCronForLocation($row['locationid'], Scheduler::ACTION_WOL);
+ }
+ // If this is WOL, and WOL is disabled, shut down any running machines, this is so
+ // anybody walking into this room will not mess with a user's session by yanking the
+ // power cord etc.
+ if ($row['action'] === Scheduler::ACTION_WOL && !$options['wol']) {
+ self::executeCronForLocation($row['locationid'], Scheduler::ACTION_SHUTDOWN, 'remoteaccess');
+ }
+ // If this is SHUTDOWN, and SHUTDOWN is enabled, leave it at that.
+ if ($row['action'] === Scheduler::ACTION_SHUTDOWN && $options['sd']) {
+ self::executeCronForLocation($row['locationid'], Scheduler::ACTION_SHUTDOWN);
+ }
+ // If this is SHUTDOWN, and SHUTDOWN is disabled, do a reboot, so the machine ends up
+ // in the proper runmode.
+ if ($row['action'] === Scheduler::ACTION_SHUTDOWN && !$options['sd']) {
+ self::executeCronForLocation($row['locationid'], Scheduler::ACTION_REBOOT, '');
+ }
+ } else {
+ // Regular case, no selective remoteaccess – just do what the cron entry says
+ self::executeCronForLocation($row['locationid'], $row['action']);
+ }
}
}
/**
* Execute the given action for the given location.
+ * @param int $locationId location
+ * @param string $action action to perform, Scheduler::*
+ * @param string|null $onlyRunmode if not null, only process running clients in given runmode
+ * @return void
*/
- private static function executeCronForLocation(int $locationId, string $action)
+ private static function executeCronForLocation(int $locationId, string $action, string $onlyRunmode = null)
{
- $machines = Database::queryAll("SELECT machineuuid, clientip, macaddr, locationid FROM machine
+ if ($onlyRunmode === null) {
+ $machines = Database::queryAll("SELECT machineuuid, clientip, macaddr, locationid FROM machine
WHERE locationid = :locid", ['locid' => $locationId]);
+ } else {
+ $machines = Database::queryAll("SELECT machineuuid, clientip, macaddr, locationid FROM machine
+ WHERE locationid = :locid AND currentrunmode = :runmode AND state <> 'OFFLINE'",
+ ['locid' => $locationId, 'runmode' => $onlyRunmode]);
+ }
if (empty($machines))
return;
- if ($action === Scheduler::SHUTDOWN) {
+ if ($action === Scheduler::ACTION_SHUTDOWN) {
RebootControl::execute($machines, RebootControl::SHUTDOWN, 0);
- } elseif ($action === Scheduler::WOL) {
+ } elseif ($action === Scheduler::ACTION_WOL) {
RebootControl::wakeMachines($machines);
- } elseif ($action === Scheduler::REBOOT) {
+ } elseif ($action === Scheduler::ACTION_REBOOT) {
RebootControl::execute($machines, RebootControl::REBOOT, 0);
} else {
EventLog::warning("Invalid action '$action' in schedule for location " . $locationId);
@@ -155,49 +201,32 @@ class Scheduler
/**
* Get current settings for given location, or false if none.
* @param int $id
- * @return false|array
*/
- public static function getLocationOptions(int $id)
+ public static function getLocationOptions(int $id): array
{
$res = Database::queryFirst("SELECT options FROM `reboot_scheduler`
WHERE locationid = :id", ['id' => $id]);
if ($res !== false) {
- return json_decode($res['options'], true);
+ return (json_decode($res['options'], true) ?? []) + self::SCHEDULE_OPTIONS_DEFAULT;
}
- return false;
+ return self::SCHEDULE_OPTIONS_DEFAULT;
}
/**
* Write new WOL/Shutdown options for given location.
* @param int $locationId
- * @param bool $wol whether WOL is enabled
- * @param bool $sd whether Shutdown is enabled
- * @param int $wolOffset how many minutes prior to opening time the WOL should be triggered
- * @param int $sdOffset how many minutes after closing time a shutdown should be triggered
+ * @param array $options 'wol' 'sd' 'wol-offset' 'sd-offset' 'ra-mode'
*/
- public static function setLocationOptions(int $locationId, bool $wol, bool $sd, int $wolOffset, int $sdOffset)
+ public static function setLocationOptions(int $locationId, array $options)
{
+ $options += self::SCHEDULE_OPTIONS_DEFAULT;
$openingTimes = OpeningTimes::forLocation($locationId);
- if (!$wol && !$sd) {
+ if (!$options['wol'] && !$options['sd'] && $options['ra-mode'] === self::RA_ALWAYS) {
self::deleteSchedule($locationId);
} else {
- // Sanity checks
- if ($wolOffset > 60) {
- $wolOffset = 60;
- } elseif ($wolOffset < 0) {
- $wolOffset = 0;
- }
- if ($sdOffset > 60) {
- $sdOffset = 60;
- } elseif ($sdOffset < 0) {
- $sdOffset = 0;
- }
- $options = [
- 'wol' => $wol,
- 'sd' => $sd,
- 'wol-offset' => $wolOffset,
- 'sd-offset' => $sdOffset,
- ];
+ // Sanitize
+ Util::clamp($options['wol-offset'], 0, 60);
+ Util::clamp($options['sd-offset'], 0, 60);
$json_options = json_encode($options);
// Write settings, reset schedule
Database::exec("INSERT INTO `reboot_scheduler` (locationid, action, nextexecution, options)
@@ -228,7 +257,7 @@ class Scheduler
*/
private static function updateScheduleSingle(int $locationid, array $options, array $openingTimes)
{
- if (!$options['wol'] && !$options['sd']) {
+ if (!$options['wol'] && !$options['sd'] && $options['ra-mode'] === self::RA_ALWAYS) {
self::deleteSchedule($locationid);
return;
}
@@ -284,6 +313,7 @@ class Scheduler
if (!is_array($options)) {
trigger_error("Invalid options for lid:$locationId", E_USER_WARNING);
} else {
+ $options += self::SCHEDULE_OPTIONS_DEFAULT;
self::updateScheduleSingle($locationId, $options, $openingTimes);
}
}
@@ -292,4 +322,9 @@ class Scheduler
}
}
+ public static function isValidRaMode(string $raMode): bool
+ {
+ return $raMode === self::RA_ALWAYS || $raMode === self::RA_NEVER || $raMode === self::RA_SELECTIVE;
+ }
+
} \ No newline at end of file
diff --git a/modules-available/remoteaccess/baseconfig/getconfig.inc.php b/modules-available/remoteaccess/baseconfig/getconfig.inc.php
index 3d0c4470..84923dc6 100644
--- a/modules-available/remoteaccess/baseconfig/getconfig.inc.php
+++ b/modules-available/remoteaccess/baseconfig/getconfig.inc.php
@@ -18,6 +18,15 @@
['lid' => $locationId], true); // TODO Remove true after next point release (2020-05-12)
if ($ret === false)
return;
+ // Special case – location admin can limit accessibility of this machine to never, or only when room is closed
+ $opts = Scheduler::getLocationOptions($locationId);
+ if ($opts['ra-mode'] === Scheduler::RA_NEVER)
+ return; // Completely disallowed
+ if ($opts['ra-mode'] === Scheduler::RA_SELECTIVE) {
+ // Only when room is closed
+ if (OpeningTimes::isRoomOpen($locationId, $opts['wol-offset'], $opts['sd-offset']))
+ return; // Open, do not interfere with ongoing lectures etc., do nothing
+ }
// TODO Properly merge
if (Property::get(RemoteAccess::PROP_TRY_VIRT_HANDOVER)) {
ConfigHolder::add("SLX_REMOTE_VNC", 'vmware virtualbox');