Require node names and preserve disk monitoring defaults

This commit is contained in:
rcourtman 2025-10-25 15:28:02 +00:00
parent a279e6720e
commit cb37d0de01

View file

@ -26,6 +26,25 @@ interface NodeModalProps {
securityStatus?: Partial<SecurityStatus>; securityStatus?: Partial<SecurityStatus>;
} }
const deriveNameFromHost = (host: string): string => {
let value = host.trim();
if (!value) {
return '';
}
try {
const url = value.includes('://') ? new URL(value) : new URL(`https://${value}`);
value = url.hostname || value;
} catch {
value = value.replace(/^https?:\/\//, '');
}
value = value.replace(/\/.*$/, '').replace(/^\[(.*)\]$/, '$1');
value = value.replace(/\s+/g, '-');
return value;
};
export const NodeModal: Component<NodeModalProps> = (props) => { export const NodeModal: Component<NodeModalProps> = (props) => {
const [testResult, setTestResult] = createSignal<{ const [testResult, setTestResult] = createSignal<{
status: string; status: string;
@ -136,7 +155,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
verifySSL: node.verifySSL ?? true, verifySSL: node.verifySSL ?? true,
monitorPhysicalDisks: monitorPhysicalDisks:
node.type === 'pve' node.type === 'pve'
? (node as NodeConfig & { monitorPhysicalDisks?: boolean }).monitorPhysicalDisks ?? false ? (node as NodeConfig & { monitorPhysicalDisks?: boolean }).monitorPhysicalDisks ?? true
: false, : false,
monitorMailStats: pmgConfig?.monitorMailStats ?? true, monitorMailStats: pmgConfig?.monitorMailStats ?? true,
monitorQueues: pmgConfig?.monitorQueues ?? true, monitorQueues: pmgConfig?.monitorQueues ?? true,
@ -150,10 +169,20 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
e.preventDefault(); e.preventDefault();
const data = formData(); const data = formData();
const normalizedName = data.name.trim() || deriveNameFromHost(data.host);
if (!normalizedName) {
showError('Node name is required');
return;
}
if (normalizedName !== data.name) {
setFormData((prev) => ({ ...prev, name: normalizedName }));
}
// Prepare data based on auth type // Prepare data based on auth type
const nodeData: Partial<NodeConfig> = { const nodeData: Partial<NodeConfig> = {
type: props.nodeType, type: props.nodeType,
name: data.name || '', // Will be auto-generated by backend if empty name: normalizedName,
host: data.host, host: data.host,
fingerprint: data.fingerprint, fingerprint: data.fingerprint,
verifySSL: data.verifySSL, verifySSL: data.verifySSL,
@ -202,10 +231,24 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
}; };
const updateField = (field: string, value: string | boolean) => { const updateField = (field: string, value: string | boolean) => {
setFormData((prev) => ({ ...prev, [field]: value })); if (field === 'host' && typeof value === 'string') {
if (field === 'host') { setFormData((prev) => {
const next = { ...prev, host: value };
const derivedName = deriveNameFromHost(value);
const previousDerivedName = deriveNameFromHost(prev.host || '');
const shouldAutoUpdate =
!prev.name.trim() || (previousDerivedName && prev.name === previousDerivedName);
if (derivedName && shouldAutoUpdate) {
next.name = derivedName;
}
return next;
});
setQuickSetupCommand(''); setQuickSetupCommand('');
return;
} }
setFormData((prev) => ({ ...prev, [field]: value }));
if (field === 'setupMode' && value !== 'auto') { if (field === 'setupMode' && value !== 'auto') {
setQuickSetupCommand(''); setQuickSetupCommand('');
} }
@ -213,6 +256,11 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
const handleTestConnection = async () => { const handleTestConnection = async () => {
const data = formData(); const data = formData();
const normalizedName = data.name.trim() || deriveNameFromHost(data.host);
if (!data.name.trim() && normalizedName) {
setFormData((prev) => ({ ...prev, name: normalizedName }));
}
// If editing an existing node and no new credentials provided, use stored credentials // If editing an existing node and no new credentials provided, use stored credentials
if (props.editingNode) { if (props.editingNode) {
@ -267,7 +315,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
// Prepare test data // Prepare test data
const testData: Partial<NodeConfig> = { const testData: Partial<NodeConfig> = {
type: props.nodeType, type: props.nodeType,
name: data.name || '', // Will be auto-generated by backend if empty name: normalizedName || '',
host: data.host, host: data.host,
fingerprint: data.fingerprint, fingerprint: data.fingerprint,
verifySSL: data.verifySSL, verifySSL: data.verifySSL,
@ -369,15 +417,19 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
<div class="grid grid-cols-1 gap-4 md:grid-cols-2"> <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class={formField}> <div class={formField}>
<label class={labelClass('flex items-center gap-2')}> <label class={labelClass('flex items-center gap-2')}>
Node Name <span class="text-xs text-gray-500">(optional)</span> Node Name <span class="text-red-500">*</span>
</label> </label>
<input <input
type="text" type="text"
value={formData().name} value={formData().name}
onInput={(e) => updateField('name', e.currentTarget.value)} onInput={(e) => updateField('name', e.currentTarget.value)}
placeholder="Will auto-detect from hostname" placeholder="Pulse uses this label across dashboards"
required
class={controlClass()} class={controlClass()}
/> />
<p class={formHelpText}>
Required and must be unique. We can auto-fill it from the Host URL if you leave it blank.
</p>
</div> </div>
<div class={formField}> <div class={formField}>