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

114 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch } from 'react-native';
import { ITSM_BASE } from '../../services/api';
interface OpsTask { id: string; name: string; type: string; status: string; autonomous: boolean; next_run?: string; last_result?: string }
const TASKS: OpsTask[] = [
{ id: 'T01', name: '야간 로그 정리', type: 'maintenance', status: 'scheduled', autonomous: true, next_run: '오늘 02:00' },
{ id: 'T02', name: '주간 보안 스캔', type: 'security', status: 'completed', autonomous: true, last_result: '취약점 0개 발견' },
{ id: 'T03', name: '스냅샷 백업', type: 'backup', status: 'running', autonomous: true },
{ id: 'T04', name: '용량 예측 보고서', type: 'analytics', status: 'pending', autonomous: false },
];
export default function AutonomousOpsScreen() {
const [tasks, setTasks] = useState(TASKS);
const [autonomyLevel, setAutonomyLevel] = useState(72);
const [fullAuto, setFullAuto] = useState(false);
const [opsLog, setOpsLog] = useState([
{ time: '02:14', msg: '로그 아카이브 완료 — 3.2GB 확보', type: 'success' },
{ time: '03:00', msg: 'DB 스냅샷 완료 (app-db-01)', type: 'success' },
{ time: '07:30', msg: 'CPU 이상 감지 — 자동 재시작 실행', type: 'warning' },
]);
const statusColor = (s: string) => ({ running: '#00A0C8', completed: '#44bb44', scheduled: '#ffbb00', pending: '#888', failed: '#ff4444' })[s] || '#888';
const statusIcon = (s: string) => ({ running: '⟳', completed: '✅', scheduled: '⏱', pending: '⏸', failed: '❌' })[s] || '?';
const toggleTask = async (id: string) => {
const task = tasks.find(t => t.id === id);
if (!task) return;
try {
await fetch(`${ITSM_BASE}/api/ops-automation/tasks/${id}/toggle`, { method: 'POST' });
setTasks(prev => prev.map(t => t.id === id ? { ...t, autonomous: !t.autonomous } : t));
} catch {
setTasks(prev => prev.map(t => t.id === id ? { ...t, autonomous: !t.autonomous } : t));
}
};
return (
<ScrollView style={s.container}>
<Text style={s.title}> </Text>
<Text style={s.sub}>GUARDiA가 </Text>
<View style={s.autonomyCard}>
<Text style={s.autonomyLabel}></Text>
<Text style={s.autonomyVal}>{autonomyLevel}%</Text>
<View style={s.barBg}>
<View style={[s.barFill, { width: `${autonomyLevel}%` }]} />
</View>
<Text style={s.autonomyDesc}>목표: 85% (2027 Q1)</Text>
</View>
<View style={s.card}>
<View style={s.row}>
<Text style={s.label}> </Text>
<Switch value={fullAuto} onValueChange={setFullAuto} trackColor={{ true: '#44bb44', false: '#333' }} />
</View>
{fullAuto && <Text style={s.warningText}> </Text>}
</View>
<Text style={s.sectionTitle}> </Text>
{tasks.map(task => (
<View key={task.id} style={s.taskCard}>
<View style={s.taskHeader}>
<Text style={[s.statusIcon, { color: statusColor(task.status) }]}>{statusIcon(task.status)}</Text>
<Text style={s.taskName}>{task.name}</Text>
<Switch value={task.autonomous} onValueChange={() => toggleTask(task.id)} trackColor={{ true: '#00A0C8', false: '#333' }} />
</View>
<View style={s.taskMeta}>
<View style={s.typeBadge}><Text style={s.typeText}>{task.type}</Text></View>
{task.next_run && <Text style={s.metaText}> : {task.next_run}</Text>}
{task.last_result && <Text style={s.metaText}>{task.last_result}</Text>}
</View>
</View>
))}
<Text style={s.sectionTitle}> </Text>
{opsLog.map((log, i) => (
<View key={i} style={s.logRow}>
<Text style={s.logTime}>{log.time}</Text>
<Text style={[s.logMsg, { color: log.type === 'success' ? '#44bb44' : log.type === 'warning' ? '#ffbb00' : '#ff4444' }]}>{log.msg}</Text>
</View>
))}
</ScrollView>
);
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0E1A', padding: 16 },
title: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 4 },
sub: { color: '#888', fontSize: 13, marginBottom: 16 },
autonomyCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' },
autonomyLabel: { color: '#888', fontSize: 12, marginBottom: 4 },
autonomyVal: { color: '#00A0C8', fontSize: 40, fontWeight: '700', marginBottom: 8 },
barBg: { height: 10, backgroundColor: '#333', borderRadius: 5, marginBottom: 8 },
barFill: { height: 10, backgroundColor: '#00A0C8', borderRadius: 5 },
autonomyDesc: { color: '#888', fontSize: 12 },
card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#333' },
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
label: { color: '#fff', fontSize: 15 },
warningText: { color: '#ffbb00', fontSize: 12, marginTop: 8 },
sectionTitle: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 10 },
taskCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' },
taskHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
statusIcon: { fontSize: 18, marginRight: 10 },
taskName: { color: '#fff', fontWeight: '600', flex: 1 },
taskMeta: { flexDirection: 'row', alignItems: 'center', gap: 10 },
typeBadge: { backgroundColor: '#003366', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6 },
typeText: { color: '#00A0C8', fontSize: 11 },
metaText: { color: '#888', fontSize: 12 },
logRow: { flexDirection: 'row', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#1A1F2E' },
logTime: { color: '#555', fontSize: 12, marginRight: 12, minWidth: 40 },
logMsg: { flex: 1, fontSize: 13 },
});