fix: finish 2.9.0 plugin moves and get every check green

This commit is contained in:
LukeGus committed 2026-09-24 23:35:18 -05:00
1 parent 8c1e02af89
commit 6489b62ece
335 files changed
+29878 -17749

No files matched your search

@@ -4,19 +4,21 @@ import { Server } from "lucide-react";
import { Input } from "@/components/input";
import { SectionCard, SettingRow, FakeSwitch } from "@/components/section-card";
import { getCredentials } from "@/main-axios";
import type { HostEditorForm } from "@/sidebar/HostEditorData";
import { Select2 } from "@/components/select2";
import type { ProxmoxHostConfig } from "./types";
type SetHostField = <K extends keyof HostEditorForm>(
key: K,
value: HostEditorForm[K],
) => void;
interface HostProxmoxForm {
enableProxmox: boolean;
proxmoxConfig: ProxmoxHostConfig | null;
}
type SetHostField = (key: string, value: unknown) => void;
export function HostProxmoxTab({
form,
setField,
}: {
form: HostEditorForm;
form: HostProxmoxForm;
setField: SetHostField;
}) {
const { t } = useTranslation();
@@ -158,9 +158,6 @@ export function ProxmoxDiscoverDialog({
// imported guests are reachable the same way; user can override.
jumpHosts: discoveredJumpHosts ?? undefined,
...importAuth,
enableTerminal: g.connectionType !== "rdp",
enableFileManager: g.connectionType !== "rdp",
enableTunnel: g.connectionType !== "rdp",
enableSsh: g.connectionType !== "rdp",
enableRdp: g.connectionType === "rdp",
enableDocker: g.enableDocker,
@@ -172,16 +169,21 @@ export function ProxmoxDiscoverDialog({
g.type === "lxc" ? `ct-${g.vmid}` : `vm-${g.vmid}`,
...(g.enableDocker ? ["docker"] : []),
],
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,
// This plugin's host settings travel with the import like an export's.
pluginSettings: {
proxmox: {
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,
},
},
},
},
}));
+39 -5
View File
@@ -25,19 +25,53 @@ interface DiscoverRequest {
type Opener = (request: DiscoverRequest) => void;
function ProxmoxHostSection({ form, setField }: HostEditorSectionProps) {
// The Proxmox fields are this plugin's host settings, saved with the host
// through form.pluginSettings.proxmox.
function ProxmoxHostSection({ form, updateForm }: HostEditorSectionProps) {
const values = (form.pluginSettings?.proxmox ?? {}) as Record<
string,
unknown
>;
const setField = (key: string, value: unknown) =>
updateForm((current) => {
const all = (current.pluginSettings ?? {}) as Record<
string,
Record<string, unknown>
>;
return {
...current,
pluginSettings: {
...all,
proxmox: { ...(all.proxmox ?? {}), [key]: value },
},
};
});
const sectionForm = {
enableProxmox: values.enableProxmox === true,
proxmoxConfig: values.proxmoxConfig ?? null,
enableProxmoxStats: values.enableProxmoxStats === true,
proxmoxStatsConfig: values.proxmoxStatsConfig ?? {
pollInterval: 60,
nodeName: null,
},
};
return (
<>
<HostProxmoxTab
form={form}
form={sectionForm as Parameters<typeof HostProxmoxTab>[0]["form"]}
setField={setField as Parameters<typeof HostProxmoxTab>[0]["setField"]}
/>
<HostProxmoxStatsTab
form={form}
setField={setField as Parameters<typeof HostProxmoxTab>[0]["setField"]}
form={sectionForm as Parameters<typeof HostProxmoxStatsTab>[0]["form"]}
setField={
setField as Parameters<typeof HostProxmoxStatsTab>[0]["setField"]
}
/>
{/* Other plugins add Proxmox-related settings here. */}
<ComponentSlot slotId="proxmox.hostEditor" props={{ form, setField }} />
<ComponentSlot
slotId="proxmox.hostEditor"
props={{ form: sectionForm, setField }}
/>
</>
);
}
@@ -3,7 +3,7 @@ import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Box, Search, Server as ServerIcon } from "lucide-react";
import { Input } from "@termix/plugin-sdk/ui";
import type { ProxmoxGuestSummary } from "../types";
import { cn } from "./utils";
import { cn } from "@termix/plugin-sdk/ui";
type StatusFilter = "all" | "running" | "stopped";
type TypeFilter = "all" | "qemu" | "lxc";
@@ -1,7 +1,7 @@
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Cpu, MemoryStick, HardDrive, Clock, Server } from "lucide-react";
import type { ProxmoxNodeStats } from "../types";
import { cn } from "./utils";
import { cn } from "@termix/plugin-sdk/ui";
function Tile({
icon,
@@ -20,7 +20,7 @@ import { NodeNetworkCard } from "./cards/NodeNetworkCard";
import { StoragePoolsCard } from "./cards/StoragePoolsCard";
import { ClusterHealthCard } from "./cards/ClusterHealthCard";
import { useConnectionRetry } from "@termix/plugin-sdk/frontend";
import { runAdaptivePolling } from "./adaptive-polling";
import { runAdaptivePolling } from "@termix/plugin-sdk/ui";
const HISTORY_LEN = 30;
@@ -1,132 +0,0 @@
export interface AdaptivePollingPolicy {
minIntervalMs: number;
maxIntervalMs: number;
stablePollsPerStep?: number;
jitterRatio?: number;
/** Prevent expensive requests from consuming more than this share of time. */
maxRequestDutyCycle?: number;
}
export interface AdaptivePollingState {
stablePolls: number;
consecutiveFailures: number;
lastPollDurationMs?: number;
}
export type AdaptivePollResult = boolean | void;
export function computeAdaptivePollDelay(
policy: AdaptivePollingPolicy,
state: AdaptivePollingState,
random = Math.random,
): number {
const min = Math.max(250, policy.minIntervalMs);
const max = Math.max(min, policy.maxIntervalMs);
const stableStep = Math.floor(
state.stablePolls / Math.max(1, policy.stablePollsPerStep ?? 3),
);
const exponent = state.consecutiveFailures || stableStep;
let base = Math.min(max, min * 2 ** Math.min(exponent, 10));
const dutyCycle = policy.maxRequestDutyCycle;
if (dutyCycle && dutyCycle > 0 && dutyCycle < 1) {
const duration = Math.max(0, state.lastPollDurationMs ?? 0);
base = Math.max(base, duration * ((1 - dutyCycle) / dutyCycle));
}
base = Math.min(max, base);
const jitterRatio = Math.max(0, Math.min(0.5, policy.jitterRatio ?? 0.1));
const jitter = base * jitterRatio * (random() * 2 - 1);
return Math.max(min, Math.min(max, Math.round(base + jitter)));
}
// Copy of src/ui/lib/adaptive-polling.ts. Duplicated here rather than exposed
// through the SDK because only this plugin's Proxmox stats tab uses it
// today; promote to @termix/plugin-sdk/frontend once a second plugin needs it.
export function runAdaptivePolling(
poll: () => AdaptivePollResult | Promise<AdaptivePollResult>,
policy: AdaptivePollingPolicy,
options: {
enabled?: () => boolean;
visible?: () => boolean;
runImmediately?: boolean;
random?: () => number;
intervalMultiplier?: () => number;
onError?: (error: unknown) => void;
} = {},
): () => void {
const enabled = options.enabled ?? (() => true);
const visible =
options.visible ??
(() =>
typeof document === "undefined" || document.visibilityState !== "hidden");
const state: AdaptivePollingState = {
stablePolls: 0,
consecutiveFailures: 0,
};
let timer: ReturnType<typeof setTimeout> | null = null;
let stopped = false;
let inFlight = false;
const clearTimer = () => {
if (!timer) return;
clearTimeout(timer);
timer = null;
};
const schedule = () => {
clearTimer();
if (stopped || !enabled() || !visible()) return;
timer = setTimeout(
() => void tick(),
Math.min(
policy.maxIntervalMs,
Math.round(
computeAdaptivePollDelay(policy, state, options.random) *
Math.max(1, options.intervalMultiplier?.() ?? 1),
),
),
);
};
const tick = async () => {
if (stopped || inFlight || !enabled() || !visible()) return;
inFlight = true;
const startedAt = Date.now();
try {
const changed = await poll();
state.consecutiveFailures = 0;
state.stablePolls = changed === false ? state.stablePolls + 1 : 0;
} catch (error) {
state.stablePolls = 0;
state.consecutiveFailures += 1;
options.onError?.(error);
} finally {
state.lastPollDurationMs = Date.now() - startedAt;
inFlight = false;
schedule();
}
};
const onVisibility = () => {
if (!visible()) {
clearTimer();
return;
}
state.stablePolls = 0;
state.consecutiveFailures = 0;
void tick();
};
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", onVisibility);
}
if (options.runImmediately ?? true) void tick();
else schedule();
return () => {
stopped = true;
clearTimer();
if (typeof document !== "undefined") {
document.removeEventListener("visibilitychange", onVisibility);
}
};
}
@@ -2,7 +2,7 @@ import { Network } from "lucide-react";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { MetricCard } from "@termix/plugin-sdk/ui";
import type { ProxmoxStatsSnapshot } from "../../types";
import { cn } from "../utils";
import { cn } from "@termix/plugin-sdk/ui";
export function ClusterHealthCard({
snapshot,
@@ -1,6 +0,0 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+32
View File
@@ -101,3 +101,35 @@ export interface ProxmoxStatsConfig {
pollInterval?: number;
enabledCards?: string[];
}
/** The proxmoxConfig host setting. */
export interface ProxmoxHostConfig {
source?: {
source: "proxmox";
sourceHostId: number;
node: string;
vmid: number;
type: "qemu" | "lxc";
lastSeenAt?: string;
lastStatus?: string;
missingSince?: string | null;
};
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[];
};
}