diff --git a/app/controllers/redirect.php b/app/controllers/redirect.php index 1c7b39568..6f1d01721 100644 --- a/app/controllers/redirect.php +++ b/app/controllers/redirect.php @@ -4,7 +4,9 @@ use Opencast\Models\VideosShares; use Opencast\Models\LTI\LtiHelper; use Opencast\Models\REST\ApiEventsClient; +use Opencast\Models\REST\RestClient; use Opencast\Errors\Error; +use Opencast\Errors\MaintenanceError; class RedirectController extends Opencast\Controller { @@ -231,10 +233,13 @@ private function getLtiLinkFor($lti, $endpoint) } /** - * Load preview image from opencast and show it + * Serves a video preview image for the given video token. * - * @param String $episode_id + * Checks user permissions, fetches the preview image from Opencast if available, + * or falls back to a default image. Sets the appropriate content type header and outputs the image. * + * @param string $token The video token. + * @throws \Exception If access is denied. * @return void */ public function preview_action($token) @@ -247,17 +252,36 @@ public function preview_action($token) throw new \Exception('Access denied!'); } - // get preview image - $api_events = ApiEventsClient::getInstance($video->config_id); + $plugin_path = $GLOBALS['ABSOLUTE_PATH_STUDIP'] . $this->plugin->getPluginPath(); + $default_image_relative_path = '/assets/images/default-preview.png'; + $image_default_url = URLHelper::getURL($this->plugin->getPluginUrl() . $default_image_relative_path); + $image_mimetype = mime_content_type($plugin_path . $default_image_relative_path); + $image_content = null; + if ($video->preview) { + $rest_client = null; + try { + $rest_client = ApiEventsClient::getInstance($video->config_id); + } catch (\Throwable $th) { + if ($th instanceof MaintenanceError) { + $rest_client = RestClient::createReadOnlyInstance($video->config_id); + } + } + + if (!empty($rest_client)) { + $response = $rest_client->fileRequest($video->preview); + $image_content = $response['body'] ?? null; + $image_mimetype = $response['mimetype'] ?? $image_mimetype; + } + } - $image = $video->preview ? - : URLHelper::getURL($this->plugin->getPluginUrl() . '/assets/images/default-preview.png'); + if (empty($image_content)) { + $image_content = file_get_contents($image_default_url); + } - $response = $api_events->fileRequest($image); + header('Content-Type: '. $image_mimetype); - header('Content-Type: '. $response['mimetype'][0]); + echo $image_content; - echo $response['body']; die; } } diff --git a/assets/css/opencast.scss b/assets/css/opencast.scss index 2d6282f94..4a8609e77 100644 --- a/assets/css/opencast.scss +++ b/assets/css/opencast.scss @@ -1477,4 +1477,18 @@ label.oc--file-upload { .oc--loading-indicator { left: 50%; } -} \ No newline at end of file +} + +/* * * * * * * * * * * */ +/* MESSAGE BOX */ +/* * * * * * * * * * * */ + +.messagebox { + .messagebox-html-container { + font-weight: 400 !important; + margin: 0px !important; + > p { + margin: 0px !important; + } + } +} diff --git a/cronjobs/opencast_clear_recycle_bin.php b/cronjobs/opencast_clear_recycle_bin.php index cb1561d68..224e569ee 100644 --- a/cronjobs/opencast_clear_recycle_bin.php +++ b/cronjobs/opencast_clear_recycle_bin.php @@ -46,8 +46,8 @@ public function execute($last_result, $parameters = array()) foreach ($videos_by_config as $config_id => $videos) { echo 'Working on config with id #' . $config_id . "\n"; - // Checking the connection. - if (!$this->isOpencastReachable($config_id)) { + // Checking the connection and maintenance status. + if (!$this->isOpencastReachable($config_id) || $this->isOpencastUnderMaintenance($config_id)) { continue; } diff --git a/cronjobs/opencast_discover_videos.php b/cronjobs/opencast_discover_videos.php index dc085ca19..64c745d33 100644 --- a/cronjobs/opencast_discover_videos.php +++ b/cronjobs/opencast_discover_videos.php @@ -51,7 +51,8 @@ public function execute($last_result, $parameters = array()) foreach ($configs as $config) { echo 'Working on config with id #' . $config->id . "\n"; - if (!$this->isOpencastReachable($config->id)) { + // Checking the connection and maintenance status. + if (!$this->isOpencastReachable($config->id) || $this->isOpencastUnderMaintenance($config->id)) { continue; } diff --git a/cronjobs/opencast_refresh_scheduling.php b/cronjobs/opencast_refresh_scheduling.php index 2de1260c9..6aa51e94f 100644 --- a/cronjobs/opencast_refresh_scheduling.php +++ b/cronjobs/opencast_refresh_scheduling.php @@ -55,7 +55,8 @@ public function execute($last_result, $parameters = array()) echo 'Working on config with id #' . $config_id . "\n"; - if (!$this->isOpencastReachable($config_id)) { + // Checking the connection and maintenance status. + if (!$this->isOpencastReachable($config_id) || $this->isOpencastUnderMaintenance($config_id)) { continue; } diff --git a/cronjobs/opencast_sync_acls.php b/cronjobs/opencast_sync_acls.php index c98db0b38..4c20bdad5 100644 --- a/cronjobs/opencast_sync_acls.php +++ b/cronjobs/opencast_sync_acls.php @@ -41,7 +41,8 @@ public function execute($last_result, $parameters = array()) foreach ($configs as $config) { echo 'Working on config with id #' . $config->id . "\n"; - if (!$this->isOpencastReachable($config->id)) { + // Checking the connection and maintenance status. + if (!$this->isOpencastReachable($config->id) || $this->isOpencastUnderMaintenance($config->id)) { continue; } diff --git a/cronjobs/opencast_sync_playlists.php b/cronjobs/opencast_sync_playlists.php index 5c3a6c5b4..06da49f75 100644 --- a/cronjobs/opencast_sync_playlists.php +++ b/cronjobs/opencast_sync_playlists.php @@ -35,7 +35,8 @@ public function execute($last_result, $parameters = array()) foreach ($configs as $config) { echo 'Working on config with id #' . $config->id . "\n"; - if (!$this->isOpencastReachable($config->id)) { + // Checking the connection and maintenance status. + if (!$this->isOpencastReachable($config->id) || $this->isOpencastUnderMaintenance($config->id)) { continue; } diff --git a/lib/Errors/MaintenanceError.php b/lib/Errors/MaintenanceError.php new file mode 100644 index 000000000..3918a7a1f --- /dev/null +++ b/lib/Errors/MaintenanceError.php @@ -0,0 +1,12 @@ +isUnderMaintenance(); + if ($maintenance_active) { + echo $this->maintenance_message . "\n"; + return true; + } + } + return false; + } } diff --git a/lib/Models/Config.php b/lib/Models/Config.php index d6b1fc7fe..4e4d783bd 100644 --- a/lib/Models/Config.php +++ b/lib/Models/Config.php @@ -16,6 +16,10 @@ class Config extends \SimpleOrMap 'lti_consumerkey', 'lti_consumersecret', 'debug', 'ssl_ignore_cert_errors', 'episode_id_role_access' ]; + const MAINTENANCE_MODE_OFF = 'off'; + const MAINTENANCE_MODE_ON = 'on'; + const MAINTENANCE_MODE_READONLY = 'read-only'; + protected static function configure($config = []) { $config['db_table'] = 'oc_config'; @@ -28,27 +32,62 @@ protected static function configure($config = []) } /** - * function getConfigForService - retries configutation for a given REST-Service-Client - * - * @param string $service_type - client label + * Returns the merged configuration and endpoint data for a given service type and config ID. * - * @return array configuration for corresponding client + * Retrieves the Opencast configuration and endpoint for the specified service type and configuration ID, + * taking maintenance mode into account. Returns false if configuration or endpoint is missing, + * or null if access is not allowed due to maintenance restrictions. * + * @param string $service_type The type of Opencast service (e.g., 'ingest', 'search'). + * @param int $config_id The configuration ID (default: 1). + * @return array|false|null Merged configuration and endpoint data, false if not found, or null if not accessible. + * @throws \Exception In case something goes wrong! */ public static function getConfigForService($service_type, $config_id = 1) { if (isset($service_type)) { - $config = Endpoints::findOneBySQL( + $result = []; + + $oc_config = self::find($config_id); + $endpoint_config = Endpoints::findOneBySQL( 'service_type = ? AND config_id = ?' , [$service_type, $config_id] ); - if ($config) { - return $config->toArray() + self::find($config_id)->toArray(); - } else { + if (empty($oc_config) || empty($endpoint_config)) { return false; } + list($maintenance_on, $maintenance_readonly) = $oc_config->isUnderMaintenance(); + + $can_access = !$maintenance_on || + ($maintenance_readonly && in_array($service_type, RESTConfig::ENGAGE_NODE_SERVICE_TYPES)); + + if (!$can_access) { + return null; + } + + $oc_config_array = $oc_config->toArray(); + $endpoint_config_array = $endpoint_config->toArray(); + + // Here we need to replace the service_url with the maintenance_engage_url_fallback (1. priority) if provided in config + // or the endpoint service_url (2. priority) + if ($maintenance_readonly) { + $replacing_server_url = $oc_config_array['maintenance_engage_url_fallback']; + if (empty($replacing_server_url)) { + $replacing_server_url = $endpoint_config_array['service_url']; + } + $replacing_server_url_parsed = parse_url($replacing_server_url); + + $replacing_server_url_clean = $replacing_server_url_parsed['scheme'] . '://'. $replacing_server_url_parsed['host'] + . (isset($replacing_server_url_parsed['port']) ? ':' . $replacing_server_url_parsed['port'] : ''); + + $oc_config_array['service_url'] = $replacing_server_url_clean; + } + + $result = $endpoint_config_array + $oc_config_array; + + return $result; } else { throw new \Exception(_("Es wurde kein Servicetyp angegeben.")); } @@ -163,6 +202,18 @@ public function updateSettings($json) */ public function updateEndpoints() { + + list($maintenance_on, $maintenance_readonly) = $this->isUnderMaintenance(); + + // Prevent updating endpoints if the server is under maintenance. + if ($maintenance_on) { + $message = [ + 'type' => 'warning', + 'text' => _('Diese Opencast-Instanz ist derzeit im Wartungsmodus, daher können Endpunkte nicht aktualisiert werden.') + ]; + return $message; + } + $service_url = parse_url($this->service_url); // check the selected url for validity @@ -257,7 +308,7 @@ public function updateEndpoints() } // create new entries for workflow_config table - WorkflowConfig::createAndUpdateByConfigId($this->id, $workflows); + WorkflowConfig::createAndUpdateByConfigId($this->id); $success_message[] = sprintf( _('Die Opencast-Installation "%s" wurde erfolgreich konfiguriert.'), @@ -282,4 +333,22 @@ public function updateEndpoints() return $message; } + + /** + * Checks if the Opencast instance is in maintenance mode. + * + * @param bool $with_keys Whether to return an associative array with keys ('active', 'read_only'). + * @return array [maintenance_on, maintenance_readonly] or ['active' => bool, 'read_only' => bool] if $with_keys is true. + */ + public function isUnderMaintenance($with_keys = false) + { + $maintenance_on = ($this->maintenance_mode === self::MAINTENANCE_MODE_ON + || $this->maintenance_mode === self::MAINTENANCE_MODE_READONLY); + + $res = [ + 'active' => $maintenance_on, + 'read_only' => $this->maintenance_mode === self::MAINTENANCE_MODE_READONLY + ]; + return $with_keys ? $res : array_values($res); + } } diff --git a/lib/Models/LTI/LtiHelper.php b/lib/Models/LTI/LtiHelper.php index 1add5d01e..d8a8e8a9a 100644 --- a/lib/Models/LTI/LtiHelper.php +++ b/lib/Models/LTI/LtiHelper.php @@ -5,6 +5,7 @@ use Opencast\Models\Config; use Opencast\Models\Endpoints; use Opencast\Providers\Perm; +use Opencast\Models\REST\Config as RESTConfig; /** * LTI Helper class to create launch data @@ -22,15 +23,27 @@ class LtiHelper public static function getLtiLinks($config_id) { $links = []; - $endpoints = Endpoints::findByConfig_id($config_id); $config = Config::find($config_id); + list($maintenance_active, $read_only) = $config->isUnderMaintenance(); + + if ($maintenance_active && !$read_only) { + return []; + } + + $endpoints = Endpoints::findByConfig_id($config_id); + foreach ($endpoints as $endpoint) { // skip 'services' endpoints if ($endpoint->service_type == 'services') { continue; } + // In read-only maintenance mode, we only allow engage node services! + if ($read_only && !in_array($endpoint->service_type, RESTConfig::ENGAGE_NODE_SERVICE_TYPES)) { + continue; + } + $url = parse_url($endpoint['service_url']); $lti_url = $url['scheme'] . '://'. $url['host'] diff --git a/lib/Models/REST/ApiEventsClient.php b/lib/Models/REST/ApiEventsClient.php index 4bf95f5d6..61cd27480 100644 --- a/lib/Models/REST/ApiEventsClient.php +++ b/lib/Models/REST/ApiEventsClient.php @@ -1,23 +1,19 @@ serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceName = 'ApiEvents'; + $this->serviceType = 'apievents'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/ApiPlaylistsClient.php b/lib/Models/REST/ApiPlaylistsClient.php index c72d62672..c2552aa98 100644 --- a/lib/Models/REST/ApiPlaylistsClient.php +++ b/lib/Models/REST/ApiPlaylistsClient.php @@ -1,21 +1,16 @@ serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceName = 'ApiPlaylists'; + $this->serviceType = 'apiplaylists'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/ApiWorkflowsClient.php b/lib/Models/REST/ApiWorkflowsClient.php index 2bc3668f4..52c7cb43a 100644 --- a/lib/Models/REST/ApiWorkflowsClient.php +++ b/lib/Models/REST/ApiWorkflowsClient.php @@ -1,21 +1,16 @@ serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceName = 'ApiWorkflows'; + $this->serviceType = 'apiworkflows'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/CaptureAgentAdminClient.php b/lib/Models/REST/CaptureAgentAdminClient.php index 2a9c96188..f0e48c151 100644 --- a/lib/Models/REST/CaptureAgentAdminClient.php +++ b/lib/Models/REST/CaptureAgentAdminClient.php @@ -1,8 +1,6 @@ serviceName = 'CaptureAgentAdminClient'; - - if ($config = Config::getConfigForService('capture-admin', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'capture-admin'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** * Retrieves capture agents of connected opencast - * + * * @return array|boolean array of capture agent list or false if unable to get. */ public function getCaptureAgents() { $response = $this->opencastApi->captureAdmin->getAgents(); - + if ($response['code'] == 200) { return $this->sanitizeAgents($response['body']); } @@ -36,15 +30,15 @@ public function getCaptureAgents() /** * Retrieves the capabilities of a given capture agent - * + * * @param string $agent_name name of capture agent - * + * * @return object|boolean capability object, or false if unable to get */ public function getCaptureAgentCapabilities($agent_name) { $response = $this->opencastApi->captureAdmin->getAgentCapabilities($agent_name); - + if ($response['code'] == 200) { $capability = $response['body']; $x = 'properties-response'; @@ -56,9 +50,9 @@ public function getCaptureAgentCapabilities($agent_name) /** * Sanitizes the list of capture agents. - * + * * @param object $agents the list of agents - * + * * @return array agents array list */ private function sanitizeAgents($agents) @@ -66,7 +60,7 @@ private function sanitizeAgents($agents) if (!isset($agents->agents->agent) || empty($agents->agents->agent)) { return []; } - + if (is_array($agents->agents->agent)) { return $agents->agents->agent; } diff --git a/lib/Models/REST/Config.php b/lib/Models/REST/Config.php index b058c94aa..71b3a6004 100644 --- a/lib/Models/REST/Config.php +++ b/lib/Models/REST/Config.php @@ -22,6 +22,11 @@ class Config 'org.opencastproject.workflow' => 'workflow', ]; + const ENGAGE_NODE_SERVICE_TYPES = [ + 'play', + 'search', + ]; + /** * Get the connected opencast instance version * diff --git a/lib/Models/REST/IngestClient.php b/lib/Models/REST/IngestClient.php index 841c28172..7a45e4b68 100644 --- a/lib/Models/REST/IngestClient.php +++ b/lib/Models/REST/IngestClient.php @@ -1,8 +1,6 @@ serviceName = 'IngestClient'; - - if ($config = Config::getConfigForService('ingest', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'ingest'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/RestClient.php b/lib/Models/REST/RestClient.php index c6e7686f4..3f919d435 100644 --- a/lib/Models/REST/RestClient.php +++ b/lib/Models/REST/RestClient.php @@ -5,6 +5,7 @@ namespace Opencast\Models\REST; use Opencast\Models\Config; +use Opencast\Errors\MaintenanceError; use Opencast\Errors\RESTError; use OpencastApi\Opencast; use OpencastApi\Rest\OcRestClient; @@ -27,6 +28,7 @@ class RestClient public $ocRestClient; public $serviceName = 'ParentRestClientClass'; + public $serviceType; /** * Get singleton instance of client @@ -113,8 +115,44 @@ public function fileRequest($file_url) } $result['body'] = $body; - $result['mimetype'] = $response->getHeader('Content-Type'); + $result['mimetype'] = $response->getHeaderLine('Content-Type'); return $result; } + + /** + * Retrieves the configuration for the client based on service type and config ID. + * + * Throws a MaintenanceError if the config is null, or a generic Exception if the config is invalid. + * + * @param int $config_id The configuration ID. + * @return array The configuration array. + * @throws MaintenanceError|\Exception + */ + protected function getConfigForClient($config_id) + { + $config = Config::getConfigForService($this->serviceType, $config_id); + if (is_null($config)) { + throw new MaintenanceError(); + } else if ($config === false) { + throw new \Exception ($this->serviceName . ': ' + . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); + } + return $config; + } + + /** + * Creates a read-only RestClient instance for the given config ID. + * + * Uses the 'search' service type to fetch configuration and returns a new instance, + * or null if configuration is missing. + * + * @param int $config_id The configuration ID. + * @return static|null The read-only RestClient instance or null if not available. + */ + public static function createReadOnlyInstance($config_id) + { + $read_only_config = Config::getConfigForService('search', $config_id); + return !empty($read_only_config) ? new static($read_only_config) : null; + } } diff --git a/lib/Models/REST/SchedulerClient.php b/lib/Models/REST/SchedulerClient.php index 4fd696f52..da6769809 100644 --- a/lib/Models/REST/SchedulerClient.php +++ b/lib/Models/REST/SchedulerClient.php @@ -2,29 +2,25 @@ namespace Opencast\Models\REST; -use Opencast\Models\Config; - class SchedulerClient extends RestClient { + + public static $me; public function __construct($config_id = 1) { $this->serviceName = 'SchedulerClient'; - - if ($config = Config::getConfigForService('recordings', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'recordings'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** * Deletes an event - * + * * @param string $event_id the event id - * + * * @return boolean success or not */ public function deleteEvent($event_id) @@ -39,7 +35,7 @@ public function deleteEvent($event_id) /** * Updates an event - * + * * @param string $event_id the event id * @param int $start start of the event * @param int $end end of the event @@ -48,7 +44,7 @@ public function deleteEvent($event_id) * @param string $mediaPackage mediapackage * @param string|array $wfproperties workflow properties * @param string|array $agentparameters agent params - * + * * @return boolean success or not */ public function updateEvent($event_id, $start = 0, $end = 0, $agent = '', $users = '', $mediaPackage = '', $wfproperties = '', $agentparameters = '') diff --git a/lib/Models/REST/SeriesClient.php b/lib/Models/REST/SeriesClient.php index e9cd132ab..ce610ebe2 100644 --- a/lib/Models/REST/SeriesClient.php +++ b/lib/Models/REST/SeriesClient.php @@ -2,7 +2,6 @@ namespace Opencast\Models\REST; -use Opencast\Models\Config; use Opencast\Models\Helpers; class SeriesClient extends RestClient @@ -12,13 +11,9 @@ class SeriesClient extends RestClient public function __construct($config_id = 1) { $this->serviceName = 'Series'; - - if ($config = Config::getConfigForService('series', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'series'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/ServicesClient.php b/lib/Models/REST/ServicesClient.php index cc8818c63..90a705913 100644 --- a/lib/Models/REST/ServicesClient.php +++ b/lib/Models/REST/ServicesClient.php @@ -2,8 +2,6 @@ namespace Opencast\Models\REST; -use Opencast\Models\Config; - class ServicesClient extends RestClient { public static $me; @@ -11,15 +9,9 @@ class ServicesClient extends RestClient public function __construct($config_id = 1, $custom_config = null) { $this->serviceName = 'ServicesClient'; - - if ($custom_config) { - parent::__construct($custom_config); - } else if ($config = Config::getConfigForService('services', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'services'; + $config = $custom_config ?? $this->getConfigForClient($config_id); + parent::__construct($config); } /** diff --git a/lib/Models/REST/WorkflowClient.php b/lib/Models/REST/WorkflowClient.php index 09c917a72..deb9aeec9 100644 --- a/lib/Models/REST/WorkflowClient.php +++ b/lib/Models/REST/WorkflowClient.php @@ -1,7 +1,6 @@ serviceName = 'WorkflowClient'; - - if ($config = Config::getConfigForService('workflow', $config_id)) { - parent::__construct($config); - } else { - throw new \Exception ($this->serviceName . ': ' - . _('Die Opencast-Konfiguration wurde nicht korrekt angegeben')); - } + $this->serviceType = 'workflow'; + $config = $this->getConfigForClient($config_id); + parent::__construct($config); } @@ -38,9 +33,9 @@ public function getDefinitions() return false; } - #################### - # HELPER FUNCTIONS # - #################### + #################### + # HELPER FUNCTIONS # + #################### /** * Returns a revised collection of all tagged Workflow definitions diff --git a/lib/Models/Videos.php b/lib/Models/Videos.php index ec553bcfa..1114c4cac 100644 --- a/lib/Models/Videos.php +++ b/lib/Models/Videos.php @@ -437,7 +437,9 @@ protected static function getFilteredVideos($query, $filters) } // Only show videos with active server - $sql .= ' INNER JOIN oc_config ON (oc_config.id = oc_video.config_id AND oc_config.active = 1)'; + $sql .= ' INNER JOIN oc_config ON + (oc_config.id = oc_video.config_id AND oc_config.active = 1 + AND oc_config.maintenance_mode <> "' . \Opencast\Models\Config::MAINTENANCE_MODE_ON . '")'; $where .= " AND trashed = " . $filters->getTrashed(); $where .= " AND oc_video.token IS NOT NULL"; diff --git a/lib/Routes/Config/SimpleConfigList.php b/lib/Routes/Config/SimpleConfigList.php index 34a3414f1..3cf392593 100644 --- a/lib/Routes/Config/SimpleConfigList.php +++ b/lib/Routes/Config/SimpleConfigList.php @@ -43,7 +43,9 @@ public function __invoke(Request $request, Response $response, $args) 'apiplaylists' => $this->getServiceUrl('apiplaylists', $conf->id), 'apiworkflows' => $this->getServiceUrl('apiworkflows', $conf->id), 'studio' => $conf->service_url . '/studio/index.html', - 'lti_num' => sizeof(LtiHelper::getLtiLinks($conf->id)) // used to iterate over all Opencast nodes + 'lti_num' => sizeof(LtiHelper::getLtiLinks($conf->id)), // used to iterate over all Opencast nodes, + 'maintenance_mode' => $conf->isUnderMaintenance(true), + 'maintenance_text' => $conf->maintenance_text ?? '', ]; } diff --git a/lib/Routes/Playlist/PlaylistAddVideos.php b/lib/Routes/Playlist/PlaylistAddVideos.php index a68529449..065acd7f5 100644 --- a/lib/Routes/Playlist/PlaylistAddVideos.php +++ b/lib/Routes/Playlist/PlaylistAddVideos.php @@ -40,6 +40,10 @@ public function __invoke(Request $request, Response $response, $args) } if (!PlaylistMigration::isConverted()) { + // In favor of checking maintenance mode in API client level, + // we should check the ACL of each video before adding it to the playlist. + Videos::checkEventACL(null, null, $video); + foreach ($videos as $video) { $plvideo = new PlaylistVideos; $plvideo->setData([ @@ -55,8 +59,6 @@ public function __invoke(Request $request, Response $response, $args) $playlist->videos->store(); - Videos::checkEventACL(null, null, $video); - return $response->withStatus(204); } diff --git a/lib/Routes/Playlist/PlaylistRemoveVideos.php b/lib/Routes/Playlist/PlaylistRemoveVideos.php index 64800e378..d8544a97a 100644 --- a/lib/Routes/Playlist/PlaylistRemoveVideos.php +++ b/lib/Routes/Playlist/PlaylistRemoveVideos.php @@ -39,6 +39,9 @@ public function __invoke(Request $request, Response $response, $args) if (!PlaylistMigration::isConverted()) { foreach ($videos as $video) { + // In favor of checking maintenance mode in API client level, + // we should check the ACL of each video before removing it from the playlist. + Videos::checkEventACL(null, null, $video); $plvideo = PlaylistVideos::findOneBySQL( 'playlist_id = :playlist_id AND video_id = :video_id', @@ -50,8 +53,6 @@ public function __invoke(Request $request, Response $response, $args) $plvideo->delete(); } - Videos::checkEventACL(null, null, $video); - return $response->withStatus(204); } diff --git a/lib/Routes/User/UserSeriesShow.php b/lib/Routes/User/UserSeriesShow.php index f6aa4347b..56a834aa7 100644 --- a/lib/Routes/User/UserSeriesShow.php +++ b/lib/Routes/User/UserSeriesShow.php @@ -18,17 +18,20 @@ public function __invoke(Request $request, Response $response, $args) { global $user; - $series = UserSeries::getSeries($user->id); + try { + $series = UserSeries::getSeries($user->id); - if (empty($series)) { - $series = UserSeries::createSeries($user->id); - } - else { - $series = $series[0]; + if (empty($series)) { + $series = UserSeries::createSeries($user->id); + } else { + $series = $series[0]; + } + } catch (\Throwable $th) { + // We do nothing here as to return a null series to display warning. } return $this->createResponse([ - 'series_id' => $series['series_id'] + 'series_id' => $series['series_id'] ?? null ], $response); } -} \ No newline at end of file +} diff --git a/migrations/113_add_oc_config_maintenance_columns.php b/migrations/113_add_oc_config_maintenance_columns.php new file mode 100644 index 000000000..5d5033b26 --- /dev/null +++ b/migrations/113_add_oc_config_maintenance_columns.php @@ -0,0 +1,35 @@ +exec("ALTER TABLE `oc_config` + ADD COLUMN `maintenance_mode` ENUM('off', 'on', 'read-only') CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT 'off', + ADD COLUMN `maintenance_engage_url_fallback` varchar(255) NOT NULL, + ADD COLUMN `maintenance_text` TEXT DEFAULT NULL + "); + + SimpleOrMap::expireTableScheme(); + } + + public function down() + { + $db = DBManager::get(); + + $db->exec('ALTER TABLE `oc_config` + DROP COLUMN `maintenance_mode`, + DROP COLUMN `maintenance_engage_url_fallback`, + DROP COLUMN `maintenance_text`' + ); + + SimpleOrMap::expireTableScheme(); + } +} diff --git a/vueapp/App.vue b/vueapp/App.vue index ef60c6a83..b8f6dbf85 100644 --- a/vueapp/App.vue +++ b/vueapp/App.vue @@ -23,6 +23,12 @@ export default { computed: { ...mapGetters(['site']) + }, + + // We moved the loading of simple config into the top level, + // in order to reduce unwanted calls in various places! + async beforeMount() { + await this.$store.dispatch('simpleConfigListRead'); } }; diff --git a/vueapp/app.js b/vueapp/app.js index c63990663..5d5d2cdc4 100644 --- a/vueapp/app.js +++ b/vueapp/app.js @@ -50,7 +50,7 @@ window.addEventListener("DOMContentLoaded", function() { return Promise.reject(error); } - if (error.response.data !== undefined) { + if (error?.response?.data !== undefined) { store.dispatch('addMessage', error.response); } diff --git a/vueapp/components/Config/EditServer.vue b/vueapp/components/Config/EditServer.vue index 6e844fb38..38db27e92 100644 --- a/vueapp/components/Config/EditServer.vue +++ b/vueapp/components/Config/EditServer.vue @@ -26,6 +26,8 @@ @updateValue="updateValue" /> + + @@ -58,6 +60,7 @@ import MessageList from "@/components/MessageList"; import ConfigOption from "@/components/Config/ConfigOption"; import WorkflowOptions from "@/components/Config/WorkflowOptions"; import UploadOptions from "@/components/Config/UploadOptions"; +import MaintenanceOptions from "@/components/Config/MaintenanceOptions"; import axios from "@/common/axios.service"; @@ -72,6 +75,7 @@ export default { MessageList, WorkflowOptions, UploadOptions, + MaintenanceOptions }, props: { @@ -256,7 +260,7 @@ export default { } }, - checkConfigResponse(data) { + async checkConfigResponse(data) { if (data.message !== undefined) { if (data.message.type === 'error') { this.$store.dispatch('addMessage', { @@ -282,7 +286,7 @@ export default { }); this.currentConfig = data.config; - this.$store.dispatch('simpleConfigListRead'); + await this.$store.dispatch('simpleConfigListRead'); let view = this; // We need to wait for a short time so the component is actually visible @@ -298,6 +302,13 @@ export default { this.$emit('close'); } + return; + } else if (data.message.type === 'warning') { + this.$store.dispatch('addMessage', { + type: data.message.type, + text: data.message.text, + dialog: true + }); return; } } @@ -318,10 +329,20 @@ export default { const regex = /^\d+\.\d+(?:\.\d+)?(?:[-.][a-zA-Z0-9]+)*$/; return regex.test(version); + }, + + printMaintenanceInfo() { + if (this.currentConfig?.maintenance_mode && this.currentConfig.maintenance_mode !== 'off') { + this.$store.dispatch('addMessage', { + type: 'warning', + text: this.$gettext('Der Server ist im Wartungsmodus.'), + dialog: true + }); + } } }, - mounted() { + beforeMount() { if (this.currentId !== 'new') { if (!this.config) { this.$store.dispatch('configRead', this.currentId) @@ -332,7 +353,16 @@ export default { this.currentConfig = this.config; } } + }, + mounted() { + this.printMaintenanceInfo(); + }, + + watch: { + currentConfig() { + this.printMaintenanceInfo(); + } } }; diff --git a/vueapp/components/Config/MaintenanceOptions.vue b/vueapp/components/Config/MaintenanceOptions.vue new file mode 100644 index 000000000..fbb380aa3 --- /dev/null +++ b/vueapp/components/Config/MaintenanceOptions.vue @@ -0,0 +1,104 @@ + + + diff --git a/vueapp/components/Config/ServerCard.vue b/vueapp/components/Config/ServerCard.vue index 8a07694b5..3aa4e38d6 100644 --- a/vueapp/components/Config/ServerCard.vue +++ b/vueapp/components/Config/ServerCard.vue @@ -5,7 +5,7 @@ -
- - -
+
+ @@ -118,11 +126,19 @@ export default { const regex = /^\d+\.\d+(?:\.\d+)?(?:[-.][a-zA-Z0-9]+)*$/; return regex.test(version); + }, + + underMaintenance() { + if (this.isAddCard || !this.config?.maintenance_mode) { + return false; + } + + return !this.isShow && this.config?.maintenance_mode !== 'off'; } }, methods: { - toogleServer(active) { + toggleServer(active) { this.config.active = active; this.$store.dispatch('configSetActivation', {id: this.config.id, active: active}) .then(({ data }) => { diff --git a/vueapp/components/Courses/CoursesSidebar.vue b/vueapp/components/Courses/CoursesSidebar.vue index ef41bb27c..2be8ee972 100644 --- a/vueapp/components/Courses/CoursesSidebar.vue +++ b/vueapp/components/Courses/CoursesSidebar.vue @@ -277,6 +277,15 @@ export default { 'schedule_list', 'schedule_loading' ]), + isUnderMaintenance() { + if (!this.simple_config_list?.settings) { + return false; + } + let config_id = this.simple_config_list.settings['OPENCAST_DEFAULT_SERVER']; + let server = this.simple_config_list.server[config_id]; + return server?.maintenance_mode?.active; + }, + fragment() { return this.$route.name; }, @@ -286,7 +295,8 @@ export default { return this.cid !== undefined && // Make sure this is happening in a course! this.currentUser.can_edit && // Make sure the user has sufficient "global" rights. this.simple_config_list['settings']['OPENCAST_ALLOW_SCHEDULER'] && // Make sure it is configured! - this.course_config.scheduling_allowed; // Make sure the user is allowed to schedule recordings in the course! + this.course_config.scheduling_allowed && // Make sure the user is allowed to schedule recordings in the course! + !this.isUnderMaintenance; } catch (error) { return false; } @@ -297,14 +307,15 @@ export default { return this.cid !== undefined && this.currentUser.can_edit && this.simple_config_list['settings']['OPENCAST_ALLOW_STUDIO'] && - this.hasDefaultPlaylist; + this.hasDefaultPlaylist && + !this.isUnderMaintenance; } catch (error) { return false; } }, recordingLink() { - if (!this.simple_config_list.settings || !this.course_config || !this.canShowStudio) { + if (!this.simple_config_list.settings || !this.course_config || !this.canShowStudio || this.isUnderMaintenance) { return; } @@ -324,7 +335,7 @@ export default { }, canEdit() { - if (!this.course_config) { + if (!this.course_config || this.isUnderMaintenance) { return false; } @@ -332,7 +343,7 @@ export default { }, canUpload() { - if (!this.course_config) { + if (!this.course_config || this.isUnderMaintenance) { return false; } @@ -503,7 +514,6 @@ export default { }, async mounted() { - this.$store.dispatch('simpleConfigListRead'); this.semesterFilter = this.semester_filter; const route = useRoute(); diff --git a/vueapp/components/LtiAuth.vue b/vueapp/components/LtiAuth.vue index 832fcc9ae..f718baedc 100644 --- a/vueapp/components/LtiAuth.vue +++ b/vueapp/components/LtiAuth.vue @@ -68,10 +68,12 @@ export default { } }, - mounted() { - this.$store.dispatch('simpleConfigListRead').then(() => { - this.checkLTIPeriodically(); - }); - } + watch: { + simple_config_list(newValue) { + if (newValue?.server && this.interval === null) { + this.checkLTIPeriodically(); + } + } + }, } diff --git a/vueapp/components/MaintenanceMessage.vue b/vueapp/components/MaintenanceMessage.vue new file mode 100644 index 000000000..ca8d42134 --- /dev/null +++ b/vueapp/components/MaintenanceMessage.vue @@ -0,0 +1,53 @@ + + + diff --git a/vueapp/components/MessageBox.vue b/vueapp/components/MessageBox.vue index a3720109c..f32f51001 100644 --- a/vueapp/components/MessageBox.vue +++ b/vueapp/components/MessageBox.vue @@ -8,7 +8,12 @@
- Message + +
@@ -16,7 +21,7 @@ diff --git a/vueapp/components/Videos/Actions/VideoAccess.vue b/vueapp/components/Videos/Actions/VideoAccess.vue index fa179dbe6..95d350bd9 100644 --- a/vueapp/components/Videos/Actions/VideoAccess.vue +++ b/vueapp/components/Videos/Actions/VideoAccess.vue @@ -128,7 +128,7 @@ -
+
{{ $gettext('Weltweiter Zugriff') }} @@ -246,6 +246,15 @@ export default { } return this.event?.perm === 'owner' || this.event?.perm === 'write'; + }, + + isUnderMaintenance() { + if (this.config?.settings) { + return false; + } + let config_id = this.event?.config_id ?? this.config.settings['OPENCAST_DEFAULT_SERVER']; + let server = this.config.server[config_id]; + return server?.maintenance_mode?.active; } }, diff --git a/vueapp/components/Videos/VideoRow.vue b/vueapp/components/Videos/VideoRow.vue index d39bc3943..0f9319267 100644 --- a/vueapp/components/Videos/VideoRow.vue +++ b/vueapp/components/Videos/VideoRow.vue @@ -385,6 +385,15 @@ export default { 'course_config' ]), + isUnderMaintenance() { + if (this.simple_config_list?.server) { + return false; + } + let config_id = this.event?.config_id ?? this.simple_config_list.settings['OPENCAST_DEFAULT_SERVER']; + let server = this.simple_config_list.server[config_id]; + return server?.maintenance_mode?.active; + }, + showCheckbox() { return this.selectable || (this.canUpload && (this.event.perm == 'owner' || this.event.perm == 'write')); }, @@ -469,44 +478,47 @@ export default { if (!this.event?.trashed) { if (this.event.perm == 'owner' || this.event.perm == 'write') { - if (this.event?.state !== 'running') { + if (!this.isUnderMaintenance) { + + if (this.event?.state !== 'running') { + menuItems.push({ + id: 1, + label: this.$gettext('Bearbeiten'), + icon: 'edit', + emit: 'performAction', + emitArguments: 'VideoEdit' + }); + } + + /* + if (this.playlistForVideos) { + menuItems.push({ + id: 3, + label: this.$gettext('Zur Wiedergabeliste hinzufügen'), + icon: 'add', + emit: 'performAction', + emitArguments: 'VideoAddToPlaylist' + }); + } + */ + + /* menuItems.push({ - id: 1, - label: this.$gettext('Bearbeiten'), - icon: 'edit', + label: this.$gettext('Zu Wiedergabeliste hinzufügen'), + icon: 'add', emit: 'performAction', - emitArguments: 'VideoEdit' + emitArguments: 'VideoLinkToPlaylists' }); - } + */ - /* - if (this.playlistForVideos) { menuItems.push({ id: 3, - label: this.$gettext('Zur Wiedergabeliste hinzufügen'), - icon: 'add', + label: this.$gettext('Verknüpfungen'), + icon: 'group', emit: 'performAction', - emitArguments: 'VideoAddToPlaylist' + emitArguments: 'VideoLinkToPlaylists' }); } - */ - - /* - menuItems.push({ - label: this.$gettext('Zu Wiedergabeliste hinzufügen'), - icon: 'add', - emit: 'performAction', - emitArguments: 'VideoLinkToPlaylists' - }); - */ - - menuItems.push({ - id: 3, - label: this.$gettext('Verknüpfungen'), - icon: 'group', - emit: 'performAction', - emitArguments: 'VideoLinkToPlaylists' - }); if (this.canShare) { menuItems.push({ @@ -529,7 +541,7 @@ export default { } // As we abandoned the preview object structure, we now have to only validate the preview URL! - if ((this.event?.preview || this.event?.state == 'cutting') && !this.isLivestream) { + if ((this.event?.preview || this.event?.state == 'cutting') && !this.isLivestream && !this.isUnderMaintenance) { menuItems.push({ id: 5, label: this.$gettext('Videoeditor öffnen'), @@ -539,7 +551,7 @@ export default { }); } - if (this.event?.publication?.annotation_tool && this.event?.state !== 'running') { + if (this.event?.publication?.annotation_tool && this.event?.state !== 'running' && !this.isUnderMaintenance) { menuItems.push({ id: 6, label: this.$gettext('Anmerkungen hinzufügen'), @@ -549,7 +561,7 @@ export default { }); } - if (this.event?.state !== 'running' && !this.isLivestream) { + if (this.event?.state !== 'running' && !this.isLivestream && !this.isUnderMaintenance) { menuItems.push({ id: 7, label: this.$gettext('Untertitel bearbeiten'), @@ -559,7 +571,7 @@ export default { }); } - if (!this.isCourse && !this.isLivestream) { + if (!this.isCourse && !this.isLivestream && !this.isUnderMaintenance) { menuItems.push({ id: 9, label: this.$gettext('Zum Löschen markieren'), @@ -569,7 +581,7 @@ export default { }); } - if (this.canUpload && this.playlist) { + if (this.canUpload && this.playlist && !this.isUnderMaintenance) { menuItems.push({ id: 10, label: this.$gettext('Aus Wiedergabeliste entfernen'), @@ -626,6 +638,10 @@ export default { }, canEdit() { + if (this.isUnderMaintenance) { + return false; + } + return this.event?.perm === 'owner' || this.event?.perm === 'write'; }, diff --git a/vueapp/components/Videos/VideoUpload.vue b/vueapp/components/Videos/VideoUpload.vue index 5f4574b26..9fd593fde 100644 --- a/vueapp/components/Videos/VideoUpload.vue +++ b/vueapp/components/Videos/VideoUpload.vue @@ -270,6 +270,15 @@ export default { 'currentUserSeries' : 'currentUserSeries' }), + isUnderMaintenance() { + if (!this.config?.settings) { + return false; + } + let config_id = this.config.settings['OPENCAST_DEFAULT_SERVER']; + let server = this.config.server[config_id]; + return server?.maintenance_mode?.active; + }, + fragment() { return this.$route.name; }, @@ -355,7 +364,7 @@ export default { }, async accept() { - if (this.uploadProgress) { + if (this.uploadProgress || this.isUnderMaintenance) { return; } @@ -520,12 +529,8 @@ export default { } }, - mounted() { - this.$store.dispatch('authenticateLti'); - this.$store.dispatch('simpleConfigListRead').then(() => { - this.selectedServer = this.config['server'][this.config.settings['OPENCAST_DEFAULT_SERVER']]; - this.selectedWorkflow = this.defaultWorkflow; - }) + async mounted() { + await this.$store.dispatch('authenticateLti'); if (this.cid) { this.$store.dispatch('loadCourseConfig', this.cid); @@ -534,6 +539,18 @@ export default { if (this.playlist) { this.upload.playlist_token = this.playlist.token; } - } + }, + + watch: { + config(newValue) { + if (newValue?.server && newValue?.settings) { + this.selectedServer = newValue['server'][newValue.settings['OPENCAST_DEFAULT_SERVER']]; + this.selectedWorkflow = this.defaultWorkflow; + } else { + this.selectedServer = false; + this.selectedWorkflow = false; + } + } + }, } diff --git a/vueapp/components/Videos/VideosSidebar.vue b/vueapp/components/Videos/VideosSidebar.vue index a6e2070f6..6bb1bc594 100644 --- a/vueapp/components/Videos/VideosSidebar.vue +++ b/vueapp/components/Videos/VideosSidebar.vue @@ -136,6 +136,15 @@ export default { 'currentUser' ]), + isUnderMaintenance() { + if (!this.simple_config_list?.settings) { + return false; + } + let config_id = this.simple_config_list.settings['OPENCAST_DEFAULT_SERVER']; + let server = this.simple_config_list.server[config_id]; + return server?.maintenance_mode?.active; + }, + isDownloadAllowedForPlaylist() { if (this.playlist) { if (this.playlist['allow_download'] === null) { @@ -150,7 +159,7 @@ export default { canShowStudio() { try { - return this.currentUserSeries && this.simple_config_list['settings']['OPENCAST_ALLOW_STUDIO'] + return this.currentUserSeries && this.simple_config_list['settings']['OPENCAST_ALLOW_STUDIO'] && !this.isUnderMaintenance; } catch (error) { return false; } @@ -159,7 +168,7 @@ export default { canShowUpload() { try { - return this.currentUserSeries + return this.currentUserSeries && !this.isUnderMaintenance && ( this.simple_config_list['settings']['OPENCAST_ALLOW_STUDENT_WORKSPACE_UPLOAD'] || ['root', 'admin', 'dozent'].includes(this.currentUser.status) @@ -170,7 +179,7 @@ export default { }, recordingLink() { - if (!this.simple_config_list.settings || !this.canShowStudio) { + if (!this.simple_config_list.settings || !this.canShowStudio || this.isUnderMaintenance) { return; } @@ -204,9 +213,5 @@ export default { return this.simple_config_list?.workflows.find(wf => wf['id'] == wf_id)['name']; } }, - - mounted() { - this.$store.dispatch('simpleConfigListRead'); - } } diff --git a/vueapp/composables/useSimpleConfigChecker.js b/vueapp/composables/useSimpleConfigChecker.js new file mode 100644 index 000000000..1623806ad --- /dev/null +++ b/vueapp/composables/useSimpleConfigChecker.js @@ -0,0 +1,70 @@ +import { ref, watch, computed, onBeforeMount, onBeforeUnmount } from 'vue'; +import { useStore } from 'vuex'; + +export function useSimpleConfigChecker() { + const store = useStore(); + const simple_config_interval = ref(null); + + const simple_config_list = computed(() => store.getters.simple_config_list); + + async function readSimpleConfigPeriodically() { + simple_config_interval.value = setInterval(async () => { + await store.dispatch('simpleConfigListRead'); + }, 7000); + } + + onBeforeMount(() => { + readSimpleConfigPeriodically(); + }); + + onBeforeUnmount(() => { + if (simple_config_interval.value !== null) { + clearInterval(simple_config_interval.value); + } + }); + + watch(simple_config_list, (newValue, oldValue) => { + // This is the first run! + if (JSON.stringify(oldValue) === '{}') { + return; + } + + let current_default_config_id, current_main_server, + new_default_config_id, new_main_server = null; + let current_maintenance, current_maintenance_readonly, + new_maintenance, new_maintenance_readonly = false; + + if (oldValue?.settings && oldValue?.server) { + current_default_config_id = oldValue.settings['OPENCAST_DEFAULT_SERVER']; + current_main_server = oldValue.server[current_default_config_id]; + current_maintenance = current_main_server?.maintenance_mode?.active; + current_maintenance_readonly = current_main_server?.maintenance_mode?.read_only; + } + + if (newValue?.settings && newValue?.server) { + new_default_config_id = newValue.settings['OPENCAST_DEFAULT_SERVER']; + new_main_server = newValue.server[new_default_config_id]; + new_maintenance = new_main_server?.maintenance_mode?.active; + new_maintenance_readonly = new_main_server?.maintenance_mode?.read_only; + } + + let needs_reload = false; + // if the default server has been changed, mostly used for upload and studio etc. + if (JSON.stringify(new_main_server) !== JSON.stringify(current_main_server)) { + needs_reload = true; + } + + // if maintenance has been changed! + if (current_maintenance !== new_maintenance || current_maintenance_readonly !== new_maintenance_readonly) { + needs_reload = true; + } + + if (needs_reload) { + window.location.reload(); + } + }); + + return { + simple_config_interval + }; +} diff --git a/vueapp/store/config.module.js b/vueapp/store/config.module.js index 57676674b..f571affa0 100644 --- a/vueapp/store/config.module.js +++ b/vueapp/store/config.module.js @@ -7,6 +7,8 @@ const initialState = { 'service_url' : null, 'service_user': null, 'service_password': null, + 'maintenance_mode': 'off', + 'maintenance_text': null, 'settings': { 'lti_consumerkey': null, 'lti_consumersecret': null, @@ -57,13 +59,14 @@ export const actions = { }); }, - async simpleConfigListRead(context) { - return new Promise(resolve => { - ApiService.get('config/simple') - .then(({ data }) => { - context.commit('simpleConfigListSet', data); - resolve(data); - }); + async simpleConfigListRead(context, force_reload = true) { + if (force_reload === false && JSON.stringify(context.getters.simple_config_list) !== '{}') { + return context.state.simple_config_list; + } + return ApiService.get('config/simple') + .then(({ data }) => { + context.commit('simpleConfigListSet', data); + return data; }); }, diff --git a/vueapp/views/Contents.vue b/vueapp/views/Contents.vue index 0d7730961..f924ffb7a 100644 --- a/vueapp/views/Contents.vue +++ b/vueapp/views/Contents.vue @@ -23,6 +23,8 @@ @cancel="closePlaylistAddVideosDialog" /> + + @@ -35,6 +37,10 @@ import VideoUpload from "@/components/Videos/VideoUpload"; import PlaylistAddVideos from "@/components/Playlists/PlaylistAddVideos"; import MessageList from "@/components/MessageList"; +import MaintenanceMessage from "@/components/MaintenanceMessage"; + +import { useSimpleConfigChecker } from '@/composables/useSimpleConfigChecker'; + import { mapGetters } from "vuex"; export default { @@ -42,7 +48,8 @@ export default { components: { VideosSidebar, VideoUpload, - PlaylistAddVideos, MessageList + PlaylistAddVideos, MessageList, + MaintenanceMessage }, computed: { @@ -116,6 +123,11 @@ export default { } }); this.$store.dispatch('loadPlaylists'); + }, + + setup() { + useSimpleConfigChecker(); + return {}; } }; diff --git a/vueapp/views/Course.vue b/vueapp/views/Course.vue index a854effaa..24ae777da 100644 --- a/vueapp/views/Course.vue +++ b/vueapp/views/Course.vue @@ -41,6 +41,8 @@ @cancel="closeChangeDefaultVisibility" /> + + @@ -56,6 +58,9 @@ import MessageList from "@/components/MessageList"; import PlaylistsLinkCard from '@/components/Playlists/PlaylistsLinkCard.vue'; import PlaylistAddCard from '@/components/Playlists/PlaylistAddCard.vue'; import PlaylistEditCard from '@/components/Playlists/PlaylistEditCard.vue'; +import MaintenanceMessage from "@/components/MaintenanceMessage"; + +import { useSimpleConfigChecker } from '@/composables/useSimpleConfigChecker'; import { mapGetters } from "vuex"; @@ -67,7 +72,8 @@ export default { PlaylistEditCard, CoursesSidebar, VideoUpload, PlaylistAddVideos, MessageList, - EpisodesDefaultVisibilityDialog + EpisodesDefaultVisibilityDialog, + MaintenanceMessage }, computed: { @@ -159,6 +165,11 @@ export default { }); } }); + }, + + setup() { + useSimpleConfigChecker(); + return {}; } };