mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-07 14:14:44 +08:00
Support Debian 13.
#29 and fix useage chart data display problem, storage on database instead of localstorage.
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
assignIPv6,
|
||||
APIResponse,
|
||||
Container,
|
||||
ContainerMetricPoint as ContainerMetricSample,
|
||||
ContainerUsage,
|
||||
createSubUser,
|
||||
createContainerSnapshot,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
deleteContainerSnapshot,
|
||||
deletePortMapping,
|
||||
getContainer,
|
||||
getContainerHistory,
|
||||
getContainerSnapshots,
|
||||
getContainerUsage,
|
||||
getHostInfo,
|
||||
@@ -209,39 +211,19 @@ export default function ContainerDetail() {
|
||||
}
|
||||
}, [containerIdentifier, container?.snapshot_limit])
|
||||
|
||||
const appendUsagePoint = useCallback((nextUsage: ContainerUsage, currentContainer: Container | null) => {
|
||||
if (!containerIdentifier || !currentContainer) return
|
||||
|
||||
const memoryTotalBytes = nextUsage.memory_total_bytes && nextUsage.memory_total_bytes > 0
|
||||
? nextUsage.memory_total_bytes
|
||||
: currentContainer.ram_mb * 1024 * 1024
|
||||
const memoryPct = memoryTotalBytes > 0
|
||||
? (nextUsage.memory_usage_bytes / memoryTotalBytes) * 100
|
||||
: 0
|
||||
const networkRx = nextUsage.network_rx_bps || 0
|
||||
const networkTx = nextUsage.network_tx_bps || 0
|
||||
const diskRead = nextUsage.disk_read_bps || 0
|
||||
const diskWrite = nextUsage.disk_write_bps || 0
|
||||
|
||||
const point: MetricPoint = {
|
||||
ts: Date.now(),
|
||||
cpu: clamp((nextUsage.cpu_usage_pct || 0) / (currentContainer.vcpu || 1)),
|
||||
memory: clamp(memoryPct),
|
||||
network: networkRx + networkTx,
|
||||
networkRx,
|
||||
networkTx,
|
||||
diskIO: diskRead + diskWrite,
|
||||
diskRead,
|
||||
diskWrite,
|
||||
const fetchMetricHistory = useCallback(async () => {
|
||||
if (!containerIdentifier) return
|
||||
try {
|
||||
const res = await getContainerHistory(containerIdentifier)
|
||||
const points = (res.data.data || []).map(normalizeContainerMetricSample)
|
||||
if (points.length > 0) {
|
||||
setHistory(points)
|
||||
localStorage.setItem(historyKey(container?.uuid || containerIdentifier), JSON.stringify(points))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch metric history:', err)
|
||||
}
|
||||
|
||||
setHistory((prev) => {
|
||||
const cutoff = Date.now() - statsRanges['1w']
|
||||
const next = [...prev.filter((item) => item.ts >= cutoff), point]
|
||||
localStorage.setItem(historyKey(currentContainer.uuid || containerIdentifier), JSON.stringify(next))
|
||||
return next
|
||||
})
|
||||
}, [containerIdentifier])
|
||||
}, [containerIdentifier, container?.uuid])
|
||||
|
||||
const fetchUsage = useCallback(async () => {
|
||||
if (!containerIdentifier) return
|
||||
@@ -249,12 +231,11 @@ export default function ContainerDetail() {
|
||||
const res = await getContainerUsage(containerIdentifier)
|
||||
if (res.data.data) {
|
||||
setUsage(res.data.data)
|
||||
appendUsagePoint(res.data.data, container)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch usage:', err)
|
||||
}
|
||||
}, [containerIdentifier, container, appendUsagePoint])
|
||||
}, [containerIdentifier])
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerIdentifier) return
|
||||
@@ -296,6 +277,12 @@ export default function ContainerDetail() {
|
||||
return () => window.clearInterval(timer)
|
||||
}, [fetchUsage])
|
||||
|
||||
useEffect(() => {
|
||||
fetchMetricHistory()
|
||||
const timer = window.setInterval(fetchMetricHistory, 30000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [fetchMetricHistory])
|
||||
|
||||
useEffect(() => {
|
||||
if (showSnapshots) fetchSnapshots()
|
||||
}, [showSnapshots, fetchSnapshots])
|
||||
@@ -2492,6 +2479,20 @@ function readHistory(containerName: string): MetricPoint[] {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeContainerMetricSample(point: ContainerMetricSample): MetricPoint {
|
||||
return {
|
||||
ts: point.ts,
|
||||
cpu: clamp(point.cpu),
|
||||
memory: clamp(point.memory),
|
||||
network: point.network || 0,
|
||||
networkRx: point.network_rx || 0,
|
||||
networkTx: point.network_tx || 0,
|
||||
diskIO: point.disk_io || 0,
|
||||
diskRead: point.disk_read || 0,
|
||||
diskWrite: point.disk_write || 0,
|
||||
}
|
||||
}
|
||||
|
||||
function historyKey(containerName: string) {
|
||||
return `clicd_container_metric_history:${containerName}`
|
||||
}
|
||||
|
||||
@@ -967,6 +967,7 @@ function getTemplateName(id: string) {
|
||||
const map: Record<string, string> = {
|
||||
'ubuntu-noble': 'Ubuntu 24.04',
|
||||
'ubuntu-jammy': 'Ubuntu 22.04',
|
||||
'debian-trixie': 'Debian 13',
|
||||
'debian-bookworm': 'Debian 12',
|
||||
'debian-bullseye': 'Debian 11',
|
||||
'alpine-3.21': 'Alpine 3.21',
|
||||
@@ -976,6 +977,8 @@ function getTemplateName(id: string) {
|
||||
'rockylinux-10': 'Rocky 10',
|
||||
'kvm-ubuntu-noble': 'Ubuntu 24.04',
|
||||
'kvm-ubuntu-jammy': 'Ubuntu 22.04',
|
||||
'kvm-debian-trixie': 'Debian 13',
|
||||
'kvm-debian-trixie-xfce': 'Debian 13 XFCE',
|
||||
'kvm-debian-bookworm': 'Debian 12',
|
||||
'kvm-debian-bullseye': 'Debian 11',
|
||||
'kvm-rockylinux-9': 'Rocky 9',
|
||||
|
||||
@@ -7,7 +7,7 @@ import ResourceStatsPanel, {
|
||||
StatsRangeKey,
|
||||
statsRanges,
|
||||
} from '../components/ResourceStatsPanel'
|
||||
import { DashboardStats, getDashboard, getHostInfo, HostInfo } from '../services/api'
|
||||
import { DashboardStats, getDashboard, getHostHistory, getHostInfo, HostInfo, HostMetricPoint as HostMetricSample } from '../services/api'
|
||||
|
||||
type HostMetricPoint = {
|
||||
ts: number
|
||||
@@ -30,6 +30,19 @@ export default function Dashboard() {
|
||||
const [range, setRange] = useState<StatsRangeKey>('30m')
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const fetchHistory = useCallback(async () => {
|
||||
try {
|
||||
const res = await getHostHistory()
|
||||
const points = (res.data.data || []).map(normalizeHostMetricSample)
|
||||
if (points.length > 0) {
|
||||
setHistory(points)
|
||||
localStorage.setItem(hostHistoryKey, JSON.stringify(points))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
const [dashRes, hostRes] = await Promise.all([getDashboard(), getHostInfo()])
|
||||
@@ -37,7 +50,6 @@ export default function Dashboard() {
|
||||
if (hostRes.data.data) {
|
||||
const nextHost = hostRes.data.data
|
||||
setHost(nextHost)
|
||||
appendHostPoint(nextHost, setHistory)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@@ -47,10 +59,15 @@ export default function Dashboard() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchHistory()
|
||||
fetchData()
|
||||
const interval = window.setInterval(fetchData, 5000)
|
||||
return () => window.clearInterval(interval)
|
||||
}, [fetchData])
|
||||
const historyInterval = window.setInterval(fetchHistory, 30000)
|
||||
return () => {
|
||||
window.clearInterval(interval)
|
||||
window.clearInterval(historyInterval)
|
||||
}
|
||||
}, [fetchData, fetchHistory])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -172,31 +189,6 @@ function SummaryCard({
|
||||
)
|
||||
}
|
||||
|
||||
function appendHostPoint(host: HostInfo, setHistory: (updater: (prev: HostMetricPoint[]) => HostMetricPoint[]) => void) {
|
||||
const networkRx = host.network.rx_bps || 0
|
||||
const networkTx = host.network.tx_bps || 0
|
||||
const diskRead = host.disk_io.read_bps || 0
|
||||
const diskWrite = host.disk_io.write_bps || 0
|
||||
const point: HostMetricPoint = {
|
||||
ts: Date.now(),
|
||||
cpu: clamp(host.cpu.usage_pct),
|
||||
memory: host.ram.total_mb > 0 ? clamp((host.ram.used_mb / host.ram.total_mb) * 100) : 0,
|
||||
network: networkRx + networkTx,
|
||||
networkRx,
|
||||
networkTx,
|
||||
diskIO: diskRead + diskWrite,
|
||||
diskRead,
|
||||
diskWrite,
|
||||
}
|
||||
|
||||
setHistory((prev) => {
|
||||
const cutoff = Date.now() - statsRanges['1w']
|
||||
const next = [...prev.filter((item) => item.ts >= cutoff), point]
|
||||
localStorage.setItem(hostHistoryKey, JSON.stringify(next))
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function readHostHistory(): HostMetricPoint[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(hostHistoryKey)
|
||||
@@ -221,6 +213,20 @@ function toChartPoints(history: HostMetricPoint[], key: keyof Omit<HostMetricPoi
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeHostMetricSample(point: HostMetricSample): HostMetricPoint {
|
||||
return {
|
||||
ts: point.ts,
|
||||
cpu: clamp(point.cpu),
|
||||
memory: clamp(point.memory),
|
||||
network: point.network || 0,
|
||||
networkRx: point.network_rx || 0,
|
||||
networkTx: point.network_tx || 0,
|
||||
diskIO: point.disk_io || 0,
|
||||
diskRead: point.disk_read || 0,
|
||||
diskWrite: point.disk_write || 0,
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(value: number) {
|
||||
if (!Number.isFinite(value)) return 0
|
||||
return Math.max(0, Math.min(value, 100))
|
||||
|
||||
@@ -247,6 +247,19 @@ export interface HostInfo {
|
||||
}
|
||||
}
|
||||
|
||||
export interface HostMetricPoint {
|
||||
ts: number
|
||||
cpu: number
|
||||
memory: number
|
||||
network: number
|
||||
network_rx: number
|
||||
network_tx: number
|
||||
disk_io: number
|
||||
disk_read: number
|
||||
disk_write: number
|
||||
disk_usage_pct: number
|
||||
}
|
||||
|
||||
export interface HostProbeReport {
|
||||
generated_at: string
|
||||
hostname: string
|
||||
@@ -355,6 +368,18 @@ export interface ContainerUsage {
|
||||
guest_metrics?: boolean
|
||||
}
|
||||
|
||||
export interface ContainerMetricPoint {
|
||||
ts: number
|
||||
cpu: number
|
||||
memory: number
|
||||
network: number
|
||||
network_rx: number
|
||||
network_tx: number
|
||||
disk_io: number
|
||||
disk_read: number
|
||||
disk_write: number
|
||||
}
|
||||
|
||||
export interface APIResponse<T = unknown> {
|
||||
success: boolean
|
||||
message?: string
|
||||
@@ -477,6 +502,9 @@ export const resetSSHPassword = (id: ContainerIdentifier, password?: string) =>
|
||||
export const getContainerUsage = (id: ContainerIdentifier) =>
|
||||
api.get<APIResponse<ContainerUsage>>(`/containers/${id}/usage`)
|
||||
|
||||
export const getContainerHistory = (id: ContainerIdentifier) =>
|
||||
api.get<APIResponse<ContainerMetricPoint[]>>(`/containers/${id}/history`)
|
||||
|
||||
export interface TrafficInfo {
|
||||
total_used_bytes: number
|
||||
rx_used_bytes: number
|
||||
@@ -669,6 +697,9 @@ export const getDashboard = () =>
|
||||
export const getHostInfo = () =>
|
||||
api.get<APIResponse<HostInfo>>('/host-info')
|
||||
|
||||
export const getHostHistory = () =>
|
||||
api.get<APIResponse<HostMetricPoint[]>>('/host-history')
|
||||
|
||||
export const getHostReport = () =>
|
||||
api.get<APIResponse<HostProbeReport>>('/host-report')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user