feat(ui,server): surface active instance service URLs in About settings (#2669)

Show the running instance's local server URL and tunnel URL (when a
tunnel is active) as labeled, click-to-open buttons on the About page.
/api/system/info now reports the instance port and tunnel URL, resolved
lazily from the tunnel runtime so each Git-worktree instance identifies
itself in the UI without parsing terminal output.

Refs OPE-194
This commit is contained in:
Serhii Dziupin
2026-08-07 00:25:57 +03:00
committed by GitHub
parent 668a6f54fe
commit 834d2edb87
17 changed files with 214 additions and 0 deletions
@@ -9,6 +9,7 @@ import { Icon } from "@/components/icon/Icon";
import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo';
import { useI18n } from '@/lib/i18n';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { InstanceServiceUrls } from './InstanceServiceUrls';
import {
SettingsSection,
SETTINGS_BRAND_TITLE_CLASS,
@@ -135,6 +136,7 @@ export const AboutSettings: React.FC<AboutSettingsProps> = ({ initialUpdateDialo
<p>{t('aboutDialog.openChamberVersionLabel', { version: currentVersion })}</p>
<p>{t('aboutDialog.openCodeVersionLabel', { version: openCodeVersion || t('settings.openchamber.about.state.unknown') })}</p>
</div>
<InstanceServiceUrls />
</div>
<div className="flex justify-center">
@@ -278,6 +280,11 @@ export const AboutSettings: React.FC<AboutSettingsProps> = ({ initialUpdateDialo
</div>
)}
<div className="flex flex-col gap-2 border-b border-border/40 px-4 py-3 @xl:flex-row @xl:items-center @xl:justify-between">
<span className={SETTINGS_FIELD_LABEL_CLASS}>{t('settings.openchamber.about.field.instanceUrls')}</span>
<InstanceServiceUrls />
</div>
<div className="flex items-center gap-4 px-4 py-4">
<a
href={GITHUB_URL}
@@ -0,0 +1,106 @@
import React from 'react';
import { useI18n } from '@/lib/i18n';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { openExternalUrl } from '@/lib/url';
import { Button } from '@/components/ui/button';
import { Icon } from '@/components/icon/Icon';
type InstanceServiceInfo = {
port: number | null;
tunnelUrl: string | null;
};
type InstanceService = {
key: string;
label: string;
url: string;
};
/**
* Shows the active instance's service URLs (local server port + tunnel URL,
* when a tunnel is active) as labeled buttons that open the URL in the
* browser. The data comes from `/api/system/info`, which the server derives
* from its own runtime state — this is what makes each Git-worktree instance
* distinguishable in the UI without reading terminal output.
*
* The section stays hidden when the endpoint is unavailable or reports no
* port/tunnel (e.g. VS Code runtime), so a failed fetch never renders stale
* or wrong URLs.
*/
export const InstanceServiceUrls: React.FC = () => {
const { t } = useI18n();
const [info, setInfo] = React.useState<InstanceServiceInfo | null>(null);
React.useEffect(() => {
let cancelled = false;
const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
const load = async () => {
try {
const response = await runtimeFetch('/api/system/info', {
signal: controller?.signal,
headers: { Accept: 'application/json' },
});
if (!response.ok) return;
const data = await response.json().catch(() => null) as { port?: unknown; tunnelUrl?: unknown } | null;
if (!data || cancelled) return;
const port = typeof data.port === 'number' && Number.isFinite(data.port) && data.port > 0 ? data.port : null;
const tunnelUrl = typeof data.tunnelUrl === 'string' && data.tunnelUrl.trim().length > 0
? data.tunnelUrl.trim()
: null;
setInfo({ port, tunnelUrl });
} catch {
// Best-effort: a failed fetch keeps the section hidden instead of
// showing data we cannot verify.
}
};
void load();
return () => {
cancelled = true;
controller?.abort();
};
}, []);
const services: InstanceService[] = [];
if (info?.port !== null && info?.port !== undefined) {
services.push({
key: 'application',
label: t('settings.openchamber.about.field.applicationUrl'),
url: `http://localhost:${info.port}/`,
});
}
if (info?.tunnelUrl) {
services.push({
key: 'tunnel',
label: t('settings.openchamber.about.field.tunnelUrl'),
url: info.tunnelUrl,
});
}
if (services.length === 0) {
return null;
}
return (
<div className="flex flex-wrap items-center gap-2">
{services.map((service) => (
<Button
key={service.key}
type="button"
variant="outline"
size="sm"
title={service.label}
className="max-w-full gap-1.5 px-2.5"
onClick={() => {
void openExternalUrl(service.url);
}}
>
<Icon name="external-link" className="size-3.5 shrink-0" />
<span className="max-w-64 truncate font-mono typography-micro">{service.url}</span>
</Button>
))}
</div>
);
};