import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query"; import { getGroups, getGroupMaps, getProjects, getSpaces, getTags, getAllProjectTagMaps, getGlobalTools, addGlobalTool, updateGlobalTool, removeGlobalTool, launchTool, type ProductGroup, type Project, type ProjectToolInput } from "../../lib/commands"; import { ProjectCard } from "./ProjectCard"; import { DeployModal } from "./DeployModal"; import { GroupResourceCanvas } from "./GroupResourceCanvas"; import { ManagePage } from "../manage/ManagePage"; import { ServerManagerPanel } from "./ServerManagerPanel"; import { ServerSoftwarePanel } from "./ServerSoftwareBlock"; import { ApiKeysPanel } from "./ApiKeysPanel"; import { CloudProductsPanel } from "./CloudProductsPanel"; import { BatchAddModal } from "./BatchAddModal"; import { MountRepoModal } from "./MountRepoModal"; import { ProjectToolsModal } from "./ProjectToolsModal"; import { StatusBadge } from "../ui/Badge"; import { openUrl } from "@tauri-apps/plugin-opener"; import { useUIStore } from "../../store/ui"; type DashView = "board" | "manage"; type BoardTab = "groups" | "standalone" | "servers" | "software" | "apikeys" | "cloud"; type GroupView = "default" | "resource"; export function Dashboard() { const [view, setView] = useState("board"); const [boardTab, setBoardTab] = useState("groups"); const [showBatch, setShowBatch] = useState(false); const [showMount, setShowMount] = useState(false); const qc = useQueryClient(); const { data: spaces = [] } = useQuery({ queryKey: ["spaces"], queryFn: getSpaces, }); const currentSpace = spaces.find((s) => s.is_current); const { data: projects = [] } = useQuery({ queryKey: ["projects", currentSpace?.id], queryFn: () => getProjects(undefined, currentSpace?.id), }); const { data: groups = [] } = useQuery({ queryKey: ["groups", currentSpace?.id], queryFn: () => getGroups(currentSpace?.id), }); const { data: maps = [] } = useQuery({ queryKey: ["maps"], queryFn: getGroupMaps }); const mappedIds = new Set(maps.map((m) => m.project_id)); const standalone = projects.filter((p) => !mappedIds.has(p.id)); return (
{/* 子导航栏 */}
{(["board", "manage"] as DashView[]).map((v) => ( ))}
{view === "board" && (
)}
{showBatch && { setShowBatch(false); qc.invalidateQueries({ queryKey: ["projects"] }); }} />} {showMount && currentSpace && ( setShowMount(false)} /> )} {/* 内容区 */} {view === "manage" ? (
) : (
{/* 看板子标签 */}
{/* ── 产品组标签 ── */} {boardTab === "groups" && ( <> {groups.map((g) => { const members = maps .filter((m) => m.group_id === g.id) .map((m) => ({ project: projects.find((p) => p.id === m.project_id), role: m.role })) .filter((x): x is { project: NonNullable; role: typeof x.role } => !!x.project); if (members.length === 0) return null; return ( ); })} {groups.every((g) => !maps.some((m) => m.group_id === g.id)) && (

🗂

还没有产品组,在「管理」中创建产品组并添加项目

)} )} {/* ── 独立项目标签 ── */} {boardTab === "standalone" && ( <> {standalone.length > 0 ? ( ) : (

📂

没有独立项目,所有项目已归属产品组

)} )} {/* ── 服务器管理标签 ── */} {boardTab === "servers" && } {/* ── 软件仓库标签 ── */} {boardTab === "software" && (
)} {/* ── API 管理标签 ── */} {boardTab === "apikeys" && } {/* ── 云产品标签 ── */} {boardTab === "cloud" && }
)}
); } /* ── GroupBoardSection ─────────────────────────────────────────────────────── */ interface GroupMember { project: Project; role: string | null | undefined; } interface GroupBoardSectionProps { group: ProductGroup; members: GroupMember[]; } function GroupBoardSection({ group: g, members }: GroupBoardSectionProps) { const [deployTarget, setDeployTarget] = useState(null); const [groupView, setGroupView] = useState("default"); const winMembers = members.filter(({ project: p }) => p.platform !== "wsl"); const wslMembers = members.filter(({ project: p }) => p.platform === "wsl"); return (
{/* 产品组标题栏 */}

{g.name}

{g.description && ( {g.description} )} {members.length} 个项目 {/* 视图切换 */}
{/* ── 资源视角画布 ── */} {groupView === "resource" && ( )} {/* ── 默认视图 ── */} {groupView === "default" && ( <> {/* 列头 */}
本地项目
部署信息
{/* 主体:每个项目一行,左=卡片 右=部署信息,平台分组作行标题 */}
{/* Windows 分组 */}

Windows

{winMembers.length} 个
{winMembers.length > 0 ? winMembers.map(({ project, role }) => ( )) :

暂无

} {/* WSL 分组 */}

WSL / Linux

{wslMembers.length} 个
{wslMembers.length > 0 ? wslMembers.map(({ project, role }) => ( )) :

暂无

}
)} {deployTarget && ( setDeployTarget(null)} /> )}
); } /* ── ProjectRow ────────────────────────────────────────────────────────────── */ function ProjectRow({ project: p, role, onDeploy, }: { project: Project; role?: string; onDeploy: (p: Project) => void; }) { return (
{/* 左:项目卡片 */}
{/* 右:部署信息 */}
{/* 状态 + 角色 + 时间 */}
{p.status && } {role && ( {role} )} {p.updated_at && ( {p.updated_at.slice(0, 10)} )}
{/* 当前专注 */} {p.recent_focus && (

📌 {p.recent_focus}

)} {/* 部署环境入口 */} {(p.prod_url || p.staging_url) && (
{p.prod_url && ( )} {p.staging_url && ( )}
)} {/* 服务器备注 */} {p.server_note && (

🖥 {p.server_note}

)} {/* 无信息提示 */} {!p.prod_url && !p.staging_url && !p.server_note && !p.recent_focus && (

未配置部署信息

)} {/* 部署配置按钮 */}
); } /* ── GlobalQuickLaunchBar ──────────────────────────────────────────────────── */ const GLOBAL_QUERY_KEY = ["globalTools"]; const globalToolCmds = { getTools: () => getGlobalTools(), addTool: (input: ProjectToolInput) => addGlobalTool(input), updateTool: (id: string, input: ProjectToolInput) => updateGlobalTool(id, input), removeTool: (id: string) => removeGlobalTool(id), }; function GlobalQuickLaunchBar() { const showToast = useUIStore((s) => s.showToast); const [showMgr, setShowMgr] = useState(false); const [mgrDefaultAdd, setMgrDefaultAdd] = useState(false); const { data: tools = [] } = useQuery({ queryKey: GLOBAL_QUERY_KEY, queryFn: () => getGlobalTools(), staleTime: 30000, }); const handleLaunch = async (exePath: string, args: string | null | undefined, name: string) => { try { await launchTool(exePath, args ?? undefined); showToast(`已启动 ${name} ✓`); } catch (e) { showToast(`❌ ${e}`); } }; return (
快启 {tools.length > 0 ? ( <> {tools.map((tool) => ( ))} ) : ( )} {showMgr && ( { setShowMgr(false); setMgrDefaultAdd(false); }} /> )}
); } /* ── StandaloneBoardSection ────────────────────────────────────────────────── */ function StandaloneBoardSection({ standalone, }: { standalone: Project[]; }) { const [deployTarget, setDeployTarget] = useState(null); const [selectedTagIds, setSelectedTagIds] = useState>(new Set()); const [search, setSearch] = useState(""); const { data: allTags = [] } = useQuery({ queryKey: ["tags"], queryFn: getTags }); const { data: tagMaps = [] } = useQuery({ queryKey: ["tagMaps"], queryFn: getAllProjectTagMaps }); const toggleTag = (tagId: string) => { setSelectedTagIds((prev) => { const next = new Set(prev); next.has(tagId) ? next.delete(tagId) : next.add(tagId); return next; }); }; const filtered = standalone.filter((p) => { if (search.trim() && !p.name.toLowerCase().includes(search.trim().toLowerCase())) return false; if (selectedTagIds.size === 0) return true; const projectTagIds = tagMaps .filter((m) => m.profile_id === p.profile_id) .map((m) => m.tag_id); return [...selectedTagIds].some((id) => projectTagIds.includes(id)); }); const winProjects = filtered.filter((p) => p.platform !== "wsl"); const wslProjects = filtered.filter((p) => p.platform === "wsl"); return (
{/* 搜索 + 标签筛选栏(始终显示) */}
{/* 搜索框 */}
setSearch(e.target.value)} placeholder="搜索项目名称…" className="flex-1 border border-gray-200 rounded-lg px-3 py-1.5 text-xs outline-none focus:ring-2 focus:ring-violet-200 bg-white" /> {filtered.length} / {standalone.length} 个 {(search || selectedTagIds.size > 0) && ( )}
{/* 标签筛选 chips */} {allTags.length > 0 && (
标签: {allTags.map((tag) => ( ))}
)} {allTags.length === 0 && (

暂无标签,在「管理-标签」里创建后可在此筛选

)}
{/* 列头 */}
本地项目
部署信息
{/* Windows */}

Windows

{winProjects.length} 个
{winProjects.length > 0 ? winProjects.map((p) => ( )) :

暂无

} {/* WSL */}

WSL / Linux

{wslProjects.length} 个
{wslProjects.length > 0 ? wslProjects.map((p) => ( )) :

暂无

}
{deployTarget && ( setDeployTarget(null)} /> )}
); }