chore: lock the plugin boundary and remove 2.9.0 leftovers

This commit is contained in:
LukeGus committed 2026-09-25 07:24:46 -05:00
1 parent 6489b62ece
commit 2961297989
399 files changed
+3964 -5627

No files matched your search

+18 -17
View File
@@ -1,10 +1,14 @@
import { useEffect, useState } from "react";
import { useSshAuthTypes, useTranslation } from "@termix/plugin-sdk/frontend";
import { Server } from "lucide-react";
import { Input } from "@/components/input";
import { SectionCard, SettingRow, FakeSwitch } from "@/components/section-card";
import { getCredentials } from "@/main-axios";
import { Select2 } from "@/components/select2";
import {
FakeSwitch,
Input,
SectionCard,
Select2,
SettingRow,
} from "@termix/plugin-sdk/ui";
import { listCredentials } from "@termix/plugin-sdk/frontend";
import type { ProxmoxHostConfig } from "./types";
interface HostProxmoxForm {
@@ -28,19 +32,16 @@ export function HostProxmoxTab({
>([]);
useEffect(() => {
getCredentials()
.then((res: unknown) => {
const raw =
(res as { credentials?: unknown })?.credentials ?? res ?? [];
const list = (Array.isArray(raw) ? raw : []).map(
(c: Record<string, unknown>) => ({
id: c.id as number,
name: c.name as string,
username: (c.username as string | null) ?? null,
}),
);
setCredentials(list);
})
listCredentials()
.then((list) =>
setCredentials(
list.map((c) => ({
id: c.id,
name: c.name,
username: c.username ?? null,
})),
),
)
.catch(() => {});
}, []);
@@ -1,31 +1,26 @@
import { useRef, useState } from "react";
import type { HostData } from "@/types/index";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Server, RefreshCw, CheckSquare, Square, Download } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/button";
import { Button } from "@termix/plugin-sdk/ui";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/dialog";
import { Select2 } from "@/components/select2";
import {
discoverProxmoxGuestsStream,
bulkImportSSHHosts,
getSSHHosts,
} from "@/main-axios";
import type { SSHHostWithStatus } from "@/main-axios";
import type { ProxmoxGuest } from "@/types/proxmox";
} from "@termix/plugin-sdk/ui";
import { Select2 } from "@termix/plugin-sdk/ui";
import { discoverProxmoxGuestsStream, importProxmoxHosts } from "./proxmox-api";
import { listHosts, type PluginHostRecord } from "@termix/plugin-sdk/frontend";
import type { ProxmoxGuest } from "./types";
import { resolveProxmoxImportAuth } from "./proxmox-import-auth";
interface ProxmoxDiscoverDialogProps {
open: boolean;
onClose: () => void;
hosts: SSHHostWithStatus[];
onHostsChanged: (hosts: SSHHostWithStatus[]) => void;
hosts: PluginHostRecord[];
onHostsChanged: (hosts: PluginHostRecord[]) => void;
/** Pre-select a specific host and skip the host picker */
preselectedHostId?: number;
/** Credential to use for imported hosts */
@@ -69,7 +64,11 @@ export function ProxmoxDiscoverDialog({
// When opened from the dropdown (no preselectedHostId), only show Proxmox-enabled hosts
const sshHosts = hosts.filter(
(h) =>
!("isFolder" in h) && h.pluginSettings?.proxmox?.enableProxmox === true,
!("isFolder" in h) &&
(
h.pluginSettings as
Record<string, Record<string, unknown> | undefined> | undefined
)?.proxmox?.enableProxmox === true,
);
// The Proxmox host the discovery runs against — imported guests are grouped
@@ -189,11 +188,11 @@ export function ProxmoxDiscoverDialog({
}));
const result = toImport.length
? await bulkImportSSHHosts(toImport as unknown as HostData[], false)
: { success: 0, updated: 0, skipped: 0, failed: 0 };
? await importProxmoxHosts(toImport)
: { success: 0, failed: 0, errors: [] };
if (toImport.length) {
const updated = await getSSHHosts();
const updated = await listHosts();
onHostsChanged(updated);
window.dispatchEvent(new CustomEvent("termix:hosts-changed"));
}
@@ -202,9 +201,6 @@ export function ProxmoxDiscoverDialog({
result.success
? t("hosts.proxmoxResultImported", { count: result.success })
: null,
result.updated
? t("hosts.proxmoxResultUpdated", { count: result.updated })
: null,
result.failed
? t("hosts.proxmoxResultFailed", { count: result.failed })
: null,
+7 -5
View File
@@ -9,13 +9,13 @@ import {
type TermixApp,
} from "@termix/plugin-sdk/frontend";
import { ComponentSlot, DropdownMenuItem } from "@termix/plugin-sdk/ui";
import type { SSHHostWithStatus } from "@/main-axios";
import type { PluginHostRecord } from "@termix/plugin-sdk/frontend";
import { setProxmoxApp } from "./proxmox-api";
import { ProxmoxDiscoverDialog } from "./ProxmoxDiscoverDialog";
import { HostProxmoxTab } from "./HostProxmoxTab";
import { ProxmoxStatsTab } from "./stats/ProxmoxStatsTab";
import ProxmoxStatsApp from "./stats/ProxmoxStatsApp";
import { HostProxmoxStatsTab } from "./stats/HostProxmoxStatsTab";
import type { SSHHost } from "@/types";
interface DiscoverRequest {
hostId?: number;
@@ -79,7 +79,7 @@ function ProxmoxHostSection({ form, updateForm }: HostEditorSectionProps) {
function ProxmoxStatsTabView({ sshHost, label, isVisible }: TabProps) {
return (
<ProxmoxStatsTab
hostConfig={sshHost as unknown as SSHHost}
hostConfig={sshHost as never}
title={label}
isVisible={isVisible}
isTopbarOpen={false}
@@ -93,6 +93,8 @@ function ProxmoxStatsStandalone({ hostId }: StandaloneViewProps) {
}
export function activate(app: TermixApp): void {
setProxmoxApp(app);
app.onDispose(() => setProxmoxApp(null));
// The dialog lives in the hosts panel; the menu item and host action open
// it through this, created per activation.
const openers = new Set<Opener>();
@@ -104,8 +106,8 @@ export function activate(app: TermixApp): void {
hosts,
onHostsChanged,
}: {
hosts: SSHHostWithStatus[];
onHostsChanged: (hosts: SSHHostWithStatus[]) => void;
hosts: PluginHostRecord[];
onHostsChanged: (hosts: PluginHostRecord[]) => void;
}) {
const [request, setRequest] = useState<DiscoverRequest | null>(null);
useEffect(() => {
+117
View File
@@ -0,0 +1,117 @@
import type { TermixApp } from "@termix/plugin-sdk/frontend";
import type { ProxmoxDiscoverResult } from "./types";
let current: TermixApp | null = null;
/** Set in activate, cleared on deactivate. */
export function setProxmoxApp(app: TermixApp | null): void {
current = app;
}
function app(): TermixApp {
if (!current) throw new Error("The Proxmox plugin is not active");
return current;
}
/**
* Runs a guest discovery over the server-sent event stream and reports
* progress. Returns a function that stops listening.
*/
export function discoverProxmoxGuestsStream(
hostId: number,
handlers: {
onProgress?: (done: number, total: number) => void;
onResult: (result: ProxmoxDiscoverResult) => void;
onError: (message: string) => void;
},
): () => void {
const controller = new AbortController();
let settled = false;
const finish = () => {
settled = true;
controller.abort();
};
const handleEvent = (event: string, data: string) => {
if (event === "progress") {
try {
const parsed = JSON.parse(data);
handlers.onProgress?.(parsed.done, parsed.total);
} catch {
// ignore malformed progress frames
}
} else if (event === "result") {
finish();
try {
handlers.onResult(JSON.parse(data));
} catch {
handlers.onError("Failed to parse discovery result");
}
} else if (event === "fail") {
finish();
let message = "Discovery failed";
try {
message = JSON.parse(data).message || message;
} catch {
// keep default message
}
handlers.onError(message);
}
};
void (async () => {
try {
const response = await app().fetch(
`/discover/stream?hostId=${encodeURIComponent(String(hostId))}`,
{ signal: controller.signal },
);
if (!response.ok || !response.body) throw new Error("stream failed");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let split = buffer.indexOf("\n\n");
while (split !== -1) {
const frame = buffer.slice(0, split);
buffer = buffer.slice(split + 2);
let event = "message";
const data: string[] = [];
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) data.push(line.slice(5).trim());
}
if (data.length) handleEvent(event, data.join("\n"));
split = buffer.indexOf("\n\n");
}
}
if (!settled) {
settled = true;
handlers.onError("Discovery connection lost");
}
} catch {
if (settled) return;
settled = true;
handlers.onError("Discovery connection lost");
}
})();
return finish;
}
export interface ProxmoxImportResult {
success: number;
failed: number;
errors: string[];
}
export async function importProxmoxHosts(
hosts: Record<string, unknown>[],
): Promise<ProxmoxImportResult> {
const response = await app().api.post<ProxmoxImportResult>("/import", {
hosts,
});
return response.data;
}
+18
View File
@@ -133,3 +133,21 @@ export interface ProxmoxHostConfig {
errors: string[];
};
}
export interface ProxmoxGuest {
name: string;
vmid: number;
type: "qemu" | "lxc";
node: string;
status: string;
ip: string | null;
connectionType: "ssh" | "rdp";
enableDocker: boolean;
}
export interface ProxmoxDiscoverResult {
guests: ProxmoxGuest[];
credentialId: number | null;
defaultCredentialId: number | null;
jumpHosts?: unknown[] | null;
}