mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 14:55:19 +00:00
ServerConnection.key
This commit is contained in:
@@ -147,10 +147,10 @@ function ServerKey(props: ParentProps) {
|
||||
export function AppInterface(props: {
|
||||
children?: JSX.Element
|
||||
defaultUrl: string
|
||||
servers?: Array<ServerConnection.Any>
|
||||
servers?: Array<ServerConnection.Ssh | ServerConnection.Sidecar>
|
||||
}) {
|
||||
return (
|
||||
<ServerProvider defaultUrl={props.defaultUrl} servers={props.servers} isSidecar>
|
||||
<ServerProvider defaultUrl={props.defaultUrl} servers={props.servers}>
|
||||
<ServerKey>
|
||||
<GlobalSDKProvider>
|
||||
<GlobalSyncProvider>
|
||||
|
||||
@@ -214,13 +214,14 @@ export function DialogSelectServer() {
|
||||
})
|
||||
}
|
||||
|
||||
const replaceServer = (original: ServerConnection.Http, next: string) => {
|
||||
const replaceServer = (original: ServerConnection.Http, next: ServerConnection.HttpBase) => {
|
||||
const active = server.url
|
||||
const nextActive = active === original.http.url ? next : active
|
||||
const newConn = server.add(next)
|
||||
if (!newConn) return
|
||||
|
||||
server.add(next)
|
||||
const nextActive = active === ServerConnection.key(original) ? ServerConnection.key(newConn) : active
|
||||
if (nextActive) server.setActive(nextActive)
|
||||
server.remove(original.http.url)
|
||||
server.remove(ServerConnection.key(original))
|
||||
}
|
||||
|
||||
const items = createMemo(() => {
|
||||
@@ -273,11 +274,11 @@ export function DialogSelectServer() {
|
||||
if (!persist && store.status[value.http.url]?.healthy === false) return
|
||||
dialog.close()
|
||||
if (persist) {
|
||||
server.add(value.http.url)
|
||||
server.add(value.http)
|
||||
navigate("/")
|
||||
return
|
||||
}
|
||||
server.setActive(value.http.url)
|
||||
server.setActive(ServerConnection.key(value))
|
||||
navigate("/")
|
||||
}
|
||||
|
||||
@@ -346,7 +347,7 @@ export function DialogSelectServer() {
|
||||
return
|
||||
}
|
||||
|
||||
replaceServer(original, normalized)
|
||||
replaceServer(original, { url: normalized })
|
||||
|
||||
resetEdit()
|
||||
}
|
||||
@@ -378,7 +379,7 @@ export function DialogSelectServer() {
|
||||
handleEdit(original, store.editServer.value)
|
||||
}
|
||||
|
||||
async function handleRemove(url: string) {
|
||||
async function handleRemove(url: ServerConnection.Key) {
|
||||
server.remove(url)
|
||||
if ((await platform.getDefaultServerUrl?.()) === url) {
|
||||
platform.setDefaultServerUrl?.(null)
|
||||
@@ -506,7 +507,7 @@ export function DialogSelectServer() {
|
||||
</Show>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => handleRemove(i.http.url)}
|
||||
onSelect={() => handleRemove(ServerConnection.key(i))}
|
||||
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ServerRow } from "@/components/server/server-row"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { normalizeServerUrl, type ServerConnection, useServer } from "@/context/server"
|
||||
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { checkServerHealth, type ServerHealth } from "@/utils/server-health"
|
||||
import { DialogSelectServer } from "./dialog-select-server"
|
||||
@@ -262,7 +262,7 @@ export function StatusPopover() {
|
||||
aria-disabled={isBlocked()}
|
||||
onClick={() => {
|
||||
if (isBlocked()) return
|
||||
server.setActive(s.http.url)
|
||||
server.setActive(ServerConnection.key(s))
|
||||
navigate("/")
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -43,7 +43,7 @@ export namespace ServerConnection {
|
||||
} & Base
|
||||
|
||||
export type Sidecar = {
|
||||
type: "local"
|
||||
type: "sidecar"
|
||||
http: HttpBase
|
||||
} & (
|
||||
| // Regular desktop server
|
||||
@@ -69,29 +69,32 @@ export namespace ServerConnection {
|
||||
// All these are desktop-only
|
||||
| (Sidecar | Ssh)
|
||||
|
||||
export const key = (conn: Any): string => {
|
||||
export const key = (conn: Any): Key => {
|
||||
switch (conn.type) {
|
||||
case "http":
|
||||
return conn.http.url
|
||||
case "local": {
|
||||
if (conn.variant === "wsl") return `wsl:${conn.distro}`
|
||||
return "local"
|
||||
return Key.make(conn.http.url)
|
||||
case "sidecar": {
|
||||
if (conn.variant === "wsl") return Key.make(`wsl:${conn.distro}`)
|
||||
return Key.make("sidecar")
|
||||
}
|
||||
case "ssh":
|
||||
return `ssh:${conn.host}`
|
||||
return Key.make(`ssh:${conn.host}`)
|
||||
}
|
||||
}
|
||||
|
||||
export type Key = string & { _brand: "Key" }
|
||||
export const Key = { make: (v: string) => v as Key }
|
||||
}
|
||||
|
||||
export const { use: useServer, provider: ServerProvider } = createSimpleContext({
|
||||
name: "Server",
|
||||
init: (props: { defaultUrl: string; isSidecar?: boolean; servers?: Array<ServerConnection.Any> }) => {
|
||||
init: (props: { defaultUrl: string; servers?: Array<ServerConnection.Ssh | ServerConnection.Sidecar> }) => {
|
||||
const platform = usePlatform()
|
||||
|
||||
const [store, setStore, _, ready] = persisted(
|
||||
Persist.global("server", ["server.v3"]),
|
||||
createStore({
|
||||
list: [] as string[],
|
||||
list: [] as (string | ServerConnection.HttpBase)[],
|
||||
projects: {} as Record<string, StoredProject[]>,
|
||||
lastProject: {} as Record<string, string>,
|
||||
}),
|
||||
@@ -99,61 +102,19 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
||||
|
||||
const allServers = (): Array<ServerConnection.Any> => [
|
||||
...(props.servers ?? []),
|
||||
...store.list.map((url) => ({
|
||||
...store.list.map((value) => ({
|
||||
type: "http" as const,
|
||||
http: { url },
|
||||
http: typeof value === "string" ? { url: value } : value,
|
||||
})),
|
||||
]
|
||||
|
||||
const [state, setState] = createStore({
|
||||
active: props.defaultUrl,
|
||||
active: ServerConnection.key({ type: "http", http: { url: props.defaultUrl } }),
|
||||
healthy: undefined as boolean | undefined,
|
||||
})
|
||||
|
||||
const healthy = () => state.healthy
|
||||
|
||||
// const defaultUrl = () => normalizeServerUrl(props.defaultUrl)
|
||||
|
||||
function reconcileStartup() {
|
||||
const fallback = props.defaultUrl
|
||||
if (!fallback) return
|
||||
// const previousSidecarUrl = normalizeServerUrl(store.currentSidecarUrl)
|
||||
// const list = previousSidecarUrl ? store.list.filter((url) => url !== previousSidecarUrl) : store.list
|
||||
// if (!props.isSidecar) {
|
||||
// batch(() => {
|
||||
// setStore("list", list)
|
||||
// if (store.currentSidecarUrl) setStore("currentSidecarUrl", "")
|
||||
setState("active", fallback)
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// const nextList = list.includes(fallback) ? list : [...list, fallback]
|
||||
// batch(() => {
|
||||
// setStore("list", nextList)
|
||||
// setStore("currentSidecarUrl", fallback)
|
||||
// setState("active", fallback)
|
||||
// })
|
||||
}
|
||||
|
||||
function updateServerList(url: string, remove = false) {
|
||||
if (remove) {
|
||||
const list = store.list.filter((x) => x !== url)
|
||||
const next = state.active === url ? (list[0] ?? props.defaultUrl ?? "") : state.active
|
||||
batch(() => {
|
||||
setStore("list", list)
|
||||
setState("active", next)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
batch(() => {
|
||||
if (!store.list.includes(url)) {
|
||||
setStore("list", store.list.length, url)
|
||||
}
|
||||
setState("active", url)
|
||||
})
|
||||
}
|
||||
|
||||
function startHealthPolling(conn: ServerConnection.Any) {
|
||||
let alive = true
|
||||
let busy = false
|
||||
@@ -179,30 +140,43 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
|
||||
}
|
||||
}
|
||||
|
||||
function setActive(input: string) {
|
||||
function setActive(input: ServerConnection.Key) {
|
||||
const url = normalizeServerUrl(input)
|
||||
if (!url) return
|
||||
setState("active", url)
|
||||
}
|
||||
|
||||
function add(input: string) {
|
||||
const url = normalizeServerUrl(input)
|
||||
function add(input: ServerConnection.HttpBase) {
|
||||
const url = normalizeServerUrl(input.url)
|
||||
if (!url) return
|
||||
updateServerList(url)
|
||||
return batch(() => {
|
||||
const http: ServerConnection.HttpBase = { ...input, url }
|
||||
if (!store.list.includes(url)) {
|
||||
setStore("list", store.list.length, http)
|
||||
}
|
||||
const conn: ServerConnection.Http = { type: "http", http }
|
||||
setState("active", ServerConnection.key(conn))
|
||||
return conn
|
||||
})
|
||||
}
|
||||
|
||||
function remove(input: string) {
|
||||
function remove(input: ServerConnection.Key) {
|
||||
const url = normalizeServerUrl(input)
|
||||
if (!url) return
|
||||
updateServerList(url, true)
|
||||
const list = store.list.filter((x) => x !== url)
|
||||
const next = state.active === url ? (list[0] ?? props.defaultUrl ?? "") : state.active
|
||||
batch(() => {
|
||||
setStore("list", list)
|
||||
setState(
|
||||
"active",
|
||||
ServerConnection.key({
|
||||
type: "http",
|
||||
http: typeof next === "string" ? { url: next } : next,
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!ready()) return
|
||||
if (state.active) return
|
||||
reconcileStartup()
|
||||
})
|
||||
|
||||
const isReady = createMemo(() => ready() && !!state.active)
|
||||
|
||||
const fetcher = platform.fetch ?? globalThis.fetch
|
||||
|
||||
@@ -447,9 +447,9 @@ render(() => {
|
||||
<AppBaseProviders>
|
||||
<ServerGate>
|
||||
{(data) => {
|
||||
const [servers] = createStore<Array<ServerConnection.Any>>([
|
||||
const [servers] = createStore<Array<ServerConnection.Sidecar>>([
|
||||
{
|
||||
type: "local",
|
||||
type: "sidecar",
|
||||
variant: "base",
|
||||
http: {
|
||||
url: data().url,
|
||||
|
||||
Reference in New Issue
Block a user