Add BIM Viewer to Synology deploy canon
This commit is contained in:
parent
3526351b1b
commit
6258de83e3
|
|
@ -4,12 +4,15 @@ AUTH_DOMAIN=auth.local.nodedc
|
|||
AUTH_ADMIN_DOMAIN=auth-admin.local.nodedc
|
||||
LAUNCHER_DOMAIN=launcher.local.nodedc
|
||||
TASK_DOMAIN=task.local.nodedc
|
||||
BIM_DOMAIN=bim.local.nodedc
|
||||
NODEDC_BIM_VIEWER_PUBLIC_URL=http://localhost:8080
|
||||
|
||||
# proxy
|
||||
PLATFORM_HTTP_PORT=80
|
||||
PLATFORM_PROXY_IMAGE=nodedc/plane-proxy:ru
|
||||
LOCAL_LAUNCHER_UPSTREAM=host.docker.internal:5173
|
||||
LOCAL_TASK_MANAGER_UPSTREAM=host.docker.internal:8090
|
||||
LOCAL_BIM_VIEWER_UPSTREAM=host.docker.internal:8080
|
||||
|
||||
# authentik image
|
||||
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
AUTH_DOMAIN=auth.staging.nodedc.example
|
||||
LAUNCHER_DOMAIN=launcher.staging.nodedc.example
|
||||
TASK_DOMAIN=task.staging.nodedc.example
|
||||
BIM_DOMAIN=bim.staging.nodedc.example
|
||||
NODEDC_BIM_VIEWER_PUBLIC_URL=https://bim.staging.nodedc.example
|
||||
|
||||
# edge proxy
|
||||
ACME_EMAIL=admin@nodedc.example
|
||||
|
|
@ -10,6 +12,7 @@ PLATFORM_HTTPS_PORT=443
|
|||
PLATFORM_PROXY_IMAGE=caddy:2-alpine
|
||||
STAGING_LAUNCHER_UPSTREAM=launcher:5173
|
||||
STAGING_TASK_MANAGER_UPSTREAM=task-manager-proxy:80
|
||||
STAGING_BIM_VIEWER_UPSTREAM=host.docker.internal:18100
|
||||
|
||||
# authentik image
|
||||
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ AUTH_ADMIN_DOMAIN=auth-admin.nas.nodedc
|
|||
AUTH_ADMIN_LAN_HOST=172.22.0.222
|
||||
LAUNCHER_DOMAIN=launcher.nas.nodedc
|
||||
TASK_DOMAIN=task.nas.nodedc
|
||||
BIM_DOMAIN=bim.nodedc.tech
|
||||
NODEDC_PUBLIC_HTTP_PORT=18080
|
||||
PLATFORM_HTTP_PORT=18080
|
||||
SYNOLOGY_TASK_MANAGER_UPSTREAM=host.docker.internal:18090
|
||||
SYNOLOGY_BIM_VIEWER_UPSTREAM=host.docker.internal:18100
|
||||
|
||||
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server
|
||||
AUTHENTIK_TAG=2026.2.2
|
||||
|
|
@ -25,6 +27,7 @@ AUTHENTIK_BOOTSTRAP_TOKEN=replace-with-random-synology-secret
|
|||
|
||||
LAUNCHER_BASE_URL=https://hub.nodedc.ru
|
||||
TASK_BASE_URL=https://ops.nodedc.ru
|
||||
NODEDC_BIM_VIEWER_PUBLIC_URL=https://bim.nodedc.tech
|
||||
TASK_LOGOUT_URI=https://ops.nodedc.ru/logout
|
||||
TASK_INTERNAL_LOGOUT_URL=https://ops.nodedc.ru/api/internal/nodedc/logout/
|
||||
NODEDC_ENGINE_DOCKER_NETWORK=nodedc-demo_default
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ http://{$TASK_DOMAIN} {
|
|||
}
|
||||
}
|
||||
|
||||
http://{$BIM_DOMAIN:bim.nodedc.ru} {
|
||||
reverse_proxy {$SYNOLOGY_BIM_VIEWER_UPSTREAM:ndc-beam-viewer:8080} {
|
||||
http://{$BIM_DOMAIN:bim.nodedc.tech} {
|
||||
reverse_proxy {$SYNOLOGY_BIM_VIEWER_UPSTREAM:host.docker.internal:18100} {
|
||||
header_up Host {http.request.host}
|
||||
header_up X-Forwarded-Host {http.request.host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
header_up X-Forwarded-Port {$NODEDC_PUBLIC_HTTP_PORT}
|
||||
header_up X-Forwarded-Proto https
|
||||
header_up X-Forwarded-Port 443
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- Не выполнять `docker stop`, `docker restart`, `docker compose down`, `docker system prune` для старых проектов.
|
||||
- Новый compose project: `nodedc-platform`.
|
||||
- Новая папка на NAS: `/volume1/docker/nodedc-platform`.
|
||||
- Внутренний HTTP edge использует `18080`, AI Workspace Hub — `18081`, Tasker upstream — `18090`, Ops Agents Gateway upstream — `18190`.
|
||||
- Внутренний HTTP edge использует `18080`, AI Workspace Hub — `18081`, Tasker upstream — `18090`, BIM Viewer upstream — `18100`, Ops Agents Gateway upstream — `18190`.
|
||||
- Старые порты `9000` и `5678` заняты старым `nodedc-demo` и не используются.
|
||||
|
||||
## Текущие внешние домены
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
https://id.nodedc.ru -> Authentik
|
||||
https://hub.nodedc.ru -> Launcher / Hub
|
||||
https://ops.nodedc.ru -> Tasker / Operational Core
|
||||
https://bim.nodedc.tech -> BIM Viewer
|
||||
https://ops-agents.nodedc.ru -> Ops Agents Gateway / MCP
|
||||
https://ai-hub.nodedc.ru -> AI Workspace Hub / WebSocket relay
|
||||
```
|
||||
|
|
@ -26,6 +27,8 @@ https://ai-hub.nodedc.ru -> AI Workspace Hub / WebSocket relay
|
|||
|
||||
В `Caddyfile.http` эти домены проксируются через локальный HTTP edge, но upstream получает `X-Forwarded-Proto: https` и `X-Forwarded-Port: 443`.
|
||||
|
||||
`bim.nodedc.tech` can either be routed by DSM directly to the BIM host port `172.22.0.222:18100`, or through the platform HTTP edge `172.22.0.222:18080`. When it goes through the platform edge, `BIM_DOMAIN=bim.nodedc.tech` and `SYNOLOGY_BIM_VIEWER_UPSTREAM=host.docker.internal:18100` keep the same target service.
|
||||
|
||||
## AI Workspace Hub / Assistant contract
|
||||
|
||||
`ai-workspace-hub` remains a thin public WebSocket relay. It is the only AI Workspace service exposed through DSM/Nginx as `https://ai-hub.nodedc.ru`.
|
||||
|
|
@ -83,8 +86,8 @@ http://task.nas.nodedc:18090
|
|||
|
||||
- `docker-compose.platform-http.yml` поднимает новый Authentik, Launcher, Notification Core, AI Workspace Hub и Caddy edge.
|
||||
- `Caddyfile.http` маршрутизирует локальные `auth/auth-admin/launcher/task.nas.nodedc`, IP fallback `172.22.0.222` для Authentik Admin и внешние `id/hub/ops.nodedc.ru`.
|
||||
- `deploy-current.sh` синхронизирует compose, Caddyfile, Notification Core source, AI Workspace Hub source и опционально Launcher source в NAS mount. Authentik templates синхронизируются только при явном `SYNC_AUTHENTIK_TEMPLATES=1`.
|
||||
- `backup-current.sh` делает snapshot Launcher runtime/uploads/Auth templates/config и готовит команду `pg_dump` для Authentik Postgres.
|
||||
- `deploy-current.sh` синхронизирует compose, Caddyfile, Notification Core source, AI Workspace Hub source и опционально Launcher/BIM source в NAS mount. Authentik templates синхронизируются только при явном `SYNC_AUTHENTIK_TEMPLATES=1`.
|
||||
- `backup-current.sh` делает snapshot Launcher runtime/uploads/Auth templates/config, BIM Viewer `server/data` и готовит команду `pg_dump` для Authentik Postgres.
|
||||
- Tasker поднимается отдельным compose из `NODEDC_TASKMANAGER/plane-app/docker-compose.yaml` на порту `18090`.
|
||||
- Ops Agents Gateway поднимается отдельным compose из `NODEDC_TASKMANAGER_CODEXAPI/docker-compose.synology.yml` на `172.22.0.222:18190`; Synology reverse proxy должен вести `ops-agents.nodedc.ru` на этот порт, а не на `18090`.
|
||||
|
||||
|
|
@ -137,6 +140,7 @@ exit 1
|
|||
cd /Users/dcconstructions/Downloads/mnt/NODEDC/platform
|
||||
NAS_ROOT=/Volumes/docker/nodedc-platform \
|
||||
LAUNCHER_REPO=/Users/dcconstructions/Downloads/mnt/data/nodedc_launcher \
|
||||
BIM_REPO=/Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_BIM_VIEWER \
|
||||
TASKER_REPO=/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER \
|
||||
TASKER_CHANGED_BASE=533f8c6 \
|
||||
GATEWAY_REPO=/Users/dcconstructions/Downloads/mnt/data/NODEDC_TASKMANAGER_CODEXAPI \
|
||||
|
|
@ -154,6 +158,7 @@ GATEWAY_REPO=/Users/dcconstructions/Downloads/mnt/data/NODEDC_TASKMANAGER_CODEXA
|
|||
- AI Workspace Hub source в `/volume1/docker/nodedc-platform/platform/ai-workspace-hub`.
|
||||
- Authentik templates только при `SYNC_AUTHENTIK_TEMPLATES=1`; по умолчанию они не трогаются, чтобы лёгкий Hub deploy не уносил экспериментальную тему/брендинг в prod.
|
||||
- Launcher source в `/volume1/docker/nodedc-platform/launcher/source`.
|
||||
- BIM Viewer source в `/volume1/docker/nodedc-platform/bim-viewer/source`; `server/data` не синхронизируется и остаётся live runtime-хранилищем моделей, shares, comments и refs.
|
||||
- Tasker `plane-app/docker-compose.yaml` и, если задан `TASKER_CHANGED_BASE`, только изменённые source-файлы из диапазона `TASKER_CHANGED_BASE..HEAD`.
|
||||
- Ops Agents Gateway source в `/volume1/docker/nodedc-platform/ops-agents`.
|
||||
|
||||
|
|
@ -166,6 +171,7 @@ TASKER_SYNC_SOURCE=1 ./infra/synology/deploy-current.sh
|
|||
Секретные runtime env-файлы не перетираются:
|
||||
|
||||
- `/volume1/docker/nodedc-platform/platform/.env.synology`
|
||||
- `/volume1/docker/nodedc-platform/bim-viewer/source/.env`
|
||||
- `/volume1/docker/nodedc-platform/tasker/plane-app/.env.synology`
|
||||
- `/volume1/docker/nodedc-platform/ops-agents/.env`
|
||||
|
||||
|
|
@ -241,6 +247,45 @@ fi
|
|||
echo 'launcher-pending-gate-ok'
|
||||
```
|
||||
|
||||
## BIM Viewer deploy
|
||||
|
||||
`bim.nodedc.tech` на Synology можно вести напрямую через DSM Reverse Proxy на `172.22.0.222:18100`. В этом режиме Platform Caddy не участвует в публичном BIM-трафике, но `Caddyfile.http` всё равно содержит BIM route как запасной вариант для маршрута через `172.22.0.222:18080`.
|
||||
|
||||
Первый старт или обновление BIM после `BIM_REPO` sync:
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/nodedc-platform/bim-viewer/source
|
||||
sudo cp -n .env.synology.example .env
|
||||
# Проверить .env: NODEDC_INTERNAL_ACCESS_TOKEN должен совпадать с platform/.env.synology.
|
||||
sudo /usr/local/bin/docker compose \
|
||||
--env-file .env \
|
||||
-f docker-compose.beam.yml \
|
||||
up -d --build --force-recreate ndc-beam-viewer nodedc-bim-converter
|
||||
```
|
||||
|
||||
Минимальная проверка на Synology:
|
||||
|
||||
```bash
|
||||
curl -k -fsS http://127.0.0.1:18100/api/auth/session
|
||||
curl -k -sS -o /dev/null -w '%{http_code}\n' https://bim.nodedc.tech/
|
||||
```
|
||||
|
||||
Ожидаемо: `/api/auth/session` возвращает `authRequired: true`, а прямой public root без BIM-сессии отдаёт `302` в Launcher login/launch. Public share `/share/<token>` должен открываться без авторизации и без toolbar.
|
||||
|
||||
## Tasker / OPS BIM iframe rebuild
|
||||
|
||||
OPS web bundle получает BIM URL на build-time через `VITE_BEAM_VIEWER_BASE_URL` и `VITE_BEAM_API_BASE_URL`. После смены домена пересобрать только web image:
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/nodedc-platform/tasker/plane-src
|
||||
VITE_BEAM_VIEWER_BASE_URL=https://bim.nodedc.tech \
|
||||
VITE_BEAM_API_BASE_URL=https://bim.nodedc.tech \
|
||||
BUILD_BACKEND=0 BUILD_WEB=1 BUILD_ADMIN=0 \
|
||||
sh rebuild-nas-legacy.sh
|
||||
```
|
||||
|
||||
Это пересоздаёт только `web` и не трогает PostgreSQL/MinIO volumes.
|
||||
|
||||
## Authentik Admin и Brand CSS
|
||||
|
||||
NODE.DC auth-flow CSS must stay template-scoped. Do not store it in Authentik `Brand.branding_custom_css`: Authentik passes that CSS into Admin/User web component runtime and breaks native controls.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ mkdir -p "${BACKUP_DIR}/files/platform" \
|
|||
"${BACKUP_DIR}/files/launcher" \
|
||||
"${BACKUP_DIR}/files/authentik" \
|
||||
"${BACKUP_DIR}/files/tasker" \
|
||||
"${BACKUP_DIR}/files/ops-agents"
|
||||
"${BACKUP_DIR}/files/ops-agents" \
|
||||
"${BACKUP_DIR}/files/bim-viewer"
|
||||
|
||||
rsync_dir() {
|
||||
local source="$1"
|
||||
|
|
@ -47,6 +48,7 @@ rsync_dir "${NAS_ROOT}/platform/authentik/" "${BACKUP_DIR}/files/platform/authen
|
|||
rsync_dir "${NAS_ROOT}/platform/notification-core/" "${BACKUP_DIR}/files/platform/notification-core/"
|
||||
rsync_dir "${NAS_ROOT}/platform/ai-workspace-hub/" "${BACKUP_DIR}/files/platform/ai-workspace-hub/"
|
||||
rsync_dir "${NAS_ROOT}/platform/ai-workspace-assistant/" "${BACKUP_DIR}/files/platform/ai-workspace-assistant/"
|
||||
rsync_dir "${NAS_ROOT}/bim-viewer/source/server/data/" "${BACKUP_DIR}/files/bim-viewer/server-data/"
|
||||
|
||||
rsync_file "${NAS_ROOT}/platform/.env.synology" "${BACKUP_DIR}/files/platform/"
|
||||
rsync_file "${NAS_ROOT}/platform/.env.synology.example" "${BACKUP_DIR}/files/platform/"
|
||||
|
|
@ -61,6 +63,10 @@ rsync_file "${NAS_ROOT}/ops-agents/.env" "${BACKUP_DIR}/files/ops-agents/"
|
|||
rsync_file "${NAS_ROOT}/ops-agents/.env.synology.example" "${BACKUP_DIR}/files/ops-agents/"
|
||||
rsync_file "${NAS_ROOT}/ops-agents/docker-compose.synology.yml" "${BACKUP_DIR}/files/ops-agents/"
|
||||
|
||||
rsync_file "${NAS_ROOT}/bim-viewer/source/.env" "${BACKUP_DIR}/files/bim-viewer/"
|
||||
rsync_file "${NAS_ROOT}/bim-viewer/source/.env.synology.example" "${BACKUP_DIR}/files/bim-viewer/"
|
||||
rsync_file "${NAS_ROOT}/bim-viewer/source/docker-compose.beam.yml" "${BACKUP_DIR}/files/bim-viewer/"
|
||||
|
||||
cat > "${BACKUP_DIR}/manifest.txt" <<EOF
|
||||
NODE.DC platform current backup
|
||||
timestamp=${TIMESTAMP}
|
||||
|
|
@ -77,11 +83,13 @@ Contains:
|
|||
- AI Workspace Assistant source/config: platform/ai-workspace-assistant, platform compose/env
|
||||
- Tasker runtime config: tasker/plane-app/.env.synology, compose, Synology override
|
||||
- Ops Agents Gateway runtime config: ops-agents/.env, compose
|
||||
- BIM Viewer runtime data/config: bim-viewer/source/server/data, .env, compose
|
||||
|
||||
Secrets:
|
||||
- files/platform/.env.synology contains live secrets.
|
||||
- files/tasker/.env.synology contains live secrets.
|
||||
- files/ops-agents/.env contains live secrets.
|
||||
- files/bim-viewer/.env contains live secrets.
|
||||
- Keep this backup private.
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ TASKER_REPO="${TASKER_REPO:-}"
|
|||
TASKER_SYNC_SOURCE="${TASKER_SYNC_SOURCE:-0}"
|
||||
TASKER_CHANGED_BASE="${TASKER_CHANGED_BASE:-}"
|
||||
GATEWAY_REPO="${GATEWAY_REPO:-}"
|
||||
BIM_REPO="${BIM_REPO:-}"
|
||||
SYNC_AUTHENTIK_TEMPLATES="${SYNC_AUTHENTIK_TEMPLATES:-0}"
|
||||
|
||||
RSYNC_METADATA_ARGS=()
|
||||
|
|
@ -24,6 +25,10 @@ fi
|
|||
|
||||
mkdir -p "${NAS_ROOT}/platform"
|
||||
|
||||
rsync -av "${RSYNC_METADATA_ARGS[@]}" \
|
||||
"${PLATFORM_REPO}/infra/synology/.env.synology.example" \
|
||||
"${NAS_ROOT}/platform/.env.synology.example"
|
||||
|
||||
rsync -av "${RSYNC_METADATA_ARGS[@]}" \
|
||||
"${PLATFORM_REPO}/infra/synology/docker-compose.platform-http.yml" \
|
||||
"${NAS_ROOT}/platform/docker-compose.platform-http.yml"
|
||||
|
|
@ -90,6 +95,7 @@ if [[ -n "${LAUNCHER_REPO}" ]]; then
|
|||
mkdir -p "${NAS_ROOT}/launcher/source"
|
||||
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='dist/' \
|
||||
--exclude='server/storage/*' \
|
||||
|
|
@ -100,6 +106,35 @@ else
|
|||
echo "LAUNCHER_REPO is not set; launcher source was not synced."
|
||||
fi
|
||||
|
||||
if [[ -n "${BIM_REPO}" ]]; then
|
||||
if [[ ! -f "${BIM_REPO}/docker-compose.beam.yml" || ! -d "${BIM_REPO}/server" || ! -d "${BIM_REPO}/frontend" ]]; then
|
||||
echo "BIM_REPO must point to NODEDC_BIM_VIEWER with docker-compose.beam.yml, server and frontend: ${BIM_REPO}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${NAS_ROOT}/bim-viewer/source"
|
||||
rm -rf \
|
||||
"${NAS_ROOT}/bim-viewer/source/app" \
|
||||
"${NAS_ROOT}/bim-viewer/source/docs"
|
||||
rsync -av "${RSYNC_METADATA_ARGS[@]}" --delete \
|
||||
--exclude='.git/' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='dist/' \
|
||||
--exclude='app/' \
|
||||
--exclude='docs/' \
|
||||
--exclude='server/node_modules/' \
|
||||
--exclude='frontend/node_modules/' \
|
||||
--exclude='server/data/' \
|
||||
--include='.env.synology.example' \
|
||||
--exclude='.env' \
|
||||
--exclude='.env.*' \
|
||||
"${BIM_REPO}/" \
|
||||
"${NAS_ROOT}/bim-viewer/source/"
|
||||
else
|
||||
echo "BIM_REPO is not set; BIM Viewer source was not synced."
|
||||
fi
|
||||
|
||||
if [[ -n "${TASKER_REPO}" ]]; then
|
||||
if [[ ! -d "${TASKER_REPO}/plane-src" || ! -d "${TASKER_REPO}/plane-app" ]]; then
|
||||
echo "TASKER_REPO must point to NODEDC_TASKMANAGER with plane-src and plane-app: ${TASKER_REPO}" >&2
|
||||
|
|
@ -208,6 +243,18 @@ If runtime was already applied and only verification is needed:
|
|||
cd /volume1/docker/nodedc-platform/platform
|
||||
sudo bash verify-current-runtime.sh
|
||||
|
||||
Optional BIM Viewer apply after BIM_REPO sync:
|
||||
|
||||
cd /volume1/docker/nodedc-platform/bim-viewer/source
|
||||
sudo cp -n .env.synology.example .env
|
||||
# Edit .env before first production start: NODEDC_INTERNAL_ACCESS_TOKEN must match platform .env.synology.
|
||||
sudo /usr/local/bin/docker compose \
|
||||
--env-file .env \
|
||||
-f docker-compose.beam.yml \
|
||||
up -d --build --force-recreate ndc-beam-viewer nodedc-bim-converter
|
||||
|
||||
curl -k -fsS http://127.0.0.1:18100/api/auth/session
|
||||
|
||||
Manual equivalent:
|
||||
|
||||
cd /volume1/docker/nodedc-platform/platform
|
||||
|
|
|
|||
|
|
@ -71,6 +71,20 @@ echo "== ai workspace assistant Ops entitlement adapter check =="
|
|||
"${DOCKER_BIN}" exec nodedc-platform-ai-workspace-assistant-1 sh -lc \
|
||||
'test "$AI_WORKSPACE_OPS_ENTITLEMENT_URL" = "http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements" && test -n "$AI_WORKSPACE_OPS_ENTITLEMENT_TOKEN" && echo ai-workspace-ops-entitlement-env-ok'
|
||||
|
||||
echo "== BIM viewer auth/session check =="
|
||||
bim_session="$(fetch_with_retry http://127.0.0.1:18100/api/auth/session)"
|
||||
printf '%s' "$bim_session" | grep -aE '"authRequired":true|"loginUrl"'
|
||||
|
||||
echo "== BIM viewer public redirect check =="
|
||||
bim_public_status="$(
|
||||
curl -k -sS -o /dev/null -w '%{http_code}' https://bim.nodedc.tech/
|
||||
)"
|
||||
if [[ "$bim_public_status" != "302" ]]; then
|
||||
echo "public BIM root did not redirect to Launcher auth: status=${bim_public_status}"
|
||||
exit 1
|
||||
fi
|
||||
echo "bim-public-auth-redirect-ok"
|
||||
|
||||
echo "== auth flow check =="
|
||||
auth_flow="$(fetch_with_retry https://id.nodedc.ru/if/flow/default-authentication-flow/)"
|
||||
printf '%s' "$auth_flow" \
|
||||
|
|
|
|||
Loading…
Reference in New Issue