import { Component, Show, createSignal, For } from 'solid-js'; import { Portal } from 'solid-js/web'; import { showSuccess, showError } from '@/utils/toast'; import { NodesAPI } from '@/api/nodes'; import type { NodeConfig } from '@/types/nodes'; import { SectionHeader } from '@/components/shared/SectionHeader'; import { formField, labelClass, controlClass, formHelpText } from '@/components/shared/Form'; interface DiscoveredServer { ip: string; port: number; type: 'pve' | 'pbs'; version: string; hostname?: string; release?: string; } interface BatchCredentialModalProps { isOpen: boolean; onClose: () => void; servers: DiscoveredServer[]; onComplete: () => void; } export const BatchCredentialModal: Component = (props) => { const [authType, setAuthType] = createSignal<'password' | 'token'>('token'); const [username, setUsername] = createSignal(''); const [password, setPassword] = createSignal(''); const [tokenName, setTokenName] = createSignal(''); const [tokenValue, setTokenValue] = createSignal(''); const [isAdding, setIsAdding] = createSignal(false); const [progress, setProgress] = createSignal<{ current: number; total: number; currentServer?: string; }>({ current: 0, total: 0 }); const handleBatchAdd = async () => { // Validate inputs if (authType() === 'password') { if (!username() || !password()) { showError('Username and password are required'); return; } } else { if (!tokenName() || !tokenValue()) { showError('Token ID and value are required'); return; } } setIsAdding(true); const total = props.servers.length; setProgress({ current: 0, total }); let successCount = 0; let failedServers: string[] = []; for (let i = 0; i < props.servers.length; i++) { const server = props.servers[i]; setProgress({ current: i + 1, total, currentServer: server.hostname || server.ip }); try { const nodeData: NodeConfig = { id: '', // Will be generated by backend type: server.type, name: server.hostname || server.ip, host: `https://${server.ip}:${server.port}`, verifySSL: false, ...(authType() === 'password' ? { user: username(), password: password() } : { tokenName: tokenName(), tokenValue: tokenValue() }), // Add default monitoring options based on type ...(server.type === 'pve' ? { monitorVMs: true, monitorContainers: true, monitorStorage: true, monitorBackups: true, monitorPhysicalDisks: false, } : { monitorDatastores: true, monitorSyncJobs: true, monitorVerifyJobs: true, monitorPruneJobs: true, monitorGarbageJobs: false, }), } as NodeConfig; await NodesAPI.addNode(nodeData); successCount++; } catch (error) { console.error(`Failed to add ${server.hostname || server.ip}:`, error); failedServers.push(server.hostname || server.ip); } } setIsAdding(false); if (successCount === total) { showSuccess(`Successfully added all ${total} servers`); props.onComplete(); props.onClose(); } else if (successCount > 0) { showError(`Added ${successCount} of ${total} servers. Failed: ${failedServers.join(', ')}`); props.onComplete(); } else { showError('Failed to add any servers. Check your credentials.'); } }; return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}
{/* Body */}
{/* Server List */}

Servers to Add

{(server) => ( {server.hostname || server.ip} )}
{/* Shared Credentials */}

Shared Credentials (Same credentials will be used for all servers)

{/* Auth Type Selector */}
{/* Token Auth Fields */}
setTokenName(e.currentTarget.value)} placeholder="user@realm!tokenname" disabled={isAdding()} class={controlClass('font-mono')} />

Example: pulse-monitor@pam!pulse-token or pulse-monitor@pbs!pulse-token

setTokenValue(e.currentTarget.value)} placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" disabled={isAdding()} class={controlClass('font-mono')} />
{/* Password Auth Fields */}
setUsername(e.currentTarget.value)} placeholder="root@pam or admin@pbs" disabled={isAdding()} class={controlClass()} />
setPassword(e.currentTarget.value)} placeholder="Password" disabled={isAdding()} class={controlClass()} />
{/* Progress */}

Adding servers... ({progress().current}/{progress().total})

Currently adding: {progress().currentServer}

{/* Footer */}
); };