tftsr-devops_investigation/src/pages/Proxmox/CephPage.tsx
Shaun Arman c5b97f8648
All checks were successful
Test / frontend-tests (pull_request) Successful in 1m36s
Test / frontend-typecheck (pull_request) Successful in 1m46s
PR Review Automation / review (pull_request) Successful in 5m8s
Test / rust-fmt-check (pull_request) Successful in 12m30s
Test / rust-clippy (pull_request) Successful in 14m10s
Test / rust-tests (pull_request) Successful in 16m35s
fix(proxmox): restore broken client retrieval across all commands
Half-completed refactor left 68 Tauri command functions with orphaned
.ok_or_else() chains after the old clusters.get() pattern was removed
without inserting the replacement helper call. Also fixed two bugs in the
new get_proxmox_client_for_cluster helper: undeclared `clusters` variable
in the early-return check, and client_arc going out of scope before return.

fix(ai): enforce system-message-first ordering for strict LLM providers

Qwen3.5-122b (and other models via LiteLLM) reject requests where system
messages appear after user/assistant turns. Moved tool-calling format
and iteration-budget system messages to before history is appended.
Changed mid-loop iteration warning and forced-stop instruction from
system role to user role so they can safely appear mid-conversation.

fix(proxmox): Remotes actions menu and connect/disconnect behaviour

Replaced the non-functional "..." toast placeholder with a proper
ActionsMenu dropdown (Edit / Test Connection / Delete). Removed inline
emoji buttons folded into the menu. Connect now calls getProxmoxCluster
as a live connection test and reflects real status; disconnect marks the
remote disconnected locally. Remote status now maps correctly from the
backend ClusterInfoWithHealth.connected field instead of hardcoding
'connected' for every entry.

fix(proxmox): Ceph page no longer shows HEALTH_OK on non-Ceph clusters

Page now fetches real health data on mount. If getCephHealth fails the
page renders an informational notice rather than fake HEALTH_OK. When
Ceph is present, pools and OSDs are loaded and displayed live.
2026-06-19 22:13:48 -05:00

179 lines
5.2 KiB
TypeScript

import React, { useState, useEffect, useCallback } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
import { Button } from '@/components/ui/index';
import { RefreshCw } from 'lucide-react';
import { PoolList, OSDList, CephHealthWidget, MonitorList } from '@/components/Proxmox';
import { listProxmoxClusters, listCephPools, listCephOsd, getCephHealth } from '@/lib/proxmoxClient';
import { toast } from 'sonner';
export function ProxmoxCephPage() {
const [clusterId, setClusterId] = useState<string>('');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [health, setHealth] = useState<any>(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [pools, setPools] = useState<any[]>([]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [osds, setOsds] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isCephEnabled, setIsCephEnabled] = useState<boolean | null>(null);
const loadData = useCallback(async (cId: string) => {
if (!cId) return;
setLoading(true);
setError(null);
// Check Ceph availability by fetching health first
let cephAvailable = false;
try {
const h = await getCephHealth(cId);
setHealth(h);
cephAvailable = true;
} catch {
setIsCephEnabled(false);
setLoading(false);
return;
}
if (cephAvailable) {
setIsCephEnabled(true);
const [poolsResult, osdsResult] = await Promise.allSettled([
listCephPools(cId),
listCephOsd(cId),
]);
if (poolsResult.status === 'fulfilled') {
setPools(poolsResult.value);
} else {
toast.error('Failed to load Ceph pools');
}
if (osdsResult.status === 'fulfilled') {
setOsds(osdsResult.value);
} else {
toast.error('Failed to load Ceph OSDs');
}
}
setLoading(false);
}, []);
useEffect(() => {
listProxmoxClusters()
.then((cls) => {
if (cls.length > 0) {
setClusterId(cls[0].id);
loadData(cls[0].id);
} else {
setIsCephEnabled(false);
}
})
.catch((err) => {
console.error('Failed to load clusters:', err);
setError('Failed to load clusters');
setIsCephEnabled(false);
});
}, [loadData]);
const handleRefresh = () => {
if (clusterId) loadData(clusterId);
};
if (isCephEnabled === false) {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Ceph Storage</h1>
<p className="text-muted-foreground">Manage Ceph clusters and storage</p>
</div>
</div>
<Card>
<CardContent className="py-12 text-center text-muted-foreground">
{error ? (
<p>{error}</p>
) : (
<>
<p className="text-base font-medium">Ceph is not configured on this cluster</p>
<p className="text-sm mt-1">
Ceph storage requires a dedicated Ceph cluster deployment on the Proxmox nodes.
</p>
</>
)}
</CardContent>
</Card>
</div>
);
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">Ceph Storage</h1>
<p className="text-muted-foreground">Manage Ceph clusters and storage</p>
</div>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={loading}>
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
Refresh
</Button>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
<Card>
<CardHeader>
<CardTitle>Ceph Health</CardTitle>
</CardHeader>
<CardContent>
{health ? (
<CephHealthWidget health={health} />
) : (
<p className="text-sm text-muted-foreground">Loading health data...</p>
)}
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>Pools</CardTitle>
</CardHeader>
<CardContent>
<PoolList
pools={pools}
onRefresh={handleRefresh}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>OSDs</CardTitle>
</CardHeader>
<CardContent>
<OSDList
osds={osds}
onRefresh={handleRefresh}
/>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Monitors</CardTitle>
</CardHeader>
<CardContent>
<MonitorList
monitors={[]}
onRefresh={handleRefresh}
/>
</CardContent>
</Card>
</div>
);
}