From 269e210cc827febb2745ddc549e973bd94b08a2b Mon Sep 17 00:00:00 2001
From: Arthur M <4rthem@users.noreply.github.com>
Date: Tue, 19 Dec 2023 18:38:26 +0100
Subject: [PATCH] Fixes (#402)
* bugfix
* add theme editor
* PS-599 add watch command
* PS-600 fix session expiration
---
.dockerignore | 4 +
.env | 8 +-
.github/workflows/ci.yaml | 5 +-
bin/dev/js-all.sh | 8 -
bin/dev/yarn-install.sh | 10 -
configurator/src/Command/ConfigureCommand.php | 8 +
.../Vendor/Keycloak/KeycloakConfigurator.php | 19 +
dashboard/.dockerignore | 3 -
dashboard/.gitignore | 1 -
dashboard/Dockerfile | 9 -
dashboard/client/.dockerignore | 6 +
dashboard/client/.eslintrc.cjs | 47 +
dashboard/client/.gitignore | 44 +
dashboard/client/.prettierrc.js | 3 +
dashboard/client/Dockerfile | 43 +
dashboard/{ => client}/README.md | 0
dashboard/client/config-compiler.js | 40 +
dashboard/client/docker/nginx/conf.d/app.conf | 17 +
.../client/docker/nginx/conf.d/gzip.conf | 19 +
dashboard/client/index.tpl.html | 44 +
dashboard/client/package.json | 35 +
dashboard/client/src/ClientApp.tsx | 29 +
dashboard/client/src/Root.tsx | 118 +
dashboard/client/src/Service.tsx | 126 +
dashboard/client/src/config.ts | 36 +
dashboard/client/src/declaration.d.ts | 4 +
.../public => client/src}/images/databox.png | Bin
.../public => client/src}/images/expose.png | Bin
.../public => client/src}/images/keycloak.png | Bin
.../public => client/src}/images/notify.png | Bin
.../public => client/src}/images/uploader.png | Bin
dashboard/client/src/index.tsx | 38 +
dashboard/client/src/theme.ts | 32 +
dashboard/client/tsconfig.json | 28 +
dashboard/client/tsconfig.node.json | 10 +
dashboard/client/vite.config.ts | 24 +
dashboard/docker/root/entrypoint.sh | 10 -
dashboard/src/index.tpl.html | 183 -
dashboard/src/menu.tpl.html | 107 -
databox/api/composer.json | 2 +-
databox/api/composer.lock | 2040 +-
databox/api/config/packages/api_platform.yaml | 1 +
databox/api/public/manifest.json | 2 +-
.../api/src/Api/InputTransformerListener.php | 85 -
.../api/src/Api/InputTransformerProvider.php | 60 +
.../src/Api/OverriddenDenyAccessListener.php | 92 -
.../AssetAttributeBatchUpdateProcessor.php | 48 +
.../Controller/Core/AbstractSortAction.php | 7 +-
.../Core/AssetAttributeBatchUpdateAction.php | 36 -
.../Core/AttributeDefinitionSortAction.php | 5 +-
.../Core/RenditionDefinitionSortAction.php | 5 +-
.../Controller/UpdateUserPreferenceAction.php | 4 +-
.../Compiler/FixApiPlatformPass.php | 19 -
databox/api/src/Entity/Core/Asset.php | 7 +-
.../api/src/Entity/Core/AttributeClass.php | 3 +-
.../src/Entity/Core/AttributeDefinition.php | 5 +-
.../api/src/Entity/Core/RenditionClass.php | 5 +-
.../src/Entity/Core/RenditionDefinition.php | 21 +-
databox/api/src/Entity/Core/RenditionRule.php | 3 +-
databox/api/src/Entity/Core/TagFilterRule.php | 3 +-
databox/api/src/Kernel.php | 2 -
.../api/src/User/UserPreferencesManager.php | 4 +-
databox/api/src/Util/DoctrineUtil.php | 39 +
databox/api/tests/Api/CrudTest.php | 10 +-
.../api/tests/Api/RenditionDefinitionTest.php | 63 +
databox/client/.dockerignore | 7 +-
databox/client/.gitignore | 5 -
databox/client/Dockerfile | 7 +-
databox/client/config-compiler.js | 76 +-
databox/client/index.tpl.html | 1 -
databox/client/manifest.json | 2 +-
databox/client/package.json | 17 +-
databox/client/src/components/App.tsx | 4 +-
.../Dialog/Collection/EditCollection.tsx | 8 +-
.../src/components/Dialog/Tabbed/FormTab.tsx | 8 +-
.../components/Dialog/Tabbed/TabbedDialog.tsx | 10 +-
.../Workspace/AttributeClassManager.tsx | 17 +-
.../Workspace/AttributeDefinitionManager.tsx | 3 +-
.../Dialog/Workspace/DefinitionManager.tsx | 46 +-
.../Workspace/RenditionClassManager.tsx | 60 +-
.../Workspace/RenditionDefinitionManager.tsx | 19 +-
.../Dialog/Workspace/TagManager.tsx | 10 +-
.../Dialog/Workspace/WorkspaceDialog.tsx | 8 +
.../src/components/Form/CollectionForm.tsx | 4 -
.../src/components/Layout/ChangeTheme.tsx | 14 +-
.../src/components/Layout/MainAppBar.tsx | 59 +-
.../src/components/Layout/ThemeEditor.tsx | 24 +
.../Media/Asset/Actions/SaveAsButton.tsx | 7 +-
.../Media/Asset/AssetContextMenu.tsx | 5 +
.../components/Media/Asset/AssetDropzone.tsx | 2 +-
.../Media/Collection/CreateCollection.tsx | 13 +-
.../components/Media/CollectionMenuItem.tsx | 2 +-
.../components/Media/Search/AssetResults.tsx | 2 +-
.../Media/Search/SelectionActions.tsx | 4 +-
databox/client/src/components/Root.tsx | 36 +-
.../src/components/Routing/RouteProxy.tsx | 16 +-
.../src/components/Upload/UploadForm.tsx | 4 +-
.../src/components/Upload/UploadModal.tsx | 5 +-
.../Preferences/UserPreferencesProvider.tsx | 13 +-
databox/client/src/config.ts | 13 +-
databox/client/src/index.tsx | 13 +-
databox/client/src/lib/auth.ts | 5 +-
databox/client/src/lib/theme.ts | 11 +-
databox/client/src/react-app-env.d.ts | 1 -
databox/client/vite.config.ts | 3 +
databox/indexer/.dockerignore | 3 -
databox/indexer/config/config.json | 4 +-
databox/indexer/nodemon.json | 2 +-
databox/indexer/package.json | 5 +-
databox/indexer/src/alternateUrl.ts | 15 +-
databox/indexer/src/amqp.ts | 95 +-
databox/indexer/src/command/index.ts | 23 +-
databox/indexer/src/command/list.ts | 10 +
databox/indexer/src/command/watch.ts | 32 +
databox/indexer/src/configLoader.ts | 85 +-
databox/indexer/src/console.ts | 32 +-
databox/indexer/src/databox/client.ts | 122 +-
databox/indexer/src/databox/entrypoint.ts | 53 +-
.../strategy/collectionBasedOnPathStrategy.ts | 44 +-
.../indexer/src/databox/strategy/types.d.ts | 8 +-
databox/indexer/src/databox/types.d.ts | 17 +-
databox/indexer/src/env.ts | 6 +-
databox/indexer/src/eventHandler.ts | 32 +-
databox/indexer/src/handlers/fs/indexer.ts | 22 +-
databox/indexer/src/handlers/fs/server.ts | 24 +-
databox/indexer/src/handlers/fs/shared.ts | 20 +-
databox/indexer/src/handlers/fs/watcher.ts | 40 +-
.../src/handlers/phraseanet/indexer.ts | 236 +-
.../handlers/phraseanet/phraseanetClient.ts | 34 +-
.../indexer/src/handlers/phraseanet/server.ts | 15 +-
.../indexer/src/handlers/phraseanet/shared.ts | 67 +-
.../src/handlers/phraseanet/types.d.ts | 24 +-
.../src/handlers/phraseanet/watcher.ts | 15 +-
.../indexer/src/handlers/s3_amqp/indexer.ts | 22 +-
.../indexer/src/handlers/s3_amqp/server.ts | 33 +-
.../indexer/src/handlers/s3_amqp/shared.ts | 22 +-
.../indexer/src/handlers/s3_amqp/types.d.ts | 4 +-
.../indexer/src/handlers/s3_amqp/watcher.ts | 95 +-
databox/indexer/src/index.ts | 1 -
databox/indexer/src/indexers.ts | 22 +-
databox/indexer/src/lib/axios.ts | 70 +-
databox/indexer/src/lib/logger.ts | 17 +-
databox/indexer/src/lib/parralelize.ts | 1 -
databox/indexer/src/lib/pathUtils.ts | 4 +-
databox/indexer/src/lib/promise.ts | 8 +-
databox/indexer/src/lib/streamify.ts | 17 +-
databox/indexer/src/lib/utils.ts | 7 +-
databox/indexer/src/locations.ts | 12 +-
databox/indexer/src/pathFilter.ts | 7 +-
databox/indexer/src/resourceResolver.ts | 12 +-
databox/indexer/src/s3/s3.ts | 12 +-
databox/indexer/src/server.ts | 93 +-
databox/indexer/src/serverFactories.ts | 10 +-
databox/indexer/src/types/config.d.ts | 13 +-
databox/indexer/src/types/event.d.ts | 34 +-
databox/indexer/src/watch.ts | 22 -
databox/indexer/src/watchers.ts | 20 +-
databox/indexer/vite.config.ts | 8 +-
doc/databox-indexer.md | 19 +
doc/indexer.md | 7 -
docker-compose.dev.yml | 25 +-
docker-compose.yml | 89 +-
expose/api/public/manifest.json | 2 +-
expose/client/.dockerignore | 6 +-
expose/client/.gitignore | 1 -
expose/client/Dockerfile | 3 +
expose/client/config-compiler.js | 8 +-
expose/client/package.json | 5 +-
expose/client/public/manifest.json | 2 +-
expose/client/src/component/App.tsx | 9 +-
expose/client/src/component/ConfigWrapper.tsx | 2 +-
expose/client/src/component/Layout.jsx | 8 +-
expose/client/src/component/Logo.jsx | 2 +-
expose/client/src/component/Publication.jsx | 2 +-
expose/client/src/component/Root.tsx | 27 +-
expose/client/src/component/RouteProxy.tsx | 14 +-
.../component/layouts/mapbox/MapboxLayout.jsx | 2 +-
.../shared-components/PublicationHeader.tsx | 2 +-
.../security/AuthenticationMethod.tsx | 19 +-
.../src/component/themes/ThemeEditorProxy.jsx | 2 +-
expose/client/src/config.ts | 19 +
expose/client/src/hooks/useUser.ts | 10 +-
expose/client/src/index.tsx | 13 +-
expose/client/src/lib/api-client.ts | 2 +-
expose/client/src/lib/config.ts | 32 -
expose/client/src/lib/matomo.ts | 2 +-
expose/client/src/lib/oauth.js | 11 -
expose/client/src/routes.ts | 3 +-
expose/client/vite.config.ts | 3 +
lib/js/api/src/useRequestErrorHandler.ts | 2 +-
lib/js/auth/index.ts | 2 +
lib/js/auth/src/client/KeycloakClient.ts | 36 +-
lib/js/auth/src/client/OAuthClient.ts | 65 +-
lib/js/core/index.ts | 10 +
lib/js/core/package.json | 19 +
lib/js/core/src/sentry.ts | 44 +
lib/js/core/src/types.ts | 32 +
lib/js/navigation/index.ts | 21 +-
lib/js/navigation/package.json | 11 +-
.../navigation/src/DefaultErrorBoundary.tsx | 37 -
.../navigation/src/DefaultErrorComponent.tsx | 8 +-
.../src/Drawer/OverlayRouteContext.ts | 18 -
.../src/Drawer/OverlayRouterContext.ts | 15 -
.../navigation/src/Overlay/OverlayContext.ts | 19 +
.../src/{Drawer => Overlay}/OverlayOutlet.tsx | 28 +-
.../src/Overlay/OverlayRouterContext.ts | 17 +
.../OverlayRouterProvider.tsx | 16 +-
lib/js/navigation/src/Router.tsx | 33 +-
...oRouteProxy.tsx => MatomoRouteWrapper.tsx} | 12 +-
lib/js/navigation/src/types.ts | 9 +
lib/js/phrasea-ui/index.ts | 4 +
lib/js/phrasea-ui/package.json | 3 +
.../phrasea-ui/src/components/ErrorLayout.tsx | 166 +
.../phrasea-ui/src/components/ErrorPage.tsx | 21 +
.../src/components/NotFoundPage.tsx | 8 +-
lib/js/phrasea-ui/src/style/error.css | 142 +
lib/js/react-auth/index.ts | 5 +-
lib/js/react-auth/package.json | 6 +-
.../src/components/AuthenticationProvider.tsx | 57 +-
.../src/components/AuthorizationCodePage.tsx | 23 +-
.../react-auth/src/components/MatomoUser.tsx | 2 +-
.../src/context/AuthenticationContext.ts | 4 +-
lib/js/react-auth/src/hooks/useForceLogin.ts | 12 +
.../react-auth/src/hooks/useKeycloakUrls.ts | 8 +-
.../DashboardMenu/DashboardMenu.tsx | 16 +-
lib/js/react-ps/src/icons/Menu.jsx | 15 -
lib/js/react-ps/src/icons/Menu.tsx | 18 +
lib/js/theme-editor/index.ts | 11 +
lib/js/theme-editor/package.json | 28 +
lib/js/theme-editor/src/MuiThemeEditor.tsx | 95 +
lib/js/theme-editor/src/ThemeEditorContext.ts | 4 +
.../theme-editor/src/ThemeEditorProvider.tsx | 40 +
lib/js/theme-editor/src/merge.ts | 29 +
lib/js/theme-editor/src/types.ts | 7 +
.../AlchemyCoreExtension.php | 10 +-
.../DependencyInjection/Configuration.php | 2 +-
.../storage-bundle/Upload/FileValidator.php | 2 +-
notify/api/public/manifest.json | 2 +-
package.json | 2 +
pnpm-lock.yaml | 38060 +++++-----------
pnpm-workspace.yaml | 1 +
turbo.json | 6 +
uploader/api/public/manifest.json | 2 +-
uploader/client/.dockerignore | 6 +-
uploader/client/.gitignore | 1 -
uploader/client/Dockerfile | 3 +
uploader/client/config-compiler.js | 10 +-
uploader/client/package.json | 3 +-
uploader/client/public/manifest.json | 2 +-
uploader/client/src/App.tsx | 31 +-
uploader/client/src/Root.tsx | 41 +-
uploader/client/src/components/Languages.jsx | 2 +-
uploader/client/src/components/Logo.jsx | 2 +-
uploader/client/src/components/Menu.tsx | 99 +-
uploader/client/src/components/RouteProxy.tsx | 19 +-
.../client/src/components/page/Download.tsx | 6 +-
.../src/components/page/SelectTarget.jsx | 34 +-
.../client/src/components/page/Upload.jsx | 30 +-
uploader/client/src/components/withRouter.tsx | 20 +-
uploader/client/src/config.ts | 28 +
.../client/src/context/UploaderUserContext.ts | 2 +-
.../src/context/UploaderUserProvider.tsx | 32 +-
uploader/client/src/index.tsx | 17 +-
uploader/client/src/lib/apiClient.ts | 2 +-
uploader/client/src/lib/config.ts | 45 -
uploader/client/src/routes.ts | 12 +-
uploader/client/src/types.ts | 2 +-
uploader/client/vite.config.ts | 3 +
uploader/client/yarn.lock | 12495 -----
269 files changed, 17133 insertions(+), 41334 deletions(-)
delete mode 100755 bin/dev/js-all.sh
delete mode 100755 bin/dev/yarn-install.sh
delete mode 100644 dashboard/.dockerignore
delete mode 100644 dashboard/.gitignore
delete mode 100644 dashboard/Dockerfile
create mode 100644 dashboard/client/.dockerignore
create mode 100644 dashboard/client/.eslintrc.cjs
create mode 100644 dashboard/client/.gitignore
create mode 100644 dashboard/client/.prettierrc.js
create mode 100644 dashboard/client/Dockerfile
rename dashboard/{ => client}/README.md (100%)
create mode 100644 dashboard/client/config-compiler.js
create mode 100644 dashboard/client/docker/nginx/conf.d/app.conf
create mode 100644 dashboard/client/docker/nginx/conf.d/gzip.conf
create mode 100644 dashboard/client/index.tpl.html
create mode 100644 dashboard/client/package.json
create mode 100644 dashboard/client/src/ClientApp.tsx
create mode 100644 dashboard/client/src/Root.tsx
create mode 100644 dashboard/client/src/Service.tsx
create mode 100644 dashboard/client/src/config.ts
create mode 100644 dashboard/client/src/declaration.d.ts
rename dashboard/{src/public => client/src}/images/databox.png (100%)
rename dashboard/{src/public => client/src}/images/expose.png (100%)
rename dashboard/{src/public => client/src}/images/keycloak.png (100%)
rename dashboard/{src/public => client/src}/images/notify.png (100%)
rename dashboard/{src/public => client/src}/images/uploader.png (100%)
create mode 100644 dashboard/client/src/index.tsx
create mode 100644 dashboard/client/src/theme.ts
create mode 100644 dashboard/client/tsconfig.json
create mode 100644 dashboard/client/tsconfig.node.json
create mode 100644 dashboard/client/vite.config.ts
delete mode 100755 dashboard/docker/root/entrypoint.sh
delete mode 100644 dashboard/src/index.tpl.html
delete mode 100644 dashboard/src/menu.tpl.html
delete mode 100644 databox/api/src/Api/InputTransformerListener.php
create mode 100644 databox/api/src/Api/InputTransformerProvider.php
delete mode 100644 databox/api/src/Api/OverriddenDenyAccessListener.php
create mode 100644 databox/api/src/Api/Processor/AssetAttributeBatchUpdateProcessor.php
delete mode 100644 databox/api/src/Controller/Core/AssetAttributeBatchUpdateAction.php
delete mode 100644 databox/api/src/DependencyInjection/Compiler/FixApiPlatformPass.php
create mode 100644 databox/api/src/Util/DoctrineUtil.php
create mode 100644 databox/api/tests/Api/RenditionDefinitionTest.php
create mode 100644 databox/client/src/components/Layout/ThemeEditor.tsx
delete mode 100644 databox/client/src/react-app-env.d.ts
create mode 100644 databox/indexer/src/command/list.ts
create mode 100644 databox/indexer/src/command/watch.ts
delete mode 100644 databox/indexer/src/index.ts
delete mode 100644 databox/indexer/src/watch.ts
create mode 100644 doc/databox-indexer.md
delete mode 100644 doc/indexer.md
create mode 100644 expose/client/src/config.ts
delete mode 100644 expose/client/src/lib/config.ts
delete mode 100644 expose/client/src/lib/oauth.js
create mode 100644 lib/js/core/index.ts
create mode 100644 lib/js/core/package.json
create mode 100644 lib/js/core/src/sentry.ts
create mode 100644 lib/js/core/src/types.ts
delete mode 100644 lib/js/navigation/src/DefaultErrorBoundary.tsx
delete mode 100644 lib/js/navigation/src/Drawer/OverlayRouteContext.ts
delete mode 100644 lib/js/navigation/src/Drawer/OverlayRouterContext.ts
create mode 100644 lib/js/navigation/src/Overlay/OverlayContext.ts
rename lib/js/navigation/src/{Drawer => Overlay}/OverlayOutlet.tsx (73%)
create mode 100644 lib/js/navigation/src/Overlay/OverlayRouterContext.ts
rename lib/js/navigation/src/{Drawer => Overlay}/OverlayRouterProvider.tsx (62%)
rename lib/js/navigation/src/proxy/{MatomoRouteProxy.tsx => MatomoRouteWrapper.tsx} (62%)
create mode 100644 lib/js/phrasea-ui/src/components/ErrorLayout.tsx
create mode 100644 lib/js/phrasea-ui/src/components/ErrorPage.tsx
create mode 100644 lib/js/phrasea-ui/src/style/error.css
create mode 100644 lib/js/react-auth/src/hooks/useForceLogin.ts
delete mode 100644 lib/js/react-ps/src/icons/Menu.jsx
create mode 100644 lib/js/react-ps/src/icons/Menu.tsx
create mode 100644 lib/js/theme-editor/index.ts
create mode 100644 lib/js/theme-editor/package.json
create mode 100644 lib/js/theme-editor/src/MuiThemeEditor.tsx
create mode 100644 lib/js/theme-editor/src/ThemeEditorContext.ts
create mode 100644 lib/js/theme-editor/src/ThemeEditorProvider.tsx
create mode 100644 lib/js/theme-editor/src/merge.ts
create mode 100644 lib/js/theme-editor/src/types.ts
create mode 100644 uploader/client/src/config.ts
delete mode 100644 uploader/client/src/lib/config.ts
delete mode 100644 uploader/client/yarn.lock
diff --git a/.dockerignore b/.dockerignore
index 519126a5e..b98a82931 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -16,3 +16,7 @@
/report
/tmp
**/dist
+/uploader/client/index.html
+/expose/client/index.html
+/databox/client/index.html
+/dashboard/index.html
diff --git a/.env b/.env
index 16ed42582..f7e8676da 100644
--- a/.env
+++ b/.env
@@ -93,7 +93,7 @@ UPLOADER_STORAGE_USE_PATH_STYLE_ENDPOINT=true
UPLOADER_REQUEST_SIGNATURE_TTL=600
UPLOADER_DELETE_ASSET_GRACEFUL_TIME=30
UPLOADER_RABBITMQ_VHOST=uploader
-UPLOADER_ALLOWED_FILE_TYPES='image/*(jpg,jpeg,bmp,tif,gif,png,heic),application/*(pdf,doc,docx,xls,xlsx,odt),video/*(mpg,mpeg,mov,avi,mp3,mp2,mp4,m4v,m4a,mkv,hevc)audio/*(aac,aiff,wav)'
+UPLOADER_ALLOWED_FILE_TYPES='image/*(.jpg,.jpeg,.bmp,.tif,.gif,.png,.heic),application/*(.pdf,.doc,.docx,.xls,.xlsx,.odt),video/*(.mpg,.mpeg,.mov,.avi,.mp3,.mp2,.mp4,.m4v,.m4a,.mkv,.hevc)audio/*(.aac,.aiff,.wav)'
# For admin OAuth clients
EXPOSE_ADMIN_CLIENT_ID=expose-admin
@@ -231,6 +231,7 @@ VERIFY_SSL=true
PHRASEANET_DOMAIN="${PHRASEANET_DOMAIN:-phraseanet.$PHRASEA_DOMAIN}"
PHRASEANET_URL=https://${PHRASEANET_DOMAIN}
+PHRASEANET_VERIFY_SSL=true
PHRASEANET_APP_OAUTH_TOKEN=define-me
# Indexer
@@ -241,7 +242,7 @@ INDEXER_DATABOX_OWNER_ID=
INDEXER_DATABOX_CONCURRENCY=3
INDEXER_WATCH_DIR=/fs-watch
INDEXER_WATCH_DIR_PREFIX=fs
-INDEXER_WATCH_SOURCE_DIR=/tmp
+INDEXER_WATCH_SOURCE_DIR=./tmp
INDEXER_BUCKET_NAME=test-indexer
INDEXER_PHRASEANET_DATABOX_ID=
INDEXER_PHRASEANET_SEARCH_QUERY=
@@ -266,6 +267,7 @@ LEGO_AWS_SDK_LOAD_CONFIG=
NEWRELIC_ENABLED=0
NEWRELIC_LICENSE_KEY=
-SENTRY_DSN=
+PHP_SENTRY_DSN=
+CLIENT_SENTRY_DSN=
SENTRY_ENVIRONMENT=prod
SENTRY_RELEASE=unknown
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 27fc3951f..3c76c6da5 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -73,8 +73,11 @@ jobs:
uses: ./.github/workflows/build.yaml
with:
image: dashboard
- context: ./dashboard
+ context: ./dashboard/client
+ withLibs: true
secrets: inherit
+ needs:
+ - build_nodejs-base
build_databox_api:
name: 'Build Databox API'
diff --git a/bin/dev/js-all.sh b/bin/dev/js-all.sh
deleted file mode 100755
index d56fad206..000000000
--- a/bin/dev/js-all.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-. bin/vars.sh
-
-for a in ${JS_PROJECTS}; do
- echo " $a:$ $@"
- (cd "$a" && $@)
-done
diff --git a/bin/dev/yarn-install.sh b/bin/dev/yarn-install.sh
deleted file mode 100755
index 0daaa08c0..000000000
--- a/bin/dev/yarn-install.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-. bin/vars.sh
-
-apps=(${JS_PROJECTS})
-
-for a in "${apps[@]}"; do
- echo "Installing $a..."
- (cd "$a" && yarn install)
-done
diff --git a/configurator/src/Command/ConfigureCommand.php b/configurator/src/Command/ConfigureCommand.php
index e11d4202f..3c6dfc672 100644
--- a/configurator/src/Command/ConfigureCommand.php
+++ b/configurator/src/Command/ConfigureCommand.php
@@ -20,6 +20,14 @@ public function __construct(
parent::__construct();
}
+ protected function configure()
+ {
+ parent::configure();
+
+ $this->addOption('preset');
+ }
+
+
public function execute(InputInterface $input, OutputInterface $output): int
{
$this->configurator->configure($output);
diff --git a/configurator/src/Configurator/Vendor/Keycloak/KeycloakConfigurator.php b/configurator/src/Configurator/Vendor/Keycloak/KeycloakConfigurator.php
index 578f0f0dc..bc059d49e 100644
--- a/configurator/src/Configurator/Vendor/Keycloak/KeycloakConfigurator.php
+++ b/configurator/src/Configurator/Vendor/Keycloak/KeycloakConfigurator.php
@@ -39,6 +39,7 @@ public function configure(OutputInterface $output): void
] as $scope) {
$this->keycloakManager->createScope($scope);
}
+
foreach ($this->getAppScopes() as $app => $appScopes) {
foreach ($appScopes as $scope) {
$this->keycloakManager->createScope($scope, [
@@ -76,6 +77,24 @@ public function configure(OutputInterface $output): void
);
}
+ if (getenv('INDEXER_DATABOX_CLIENT_ID')) {
+ $clientData = $this->keycloakManager->createClient(
+ getenv('INDEXER_DATABOX_CLIENT_ID'),
+ getenv('INDEXER_DATABOX_CLIENT_SECRET'),
+ null,
+ [
+ 'standardFlowEnabled' => false,
+ 'implicitFlowEnabled' => false,
+ 'directAccessGrantsEnabled' => false,
+ 'serviceAccountsEnabled' => true,
+ ],
+ );
+
+ foreach ($this->getAppScopes()['databox'] as $scope) {
+ $this->keycloakManager->addScopeToClient($scope, $clientData['id']);
+ }
+ }
+
$defaultAdmin = $this->keycloakManager->createUser([
'username' => getenv('DEFAULT_ADMIN_USERNAME'),
'enabled' => true,
diff --git a/dashboard/.dockerignore b/dashboard/.dockerignore
deleted file mode 100644
index 8559c16b4..000000000
--- a/dashboard/.dockerignore
+++ /dev/null
@@ -1,3 +0,0 @@
-Dockerfile
-.dockerignore
-/src/public/*.html
diff --git a/dashboard/.gitignore b/dashboard/.gitignore
deleted file mode 100644
index 8b8cfaa77..000000000
--- a/dashboard/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/src/public/*.html
diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile
deleted file mode 100644
index 96245a275..000000000
--- a/dashboard/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM nginx:1.17.9-alpine
-
-COPY docker/root /
-COPY src /var/app
-COPY src/public /usr/share/nginx/html
-
-ENTRYPOINT ["/entrypoint.sh"]
-
-CMD ["nginx", "-g", "daemon off;"]
diff --git a/dashboard/client/.dockerignore b/dashboard/client/.dockerignore
new file mode 100644
index 000000000..7f5e465ee
--- /dev/null
+++ b/dashboard/client/.dockerignore
@@ -0,0 +1,6 @@
+/node_modules
+Dockerfile
+/README.md
+.dockerignore
+/index.html
+.idea
diff --git a/dashboard/client/.eslintrc.cjs b/dashboard/client/.eslintrc.cjs
new file mode 100644
index 000000000..b791d0141
--- /dev/null
+++ b/dashboard/client/.eslintrc.cjs
@@ -0,0 +1,47 @@
+module.exports = {
+ root: true,
+ env: {browser: true, es2020: true},
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['react-refresh', 'unused-imports'],
+ rules: {
+ '@typescript-eslint/no-explicit-any': ['warn'],
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ 'unused-imports/no-unused-imports-ts': 'error',
+ 'unused-imports/no-unused-vars-ts': [
+ 'error',
+ {
+ vars: 'all',
+ varsIgnorePattern: '^_',
+ args: 'after-used',
+ argsIgnorePattern: '^_',
+ },
+ ],
+ '@typescript-eslint/ban-types': [
+ 'error',
+ {
+ types: {
+ '{}': false,
+ },
+ extendDefaults: true,
+ },
+ ],
+ 'react/react-in-jsx-scope': 'off',
+ 'no-empty-pattern': 'off',
+ 'no-undef': 'off',
+ 'react/prop-types': 'off',
+ 'react/display-name': 'off',
+ 'react/no-unescaped-entities': 'off',
+ 'no-irregular-whitespace': 'off',
+ 'react-refresh/only-export-components': [
+ 'warn',
+ {allowConstantExport: true},
+ ],
+ },
+};
diff --git a/dashboard/client/.gitignore b/dashboard/client/.gitignore
new file mode 100644
index 000000000..04946d4a5
--- /dev/null
+++ b/dashboard/client/.gitignore
@@ -0,0 +1,44 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+/index.html
+# Logs
+logs
+*.log
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/dashboard/client/.prettierrc.js b/dashboard/client/.prettierrc.js
new file mode 100644
index 000000000..13ed8bbea
--- /dev/null
+++ b/dashboard/client/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('../../.prettierrc'),
+};
diff --git a/dashboard/client/Dockerfile b/dashboard/client/Dockerfile
new file mode 100644
index 000000000..70c0a891f
--- /dev/null
+++ b/dashboard/client/Dockerfile
@@ -0,0 +1,43 @@
+ARG BASE_TAG=latest
+ARG REGISTRY_NAMESPACE
+FROM ${REGISTRY_NAMESPACE}nodejs-base:${BASE_TAG} as client-build
+
+COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
+COPY --chown=node:node ./lib/js ./lib/js
+COPY --chown=node:node ./dashboard/client ./dashboard/client
+
+USER node
+
+WORKDIR /srv/workspace/dashboard/client
+
+RUN pnpm install \
+ && mv index.tpl.html index.html
+
+RUN pnpm build
+
+############
+
+FROM nginx:1.17.6-alpine as client-nginx
+
+COPY --from=client-build /srv/workspace/dashboard/client/dist /var/app
+COPY ./dashboard/client/docker/nginx/conf.d /etc/nginx/conf.d
+
+RUN apk add --no-cache libstdc++ \
+ && apk add --virtual .build \
+ wget \
+ && mkdir -p /var/docker \
+ && wget -q -O /var/docker/generate-env https://github.com/alchemy-fr/config-compiler/releases/download/v2.2.1/generate-env-alpine \
+ && chmod +x /var/docker/generate-env \
+ && apk del .build \
+ && rm /etc/nginx/conf.d/default.conf
+
+EXPOSE 80
+
+ARG SENTRY_RELEASE
+ENV SENTRY_RELEASE=${SENTRY_RELEASE}
+
+COPY ./dashboard/client/config-compiler.js /var/app/
+
+WORKDIR /var/app
+
+CMD ["/bin/sh", "-c", "/var/docker/generate-env ./ && nginx -g 'daemon off;'"]
diff --git a/dashboard/README.md b/dashboard/client/README.md
similarity index 100%
rename from dashboard/README.md
rename to dashboard/client/README.md
diff --git a/dashboard/client/config-compiler.js b/dashboard/client/config-compiler.js
new file mode 100644
index 000000000..52ee823a5
--- /dev/null
+++ b/dashboard/client/config-compiler.js
@@ -0,0 +1,40 @@
+(function (config, env) {
+ const whiteList = [
+ 'DATABOX_API_URL',
+ 'DATABOX_CLIENT_URL',
+ 'DEV_MODE',
+ 'DISPLAY_SERVICES_MENU',
+ 'ELASTICHQ_URL',
+ 'EXPOSE_API_URL',
+ 'EXPOSE_CLIENT_URL',
+ 'KEYCLOAK_URL',
+ 'MAILHOG_URL',
+ 'MATOMO_URL',
+ 'NOTIFY_API_URL',
+ 'PGADMIN_URL',
+ 'PHPMYADMIN_URL',
+ 'RABBITMQ_CONSOLE_URL',
+ 'REPORT_API_URL',
+ 'SAML2_URL',
+ 'SAML_URL',
+ 'STACK_NAME',
+ 'STACK_VERSION',
+ 'TRAEFIK_CONSOLE_URL',
+ 'UPLOADER_API_URL',
+ 'UPLOADER_CLIENT_URL',
+ 'ZIPPY_URL',
+ ];
+
+ const e = {};
+
+ Object.entries(env).forEach(([key, value]) => {
+ if (whiteList.includes(key)) {
+ e[key] = value;
+ }
+ });
+
+ return {
+ locales: config.available_locales,
+ env: e,
+ };
+});
diff --git a/dashboard/client/docker/nginx/conf.d/app.conf b/dashboard/client/docker/nginx/conf.d/app.conf
new file mode 100644
index 000000000..48003d433
--- /dev/null
+++ b/dashboard/client/docker/nginx/conf.d/app.conf
@@ -0,0 +1,17 @@
+server {
+ listen 80;
+
+ server_name _;
+ server_tokens off;
+
+ add_header X-Content-Type-Options "nosniff";
+ add_header X-Frame-Options "deny";
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
+
+ root /var/app;
+ index index.html;
+
+ location / {
+ try_files $uri /index.html =404;
+ }
+}
diff --git a/dashboard/client/docker/nginx/conf.d/gzip.conf b/dashboard/client/docker/nginx/conf.d/gzip.conf
new file mode 100644
index 000000000..7fadf0cde
--- /dev/null
+++ b/dashboard/client/docker/nginx/conf.d/gzip.conf
@@ -0,0 +1,19 @@
+## Compression.
+gzip on;
+gzip_buffers 16 8k;
+gzip_comp_level 1;
+gzip_http_version 1.1;
+gzip_min_length 10;
+gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/x-icon application/vnd.ms-fontobject font/opentype application/x-font-ttf;
+gzip_vary on;
+gzip_proxied any; # Compression for all requests.
+## No need for regexps. See
+## http://wiki.nginx.org/NginxHttpGzipModule#gzip_disable
+gzip_disable msie6;
+
+## Serve already compressed files directly, bypassing on-the-fly
+## compression.
+##
+# Usually you don't make much use of this. It's better to just
+# enable gzip_static on the locations you need it.
+# gzip_static on;
diff --git a/dashboard/client/index.tpl.html b/dashboard/client/index.tpl.html
new file mode 100644
index 000000000..f28d40056
--- /dev/null
+++ b/dashboard/client/index.tpl.html
@@ -0,0 +1,44 @@
+
+
+
+ $repo
+ *
+ * @return T
+ */
+ public static function findStrictByRepo(EntityRepository $repo, string $id): object
+ {
+ $object = $repo->find($id);
+
+ return $object ?? throw new \InvalidArgumentException(sprintf('%s %s not found', $repo->getClassName(), $id));
+ }
+}
diff --git a/databox/api/tests/Api/CrudTest.php b/databox/api/tests/Api/CrudTest.php
index 2a34f592a..21635c85a 100644
--- a/databox/api/tests/Api/CrudTest.php
+++ b/databox/api/tests/Api/CrudTest.php
@@ -129,7 +129,7 @@ public function getCases(): array
['POST', '/attribute-classes', KeycloakClientTestMock::USER_UID, [
'workspace' => '/workspaces/{workspaceId}',
], [
- 'code' => 422,
+ 'code' => 403,
]],
['POST', '/attribute-classes', KeycloakClientTestMock::ADMIN_UID, [
@@ -163,7 +163,6 @@ public function getCases(): array
]],
['POST', '/rendition-classes', KeycloakClientTestMock::USER_UID, [
- 'workspace' => '/workspaces/{workspaceId}',
], [
'code' => 422,
]],
@@ -196,10 +195,15 @@ public function getCases(): array
'code' => 401,
]],
+ ['POST', '/attribute-definitions', KeycloakClientTestMock::USER_UID, [
+ ], [
+ 'code' => 400,
+ ]],
+
['POST', '/attribute-definitions', KeycloakClientTestMock::USER_UID, [
'workspace' => '/workspaces/{workspaceId}',
], [
- 'code' => 422,
+ 'code' => 403,
]],
['POST', '/attribute-definitions', KeycloakClientTestMock::ADMIN_UID, [
diff --git a/databox/api/tests/Api/RenditionDefinitionTest.php b/databox/api/tests/Api/RenditionDefinitionTest.php
new file mode 100644
index 000000000..c51b9cd7d
--- /dev/null
+++ b/databox/api/tests/Api/RenditionDefinitionTest.php
@@ -0,0 +1,63 @@
+findIriBy(RenditionDefinition::class, ['name' => 'preview']);
+
+ $client->request('PUT', $iri, [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.KeycloakClientTestMock::getJwtFor(KeycloakClientTestMock::ADMIN_UID),
+ ],
+ 'json' => [
+ 'useAsThumbnail' => true,
+ ],
+ ]);
+
+ $this->assertResponseIsSuccessful();
+ $this->assertJsonContains([
+ '@id' => $iri,
+ 'useAsThumbnail' => true,
+ ]);
+ }
+
+ public function testGetRenditionDefinition(): void
+ {
+ self::enableFixtures();
+ $client = static::createClient();
+ $iri = $this->findIriBy(RenditionDefinition::class, ['name' => 'preview']);
+
+ $response = $client->request('GET', $iri, [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.KeycloakClientTestMock::getJwtFor(KeycloakClientTestMock::USER_UID),
+ ],
+ ]);
+ $this->assertResponseIsSuccessful();
+ $this->assertJsonContains([
+ '@id' => $iri,
+ ]);
+ $this->assertArrayNotHasKey('useAsThumbnail', $response->toArray());
+
+ $client->request('GET', $iri, [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.KeycloakClientTestMock::getJwtFor(KeycloakClientTestMock::ADMIN_UID),
+ ],
+ ]);
+ $this->assertResponseIsSuccessful();
+ $this->assertJsonContains([
+ '@id' => $iri,
+ 'useAsThumbnail' => false,
+ ]);
+ }
+}
diff --git a/databox/client/.dockerignore b/databox/client/.dockerignore
index 1d4c4aaab..727aa0d10 100644
--- a/databox/client/.dockerignore
+++ b/databox/client/.dockerignore
@@ -2,11 +2,6 @@
Dockerfile
/README.md
.dockerignore
-/public/env-config.*.js
-/public/index.html
-/npm-debug.log*
-/yarn-debug.log*
-/yarn-error.log*
+/index.html
/i18n-scan-tmp
.idea
-
diff --git a/databox/client/.gitignore b/databox/client/.gitignore
index bda9eb24c..3c3a6868d 100644
--- a/databox/client/.gitignore
+++ b/databox/client/.gitignore
@@ -20,10 +20,5 @@
.env.test.local
.env.production.local
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-/env-config.*.js
/index.html
/i18n-scan-tmp
diff --git a/databox/client/Dockerfile b/databox/client/Dockerfile
index 32fbc17fe..37a94427b 100644
--- a/databox/client/Dockerfile
+++ b/databox/client/Dockerfile
@@ -10,13 +10,9 @@ USER node
WORKDIR /srv/workspace/databox/client
-ENV NODE_ENV=development
-
RUN pnpm install \
&& mv index.tpl.html index.html
-RUN ls -la .
-
RUN pnpm build
############
@@ -37,6 +33,9 @@ RUN apk add --no-cache libstdc++ \
EXPOSE 80
+ARG SENTRY_RELEASE
+ENV SENTRY_RELEASE=${SENTRY_RELEASE}
+
COPY ./databox/client/config-compiler.js /var/app/
WORKDIR /var/app
diff --git a/databox/client/config-compiler.js b/databox/client/config-compiler.js
index 101519556..50ea74c68 100644
--- a/databox/client/config-compiler.js
+++ b/databox/client/config-compiler.js
@@ -1,49 +1,13 @@
(function (config, env) {
config = config || {};
- let scriptTpl = '';
- const analytics = config.databox.analytics;
+ const analytics = {};
- if (analytics) {
- switch (analytics.provider) {
- case 'matomo':
- scriptTpl = `
-
-
-`
- .replace('{host}', analytics.options.host)
- .replace('{siteId}', analytics.options.siteId);
- break;
- case 'google_analytics':
- scriptTpl =
- `
-
-
-`.replace(/{propertyId}/g, analytics.options.propertyId);
- break;
- default:
- console.error(
- `Unsupported analytics provider ${analytics.provider}`
- );
- }
+ if (env.MATOMO_URL) {
+ analytics.matomo = {
+ baseUrl: env.MATOMO_URL,
+ siteId: env.MATOMO_SITE_ID,
+ };
}
const normalizeTypes = value => {
@@ -56,7 +20,7 @@
return {};
}
- const types = [...v.matchAll(/([\w*]+\/[\w*+.-]+)(\([\w,]*\))?/g)];
+ const types = [...v.matchAll(/([\w*]+\/[\w*+.-]+)(\([.\w,]*\))?/g)];
const struct = {};
for (const t of types) {
struct[t[1]] = t[2]
@@ -71,10 +35,21 @@
return struct;
};
+ function castBoolean(value) {
+ if (typeof value === 'boolean') {
+ return value;
+ }
+
+ if (typeof value === 'string') {
+ return ['true', '1', 'on', 'y', 'yes'].includes(
+ value.toLowerCase()
+ );
+ }
+
+ return false;
+ }
+
return {
- customHTML: {
- __TPL_HEAD__: scriptTpl,
- },
locales: config.available_locales,
autoConnectIdP: env.AUTO_CONNECT_IDP,
baseUrl: env.DATABOX_API_URL,
@@ -83,10 +58,15 @@
keycloakUrl: env.KEYCLOAK_URL,
realmName: env.KEYCLOAK_REALM_NAME,
clientId: env.CLIENT_ID,
- devMode: env.DEV_MODE === 'true',
+ devMode: castBoolean(env.DEV_MODE),
requestSignatureTtl: env.S3_REQUEST_SIGNATURE_TTL,
- displayServicesMenu: env.DISPLAY_SERVICES_MENU === 'true',
+ displayServicesMenu: castBoolean(env.DISPLAY_SERVICES_MENU),
dashboardBaseUrl: env.DASHBOARD_URL,
allowedTypes: normalizeTypes(env.ALLOWED_FILE_TYPES),
+ analytics,
+ appId: env.APP_ID || 'databox',
+ sentryDsn: env.SENTRY_DSN,
+ sentryEnvironment: env.SENTRY_ENVIRONMENT,
+ sentryRelease: env.SENTRY_RELEASE,
};
});
diff --git a/databox/client/index.tpl.html b/databox/client/index.tpl.html
index 84884b7b5..a53b855aa 100644
--- a/databox/client/index.tpl.html
+++ b/databox/client/index.tpl.html
@@ -26,7 +26,6 @@
-->
Databox.
__TPL_CONFIG__
- __TPL_HEAD__
diff --git a/databox/client/manifest.json b/databox/client/manifest.json
index 080d6c77a..478bf8e25 100644
--- a/databox/client/manifest.json
+++ b/databox/client/manifest.json
@@ -18,7 +18,7 @@
"sizes": "512x512"
}
],
- "start_url": ".",
+ "start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
diff --git a/databox/client/package.json b/databox/client/package.json
index 193b1e37c..042e2e49e 100644
--- a/databox/client/package.json
+++ b/databox/client/package.json
@@ -5,19 +5,21 @@
"dependencies": {
"@alchemy/api": "workspace:*",
"@alchemy/auth": "workspace:*",
+ "@alchemy/core": "workspace:*",
"@alchemy/react-auth": "workspace:*",
"@alchemy/react-ps": "workspace:*",
+ "@alchemy/theme-editor": "workspace:*",
"@alchemy/visual-workflow": "workspace:*",
"@alchemy/react-hooks": "workspace:*",
"@alchemy/navigation": "workspace:*",
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/sortable": "^7.0.1",
"@dnd-kit/utilities": "^3.2.0",
- "@emotion/react": "^11.9.0",
- "@emotion/styled": "^11.8.1",
- "@mui/icons-material": "^5.6.2",
- "@mui/lab": "^5.0.0-alpha.80",
- "@mui/material": "^5.10.13",
+ "@emotion/react": "^11.11.1",
+ "@emotion/styled": "^11.11.0",
+ "@mui/icons-material": "^5.15.0",
+ "@mui/lab": "^5.0.0-alpha.156",
+ "@mui/material": "^5.15.0",
"@toast-ui/react-image-editor": "^3.15.2",
"ace-builds": "^1.14.0",
"axios": "^1.6.2",
@@ -38,7 +40,7 @@
"react-compare-image": "^3.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
- "react-dom": "^18.1.0",
+ "react-dom": "^18.2.0",
"react-dropzone": "^14.2.1",
"react-hook-form": "^7.48.2",
"react-i18next": "^13.5.0",
@@ -46,10 +48,9 @@
"react-nl2br": "^1.0.3",
"react-pdf": "^7.5.1",
"react-player": "^2.10.1",
- "react-scripts": "^5.0.1",
"react-select": "^5.3.2",
"react-string-replace": "^1.1.0",
- "react-toastify": "^9.0.1",
+ "react-toastify": "^9.1.3",
"sass": "^1.60.0",
"tui-image-editor": "^3.15.3",
"uuid": "^9.0.0",
diff --git a/databox/client/src/components/App.tsx b/databox/client/src/components/App.tsx
index 8d6625c6e..008824faa 100644
--- a/databox/client/src/components/App.tsx
+++ b/databox/client/src/components/App.tsx
@@ -83,7 +83,9 @@ const AppProxy = React.memo(() => {
export default function App() {
const {logout} = useAuth();
const onError = useRequestErrorHandler({
- logout,
+ logout: (redirectPathAfterLogin) => {
+ logout(redirectPathAfterLogin, true);
+ },
});
React.useEffect(() => {
diff --git a/databox/client/src/components/Dialog/Collection/EditCollection.tsx b/databox/client/src/components/Dialog/Collection/EditCollection.tsx
index b1bc14d65..535c965bb 100644
--- a/databox/client/src/components/Dialog/Collection/EditCollection.tsx
+++ b/databox/client/src/components/Dialog/Collection/EditCollection.tsx
@@ -6,6 +6,7 @@ import {useFormSubmit} from '@alchemy/api';
import FormTab from '../Tabbed/FormTab';
import {DialogTabProps} from '../Tabbed/TabbedDialog';
import {CollectionForm} from '../../Form/CollectionForm';
+import {useInRouterDirtyFormPrompt} from '@alchemy/navigation';
export type OnCollectionEdit = (coll: Collection) => void;
@@ -36,7 +37,12 @@ export default function EditCollection({data, onClose, minHeight}: Props) {
},
});
- const {submitting, remoteErrors} = usedFormSubmit;
+ const {
+ submitting,
+ remoteErrors,
+ forbidNavigation,
+ } = usedFormSubmit;
+ useInRouterDirtyFormPrompt(t, forbidNavigation);
const formId = 'edit-collection';
diff --git a/databox/client/src/components/Dialog/Tabbed/FormTab.tsx b/databox/client/src/components/Dialog/Tabbed/FormTab.tsx
index 7095ff724..e5f176ff7 100644
--- a/databox/client/src/components/Dialog/Tabbed/FormTab.tsx
+++ b/databox/client/src/components/Dialog/Tabbed/FormTab.tsx
@@ -6,7 +6,7 @@ import {LoadingButton} from '@mui/lab';
import SaveIcon from '@mui/icons-material/Save';
import RemoteErrors from '../../Form/RemoteErrors';
import {useTranslation} from 'react-i18next';
-import {useInRouterDirtyFormPrompt} from '@alchemy/navigation';
+import {useInRouterDirtyFormPrompt, useOutsideRouterDirtyFormPrompt} from '@alchemy/navigation';
type Props = PropsWithChildren<{
loading: boolean;
@@ -25,6 +25,12 @@ export function useDirtyFormPrompt(isDirty: boolean) {
useInRouterDirtyFormPrompt(t, isDirty);
}
+export function useDirtyFormPromptOutsideRouter(isDirty: boolean) {
+ const {t} = useTranslation();
+
+ useOutsideRouterDirtyFormPrompt(t, isDirty);
+}
+
export default function FormTab({
formId,
onSave,
diff --git a/databox/client/src/components/Dialog/Tabbed/TabbedDialog.tsx b/databox/client/src/components/Dialog/Tabbed/TabbedDialog.tsx
index ca547c313..c0ae39191 100644
--- a/databox/client/src/components/Dialog/Tabbed/TabbedDialog.tsx
+++ b/databox/client/src/components/Dialog/Tabbed/TabbedDialog.tsx
@@ -42,7 +42,7 @@ export default function TabbedDialog({
const closeModal = useCloseModal();
const tabs = configTabs.filter(t => t.enabled);
const tabIndex = tabs.findIndex(t => t.id === tab);
- const currentTab = tabs[tabIndex];
+ const currentTab = tabIndex >= 0 ? tabs[tabIndex] : undefined;
const handleChange = (_event: React.SyntheticEvent, newValue: number) => {
navigateToModal(route, {
@@ -51,6 +51,12 @@ export default function TabbedDialog
({
});
};
+ React.useEffect(() => {
+ if (!currentTab) {
+ closeModal();
+ }
+ }, [currentTab, closeModal]);
+
return (
{({open}) => (
@@ -81,7 +87,7 @@ export default function TabbedDialog({
);
})}
- {React.createElement(currentTab.component, {
+ {currentTab && React.createElement(currentTab.component, {
...rest,
...currentTab.props,
onClose: closeModal,
diff --git a/databox/client/src/components/Dialog/Workspace/AttributeClassManager.tsx b/databox/client/src/components/Dialog/Workspace/AttributeClassManager.tsx
index 38f60c359..a861cc9e4 100644
--- a/databox/client/src/components/Dialog/Workspace/AttributeClassManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/AttributeClassManager.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect} from 'react';
+import {useEffect} from 'react';
import {AttributeClass, Workspace} from '../../../types';
import {
deleteAttributeClass,
@@ -23,21 +23,14 @@ function Item({
usedFormSubmit: {
submitting,
register,
- handleSubmit,
control,
watch,
setValue,
- reset,
formState: {errors},
},
- formId,
}: DefinitionItemFormProps) {
const {t} = useTranslation();
- React.useEffect(() => {
- reset(data);
- }, [data]);
-
const isPublic = watch('public');
const isEditable = watch('editable');
const displayedPermissions = !isPublic
@@ -45,13 +38,13 @@ function Item({
: [AclPermission.EDIT];
useEffect(() => {
- if (!isPublic) {
+ if (!isPublic && isEditable) {
setValue('editable', false);
}
- }, [isPublic]);
+ }, [isPublic, isEditable]);
return (
-
+ >
);
}
diff --git a/databox/client/src/components/Dialog/Workspace/AttributeDefinitionManager.tsx b/databox/client/src/components/Dialog/Workspace/AttributeDefinitionManager.tsx
index 2cb67a068..a3d0be068 100644
--- a/databox/client/src/components/Dialog/Workspace/AttributeDefinitionManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/AttributeDefinitionManager.tsx
@@ -200,7 +200,7 @@ export default function AttributeDefinitionManager({
};
const onSort: OnSort = async ids => {
- await apiClient.put(`/attribute-definitions/sort`, ids);
+ await apiClient.post(`/attribute-definitions/sort`, ids);
toast.success(t('common.item_sorted', 'Order saved!') as string);
};
@@ -217,6 +217,7 @@ export default function AttributeDefinitionManager({
handleSave={handleSave}
handleDelete={deleteAttributeDefinition}
onSort={onSort}
+ normalizeData={createData}
/>
);
}
diff --git a/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx b/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx
index 3f24a3d0b..c1b8dfe24 100644
--- a/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/DefinitionManager.tsx
@@ -40,7 +40,6 @@ export type DefinitionItemProps = {
};
export type DefinitionItemFormProps = {
- formId: string;
usedFormSubmit: UseFormSubmitReturn;
workspaceId: string;
} & DefinitionItemProps;
@@ -96,6 +95,7 @@ type Props = {
handleDelete?: (id: string) => Promise;
workspaceId: string;
onSort?: OnSort;
+ normalizeData?: (data: D) => D;
};
export default function DefinitionManager({
@@ -186,7 +186,18 @@ export default function DefinitionManager({
},
});
- const {submitting, remoteErrors, forbidNavigation} = usedFormSubmit;
+ const {
+ submitting,
+ remoteErrors,
+ forbidNavigation,
+ reset,
+ } = usedFormSubmit;
+
+ React.useEffect(() => {
+ if (item && 'new' !== item) {
+ reset(item);
+ }
+ }, [item]);
const onDelete = useCallback(() => {
if (handleDelete && typeof item === 'object') {
@@ -344,23 +355,20 @@ export default function DefinitionManager({
}}
>
{item && (
- <>
-
- >
+
)}
{item && item !== 'new' && handleDelete && (
diff --git a/databox/client/src/components/Dialog/Workspace/RenditionClassManager.tsx b/databox/client/src/components/Dialog/Workspace/RenditionClassManager.tsx
index f9ba71564..e20ac5aa9 100644
--- a/databox/client/src/components/Dialog/Workspace/RenditionClassManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/RenditionClassManager.tsx
@@ -6,7 +6,6 @@ import DefinitionManager, {
DefinitionItemProps,
} from './DefinitionManager';
import {useTranslation} from 'react-i18next';
-import {useForm} from 'react-hook-form';
import FormFieldErrors from '../../Form/FormFieldErrors';
import {
deleteRenditionClass,
@@ -16,53 +15,40 @@ import {
} from '../../../api/rendition';
import CheckboxWidget from '../../Form/CheckboxWidget';
import RenditionClassPermissions from './RenditionClassPermissions';
-import {useDirtyFormPrompt} from '../Tabbed/FormTab';
function Item({
data,
- usedFormSubmit,
- formId,
-}: DefinitionItemFormProps) {
- const {t} = useTranslation();
-
- const {
- register,
- handleSubmit,
+ usedFormSubmit: {
submitting,
- watch,
+ register,
control,
+ watch,
formState: {errors},
- forbidNavigation,
- } = usedFormSubmit;
-
- const {} = useForm({
- defaultValues: data,
- });
- useDirtyFormPrompt(forbidNavigation);
+ },
+}: DefinitionItemFormProps) {
+ const {t} = useTranslation();
const isPublic = watch('public');
return (
<>
-
+
+
+
+
+
+
+
+
{data.id && !isPublic && (
diff --git a/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx b/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx
index daa50c3d2..f51220374 100644
--- a/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/RenditionDefinitionManager.tsx
@@ -18,26 +18,27 @@ import RenditionClassSelect from '../../Form/RenditionClassSelect';
import CheckboxWidget from '../../Form/CheckboxWidget';
import apiClient from '../../../api/api-client';
import {toast} from 'react-toastify';
-import {useDirtyFormPrompt} from '../Tabbed/FormTab';
+import React from "react";
function Item({
- formId,
+ data,
usedFormSubmit: {
+ submitting,
register,
- handleSubmit,
control,
+ reset,
formState: {errors},
- submitting,
- forbidNavigation,
},
workspaceId,
}: DefinitionItemFormProps) {
const {t} = useTranslation();
- useDirtyFormPrompt(forbidNavigation);
+ React.useEffect(() => {
+ reset(data);
+ }, [data]);
return (
-
+ >
);
}
@@ -172,7 +173,7 @@ export default function RenditionDefinitionManager({
};
const onSort: OnSort = async ids => {
- await apiClient.put(`/rendition-definitions/sort`, ids);
+ await apiClient.post(`/rendition-definitions/sort`, ids);
toast.success(t('common.item_sorted', 'Order saved!') as string);
};
diff --git a/databox/client/src/components/Dialog/Workspace/TagManager.tsx b/databox/client/src/components/Dialog/Workspace/TagManager.tsx
index 8b375943c..b01192fc7 100644
--- a/databox/client/src/components/Dialog/Workspace/TagManager.tsx
+++ b/databox/client/src/components/Dialog/Workspace/TagManager.tsx
@@ -9,26 +9,20 @@ import {useTranslation} from 'react-i18next';
import {Controller} from 'react-hook-form';
import FormFieldErrors from '../../Form/FormFieldErrors';
import {deleteTag, getTags, postTag, putTag} from '../../../api/tag';
-import {useDirtyFormPrompt} from '../Tabbed/FormTab';
import ColorPicker from '../../Form/ColorPicker';
function Item({
usedFormSubmit: {
register,
control,
- handleSubmit,
submitting,
- forbidNavigation,
formState: {errors},
},
- formId,
}: DefinitionItemFormProps) {
const {t} = useTranslation();
- useDirtyFormPrompt(forbidNavigation);
-
return (
-
+ >
);
}
diff --git a/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx b/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx
index a6bd3ebf8..b7c0661fb 100644
--- a/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx
+++ b/databox/client/src/components/Dialog/Workspace/WorkspaceDialog.tsx
@@ -15,6 +15,9 @@ import RenditionClassManager from './RenditionClassManager';
import RenditionDefinitionManager from './RenditionDefinitionManager';
import InfoWorkspace from './InfoWorkspace';
import {modalRoutes} from '../../../routes.ts';
+import {useForceLogin} from '@alchemy/react-auth';
+import config from "../../../config.ts";
+import {keycloakClient} from "../../../api/api-client.ts";
type Props = {};
@@ -24,6 +27,11 @@ export default function WorkspaceDialog({}: Props) {
const [data, setData] = useState();
+ useForceLogin({
+ keycloakClient,
+ autoConnectIdP: config.autoConnectIdP,
+ });
+
useEffect(() => {
getWorkspace(id!).then(c => setData(c));
}, [id]);
diff --git a/databox/client/src/components/Form/CollectionForm.tsx b/databox/client/src/components/Form/CollectionForm.tsx
index 3ac8e77e3..3adc31ab2 100644
--- a/databox/client/src/components/Form/CollectionForm.tsx
+++ b/databox/client/src/components/Form/CollectionForm.tsx
@@ -6,7 +6,6 @@ import FormFieldErrors from './FormFieldErrors';
import PrivacyField from '../Ui/PrivacyField';
import FormRow from './FormRow';
import {FormProps} from './types';
-import {useDirtyFormPrompt} from '../Dialog/Tabbed/FormTab';
export const CollectionForm: FC> = function ({
formId,
@@ -15,14 +14,11 @@ export const CollectionForm: FC> = function ({
submitting,
register,
control,
- forbidNavigation,
formState: {errors},
},
}) {
const {t} = useTranslation();
- useDirtyFormPrompt(forbidNavigation);
-
return (