mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 05:13:36 +00:00
Add Proxmox guest auto sync (#1053)
This commit is contained in:
@@ -276,6 +276,9 @@ export function registerHostBulkRoutes(
|
||||
dockerPatterns: existing.dockerPatterns ?? "docker",
|
||||
preferredPrefixes:
|
||||
existing.preferredPrefixes ?? "10., 192.168.",
|
||||
autoSyncEnabled: existing.autoSyncEnabled ?? false,
|
||||
syncIntervalMinutes: existing.syncIntervalMinutes ?? 15,
|
||||
markMissingGuests: existing.markMissingGuests ?? true,
|
||||
};
|
||||
await db
|
||||
.update(hosts)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -70,9 +70,23 @@ export type GuacamoleAuthType = "password" | "credential";
|
||||
|
||||
export interface ProxmoxConfig {
|
||||
defaultCredentialId: number | null;
|
||||
defaultAuthType?: string;
|
||||
windowsPatterns: string;
|
||||
dockerPatterns: string;
|
||||
preferredPrefixes: string;
|
||||
autoSyncEnabled?: boolean;
|
||||
syncIntervalMinutes?: number;
|
||||
markMissingGuests?: boolean;
|
||||
lastSyncAt?: string;
|
||||
lastSyncStatus?: "success" | "error";
|
||||
lastSyncError?: string | null;
|
||||
lastSyncResult?: {
|
||||
created: number;
|
||||
updated: number;
|
||||
markedMissing: number;
|
||||
skipped: number;
|
||||
errors: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface HostFeatureFlags {
|
||||
|
||||
@@ -14,3 +14,11 @@ export interface ProxmoxDiscoverResult {
|
||||
credentialId: number | null;
|
||||
defaultCredentialId: number | null;
|
||||
}
|
||||
|
||||
export interface ProxmoxSyncResult {
|
||||
created: number;
|
||||
updated: number;
|
||||
markedMissing: number;
|
||||
skipped: number;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
@@ -116,6 +116,19 @@ export type Host = {
|
||||
windowsPatterns: string;
|
||||
dockerPatterns: string;
|
||||
preferredPrefixes: string;
|
||||
autoSyncEnabled?: boolean;
|
||||
syncIntervalMinutes?: number;
|
||||
markMissingGuests?: boolean;
|
||||
lastSyncAt?: string;
|
||||
lastSyncStatus?: "success" | "error";
|
||||
lastSyncError?: string | null;
|
||||
lastSyncResult?: {
|
||||
created: number;
|
||||
updated: number;
|
||||
markedMissing: number;
|
||||
skipped: number;
|
||||
errors: string[];
|
||||
};
|
||||
} | null;
|
||||
|
||||
statsConfig?: {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@/main-axios";
|
||||
import type { SSHHost, SSHHostData, ProxyNode } from "@/types/index";
|
||||
import type { ServerStatus, SSHHostWithStatus } from "@/main-axios";
|
||||
import type { ProxmoxDiscoverResult } from "@/types/proxmox";
|
||||
import type { ProxmoxDiscoverResult, ProxmoxSyncResult } from "@/types/proxmox";
|
||||
import {
|
||||
getCachedSSHHosts,
|
||||
invalidateHostsAndStatusCaches,
|
||||
@@ -174,6 +174,21 @@ export async function discoverProxmoxGuests(
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncProxmoxGuests(
|
||||
hostId: number,
|
||||
): Promise<ProxmoxSyncResult> {
|
||||
try {
|
||||
const response = await authApi.post(
|
||||
"/proxmox/sync",
|
||||
{ hostId },
|
||||
{ timeout: 120000 },
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
handleApiError(error, "sync Proxmox guests");
|
||||
}
|
||||
}
|
||||
|
||||
export async function bulkUpdateSSHHosts(
|
||||
hostIds: number[],
|
||||
updates: Record<string, unknown>,
|
||||
|
||||
@@ -138,6 +138,18 @@ export function ProxmoxDiscoverDialog({
|
||||
enableDocker: g.enableDocker,
|
||||
connectionType: g.connectionType,
|
||||
tags: ["proxmox", g.type, g.node],
|
||||
proxmoxConfig: {
|
||||
source: {
|
||||
source: "proxmox",
|
||||
sourceHostId: Number(effectiveHostId),
|
||||
node: g.node,
|
||||
vmid: g.vmid,
|
||||
type: g.type,
|
||||
lastSeenAt: new Date().toISOString(),
|
||||
lastStatus: g.status,
|
||||
missingSince: null,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await bulkImportSSHHosts(toImport, false);
|
||||
|
||||
@@ -607,6 +607,21 @@
|
||||
"proxmoxDockerDetectionDesc": "Comma-separated name patterns that enable Docker for matching guests",
|
||||
"proxmoxPreferredRanges": "Preferred IP ranges",
|
||||
"proxmoxPreferredRangesDesc": "Comma-separated prefixes in priority order for IP selection when a guest has multiple interfaces",
|
||||
"proxmoxAutoSync": "Auto sync guests",
|
||||
"proxmoxAutoSyncDesc": "Periodically discover this Proxmox node and create or update imported guest hosts while your session is unlocked.",
|
||||
"proxmoxSyncInterval": "Sync interval (minutes)",
|
||||
"proxmoxSyncIntervalDesc": "Minimum 5 minutes. The scheduler skips locked user sessions.",
|
||||
"proxmoxMarkMissing": "Mark missing guests",
|
||||
"proxmoxMarkMissingDesc": "Add a proxmox-missing tag when a previously imported guest disappears instead of deleting it.",
|
||||
"proxmoxLastSync": "Last sync",
|
||||
"proxmoxLastSyncNever": "Not synced yet",
|
||||
"proxmoxLastSyncNoResult": "No result details",
|
||||
"proxmoxLastSyncSummary": "{{created}} created, {{updated}} updated, {{markedMissing}} missing, {{skipped}} skipped",
|
||||
"proxmoxLastSyncStatus": {
|
||||
"success": "Success",
|
||||
"error": "Failed",
|
||||
"pending": "Pending"
|
||||
},
|
||||
"proxmoxDiscoverAction": "Discover & import Proxmox guests",
|
||||
"proxmoxImportTitle": "Import from Proxmox",
|
||||
"proxmoxSelectHost": "Select a Proxmox host…",
|
||||
|
||||
@@ -864,6 +864,21 @@
|
||||
"proxmoxDockerDetectionDesc": "逗号分隔的名称模式使 Docker 能够匹配虚拟机",
|
||||
"proxmoxPreferredRanges": "首选 IP 地址范围",
|
||||
"proxmoxPreferredRangesDesc": "当访客拥有多个接口时,用于选择 IP 地址的优先级前缀以逗号分隔",
|
||||
"proxmoxAutoSync": "自动同步访客",
|
||||
"proxmoxAutoSyncDesc": "在会话解锁时定期发现此 Proxmox 节点,并创建或更新导入的访客主机。",
|
||||
"proxmoxSyncInterval": "同步间隔(分钟)",
|
||||
"proxmoxSyncIntervalDesc": "最小 5 分钟。用户会话锁定时调度器会跳过同步。",
|
||||
"proxmoxMarkMissing": "标记消失的访客",
|
||||
"proxmoxMarkMissingDesc": "以前导入的访客消失时添加 proxmox-missing 标签,而不是删除主机。",
|
||||
"proxmoxLastSync": "上次同步",
|
||||
"proxmoxLastSyncNever": "尚未同步",
|
||||
"proxmoxLastSyncNoResult": "暂无结果详情",
|
||||
"proxmoxLastSyncSummary": "创建 {{created}},更新 {{updated}},消失 {{markedMissing}},跳过 {{skipped}}",
|
||||
"proxmoxLastSyncStatus": {
|
||||
"success": "成功",
|
||||
"error": "失败",
|
||||
"pending": "等待中"
|
||||
},
|
||||
"proxmoxDiscoverAction": "发现并导入 Proxmox 访客",
|
||||
"proxmoxImportTitle": "从 Proxmox 导入",
|
||||
"proxmoxSelectHost": "选择 Proxmox 主机…",
|
||||
|
||||
@@ -1517,6 +1517,7 @@ export {
|
||||
bulkImportSSHHosts,
|
||||
importSSHConfigHosts,
|
||||
discoverProxmoxGuests,
|
||||
syncProxmoxGuests,
|
||||
bulkUpdateSSHHosts,
|
||||
deleteSSHHost,
|
||||
getSSHHostById,
|
||||
|
||||
@@ -114,6 +114,9 @@ export function createHostEditorForm(
|
||||
windowsPatterns: "win, windows",
|
||||
dockerPatterns: "docker",
|
||||
preferredPrefixes: "10., 192.168.",
|
||||
autoSyncEnabled: false,
|
||||
syncIntervalMinutes: 15,
|
||||
markMissingGuests: true,
|
||||
},
|
||||
enableTunnel: host?.enableTunnel ?? false,
|
||||
defaultPath: host?.defaultPath ?? "/",
|
||||
|
||||
@@ -105,7 +105,22 @@ export function HostProxmoxTab({
|
||||
windowsPatterns: "win, windows",
|
||||
dockerPatterns: "docker",
|
||||
preferredPrefixes: "10., 192.168.",
|
||||
autoSyncEnabled: false,
|
||||
syncIntervalMinutes: 15,
|
||||
markMissingGuests: true,
|
||||
};
|
||||
const lastSyncResult = cfg.lastSyncResult;
|
||||
const lastSyncSummary = lastSyncResult
|
||||
? t("hosts.proxmoxLastSyncSummary", {
|
||||
created: lastSyncResult.created,
|
||||
updated: lastSyncResult.updated,
|
||||
markedMissing: lastSyncResult.markedMissing,
|
||||
skipped: lastSyncResult.skipped,
|
||||
})
|
||||
: t("hosts.proxmoxLastSyncNoResult");
|
||||
const lastSyncDescription = cfg.lastSyncAt
|
||||
? `${new Date(cfg.lastSyncAt).toLocaleString()} · ${lastSyncSummary}`
|
||||
: t("hosts.proxmoxLastSyncNever");
|
||||
|
||||
return (
|
||||
<SectionCard
|
||||
@@ -231,6 +246,71 @@ export function HostProxmoxTab({
|
||||
placeholder="10., 192.168."
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.proxmoxAutoSync")}
|
||||
description={t("hosts.proxmoxAutoSyncDesc")}
|
||||
>
|
||||
<FakeSwitch
|
||||
checked={cfg.autoSyncEnabled === true}
|
||||
onChange={(v) =>
|
||||
setField("proxmoxConfig", {
|
||||
...cfg,
|
||||
autoSyncEnabled: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.proxmoxSyncInterval")}
|
||||
description={t("hosts.proxmoxSyncIntervalDesc")}
|
||||
>
|
||||
<Input
|
||||
className="w-24 h-7 text-xs"
|
||||
type="number"
|
||||
min={5}
|
||||
value={cfg.syncIntervalMinutes ?? 15}
|
||||
onChange={(e) =>
|
||||
setField("proxmoxConfig", {
|
||||
...cfg,
|
||||
syncIntervalMinutes: Math.max(
|
||||
5,
|
||||
Number.parseInt(e.target.value || "15", 10),
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.proxmoxMarkMissing")}
|
||||
description={t("hosts.proxmoxMarkMissingDesc")}
|
||||
>
|
||||
<FakeSwitch
|
||||
checked={cfg.markMissingGuests !== false}
|
||||
onChange={(v) =>
|
||||
setField("proxmoxConfig", {
|
||||
...cfg,
|
||||
markMissingGuests: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.proxmoxLastSync")}
|
||||
description={lastSyncDescription}
|
||||
>
|
||||
<span
|
||||
className={`text-xs ${
|
||||
cfg.lastSyncStatus === "error"
|
||||
? "text-destructive"
|
||||
: "text-muted-foreground"
|
||||
}`}
|
||||
title={cfg.lastSyncError ?? undefined}
|
||||
>
|
||||
{cfg.lastSyncStatus
|
||||
? t(`hosts.proxmoxLastSyncStatus.${cfg.lastSyncStatus}`)
|
||||
: t("hosts.proxmoxLastSyncStatus.pending")}
|
||||
</span>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user