diff --git a/.github/workflows/tests-deploy.yml b/.github/workflows/tests-deploy.yml index 036a17fc..121c937f 100644 --- a/.github/workflows/tests-deploy.yml +++ b/.github/workflows/tests-deploy.yml @@ -237,8 +237,8 @@ jobs: docker network create master_bridge docker run -v /var/run/docker.sock:/var/run/docker.sock \ -e NC_HAPROXY_PASSWORD="some_secure_password" \ - --net master_bridge --name aa-docker-socket-proxy -h aa-docker-socket-proxy \ - --privileged -d ghcr.io/cloud-py-api/aa-docker-socket-proxy:latest + --net master_bridge --name nextcloud-appapi-dsp -h nextcloud-appapi-dsp \ + --privileged -d ghcr.io/cloud-py-api/nextcloud-appapi-dsp:latest docker run --net master_bridge --name nextcloud --rm -d ${{ env.docker-image }} sleep 60s @@ -249,7 +249,7 @@ jobs: docker exec -w /var/www/html/apps/${{ env.APP_NAME }} nextcloud git checkout FETCH_HEAD docker exec nextcloud sudo -u www-data php occ app:enable app_api docker exec nextcloud sudo -u www-data php occ app_api:daemon:register \ - docker_by_port Docker docker-install http aa-docker-socket-proxy:2375 http://nextcloud/index.php \ + docker_by_port Docker docker-install http nextcloud-appapi-dsp:2375 http://nextcloud/index.php \ --net=master_bridge --haproxy_password=some_secure_password docker exec nextcloud sudo -u www-data php occ app_api:daemon:list docker exec nextcloud sudo -u www-data php occ app_api:app:deploy skeleton docker_by_port \ @@ -326,12 +326,12 @@ jobs: -e NC_HAPROXY_PASSWORD="some_secure_password" \ -e BIND_ADDRESS="172.17.0.1" \ -e EX_APPS_NET_FOR_HTTPS="ipv4@localhost" \ - --net host --name aa-docker-socket-proxy -h aa-docker-socket-proxy \ - --privileged -d ghcr.io/cloud-py-api/aa-docker-socket-proxy:latest + --net host --name nextcloud-appapi-dsp -h nextcloud-appapi-dsp \ + --privileged -d ghcr.io/cloud-py-api/nextcloud-appapi-dsp:latest docker run --net=bridge --name=nextcloud -p 8080:80 --rm -d ${{ env.docker-image }} sleep 60s hostname -I - docker exec aa-docker-socket-proxy ip addr show | grep inet | awk '{print $2}' | cut -d/ -f1 + docker exec nextcloud-appapi-dsp ip addr show | grep inet | awk '{print $2}' | cut -d/ -f1 - name: Install AppAPI run: | @@ -363,7 +363,7 @@ jobs: - name: Save HaProxy logs if: always() - run: docker logs aa-docker-socket-proxy > haproxy.log 2>&1 + run: docker logs nextcloud-appapi-dsp > haproxy.log 2>&1 - name: Save container info & logs if: always() diff --git a/.github/workflows/tests-special.yml b/.github/workflows/tests-special.yml index 5f38ae64..dc2d25e3 100644 --- a/.github/workflows/tests-special.yml +++ b/.github/workflows/tests-special.yml @@ -117,7 +117,7 @@ jobs: sleep 5s php occ app_api:daemon:register manual_install "Manual Install" manual-install http localhost 0 php occ app_api:app:register $APP_ID manual_install --json-info \ - "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":{\"required\":[\"SYSTEM\", \"NOTIFICATIONS\"],\"optional\":[\"USER_INFO\"]},\"system_app\":1}" \ + "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":[\"SYSTEM\", \"NOTIFICATIONS\", \"USER_INFO\"],\"system_app\":1}" \ --force-scopes --wait-finish kill -15 $(cat /tmp/_install.pid) timeout 3m tail --pid=$(cat /tmp/_install.pid) -f /dev/null @@ -220,7 +220,7 @@ jobs: sleep 5s php occ app_api:daemon:register manual_install "Manual Install" manual-install http localhost 0 php occ app_api:app:register $APP_ID manual_install --json-info \ - "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":{\"required\":[\"ALL\"],\"optional\":[]},\"system_app\":1}" \ + "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":[\"ALL\"],\"system_app\":1}" \ --force-scopes --wait-finish kill -15 $(cat /tmp/_install.pid) timeout 3m tail --pid=$(cat /tmp/_install.pid) -f /dev/null @@ -238,7 +238,7 @@ jobs: echo $! > /tmp/_install.pid sleep 5s php occ app_api:app:register $APP_ID manual_install --json-info \ - "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":{\"required\":[\"SYSTEM\"],\"optional\":[]},\"system_app\":1}" \ + "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":[\"SYSTEM\"],\"system_app\":1}" \ --force-scopes --wait-finish kill -15 $(cat /tmp/_install.pid) timeout 3m tail --pid=$(cat /tmp/_install.pid) -f /dev/null @@ -253,7 +253,7 @@ jobs: echo $! > /tmp/_install.pid sleep 5s php occ app_api:app:register $APP_ID manual_install --json-info \ - "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":{\"required\":[\"ALL\"],\"optional\":[]},\"system_app\":0}" \ + "{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"port\":$APP_PORT,\"scopes\":[\"ALL\"],\"system_app\":0}" \ --force-scopes --wait-finish kill -15 $(cat /tmp/_install.pid) timeout 3m tail --pid=$(cat /tmp/_install.pid) -f /dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index 411145c2..f150c72c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [2.0.3 - 2024-02-01] + +### Added + +- Added RestartPolicy option (Admin settings) #220 +- Added ExApp init timeout option (Admin settings) #220 + +### Changed + +- Removed support of `Optional` API scopes. #220 + ## [2.0.2 - 2024-01-28] ### Fixed diff --git a/appinfo/info.xml b/appinfo/info.xml index eb40e593..23f29aec 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -42,7 +42,7 @@ to join us in shaping a more versatile, stable, and secure app landscape. *Your insights, suggestions, and contributions are invaluable to us.* ]]> - 2.0.2 + 2.0.3 agpl Andrey Borysenko Alexander Piskun diff --git a/docs/Concepts.rst b/docs/Concepts.rst index 5a244f9c..ae731907 100644 --- a/docs/Concepts.rst +++ b/docs/Concepts.rst @@ -4,9 +4,7 @@ Concepts API Access Control Mechanism ---------------------------- -Each application defines required and an optional list of API groups it intends to access. - -Administrators can permit or deny an application's access to any API from the **optional** list. +Each application defines list of API groups it intends to access. This system easily allows you to increase the level of trust in applications. Even prior to installation, it's possible to ascertain the API groups to which an application will gain access. diff --git a/docs/DeployConfigurations.rst b/docs/DeployConfigurations.rst index 7f0d268c..66d66b5c 100644 --- a/docs/DeployConfigurations.rst +++ b/docs/DeployConfigurations.rst @@ -195,7 +195,7 @@ Suggested way to communicate with Docker: via ``docker-socket-proxy``. class ExApp3 python Suggested config values(template *Docker Socket Proxy*): - 1. Daemon host: aa-docker-socket-proxy:2375 + 1. Daemon host: nextcloud-appapi-dsp:2375 2. HTTPS checkbox: ``disabled`` 3. Network: `user defined network `_ 4. HaProxy password: ``optional`` diff --git a/docs/tech_details/Deployment.rst b/docs/tech_details/Deployment.rst index 6c1c4a64..c26bb85d 100644 --- a/docs/tech_details/Deployment.rst +++ b/docs/tech_details/Deployment.rst @@ -27,7 +27,7 @@ This can be done by ``occ`` CLI command **app_api:daemon:register**: .. code-block:: bash - app_api:daemon:register [--net NET] [--host HOST] [--ssl_key SSL_KEY] [--ssl_key_password SSL_KEY_PASSWORD] [--ssl_cert SSL_CERT] [--ssl_cert_password SSL_CERT_PASSWORD] [--] + app_api:daemon:register [--net NET] [--haproxy_password PASSWORD] [--] Arguments ********* @@ -35,51 +35,16 @@ Arguments * ``name`` - unique name of the daemon (e.g. ``docker_local_sock``) * ``display-name`` - name of the daemon (e.g. ``My Local Docker``, will be displayed in the UI) * ``accepts-deploy-id`` - type of deployment (``docker-install`` or ``manual-install``) - * ``protocol`` - protocol used to connect to the daemon (``unix-socket``, ``http`` or ``https``) - * ``host`` - host of the daemon (e.g. ``/var/run/docker.sock`` for ``unix-socket`` protocol or ``host:port`` for ``http(s)`` protocol) + * ``protocol`` - protocol used to connect to the daemon (``http`` or ``https``) + * ``host`` - host of the daemon (e.g. ``/var/run/docker.sock`` or ``host:port``) * ``nextcloud_url`` - Nextcloud URL, Daemon config required option (e.g. ``https://nextcloud.local``) - * ``--gpu`` - ``[optional]`` GPU device to expose to the daemon (e.g. ``/dev/dri``) Options ******* * ``--net [network-name]`` - ``[required]`` network name to bind docker container to (default: ``host``) - * ``--hostname HOST`` - ``[required]`` host to expose daemon to (defaults to ExApp appid) - * ``--ssl_key SSL_KEY`` - ``[optional]`` path to SSL key file (local absolute path) - * ``--ssl_password SSL_PASSWORD`` - ``[optional]`` SSL key password - * ``--ssl_cert SSL_CERT`` - ``[optional]`` path to SSL cert file (local absolute path) - * ``--ssl_cert_password SSL_CERT_PASSWORD`` - ``[optional]`` SSL cert password - -DeployConfig -************ - -DeployConfig is a set of additional options in Daemon config, which are used in deployment algorithms to configure -ExApp container. - -.. code-block:: json - - { - "net": "nextcloud", - "host": null, - "nextcloud_url": "https://nextcloud.local", - "ssl_key": "/path/to/ssl/key.pem", - "ssl_key_password": "ssl_key_password", - "ssl_cert": "/path/to/ssl/cert.pem", - "ssl_cert_password": "ssl_cert_password", - "gpus": ["/dev/dri"], - } - - -DeployConfig options -"""""""""""""""""""" - - * ``net`` **[required]** - network name to bind docker container to (default: ``host``) - * ``host`` *[optional]* - in case Docker is on remote host, this should be a hostname of remote machine - * ``nextcloud_url`` **[required]** - Nextcloud URL (e.g. ``https://nextcloud.local``) - * ``ssl_key`` *[optional]* - path to SSL key file (local absolute path) - * ``ssl_key_password`` *[optional]* - SSL key password - * ``ssl_cert`` *[optional]* - path to SSL cert file (local absolute path) - * ``ssl_cert_password`` *[optional]* - SSL cert password + * ``--haproxy_password PASSWORD`` - ``[optional]`` password if ``AppAPI Docker Socket Proxy`` is used + * ``--gpu`` - ``[optional]`` GPU device to expose to the daemon (e.g. ``/dev/dri``) .. note:: Common configurations are tested by CI in our repository, see `workflows on github `_. @@ -91,7 +56,7 @@ Example of ``occ`` **app_api:daemon:register** command: .. code-block:: bash - sudo -u www-data php occ app_api:daemon:register docker_local_sock "My Local Docker" docker-install unix-socket /var/run/docker.sock "https://nextcloud.local" --net nextcloud + sudo -u www-data php occ app_api:daemon:register docker_local_sock "My Local Docker" docker-install http /var/run/docker.sock "https://nextcloud.local" --net nextcloud ExApp deployment @@ -156,7 +121,7 @@ For all examples and applications we release we usually add manual_install comma .. code-block:: php occ app_api:app:register nc_py_api manual_install --json-info \ - "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"localhost\",\"port\":$APP_PORT,\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\"]},\"protocol\":\"http\",\"system_app\":1}" \ + "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"port\":$APP_PORT,\"scopes\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\", \"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\"],\"system_app\":1}" \ --force-scopes .. note:: **Deployment/Startup of App should be done by developer when manual_install DeployConfig type is used.** @@ -172,7 +137,6 @@ The following env variables are required and built automatically: * ``APP_ID`` - ExApp appid * ``APP_DISPLAY_NAME`` - ExApp display name * ``APP_VERSION`` - ExApp version - * ``APP_PROTOCOL`` - protocol ExApp is listening on (http|https) * ``APP_HOST`` - host ExApp is listening on * ``APP_PORT`` - port ExApp is listening on (randomly selected by AppAPI) * ``APP_PERSISTENT_STORAGE`` - path to mounted volume for persistent data storage between ExApp updates @@ -182,19 +146,6 @@ The following env variables are required and built automatically: .. note:: Additional envs can be passed using multiple ``--env ENV_NAME=ENV_VAL`` options -Docker daemon remote -******************** - -If you want to connect to remote docker daemon with TLS enabled, you need to provide SSL key and cert by provided options. -Important: before deploy you need to import ca.pem file using `occ security `_ command: - -``php occ security:certificates:import /path/to/ca.pem`` - -The daemon must be configured with ``protocol=http|https``, ``host=https://dockerapihost``, ``port=8443``. -DaemonConfig deploy options ``ssl_key`` and ``ssl_cert`` must be provided with local absolute paths to SSL key and cert files. -In case of password protected key or cert, you can provide ``ssl_key_password`` and ``ssl_cert_password`` options. -More info about how to configure daemon will be added soon. - ExApp registration ------------------ @@ -250,14 +201,9 @@ It has the same structure as Nextcloud appinfo/info.xml file, but with some addi latest - - TALK - TALK_BOT - - - + TALK + TALK_BOT - http 0 ... diff --git a/lib/BackgroundJob/ExAppInitStatusCheckJob.php b/lib/BackgroundJob/ExAppInitStatusCheckJob.php index 863e1f91..f1ca850d 100644 --- a/lib/BackgroundJob/ExAppInitStatusCheckJob.php +++ b/lib/BackgroundJob/ExAppInitStatusCheckJob.php @@ -27,11 +27,11 @@ public function __construct( } protected function run($argument): void { - // Iterate over all ExApp and check for status.init_start_time if it is older than ex_app_init_timeout minutes + // Iterate over all ExApp and check for status.init_start_time if it is older than init_timeout minutes // set status.progress=0 and status.error message with timeout error try { $exApps = $this->mapper->findAll(); - $initTimeoutMinutes = intval($this->config->getAppValue(Application::APP_ID, 'ex_app_init_timeout', '40')); + $initTimeoutMinutes = intval($this->config->getAppValue(Application::APP_ID, 'init_timeout', '40')); foreach ($exApps as $exApp) { $status = $exApp->getStatus(); if (!isset($status['init_start_time'])) { diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 2eb99b78..6ea4ada8 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -5,23 +5,16 @@ namespace OCA\AppAPI; use OCA\AppAPI\AppInfo\Application; -use OCA\AppAPI\Db\ExAppScope; -use OCA\AppAPI\Service\ExAppScopesService; -use OCA\AppAPI\Service\ExAppService; use OCA\AppAPI\Service\ProvidersAI\TextProcessingService; use OCP\App\IAppManager; use OCP\Capabilities\ICapability; use OCP\IConfig; -use OCP\IRequest; class Capabilities implements ICapability { public function __construct( - private IConfig $config, - private IAppManager $appManager, - private ExAppService $service, - private ExAppScopesService $exAppScopesService, - private IRequest $request, + private readonly IConfig $config, + private readonly IAppManager $appManager, ) { } @@ -33,21 +26,8 @@ public function getCapabilities(): array { 'task_types' => array_keys(TextProcessingService::TASK_TYPES), ] ]; - $this->attachExAppScopes($capabilities); return [ 'app_api' => $capabilities, ]; } - - private function attachExAppScopes(&$capabilities): void { - $appId = $this->request->getHeader('EX-APP-ID'); - if ($appId !== '') { - $exApp = $this->service->getExApp($appId); - if ($exApp !== null) { - $capabilities['scopes'] = array_map(function (ExAppScope $scope) { - return intval($scope->getScopeGroup()); - }, $this->exAppScopesService->getExAppScopes($exApp)); - } - } - } } diff --git a/lib/Command/ExApp/Deploy.php b/lib/Command/ExApp/Deploy.php index a988070b..3f0da5f0 100644 --- a/lib/Command/ExApp/Deploy.php +++ b/lib/Command/ExApp/Deploy.php @@ -101,7 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $daemonConfig->getProtocol(), $daemonConfig->getHost(), $daemonConfig->getDeployConfig(), - (int)explode('=', $deployParams['container_params']['env'][7])[1], + (int)explode('=', $deployParams['container_params']['env'][6])[1], $auth, ); if (!$this->service->heartbeatExApp($exAppUrl, $auth)) { diff --git a/lib/Command/ExApp/Register.php b/lib/Command/ExApp/Register.php index bc447cfb..38886dee 100644 --- a/lib/Command/ExApp/Register.php +++ b/lib/Command/ExApp/Register.php @@ -145,7 +145,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - $requestedExAppScopeGroups = $this->exAppService->getExAppRequestedScopes($exApp, $infoXml, $exAppInfo); + $requestedExAppScopeGroups = $this->exAppService->getExAppScopes($exApp, $infoXml, $exAppInfo); if (isset($requestedExAppScopeGroups['error'])) { $output->writeln($requestedExAppScopeGroups['error']); $this->exAppService->unregisterExApp($exApp->getAppid()); @@ -154,40 +154,29 @@ protected function execute(InputInterface $input, OutputInterface $output): int $forceScopes = (bool) $input->getOption('force-scopes'); $confirmRequiredScopes = $forceScopes; - $confirmOptionalScopes = $forceScopes; if (!$forceScopes && $input->isInteractive()) { /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); // Prompt to approve required ExApp scopes - if (count($requestedExAppScopeGroups['required']) > 0) { - $output->writeln(sprintf('ExApp %s requested required scopes: %s', $appId, implode(', ', $requestedExAppScopeGroups['required']))); + if (count($requestedExAppScopeGroups) > 0) { + $output->writeln(sprintf('ExApp %s requested required scopes: %s', $appId, implode(', ', $requestedExAppScopeGroups))); $question = new ConfirmationQuestion('Do you want to approve it? [y/N] ', false); $confirmRequiredScopes = $helper->ask($input, $output, $question); } else { $confirmRequiredScopes = true; } - - // Prompt to approve optional ExApp scopes - if ($confirmRequiredScopes && count($requestedExAppScopeGroups['optional']) > 0) { - $output->writeln(sprintf('ExApp %s requested optional scopes: %s', $appId, implode(', ', $requestedExAppScopeGroups['optional']))); - $question = new ConfirmationQuestion('Do you want to approve it? [y/N] ', false); - $confirmOptionalScopes = $helper->ask($input, $output, $question); - } } - if (!$confirmRequiredScopes && count($requestedExAppScopeGroups['required']) > 0) { + if (!$confirmRequiredScopes && count($requestedExAppScopeGroups) > 0) { $output->writeln(sprintf('ExApp %s required scopes not approved.', $appId)); $this->exAppService->unregisterExApp($exApp->getAppid()); return 1; } - if (count($requestedExAppScopeGroups['required']) > 0) { - $this->registerExAppScopes($output, $exApp, $requestedExAppScopeGroups['required'], 'required'); - } - if ($confirmOptionalScopes && count($requestedExAppScopeGroups['optional']) > 0) { - $this->registerExAppScopes($output, $exApp, $requestedExAppScopeGroups['optional'], 'optional'); + if (count($requestedExAppScopeGroups) > 0) { + $this->registerExAppScopes($output, $exApp, $requestedExAppScopeGroups); } if (!$this->service->dispatchExAppInit($exApp->getAppid())) { @@ -212,17 +201,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - private function registerExAppScopes($output, ExApp $exApp, array $requestedExAppScopeGroups, string $scopeType): void { + private function registerExAppScopes($output, ExApp $exApp, array $requestedExAppScopeGroups): void { $registeredScopeGroups = []; foreach ($this->exAppApiScopeService->mapScopeNamesToNumbers($requestedExAppScopeGroups) as $scopeGroup) { if ($this->exAppScopesService->setExAppScopeGroup($exApp, $scopeGroup)) { $registeredScopeGroups[] = $scopeGroup; } else { - $output->writeln(sprintf('Failed to set %s ExApp scope group: %s', $scopeType, $scopeGroup)); + $output->writeln(sprintf('Failed to set %s ExApp scope group: %s', $exApp->getAppid(), $scopeGroup)); } } if (count($registeredScopeGroups) > 0) { - $output->writeln(sprintf('ExApp %s %s scope groups successfully set: %s', $exApp->getAppid(), $scopeType, implode(', ', + $output->writeln(sprintf('ExApp %s scope groups successfully set: %s', $exApp->getAppid(), implode(', ', $this->exAppApiScopeService->mapScopeGroupsToNames($registeredScopeGroups)))); } } diff --git a/lib/Command/ExApp/Update.php b/lib/Command/ExApp/Update.php index 1665f4db..bc9b9f93 100644 --- a/lib/Command/ExApp/Update.php +++ b/lib/Command/ExApp/Update.php @@ -164,53 +164,36 @@ protected function execute(InputInterface $input, OutputInterface $output): int $currentExAppScopes = array_map(function (ExAppScope $exAppScope) { return $exAppScope->getScopeGroup(); }, $this->exAppScopeService->getExAppScopes($exApp)); - $newExAppScopes = $this->exAppService->getExAppRequestedScopes($exApp, $infoXml); + $newExAppScopes = $this->exAppService->getExAppScopes($exApp, $infoXml); if (isset($newExAppScopes['error'])) { $output->writeln($newExAppScopes['error']); } // Prepare for prompt of newly requested ExApp scopes - $requiredScopes = $this->compareExAppScopes($currentExAppScopes, $newExAppScopes, 'required'); - $optionalScopes = $this->compareExAppScopes($currentExAppScopes, $newExAppScopes, 'optional'); + $requiredScopes = $this->compareExAppScopes($currentExAppScopes, $newExAppScopes); $forceScopes = (bool) $input->getOption('force-scopes'); - $confirmRequiredScopes = $forceScopes; - $confirmOptionalScopes = $forceScopes; + $confirmScopes = $forceScopes; if (!$forceScopes && $input->isInteractive()) { /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); if (count($requiredScopes) > 0) { - $output->writeln(sprintf('ExApp %s requested required scopes: %s', $appId, implode(', ', + $output->writeln(sprintf('ExApp %s requested scopes: %s', $appId, implode(', ', $this->exAppApiScopeService->mapScopeGroupsToNames($requiredScopes)))); $question = new ConfirmationQuestion('Do you want to approve it? [y/N] ', false); - $confirmRequiredScopes = $helper->ask($input, $output, $question); + $confirmScopes = $helper->ask($input, $output, $question); } else { - $confirmRequiredScopes = true; - } - - if ($confirmRequiredScopes && count($optionalScopes) > 0) { - $output->writeln(sprintf('ExApp %s requested optional scopes: %s', $appId, implode(', ', - $this->exAppApiScopeService->mapScopeGroupsToNames($optionalScopes)))); - $question = new ConfirmationQuestion('Do you want to approve it? [y/N] ', false); - $confirmOptionalScopes = $helper->ask($input, $output, $question); + $confirmScopes = true; } } - if (!$confirmRequiredScopes && count($requiredScopes) > 0) { + if (!$confirmScopes && count($requiredScopes) > 0) { $output->writeln(sprintf('ExApp %s required scopes not approved. Failed to finish ExApp update.', $appId)); return 1; } - if (!$confirmOptionalScopes && count($optionalScopes) > 0) { - // Remove optional scopes from the list so that they will be removed - $newExAppScopes['optional'] = []; - } - - $newExAppScopes = array_merge( - $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes['required']), - $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes['optional']) - ); + $newExAppScopes = $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes); if (!$this->exAppScopeService->updateExAppScopes($exApp, $newExAppScopes)) { $output->writeln(sprintf('Failed to update ExApp %s scopes.', $appId)); return 1; @@ -230,10 +213,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int * * @param array $currentExAppScopes * @param array $newExAppScopes - * @param string $type * @return array */ - private function compareExAppScopes(array $currentExAppScopes, array $newExAppScopes, string $type): array { - return array_values(array_diff($this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes[$type]), $currentExAppScopes)); + private function compareExAppScopes(array $currentExAppScopes, array $newExAppScopes): array { + return array_values(array_diff($this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes), $currentExAppScopes)); } } diff --git a/lib/Controller/ExAppsPageController.php b/lib/Controller/ExAppsPageController.php index fd280e30..a2a7ff5e 100644 --- a/lib/Controller/ExAppsPageController.php +++ b/lib/Controller/ExAppsPageController.php @@ -523,7 +523,7 @@ private function deployExApp(string $appId, SimpleXMLElement $infoXml, DaemonCon $daemonConfig->getProtocol(), $daemonConfig->getHost(), $daemonConfig->getDeployConfig(), - (int) explode('=', $deployParams['container_params']['env'][7])[1], + (int) explode('=', $deployParams['container_params']['env'][6])[1], $auth, ); if (!$this->service->heartbeatExApp($exAppUrl, $auth)) { @@ -560,26 +560,18 @@ private function registerExApp(string $appId, SimpleXMLElement $infoXml, DaemonC } // Register ExApp ApiScopes - $requestedExAppScopeGroups = $this->exAppService->getExAppRequestedScopes($exApp, $infoXml, $exAppInfo); - if (!$this->registerApiScopes($exApp, $requestedExAppScopeGroups, 'required')) { - return false; - } - $this->registerApiScopes($exApp, $requestedExAppScopeGroups, 'optional'); - - return true; + $requestedExAppScopeGroups = $this->exAppService->getExAppScopes($exApp, $infoXml, $exAppInfo); + return $this->registerApiScopes($exApp, $requestedExAppScopeGroups); } - private function registerApiScopes(ExApp $exApp, array $requestedExAppScopeGroups, string $scopeType): bool { + private function registerApiScopes(ExApp $exApp, array $requestedExAppScopeGroups): bool { $registeredScopeGroups = []; - foreach ($this->exAppApiScopeService->mapScopeNamesToNumbers($requestedExAppScopeGroups[$scopeType]) as $scopeGroup) { + foreach ($this->exAppApiScopeService->mapScopeNamesToNumbers($requestedExAppScopeGroups) as $scopeGroup) { if ($this->exAppScopeService->setExAppScopeGroup($exApp, $scopeGroup)) { $registeredScopeGroups[] = $scopeGroup; } } - if (count($registeredScopeGroups) !== count($requestedExAppScopeGroups['required'])) { - return false; - } - return true; + return count($registeredScopeGroups) === count($requestedExAppScopeGroups); } #[PasswordConfirmationRequired] @@ -704,13 +696,8 @@ public function enableExApp(string $appId): JSONResponse { } private function upgradeExAppScopes(ExApp $exApp, SimpleXMLElement $infoXml): void { - $newExAppScopes = $this->exAppService->getExAppRequestedScopes($exApp, $infoXml); - - $newExAppScopes = array_merge( - $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes['required']), - $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes['optional']) - ); - + $newExAppScopes = $this->exAppService->getExAppScopes($exApp, $infoXml); + $newExAppScopes = $this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes); $this->exAppScopeService->updateExAppScopes($exApp, $newExAppScopes); } diff --git a/lib/DeployActions/DockerActions.php b/lib/DeployActions/DockerActions.php index 6b453b42..7f243739 100644 --- a/lib/DeployActions/DockerActions.php +++ b/lib/DeployActions/DockerActions.php @@ -30,7 +30,6 @@ class DockerActions implements IDeployActions { 'APP_ID', 'APP_DISPLAY_NAME', 'APP_VERSION', - 'APP_PROTOCOL', 'APP_HOST', 'APP_PORT', 'APP_PERSISTENT_STORAGE', @@ -124,6 +123,9 @@ public function createContainer(string $dockerUrl, array $imageParams, array $pa 'HostConfig' => [ 'NetworkMode' => $params['net'], 'Mounts' => $this->buildDefaultExAppVolume($params['hostname']), + 'RestartPolicy' => [ + 'Name' => $this->config->getAppValue(Application::APP_ID, 'container_restart_policy', 'unless-stopped'), + ], ], 'Env' => $params['env'], ]; @@ -201,16 +203,12 @@ public function removeContainer(string $dockerUrl, string $containerId): array { } public function pullContainer(string $dockerUrl, array $params): array { - $url = $this->buildApiUrl($dockerUrl, sprintf('images/create?fromImage=%s', $this->buildImageName($params))); + $imageId = $this->buildImageName($params); + $url = $this->buildApiUrl($dockerUrl, sprintf('images/create?fromImage=%s', urlencode($imageId))); + $this->logger->info(sprintf('Pulling ExApp Image: %s', $imageId)); try { - $xRegistryAuth = json_encode([ - 'https://' . $params['image_src'] => [] - ], JSON_UNESCAPED_SLASHES); - $response = $this->guzzleClient->post($url, [ - 'headers' => [ - 'X-Registry-Auth' => base64_encode($xRegistryAuth), - ], - ]); + $response = $this->guzzleClient->post($url); + $this->logger->info(sprintf('Pull ExApp image result=%d for %s', $response->getStatusCode(), $imageId)); return ['success' => $response->getStatusCode() === 200]; } catch (GuzzleException $e) { $this->logger->error('Failed to pull image', ['exception' => $e]); @@ -368,7 +366,6 @@ public function buildDeployParams(DaemonConfig $daemonConfig, SimpleXMLElement $ 'appid' => $appId, 'name' => (string) $infoXml->name, 'version' => (string) $infoXml->version, - 'protocol' => (string) ($infoXml->xpath('external-app/protocol')[0] ?? 'http'), 'host' => $this->service->buildExAppHost($deployConfig), 'port' => $port, 'storage' => $storage, @@ -410,7 +407,6 @@ public function buildDeployEnvs(array $params, array $envOptions, array $deployC sprintf('APP_ID=%s', $params['appid']), sprintf('APP_DISPLAY_NAME=%s', $params['name']), sprintf('APP_VERSION=%s', $params['version']), - sprintf('APP_PROTOCOL=%s', $params['protocol']), sprintf('APP_HOST=%s', $params['host']), sprintf('APP_PORT=%s', $params['port']), sprintf('APP_PERSISTENT_STORAGE=%s', $params['storage']), diff --git a/lib/Service/ExAppService.php b/lib/Service/ExAppService.php index 70d39eab..baf3c5f1 100644 --- a/lib/Service/ExAppService.php +++ b/lib/Service/ExAppService.php @@ -241,8 +241,11 @@ public function updateExApp(ExApp $exApp, array $fields = ['version', 'name', 'p return false; } - public function getExAppRequestedScopes(ExApp $exApp, ?SimpleXMLElement $infoXml = null, array $jsonInfo = []): ?array { + public function getExAppScopes(ExApp $exApp, ?SimpleXMLElement $infoXml = null, array $jsonInfo = []): ?array { if (isset($jsonInfo['scopes'])) { + if (isset($jsonInfo['scopes']['required'])) { + return $jsonInfo['scopes']['required']; // Will be removed in AppAPI 2.1.0 version + } return $jsonInfo['scopes']; } @@ -254,19 +257,15 @@ public function getExAppRequestedScopes(ExApp $exApp, ?SimpleXMLElement $infoXml } if (isset($infoXml)) { + $oldFormatScopes = $infoXml->xpath('external-app/scopes/required'); + if ($oldFormatScopes !== false) { + $scopes = (array) $oldFormatScopes[0]->value; + return array_values($scopes); // Will be removed in AppAPI 2.2.0 version + } $scopes = $infoXml->xpath('external-app/scopes'); if ($scopes !== false) { $scopes = (array) $scopes[0]; - $required = array_map(function (string $scopeGroup) { - return $scopeGroup; - }, (array) $scopes['required']->value); - $optional = array_map(function (string $scopeGroup) { - return $scopeGroup; - }, (array) $scopes['optional']->value); - return [ - 'required' => array_values($required), - 'optional' => array_values($optional), - ]; + return array_values($scopes); } } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index 1a219d9b..a76e6aa5 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -47,6 +47,8 @@ public function getForm(): TemplateResponse { $adminInitialData = [ 'daemons' => $daemons, 'default_daemon_config' => $this->config->getAppValue(Application::APP_ID, 'default_daemon_config'), + 'init_timeout' => $this->config->getAppValue(Application::APP_ID, 'init_timeout', '40'), + 'container_restart_policy' => $this->config->getAppValue(Application::APP_ID, 'container_restart_policy', 'unless-stopped'), ]; $defaultDaemonConfigName = $this->config->getAppValue(Application::APP_ID, 'default_daemon_config'); diff --git a/package-lock.json b/package-lock.json index 34466548..774bd17d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,15 +19,15 @@ "@nextcloud/moment": "^1.3.1", "@nextcloud/password-confirmation": "^5.0.1", "@nextcloud/router": "^2.2.0", - "@nextcloud/vue": "^8.4.0", + "@nextcloud/vue": "^8.6.1", "dompurify": "^3.0.8", - "marked": "^11.1.1", + "marked": "^11.2.0", "p-limit": "^5.0.0", "vue": "^2.7.14", "vue-click-outside": "^1.1.0", "vue-clipboard2": "^0.3.3", "vue-localstorage": "^0.6.2", - "vue-material-design-icons": "^5.2.0", + "vue-material-design-icons": "^5.3.0", "vuex": "^3.6.2", "vuex-router-sync": "^5.0.0" }, @@ -2840,6 +2840,17 @@ "dev": true, "peer": true }, + "node_modules/@linusborg/vue-simple-portal": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz", + "integrity": "sha512-dq+oubEVW4UabBoQxmH97GiDa+F6sTomw4KcXFHnXEpw69rdkXFCxo1WzwuvWjoLiUVYJTyN1dtlUvTa50VcXg==", + "dependencies": { + "nanoid": "^3.1.20" + }, + "peerDependencies": { + "vue": "^2.6.6" + } + }, "node_modules/@mapbox/hast-util-table-cell-style": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.2.1.tgz", @@ -3302,11 +3313,12 @@ } }, "node_modules/@nextcloud/vue": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.4.0.tgz", - "integrity": "sha512-K+oDwwpy3ka5VBYi0S92dpBKJYDI78HGe0Ln2cfURtyH69DjtPAbDTj5CjFP8ccY/QsZgIjBekX6Ftw6fg1czQ==", + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.6.1.tgz", + "integrity": "sha512-CPPo3A1Az0DDRNI0pn7w6Yn8pJtEDktTXnrfym2Pd+lTzqTj4P2ywf+spArXMHu0BPIB95Eero7XlCiRpmhg/g==", "dependencies": { "@floating-ui/dom": "^1.1.0", + "@linusborg/vue-simple-portal": "^0.1.5", "@nextcloud/auth": "^2.0.0", "@nextcloud/axios": "^2.0.0", "@nextcloud/browser-storage": "^0.3.0", @@ -3317,13 +3329,13 @@ "@nextcloud/l10n": "^2.0.1", "@nextcloud/logger": "^2.2.1", "@nextcloud/router": "^2.0.0", - "@nextcloud/vue-select": "^3.24.0", + "@nextcloud/vue-select": "^3.25.0", "@vueuse/components": "^10.0.2", "@vueuse/core": "^10.1.2", "clone": "^2.1.2", "debounce": "2.0.0", "dompurify": "^3.0.5", - "emoji-mart-vue-fast": "^15.0.0", + "emoji-mart-vue-fast": "^15.0.1", "escape-html": "^1.0.3", "floating-vue": "^1.0.0-beta.19", "focus-trap": "^7.4.3", @@ -3332,6 +3344,7 @@ "rehype-external-links": "^3.0.0", "rehype-react": "^7.1.2", "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "splitpanes": "^2.4.1", @@ -3352,9 +3365,12 @@ } }, "node_modules/@nextcloud/vue-select": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue-select/-/vue-select-3.24.0.tgz", - "integrity": "sha512-+TQYaqB57OcwG3XSKpUtVcbUZIkX8KHzjTCWRFAiRqwryXTuBvY/JHzB5i31BFHJ6CK+l8WyBu8LgmtQW8ktrw==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue-select/-/vue-select-3.25.0.tgz", + "integrity": "sha512-zILFuJmUxp2oY09QUE65u69SxoQaR0RJdfnkpQlj2hcvzyOTLkYuyZwpxvseCf31WZnh9i2MO5mAddhsDCmw5g==", + "engines": { + "node": "^20.0.0" + }, "peerDependencies": { "vue": "2.x" } @@ -6004,6 +6020,15 @@ } ] }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -7229,9 +7254,9 @@ } }, "node_modules/emoji-mart-vue-fast": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/emoji-mart-vue-fast/-/emoji-mart-vue-fast-15.0.0.tgz", - "integrity": "sha512-3BzkDrs60JyT00dLHMAxWKbpFhbyaW9C+q1AjtqGovSxTu8TC2mYAGsvTmXNYKm39IRRAS56v92TihOcB98IsQ==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/emoji-mart-vue-fast/-/emoji-mart-vue-fast-15.0.1.tgz", + "integrity": "sha512-FcBio4MZsad+IwbaD2+1/obaK7W0F8EXlVXOXKgNCICaxkJD5WnA5bAtSXR0+FSBrMWz7DCAOqOojm7EapZ1eg==", "dependencies": { "@babel/runtime": "^7.18.6", "core-js": "^3.23.5" @@ -12323,6 +12348,15 @@ "dev": true, "peer": true }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12388,10 +12422,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/marked": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", - "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz", + "integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==", "bin": { "marked": "bin/marked.js" }, @@ -12486,6 +12529,101 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-newline-to-break": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", @@ -12499,6 +12637,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-hast": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.0.2.tgz", @@ -12518,6 +12669,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", @@ -12708,6 +12878,120 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", @@ -14912,6 +15196,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -14943,6 +15244,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -17615,9 +17930,9 @@ "integrity": "sha512-29YQVVkIdoS6BZBCJAyu9d0OR0eKSm5gk5OjsLssV1+NM4zJnf9cxhN1AVeXkUHJLqOonECweuaR8PZ2x307dw==" }, "node_modules/vue-material-design-icons": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-5.2.0.tgz", - "integrity": "sha512-fcdcJHQ9fQw2CAytuLAzWSELcxH138sCdMItVhvmO7Lu9afIgojB/UCWv7XHt/lURsnq/n6O+muM4AQgw8yfig==" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vue-material-design-icons/-/vue-material-design-icons-5.3.0.tgz", + "integrity": "sha512-wnbRh+48RwX/Gt+iqwCSdWpm0hPBwwv9F7MSouUzZ2PsphYVMJB9KkG9iGs+tgBiT57ZiurFEK07Y/rFKx+Ekg==" }, "node_modules/vue-resize": { "version": "1.0.1", @@ -18265,6 +18580,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index ef105460..9c9289b2 100644 --- a/package.json +++ b/package.json @@ -45,15 +45,15 @@ "@nextcloud/moment": "^1.3.1", "@nextcloud/password-confirmation": "^5.0.1", "@nextcloud/router": "^2.2.0", - "@nextcloud/vue": "^8.4.0", + "@nextcloud/vue": "^8.6.1", "dompurify": "^3.0.8", - "marked": "^11.1.1", + "marked": "^11.2.0", "p-limit": "^5.0.0", "vue": "^2.7.14", "vue-click-outside": "^1.1.0", "vue-clipboard2": "^0.3.3", "vue-localstorage": "^0.6.2", - "vue-material-design-icons": "^5.2.0", + "vue-material-design-icons": "^5.3.0", "vuex": "^3.6.2", "vuex-router-sync": "^5.0.0" }, diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue index 70724700..c1f008d6 100644 --- a/src/components/AdminSettings.vue +++ b/src/components/AdminSettings.vue @@ -17,6 +17,30 @@ + + + + + + {{ t('app_api', 'This settings changes are reflected only for newly created containers') }} + + + @@ -28,6 +52,8 @@ import { delay } from '../utils.js' import { showSuccess, showError } from '@nextcloud/dialogs' import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js' +import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' import AppAPIIcon from './icons/AppAPIIcon.vue' @@ -40,6 +66,8 @@ export default { DaemonConfigList, AppAPIIcon, NcNoteCard, + NcInputField, + NcSelect, }, data() { return { @@ -65,7 +93,8 @@ export default { onInput() { delay(() => { this.saveOptions({ - file_actions_menu: this.state.file_actions_menu, + ex_app_init_timeout: this.state.init_timeout, + container_restart_policy: this.state.container_restart_policy, }) }, 2000)() }, @@ -103,5 +132,10 @@ export default { margin-right: 12px; } } + + .setting { + width: fit-content; + max-width: 400px; + } } diff --git a/src/components/Apps/ScopesDetails.vue b/src/components/Apps/ScopesDetails.vue index 7b8057ac..0263c777 100644 --- a/src/components/Apps/ScopesDetails.vue +++ b/src/components/Apps/ScopesDetails.vue @@ -1,20 +1,13 @@