同步完整源码至远程:扁平化重构现状 + 未入库源文件
- 补入库:server/internal/db/dump.go、redis 分页/扫描预算测试、opsxdb-mcp main_test、 packages/opsxdb-mcp package-lock、docs 审计与对比文档等 37 个新文件 - 含结构扁平化进行中的 80 处修改与 155 处旧结构删除(server/ src/ src-tauri/ 为当前有效源) - 构建产物(bin/target/dist)与签名私钥仍按规则排除
This commit is contained in:
1 parent
5c0be49217
commit
2c0bfafd8d
269 files changed
+10372
-16435
No files matched your search
+32
-84
@@ -24,11 +24,9 @@ const CLIENT_TABS: Array<{ id: ClientTab; label: string }> = [
|
||||
{ id: "claude-desktop", label: "Claude Desktop" },
|
||||
];
|
||||
|
||||
function buildClientSnippet(tab: ClientTab, goBin: string, readOnly: boolean): { path: string; json: string } {
|
||||
function buildClientSnippet(tab: ClientTab, goBin: string): { path: string; json: string } {
|
||||
const env: Record<string, string> = {};
|
||||
if (goBin) env.OPSXDB_MCP_BIN = goBin;
|
||||
// 权限以 ~/.opsxdb/mcp.json 为准;客户端 env 不放宽。
|
||||
void readOnly;
|
||||
|
||||
const npxServer = {
|
||||
command: "npx",
|
||||
@@ -88,6 +86,7 @@ function buildClientSnippet(tab: ClientTab, goBin: string, readOnly: boolean): {
|
||||
|
||||
function StatusBadge({ status }: { status: string }) {
|
||||
const map: Record<string, { label: string; color: string; bg: string }> = {
|
||||
checking: { label: "检测中", color: "var(--ops-muted)", bg: "var(--ops-surface-2)" },
|
||||
ready: { label: "已就绪", color: "var(--ops-accent)", bg: "color-mix(in srgb, var(--ops-accent) 14%, transparent)" },
|
||||
partial: { label: "部分就绪", color: "var(--ops-warning, #d97706)", bg: "color-mix(in srgb, #d97706 14%, transparent)" },
|
||||
not_installed: { label: "未安装", color: "var(--ops-warning, #d97706)", bg: "color-mix(in srgb, #d97706 14%, transparent)" },
|
||||
@@ -103,51 +102,16 @@ function StatusBadge({ status }: { status: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function Row({ label, children, hint }: { label: string; children: React.ReactNode; hint?: string }) {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="text-[12.5px]" style={{ color: "var(--ops-text)" }}>{label}</span>
|
||||
{children}
|
||||
</div>
|
||||
{hint && <div className="text-[11px] leading-relaxed" style={{ color: "var(--ops-muted)" }}>{hint}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Toggle({ checked, onChange, disabled }: { checked: boolean; onChange: (v: boolean) => void; disabled?: boolean }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
disabled={disabled}
|
||||
className="rounded-full border transition disabled:opacity-50"
|
||||
style={{
|
||||
height: 18,
|
||||
width: 36,
|
||||
borderColor: "var(--ops-border)",
|
||||
background: checked ? "var(--ops-accent)" : "var(--ops-surface-2)",
|
||||
}}
|
||||
onClick={() => onChange(!checked)}
|
||||
>
|
||||
<span
|
||||
className="block rounded-full bg-white transition-all"
|
||||
style={{ width: 12, height: 12, marginLeft: checked ? 20 : 3, boxShadow: "0 1px 2px rgba(0,0,0,.3)" }}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
/** 设置 → MCP:安装检测、只读/危险 SQL、白名单、多客户端配置片段 */
|
||||
/** 设置 → MCP:权限状态、授权连接、客户端接入 */
|
||||
export function McpSettings() {
|
||||
const notify = useWorkspaceStore((s) => s.notify);
|
||||
const connections = useWorkspaceStore((s) => s.connections);
|
||||
|
||||
const [install, setInstall] = useState<McpInstallInfo | null>(null);
|
||||
const [policy, setPolicy] = useState<McpPolicy | null>(null);
|
||||
const [policyPath, setPolicyPath] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [installLoading, setInstallLoading] = useState(true);
|
||||
const [installError, setInstallError] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [clientTab, setClientTab] = useState<ClientTab>("cursor");
|
||||
@@ -160,12 +124,10 @@ export function McpSettings() {
|
||||
let alive = true;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
Promise.all([getMcpInstall(), getMcpPolicy()])
|
||||
.then(([inst, pol]) => {
|
||||
getMcpPolicy()
|
||||
.then((pol) => {
|
||||
if (!alive) return;
|
||||
setInstall(inst);
|
||||
setPolicy(pol.policy);
|
||||
setPolicyPath(pol.path || "");
|
||||
})
|
||||
.catch((e) => {
|
||||
if (alive) setError(e instanceof Error ? e.message : String(e));
|
||||
@@ -173,6 +135,12 @@ export function McpSettings() {
|
||||
.finally(() => {
|
||||
if (alive) setLoading(false);
|
||||
});
|
||||
setInstallLoading(true);
|
||||
setInstallError("");
|
||||
getMcpInstall()
|
||||
.then((inst) => { if (alive) setInstall(inst); })
|
||||
.catch((e) => { if (alive) setInstallError(e instanceof Error ? e.message : String(e)); })
|
||||
.finally(() => { if (alive) setInstallLoading(false); });
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
@@ -183,8 +151,8 @@ export function McpSettings() {
|
||||
const allowed = new Set(policy?.allowed_connection_ids ?? []);
|
||||
|
||||
const snippet = useMemo(
|
||||
() => buildClientSnippet(clientTab, install?.goBinaryPath || "", readOnly),
|
||||
[clientTab, install?.goBinaryPath, readOnly],
|
||||
() => buildClientSnippet(clientTab, install?.goBinaryPath || ""),
|
||||
[clientTab, install?.goBinaryPath],
|
||||
);
|
||||
|
||||
const persist = async (next: McpPolicy) => {
|
||||
@@ -192,7 +160,6 @@ export function McpSettings() {
|
||||
try {
|
||||
const res = await saveMcpPolicy(next);
|
||||
setPolicy(res.policy);
|
||||
setPolicyPath(res.path || policyPath);
|
||||
notify({ kind: "success", title: "MCP", message: "策略已保存", mode: "toast", durationMs: 1400 });
|
||||
} catch (e) {
|
||||
notify({ kind: "error", title: "MCP", message: e instanceof Error ? e.message : String(e), mode: "modal" });
|
||||
@@ -220,7 +187,7 @@ export function McpSettings() {
|
||||
void persist({ ...policy, allowed_connection_ids: Array.from(set) });
|
||||
};
|
||||
|
||||
if (loading) return <p role="status" className="text-xs" style={{ color: "var(--ops-muted)" }}>正在检测 MCP 环境…</p>;
|
||||
if (loading) return <p role="status" className="text-xs" style={{ color: "var(--ops-muted)" }}>正在读取 MCP 设置…</p>;
|
||||
if (error) {
|
||||
return (
|
||||
<div role="alert" className="space-y-2 text-xs">
|
||||
@@ -237,7 +204,7 @@ export function McpSettings() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3 className="text-[13px] font-semibold">MCP Server</h3>
|
||||
<StatusBadge status={install?.status || "not_installed"} />
|
||||
<StatusBadge status={installLoading && !install ? "checking" : install?.status || "not_installed"} />
|
||||
<button
|
||||
type="button"
|
||||
className="ops-btn-ghost ml-auto inline-flex h-6 items-center gap-1 px-2 text-[11px]"
|
||||
@@ -247,14 +214,15 @@ export function McpSettings() {
|
||||
MCP 指南 <ExternalLink className="h-3 w-3" strokeWidth={1.5} />
|
||||
</button>
|
||||
</div>
|
||||
{installError && <p role="alert" className="text-[11px]" style={{ color: "var(--ops-warning, #d97706)" }}>安装检测失败:{installError}。连接白名单与权限设置仍可使用。</p>}
|
||||
|
||||
{/* Version grid */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
["当前版本", install?.currentVersion || (install?.goBinaryFound ? "native" : "未检测到全局安装")],
|
||||
["当前版本", install?.currentVersion || (installLoading ? "检测中…" : install?.goBinaryFound ? "native" : "未检测到全局安装")],
|
||||
["最新版本", install?.latestVersion ? `v${install.latestVersion}` : "—"],
|
||||
["Node.js", install?.nodeVersion || "未检测到"],
|
||||
["npm", install?.npmAvailable ? (install.npmVersion || "可用") : "不可用"],
|
||||
["Node.js", install?.nodeVersion || (installLoading ? "检测中…" : "未检测到")],
|
||||
["npm", install?.npmAvailable ? (install.npmVersion || "可用") : installLoading ? "检测中…" : "不可用"],
|
||||
].map(([k, v]) => (
|
||||
<div key={k} className="rounded-lg border px-2.5 py-2" style={{ borderColor: "var(--ops-border-soft)", background: "var(--ops-surface-2)" }}>
|
||||
<div className="text-[10px] uppercase tracking-wide" style={{ color: "var(--ops-muted)" }}>{k}</div>
|
||||
@@ -296,42 +264,22 @@ export function McpSettings() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Toggles */}
|
||||
<div className="space-y-3 border-t pt-3" style={{ borderColor: "var(--ops-border-soft)" }}>
|
||||
<Row
|
||||
label="只读模式"
|
||||
hint="开启后 MCP/Agent 仅允许查询与元数据读取(mcp.json mode=read_only)。关闭后变为 read_write,写操作仍须确认。"
|
||||
>
|
||||
<Toggle
|
||||
checked={readOnly}
|
||||
disabled={saving || !policy}
|
||||
onChange={(v) => {
|
||||
if (!policy) return;
|
||||
void persist({ ...policy, mode: v ? "read_only" : "read_write", allow_dangerous: v ? false : policy.allow_dangerous });
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
<Row
|
||||
label="允许危险 SQL"
|
||||
hint="开启后,在非只读模式下经确认可执行 DROP / TRUNCATE / ALTER / 无 WHERE 的 UPDATE·DELETE 等。默认关闭。"
|
||||
>
|
||||
<Toggle
|
||||
checked={allowDangerous}
|
||||
disabled={saving || !policy || readOnly}
|
||||
onChange={(v) => {
|
||||
if (!policy) return;
|
||||
void persist({ ...policy, allow_dangerous: v });
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
<div className="space-y-2 border-t pt-3" style={{ borderColor: "var(--ops-border-soft)" }}>
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span className="rounded-md px-2 py-1" style={{ background: "var(--ops-surface-2)" }}>{readOnly ? "只读模式" : "读写模式"}</span>
|
||||
{!readOnly && allowDangerous && <span className="rounded-md px-2 py-1" style={{ background: "var(--ops-surface-2)" }}>允许危险 SQL</span>}
|
||||
{(readOnly || !allowDangerous) && <button type="button" className="ops-btn-secondary" disabled={saving || !policy} onClick={() => {
|
||||
if (policy) void persist({ ...policy, mode: "read_write", allow_dangerous: true });
|
||||
}}>使用默认权限</button>}
|
||||
</div>
|
||||
<p className="text-[11px]" style={{ color: "var(--ops-muted)" }}>Agent 写入前确认具体命令、连接与数据库。</p>
|
||||
</div>
|
||||
|
||||
{/* Whitelist */}
|
||||
<div className="space-y-2 border-t pt-3" style={{ borderColor: "var(--ops-border-soft)" }}>
|
||||
<div className="text-[12px] font-medium">连接白名单</div>
|
||||
<p className="text-[11px] leading-relaxed" style={{ color: "var(--ops-muted)" }}>
|
||||
空白名单时 MCP/Agent <strong>拒绝全部连接</strong>。勾选后写入 <code className="font-mono">~/.opsxdb/mcp.json</code>
|
||||
{policyPath ? `(${policyPath})` : ""}。
|
||||
仅可访问勾选的连接。
|
||||
</p>
|
||||
{connections.length === 0 ? (
|
||||
<p className="text-[11px]" style={{ color: "var(--ops-muted)" }}>暂无已保存连接。请先在侧栏新建连接。</p>
|
||||
@@ -388,7 +336,7 @@ export function McpSettings() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2 border-t pt-3" style={{ borderColor: "var(--ops-border-soft)" }}>
|
||||
<button type="button" className="ops-btn-secondary inline-flex h-7 items-center gap-1 px-2.5 text-[11px]" onClick={reload} disabled={loading}>
|
||||
<button type="button" className="ops-btn-secondary inline-flex h-7 items-center gap-1 px-2.5 text-[11px]" onClick={reload} disabled={loading || installLoading}>
|
||||
<RefreshCw className="h-3 w-3" /> 重新检查
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user