diff --git a/.yoi/profiles.toml b/.yoi/profiles.toml index 7fde639f..de011901 100644 --- a/.yoi/profiles.toml +++ b/.yoi/profiles.toml @@ -1 +1,3 @@ default = "builtin:companion" + +[profile] diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css index 9d9bd050..e11f749b 100644 --- a/web/workspace/src/app.css +++ b/web/workspace/src/app.css @@ -1446,3 +1446,212 @@ display: block; } } + +@layer components { + .settings-page { + display: grid; + gap: var(--space-5); + } + + .settings-nav { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + } + + .settings-nav a, + .settings-section-card, + .button-link { + color: inherit; + text-decoration: none; + } + + .settings-nav a, + .settings-section-card { + display: grid; + gap: var(--space-1); + min-width: min(16rem, 100%); + padding: var(--space-3) var(--space-4); + border: 1px solid var(--line); + border-radius: var(--radius-panel); + background: var(--bg-raised); + } + + .settings-nav a:hover, + .settings-nav a:focus-visible, + .settings-nav a.active, + .settings-section-card:hover, + .settings-section-card:focus-visible { + background: var(--interactive-hover); + } + + .settings-nav span, + .settings-section-card h3 { + color: var(--text-strong); + font-weight: 800; + } + + .settings-section-grid, + .settings-profile-grid, + .settings-pattern-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); + gap: var(--space-4); + } + + .settings-card { + display: grid; + gap: var(--space-4); + } + + .settings-card-header, + .settings-section-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); + } + + .settings-form, + .settings-registry-editor { + display: grid; + gap: var(--space-3); + } + + .settings-form label, + .settings-registry-editor label { + display: grid; + gap: var(--space-1); + color: var(--text-muted); + font-size: 0.82rem; + } + + .settings-form input, + .settings-form select, + .settings-form textarea, + .settings-registry-editor input, + .settings-registry-editor select, + .settings-runtime-form input { + width: 100%; + border: 1px solid var(--line); + border-radius: 0.55rem; + background: var(--bg-raised); + color: var(--text-strong); + padding: 0.45rem 0.55rem; + font: inherit; + } + + .settings-form textarea { + min-height: 18rem; + resize: vertical; + font-family: var(--font-mono); + } + + .settings-form button, + .button-link { + width: fit-content; + border: 0; + border-radius: 0.6rem; + background: var(--accent); + color: var(--bg); + font-weight: 700; + padding: 0.5rem 0.75rem; + cursor: pointer; + } + + .settings-form button:disabled { + cursor: not-allowed; + opacity: 0.55; + } + + .settings-form button.danger { + background: var(--danger); + } + + .settings-note { + margin: 0; + color: var(--text-muted); + } + + .settings-profile-list { + display: grid; + gap: var(--space-3); + margin: 0; + padding: 0; + list-style: none; + } + + .settings-profile-list li, + .settings-registry-editor fieldset, + .settings-pattern-grid article { + display: grid; + gap: var(--space-2); + padding: var(--space-3); + border: 1px solid var(--line); + border-radius: var(--radius-panel); + background: var(--bg-raised); + } + + .settings-profile-list strong, + .settings-profile-list span, + .settings-profile-list small { + display: block; + } + + .settings-profile-list strong { + color: var(--text-strong); + } + + .settings-profile-list span, + .settings-profile-list small, + .settings-profile-list p { + margin: 0; + color: var(--text-muted); + } + + .settings-registry-editor fieldset { + margin: 0; + } + + .settings-registry-editor legend { + color: var(--text-strong); + font-weight: 750; + } + + .settings-identity-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr)); + gap: var(--space-3); + margin: 0; + } + + .settings-identity-list div { + display: grid; + gap: var(--space-1); + } + + .settings-identity-list dt { + color: var(--text-muted); + font-size: 0.78rem; + } + + .settings-identity-list dd { + margin: 0; + color: var(--text-strong); + } + + .section-status-pill { + width: fit-content; + border-radius: 999px; + padding: 0.25rem 0.55rem; + background: var(--bg-subtle); + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 800; + text-transform: uppercase; + } + + .status-message.error { + color: var(--danger); + } +} diff --git a/web/workspace/src/lib/workspace-settings/DiagnosticsList.svelte b/web/workspace/src/lib/workspace-settings/DiagnosticsList.svelte new file mode 100644 index 00000000..475ea6b4 --- /dev/null +++ b/web/workspace/src/lib/workspace-settings/DiagnosticsList.svelte @@ -0,0 +1,20 @@ + + +{#if diagnostics.length > 0} + +{/if} diff --git a/web/workspace/src/lib/workspace-settings/model.test.ts b/web/workspace/src/lib/workspace-settings/model.test.ts index bd4c96ad..b8cbac6d 100644 --- a/web/workspace/src/lib/workspace-settings/model.test.ts +++ b/web/workspace/src/lib/workspace-settings/model.test.ts @@ -1,9 +1,9 @@ import { + diagnosticLabel, SETTINGS_PATTERNS, SETTINGS_PERMISSION_NOTICE, SETTINGS_ROUTE, SETTINGS_SECTIONS, - diagnosticLabel, settingsSectionHref, } from "./model.ts"; @@ -23,12 +23,12 @@ Deno.test("settings section navigation stays under the settings route", () => { for (const section of SETTINGS_SECTIONS) { const href = settingsSectionHref(section.id); assert( - href.startsWith("/settings#"), + href.startsWith("/settings"), `${section.id} should link under settings`, ); assert( - href.endsWith(section.id), - `${section.id} href should preserve section id`, + !href.includes("#"), + `${section.id} href should use a dedicated route instead of a page anchor`, ); } }); @@ -48,7 +48,10 @@ Deno.test("runtime connections are editable without advertising raw authority le const runtimeSection = SETTINGS_SECTIONS.find((section) => section.id === "runtime-connections" ); - assert(runtimeSection?.status === "editable", "Runtime Connections should be editable"); + assert( + runtimeSection?.status === "editable", + "Runtime Connections should be editable", + ); const allText = [ SETTINGS_PERMISSION_NOTICE, @@ -61,7 +64,8 @@ Deno.test("runtime connections are editable without advertising raw authority le ].join("\n"); assert( - allText.includes("restart_required=true") || allText.includes("Restart-required"), + allText.includes("restart_required=true") || + allText.includes("Restart-required"), "restart-required pattern should be visible", ); assert( @@ -93,7 +97,8 @@ Deno.test("diagnostic labels preserve severity and code", () => { message: "Restart required.", } as const; assert( - diagnosticLabel(diagnostic) === "warning: runtime_registry_restart_required", + diagnosticLabel(diagnostic) === + "warning: runtime_registry_restart_required", "diagnostic label should be bounded and stable", ); }); diff --git a/web/workspace/src/lib/workspace-settings/model.ts b/web/workspace/src/lib/workspace-settings/model.ts index 8c0b4689..7fad42f4 100644 --- a/web/workspace/src/lib/workspace-settings/model.ts +++ b/web/workspace/src/lib/workspace-settings/model.ts @@ -142,7 +142,16 @@ export const SETTINGS_PATTERNS: readonly SettingsPattern[] = [ ]; export function settingsSectionHref(id: SettingsSectionId): string { - return `${SETTINGS_ROUTE}#${id}`; + switch (id) { + case "runtime-connections": + return `${SETTINGS_ROUTE}/runtime-connections`; + case "profile-sources": + return `${SETTINGS_ROUTE}/profiles`; + case "workspace-identity": + return `${SETTINGS_ROUTE}/workspace`; + case "backend-config": + return `${SETTINGS_ROUTE}/backend`; + } } export function diagnosticLabel(diagnostic: Diagnostic): string { diff --git a/web/workspace/src/lib/workspace-settings/profile-api.ts b/web/workspace/src/lib/workspace-settings/profile-api.ts index 79141333..3b3df0c3 100644 --- a/web/workspace/src/lib/workspace-settings/profile-api.ts +++ b/web/workspace/src/lib/workspace-settings/profile-api.ts @@ -1,4 +1,7 @@ -import { workspaceApiJson, workspaceApiJsonWithBody } from "../workspace-api/http"; +import { + workspaceApiJson, + workspaceApiJsonWithBody, +} from "../workspace-api/http"; import type { ProfileSettingsMutationResponse, ProfileSettingsResponse, @@ -7,32 +10,51 @@ import type { WorkspaceProfileSourceDetailResponse, } from "./profile-types"; -export function fetchWorkspaceMetadataSettings(workspaceId: string): Promise { - return workspaceApiJson(`/api/w/${encodeURIComponent(workspaceId)}/settings/workspace`); +export function fetchWorkspaceMetadataSettings( + workspaceId: string, +): Promise { + return workspaceApiJson( + `/api/w/${encodeURIComponent(workspaceId)}/settings/workspace`, + ); } export function updateWorkspaceMetadataSettings( workspaceId: string, request: { display_name: string; revision: string }, ): Promise { - return workspaceApiJsonWithBody(`/api/w/${encodeURIComponent(workspaceId)}/settings/workspace`, { - method: "PUT", - body: JSON.stringify(request), - }); + return workspaceApiJsonWithBody( + `/api/w/${encodeURIComponent(workspaceId)}/settings/workspace`, + { + method: "PUT", + body: JSON.stringify(request), + }, + ); } -export function fetchProfileSettings(workspaceId: string): Promise { - return workspaceApiJson(`/api/w/${encodeURIComponent(workspaceId)}/settings/profiles`); +export function fetchProfileSettings( + workspaceId: string, +): Promise { + return workspaceApiJson( + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles`, + ); } export function createProfileSource( workspaceId: string, - request: { name: string; description?: string; content: string; registry_revision: string }, + request: { + name: string; + description?: string; + content: string; + registry_revision: string; + }, ): Promise { - return workspaceApiJsonWithBody(`/api/w/${encodeURIComponent(workspaceId)}/settings/profiles`, { - method: "POST", - body: JSON.stringify(request), - }); + return workspaceApiJsonWithBody( + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles`, + { + method: "POST", + body: JSON.stringify(request), + }, + ); } export function updateProfileRegistry( @@ -40,13 +62,22 @@ export function updateProfileRegistry( request: { registry_revision: string; default_profile?: string | null; - profiles: Array<{ name: string; description?: string | null; profile_source_id?: string | null }>; + profiles: Array< + { + name: string; + description?: string | null; + profile_source_id?: string | null; + } + >; }, ): Promise { - return workspaceApiJsonWithBody(`/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/registry`, { - method: "PUT", - body: JSON.stringify(request), - }); + return workspaceApiJsonWithBody( + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/registry`, + { + method: "PUT", + body: JSON.stringify(request), + }, + ); } export function fetchProfileSource( @@ -54,7 +85,9 @@ export function fetchProfileSource( sourceId: string, ): Promise { return workspaceApiJson( - `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${encodeURIComponent(sourceId)}`, + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${ + encodeURIComponent(sourceId) + }`, ); } @@ -64,7 +97,9 @@ export function updateProfileSource( request: { content: string; revision: string }, ): Promise { return workspaceApiJsonWithBody( - `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${encodeURIComponent(sourceId)}`, + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${ + encodeURIComponent(sourceId) + }`, { method: "PUT", body: JSON.stringify(request) }, ); } @@ -75,7 +110,9 @@ export function deleteProfileSource( request: { registry_revision: string; source_revision: string }, ): Promise { return workspaceApiJsonWithBody( - `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${encodeURIComponent(sourceId)}`, + `/api/w/${encodeURIComponent(workspaceId)}/settings/profiles/${ + encodeURIComponent(sourceId) + }`, { method: "DELETE", body: JSON.stringify(request) }, ); } diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/+layout.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/+layout.svelte new file mode 100644 index 00000000..84196281 --- /dev/null +++ b/web/workspace/src/routes/w/[workspaceId]/settings/+layout.svelte @@ -0,0 +1,46 @@ + + +
+
+

Settings / Admin

+

Workspace settings

+

+ Configure workspace metadata, runtime connections, and Decodal profile sources through the Backend. +

+
+ + + + {@render children()} +
diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/+page.svelte index 4c7faefb..7328f8e9 100644 --- a/web/workspace/src/routes/w/[workspaceId]/settings/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/settings/+page.svelte @@ -1,789 +1,59 @@ - - Settings · Yoi Workspace - - -
-
-
-

Workspace Browser

-

Settings / Admin

-

- Local administration surfaces for the Workspace backend. Runtime Connections v0 is editable through typed APIs; broader admin controls remain bounded placeholders. -

-
- local only -
- -
-
-

Authority boundary

-

No browser admin permission model

-

{SETTINGS_PERMISSION_NOTICE}

-
-
- Restart-required - Runtime config changes are persisted, then applied after backend restart. -
-
- -
- {#each SETTINGS_SECTIONS as section} - - {section.label} - {section.status === "editable" ? "Editable" : section.status === "read-only" ? "Read-only" : "Placeholder"} - - {/each} -
- -
-
-
-

editable

-

Runtime Connections

-
- typed API -
-

{SETTINGS_SECTIONS.find((section) => section.id === "runtime-connections")?.summary}

- - {#if runtimeLoading} -

Loading Runtime connections…

- {:else if runtimeError} -

Runtime connection settings unavailable: {runtimeError}

- {:else if runtimeSettings} -
- -
- - {#if showAddRuntimeForm} -
{ event.preventDefault(); void submitRemoteRuntime(); }}> -

Add remote Runtime

-

Endpoint is submitted to the Backend but not echoed back in settings responses.

- - - - -
- {/if} - - {#if mutationMessage} -

{mutationMessage}

- {/if} - {@render DiagnosticsList({ diagnostics: mutationDiagnostics })} - -
-

Runtimes

-
- - - - - - - - - - - - - - - - - - - {#if runtimeSettings.embedded.diagnostics.length > 0} - - - - {/if} - {#each runtimeSettings.remotes as remote (remote.runtime_id)} - - - - - - - - {#if remote.diagnostics.length > 0} - - - - {/if} - {#if tests[remote.runtime_id]} - {@const test = tests[remote.runtime_id]} - {@const available = capabilityOperations(test, "available")} - {@const unchecked = capabilityOperations(test, "unknown")} - - - - {/if} - {/each} - -
RuntimeSourceConnectionStatusActions
- {runtimeSettings.embedded.display_name} - {runtimeSettings.embedded.runtime_id} - - embedded - Workspace backend process - Local Backend runtime - {runtimeSettings.embedded.status} - {#if runtimeSettings.embedded.restart_required} - restart required - {/if} - Managed by backend
{@render DiagnosticsList({ diagnostics: runtimeSettings.embedded.diagnostics })}
- {remote.display_name} - {remote.runtime_id} - - remote - Configured Runtime endpoint - - Endpoint: {remote.endpoint_configured ? "configured" : "not configured"} - {#if remote.endpoint_configured}hidden{/if} - Token: {remote.token_ref_configured ? "configured" : "not configured"} - - {remote.status} - {#if remote.restart_required} - restart required - {/if} - -
- - -
-
{@render DiagnosticsList({ diagnostics: remote.diagnostics })}
-
- Test: {test.state} - {test.health_result} · {test.checked_at} -

{test.compatibility_basis}

- {#if available.length > 0} -

Verified areas: {available.join(', ')}

- {/if} - {#if unchecked.length > 0} -

Unchecked warning areas: {unchecked.join(', ')}

- {/if} - {@render DiagnosticsList({ diagnostics: test.diagnostics })} -
-
-
- {#if runtimeSettings.remotes.length === 0} -

No remote Runtime connections configured.

- {/if} -
- {/if} -
- -
-
-
-

editable

-

Profile Sources

-
- Backend scoped -
-

- Workspace profile settings are surfaced as source-qualified selectors and Decodal source summaries. The browser never receives raw host paths, runtime endpoints, tokens, resource handles, archive content, or archive digests. -

- - {#if profileLoading} -

Loading profile settings…

- {:else} -
-
{ event.preventDefault(); submitWorkspaceName(); }}> -

Workspace display name

- -

Workspace id: {workspaceMetadata?.workspace_id ?? workspaceId}

- -
- -
{ event.preventDefault(); submitProfileRegistry(); }}> -

Default launch profile

- - -
-
- -
-

Discovered profiles

- - - - - - {#each profileSettings?.profiles ?? [] as profile} - - - - - - - - {/each} - -
SelectorLabelSourceStatusAction
{profile.selector}{profile.label}{profile.source_kind}{profile.diagnostics.length === 0 ? "ok" : `${profile.diagnostics.length} diagnostics`} - {#if profile.profile_source_id} - - {:else} - builtin - {/if} -
-
- -
-
{ event.preventDefault(); submitNewProfileSource(); }}> -

Create project profile source

- - - - -
- - {#if selectedProfileSource} -
{ event.preventDefault(); submitProfileSourceUpdate(); }}> -

Edit {selectedProfileSource.profile.label}

-

Source id: {selectedProfileSource.source.profile_source_id}; display path: {selectedProfileSource.source.display_path}

- -
- - -
-
- {:else} -
-

No profile source selected

-

Open a project source from the discovered profile table to edit it.

-
- {/if} -
- - {#if profileMessage} -

{profileMessage}

- {/if} - {@render DiagnosticsList({ diagnostics: profileDiagnostics })} - {/if} -
- -
- {#each SETTINGS_SECTIONS.filter((section) => section.id !== "runtime-connections" && section.id !== "profile-sources") as section} -
-
-
-

{section.status}

-

{section.label}

-
- {#if section.status === "placeholder"} - not implemented - {:else} - read-only - {/if} -
-

{section.summary}

-
    - {#each section.bullets as bullet} -
  • {bullet}
  • - {/each} -
- - {#if section.id === "workspace-identity"} -
-
-
Workspace id
-
{workspace?.workspace_id ?? "loading"}
-
-
-
Display name
-
{workspace?.display_name ?? "loading"}
-
-
-
Record authority
-
.yoi tickets/objectives through the Backend projection
-
-
- {/if} -
- {/each} +
+
+
+

Overview

+

Settings areas

+
+
+ {#each SETTINGS_SECTIONS as section} + + {section.status} +

{section.label}

+

{section.summary}

+
+ {/each} +
+
-
-
-

Implementation patterns

-

How settings should appear

-
-
- {#each SETTINGS_PATTERNS as pattern} -
-

{pattern.title}

-

{pattern.body}

-
- {/each} -
-
- - {#if !workspace && !workspaceError} -

Loading workspace summary…

- {:else if workspaceError} -

Workspace summary unavailable: {workspaceError}

- {/if} -
- -{#snippet DiagnosticsList({ diagnostics }: { diagnostics: Diagnostic[] })} - {#if diagnostics.length > 0} -
    - {#each diagnostics as diagnostic} -
  • - {diagnosticLabel(diagnostic)} - {diagnostic.message} -
  • - {/each} -
- {/if} -{/snippet} +
+
+
+

Backend pattern

+

Authority boundaries

+
+
+
+
+

Backend-owned state

+

+ Workspace metadata and Decodal profile sources are edited through Backend APIs, not direct frontend filesystem access. +

+
+
+

Runtime inputs

+

+ Runtime execution receives explicit resources and WorkingDirectory handles; raw workspace paths stay out of Browser-facing payloads. +

+
+
+

Diagnostics first

+

+ Settings mutations return typed diagnostics so validation issues are visible without exposing internal paths or credentials. +

+
+
+
diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/backend/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/backend/+page.svelte new file mode 100644 index 00000000..750a58d9 --- /dev/null +++ b/web/workspace/src/routes/w/[workspaceId]/settings/backend/+page.svelte @@ -0,0 +1,12 @@ +
+
+
+

read-only

+

Backend config

+
+ planned +
+

+ Backend-owned config is exposed through typed settings APIs. Additional Backend config surfaces will be added as they become editable product settings. +

+
diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/profiles/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/profiles/+page.svelte new file mode 100644 index 00000000..2cd14807 --- /dev/null +++ b/web/workspace/src/routes/w/[workspaceId]/settings/profiles/+page.svelte @@ -0,0 +1,100 @@ + + + + Profiles · Yoi Workspace + + +
+
+
+

read-only

+

Profiles

+
+ editing pending +
+

+ Review the effective launch profiles and their source files. Editing is intentionally deferred until the Decodal profile source model is settled. +

+ + {#if loading} +

Loading profiles…

+ {:else if profileSettings} +
+
+

Available profiles

+

Profiles that can be selected when launching a Worker.

+
    + {#each profileSettings.profiles as profile (profile.profile_id)} +
  • + {profile.selector} + {profile.label} + {profile.source_kind}{profile.is_default ? ' · default' : ''} + {#if profile.description}

    {profile.description}

    {/if} + +
  • + {/each} +
+
+
+

Profile source files

+

Decodal source files that define or contribute to launch profiles.

+
    + {#each profileSettings.sources as source (source.profile_source_id)} +
  • + {source.display_path} + {source.kind} · {source.size_bytes} bytes + {source.editable ? 'editable later' : 'read-only'} · rev {source.revision} + +
  • + {/each} +
+
+
+ {/if} + + {#if message} +

{message}

+ {/if} + +
diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/runtime-connections/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/runtime-connections/+page.svelte new file mode 100644 index 00000000..494f2cbb --- /dev/null +++ b/web/workspace/src/routes/w/[workspaceId]/settings/runtime-connections/+page.svelte @@ -0,0 +1,371 @@ + + + + Runtime Connections · Yoi Workspace + + +
+
+
+

editable

+

Runtime Connections

+
+ typed API +
+

Manage remote Runtime connection records stored in the workspace-local Backend config. The embedded Runtime is built in and shown separately.

+ + {#if runtimeLoading} +

Loading Runtime connections…

+ {:else if runtimeError} +

Runtime connection settings unavailable: {runtimeError}

+ {:else if runtimeSettings} +
+ +
+ + {#if showAddRuntimeForm} +
{ event.preventDefault(); void submitRemoteRuntime(); }}> +

Add remote Runtime

+

Endpoint is submitted to the Backend but not echoed back in settings responses.

+ + + + +
+ {/if} + + {#if mutationMessage} +

{mutationMessage}

+ {/if} + + +
+

Runtimes

+
+ + + + + + + + + + + + + + + + + + + {#if runtimeSettings.embedded.diagnostics.length > 0} + + + + {/if} + {#each runtimeSettings.remotes as remote (remote.runtime_id)} + + + + + + + + {#if remote.diagnostics.length > 0} + + + + {/if} + {#if tests[remote.runtime_id]} + {@const test = tests[remote.runtime_id]} + {@const available = capabilityOperations(test, 'available')} + {@const unchecked = capabilityOperations(test, 'unknown')} + + + + {/if} + {/each} + +
RuntimeSourceConnectionStatusActions
+ {runtimeSettings.embedded.display_name} + {runtimeSettings.embedded.runtime_id} + + embedded + Workspace backend process + Local Backend runtime + {runtimeSettings.embedded.status} + {#if runtimeSettings.embedded.restart_required} + restart required + {/if} + Managed by backend
+ {remote.display_name} + {remote.runtime_id} + + remote + Configured Runtime endpoint + + Endpoint: {remote.endpoint_configured ? 'configured' : 'not configured'} + {#if remote.endpoint_configured}hidden{/if} + Token: {remote.token_ref_configured ? 'configured' : 'not configured'} + + {remote.status} + {#if remote.restart_required} + restart required + {/if} + +
+ + +
+
+
+ Test: {test.state} + {test.health_result} · {test.checked_at} +

{test.compatibility_basis}

+ {#if available.length > 0} +

Verified areas: {available.join(', ')}

+ {/if} + {#if unchecked.length > 0} +

Unchecked warning areas: {unchecked.join(', ')}

+ {/if} + +
+
+
+ {#if runtimeSettings.remotes.length === 0} +

No remote Runtime connections configured.

+ {/if} +
+ {/if} +
diff --git a/web/workspace/src/routes/w/[workspaceId]/settings/workspace/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/settings/workspace/+page.svelte new file mode 100644 index 00000000..5157ad6d --- /dev/null +++ b/web/workspace/src/routes/w/[workspaceId]/settings/workspace/+page.svelte @@ -0,0 +1,113 @@ + + + + Workspace settings · Yoi Workspace + + +
+
+
+

editable

+

Workspace Identity

+
+ Backend scoped +
+ + {#if loading} +

Loading workspace settings…

+ {:else} +
{ event.preventDefault(); void submitWorkspaceName(); }}> + +

Workspace id: {workspaceMetadata?.workspace_id ?? workspaceId}

+ +
+ +
+
+
Source
+
{workspaceMetadata?.source ?? 'unknown'}
+
+
+
Revision
+
{workspaceMetadata?.revision ?? 'unknown'}
+
+
+ {/if} + + {#if message} +

{message}

+ {/if} + +