Restore interactive server terminal drawer
This commit is contained in:
@@ -6,7 +6,6 @@ import type {
|
||||
ConfigDiffLineResponse,
|
||||
GamePluginResponse,
|
||||
JobResponse,
|
||||
RemoteAdapterDeclarationResponse,
|
||||
ServerInstanceResponse,
|
||||
ServerMemberResponse,
|
||||
ServerMetricsResponse,
|
||||
@@ -14,7 +13,8 @@ import type {
|
||||
ServerConfigDiffPreviewResponse,
|
||||
RunEndpointResponse
|
||||
} from "../api/types";
|
||||
import { ConfirmDialog, ManagementDialog, UsageMeter } from "../components/OperationControls";
|
||||
import { ConfirmDialog, UsageMeter } from "../components/OperationControls";
|
||||
import { ServerManagementTerminalDrawer } from "../components/ServerManagementTerminalDrawer";
|
||||
import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews";
|
||||
import type { PageComponentProps } from "../contracts/page";
|
||||
import { canStartServer, canStopServer, runtimeObservationFreshness, serverMetadataFormFromInstance, type ServerMetadataFormState } from "../contracts/serverManagement";
|
||||
@@ -51,7 +51,6 @@ export function ServerDetailPage(props: PageComponentProps) {
|
||||
const [confirm, setConfirm] = useState<null | { title: string; description: string; danger?: boolean; run: () => Promise<void> }>(null);
|
||||
const [confirmBusy, setConfirmBusy] = useState(false);
|
||||
const [terminalOpen, setTerminalOpen] = useState(false);
|
||||
const [terminalAdapters, setTerminalAdapters] = useState<LoadState<RemoteAdapterDeclarationResponse[]>>({ status: "loading" });
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
if (!serverId) {
|
||||
@@ -160,15 +159,6 @@ export function ServerDetailPage(props: PageComponentProps) {
|
||||
});
|
||||
}
|
||||
|
||||
function openTerminalEntry(current: ServerInstanceResponse) {
|
||||
setTerminalOpen(true);
|
||||
setTerminalAdapters({ status: "loading" });
|
||||
void platformApiClient
|
||||
.listRemoteAdapters(current.id)
|
||||
.then((response) => setTerminalAdapters({ status: "ready", data: response.items }))
|
||||
.catch((error) => setTerminalAdapters({ status: "error", reason: error instanceof Error ? error.message : "终端能力加载失败" }));
|
||||
}
|
||||
|
||||
if (!serverId) {
|
||||
return (
|
||||
<EmptyState title="未选择服务器" description="请从服务器列表进入详情页。" actionLabel="返回服务器列表" onAction={() => onNavigate("servers")} />
|
||||
@@ -226,7 +216,7 @@ export function ServerDetailPage(props: PageComponentProps) {
|
||||
className="icon-command"
|
||||
disabled={!canManageServers}
|
||||
title={canManageServers ? "打开终端" : "当前账号没有管理权限"}
|
||||
onClick={() => openTerminalEntry(instance.data)}
|
||||
onClick={() => setTerminalOpen(true)}
|
||||
>
|
||||
<Terminal size={15} />
|
||||
<span>打开终端</span>
|
||||
@@ -291,6 +281,7 @@ export function ServerDetailPage(props: PageComponentProps) {
|
||||
)}
|
||||
{section === "manage" && <ServerAdministratorsSection instance={instance.data} session={session} onChanged={(next) => setInstance({ status: "ready", data: next })} />}
|
||||
{section === "llm" && <LlmSection serverId={serverId} instance={instance.data} session={session} operations={operations} />}
|
||||
<ServerManagementTerminalDrawer open={terminalOpen} serverId={instance.data.id} serverName={instance.data.name} pluginId={instance.data.pluginId} canManage={canManageServers} onClose={() => setTerminalOpen(false)} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -313,67 +304,10 @@ export function ServerDetailPage(props: PageComponentProps) {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{instance.status === "ready" && (
|
||||
<RemoteTerminalEntryDialog
|
||||
open={terminalOpen}
|
||||
instance={instance.data}
|
||||
adapters={terminalAdapters}
|
||||
onRefresh={() => openTerminalEntry(instance.data)}
|
||||
onClose={() => setTerminalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
interface RemoteTerminalEntryDialogProps {
|
||||
open: boolean;
|
||||
instance: ServerInstanceResponse;
|
||||
adapters: LoadState<RemoteAdapterDeclarationResponse[]>;
|
||||
onRefresh: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function RemoteTerminalEntryDialog({ open, instance, adapters, onRefresh, onClose }: RemoteTerminalEntryDialogProps) {
|
||||
const terminalReadyAdapters = adapters.status === "ready" ? adapters.data.filter(isTerminalReadyAdapter) : [];
|
||||
return (
|
||||
<ManagementDialog
|
||||
open={open}
|
||||
title="打开终端"
|
||||
description={`服务器 ${instance.name} 的快捷入口已保留;这里仅使用 Run 声明的安全远程能力,不向浏览器暴露主机连接细节。`}
|
||||
wide
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="console-row-list">
|
||||
<div className="console-row"><span>服务器</span><strong>{instance.id}</strong></div>
|
||||
<div className="console-row"><span>Run 节点</span><strong>{instance.runEndpointId || "未绑定"}</strong></div>
|
||||
<div className="console-row"><span>终端会话</span><strong>{terminalReadyAdapters.length > 0 ? "等待 Run 会话实现" : "尚未声明交互会话"}</strong></div>
|
||||
</div>
|
||||
<p className="section-copy">直接命令面板已拆掉;真正的交互终端要由 Run 返回受控会话,再由平台打开。当前先展示已声明的适配能力,避免凭空创建连接或命令。</p>
|
||||
<div className="action-strip" style={{ marginTop: 12 }}>
|
||||
<button type="button" className="icon-command" onClick={onRefresh}><Sparkles size={14} /><span>刷新能力</span></button>
|
||||
</div>
|
||||
{adapters.status === "loading" && <LoadingState label="正在读取 Run 远程能力…" compact />}
|
||||
{adapters.status === "error" && <ErrorState title="终端能力不可用" reason={adapters.reason} diagnosticId={`remote-terminal:${instance.id}`} onRetry={onRefresh} compact />}
|
||||
{adapters.status === "ready" && adapters.data.length === 0 && <EmptyState title="暂无远程适配能力" description="Run 还没有为这个服务器上报可用的安全远程入口。" />}
|
||||
{adapters.status === "ready" && adapters.data.length > 0 && (
|
||||
<div className="console-row-list" style={{ marginTop: 12 }}>
|
||||
{adapters.data.map((adapter) => (
|
||||
<div key={adapter.key} className="console-row">
|
||||
<span>{adapter.key} · {adapter.kind}</span>
|
||||
<strong>{adapter.capabilities.join(" / ")}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ManagementDialog>
|
||||
);
|
||||
}
|
||||
|
||||
function isTerminalReadyAdapter(adapter: RemoteAdapterDeclarationResponse): boolean {
|
||||
return adapter.kind === "run-process" || adapter.capabilities.some((capability) => capability === "remote.run.process.start" || capability === "remote.run.process.stop" || capability === "remote.run.program.command");
|
||||
}
|
||||
|
||||
function serverDetailSectionEntries(plugin?: GamePluginResponse): Array<{ id: ServerDetailSection; label: string }> {
|
||||
const pluginPages = (plugin?.pages ?? []).map((page) => ({ id: `plugin:${page.key}` as ServerDetailSection, label: page.title }));
|
||||
return [...pluginPages, ...serverDetailSections];
|
||||
|
||||
Reference in New Issue
Block a user