From 40164a6652f20bdcb79faf56345b979aeb5f0f46 Mon Sep 17 00:00:00 2001 From: Steven RYDELL Date: Sat, 1 Aug 2026 19:17:44 +0200 Subject: [PATCH] feat(webapps): show and correctly set the active WebUI's resource URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Active WebUI" card (Settings > Web Applications) only showed description and __APP_VERSION__, never the resourceUrl it's actually built from — add it as a linked "Source" row. That card also turned out to be misleading in local dev: it reads the x:Application record whose urlPrefix matches /admin or /account, and a freshly bootstrapped server keeps Stalwart's seeded default there ("Stalwart Web Interface", pointing at stalwartlabs/webui's release), regardless of what's actually being served — in dev that's this fork, served directly by Vite, which never touches that record at all. dev-server-init.sh/.ps1 now point that record's description and resourceUrl at this fork's own release, so the card (now including the visible Source URL) reflects what's actually running instead of Stalwart's factory default. Verified end-to-end against a fresh container with both scripts. --- DEVELOPMENT.md | 13 +++++++++---- scripts/dev-server-init.ps1 | 25 +++++++++++++++++++++++++ scripts/dev-server-init.sh | 11 +++++++++++ src/components/lists/DynamicList.tsx | 15 ++++++++++++++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4ca12d4..e37b96e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -55,10 +55,15 @@ bash ./scripts/dev-server-init.sh This completes the bootstrap wizard (default domain `example.org`, no TLS certificate request — safe for local/offline use), creates a real -`devadmin@example.org` admin account, and sets the server's default OAuth -access token lifetime to 3 hours. It's idempotent — safe to re-run, it -no-ops once the server is already bootstrapped. You only need to re-run it -after `docker compose down -v` (which wipes the volumes). +`devadmin@example.org` admin account, sets the server's default OAuth +access token lifetime to 3 hours, and points the `x:Application` record +serving `/admin`/`/account` at this fork's description and release URL +(cosmetic — otherwise Settings > Web Applications' "Active WebUI" card +shows Stalwart's default "Stalwart Web Interface" / upstream release URL, +even though what's actually running is this fork served by Vite). It's +idempotent — safe to re-run, it no-ops once the server is already +bootstrapped. You only need to re-run it after `docker compose down -v` +(which wipes the volumes). ## 3. Get an access token diff --git a/scripts/dev-server-init.ps1 b/scripts/dev-server-init.ps1 index b165e81..7cfa0f4 100644 --- a/scripts/dev-server-init.ps1 +++ b/scripts/dev-server-init.ps1 @@ -119,4 +119,29 @@ try { } | Out-Null } catch {} +# Cosmetic only: Stalwart seeds a default "Stalwart Web Interface" / +# stalwartlabs/webui entry for the x:Application record serving /admin and +# /account (see Settings > Web Applications' "Active WebUI" card). Point it +# at this fork so that card isn't misleading in local dev too. +Write-Host "Pointing the active WebUI's description at this fork..." +try { + $appResult = Invoke-Jmap @{ + using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap") + methodCalls = @(, @("x:Application/query", @{ accountId = $accountId }, "0")) + } + $appId = $appResult.methodResponses[0][1].ids[0] + if ($appId) { + Invoke-Jmap @{ + using = @("urn:ietf:params:jmap:core", "urn:stalwart:jmap") + methodCalls = @(, @("x:Application/set", @{ + accountId = $accountId + update = @{ $appId = @{ + description = "Stalwart WebUI Fork" + resourceUrl = "https://github.com/LinkPhoenix/stalwart-webui-fork/releases/latest/download/webui.zip" + } } + }, "0")) + } | Out-Null + } +} catch {} + Write-Host "Done. $DevAdminName@$DevDomain / $DevAdminSecret is ready - run scripts/dev-token.ps1 to get a token." diff --git a/scripts/dev-server-init.sh b/scripts/dev-server-init.sh index b02f3ed..9ca811a 100644 --- a/scripts/dev-server-init.sh +++ b/scripts/dev-server-init.sh @@ -61,5 +61,16 @@ else jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:OidcProvider/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"singleton\":{\"accessTokenExpiry\":$DEFAULT_TOKEN_EXPIRY_MS}}},\"0\"]]}" > /dev/null jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Action/set\",{\"accountId\":\"$ACCOUNT_ID\",\"create\":{\"a1\":{\"@type\":\"ReloadSettings\"}}},\"0\"]]}" > /dev/null 2>&1 || true + # Cosmetic only: Stalwart seeds a default "Stalwart Web Interface" / + # stalwartlabs/webui entry for the x:Application record serving /admin + # and /account (see Settings > Web Applications' "Active WebUI" card). + # Point it at this fork so that card isn't misleading in local dev too. + echo "Pointing the active WebUI's description at this fork..." + APP_RESULT=$(jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Application/query\",{\"accountId\":\"$ACCOUNT_ID\"},\"0\"]]}") || true + APP_ID=$(printf '%s' "$APP_RESULT" | grep -o '"ids":\["[^"]*"' | cut -d'"' -f4) + if [ -n "$APP_ID" ]; then + jmap "{\"using\":[\"urn:ietf:params:jmap:core\",\"urn:stalwart:jmap\"],\"methodCalls\":[[\"x:Application/set\",{\"accountId\":\"$ACCOUNT_ID\",\"update\":{\"$APP_ID\":{\"description\":\"Stalwart WebUI Fork\",\"resourceUrl\":\"https://github.com/LinkPhoenix/stalwart-webui-fork/releases/latest/download/webui.zip\"}}},\"0\"]]}" > /dev/null 2>&1 || true + fi + echo "Done. devadmin@$DEV_DOMAIN / $DEVADMIN_SECRET is ready — run scripts/dev-token.sh to get a token." fi diff --git a/src/components/lists/DynamicList.tsx b/src/components/lists/DynamicList.tsx index fc62e3e..d55227f 100644 --- a/src/components/lists/DynamicList.tsx +++ b/src/components/lists/DynamicList.tsx @@ -590,7 +590,7 @@ export function DynamicList({ viewName }: DynamicListProps) { objectName ?? 'x:Application', accountId, {}, - ['id', 'description', 'enabled', 'urlPrefix'], + ['id', 'description', 'enabled', 'urlPrefix', 'resourceUrl'], ); if (cancelled) return; const active = result.list.find((item) => isActiveWebApplication(item)); @@ -1573,6 +1573,19 @@ export function DynamicList({ viewName }: DynamicListProps) { {t('webApplications.version', 'Version')}: {__APP_VERSION__} + {typeof activeWebApp.resourceUrl === 'string' && activeWebApp.resourceUrl && ( +
+ {t('webApplications.resourceUrl', 'Source')}: + + {activeWebApp.resourceUrl} + +
+ )} )}