guardia-messenger/app/(tabs)/capacity_plan.tsx

81 lines
3.5 KiB
TypeScript

import React, { useState, useCallback } from 'react'
import { View, Text, ScrollView, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native'
import { useFocusEffect } from 'expo-router'
import { COLORS } from '../../constants/Config'
import client from '../../services/api'
type Period = '30d' | '60d' | '90d'
export default function CapacityPlanScreen() {
const [data, setData] = useState<any>(null)
const [period, setPeriod] = useState<Period>('30d')
const [loading, setLoading] = useState(false)
const load = useCallback(async () => {
setLoading(true)
try { const r = await client.get(`/api/capacity/predictions?days=${period.replace('d', '')}`); setData(r.data) }
catch { setData(null) }
finally { setLoading(false) }
}, [period])
useFocusEffect(useCallback(() => { load() }, [load]))
const predictions = data?.predictions ?? data?.items ?? []
return (
<ScrollView style={s.container} refreshControl={<RefreshControl refreshing={loading} onRefresh={load} />}>
<View style={s.tabs}>
{(['30d','60d','90d'] as Period[]).map(p => (
<TouchableOpacity key={p} style={[s.tab, period===p && s.tabActive]} onPress={() => setPeriod(p)}>
<Text style={[s.tabText, period===p && s.tabTextActive]}>{p}</Text>
</TouchableOpacity>
))}
</View>
{predictions.map((item: any, i: number) => {
const util = item.predicted_utilization ?? item.cpu_avg ?? 0
const color = util >= 90 ? COLORS.danger : util >= 70 ? COLORS.warning : COLORS.success
return (
<View key={i} style={s.card}>
<View style={s.row}>
<View style={{ flex: 1 }}>
<Text style={s.serverName}>{item.server_name ?? item.name ?? 'N/A'}</Text>
<Text style={s.meta}>{item.inst_name ?? ''} · {item.resource_type ?? 'CPU'}</Text>
</View>
<Text style={[s.pct, { color }]}>{util}%</Text>
</View>
<View style={s.barBg}>
<View style={[s.barFill, { width: `${Math.min(100, util)}%`, backgroundColor: color }]} />
</View>
{item.recommendation && (
<Text style={s.rec}>AI : {item.recommendation}</Text>
)}
</View>
)
})}
{predictions.length === 0 && !loading && (
<Text style={s.empty}> .</Text>
)}
</ScrollView>
)
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: COLORS.bg },
tabs: { flexDirection: 'row', backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border },
tab: { flex: 1, paddingVertical: 12, alignItems: 'center' },
tabActive: { borderBottomWidth: 2, borderBottomColor: COLORS.accent },
tabText: { fontSize: 13, color: COLORS.muted },
tabTextActive:{ color: COLORS.accent, fontWeight: '700' },
card: { backgroundColor: '#fff', margin: 8, marginBottom: 0, borderRadius: 10, padding: 14, elevation: 1 },
row: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
serverName: { fontSize: 14, fontWeight: '700', color: COLORS.text },
meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 },
pct: { fontSize: 22, fontWeight: '800' },
barBg: { height: 6, backgroundColor: COLORS.border, borderRadius: 3, marginBottom: 6 },
barFill: { height: 6, borderRadius: 3 },
rec: { fontSize: 12, color: COLORS.blue, fontStyle: 'italic' },
empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 },
})