feat(webapps): show and correctly set the active WebUI's resource URL

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.
This commit is contained in:
Steven RYDELL
2026-08-01 19:17:44 +02:00
parent 7fab3540ea
commit 40164a6652
4 changed files with 59 additions and 5 deletions
+9 -4
View File
@@ -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
+25
View File
@@ -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."
+11
View File
@@ -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
+14 -1
View File
@@ -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) {
<span className="text-muted-foreground">{t('webApplications.version', 'Version')}:</span>
<Badge variant="secondary">{__APP_VERSION__}</Badge>
</div>
{typeof activeWebApp.resourceUrl === 'string' && activeWebApp.resourceUrl && (
<div className="flex items-start gap-2 text-sm">
<span className="shrink-0 text-muted-foreground">{t('webApplications.resourceUrl', 'Source')}:</span>
<a
href={activeWebApp.resourceUrl}
target="_blank"
rel="noreferrer"
className="break-all text-primary hover:underline"
>
{activeWebApp.resourceUrl}
</a>
</div>
)}
</CardContent>
</Card>
)}