Skip to content

Commit 8783853

Browse files
committed
feat(app): move local server controls into server panel
1 parent d08f311 commit 8783853

15 files changed

Lines changed: 477 additions & 505 deletions

File tree

packages/app/src/components/dialog-select-server.tsx

Lines changed: 339 additions & 48 deletions
Large diffs are not rendered by default.

packages/app/src/components/server/server-row.tsx

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@ interface ServerRowProps extends ParentProps {
2323
dimmed?: boolean
2424
badge?: JSXElement
2525
showCredentials?: boolean
26+
name?: string
27+
details?: string[]
28+
credentials?: {
29+
username?: string
30+
password?: string
31+
}
2632
}
2733

2834
export function ServerRow(props: ServerRowProps) {
2935
const language = useLanguage()
3036
const [truncated, setTruncated] = createSignal(false)
3137
let nameRef: HTMLSpanElement | undefined
3238
let versionRef: HTMLSpanElement | undefined
33-
const name = createMemo(() => serverName(props.conn))
39+
const name = createMemo(() => props.name ?? serverName(props.conn))
3440

3541
const check = () => {
3642
const nameTruncated = nameRef ? nameRef.scrollWidth > nameRef.clientWidth : false
@@ -53,22 +59,31 @@ export function ServerRow(props: ServerRowProps) {
5359

5460
const tooltipValue = () => (
5561
<span class="flex items-center gap-2">
56-
<span>{serverName(props.conn, true)}</span>
62+
<span>{name()}</span>
5763
<Show when={props.status?.version}>
5864
<span class="text-text-invert-weak">v{props.status?.version}</span>
5965
</Show>
6066
</span>
6167
)
6268

6369
const badge = children(() => props.badge)
70+
const details = createMemo(() => props.details?.filter(Boolean) ?? [])
71+
const credentials = createMemo(() => {
72+
if (props.credentials) return props.credentials
73+
if (props.conn.type !== "http") return
74+
return {
75+
username: props.conn.http.username,
76+
password: props.conn.http.password,
77+
}
78+
})
6479

6580
return (
6681
<Tooltip
6782
class="flex-1 min-w-0"
6883
value={tooltipValue()}
6984
contentStyle={{ "max-width": "none", "white-space": "nowrap" }}
7085
placement="top-start"
71-
inactive={!truncated() && !props.conn.displayName}
86+
inactive={!truncated() && !props.conn.displayName && !props.name}
7287
>
7388
<div class={props.class} classList={{ "opacity-50": props.dimmed }}>
7489
<div class="flex flex-col items-start min-w-0 w-full">
@@ -92,19 +107,21 @@ export function ServerRow(props: ServerRowProps) {
92107
{(badge) => badge()}
93108
</Show>
94109
</div>
95-
<Show when={props.showCredentials && props.conn.type === "http" && props.conn}>
96-
{(conn) => (
97-
<div class="flex flex-row gap-3">
98-
<span>
99-
{conn().http.username ? (
100-
<span class="text-text-weak">{conn().http.username}</span>
101-
) : (
102-
<span class="text-text-weaker">{language.t("server.row.noUsername")}</span>
103-
)}
104-
</span>
105-
{conn().http.password && <span class="text-text-weak">••••••••</span>}
106-
</div>
107-
)}
110+
<Show when={props.showCredentials && (details().length || credentials())}>
111+
<div class="flex flex-row gap-3">
112+
<Show when={details().length}>
113+
<span class="text-text-weak">{details().join(" · ")}</span>
114+
</Show>
115+
<Show when={credentials()?.username}>
116+
<span class="text-text-weak">{credentials()?.username}</span>
117+
</Show>
118+
<Show when={!details().length && credentials() && !credentials()?.username}>
119+
<span class="text-text-weaker">{language.t("server.row.noUsername")}</span>
120+
</Show>
121+
<Show when={credentials()?.password}>
122+
<span class="text-text-weak">••••••••</span>
123+
</Show>
124+
</div>
108125
</Show>
109126
</div>
110127
{props.children}

packages/app/src/components/settings-general.tsx

Lines changed: 2 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Show, createEffect, createMemo, createResource, createSignal, onMount, type JSX } from "solid-js"
1+
import { Component, Show, createMemo, createResource, onMount, type JSX } from "solid-js"
22
import { createStore } from "solid-js/store"
33
import { Button } from "@opencode-ai/ui/button"
44
import { Icon } from "@opencode-ai/ui/icon"
@@ -91,11 +91,6 @@ export const SettingsGeneral: Component = () => {
9191

9292
const [store, setStore] = createStore({
9393
checking: false,
94-
inboundEnabled: false,
95-
inboundUsername: "",
96-
inboundPassword: "",
97-
inboundPort: "",
98-
inboundSaving: false,
9994
})
10095

10196
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
@@ -124,102 +119,6 @@ export const SettingsGeneral: Component = () => {
124119

125120
permission.disableAutoAccept(params.id, value)
126121
}
127-
const desktop = createMemo(() => platform.platform === "desktop")
128-
const [inboundConfig] = createResource(() => platform.getInboundServerConfig?.())
129-
const [inboundRuntimeConfig] = createResource(() => platform.getInboundRuntimeServerConfig?.())
130-
const [inboundHydrated, setInboundHydrated] = createSignal(false)
131-
const [savedInboundConfig, setSavedInboundConfig] = createSignal<{
132-
enabled: boolean
133-
username: string
134-
password: string
135-
port: number | null
136-
}>()
137-
138-
createEffect(() => {
139-
if (inboundHydrated()) return
140-
141-
const config = inboundConfig()
142-
if (!config) return
143-
setStore("inboundEnabled", config.enabled)
144-
setStore("inboundUsername", config.username)
145-
setStore("inboundPassword", config.password)
146-
setStore("inboundPort", config.port === null ? "" : String(config.port))
147-
setSavedInboundConfig(config)
148-
setInboundHydrated(true)
149-
})
150-
const inboundRuntime = createMemo(() => platform.inboundRuntimeServerConfig?.() ?? inboundRuntimeConfig())
151-
const inboundUsernamePlaceholder = createMemo(() => inboundRuntime()?.username ?? "opencode")
152-
const inboundPasswordPlaceholder = createMemo(
153-
() => inboundRuntime()?.password ?? language.t("settings.general.row.inboundPassword.placeholder"),
154-
)
155-
const inboundPortPlaceholder = createMemo(() => {
156-
const port = inboundRuntime()?.port
157-
return typeof port === "number" ? String(port) : ""
158-
})
159-
160-
const parseInboundPort = (value: string) => {
161-
const trimmed = value.trim()
162-
if (!trimmed) return null
163-
164-
const parsed = Number.parseInt(trimmed, 10)
165-
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) return undefined
166-
167-
return parsed
168-
}
169-
170-
const saveInboundConfig = (config: { enabled: boolean; username: string; password: string; port: string }) => {
171-
if (!platform.setInboundServerConfig) return
172-
173-
const current = savedInboundConfig() ?? inboundConfig.latest
174-
const port = parseInboundPort(config.port)
175-
if (config.enabled && port === undefined) {
176-
showToast({
177-
title: language.t("common.requestFailed"),
178-
description: language.t("settings.general.row.inboundPort.invalid"),
179-
})
180-
return
181-
}
182-
183-
const next = {
184-
enabled: config.enabled,
185-
username: config.username.trim(),
186-
password: config.password,
187-
port: port ?? null,
188-
}
189-
190-
const changed =
191-
!current ||
192-
current.enabled !== next.enabled ||
193-
current.username !== next.username ||
194-
current.password !== next.password ||
195-
current.port !== next.port
196-
197-
if (!changed) return
198-
199-
setStore("inboundSaving", true)
200-
void platform
201-
.setInboundServerConfig(next)
202-
.then(() => {
203-
setSavedInboundConfig(next)
204-
})
205-
.finally(() => setStore("inboundSaving", false))
206-
}
207-
208-
const saveInboundOnBlur = () => {
209-
saveInboundConfig({
210-
enabled: store.inboundEnabled,
211-
username: store.inboundUsername,
212-
password: store.inboundPassword,
213-
port: store.inboundPort,
214-
})
215-
}
216-
217-
const saveInboundOnEnter = (event: KeyboardEvent) => {
218-
if (event.key !== "Enter") return
219-
event.preventDefault()
220-
saveInboundOnBlur()
221-
}
222-
223122
const check = () => {
224123
if (!platform.checkUpdate) return
225124
setStore("checking", true)
@@ -827,106 +726,6 @@ export const SettingsGeneral: Component = () => {
827726
</div>
828727
)
829728

830-
const DesktopNetworkSection = () => (
831-
<Show when={desktop() && platform.getInboundServerConfig && platform.setInboundServerConfig}>
832-
<div class="flex flex-col gap-1">
833-
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.network")}</h3>
834-
<SettingsList>
835-
<SettingsRow
836-
title={language.t("settings.general.row.inboundAccess.title")}
837-
description={language.t("settings.general.row.inboundAccess.description")}
838-
>
839-
<div data-action="settings-inbound-access">
840-
<Switch
841-
checked={store.inboundEnabled}
842-
disabled={inboundConfig.state === "pending" || store.inboundSaving}
843-
onChange={(checked) => {
844-
setStore("inboundEnabled", checked)
845-
saveInboundConfig({
846-
enabled: checked,
847-
username: store.inboundUsername,
848-
password: store.inboundPassword,
849-
port: store.inboundPort,
850-
})
851-
}}
852-
/>
853-
</div>
854-
</SettingsRow>
855-
<Show when={store.inboundEnabled}>
856-
<SettingsRow
857-
title={language.t("settings.general.row.inboundUsername.title")}
858-
description={language.t("settings.general.row.inboundUsername.description")}
859-
>
860-
<div class="w-full sm:w-[220px]">
861-
<TextField
862-
data-action="settings-inbound-username"
863-
label={language.t("settings.general.row.inboundUsername.title")}
864-
hideLabel
865-
type="text"
866-
value={store.inboundUsername || ""}
867-
onChange={(value) => setStore("inboundUsername", value)}
868-
onBlur={saveInboundOnBlur}
869-
onKeyDown={saveInboundOnEnter}
870-
placeholder={inboundUsernamePlaceholder()}
871-
spellcheck={false}
872-
autocorrect="off"
873-
autocomplete="off"
874-
autocapitalize="off"
875-
class="text-12-regular"
876-
/>
877-
</div>
878-
</SettingsRow>
879-
<SettingsRow
880-
title={language.t("settings.general.row.inboundPassword.title")}
881-
description={language.t("settings.general.row.inboundPassword.description")}
882-
>
883-
<div class="w-full sm:w-[220px]">
884-
<TextField
885-
data-action="settings-inbound-password"
886-
label={language.t("settings.general.row.inboundPassword.title")}
887-
hideLabel
888-
type="text"
889-
value={store.inboundPassword || ""}
890-
onChange={(value) => setStore("inboundPassword", value)}
891-
onBlur={saveInboundOnBlur}
892-
onKeyDown={saveInboundOnEnter}
893-
placeholder={inboundPasswordPlaceholder()}
894-
class="text-12-regular"
895-
/>
896-
</div>
897-
</SettingsRow>
898-
<SettingsRow
899-
title={language.t("settings.general.row.inboundPort.title")}
900-
description={language.t("settings.general.row.inboundPort.description")}
901-
>
902-
<div class="flex gap-2 items-center">
903-
<div class="w-full sm:w-[220px]">
904-
<TextField
905-
data-action="settings-inbound-port"
906-
label={language.t("settings.general.row.inboundPort.title")}
907-
hideLabel
908-
type="text"
909-
inputMode="numeric"
910-
value={store.inboundPort || ""}
911-
onChange={(value) => setStore("inboundPort", value)}
912-
onBlur={saveInboundOnBlur}
913-
onKeyDown={saveInboundOnEnter}
914-
placeholder={inboundPortPlaceholder()}
915-
spellcheck={false}
916-
autocorrect="off"
917-
autocomplete="off"
918-
autocapitalize="off"
919-
class="text-12-regular"
920-
/>
921-
</div>
922-
</div>
923-
</SettingsRow>
924-
</Show>
925-
</SettingsList>
926-
</div>
927-
</Show>
928-
)
929-
930729
return (
931730
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
932731
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
@@ -943,7 +742,6 @@ export const SettingsGeneral: Component = () => {
943742
<NotificationsSection />
944743

945744
<SoundsSection />
946-
<DesktopNetworkSection />
947745

948746
<UpdatesSection />
949747

@@ -973,7 +771,7 @@ export const SettingsGeneral: Component = () => {
973771
</div>
974772
</Show>
975773

976-
<Show when={desktop() && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}>
774+
<Show when={platform.platform === "desktop" && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}>
977775
<AdvancedSection />
978776
</Show>
979777
</div>

0 commit comments

Comments
 (0)