117 lines
6.0 KiB
TypeScript
117 lines
6.0 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert } from 'react-native';
|
|
import { ITSM_BASE } from '../../services/api';
|
|
|
|
interface AROverlay { label: string; value: string; color: string; x: number; y: number }
|
|
|
|
const SAMPLE_OVERLAYS: AROverlay[] = [
|
|
{ label: 'CPU', value: '42%', color: '#44bb44', x: 20, y: 80 },
|
|
{ label: 'RAM', value: '67%', color: '#ffbb00', x: 60, y: 40 },
|
|
{ label: 'DISK', value: '81%', color: '#ff8800', x: 70, y: 70 },
|
|
{ label: 'NET', value: '1.2GB/s', color: '#44bb44', x: 30, y: 50 },
|
|
{ label: 'TEMP', value: '52°C', color: '#44bb44', x: 50, y: 20 },
|
|
];
|
|
|
|
export default function CameraARScreen() {
|
|
const [scanning, setScanning] = useState(false);
|
|
const [overlays, setOverlays] = useState<AROverlay[]>([]);
|
|
const [detectedServer, setDetectedServer] = useState<string | null>(null);
|
|
const [serverInfo, setServerInfo] = useState<any>(null);
|
|
|
|
const startScan = async () => {
|
|
setScanning(true);
|
|
setTimeout(() => {
|
|
setOverlays(SAMPLE_OVERLAYS);
|
|
setDetectedServer('app-svr-01');
|
|
setScanning(false);
|
|
}, 1500);
|
|
};
|
|
|
|
const fetchServerDetail = async (name: string) => {
|
|
try {
|
|
const r = await fetch(`${ITSM_BASE}/api/cmdb/servers?search=${name}`);
|
|
if (r.ok) { const d = await r.json(); setServerInfo(d.servers?.[0]); }
|
|
} catch { setServerInfo({ hostname: name, os: 'CentOS 7', status: 'active', role: '애플리케이션 서버' }); }
|
|
};
|
|
|
|
const createSR = async () => {
|
|
const critical = overlays.filter(o => o.color === '#ff4444' || o.color === '#ff8800');
|
|
if (critical.length === 0) { Alert.alert('정상', '감지된 이상 없음'); return; }
|
|
try {
|
|
await fetch(`${ITSM_BASE}/api/tasks`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ title: `[AR스캔] ${detectedServer} 리소스 이상`, description: critical.map(c => `${c.label}: ${c.value}`).join('\n'), priority: 'high' }),
|
|
});
|
|
Alert.alert('SR 등록', 'AR 스캔 기반 SR이 등록되었습니다.');
|
|
} catch { Alert.alert('오류', 'SR 등록 실패'); }
|
|
};
|
|
|
|
return (
|
|
<ScrollView style={s.container}>
|
|
<Text style={s.title}>카메라 AR 오버레이</Text>
|
|
<Text style={s.sub}>서버/장비를 카메라로 비추면 실시간 메트릭을 오버레이합니다</Text>
|
|
|
|
<View style={s.cameraView}>
|
|
<View style={s.cameraFrame}>
|
|
{!scanning && overlays.length === 0 && (
|
|
<Text style={s.cameraPlaceholder}>📷 카메라 영역{'\n'}(실제 기기에서 카메라 활성화)</Text>
|
|
)}
|
|
{scanning && <Text style={s.scanningText}>🔍 서버 인식 중...</Text>}
|
|
{overlays.map((o, i) => (
|
|
<View key={i} style={[s.overlay, { left: `${o.x}%`, top: `${o.y}%`, borderColor: o.color }]}>
|
|
<Text style={s.overlayLabel}>{o.label}</Text>
|
|
<Text style={[s.overlayValue, { color: o.color }]}>{o.value}</Text>
|
|
</View>
|
|
))}
|
|
{detectedServer && <View style={s.detectedBadge}><Text style={s.detectedText}>✅ {detectedServer}</Text></View>}
|
|
</View>
|
|
<TouchableOpacity style={s.scanBtn} onPress={startScan} disabled={scanning}>
|
|
<Text style={s.scanBtnText}>{scanning ? '스캔 중...' : '📡 AR 스캔 시작'}</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{overlays.length > 0 && (
|
|
<View style={s.metricsCard}>
|
|
<Text style={s.metricsTitle}>감지된 메트릭</Text>
|
|
<View style={s.metricsGrid}>
|
|
{overlays.map((o, i) => (
|
|
<View key={i} style={[s.metricItem, { borderColor: o.color }]}>
|
|
<Text style={s.metricLabel}>{o.label}</Text>
|
|
<Text style={[s.metricValue, { color: o.color }]}>{o.value}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
<TouchableOpacity style={s.srBtn} onPress={createSR}>
|
|
<Text style={s.srBtnText}>📋 이상 항목 SR 등록</Text>
|
|
</TouchableOpacity>
|
|
</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 },
|
|
cameraView: { backgroundColor: '#1A1F2E', borderRadius: 12, overflow: 'hidden', marginBottom: 16, borderWidth: 1, borderColor: '#333' },
|
|
cameraFrame: { height: 260, position: 'relative', alignItems: 'center', justifyContent: 'center' },
|
|
cameraPlaceholder: { color: '#555', textAlign: 'center', fontSize: 14 },
|
|
scanningText: { color: '#00A0C8', fontSize: 16, fontWeight: '600' },
|
|
overlay: { position: 'absolute', backgroundColor: 'rgba(0,0,0,0.75)', borderWidth: 1, borderRadius: 6, padding: 6 },
|
|
overlayLabel: { color: '#fff', fontSize: 10, fontWeight: '600' },
|
|
overlayValue: { fontSize: 12, fontWeight: '700' },
|
|
detectedBadge: { position: 'absolute', bottom: 12, right: 12, backgroundColor: '#003366', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, borderWidth: 1, borderColor: '#00A0C8' },
|
|
detectedText: { color: '#fff', fontSize: 12, fontWeight: '600' },
|
|
scanBtn: { backgroundColor: '#00A0C8', padding: 14, alignItems: 'center' },
|
|
scanBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 },
|
|
metricsCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: '#333' },
|
|
metricsTitle: { color: '#fff', fontWeight: '700', fontSize: 16, marginBottom: 12 },
|
|
metricsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 14 },
|
|
metricItem: { backgroundColor: '#0A0E1A', borderWidth: 1, borderRadius: 10, padding: 10, minWidth: '28%', alignItems: 'center' },
|
|
metricLabel: { color: '#aaa', fontSize: 11, marginBottom: 4 },
|
|
metricValue: { fontSize: 16, fontWeight: '700' },
|
|
srBtn: { backgroundColor: '#003366', padding: 12, borderRadius: 10, alignItems: 'center', borderWidth: 1, borderColor: '#00A0C8' },
|
|
srBtnText: { color: '#fff', fontWeight: '700' },
|
|
});
|