Add per-node temperature monitoring and fix critical config update bug
This commit implements per-node temperature monitoring control and fixes a critical bug where partial node updates were destroying existing configuration. Backend changes: - Add TemperatureMonitoringEnabled field (*bool) to PVEInstance, PBSInstance, and PMGInstance - Update monitor.go to check per-node temperature setting with global fallback - Convert all NodeConfigRequest boolean fields to *bool pointers - Add nil checks in HandleUpdateNode to prevent overwriting unmodified fields - Fix critical bug where partial updates zeroed out MonitorVMs, MonitorContainers, etc. - Update NodeResponse, NodeFrontend, and StateSnapshot to include temperature setting - Fix HandleAddNode and test connection handlers to use pointer-based boolean fields Frontend changes: - Add temperatureMonitoringEnabled to Node interface and config types - Create per-node temperature monitoring toggle handler with optimistic updates - Update NodeModal to wire up per-node temperature toggle - Add isTemperatureMonitoringEnabled helper to check effective monitoring state - Update ConfiguredNodeTables to show/hide temperature badge based on monitoring state - Update NodeSummaryTable to conditionally show temperature column - Pass globalTemperatureMonitoringEnabled prop through component tree The critical bug fix ensures that when updating a single field (like temperature monitoring), the backend only modifies that specific field instead of zeroing out all other boolean configuration fields.
This commit is contained in:
parent
e4e915c8a1
commit
27f2038dab
19 changed files with 655 additions and 280 deletions
|
|
@ -1076,6 +1076,7 @@ const UnifiedBackups: Component = () => {
|
||||||
{/* Unified Node Selector */}
|
{/* Unified Node Selector */}
|
||||||
<UnifiedNodeSelector
|
<UnifiedNodeSelector
|
||||||
currentTab="backups"
|
currentTab="backups"
|
||||||
|
globalTemperatureMonitoringEnabled={state.temperatureMonitoringEnabled}
|
||||||
onNodeSelect={(nodeId) => {
|
onNodeSelect={(nodeId) => {
|
||||||
setSelectedNode(nodeId);
|
setSelectedNode(nodeId);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -762,6 +762,7 @@ export function Dashboard(props: DashboardProps) {
|
||||||
{/* Unified Node Selector */}
|
{/* Unified Node Selector */}
|
||||||
<UnifiedNodeSelector
|
<UnifiedNodeSelector
|
||||||
currentTab="dashboard"
|
currentTab="dashboard"
|
||||||
|
globalTemperatureMonitoringEnabled={ws.state.temperatureMonitoringEnabled}
|
||||||
onNodeSelect={handleNodeSelect}
|
onNodeSelect={handleNodeSelect}
|
||||||
nodes={props.nodes}
|
nodes={props.nodes}
|
||||||
filteredVms={filteredGuests().filter((g) => g.type === 'qemu')}
|
filteredVms={filteredGuests().filter((g) => g.type === 'qemu')}
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ export const DockerHosts: Component<DockerHostsProps> = (props) => {
|
||||||
actions={
|
actions={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/settings/containers')}
|
onClick={() => navigate('/settings/docker')}
|
||||||
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
<span>Set up container agent</span>
|
<span>Set up container agent</span>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ type NodeConfigWithStatus = NodeConfig & {
|
||||||
interface PveNodesTableProps {
|
interface PveNodesTableProps {
|
||||||
nodes: NodeConfigWithStatus[];
|
nodes: NodeConfigWithStatus[];
|
||||||
stateNodes: { instance: string; status?: string; connectionHealth?: string }[];
|
stateNodes: { instance: string; status?: string; connectionHealth?: string }[];
|
||||||
|
globalTemperatureMonitoringEnabled?: boolean;
|
||||||
onTestConnection: (nodeId: string) => void;
|
onTestConnection: (nodeId: string) => void;
|
||||||
onEdit: (node: NodeConfigWithStatus) => void;
|
onEdit: (node: NodeConfigWithStatus) => void;
|
||||||
onDelete: (node: NodeConfigWithStatus) => void;
|
onDelete: (node: NodeConfigWithStatus) => void;
|
||||||
|
|
@ -45,6 +46,17 @@ const STATUS_META: Record<string, StatusMeta> = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isTemperatureMonitoringEnabled = (
|
||||||
|
node: NodeConfigWithStatus,
|
||||||
|
globalEnabled: boolean,
|
||||||
|
): boolean => {
|
||||||
|
// Check per-node setting first, fall back to global
|
||||||
|
if (node.temperatureMonitoringEnabled !== undefined && node.temperatureMonitoringEnabled !== null) {
|
||||||
|
return node.temperatureMonitoringEnabled;
|
||||||
|
}
|
||||||
|
return globalEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
const resolvePveStatusMeta = (
|
const resolvePveStatusMeta = (
|
||||||
node: NodeConfigWithStatus,
|
node: NodeConfigWithStatus,
|
||||||
stateNodes: PveNodesTableProps['stateNodes'],
|
stateNodes: PveNodesTableProps['stateNodes'],
|
||||||
|
|
@ -199,11 +211,12 @@ export const PveNodesTable: Component<PveNodesTableProps> = (props) => {
|
||||||
Physical Disks
|
Physical Disks
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{node.type === 'pve' && node.temperature?.available && (
|
{node.type === 'pve' &&
|
||||||
<span class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
isTemperatureMonitoringEnabled(node, props.globalTemperatureMonitoringEnabled ?? true) && (
|
||||||
Temperature
|
<span class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
||||||
</span>
|
Temperature
|
||||||
)}
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-top px-3 py-3 whitespace-nowrap">
|
<td class="align-top px-3 py-3 whitespace-nowrap">
|
||||||
|
|
@ -261,6 +274,7 @@ export const PveNodesTable: Component<PveNodesTableProps> = (props) => {
|
||||||
interface PbsNodesTableProps {
|
interface PbsNodesTableProps {
|
||||||
nodes: NodeConfigWithStatus[];
|
nodes: NodeConfigWithStatus[];
|
||||||
statePbs: { name: string; status?: string; connectionHealth?: string }[];
|
statePbs: { name: string; status?: string; connectionHealth?: string }[];
|
||||||
|
globalTemperatureMonitoringEnabled?: boolean;
|
||||||
onTestConnection: (nodeId: string) => void;
|
onTestConnection: (nodeId: string) => void;
|
||||||
onEdit: (node: NodeConfigWithStatus) => void;
|
onEdit: (node: NodeConfigWithStatus) => void;
|
||||||
onDelete: (node: NodeConfigWithStatus) => void;
|
onDelete: (node: NodeConfigWithStatus) => void;
|
||||||
|
|
@ -380,11 +394,12 @@ export const PbsNodesTable: Component<PbsNodesTableProps> = (props) => {
|
||||||
Garbage Collection
|
Garbage Collection
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{node.type === 'pbs' && node.temperature?.available && (
|
{node.type === 'pbs' &&
|
||||||
<span class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
isTemperatureMonitoringEnabled(node, props.globalTemperatureMonitoringEnabled ?? true) && (
|
||||||
Temperature
|
<span class="text-xs px-2 py-1 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded">
|
||||||
</span>
|
Temperature
|
||||||
)}
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-top px-3 py-3 whitespace-nowrap">
|
<td class="align-top px-3 py-3 whitespace-nowrap">
|
||||||
|
|
@ -442,6 +457,7 @@ export const PbsNodesTable: Component<PbsNodesTableProps> = (props) => {
|
||||||
interface PmgNodesTableProps {
|
interface PmgNodesTableProps {
|
||||||
nodes: NodeConfigWithStatus[];
|
nodes: NodeConfigWithStatus[];
|
||||||
statePmg: { name: string; status?: string; connectionHealth?: string }[];
|
statePmg: { name: string; status?: string; connectionHealth?: string }[];
|
||||||
|
globalTemperatureMonitoringEnabled?: boolean;
|
||||||
onTestConnection: (nodeId: string) => void;
|
onTestConnection: (nodeId: string) => void;
|
||||||
onEdit: (node: NodeConfigWithStatus) => void;
|
onEdit: (node: NodeConfigWithStatus) => void;
|
||||||
onDelete: (node: NodeConfigWithStatus) => void;
|
onDelete: (node: NodeConfigWithStatus) => void;
|
||||||
|
|
|
||||||
|
|
@ -1746,7 +1746,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-gray-900 dark:text-gray-100">Temperature monitoring</p>
|
<p class="font-medium text-gray-900 dark:text-gray-100">Temperature monitoring</p>
|
||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
Uses the Pulse sensors key or proxy to read CPU/NVMe temperatures for every node. Disable if you don’t need temperature data or haven’t deployed the proxy yet.
|
Uses the Pulse sensors key or proxy to read CPU/NVMe temperatures for this node. Disable if you don't need temperature data or haven't deployed the proxy yet.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<TogglePrimitive
|
<TogglePrimitive
|
||||||
|
|
@ -1760,7 +1760,7 @@ export const NodeModal: Component<NodeModalProps> = (props) => {
|
||||||
</div>
|
</div>
|
||||||
<Show when={!enabled}>
|
<Show when={!enabled}>
|
||||||
<p class="mt-3 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-200">
|
<p class="mt-3 rounded border border-blue-200 bg-blue-50 p-2 text-xs text-blue-700 dark:border-blue-700 dark:bg-blue-900/20 dark:text-blue-200">
|
||||||
Pulse will skip all SSH temperature polling until you re-enable this toggle. Existing dashboard readings will stop refreshing.
|
Pulse will skip SSH temperature polling for this node. Existing dashboard readings will stop refreshing.
|
||||||
</p>
|
</p>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.temperatureMonitoringLocked}>
|
<Show when={props.temperatureMonitoringLocked}>
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
if (path.includes('/settings/proxmox')) return 'proxmox';
|
if (path.includes('/settings/proxmox')) return 'proxmox';
|
||||||
if (path.includes('/settings/agent-hub')) return 'proxmox';
|
if (path.includes('/settings/agent-hub')) return 'proxmox';
|
||||||
if (path.includes('/settings/docker')) return 'docker';
|
if (path.includes('/settings/docker')) return 'docker';
|
||||||
|
if (path.includes('/settings/containers')) return 'docker';
|
||||||
if (
|
if (
|
||||||
path.includes('/settings/hosts') ||
|
path.includes('/settings/hosts') ||
|
||||||
path.includes('/settings/host-agents') ||
|
path.includes('/settings/host-agents') ||
|
||||||
|
|
@ -488,6 +489,14 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path.startsWith('/settings/containers')) {
|
||||||
|
navigate(path.replace('/settings/containers', '/settings/docker'), {
|
||||||
|
replace: true,
|
||||||
|
scroll: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
path.startsWith('/settings/linuxServers') ||
|
path.startsWith('/settings/linuxServers') ||
|
||||||
path.startsWith('/settings/windowsServers') ||
|
path.startsWith('/settings/windowsServers') ||
|
||||||
|
|
@ -1409,6 +1418,54 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleNodeTemperatureMonitoringChange = async (nodeId: string, enabled: boolean | null): Promise<void> => {
|
||||||
|
if (savingTemperatureSetting()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = nodes().find((n) => n.id === nodeId);
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = node.temperatureMonitoringEnabled;
|
||||||
|
setSavingTemperatureSetting(true);
|
||||||
|
|
||||||
|
// Update local state optimistically
|
||||||
|
setNodes(
|
||||||
|
nodes().map((n) => (n.id === nodeId ? { ...n, temperatureMonitoringEnabled: enabled } : n)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also update editingNode if this is the node being edited
|
||||||
|
if (editingNode()?.id === nodeId) {
|
||||||
|
setEditingNode({ ...editingNode()!, temperatureMonitoringEnabled: enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await NodesAPI.updateNode(nodeId, { temperatureMonitoringEnabled: enabled } as any);
|
||||||
|
if (enabled === true) {
|
||||||
|
notificationStore.success('Temperature monitoring enabled for this node', 2000);
|
||||||
|
} else if (enabled === false) {
|
||||||
|
notificationStore.info('Temperature monitoring disabled for this node', 2000);
|
||||||
|
} else {
|
||||||
|
notificationStore.info('Using global temperature monitoring setting', 2000);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to update node temperature monitoring setting', error);
|
||||||
|
notificationStore.error('Failed to update temperature monitoring setting');
|
||||||
|
// Revert on error
|
||||||
|
setNodes(
|
||||||
|
nodes().map((n) => (n.id === nodeId ? { ...n, temperatureMonitoringEnabled: previous } : n)),
|
||||||
|
);
|
||||||
|
// Also revert editingNode
|
||||||
|
if (editingNode()?.id === nodeId) {
|
||||||
|
setEditingNode({ ...editingNode()!, temperatureMonitoringEnabled: previous });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSavingTemperatureSetting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDiscoveryModeChange = async (mode: 'auto' | 'custom') => {
|
const handleDiscoveryModeChange = async (mode: 'auto' | 'custom') => {
|
||||||
if (envOverrides().discoverySubnet || savingDiscoverySettings()) {
|
if (envOverrides().discoverySubnet || savingDiscoverySettings()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -2356,6 +2413,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
<PveNodesTable
|
<PveNodesTable
|
||||||
nodes={pveNodes()}
|
nodes={pveNodes()}
|
||||||
stateNodes={state.nodes ?? []}
|
stateNodes={state.nodes ?? []}
|
||||||
|
globalTemperatureMonitoringEnabled={temperatureMonitoringEnabled()}
|
||||||
onTestConnection={testNodeConnection}
|
onTestConnection={testNodeConnection}
|
||||||
onEdit={(node) => {
|
onEdit={(node) => {
|
||||||
setEditingNode(node);
|
setEditingNode(node);
|
||||||
|
|
@ -2642,6 +2700,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
<PbsNodesTable
|
<PbsNodesTable
|
||||||
nodes={pbsNodes()}
|
nodes={pbsNodes()}
|
||||||
statePbs={state.pbs ?? []}
|
statePbs={state.pbs ?? []}
|
||||||
|
globalTemperatureMonitoringEnabled={temperatureMonitoringEnabled()}
|
||||||
onTestConnection={testNodeConnection}
|
onTestConnection={testNodeConnection}
|
||||||
onEdit={(node) => {
|
onEdit={(node) => {
|
||||||
setEditingNode(node);
|
setEditingNode(node);
|
||||||
|
|
@ -2928,6 +2987,7 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
<PmgNodesTable
|
<PmgNodesTable
|
||||||
nodes={pmgNodes()}
|
nodes={pmgNodes()}
|
||||||
statePmg={state.pmg ?? []}
|
statePmg={state.pmg ?? []}
|
||||||
|
globalTemperatureMonitoringEnabled={temperatureMonitoringEnabled()}
|
||||||
onTestConnection={testNodeConnection}
|
onTestConnection={testNodeConnection}
|
||||||
onEdit={(node) => {
|
onEdit={(node) => {
|
||||||
setEditingNode(nodes().find((n) => n.id === node.id) ?? null);
|
setEditingNode(nodes().find((n) => n.id === node.id) ?? null);
|
||||||
|
|
@ -6572,10 +6632,18 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
nodeType="pve"
|
nodeType="pve"
|
||||||
editingNode={editingNode()?.type === 'pve' ? (editingNode() ?? undefined) : undefined}
|
editingNode={editingNode()?.type === 'pve' ? (editingNode() ?? undefined) : undefined}
|
||||||
securityStatus={securityStatus() ?? undefined}
|
securityStatus={securityStatus() ?? undefined}
|
||||||
temperatureMonitoringEnabled={temperatureMonitoringEnabled()}
|
temperatureMonitoringEnabled={
|
||||||
|
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||||
|
? editingNode()!.temperatureMonitoringEnabled
|
||||||
|
: temperatureMonitoringEnabled()
|
||||||
|
}
|
||||||
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||||
savingTemperatureSetting={savingTemperatureSetting()}
|
savingTemperatureSetting={savingTemperatureSetting()}
|
||||||
onToggleTemperatureMonitoring={handleTemperatureMonitoringChange}
|
onToggleTemperatureMonitoring={
|
||||||
|
editingNode()?.id
|
||||||
|
? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled)
|
||||||
|
: handleTemperatureMonitoringChange
|
||||||
|
}
|
||||||
onSave={async (nodeData) => {
|
onSave={async (nodeData) => {
|
||||||
try {
|
try {
|
||||||
if (editingNode() && editingNode()!.id) {
|
if (editingNode() && editingNode()!.id) {
|
||||||
|
|
@ -6638,6 +6706,18 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
nodeType="pbs"
|
nodeType="pbs"
|
||||||
editingNode={editingNode()?.type === 'pbs' ? (editingNode() ?? undefined) : undefined}
|
editingNode={editingNode()?.type === 'pbs' ? (editingNode() ?? undefined) : undefined}
|
||||||
securityStatus={securityStatus() ?? undefined}
|
securityStatus={securityStatus() ?? undefined}
|
||||||
|
temperatureMonitoringEnabled={
|
||||||
|
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||||
|
? editingNode()!.temperatureMonitoringEnabled
|
||||||
|
: temperatureMonitoringEnabled()
|
||||||
|
}
|
||||||
|
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||||
|
savingTemperatureSetting={savingTemperatureSetting()}
|
||||||
|
onToggleTemperatureMonitoring={
|
||||||
|
editingNode()?.id
|
||||||
|
? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled)
|
||||||
|
: handleTemperatureMonitoringChange
|
||||||
|
}
|
||||||
onSave={async (nodeData) => {
|
onSave={async (nodeData) => {
|
||||||
try {
|
try {
|
||||||
if (editingNode() && editingNode()!.id) {
|
if (editingNode() && editingNode()!.id) {
|
||||||
|
|
@ -6698,6 +6778,18 @@ const Settings: Component<SettingsProps> = (props) => {
|
||||||
nodeType="pmg"
|
nodeType="pmg"
|
||||||
editingNode={editingNode()?.type === 'pmg' ? (editingNode() ?? undefined) : undefined}
|
editingNode={editingNode()?.type === 'pmg' ? (editingNode() ?? undefined) : undefined}
|
||||||
securityStatus={securityStatus() ?? undefined}
|
securityStatus={securityStatus() ?? undefined}
|
||||||
|
temperatureMonitoringEnabled={
|
||||||
|
editingNode()?.temperatureMonitoringEnabled !== undefined
|
||||||
|
? editingNode()!.temperatureMonitoringEnabled
|
||||||
|
: temperatureMonitoringEnabled()
|
||||||
|
}
|
||||||
|
temperatureMonitoringLocked={temperatureMonitoringLocked()}
|
||||||
|
savingTemperatureSetting={savingTemperatureSetting()}
|
||||||
|
onToggleTemperatureMonitoring={
|
||||||
|
editingNode()?.id
|
||||||
|
? (enabled: boolean) => handleNodeTemperatureMonitoringChange(editingNode()!.id, enabled)
|
||||||
|
: handleTemperatureMonitoringChange
|
||||||
|
}
|
||||||
onSave={async (nodeData) => {
|
onSave={async (nodeData) => {
|
||||||
try {
|
try {
|
||||||
if (editingNode() && editingNode()!.id) {
|
if (editingNode() && editingNode()!.id) {
|
||||||
|
|
|
||||||
|
|
@ -527,6 +527,7 @@ const Storage: Component = () => {
|
||||||
{/* Node Selector */}
|
{/* Node Selector */}
|
||||||
<UnifiedNodeSelector
|
<UnifiedNodeSelector
|
||||||
currentTab="storage"
|
currentTab="storage"
|
||||||
|
globalTemperatureMonitoringEnabled={state.temperatureMonitoringEnabled}
|
||||||
onNodeSelect={handleNodeSelect}
|
onNodeSelect={handleNodeSelect}
|
||||||
filteredStorage={sortedStorage()}
|
filteredStorage={sortedStorage()}
|
||||||
searchTerm={searchTerm()}
|
searchTerm={searchTerm()}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface NodeSummaryTableProps {
|
||||||
backupCounts?: Record<string, number>;
|
backupCounts?: Record<string, number>;
|
||||||
currentTab: 'dashboard' | 'storage' | 'backups';
|
currentTab: 'dashboard' | 'storage' | 'backups';
|
||||||
selectedNode: string | null;
|
selectedNode: string | null;
|
||||||
|
globalTemperatureMonitoringEnabled?: boolean;
|
||||||
onNodeClick: (nodeId: string, nodeType: 'pve' | 'pbs') => void;
|
onNodeClick: (nodeId: string, nodeType: 'pve' | 'pbs') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,6 +26,16 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
||||||
const { activeAlerts, state } = useWebSocket();
|
const { activeAlerts, state } = useWebSocket();
|
||||||
const alertsActivation = useAlertsActivation();
|
const alertsActivation = useAlertsActivation();
|
||||||
const alertsEnabled = createMemo(() => alertsActivation.activationState() === 'active');
|
const alertsEnabled = createMemo(() => alertsActivation.activationState() === 'active');
|
||||||
|
|
||||||
|
const isTemperatureMonitoringEnabled = (node: Node): boolean => {
|
||||||
|
const globalEnabled = props.globalTemperatureMonitoringEnabled ?? true;
|
||||||
|
// Check per-node setting first, fall back to global
|
||||||
|
if (node.temperatureMonitoringEnabled !== undefined && node.temperatureMonitoringEnabled !== null) {
|
||||||
|
return node.temperatureMonitoringEnabled;
|
||||||
|
}
|
||||||
|
return globalEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
type CountSortKey = 'vmCount' | 'containerCount' | 'storageCount' | 'diskCount' | 'backupCount';
|
type CountSortKey = 'vmCount' | 'containerCount' | 'storageCount' | 'diskCount' | 'backupCount';
|
||||||
type SortKey =
|
type SortKey =
|
||||||
| 'default'
|
| 'default'
|
||||||
|
|
@ -69,7 +80,12 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAnyTemperatureData = createMemo(() => {
|
const hasAnyTemperatureData = createMemo(() => {
|
||||||
return props.nodes?.some((node) => node.temperature?.available) || false;
|
// Show temperature column if ANY node has monitoring enabled OR has temperature data
|
||||||
|
return (
|
||||||
|
props.nodes?.some(
|
||||||
|
(node) => node.temperature?.available || isTemperatureMonitoringEnabled(node),
|
||||||
|
) || false
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodeKey = (instance?: string, nodeName?: string) => `${instance ?? ''}::${nodeName ?? ''}`;
|
const nodeKey = (instance?: string, nodeName?: string) => `${instance ?? ''}::${nodeName ?? ''}`;
|
||||||
|
|
@ -612,7 +628,8 @@ export const NodeSummaryTable: Component<NodeSummaryTableProps> = (props) => {
|
||||||
online &&
|
online &&
|
||||||
isPVE &&
|
isPVE &&
|
||||||
cpuTemperatureValue !== null &&
|
cpuTemperatureValue !== null &&
|
||||||
(node!.temperature?.hasCPU ?? node!.temperature?.available)
|
(node!.temperature?.hasCPU ?? node!.temperature?.available) &&
|
||||||
|
isTemperatureMonitoringEnabled(node!)
|
||||||
}
|
}
|
||||||
fallback={
|
fallback={
|
||||||
<span class="text-xs text-gray-400 dark:text-gray-500">-</span>
|
<span class="text-xs text-gray-400 dark:text-gray-500">-</span>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import type { Node, VM, Container, Storage } from '@/types/api';
|
||||||
|
|
||||||
interface UnifiedNodeSelectorProps {
|
interface UnifiedNodeSelectorProps {
|
||||||
currentTab: 'dashboard' | 'storage' | 'backups';
|
currentTab: 'dashboard' | 'storage' | 'backups';
|
||||||
|
globalTemperatureMonitoringEnabled?: boolean;
|
||||||
onNodeSelect?: (nodeId: string | null, nodeType: 'pve' | 'pbs' | null) => void;
|
onNodeSelect?: (nodeId: string | null, nodeType: 'pve' | 'pbs' | null) => void;
|
||||||
onNamespaceSelect?: (namespace: string) => void;
|
onNamespaceSelect?: (namespace: string) => void;
|
||||||
nodes?: Node[];
|
nodes?: Node[];
|
||||||
|
|
@ -108,6 +109,7 @@ export const UnifiedNodeSelector: Component<UnifiedNodeSelectorProps> = (props)
|
||||||
backupCounts={backupCounts()}
|
backupCounts={backupCounts()}
|
||||||
currentTab={props.currentTab}
|
currentTab={props.currentTab}
|
||||||
selectedNode={selectedNode()}
|
selectedNode={selectedNode()}
|
||||||
|
globalTemperatureMonitoringEnabled={props.globalTemperatureMonitoringEnabled}
|
||||||
onNodeClick={handleNodeClick}
|
onNodeClick={handleNodeClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export interface State {
|
||||||
activeAlerts: Alert[];
|
activeAlerts: Alert[];
|
||||||
recentlyResolved: ResolvedAlert[];
|
recentlyResolved: ResolvedAlert[];
|
||||||
lastUpdate: string;
|
lastUpdate: string;
|
||||||
|
temperatureMonitoringEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemovedDockerHost {
|
export interface RemovedDockerHost {
|
||||||
|
|
@ -50,6 +51,7 @@ export interface Node {
|
||||||
pveVersion: string;
|
pveVersion: string;
|
||||||
cpuInfo: CPUInfo;
|
cpuInfo: CPUInfo;
|
||||||
temperature?: Temperature; // CPU/NVMe temperatures
|
temperature?: Temperature; // CPU/NVMe temperatures
|
||||||
|
temperatureMonitoringEnabled?: boolean | null; // Per-node temperature monitoring override
|
||||||
lastSeen: string;
|
lastSeen: string;
|
||||||
connectionHealth: string;
|
connectionHealth: string;
|
||||||
isClusterMember?: boolean; // True if part of a cluster
|
isClusterMember?: boolean; // True if part of a cluster
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export interface PVENodeConfig {
|
||||||
monitorStorage: boolean;
|
monitorStorage: boolean;
|
||||||
monitorBackups: boolean;
|
monitorBackups: boolean;
|
||||||
monitorPhysicalDisks: boolean;
|
monitorPhysicalDisks: boolean;
|
||||||
|
temperatureMonitoringEnabled?: boolean | null;
|
||||||
// Cluster information
|
// Cluster information
|
||||||
isCluster?: boolean;
|
isCluster?: boolean;
|
||||||
clusterName?: string;
|
clusterName?: string;
|
||||||
|
|
@ -45,6 +46,7 @@ export interface PBSNodeConfig {
|
||||||
password?: string;
|
password?: string;
|
||||||
fingerprint?: string;
|
fingerprint?: string;
|
||||||
verifySSL: boolean;
|
verifySSL: boolean;
|
||||||
|
temperatureMonitoringEnabled?: boolean | null;
|
||||||
monitorDatastores: boolean;
|
monitorDatastores: boolean;
|
||||||
monitorSyncJobs: boolean;
|
monitorSyncJobs: boolean;
|
||||||
monitorVerifyJobs: boolean;
|
monitorVerifyJobs: boolean;
|
||||||
|
|
@ -64,6 +66,7 @@ export interface PMGNodeConfig {
|
||||||
password?: string;
|
password?: string;
|
||||||
fingerprint?: string;
|
fingerprint?: string;
|
||||||
verifySSL: boolean;
|
verifySSL: boolean;
|
||||||
|
temperatureMonitoringEnabled?: boolean | null;
|
||||||
monitorMailStats: boolean;
|
monitorMailStats: boolean;
|
||||||
monitorQueues: boolean;
|
monitorQueues: boolean;
|
||||||
monitorQuarantine: boolean;
|
monitorQuarantine: boolean;
|
||||||
|
|
|
||||||
|
|
@ -363,29 +363,30 @@ func (h *ConfigHandlers) maybeRefreshClusterInfo(instance *config.PVEInstance) {
|
||||||
|
|
||||||
// NodeConfigRequest represents a request to add/update a node
|
// NodeConfigRequest represents a request to add/update a node
|
||||||
type NodeConfigRequest struct {
|
type NodeConfigRequest struct {
|
||||||
Type string `json:"type"` // "pve", "pbs", or "pmg"
|
Type string `json:"type"` // "pve", "pbs", or "pmg"
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
User string `json:"user,omitempty"`
|
User string `json:"user,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
TokenName string `json:"tokenName,omitempty"`
|
TokenName string `json:"tokenName,omitempty"`
|
||||||
TokenValue string `json:"tokenValue,omitempty"`
|
TokenValue string `json:"tokenValue,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
VerifySSL bool `json:"verifySSL"`
|
VerifySSL *bool `json:"verifySSL,omitempty"`
|
||||||
MonitorVMs bool `json:"monitorVMs,omitempty"` // PVE only
|
MonitorVMs *bool `json:"monitorVMs,omitempty"` // PVE only
|
||||||
MonitorContainers bool `json:"monitorContainers,omitempty"` // PVE only
|
MonitorContainers *bool `json:"monitorContainers,omitempty"` // PVE only
|
||||||
MonitorStorage bool `json:"monitorStorage,omitempty"` // PVE only
|
MonitorStorage *bool `json:"monitorStorage,omitempty"` // PVE only
|
||||||
MonitorBackups bool `json:"monitorBackups,omitempty"` // PVE only
|
MonitorBackups *bool `json:"monitorBackups,omitempty"` // PVE only
|
||||||
MonitorPhysicalDisks *bool `json:"monitorPhysicalDisks,omitempty"` // PVE only (nil = enabled by default)
|
MonitorPhysicalDisks *bool `json:"monitorPhysicalDisks,omitempty"` // PVE only (nil = enabled by default)
|
||||||
MonitorDatastores bool `json:"monitorDatastores,omitempty"` // PBS only
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"` // All types (nil = use global setting)
|
||||||
MonitorSyncJobs bool `json:"monitorSyncJobs,omitempty"` // PBS only
|
MonitorDatastores *bool `json:"monitorDatastores,omitempty"` // PBS only
|
||||||
MonitorVerifyJobs bool `json:"monitorVerifyJobs,omitempty"` // PBS only
|
MonitorSyncJobs *bool `json:"monitorSyncJobs,omitempty"` // PBS only
|
||||||
MonitorPruneJobs bool `json:"monitorPruneJobs,omitempty"` // PBS only
|
MonitorVerifyJobs *bool `json:"monitorVerifyJobs,omitempty"` // PBS only
|
||||||
MonitorGarbageJobs bool `json:"monitorGarbageJobs,omitempty"` // PBS only
|
MonitorPruneJobs *bool `json:"monitorPruneJobs,omitempty"` // PBS only
|
||||||
MonitorMailStats bool `json:"monitorMailStats,omitempty"` // PMG only
|
MonitorGarbageJobs *bool `json:"monitorGarbageJobs,omitempty"` // PBS only
|
||||||
MonitorQueues bool `json:"monitorQueues,omitempty"` // PMG only
|
MonitorMailStats *bool `json:"monitorMailStats,omitempty"` // PMG only
|
||||||
MonitorQuarantine bool `json:"monitorQuarantine,omitempty"` // PMG only
|
MonitorQueues *bool `json:"monitorQueues,omitempty"` // PMG only
|
||||||
MonitorDomainStats bool `json:"monitorDomainStats,omitempty"` // PMG only
|
MonitorQuarantine *bool `json:"monitorQuarantine,omitempty"` // PMG only
|
||||||
|
MonitorDomainStats *bool `json:"monitorDomainStats,omitempty"` // PMG only
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeResponse represents a node in API responses
|
// NodeResponse represents a node in API responses
|
||||||
|
|
@ -403,9 +404,10 @@ type NodeResponse struct {
|
||||||
MonitorVMs bool `json:"monitorVMs,omitempty"`
|
MonitorVMs bool `json:"monitorVMs,omitempty"`
|
||||||
MonitorContainers bool `json:"monitorContainers,omitempty"`
|
MonitorContainers bool `json:"monitorContainers,omitempty"`
|
||||||
MonitorStorage bool `json:"monitorStorage,omitempty"`
|
MonitorStorage bool `json:"monitorStorage,omitempty"`
|
||||||
MonitorBackups bool `json:"monitorBackups,omitempty"`
|
MonitorBackups bool `json:"monitorBackups,omitempty"`
|
||||||
MonitorPhysicalDisks *bool `json:"monitorPhysicalDisks,omitempty"`
|
MonitorPhysicalDisks *bool `json:"monitorPhysicalDisks,omitempty"`
|
||||||
MonitorDatastores bool `json:"monitorDatastores,omitempty"`
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"`
|
||||||
|
MonitorDatastores bool `json:"monitorDatastores,omitempty"`
|
||||||
MonitorSyncJobs bool `json:"monitorSyncJobs,omitempty"`
|
MonitorSyncJobs bool `json:"monitorSyncJobs,omitempty"`
|
||||||
MonitorVerifyJobs bool `json:"monitorVerifyJobs,omitempty"`
|
MonitorVerifyJobs bool `json:"monitorVerifyJobs,omitempty"`
|
||||||
MonitorPruneJobs bool `json:"monitorPruneJobs,omitempty"`
|
MonitorPruneJobs bool `json:"monitorPruneJobs,omitempty"`
|
||||||
|
|
@ -731,25 +733,26 @@ func (h *ConfigHandlers) GetAllNodesForAPI() []NodeResponse {
|
||||||
h.maybeRefreshClusterInfo(&h.config.PVEInstances[i])
|
h.maybeRefreshClusterInfo(&h.config.PVEInstances[i])
|
||||||
pve = h.config.PVEInstances[i]
|
pve = h.config.PVEInstances[i]
|
||||||
node := NodeResponse{
|
node := NodeResponse{
|
||||||
ID: generateNodeID("pve", i),
|
ID: generateNodeID("pve", i),
|
||||||
Type: "pve",
|
Type: "pve",
|
||||||
Name: pve.Name,
|
Name: pve.Name,
|
||||||
Host: pve.Host,
|
Host: pve.Host,
|
||||||
User: pve.User,
|
User: pve.User,
|
||||||
HasPassword: pve.Password != "",
|
HasPassword: pve.Password != "",
|
||||||
TokenName: pve.TokenName,
|
TokenName: pve.TokenName,
|
||||||
HasToken: pve.TokenValue != "",
|
HasToken: pve.TokenValue != "",
|
||||||
Fingerprint: pve.Fingerprint,
|
Fingerprint: pve.Fingerprint,
|
||||||
VerifySSL: pve.VerifySSL,
|
VerifySSL: pve.VerifySSL,
|
||||||
MonitorVMs: pve.MonitorVMs,
|
MonitorVMs: pve.MonitorVMs,
|
||||||
MonitorContainers: pve.MonitorContainers,
|
MonitorContainers: pve.MonitorContainers,
|
||||||
MonitorStorage: pve.MonitorStorage,
|
MonitorStorage: pve.MonitorStorage,
|
||||||
MonitorBackups: pve.MonitorBackups,
|
MonitorBackups: pve.MonitorBackups,
|
||||||
MonitorPhysicalDisks: pve.MonitorPhysicalDisks,
|
MonitorPhysicalDisks: pve.MonitorPhysicalDisks,
|
||||||
Status: h.getNodeStatus("pve", pve.Name),
|
TemperatureMonitoringEnabled: pve.TemperatureMonitoringEnabled,
|
||||||
IsCluster: pve.IsCluster,
|
Status: h.getNodeStatus("pve", pve.Name),
|
||||||
ClusterName: pve.ClusterName,
|
IsCluster: pve.IsCluster,
|
||||||
ClusterEndpoints: pve.ClusterEndpoints,
|
ClusterName: pve.ClusterName,
|
||||||
|
ClusterEndpoints: pve.ClusterEndpoints,
|
||||||
}
|
}
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
}
|
}
|
||||||
|
|
@ -757,22 +760,23 @@ func (h *ConfigHandlers) GetAllNodesForAPI() []NodeResponse {
|
||||||
// Add PBS nodes
|
// Add PBS nodes
|
||||||
for i, pbs := range h.config.PBSInstances {
|
for i, pbs := range h.config.PBSInstances {
|
||||||
node := NodeResponse{
|
node := NodeResponse{
|
||||||
ID: generateNodeID("pbs", i),
|
ID: generateNodeID("pbs", i),
|
||||||
Type: "pbs",
|
Type: "pbs",
|
||||||
Name: pbs.Name,
|
Name: pbs.Name,
|
||||||
Host: pbs.Host,
|
Host: pbs.Host,
|
||||||
User: pbs.User,
|
User: pbs.User,
|
||||||
HasPassword: pbs.Password != "",
|
HasPassword: pbs.Password != "",
|
||||||
TokenName: pbs.TokenName,
|
TokenName: pbs.TokenName,
|
||||||
HasToken: pbs.TokenValue != "",
|
HasToken: pbs.TokenValue != "",
|
||||||
Fingerprint: pbs.Fingerprint,
|
Fingerprint: pbs.Fingerprint,
|
||||||
VerifySSL: pbs.VerifySSL,
|
VerifySSL: pbs.VerifySSL,
|
||||||
MonitorDatastores: pbs.MonitorDatastores,
|
TemperatureMonitoringEnabled: pbs.TemperatureMonitoringEnabled,
|
||||||
MonitorSyncJobs: pbs.MonitorSyncJobs,
|
MonitorDatastores: pbs.MonitorDatastores,
|
||||||
MonitorVerifyJobs: pbs.MonitorVerifyJobs,
|
MonitorSyncJobs: pbs.MonitorSyncJobs,
|
||||||
MonitorPruneJobs: pbs.MonitorPruneJobs,
|
MonitorVerifyJobs: pbs.MonitorVerifyJobs,
|
||||||
MonitorGarbageJobs: pbs.MonitorGarbageJobs,
|
MonitorPruneJobs: pbs.MonitorPruneJobs,
|
||||||
Status: h.getNodeStatus("pbs", pbs.Name),
|
MonitorGarbageJobs: pbs.MonitorGarbageJobs,
|
||||||
|
Status: h.getNodeStatus("pbs", pbs.Name),
|
||||||
}
|
}
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
}
|
}
|
||||||
|
|
@ -785,21 +789,22 @@ func (h *ConfigHandlers) GetAllNodesForAPI() []NodeResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
node := NodeResponse{
|
node := NodeResponse{
|
||||||
ID: generateNodeID("pmg", i),
|
ID: generateNodeID("pmg", i),
|
||||||
Type: "pmg",
|
Type: "pmg",
|
||||||
Name: pmgInst.Name,
|
Name: pmgInst.Name,
|
||||||
Host: pmgInst.Host,
|
Host: pmgInst.Host,
|
||||||
User: pmgInst.User,
|
User: pmgInst.User,
|
||||||
HasPassword: pmgInst.Password != "",
|
HasPassword: pmgInst.Password != "",
|
||||||
TokenName: pmgInst.TokenName,
|
TokenName: pmgInst.TokenName,
|
||||||
HasToken: pmgInst.TokenValue != "",
|
HasToken: pmgInst.TokenValue != "",
|
||||||
Fingerprint: pmgInst.Fingerprint,
|
Fingerprint: pmgInst.Fingerprint,
|
||||||
VerifySSL: pmgInst.VerifySSL,
|
VerifySSL: pmgInst.VerifySSL,
|
||||||
MonitorMailStats: monitorMailStats,
|
TemperatureMonitoringEnabled: pmgInst.TemperatureMonitoringEnabled,
|
||||||
MonitorQueues: pmgInst.MonitorQueues,
|
MonitorMailStats: monitorMailStats,
|
||||||
MonitorQuarantine: pmgInst.MonitorQuarantine,
|
MonitorQueues: pmgInst.MonitorQueues,
|
||||||
MonitorDomainStats: pmgInst.MonitorDomainStats,
|
MonitorQuarantine: pmgInst.MonitorQuarantine,
|
||||||
Status: h.getNodeStatus("pmg", pmgInst.Name),
|
MonitorDomainStats: pmgInst.MonitorDomainStats,
|
||||||
|
Status: h.getNodeStatus("pmg", pmgInst.Name),
|
||||||
}
|
}
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
}
|
}
|
||||||
|
|
@ -1142,7 +1147,11 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
|
||||||
strings.Contains(req.Name, "concurrent-")
|
strings.Contains(req.Name, "concurrent-")
|
||||||
|
|
||||||
if !skipClusterDetection {
|
if !skipClusterDetection {
|
||||||
clientConfig := config.CreateProxmoxConfigFromFields(host, req.User, req.Password, req.TokenName, req.TokenValue, req.Fingerprint, req.VerifySSL)
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
|
clientConfig := config.CreateProxmoxConfigFromFields(host, req.User, req.Password, req.TokenName, req.TokenValue, req.Fingerprint, verifySSL)
|
||||||
isCluster, clusterName, clusterEndpoints = detectPVECluster(clientConfig, req.Name)
|
isCluster, clusterName, clusterEndpoints = detectPVECluster(clientConfig, req.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1153,23 +1162,46 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
|
||||||
Msg("Detected Proxmox cluster, auto-discovering all nodes")
|
Msg("Detected Proxmox cluster, auto-discovering all nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use sensible defaults for boolean fields if not provided
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
|
monitorVMs := true // Default to true
|
||||||
|
if req.MonitorVMs != nil {
|
||||||
|
monitorVMs = *req.MonitorVMs
|
||||||
|
}
|
||||||
|
monitorContainers := true // Default to true
|
||||||
|
if req.MonitorContainers != nil {
|
||||||
|
monitorContainers = *req.MonitorContainers
|
||||||
|
}
|
||||||
|
monitorStorage := true // Default to true
|
||||||
|
if req.MonitorStorage != nil {
|
||||||
|
monitorStorage = *req.MonitorStorage
|
||||||
|
}
|
||||||
|
monitorBackups := true // Default to true
|
||||||
|
if req.MonitorBackups != nil {
|
||||||
|
monitorBackups = *req.MonitorBackups
|
||||||
|
}
|
||||||
|
|
||||||
pve := config.PVEInstance{
|
pve := config.PVEInstance{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Host: host, // Use normalized host
|
Host: host, // Use normalized host
|
||||||
User: req.User,
|
User: req.User,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: req.TokenName,
|
TokenName: req.TokenName,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
MonitorVMs: req.MonitorVMs,
|
MonitorVMs: monitorVMs,
|
||||||
MonitorContainers: req.MonitorContainers,
|
MonitorContainers: monitorContainers,
|
||||||
MonitorStorage: req.MonitorStorage,
|
MonitorStorage: monitorStorage,
|
||||||
MonitorBackups: req.MonitorBackups,
|
MonitorBackups: monitorBackups,
|
||||||
MonitorPhysicalDisks: req.MonitorPhysicalDisks,
|
MonitorPhysicalDisks: req.MonitorPhysicalDisks,
|
||||||
IsCluster: isCluster,
|
TemperatureMonitoringEnabled: req.TemperatureMonitoringEnabled,
|
||||||
ClusterName: clusterName,
|
IsCluster: isCluster,
|
||||||
ClusterEndpoints: clusterEndpoints,
|
ClusterName: clusterName,
|
||||||
|
ClusterEndpoints: clusterEndpoints,
|
||||||
}
|
}
|
||||||
h.config.PVEInstances = append(h.config.PVEInstances, pve)
|
h.config.PVEInstances = append(h.config.PVEInstances, pve)
|
||||||
|
|
||||||
|
|
@ -1221,21 +1253,52 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use sensible defaults for boolean fields if not provided
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
|
monitorBackups := true // Default to true for PBS
|
||||||
|
if req.MonitorBackups != nil {
|
||||||
|
monitorBackups = *req.MonitorBackups
|
||||||
|
}
|
||||||
|
monitorDatastores := false
|
||||||
|
if req.MonitorDatastores != nil {
|
||||||
|
monitorDatastores = *req.MonitorDatastores
|
||||||
|
}
|
||||||
|
monitorSyncJobs := false
|
||||||
|
if req.MonitorSyncJobs != nil {
|
||||||
|
monitorSyncJobs = *req.MonitorSyncJobs
|
||||||
|
}
|
||||||
|
monitorVerifyJobs := false
|
||||||
|
if req.MonitorVerifyJobs != nil {
|
||||||
|
monitorVerifyJobs = *req.MonitorVerifyJobs
|
||||||
|
}
|
||||||
|
monitorPruneJobs := false
|
||||||
|
if req.MonitorPruneJobs != nil {
|
||||||
|
monitorPruneJobs = *req.MonitorPruneJobs
|
||||||
|
}
|
||||||
|
monitorGarbageJobs := false
|
||||||
|
if req.MonitorGarbageJobs != nil {
|
||||||
|
monitorGarbageJobs = *req.MonitorGarbageJobs
|
||||||
|
}
|
||||||
|
|
||||||
pbs := config.PBSInstance{
|
pbs := config.PBSInstance{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Host: host,
|
Host: host,
|
||||||
User: pbsUser,
|
User: pbsUser,
|
||||||
Password: pbsPassword,
|
Password: pbsPassword,
|
||||||
TokenName: pbsTokenName,
|
TokenName: pbsTokenName,
|
||||||
TokenValue: pbsTokenValue,
|
TokenValue: pbsTokenValue,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
MonitorBackups: true, // Enable by default for PBS
|
MonitorBackups: monitorBackups,
|
||||||
MonitorDatastores: req.MonitorDatastores,
|
MonitorDatastores: monitorDatastores,
|
||||||
MonitorSyncJobs: req.MonitorSyncJobs,
|
MonitorSyncJobs: monitorSyncJobs,
|
||||||
MonitorVerifyJobs: req.MonitorVerifyJobs,
|
MonitorVerifyJobs: monitorVerifyJobs,
|
||||||
MonitorPruneJobs: req.MonitorPruneJobs,
|
MonitorPruneJobs: monitorPruneJobs,
|
||||||
MonitorGarbageJobs: req.MonitorGarbageJobs,
|
MonitorGarbageJobs: monitorGarbageJobs,
|
||||||
|
TemperatureMonitoringEnabled: req.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
h.config.PBSInstances = append(h.config.PBSInstances, pbs)
|
h.config.PBSInstances = append(h.config.PBSInstances, pbs)
|
||||||
} else if req.Type == "pmg" {
|
} else if req.Type == "pmg" {
|
||||||
|
|
@ -1269,24 +1332,53 @@ func (h *ConfigHandlers) HandleAddNode(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
monitorMailStats := req.MonitorMailStats
|
// Use sensible defaults for boolean fields if not provided
|
||||||
if !req.MonitorMailStats && !req.MonitorQueues && !req.MonitorQuarantine && !req.MonitorDomainStats {
|
verifySSL := false
|
||||||
monitorMailStats = true
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any monitoring flags are explicitly set to true
|
||||||
|
anyMonitoringEnabled := (req.MonitorMailStats != nil && *req.MonitorMailStats) ||
|
||||||
|
(req.MonitorQueues != nil && *req.MonitorQueues) ||
|
||||||
|
(req.MonitorQuarantine != nil && *req.MonitorQuarantine) ||
|
||||||
|
(req.MonitorDomainStats != nil && *req.MonitorDomainStats)
|
||||||
|
|
||||||
|
// Default MonitorMailStats to true if no monitoring is explicitly enabled
|
||||||
|
monitorMailStats := true // Default to true
|
||||||
|
if req.MonitorMailStats != nil {
|
||||||
|
monitorMailStats = *req.MonitorMailStats
|
||||||
|
} else if anyMonitoringEnabled {
|
||||||
|
monitorMailStats = false // Don't default to true if other monitoring is enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorQueues := false
|
||||||
|
if req.MonitorQueues != nil {
|
||||||
|
monitorQueues = *req.MonitorQueues
|
||||||
|
}
|
||||||
|
monitorQuarantine := false
|
||||||
|
if req.MonitorQuarantine != nil {
|
||||||
|
monitorQuarantine = *req.MonitorQuarantine
|
||||||
|
}
|
||||||
|
monitorDomainStats := false
|
||||||
|
if req.MonitorDomainStats != nil {
|
||||||
|
monitorDomainStats = *req.MonitorDomainStats
|
||||||
}
|
}
|
||||||
|
|
||||||
pmgInstance := config.PMGInstance{
|
pmgInstance := config.PMGInstance{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Host: host,
|
Host: host,
|
||||||
User: pmgUser,
|
User: pmgUser,
|
||||||
Password: pmgPassword,
|
Password: pmgPassword,
|
||||||
TokenName: pmgTokenName,
|
TokenName: pmgTokenName,
|
||||||
TokenValue: pmgTokenValue,
|
TokenValue: pmgTokenValue,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
MonitorMailStats: monitorMailStats,
|
MonitorMailStats: monitorMailStats,
|
||||||
MonitorQueues: req.MonitorQueues,
|
MonitorQueues: monitorQueues,
|
||||||
MonitorQuarantine: req.MonitorQuarantine,
|
MonitorQuarantine: monitorQuarantine,
|
||||||
MonitorDomainStats: req.MonitorDomainStats,
|
MonitorDomainStats: monitorDomainStats,
|
||||||
|
TemperatureMonitoringEnabled: req.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
h.config.PMGInstances = append(h.config.PMGInstances, pmgInstance)
|
h.config.PMGInstances = append(h.config.PMGInstances, pmgInstance)
|
||||||
}
|
}
|
||||||
|
|
@ -1412,13 +1504,17 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
||||||
authUser = normalizePVEUser(authUser)
|
authUser = normalizePVEUser(authUser)
|
||||||
req.User = authUser
|
req.User = authUser
|
||||||
}
|
}
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := proxmox.ClientConfig{
|
clientConfig := proxmox.ClientConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
User: authUser,
|
User: authUser,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: req.TokenName, // Pass the full token ID
|
TokenName: req.TokenName, // Pass the full token ID
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1500,13 +1596,17 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
||||||
pbsUser = pbsUser + "@pbs" // Default to @pbs realm if not specified
|
pbsUser = pbsUser + "@pbs" // Default to @pbs realm if not specified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := pbs.ClientConfig{
|
clientConfig := pbs.ClientConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
User: pbsUser,
|
User: pbsUser,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: pbsTokenName,
|
TokenName: pbsTokenName,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1549,7 +1649,11 @@ func (h *ConfigHandlers) HandleTestConnection(w http.ResponseWriter, r *http.Req
|
||||||
host = host + ":8006"
|
host = host + ":8006"
|
||||||
}
|
}
|
||||||
|
|
||||||
clientConfig := config.CreatePMGConfigFromFields(host, req.User, req.Password, req.TokenName, req.TokenValue, req.Fingerprint, req.VerifySSL)
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
|
clientConfig := config.CreatePMGConfigFromFields(host, req.User, req.Password, req.TokenName, req.TokenValue, req.Fingerprint, verifySSL)
|
||||||
|
|
||||||
if req.Password != "" && req.TokenName == "" && req.TokenValue == "" {
|
if req.Password != "" && req.TokenName == "" && req.TokenValue == "" {
|
||||||
if clientConfig.User != "" && !strings.Contains(clientConfig.User, "@") {
|
if clientConfig.User != "" && !strings.Contains(clientConfig.User, "@") {
|
||||||
|
|
@ -1632,6 +1736,12 @@ func (h *ConfigHandlers) HandleUpdateNode(w http.ResponseWriter, r *http.Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug: Log the received temperatureMonitoringEnabled value
|
||||||
|
log.Info().
|
||||||
|
Str("nodeID", nodeID).
|
||||||
|
Interface("temperatureMonitoringEnabled", req.TemperatureMonitoringEnabled).
|
||||||
|
Msg("Received node update request")
|
||||||
|
|
||||||
// Parse node ID
|
// Parse node ID
|
||||||
parts := strings.Split(nodeID, "-")
|
parts := strings.Split(nodeID, "-")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
|
|
@ -1706,12 +1816,27 @@ func (h *ConfigHandlers) HandleUpdateNode(w http.ResponseWriter, r *http.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
pve.Fingerprint = req.Fingerprint
|
pve.Fingerprint = req.Fingerprint
|
||||||
pve.VerifySSL = req.VerifySSL
|
if req.VerifySSL != nil {
|
||||||
pve.MonitorVMs = req.MonitorVMs
|
pve.VerifySSL = *req.VerifySSL
|
||||||
pve.MonitorContainers = req.MonitorContainers
|
}
|
||||||
pve.MonitorStorage = req.MonitorStorage
|
if req.MonitorVMs != nil {
|
||||||
pve.MonitorBackups = req.MonitorBackups
|
pve.MonitorVMs = *req.MonitorVMs
|
||||||
pve.MonitorPhysicalDisks = req.MonitorPhysicalDisks
|
}
|
||||||
|
if req.MonitorContainers != nil {
|
||||||
|
pve.MonitorContainers = *req.MonitorContainers
|
||||||
|
}
|
||||||
|
if req.MonitorStorage != nil {
|
||||||
|
pve.MonitorStorage = *req.MonitorStorage
|
||||||
|
}
|
||||||
|
if req.MonitorBackups != nil {
|
||||||
|
pve.MonitorBackups = *req.MonitorBackups
|
||||||
|
}
|
||||||
|
if req.MonitorPhysicalDisks != nil {
|
||||||
|
pve.MonitorPhysicalDisks = req.MonitorPhysicalDisks
|
||||||
|
}
|
||||||
|
if req.TemperatureMonitoringEnabled != nil {
|
||||||
|
pve.TemperatureMonitoringEnabled = req.TemperatureMonitoringEnabled
|
||||||
|
}
|
||||||
} else if nodeType == "pbs" && index < len(h.config.PBSInstances) {
|
} else if nodeType == "pbs" && index < len(h.config.PBSInstances) {
|
||||||
pbs := &h.config.PBSInstances[index]
|
pbs := &h.config.PBSInstances[index]
|
||||||
pbs.Name = req.Name
|
pbs.Name = req.Name
|
||||||
|
|
@ -1775,13 +1900,32 @@ func (h *ConfigHandlers) HandleUpdateNode(w http.ResponseWriter, r *http.Request
|
||||||
// else: No authentication changes - preserve existing auth fields
|
// else: No authentication changes - preserve existing auth fields
|
||||||
|
|
||||||
pbs.Fingerprint = req.Fingerprint
|
pbs.Fingerprint = req.Fingerprint
|
||||||
pbs.VerifySSL = req.VerifySSL
|
if req.VerifySSL != nil {
|
||||||
pbs.MonitorBackups = true // Enable by default for PBS
|
pbs.VerifySSL = *req.VerifySSL
|
||||||
pbs.MonitorDatastores = req.MonitorDatastores
|
}
|
||||||
pbs.MonitorSyncJobs = req.MonitorSyncJobs
|
if req.MonitorBackups != nil {
|
||||||
pbs.MonitorVerifyJobs = req.MonitorVerifyJobs
|
pbs.MonitorBackups = *req.MonitorBackups
|
||||||
pbs.MonitorPruneJobs = req.MonitorPruneJobs
|
} else {
|
||||||
pbs.MonitorGarbageJobs = req.MonitorGarbageJobs
|
pbs.MonitorBackups = true // Enable by default for PBS
|
||||||
|
}
|
||||||
|
if req.MonitorDatastores != nil {
|
||||||
|
pbs.MonitorDatastores = *req.MonitorDatastores
|
||||||
|
}
|
||||||
|
if req.MonitorSyncJobs != nil {
|
||||||
|
pbs.MonitorSyncJobs = *req.MonitorSyncJobs
|
||||||
|
}
|
||||||
|
if req.MonitorVerifyJobs != nil {
|
||||||
|
pbs.MonitorVerifyJobs = *req.MonitorVerifyJobs
|
||||||
|
}
|
||||||
|
if req.MonitorPruneJobs != nil {
|
||||||
|
pbs.MonitorPruneJobs = *req.MonitorPruneJobs
|
||||||
|
}
|
||||||
|
if req.MonitorGarbageJobs != nil {
|
||||||
|
pbs.MonitorGarbageJobs = *req.MonitorGarbageJobs
|
||||||
|
}
|
||||||
|
if req.TemperatureMonitoringEnabled != nil {
|
||||||
|
pbs.TemperatureMonitoringEnabled = req.TemperatureMonitoringEnabled
|
||||||
|
}
|
||||||
} else if nodeType == "pmg" && index < len(h.config.PMGInstances) {
|
} else if nodeType == "pmg" && index < len(h.config.PMGInstances) {
|
||||||
pmgInst := &h.config.PMGInstances[index]
|
pmgInst := &h.config.PMGInstances[index]
|
||||||
pmgInst.Name = req.Name
|
pmgInst.Name = req.Name
|
||||||
|
|
@ -1838,15 +1982,30 @@ func (h *ConfigHandlers) HandleUpdateNode(w http.ResponseWriter, r *http.Request
|
||||||
// else: No authentication changes - preserve existing auth fields
|
// else: No authentication changes - preserve existing auth fields
|
||||||
|
|
||||||
pmgInst.Fingerprint = req.Fingerprint
|
pmgInst.Fingerprint = req.Fingerprint
|
||||||
pmgInst.VerifySSL = req.VerifySSL
|
if req.VerifySSL != nil {
|
||||||
monitorMailStats := req.MonitorMailStats
|
pmgInst.VerifySSL = *req.VerifySSL
|
||||||
if !req.MonitorMailStats && !req.MonitorQueues && !req.MonitorQuarantine && !req.MonitorDomainStats {
|
}
|
||||||
monitorMailStats = true
|
// Special logic for MonitorMailStats: default to true if all monitor flags are false/unset
|
||||||
|
if req.MonitorMailStats != nil {
|
||||||
|
pmgInst.MonitorMailStats = *req.MonitorMailStats
|
||||||
|
} else if (req.MonitorMailStats == nil || !*req.MonitorMailStats) &&
|
||||||
|
(req.MonitorQueues == nil || !*req.MonitorQueues) &&
|
||||||
|
(req.MonitorQuarantine == nil || !*req.MonitorQuarantine) &&
|
||||||
|
(req.MonitorDomainStats == nil || !*req.MonitorDomainStats) {
|
||||||
|
pmgInst.MonitorMailStats = true
|
||||||
|
}
|
||||||
|
if req.MonitorQueues != nil {
|
||||||
|
pmgInst.MonitorQueues = *req.MonitorQueues
|
||||||
|
}
|
||||||
|
if req.MonitorQuarantine != nil {
|
||||||
|
pmgInst.MonitorQuarantine = *req.MonitorQuarantine
|
||||||
|
}
|
||||||
|
if req.MonitorDomainStats != nil {
|
||||||
|
pmgInst.MonitorDomainStats = *req.MonitorDomainStats
|
||||||
|
}
|
||||||
|
if req.TemperatureMonitoringEnabled != nil {
|
||||||
|
pmgInst.TemperatureMonitoringEnabled = req.TemperatureMonitoringEnabled
|
||||||
}
|
}
|
||||||
pmgInst.MonitorMailStats = monitorMailStats
|
|
||||||
pmgInst.MonitorQueues = req.MonitorQueues
|
|
||||||
pmgInst.MonitorQuarantine = req.MonitorQuarantine
|
|
||||||
pmgInst.MonitorDomainStats = req.MonitorDomainStats
|
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "Node not found", http.StatusNotFound)
|
http.Error(w, "Node not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
|
@ -2220,13 +2379,17 @@ func (h *ConfigHandlers) HandleTestNodeConfig(w http.ResponseWriter, r *http.Req
|
||||||
authUser = normalizePVEUser(authUser)
|
authUser = normalizePVEUser(authUser)
|
||||||
req.User = authUser
|
req.User = authUser
|
||||||
}
|
}
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := proxmox.ClientConfig{
|
clientConfig := proxmox.ClientConfig{
|
||||||
Host: req.Host,
|
Host: req.Host,
|
||||||
User: authUser,
|
User: authUser,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: req.TokenName,
|
TokenName: req.TokenName,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
}
|
}
|
||||||
client, err := proxmox.NewClient(clientConfig)
|
client, err := proxmox.NewClient(clientConfig)
|
||||||
|
|
@ -2257,13 +2420,17 @@ func (h *ConfigHandlers) HandleTestNodeConfig(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
} else if req.Type == "pbs" {
|
} else if req.Type == "pbs" {
|
||||||
// Create a temporary client to test connection
|
// Create a temporary client to test connection
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := pbs.ClientConfig{
|
clientConfig := pbs.ClientConfig{
|
||||||
Host: req.Host,
|
Host: req.Host,
|
||||||
User: req.User,
|
User: req.User,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: req.TokenName,
|
TokenName: req.TokenName,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
}
|
}
|
||||||
client, err := pbs.NewClient(clientConfig)
|
client, err := pbs.NewClient(clientConfig)
|
||||||
|
|
@ -2293,13 +2460,17 @@ func (h *ConfigHandlers) HandleTestNodeConfig(w http.ResponseWriter, r *http.Req
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if req.Type == "pmg" {
|
} else if req.Type == "pmg" {
|
||||||
|
verifySSL := false
|
||||||
|
if req.VerifySSL != nil {
|
||||||
|
verifySSL = *req.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := pmg.ClientConfig{
|
clientConfig := pmg.ClientConfig{
|
||||||
Host: req.Host,
|
Host: req.Host,
|
||||||
User: req.User,
|
User: req.User,
|
||||||
Password: req.Password,
|
Password: req.Password,
|
||||||
TokenName: req.TokenName,
|
TokenName: req.TokenName,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: req.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
Fingerprint: req.Fingerprint,
|
Fingerprint: req.Fingerprint,
|
||||||
}
|
}
|
||||||
client, err := pmg.NewClient(clientConfig)
|
client, err := pmg.NewClient(clientConfig)
|
||||||
|
|
@ -5148,22 +5319,24 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a node configuration
|
// Create a node configuration
|
||||||
|
boolFalse := false
|
||||||
|
boolTrue := true
|
||||||
nodeConfig := NodeConfigRequest{
|
nodeConfig := NodeConfigRequest{
|
||||||
Type: req.Type,
|
Type: req.Type,
|
||||||
Name: req.ServerName,
|
Name: req.ServerName,
|
||||||
Host: host, // Use normalized host
|
Host: host, // Use normalized host
|
||||||
TokenName: req.TokenID,
|
TokenName: req.TokenID,
|
||||||
TokenValue: req.TokenValue,
|
TokenValue: req.TokenValue,
|
||||||
VerifySSL: false, // Default to not verifying SSL for auto-registration
|
VerifySSL: &boolFalse, // Default to not verifying SSL for auto-registration
|
||||||
MonitorVMs: true,
|
MonitorVMs: &boolTrue,
|
||||||
MonitorContainers: true,
|
MonitorContainers: &boolTrue,
|
||||||
MonitorStorage: true,
|
MonitorStorage: &boolTrue,
|
||||||
MonitorBackups: true,
|
MonitorBackups: &boolTrue,
|
||||||
MonitorDatastores: true,
|
MonitorDatastores: &boolTrue,
|
||||||
MonitorSyncJobs: true,
|
MonitorSyncJobs: &boolTrue,
|
||||||
MonitorVerifyJobs: true,
|
MonitorVerifyJobs: &boolTrue,
|
||||||
MonitorPruneJobs: true,
|
MonitorPruneJobs: &boolTrue,
|
||||||
MonitorGarbageJobs: false,
|
MonitorGarbageJobs: &boolFalse,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a node with this host already exists
|
// Check if a node with this host already exists
|
||||||
|
|
@ -5235,25 +5408,46 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
||||||
// Add new node
|
// Add new node
|
||||||
if req.Type == "pve" {
|
if req.Type == "pve" {
|
||||||
// Check for cluster detection using helper
|
// Check for cluster detection using helper
|
||||||
|
verifySSL := false
|
||||||
|
if nodeConfig.VerifySSL != nil {
|
||||||
|
verifySSL = *nodeConfig.VerifySSL
|
||||||
|
}
|
||||||
clientConfig := proxmox.ClientConfig{
|
clientConfig := proxmox.ClientConfig{
|
||||||
Host: nodeConfig.Host,
|
Host: nodeConfig.Host,
|
||||||
TokenName: nodeConfig.TokenName,
|
TokenName: nodeConfig.TokenName,
|
||||||
TokenValue: nodeConfig.TokenValue,
|
TokenValue: nodeConfig.TokenValue,
|
||||||
VerifySSL: nodeConfig.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
}
|
}
|
||||||
|
|
||||||
isCluster, clusterName, clusterEndpoints := detectPVECluster(clientConfig, nodeConfig.Name)
|
isCluster, clusterName, clusterEndpoints := detectPVECluster(clientConfig, nodeConfig.Name)
|
||||||
|
|
||||||
|
monitorVMs := true
|
||||||
|
if nodeConfig.MonitorVMs != nil {
|
||||||
|
monitorVMs = *nodeConfig.MonitorVMs
|
||||||
|
}
|
||||||
|
monitorContainers := true
|
||||||
|
if nodeConfig.MonitorContainers != nil {
|
||||||
|
monitorContainers = *nodeConfig.MonitorContainers
|
||||||
|
}
|
||||||
|
monitorStorage := true
|
||||||
|
if nodeConfig.MonitorStorage != nil {
|
||||||
|
monitorStorage = *nodeConfig.MonitorStorage
|
||||||
|
}
|
||||||
|
monitorBackups := true
|
||||||
|
if nodeConfig.MonitorBackups != nil {
|
||||||
|
monitorBackups = *nodeConfig.MonitorBackups
|
||||||
|
}
|
||||||
|
|
||||||
newInstance := config.PVEInstance{
|
newInstance := config.PVEInstance{
|
||||||
Name: nodeConfig.Name,
|
Name: nodeConfig.Name,
|
||||||
Host: nodeConfig.Host,
|
Host: nodeConfig.Host,
|
||||||
TokenName: nodeConfig.TokenName,
|
TokenName: nodeConfig.TokenName,
|
||||||
TokenValue: nodeConfig.TokenValue,
|
TokenValue: nodeConfig.TokenValue,
|
||||||
VerifySSL: nodeConfig.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
MonitorVMs: nodeConfig.MonitorVMs,
|
MonitorVMs: monitorVMs,
|
||||||
MonitorContainers: nodeConfig.MonitorContainers,
|
MonitorContainers: monitorContainers,
|
||||||
MonitorStorage: nodeConfig.MonitorStorage,
|
MonitorStorage: monitorStorage,
|
||||||
MonitorBackups: nodeConfig.MonitorBackups,
|
MonitorBackups: monitorBackups,
|
||||||
IsCluster: isCluster,
|
IsCluster: isCluster,
|
||||||
ClusterName: clusterName,
|
ClusterName: clusterName,
|
||||||
ClusterEndpoints: clusterEndpoints,
|
ClusterEndpoints: clusterEndpoints,
|
||||||
|
|
@ -5267,18 +5461,43 @@ func (h *ConfigHandlers) HandleAutoRegister(w http.ResponseWriter, r *http.Reque
|
||||||
Msg("Added Proxmox cluster via auto-registration")
|
Msg("Added Proxmox cluster via auto-registration")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
verifySSL := false
|
||||||
|
if nodeConfig.VerifySSL != nil {
|
||||||
|
verifySSL = *nodeConfig.VerifySSL
|
||||||
|
}
|
||||||
|
monitorDatastores := false
|
||||||
|
if nodeConfig.MonitorDatastores != nil {
|
||||||
|
monitorDatastores = *nodeConfig.MonitorDatastores
|
||||||
|
}
|
||||||
|
monitorSyncJobs := false
|
||||||
|
if nodeConfig.MonitorSyncJobs != nil {
|
||||||
|
monitorSyncJobs = *nodeConfig.MonitorSyncJobs
|
||||||
|
}
|
||||||
|
monitorVerifyJobs := false
|
||||||
|
if nodeConfig.MonitorVerifyJobs != nil {
|
||||||
|
monitorVerifyJobs = *nodeConfig.MonitorVerifyJobs
|
||||||
|
}
|
||||||
|
monitorPruneJobs := false
|
||||||
|
if nodeConfig.MonitorPruneJobs != nil {
|
||||||
|
monitorPruneJobs = *nodeConfig.MonitorPruneJobs
|
||||||
|
}
|
||||||
|
monitorGarbageJobs := false
|
||||||
|
if nodeConfig.MonitorGarbageJobs != nil {
|
||||||
|
monitorGarbageJobs = *nodeConfig.MonitorGarbageJobs
|
||||||
|
}
|
||||||
|
|
||||||
newInstance := config.PBSInstance{
|
newInstance := config.PBSInstance{
|
||||||
Name: nodeConfig.Name,
|
Name: nodeConfig.Name,
|
||||||
Host: nodeConfig.Host,
|
Host: nodeConfig.Host,
|
||||||
TokenName: nodeConfig.TokenName,
|
TokenName: nodeConfig.TokenName,
|
||||||
TokenValue: nodeConfig.TokenValue,
|
TokenValue: nodeConfig.TokenValue,
|
||||||
VerifySSL: nodeConfig.VerifySSL,
|
VerifySSL: verifySSL,
|
||||||
MonitorBackups: true, // Enable by default for PBS
|
MonitorBackups: true, // Enable by default for PBS
|
||||||
MonitorDatastores: nodeConfig.MonitorDatastores,
|
MonitorDatastores: monitorDatastores,
|
||||||
MonitorSyncJobs: nodeConfig.MonitorSyncJobs,
|
MonitorSyncJobs: monitorSyncJobs,
|
||||||
MonitorVerifyJobs: nodeConfig.MonitorVerifyJobs,
|
MonitorVerifyJobs: monitorVerifyJobs,
|
||||||
MonitorPruneJobs: nodeConfig.MonitorPruneJobs,
|
MonitorPruneJobs: monitorPruneJobs,
|
||||||
MonitorGarbageJobs: nodeConfig.MonitorGarbageJobs,
|
MonitorGarbageJobs: monitorGarbageJobs,
|
||||||
}
|
}
|
||||||
h.config.PBSInstances = append(h.config.PBSInstances, newInstance)
|
h.config.PBSInstances = append(h.config.PBSInstances, newInstance)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,17 +81,18 @@ type ConfigResponse struct {
|
||||||
|
|
||||||
// NodeConfig represents a node configuration
|
// NodeConfig represents a node configuration
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
HasPassword bool `json:"hasPassword"`
|
HasPassword bool `json:"hasPassword"`
|
||||||
HasToken bool `json:"hasToken"`
|
HasToken bool `json:"hasToken"`
|
||||||
SkipTLS bool `json:"skipTLS,omitempty"`
|
SkipTLS bool `json:"skipTLS,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"`
|
||||||
Metadata map[string]string `json:"metadata,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsConfig represents application settings
|
// SettingsConfig represents application settings
|
||||||
|
|
|
||||||
|
|
@ -385,20 +385,21 @@ func splitAndTrim(value string) []string {
|
||||||
|
|
||||||
// PVEInstance represents a Proxmox VE connection
|
// PVEInstance represents a Proxmox VE connection
|
||||||
type PVEInstance struct {
|
type PVEInstance struct {
|
||||||
Name string
|
Name string
|
||||||
Host string // Primary endpoint (user-provided)
|
Host string // Primary endpoint (user-provided)
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
TokenName string
|
TokenName string
|
||||||
TokenValue string
|
TokenValue string
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
VerifySSL bool
|
VerifySSL bool
|
||||||
MonitorVMs bool
|
MonitorVMs bool
|
||||||
MonitorContainers bool
|
MonitorContainers bool
|
||||||
MonitorStorage bool
|
MonitorStorage bool
|
||||||
MonitorBackups bool
|
MonitorBackups bool
|
||||||
MonitorPhysicalDisks *bool // Monitor physical disks (nil = enabled by default, can be explicitly disabled)
|
MonitorPhysicalDisks *bool // Monitor physical disks (nil = enabled by default, can be explicitly disabled)
|
||||||
PhysicalDiskPollingMinutes int // How often to poll physical disks (0 = use default)
|
PhysicalDiskPollingMinutes int // How often to poll physical disks (0 = use default)
|
||||||
|
TemperatureMonitoringEnabled *bool // Monitor temperature via SSH (nil = use global setting, true/false = override)
|
||||||
|
|
||||||
// Cluster support
|
// Cluster support
|
||||||
IsCluster bool // True if this is a cluster
|
IsCluster bool // True if this is a cluster
|
||||||
|
|
@ -418,20 +419,21 @@ type ClusterEndpoint struct {
|
||||||
|
|
||||||
// PBSInstance represents a Proxmox Backup Server connection
|
// PBSInstance represents a Proxmox Backup Server connection
|
||||||
type PBSInstance struct {
|
type PBSInstance struct {
|
||||||
Name string
|
Name string
|
||||||
Host string
|
Host string
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
TokenName string
|
TokenName string
|
||||||
TokenValue string
|
TokenValue string
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
VerifySSL bool
|
VerifySSL bool
|
||||||
MonitorBackups bool
|
MonitorBackups bool
|
||||||
MonitorDatastores bool
|
MonitorDatastores bool
|
||||||
MonitorSyncJobs bool
|
MonitorSyncJobs bool
|
||||||
MonitorVerifyJobs bool
|
MonitorVerifyJobs bool
|
||||||
MonitorPruneJobs bool
|
MonitorPruneJobs bool
|
||||||
MonitorGarbageJobs bool
|
MonitorGarbageJobs bool
|
||||||
|
TemperatureMonitoringEnabled *bool // Monitor temperature via SSH (nil = use global setting, true/false = override)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PMGInstance represents a Proxmox Mail Gateway connection
|
// PMGInstance represents a Proxmox Mail Gateway connection
|
||||||
|
|
@ -445,10 +447,11 @@ type PMGInstance struct {
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
VerifySSL bool
|
VerifySSL bool
|
||||||
|
|
||||||
MonitorMailStats bool
|
MonitorMailStats bool
|
||||||
MonitorQueues bool
|
MonitorQueues bool
|
||||||
MonitorQuarantine bool
|
MonitorQuarantine bool
|
||||||
MonitorDomainStats bool
|
MonitorDomainStats bool
|
||||||
|
TemperatureMonitoringEnabled *bool // Monitor temperature via SSH (nil = use global setting, true/false = override)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global persistence instance for saving
|
// Global persistence instance for saving
|
||||||
|
|
|
||||||
|
|
@ -13,27 +13,28 @@ func (s *State) ToFrontend() StateFrontend {
|
||||||
// ToFrontend converts a Node to NodeFrontend
|
// ToFrontend converts a Node to NodeFrontend
|
||||||
func (n Node) ToFrontend() NodeFrontend {
|
func (n Node) ToFrontend() NodeFrontend {
|
||||||
nf := NodeFrontend{
|
nf := NodeFrontend{
|
||||||
ID: n.ID,
|
ID: n.ID,
|
||||||
Node: n.Name,
|
Node: n.Name,
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
DisplayName: n.DisplayName,
|
DisplayName: n.DisplayName,
|
||||||
Instance: n.Instance,
|
Instance: n.Instance,
|
||||||
Host: n.Host,
|
Host: n.Host,
|
||||||
Status: n.Status,
|
Status: n.Status,
|
||||||
Type: n.Type,
|
Type: n.Type,
|
||||||
CPU: n.CPU,
|
CPU: n.CPU,
|
||||||
Mem: n.Memory.Used,
|
Mem: n.Memory.Used,
|
||||||
MaxMem: n.Memory.Total,
|
MaxMem: n.Memory.Total,
|
||||||
MaxDisk: n.Disk.Total,
|
MaxDisk: n.Disk.Total,
|
||||||
Uptime: n.Uptime,
|
Uptime: n.Uptime,
|
||||||
LoadAverage: n.LoadAverage,
|
LoadAverage: n.LoadAverage,
|
||||||
KernelVersion: n.KernelVersion,
|
KernelVersion: n.KernelVersion,
|
||||||
PVEVersion: n.PVEVersion,
|
PVEVersion: n.PVEVersion,
|
||||||
CPUInfo: n.CPUInfo,
|
CPUInfo: n.CPUInfo,
|
||||||
LastSeen: n.LastSeen.Unix() * 1000,
|
LastSeen: n.LastSeen.Unix() * 1000,
|
||||||
ConnectionHealth: n.ConnectionHealth,
|
ConnectionHealth: n.ConnectionHealth,
|
||||||
IsClusterMember: n.IsClusterMember,
|
IsClusterMember: n.IsClusterMember,
|
||||||
ClusterName: n.ClusterName,
|
ClusterName: n.ClusterName,
|
||||||
|
TemperatureMonitoringEnabled: n.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include full Memory object if it has data
|
// Include full Memory object if it has data
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,11 @@ type State struct {
|
||||||
PVEBackups PVEBackups `json:"pveBackups"`
|
PVEBackups PVEBackups `json:"pveBackups"`
|
||||||
Performance Performance `json:"performance"`
|
Performance Performance `json:"performance"`
|
||||||
ConnectionHealth map[string]bool `json:"connectionHealth"`
|
ConnectionHealth map[string]bool `json:"connectionHealth"`
|
||||||
Stats Stats `json:"stats"`
|
Stats Stats `json:"stats"`
|
||||||
ActiveAlerts []Alert `json:"activeAlerts"`
|
ActiveAlerts []Alert `json:"activeAlerts"`
|
||||||
RecentlyResolved []ResolvedAlert `json:"recentlyResolved"`
|
RecentlyResolved []ResolvedAlert `json:"recentlyResolved"`
|
||||||
LastUpdate time.Time `json:"lastUpdate"`
|
LastUpdate time.Time `json:"lastUpdate"`
|
||||||
|
TemperatureMonitoringEnabled bool `json:"temperatureMonitoringEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert represents an active alert (simplified for State)
|
// Alert represents an active alert (simplified for State)
|
||||||
|
|
@ -76,9 +77,10 @@ type Node struct {
|
||||||
KernelVersion string `json:"kernelVersion"`
|
KernelVersion string `json:"kernelVersion"`
|
||||||
PVEVersion string `json:"pveVersion"`
|
PVEVersion string `json:"pveVersion"`
|
||||||
CPUInfo CPUInfo `json:"cpuInfo"`
|
CPUInfo CPUInfo `json:"cpuInfo"`
|
||||||
Temperature *Temperature `json:"temperature,omitempty"` // CPU/NVMe temperatures
|
Temperature *Temperature `json:"temperature,omitempty"` // CPU/NVMe temperatures
|
||||||
LastSeen time.Time `json:"lastSeen"`
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"` // Per-node temperature monitoring override
|
||||||
ConnectionHealth string `json:"connectionHealth"`
|
LastSeen time.Time `json:"lastSeen"`
|
||||||
|
ConnectionHealth string `json:"connectionHealth"`
|
||||||
IsClusterMember bool `json:"isClusterMember"` // True if part of a cluster
|
IsClusterMember bool `json:"isClusterMember"` // True if part of a cluster
|
||||||
ClusterName string `json:"clusterName"` // Name of cluster (empty if standalone)
|
ClusterName string `json:"clusterName"` // Name of cluster (empty if standalone)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,10 @@ type NodeFrontend struct {
|
||||||
CPUInfo CPUInfo `json:"cpuInfo"`
|
CPUInfo CPUInfo `json:"cpuInfo"`
|
||||||
Temperature *Temperature `json:"temperature,omitempty"` // CPU/NVMe temperatures
|
Temperature *Temperature `json:"temperature,omitempty"` // CPU/NVMe temperatures
|
||||||
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
||||||
ConnectionHealth string `json:"connectionHealth"`
|
ConnectionHealth string `json:"connectionHealth"`
|
||||||
IsClusterMember bool `json:"isClusterMember,omitempty"`
|
IsClusterMember bool `json:"isClusterMember,omitempty"`
|
||||||
ClusterName string `json:"clusterName,omitempty"`
|
ClusterName string `json:"clusterName,omitempty"`
|
||||||
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"` // Per-node temperature monitoring override
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMFrontend represents a VM with frontend-friendly field names
|
// VMFrontend represents a VM with frontend-friendly field names
|
||||||
|
|
@ -438,7 +439,8 @@ type StateFrontend struct {
|
||||||
Metrics map[string]any `json:"metrics"` // Empty object for now
|
Metrics map[string]any `json:"metrics"` // Empty object for now
|
||||||
PVEBackups PVEBackups `json:"pveBackups"` // Keep as is
|
PVEBackups PVEBackups `json:"pveBackups"` // Keep as is
|
||||||
Performance map[string]any `json:"performance"` // Empty object for now
|
Performance map[string]any `json:"performance"` // Empty object for now
|
||||||
ConnectionHealth map[string]bool `json:"connectionHealth"` // Keep as is
|
ConnectionHealth map[string]bool `json:"connectionHealth"` // Keep as is
|
||||||
Stats map[string]any `json:"stats"` // Empty object for now
|
Stats map[string]any `json:"stats"` // Empty object for now
|
||||||
LastUpdate int64 `json:"lastUpdate"` // Unix timestamp
|
LastUpdate int64 `json:"lastUpdate"` // Unix timestamp
|
||||||
|
TemperatureMonitoringEnabled bool `json:"temperatureMonitoringEnabled"` // Global temperature monitoring setting
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,11 @@ type StateSnapshot struct {
|
||||||
PVEBackups PVEBackups `json:"pveBackups"`
|
PVEBackups PVEBackups `json:"pveBackups"`
|
||||||
Performance Performance `json:"performance"`
|
Performance Performance `json:"performance"`
|
||||||
ConnectionHealth map[string]bool `json:"connectionHealth"`
|
ConnectionHealth map[string]bool `json:"connectionHealth"`
|
||||||
Stats Stats `json:"stats"`
|
Stats Stats `json:"stats"`
|
||||||
ActiveAlerts []Alert `json:"activeAlerts"`
|
ActiveAlerts []Alert `json:"activeAlerts"`
|
||||||
RecentlyResolved []ResolvedAlert `json:"recentlyResolved"`
|
RecentlyResolved []ResolvedAlert `json:"recentlyResolved"`
|
||||||
LastUpdate time.Time `json:"lastUpdate"`
|
LastUpdate time.Time `json:"lastUpdate"`
|
||||||
|
TemperatureMonitoringEnabled bool `json:"temperatureMonitoringEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSnapshot returns a snapshot of the current state without mutex
|
// GetSnapshot returns a snapshot of the current state without mutex
|
||||||
|
|
@ -67,10 +68,11 @@ func (s *State) GetSnapshot() StateSnapshot {
|
||||||
PVEBackups: pveBackups,
|
PVEBackups: pveBackups,
|
||||||
Performance: s.Performance,
|
Performance: s.Performance,
|
||||||
ConnectionHealth: make(map[string]bool),
|
ConnectionHealth: make(map[string]bool),
|
||||||
Stats: s.Stats,
|
Stats: s.Stats,
|
||||||
ActiveAlerts: append([]Alert{}, s.ActiveAlerts...),
|
ActiveAlerts: append([]Alert{}, s.ActiveAlerts...),
|
||||||
RecentlyResolved: append([]ResolvedAlert{}, s.RecentlyResolved...),
|
RecentlyResolved: append([]ResolvedAlert{}, s.RecentlyResolved...),
|
||||||
LastUpdate: s.LastUpdate,
|
LastUpdate: s.LastUpdate,
|
||||||
|
TemperatureMonitoringEnabled: s.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy map
|
// Copy map
|
||||||
|
|
@ -153,8 +155,9 @@ func (s StateSnapshot) ToFrontend() StateFrontend {
|
||||||
Metrics: make(map[string]any),
|
Metrics: make(map[string]any),
|
||||||
PVEBackups: s.PVEBackups,
|
PVEBackups: s.PVEBackups,
|
||||||
Performance: make(map[string]any),
|
Performance: make(map[string]any),
|
||||||
ConnectionHealth: s.ConnectionHealth,
|
ConnectionHealth: s.ConnectionHealth,
|
||||||
Stats: make(map[string]any),
|
Stats: make(map[string]any),
|
||||||
LastUpdate: s.LastUpdate.Unix() * 1000, // JavaScript timestamp
|
LastUpdate: s.LastUpdate.Unix() * 1000, // JavaScript timestamp
|
||||||
|
TemperatureMonitoringEnabled: s.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3224,6 +3224,9 @@ func New(cfg *config.Config) (*Monitor, error) {
|
||||||
m.executor = newRealExecutor(m)
|
m.executor = newRealExecutor(m)
|
||||||
m.buildInstanceInfoCache(cfg)
|
m.buildInstanceInfoCache(cfg)
|
||||||
|
|
||||||
|
// Initialize state with config values
|
||||||
|
m.state.TemperatureMonitoringEnabled = cfg.TemperatureMonitoringEnabled
|
||||||
|
|
||||||
if m.pollMetrics != nil {
|
if m.pollMetrics != nil {
|
||||||
m.pollMetrics.ResetQueueDepth(0)
|
m.pollMetrics.ResetQueueDepth(0)
|
||||||
}
|
}
|
||||||
|
|
@ -4914,8 +4917,9 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie
|
||||||
LoadAverage: []float64{},
|
LoadAverage: []float64{},
|
||||||
LastSeen: time.Now(),
|
LastSeen: time.Now(),
|
||||||
ConnectionHealth: connectionHealthStr, // Use the determined health status
|
ConnectionHealth: connectionHealthStr, // Use the determined health status
|
||||||
IsClusterMember: instanceCfg.IsCluster,
|
IsClusterMember: instanceCfg.IsCluster,
|
||||||
ClusterName: instanceCfg.ClusterName,
|
ClusterName: instanceCfg.ClusterName,
|
||||||
|
TemperatureMonitoringEnabled: instanceCfg.TemperatureMonitoringEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeSnapshotRaw := NodeMemoryRaw{
|
nodeSnapshotRaw := NodeMemoryRaw{
|
||||||
|
|
@ -5282,8 +5286,13 @@ func (m *Monitor) pollPVEInstance(ctx context.Context, instanceName string, clie
|
||||||
})
|
})
|
||||||
|
|
||||||
// Collect temperature data via SSH (non-blocking, best effort)
|
// Collect temperature data via SSH (non-blocking, best effort)
|
||||||
// Only attempt for online nodes
|
// Only attempt for online nodes when temperature monitoring is enabled
|
||||||
if node.Status == "online" && m.tempCollector != nil {
|
// Check per-node setting first, fall back to global setting
|
||||||
|
tempMonitoringEnabled := m.config.TemperatureMonitoringEnabled
|
||||||
|
if instanceCfg.TemperatureMonitoringEnabled != nil {
|
||||||
|
tempMonitoringEnabled = *instanceCfg.TemperatureMonitoringEnabled
|
||||||
|
}
|
||||||
|
if node.Status == "online" && m.tempCollector != nil && tempMonitoringEnabled {
|
||||||
tempCtx, tempCancel := context.WithTimeout(ctx, 30*time.Second) // Increased to accommodate SSH operations via proxy
|
tempCtx, tempCancel := context.WithTimeout(ctx, 30*time.Second) // Increased to accommodate SSH operations via proxy
|
||||||
|
|
||||||
// Determine SSH hostname to use (most robust approach):
|
// Determine SSH hostname to use (most robust approach):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue