Add shared instance metadata context
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Component, For, Show } from "solid-js"
|
||||
import { Component, For, Show, createMemo } from "solid-js"
|
||||
import type { Instance } from "../types/instance"
|
||||
import { useInstanceMetadata } from "../lib/hooks/use-instance-metadata"
|
||||
import { useOptionalInstanceMetadataContext } from "../lib/contexts/instance-metadata-context"
|
||||
import InstanceServiceStatus from "./instance-service-status"
|
||||
|
||||
interface InstanceInfoProps {
|
||||
@@ -9,10 +9,19 @@ interface InstanceInfoProps {
|
||||
}
|
||||
|
||||
const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
const { isLoading: isLoadingMetadata } = useInstanceMetadata(() => props.instance)
|
||||
const metadataContext = useOptionalInstanceMetadataContext()
|
||||
const isLoadingMetadata = metadataContext?.isLoading ?? (() => false)
|
||||
const instanceAccessor = metadataContext?.instance ?? (() => props.instance)
|
||||
const metadataAccessor = metadataContext?.metadata ?? (() => props.instance.metadata)
|
||||
|
||||
const metadata = () => props.instance.metadata
|
||||
const binaryVersion = () => props.instance.binaryVersion || metadata()?.version
|
||||
const currentInstance = () => instanceAccessor()
|
||||
const metadata = () => metadataAccessor()
|
||||
const binaryVersion = () => currentInstance().binaryVersion || metadata()?.version
|
||||
const environmentVariables = () => currentInstance().environmentVariables
|
||||
const environmentEntries = createMemo(() => {
|
||||
const env = environmentVariables()
|
||||
return env ? Object.entries(env) : []
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="panel">
|
||||
@@ -23,7 +32,7 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
<div>
|
||||
<div class="text-xs font-medium text-muted uppercase tracking-wide mb-1">Folder</div>
|
||||
<div class="text-xs text-primary font-mono break-all px-2 py-1.5 rounded border bg-surface-secondary border-base">
|
||||
{props.instance.folder}
|
||||
{currentInstance().folder}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,24 +81,24 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.instance.binaryPath}>
|
||||
<Show when={currentInstance().binaryPath}>
|
||||
<div>
|
||||
<div class="text-xs font-medium text-muted uppercase tracking-wide mb-1">
|
||||
Binary Path
|
||||
</div>
|
||||
<div class="text-xs font-mono break-all px-2 py-1.5 rounded border bg-surface-secondary border-base text-primary">
|
||||
{props.instance.binaryPath}
|
||||
{currentInstance().binaryPath}
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.instance.environmentVariables && Object.keys(props.instance.environmentVariables).length > 0}>
|
||||
<Show when={environmentEntries().length > 0}>
|
||||
<div>
|
||||
<div class="text-xs font-medium text-muted uppercase tracking-wide mb-1.5">
|
||||
Environment Variables ({Object.keys(props.instance.environmentVariables!).length})
|
||||
Environment Variables ({environmentEntries().length})
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<For each={Object.entries(props.instance.environmentVariables!)}>
|
||||
<For each={environmentEntries()}>
|
||||
{([key, value]) => (
|
||||
<div class="flex items-center gap-2 px-2 py-1.5 rounded border bg-surface-secondary border-base">
|
||||
<span class="text-xs font-mono font-medium flex-1 text-primary" title={key}>
|
||||
@@ -105,11 +114,7 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<InstanceServiceStatus
|
||||
instanceId={props.instance.id}
|
||||
initialInstance={props.instance}
|
||||
class="space-y-3"
|
||||
/>
|
||||
<InstanceServiceStatus initialInstance={props.instance} class="space-y-3" />
|
||||
|
||||
<Show when={isLoadingMetadata()}>
|
||||
<div class="text-xs text-muted py-1">
|
||||
@@ -132,21 +137,19 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-secondary">Port:</span>
|
||||
<span class="text-primary font-mono">{props.instance.port}</span>
|
||||
<span class="text-primary font-mono">{currentInstance().port}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-secondary">PID:</span>
|
||||
<span class="text-primary font-mono">{props.instance.pid}</span>
|
||||
<span class="text-primary font-mono">{currentInstance().pid}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-secondary">Status:</span>
|
||||
<span
|
||||
class={`status-badge ${props.instance.status}`}
|
||||
>
|
||||
<span class={`status-badge ${currentInstance().status}`}>
|
||||
<div
|
||||
class={`status-dot ${props.instance.status === "ready" ? "ready" : props.instance.status === "starting" ? "starting" : props.instance.status === "error" ? "error" : "stopped"} ${props.instance.status === "ready" || props.instance.status === "starting" ? "animate-pulse" : ""}`}
|
||||
class={`status-dot ${currentInstance().status === "ready" ? "ready" : currentInstance().status === "starting" ? "starting" : currentInstance().status === "error" ? "error" : "stopped"} ${currentInstance().status === "ready" || currentInstance().status === "starting" ? "animate-pulse" : ""}`}
|
||||
/>
|
||||
{props.instance.status}
|
||||
{currentInstance().status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { For, Show, createMemo, createSignal, type Component } from "solid-js"
|
||||
import Switch from "@suid/material/Switch"
|
||||
import type { Instance, RawMcpStatus } from "../types/instance"
|
||||
import { instances, updateInstance } from "../stores/instances"
|
||||
import { useInstanceMetadata } from "../lib/hooks/use-instance-metadata"
|
||||
import { useOptionalInstanceMetadataContext } from "../lib/contexts/instance-metadata-context"
|
||||
import { getLogger } from "../lib/logger"
|
||||
|
||||
const log = getLogger("session")
|
||||
@@ -10,11 +9,10 @@ const log = getLogger("session")
|
||||
type ServiceSection = "lsp" | "mcp"
|
||||
|
||||
interface InstanceServiceStatusProps {
|
||||
instanceId: string
|
||||
initialInstance?: Instance
|
||||
sections?: ServiceSection[]
|
||||
showSectionHeadings?: boolean
|
||||
class?: string
|
||||
initialInstance?: Instance
|
||||
}
|
||||
|
||||
type ParsedMcpStatus = {
|
||||
@@ -44,17 +42,30 @@ function parseMcpStatus(status?: RawMcpStatus): ParsedMcpStatus[] {
|
||||
}
|
||||
|
||||
const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) => {
|
||||
const instance = createMemo(() => instances().get(props.instanceId) ?? props.initialInstance)
|
||||
const metadataContext = useOptionalInstanceMetadataContext()
|
||||
const instance = metadataContext?.instance ?? (() => {
|
||||
if (props.initialInstance) {
|
||||
return props.initialInstance
|
||||
}
|
||||
throw new Error("InstanceServiceStatus requires InstanceMetadataProvider or initialInstance prop")
|
||||
})
|
||||
const isLoading = metadataContext?.isLoading ?? (() => false)
|
||||
const refreshMetadata = metadataContext?.refreshMetadata ?? (async () => Promise.resolve())
|
||||
const sections = createMemo<ServiceSection[]>(() => props.sections ?? ["lsp", "mcp"])
|
||||
const includeLsp = createMemo(() => sections().includes("lsp"))
|
||||
const includeMcp = createMemo(() => sections().includes("mcp"))
|
||||
const showHeadings = () => props.showSectionHeadings !== false
|
||||
const { isLoading } = useInstanceMetadata(instance)
|
||||
|
||||
const metadata = createMemo(() => instance()?.metadata)
|
||||
const metadata = createMemo(() => instance().metadata)
|
||||
const hasLspMetadata = () => metadata()?.lspStatus !== undefined
|
||||
const hasMcpMetadata = () => metadata()?.mcpStatus !== undefined
|
||||
const lspServers = createMemo(() => metadata()?.lspStatus ?? [])
|
||||
const mcpServers = createMemo(() => parseMcpStatus(metadata()?.mcpStatus))
|
||||
|
||||
const isLspLoading = () => isLoading() || !hasLspMetadata()
|
||||
const isMcpLoading = () => isLoading() || !hasMcpMetadata()
|
||||
|
||||
|
||||
const [pendingMcpActions, setPendingMcpActions] = createSignal<Record<string, "connect" | "disconnect">>({})
|
||||
|
||||
const setPendingMcpAction = (name: string, action?: "connect" | "disconnect") => {
|
||||
@@ -66,26 +77,8 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
|
||||
})
|
||||
}
|
||||
|
||||
const refreshMcpStatus = async () => {
|
||||
const client = instance()?.client
|
||||
if (!client?.mcp?.status) return
|
||||
try {
|
||||
const result = await client.mcp.status()
|
||||
const status = result.data as RawMcpStatus | undefined
|
||||
if (!status) return
|
||||
updateInstance(props.instanceId, {
|
||||
metadata: {
|
||||
...(instance()?.metadata ?? {}),
|
||||
mcpStatus: status,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
log.error("Failed to refresh MCP status", error)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleMcpServer = async (serverName: string, shouldEnable: boolean) => {
|
||||
const client = instance()?.client
|
||||
const client = instance().client
|
||||
if (!client?.mcp) return
|
||||
const action: "connect" | "disconnect" = shouldEnable ? "connect" : "disconnect"
|
||||
setPendingMcpAction(serverName, action)
|
||||
@@ -95,7 +88,7 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
|
||||
} else {
|
||||
await client.mcp.disconnect({ path: { name: serverName } })
|
||||
}
|
||||
await refreshMcpStatus()
|
||||
await refreshMetadata()
|
||||
} catch (error) {
|
||||
log.error("Failed to toggle MCP server", { serverName, action, error })
|
||||
} finally {
|
||||
@@ -117,8 +110,8 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
when={!isLoading() && lspServers().length > 0}
|
||||
fallback={renderEmptyState(isLoading() ? "Loading server status..." : "No LSP servers detected.")}
|
||||
when={!isLspLoading() && lspServers().length > 0}
|
||||
fallback={renderEmptyState(isLspLoading() ? "Loading LSP servers..." : "No LSP servers detected.")}
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<For each={lspServers()}>
|
||||
@@ -152,8 +145,8 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
when={!isLoading() && mcpServers().length > 0}
|
||||
fallback={renderEmptyState(isLoading() ? "Loading server status..." : "No MCP servers detected.")}
|
||||
when={!isMcpLoading() && mcpServers().length > 0}
|
||||
fallback={renderEmptyState(isMcpLoading() ? "Loading MCP servers..." : "No MCP servers detected.")}
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<For each={mcpServers()}>
|
||||
@@ -161,7 +154,7 @@ const InstanceServiceStatus: Component<InstanceServiceStatusProps> = (props) =>
|
||||
const pendingAction = () => pendingMcpActions()[server.name]
|
||||
const isPending = () => Boolean(pendingAction())
|
||||
const isRunning = () => server.status === "running"
|
||||
const switchDisabled = () => isPending() || !instance()?.client
|
||||
const switchDisabled = () => isPending() || !instance().client
|
||||
const statusDotClass = () => {
|
||||
if (isPending()) return "status-dot animate-pulse"
|
||||
if (server.status === "running") return "status-dot ready animate-pulse"
|
||||
|
||||
@@ -132,7 +132,6 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
||||
|
||||
const messageStore = createMemo(() => messageStoreBus.getOrCreate(props.instance.id))
|
||||
|
||||
|
||||
const desktopQuery = useMediaQuery("(min-width: 1280px)")
|
||||
|
||||
const tabletQuery = useMediaQuery("(min-width: 768px)")
|
||||
@@ -860,7 +859,6 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
||||
label: "LSP Servers",
|
||||
render: () => (
|
||||
<InstanceServiceStatus
|
||||
instanceId={props.instance.id}
|
||||
initialInstance={props.instance}
|
||||
sections={["lsp"]}
|
||||
showSectionHeadings={false}
|
||||
@@ -873,7 +871,6 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
||||
label: "MCP Servers",
|
||||
render: () => (
|
||||
<InstanceServiceStatus
|
||||
instanceId={props.instance.id}
|
||||
initialInstance={props.instance}
|
||||
sections={["mcp"]}
|
||||
showSectionHeadings={false}
|
||||
|
||||
Reference in New Issue
Block a user