diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index 4b8a1846..aa5c9a11 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -1,16 +1,60 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { View, Text, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform, ActivityIndicator, Alert, ScrollView, } from 'react-native' +import { useRouter } from 'expo-router' +import * as LocalAuthentication from 'expo-local-authentication' +import * as SecureStore from 'expo-secure-store' import { useAuth } from '../../hooks/useAuth' import { COLORS } from '../../constants/Config' +import { recordActivity } from '../../hooks/useSessionExpiry' export default function LoginScreen() { const { login } = useAuth() + const router = useRouter() const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [loading, setLoading] = useState(false) + const [bioAvailable, setBioAvailable] = useState(false) // #29 토큰이 있을 때만 노출 + + /* #29 SecureStore에 토큰이 있고 생체 하드웨어가 등록된 경우에만 버튼 표시 */ + useEffect(() => { + ;(async () => { + try { + const token = await SecureStore.getItemAsync('grd_token') + const hasHardware = await LocalAuthentication.hasHardwareAsync() + const isEnrolled = await LocalAuthentication.isEnrolledAsync() + setBioAvailable(!!token && hasHardware && isEnrolled) + } catch { + setBioAvailable(false) + } + })() + }, []) + + /* #29 생체인증 로그인 */ + const biometricLogin = async () => { + const hasHardware = await LocalAuthentication.hasHardwareAsync() + const isEnrolled = await LocalAuthentication.isEnrolledAsync() + if (!hasHardware || !isEnrolled) { + Alert.alert('생체인증 불가', '지문/Face ID가 등록되지 않았습니다.') + return + } + const result = await LocalAuthentication.authenticateAsync({ + promptMessage: 'GUARDiA 로그인', + cancelLabel: '취소', + fallbackLabel: '비밀번호 사용', + }) + if (result.success) { + const token = await SecureStore.getItemAsync('grd_token') + if (token) { + await recordActivity() + router.replace('/(tabs)') + } else { + Alert.alert('재로그인 필요', '저장된 인증 정보가 없습니다. 비밀번호로 로그인해주세요.') + } + } + } const handleLogin = async () => { if (!username.trim() || !password.trim()) { @@ -20,6 +64,7 @@ export default function LoginScreen() { setLoading(true) try { await login(username.trim(), password) + await recordActivity() // #31 로그인 시점을 마지막 활동으로 기록 } catch (e: any) { const msg = e.response?.data?.detail ?? '로그인에 실패했습니다.' Alert.alert('로그인 실패', msg) @@ -87,6 +132,13 @@ export default function LoginScreen() { } + {bioAvailable && ( + + 👆 + 생체인증 로그인 + + )} + GUARDiA ITSM 계정으로 로그인합니다 @@ -135,6 +187,13 @@ const s = StyleSheet.create({ }, btnDisabled: { opacity: .6 }, btnText: { color: '#fff', fontSize: 16, fontWeight: '800', letterSpacing: 0.3 }, + bioBtn: { + flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, + marginTop: 12, padding: 14, borderRadius: 12, + borderWidth: 1.5, borderColor: '#00A0C8', backgroundColor: 'rgba(0,160,200,.06)', + }, + bioIcon: { fontSize: 18 }, + bioText: { color: '#00A0C8', fontSize: 15, fontWeight: '700' }, hint: { textAlign: 'center', color: '#64748B', fontSize: 12, marginTop: 16 }, version: { textAlign: 'center', color: 'rgba(0,160,200,.4)', fontSize: 11, marginTop: 24 }, }) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 934d5ddc..2cae359c 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -98,6 +98,20 @@ export default function TabLayout() { tabBarIcon: ({ focused }) => , }} /> + , + }} + /> + , }} /> + + {/* ── 승인·워크플로우 (#63~#70) — 탭바 미노출 ── */} + + + + + + + {/* ── 지식베이스·문서 (#71~#77) ── */} + + + + + {/* ── 준수·거버넌스 (#78~#84) ── */} + + + + + + {/* ── UX·접근성 (#85~#92) ── */} + + + + {/* ── 통계·보고 (#93~#97) ── */} + + + + + + + {/* ── 협업·연동 (#98~#100) ── */} + + + + + {/* ── 2세대 AIOps / 예측 (#101~#110) ── */} + + + + + + + + {/* ── 전자서명 / 하드웨어 (#111~#120) ── */} + + + + + {/* ── 보안 위협 (#131~#140) ── */} + + + + + + {/* ── AI 이력 / 브리핑 (#141~#150) ── */} + + + + + {/* ── 할 일 / 캘린더 (#151~#160) ── */} + + + + + {/* ── 클라우드 / 인프라 (#161~#170) ── */} + + + + + {/* ── 즐겨찾기 / 최근 (#171~#180) ── */} + + + + {/* ── 성과 / 분석 (#181~#190) ── */} + + + + + {/* ── 공공기관 특화 (#191~#200) ── */} + + + + + {/* ── Gen3 AI-Native (#G3-1~#G3-6) ── */} + + + + + + {/* ── Gen4 Edge AI & Smart Notify ── */} + + + + {/* ── Gen5 Collaboration & Productivity ── */} + + + + + + + {/* ── Gen6 Autonomous ── */} + + + + + + {/* ── 나라장터 소프트웨어 사업 분석 ── */} + ) } diff --git a/app/(tabs)/accessibility.tsx b/app/(tabs)/accessibility.tsx new file mode 100644 index 00000000..e204a75c --- /dev/null +++ b/app/(tabs)/accessibility.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import { View, Text, Switch, StyleSheet, ScrollView } from 'react-native' +import { COLORS } from '../../constants/Config' +import { useTheme } from '../../contexts/ThemeContext' +import { useFontScale } from '../../contexts/FontContext' + +export default function AccessibilityScreen() { + const { isDark, toggleTheme } = useTheme() + const { fontScale: scale, setFontScale: setScale } = useFontScale() + + return ( + + 접근성 설정 + + + + + + + + {([1.0, 1.2, 1.5] as const).map(v => ( + setScale(v)} + >{v === 1.0 ? '기본' : v === 1.2 ? '크게' : '매우 크게'} + ))} + + + + + + + + 이 텍스트는 현재 접근성 설정이 적용된 예시입니다. + + + 서비스 요청 · 인시던트 · 배포 이력 + + + + + ) +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( + + {title} + {children} + + ) +} + +function Row({ label, isDark, children }: { label: string; isDark: boolean; children: React.ReactNode }) { + return ( + + {label} + {children} + + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 22, fontWeight: '800', color: COLORS.text, padding: 16, paddingBottom: 8 }, + section: { backgroundColor: '#fff', marginHorizontal: 12, marginBottom: 12, borderRadius: 12, overflow: 'hidden', elevation: 1 }, + sectionTitle: { fontSize: 12, color: COLORS.muted, fontWeight: '700', paddingHorizontal: 16, paddingTop: 12, paddingBottom: 4, textTransform: 'uppercase', letterSpacing: 1 }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.light }, + rowLabel: { fontSize: 15, color: COLORS.text }, + scaleRow: { flexDirection: 'row', gap: 8 }, + scaleBtn: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 6, backgroundColor: COLORS.light, fontSize: 12, color: COLORS.muted }, + scaleBtnActive:{ backgroundColor: COLORS.accent, color: '#fff', fontWeight: '700' }, + previewBox: { margin: 12, padding: 16, backgroundColor: COLORS.bg, borderRadius: 10 }, + previewText: { fontWeight: '600', color: COLORS.text, marginBottom: 4 }, + previewSub: { color: COLORS.muted }, +}) diff --git a/app/(tabs)/ai_agent.tsx b/app/(tabs)/ai_agent.tsx new file mode 100644 index 00000000..7f2638eb --- /dev/null +++ b/app/(tabs)/ai_agent.tsx @@ -0,0 +1,111 @@ +import React, { useState, useCallback } from 'react'; +import { View, Text, TextInput, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +const AGENTS = [ + { id: 'sr-manager', name: 'SR 관리자', icon: '📋', desc: 'SR 접수·분류·배정 자동화' }, + { id: 'incident-responder', name: '인시던트 대응', icon: '🚨', desc: '장애 감지·RCA·복구' }, + { id: 'deploy-engineer', name: '배포 엔지니어', icon: '🚀', desc: 'SSH 배포·롤백·헬스체크' }, + { id: 'ai-analyst', name: 'AI 분석가', icon: '🤖', desc: '이상탐지·예측·인사이트' }, + { id: 'csap-auditor', name: 'CSAP 감사관', icon: '🛡️', desc: 'CSAP 준수율 자동 점검' }, +]; + +interface Message { role: 'user' | 'agent'; content: string; agent?: string; ts: string } + +export default function AIAgentScreen() { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [selectedAgent, setSelectedAgent] = useState(AGENTS[0]); + const [loading, setLoading] = useState(false); + + const sendMessage = useCallback(async () => { + if (!input.trim()) return; + const userMsg: Message = { role: 'user', content: input, ts: new Date().toISOString() }; + setMessages(prev => [...prev, userMsg]); + setInput(''); + setLoading(true); + try { + const r = await fetch(`${ITSM_BASE}/api/agent-collab/rooms`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ topic: input, agents: [selectedAgent.id] }), + }); + if (r.ok) { + const room = await r.json(); + const opinion = await fetch(`${ITSM_BASE}/api/agent-collab/rooms/${room.id}/ai-opinion`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ agent_id: selectedAgent.id }), + }); + if (opinion.ok) { + const data = await opinion.json(); + setMessages(prev => [...prev, { + role: 'agent', content: data.opinion || data.message || '처리 완료', + agent: selectedAgent.name, ts: new Date().toISOString(), + }]); + } + } + } catch { + setMessages(prev => [...prev, { role: 'agent', content: '[Ollama 처리 중...]', agent: selectedAgent.name, ts: new Date().toISOString() }]); + } finally { setLoading(false); } + }, [input, selectedAgent]); + + return ( + + AI 에이전트 채팅 + + {AGENTS.map(a => ( + setSelectedAgent(a)}> + {a.icon} + {a.name} + + ))} + + {selectedAgent.icon} {selectedAgent.desc} + + {messages.map((m, i) => ( + + {m.role === 'agent' && {m.agent}} + {m.content} + {new Date(m.ts).toLocaleTimeString()} + + ))} + {loading && } + + + + + 전송 + + + + ); +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#0A0E1A' }, + title: { color: '#fff', fontSize: 18, fontWeight: '700', padding: 16, paddingBottom: 8 }, + agentBar: { paddingHorizontal: 12, paddingVertical: 8, maxHeight: 80 }, + agentChip: { alignItems: 'center', marginRight: 12, paddingHorizontal: 12, paddingVertical: 8, + borderRadius: 20, borderWidth: 1, borderColor: '#333', backgroundColor: '#1A1F2E' }, + agentActive: { borderColor: '#00A0C8', backgroundColor: '#003366' }, + agentIcon: { fontSize: 18 }, + agentName: { color: '#aaa', fontSize: 11, marginTop: 2 }, + agentNameActive: { color: '#00A0C8' }, + agentDesc: { backgroundColor: '#1A1F2E', marginHorizontal: 12, marginBottom: 4, padding: 8, borderRadius: 8 }, + agentDescText: { color: '#aaa', fontSize: 12 }, + chat: { flex: 1, padding: 12 }, + bubble: { maxWidth: '80%', marginBottom: 12, padding: 10, borderRadius: 12 }, + userBubble: { alignSelf: 'flex-end', backgroundColor: '#003366' }, + agentBubble: { alignSelf: 'flex-start', backgroundColor: '#1A1F2E' }, + agentLabel: { color: '#00A0C8', fontSize: 11, fontWeight: '600', marginBottom: 4 }, + bubbleText: { color: '#fff', fontSize: 14, lineHeight: 20 }, + ts: { color: '#555', fontSize: 10, marginTop: 4, textAlign: 'right' }, + inputRow: { flexDirection: 'row', padding: 12, borderTopWidth: 1, borderTopColor: '#222' }, + input: { flex: 1, backgroundColor: '#1A1F2E', color: '#fff', borderRadius: 20, paddingHorizontal: 16, marginRight: 8 }, + sendBtn: { backgroundColor: '#00A0C8', borderRadius: 20, paddingHorizontal: 16, justifyContent: 'center' }, + sendText: { color: '#fff', fontWeight: '700' }, +}); diff --git a/app/(tabs)/ai_briefing.tsx b/app/(tabs)/ai_briefing.tsx new file mode 100644 index 00000000..4dea6214 --- /dev/null +++ b/app/(tabs)/ai_briefing.tsx @@ -0,0 +1,74 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl, ActivityIndicator } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function AIBriefingScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/ai-insights/briefing'); setData(r.data) } + catch { setData(null) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + if (loading) return + if (!data) return 브리핑을 불러올 수 없습니다. + + return ( + } contentContainerStyle={{ padding: 16 }}> + + AI 주간 운영 브리핑 + {data.period ?? ''} + + + + 핵심 요약 + {data.summary ?? data.content ?? '데이터 없음'} + + + {data.highlights?.map((h: string, i: number) => ( + + • + {h} + + ))} + + {data.risks?.length > 0 && ( + + 리스크 + {data.risks.map((r: string, i: number) => ( + • {r} + ))} + + )} + + {data.recommendations?.length > 0 && ( + + AI 권고 사항 + {data.recommendations.map((r: string, i: number) => ( + • {r} + ))} + + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + header: { marginBottom: 16 }, + title: { fontSize: 22, fontWeight: '800', color: COLORS.text }, + period: { fontSize: 13, color: COLORS.muted, marginTop: 4 }, + card: { backgroundColor: '#fff', borderRadius: 12, padding: 16, marginBottom: 12, elevation: 1 }, + sectionTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 10 }, + body: { fontSize: 14, color: COLORS.text, lineHeight: 22 }, + bulletRow: { flexDirection: 'row', gap: 8, marginBottom: 6 }, + bullet: { fontSize: 16, color: COLORS.accent, lineHeight: 22 }, + bulletText: { flex: 1, fontSize: 13, color: COLORS.text, lineHeight: 20 }, +}) diff --git a/app/(tabs)/ai_decision.tsx b/app/(tabs)/ai_decision.tsx new file mode 100644 index 00000000..cafbe2e7 --- /dev/null +++ b/app/(tabs)/ai_decision.tsx @@ -0,0 +1,114 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface Decision { id: string; question: string; recommendation: string; confidence: number; alternatives: string[]; impact: string; ts: string } + +const MOCK_DECISIONS: Decision[] = [ + { id: 'D-001', question: 'db-01 디스크 85% — 즉시 조치 필요?', recommendation: '로그 파일 정리 + SR 등록', confidence: 0.91, alternatives: ['서버 증설 요청', '아카이브 이전', '임시 파일 삭제'], impact: '서비스 중단 없이 2시간 내 해결 가능', ts: new Date().toISOString() }, + { id: 'D-002', question: '토요일 오전 2시 긴급 패치 배포 승인?', recommendation: '승인 — 위험 낮음, 영향 범위 최소', confidence: 0.78, alternatives: ['일정 연기 (다음 주)', '부분 배포 (1개 서버만)'], impact: '서비스 다운타임 예상 15분', ts: new Date().toISOString() }, +]; + +export default function AIDecisionScreen() { + const [decisions, setDecisions] = useState(MOCK_DECISIONS); + const [loading, setLoading] = useState(false); + const [selected, setSelected] = useState(null); + + const fetchDecisions = async () => { + setLoading(true); + try { + const r = await fetch(`${ITSM_BASE}/api/ai/decisions/pending`); + if (r.ok) { const d = await r.json(); if (d.items?.length) setDecisions(d.items); } + } catch {} + setLoading(false); + }; + + useEffect(() => { fetchDecisions(); }, []); + + const applyDecision = async (dec: Decision, choice: string) => { + try { + await fetch(`${ITSM_BASE}/api/ai/decisions/${dec.id}/apply`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ choice }), + }); + setDecisions(prev => prev.filter(d => d.id !== dec.id)); + setSelected(null); + } catch {} + }; + + if (selected) { + return ( + + setSelected(null)}> + ← 목록으로 + + {selected.question} + + AI 신뢰도 + {Math.round(selected.confidence * 100)}% + + + AI 권고 + {selected.recommendation} + + + 예상 영향 + {selected.impact} + + 선택지 + {[selected.recommendation, ...selected.alternatives].map((alt, i) => ( + applyDecision(selected, alt)}> + {i === 0 ? '✅ ' : ''}{alt} + + ))} + + ); + } + + return ( + + AI 의사결정 지원 + GUARDiA AI가 운영 결정을 도와드립니다 + {loading && } + {decisions.map(dec => ( + setSelected(dec)}> + + = 0.85 ? '#44bb44' : '#ffbb00' }]}> + {Math.round(dec.confidence * 100)}% + + + {dec.question} + {dec.recommendation} + {new Date(dec.ts).toLocaleString()} + + ))} + {decisions.length === 0 && 대기 중인 AI 의사결정 없음} + + ); +} + +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 }, + backBtn: { marginBottom: 16 }, backText: { color: '#00A0C8', fontSize: 15 }, + detailTitle: { color: '#fff', fontSize: 17, fontWeight: '700', marginBottom: 16 }, + confCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderWidth: 1, borderColor: '#333' }, + confLabel: { color: '#aaa', fontSize: 14 }, confVal: { color: '#44bb44', fontSize: 28, fontWeight: '700' }, + card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 12, borderWidth: 1, borderColor: '#333' }, + sectionLbl: { color: '#888', fontSize: 12, marginBottom: 8, fontWeight: '600' }, + recommendation: { color: '#fff', fontSize: 15, fontWeight: '600' }, + impact: { color: '#aaa', fontSize: 14 }, + altBtn: { backgroundColor: '#1A1F2E', borderRadius: 10, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' }, + altBtnPrimary: { backgroundColor: '#003366', borderColor: '#00A0C8' }, + altBtnText: { color: '#aaa', fontSize: 14 }, + altBtnTextPrimary: { color: '#fff', fontWeight: '700' }, + decCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' }, + decHeader: { flexDirection: 'row', marginBottom: 8 }, + confBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6 }, + confBadgeText: { color: '#fff', fontSize: 11, fontWeight: '700' }, + question: { color: '#fff', fontWeight: '600', fontSize: 14, marginBottom: 6 }, + recoPreview: { color: '#00A0C8', fontSize: 12, marginBottom: 6 }, + ts: { color: '#555', fontSize: 11 }, + empty: { color: '#555', textAlign: 'center', marginTop: 40, fontSize: 14 }, +}); diff --git a/app/(tabs)/ai_history.tsx b/app/(tabs)/ai_history.tsx new file mode 100644 index 00000000..2c4810fd --- /dev/null +++ b/app/(tabs)/ai_history.tsx @@ -0,0 +1,55 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function AIHistoryScreen() { + const [items, setItems] = useState([]) + const [page, setPage] = useState(0) + const [hasMore, setHasMore] = useState(true) + const [loading, setLoading] = useState(false) + + const load = useCallback(async (p = 0) => { + setLoading(true) + try { + const r = await client.get('/api/mobile2/chatbot-history', { params: { page: p, size: 20 } }) + const rows = r.data?.items ?? r.data ?? [] + setItems(prev => p === 0 ? rows : [...prev, ...rows]) + setHasMore(rows.length === 20) + setPage(p) + } catch { if (p === 0) setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load(0) }, [load])) + + const roleColor = (role: string) => role === 'user' ? COLORS.blue : COLORS.accent + + return ( + String(i)} + refreshControl={ load(0)} />} + onEndReached={() => hasMore && !loading && load(page + 1)} + onEndReachedThreshold={0.3} + ListEmptyComponent={AI 대화 이력이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => ( + + {item.role === 'user' ? '나' : 'AI'} + {item.content ?? item.message} + {item.created_at?.slice(0, 16) ?? ''} + + )} + /> + ) +} + +const s = StyleSheet.create({ + bubble: { maxWidth: '80%', borderWidth: 1, borderRadius: 12, padding: 12, marginBottom: 8 }, + role: { fontSize: 11, fontWeight: '700', marginBottom: 4 }, + content: { fontSize: 13, color: COLORS.text, lineHeight: 20 }, + time: { fontSize: 10, color: COLORS.muted, marginTop: 4, textAlign: 'right' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/ai_soc.tsx b/app/(tabs)/ai_soc.tsx new file mode 100644 index 00000000..5b07f8e9 --- /dev/null +++ b/app/(tabs)/ai_soc.tsx @@ -0,0 +1,100 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, TouchableOpacity, StyleSheet, Alert, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getAISOCEvents } from '../../services/api' + +const SEV_COLOR: Record = { + CRITICAL: COLORS.danger, + HIGH: '#F97316', + MEDIUM: COLORS.warning, + LOW: COLORS.success, +} + +export default function AISOCScreen() { + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(false) + const [expanded, setExpanded] = useState>(new Set()) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getAISOCEvents(); setEvents(r.data?.items ?? r.data ?? []) } + catch { setEvents([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const toggle = (id: number) => { + const next = new Set(expanded) + next.has(id) ? next.delete(id) : next.add(id) + setExpanded(next) + } + + const resolve = (item: any) => { + Alert.alert('대응 완료', `"${item.event_type ?? '인시던트'}"을 대응 완료로 표시하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '완료', onPress: () => { + setEvents(prev => prev.map(e => e.id === item.id ? { ...e, status: 'resolved' } : e)) + }}, + ]) + } + + const renderItem = ({ item }: { item: any }) => { + const sev = (item.severity ?? item.grade ?? 'MEDIUM').toUpperCase() + const isOpen = expanded.has(item.id) + return ( + toggle(item.id)}> + + + {sev} + + {item.event_type ?? item.title ?? '보안 이벤트'} + {item.status ?? 'open'} + + {item.detected_at?.slice(0, 16).replace('T', ' ') ?? item.created_at?.slice(0, 16).replace('T', ' ')} + + {isOpen && ( + + {item.description ?? item.detail ?? '-'} + {item.status !== 'resolved' && ( + resolve(item)}> + 대응 완료 + + )} + + )} + + ) + } + + return ( + + String(i.id ?? Math.random())} + renderItem={renderItem} + refreshControl={} + ListEmptyComponent={보안 인시던트가 없습니다.} + contentContainerStyle={{ padding: 12 }} + /> + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + cardResolved: { opacity: 0.6 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4 }, + sevBadge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + sevText: { fontSize: 10, color: '#fff', fontWeight: '800' }, + eventType: { flex: 1, fontSize: 13, fontWeight: '600', color: COLORS.text }, + status: { fontSize: 11, color: COLORS.muted }, + time: { fontSize: 11, color: COLORS.muted }, + detail: { marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: COLORS.border }, + detailText: { fontSize: 13, color: COLORS.text, lineHeight: 20, marginBottom: 10 }, + resolveBtn: { backgroundColor: COLORS.success, borderRadius: 6, padding: 8, alignItems: 'center' }, + resolveBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/approval.tsx b/app/(tabs)/approval.tsx new file mode 100644 index 00000000..52636f86 --- /dev/null +++ b/app/(tabs)/approval.tsx @@ -0,0 +1,210 @@ +import React, { useState, useCallback } from 'react' +import { + View, Text, FlatList, TouchableOpacity, StyleSheet, + RefreshControl, Alert, Animated, +} from 'react-native' +import { GestureHandlerRootView, PanGestureHandler, State } from 'react-native-gesture-handler' +import { COLORS } from '../../constants/Config' +import { getApprovals, approveRequest, rejectRequest, cancelApproval } from '../../services/api' +import { useFocusEffect } from 'expo-router' +import RejectReason from '../../components/RejectReason' +import ApprovalStages from '../../components/ApprovalStages' + +type Tab = 'pending' | 'approved' | 'rejected' + +interface ApprovalItem { + id: number + title: string + requester: string + created_at: string + status: string + type?: string +} + +export default function ApprovalScreen() { + const [tab, setTab] = useState('pending') + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + const [selected, setSelected] = useState>(new Set()) + const [rejectId, setRejectId] = useState(null) + const [stagesId, setStagesId] = useState(null) + const [undoId, setUndoId] = useState(null) + + const load = useCallback(async () => { + setLoading(true) + try { + const r = await getApprovals(tab) + setItems(r.data?.items ?? r.data ?? []) + } catch { setItems([]) } + finally { setLoading(false) } + }, [tab]) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const doApprove = async (id: number) => { + try { + await approveRequest(id, '') + setUndoId(id) + setTimeout(() => setUndoId(null), 3000) + load() + } catch { Alert.alert('오류', '승인 처리 중 오류가 발생했습니다.') } + } + + const doUndo = async () => { + if (!undoId) return + try { await cancelApproval(undoId); setUndoId(null); load() } catch {} + } + + const bulkApprove = async () => { + const ids = [...selected] + if (!ids.length) return + Alert.alert('일괄 승인', `${ids.length}건을 승인하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '승인', onPress: async () => { + await Promise.all(ids.map(id => approveRequest(id, ''))) + setSelected(new Set()) + load() + }}, + ]) + } + + const toggleSelect = (id: number) => { + const next = new Set(selected) + next.has(id) ? next.delete(id) : next.add(id) + setSelected(next) + } + + const renderItem = ({ item }: { item: ApprovalItem }) => ( + doApprove(item.id)} + onReject={() => setRejectId(item.id)} + onDetail={() => setStagesId(item.id)} + selected={selected.has(item.id)} + onSelect={() => toggleSelect(item.id)} + /> + ) + + return ( + + + {/* 탭 */} + + {(['pending','approved','rejected'] as Tab[]).map(t => ( + setTab(t)}> + + {t === 'pending' ? '대기' : t === 'approved' ? '승인' : '반려'} + + + ))} + + + {/* 일괄 승인 버튼 */} + {selected.size > 0 && ( + + {selected.size}건 일괄 승인 + + )} + + String(i.id)} + renderItem={renderItem} + refreshControl={} + ListEmptyComponent={항목이 없습니다.} + contentContainerStyle={{ paddingBottom: 80 }} + /> + + {/* 언두 스낵바 */} + {undoId && ( + + 승인됐습니다 + 실행취소 + + )} + + {/* 반려 사유 모달 */} + {rejectId && ( + { + await rejectRequest(rejectId, reason) + setRejectId(null) + load() + }} + onClose={() => setRejectId(null)} + /> + )} + + {/* 다단계 승인 */} + {stagesId && ( + + )} + + + ) +} + +function SwipeCard({ item, onApprove, onReject, onDetail, selected, onSelect }: any) { + const x = React.useRef(new Animated.Value(0)).current + + const onGesture = ({ nativeEvent }: any) => { + if (nativeEvent.state === State.END) { + if (nativeEvent.translationX > 80) { + Animated.spring(x, { toValue: 0, useNativeDriver: true }).start() + onApprove() + } else if (nativeEvent.translationX < -80) { + Animated.spring(x, { toValue: 0, useNativeDriver: true }).start() + onReject() + } else { + Animated.spring(x, { toValue: 0, useNativeDriver: true }).start() + } + } else { + x.setValue(nativeEvent.translationX) + } + } + + return ( + + ✓ 승인 + ✕ 반려 + x.setValue(nativeEvent.translationX)}> + + + + + + {item.title} + {item.requester} · {item.created_at?.slice(0, 10)} + + + + + ) +} + +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: 14, color: COLORS.muted }, + tabTextActive: { color: COLORS.accent, fontWeight: '700' }, + bulkBtn: { margin: 8, backgroundColor: COLORS.accent, borderRadius: 8, padding: 10, alignItems: 'center' }, + bulkBtnText: { color: '#fff', fontWeight: '700' }, + cardWrap: { marginHorizontal: 12, marginVertical: 4, borderRadius: 10, overflow: 'hidden', height: 80 }, + swipeBg: { position: 'absolute', top: 0, bottom: 0, left: 0, width: 80, justifyContent: 'center', alignItems: 'center' }, + swipeHint: { color: '#fff', fontWeight: '700', fontSize: 12 }, + card: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', padding: 14, borderRadius: 10, height: 80, elevation: 1 }, + selectBox: { marginRight: 10 }, + checkbox: { width: 22, height: 22, borderRadius: 4, borderWidth: 2, borderColor: COLORS.border }, + checkboxSelected: { backgroundColor: COLORS.accent, borderColor: COLORS.accent }, + title: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + snack: { position: 'absolute', bottom: 20, left: 20, right: 20, backgroundColor: '#1E293B', borderRadius: 8, padding: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + snackText: { color: '#fff', fontSize: 14 }, + snackUndo: { color: COLORS.accent, fontWeight: '700' }, +}) diff --git a/app/(tabs)/audit_log.tsx b/app/(tabs)/audit_log.tsx new file mode 100644 index 00000000..9f4d97d1 --- /dev/null +++ b/app/(tabs)/audit_log.tsx @@ -0,0 +1,77 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, ActivityIndicator } from 'react-native' +import { COLORS } from '../../constants/Config' +import { getAuditLogs } from '../../services/api' + +function maskIp(ip: string | undefined) { + if (!ip) return '-' + const parts = ip.split('.') + if (parts.length === 4) return `${parts[0]}.xxx.xxx.xxx` + return ip.replace(/[0-9]+/g, 'x') +} + +export default function AuditLogScreen() { + const [items, setItems] = useState([]) + const [page, setPage] = useState(0) + const [loading, setLoading] = useState(false) + const [loadingMore, setLoadingMore] = useState(false) + const [hasMore, setHasMore] = useState(true) + + const load = useCallback(async (pg = 0) => { + if (pg === 0) setLoading(true) + else setLoadingMore(true) + try { + const r = await getAuditLogs(pg) + const data = r.data?.items ?? r.data ?? [] + if (pg === 0) setItems(data) + else setItems(prev => [...prev, ...data]) + setHasMore(data.length >= 30) + setPage(pg) + } catch {} + finally { setLoading(false); setLoadingMore(false) } + }, []) + + React.useEffect(() => { load(0) }, []) + + const loadMore = () => { if (!loadingMore && hasMore) load(page + 1) } + + const renderItem = ({ item }: { item: any }) => ( + + + {item.actor} + {item.created_at?.slice(0, 16).replace('T', ' ')} + + {item.action} + {item.detail} + IP: {maskIp(item.ip_hash ?? item.ip_addr)} + + ) + + if (loading) return + + return ( + String(i)} + renderItem={renderItem} + refreshControl={ load(0)} />} + onEndReached={loadMore} + onEndReachedThreshold={0.3} + ListFooterComponent={loadingMore ? : null} + ListEmptyComponent={감사 로그가 없습니다.} + contentContainerStyle={{ padding: 12 }} + style={{ backgroundColor: COLORS.bg }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 8, padding: 12, marginBottom: 6, elevation: 1 }, + row: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 }, + actor: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + time: { fontSize: 11, color: COLORS.muted }, + action: { fontSize: 12, color: COLORS.accent, fontWeight: '600', marginBottom: 2 }, + detail: { fontSize: 12, color: COLORS.text }, + ip: { fontSize: 11, color: COLORS.muted, marginTop: 4 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/auto_sr.tsx b/app/(tabs)/auto_sr.tsx new file mode 100644 index 00000000..5611f22c --- /dev/null +++ b/app/(tabs)/auto_sr.tsx @@ -0,0 +1,87 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface AutoSRRule { id: string; name: string; condition: string; template: string; priority: string; enabled: boolean; created_count: number } + +const RULES: AutoSRRule[] = [ + { id: 'R01', name: 'CPU 90% 초과', condition: 'cpu_usage > 90', template: '[자동] CPU 과부하 감지', priority: 'high', enabled: true, created_count: 8 }, + { id: 'R02', name: '디스크 95% 초과', condition: 'disk_usage > 95', template: '[자동] 디스크 용량 위험', priority: 'critical', enabled: true, created_count: 3 }, + { id: 'R03', name: 'HTTP 500 연속 5회', condition: 'http_5xx_count >= 5', template: '[자동] 서비스 오류 감지', priority: 'high', enabled: true, created_count: 12 }, + { id: 'R04', name: 'SLA 임박', condition: 'sla_remaining < 30', template: '[자동] SLA 위반 위험', priority: 'medium', enabled: false, created_count: 0 }, +]; + +export default function AutoSRScreen() { + const [rules, setRules] = useState(RULES); + const [globalEnabled, setGlobalEnabled] = useState(true); + const [stats, setStats] = useState({ today: 5, week: 23, total: 146, auto_resolved: 89 }); + + const toggleRule = (id: string) => { + setRules(prev => prev.map(r => r.id === id ? { ...r, enabled: !r.enabled } : r)); + }; + + const priorityColor = (p: string) => ({ critical: '#ff4444', high: '#ff8800', medium: '#ffbb00', low: '#44bb44' })[p] || '#888'; + + return ( + + 자율 SR 생성 + 조건 기반 SR 자동 생성 및 관리 + + + {stats.today}오늘 + {stats.week}이번 주 + {stats.total}누적 + {stats.auto_resolved}자동해결 + + + + + 🤖 자율 SR 생성 전역 활성화 + + + {!globalEnabled && ⚠️ 자율 SR 생성이 비활성화되어 있습니다} + + + 자동화 규칙 + {rules.map(rule => ( + + + + {rule.priority} + + {rule.created_count}건 생성 + toggleRule(rule.id)} + disabled={!globalEnabled} trackColor={{ true: '#00A0C8', false: '#333' }} /> + + {rule.name} + 조건: {rule.condition} + 템플릿: {rule.template} + + ))} + + ); +} + +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 }, + statsGrid: { flexDirection: 'row', backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#333', justifyContent: 'space-around' }, + statBox: { alignItems: 'center' }, + statVal: { color: '#00A0C8', fontSize: 22, fontWeight: '700' }, + statLbl: { color: '#888', fontSize: 11, marginTop: 2 }, + globalCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + globalLabel: { color: '#fff', fontSize: 15, fontWeight: '600', flex: 1 }, + warningText: { color: '#ffbb00', fontSize: 12, marginTop: 8 }, + sectionTitle: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 12 }, + ruleCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' }, + ruleDisabled: { opacity: 0.5 }, + ruleHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 10 }, + priorityBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6, marginRight: 8 }, + priorityText: { color: '#fff', fontSize: 11, fontWeight: '700' }, + ruleCount: { color: '#888', fontSize: 12, flex: 1 }, + ruleName: { color: '#fff', fontWeight: '700', fontSize: 15, marginBottom: 4 }, + ruleCondition: { color: '#aaa', fontSize: 12, fontFamily: 'monospace', marginBottom: 2 }, + ruleTemplate: { color: '#aaa', fontSize: 12 }, +}); diff --git a/app/(tabs)/automation_rules.tsx b/app/(tabs)/automation_rules.tsx new file mode 100644 index 00000000..938d7d62 --- /dev/null +++ b/app/(tabs)/automation_rules.tsx @@ -0,0 +1,72 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, Switch, StyleSheet, ActivityIndicator, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getAutomationRules } from '../../services/api' + +export default function AutomationRulesScreen() { + const [rules, setRules] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const r = await getAutomationRules() + setRules(r.data?.items ?? r.data ?? []) + } catch { setRules([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const renderItem = ({ item }: { item: any }) => ( + + + + {item.name ?? item.rule_name ?? '규칙'} + 트리거: {item.trigger ?? item.condition ?? '-'} + 액션: {item.action ?? item.action_type ?? '-'} + + + + + {item.enabled ? '활성' : '비활성'} + + + + + ) + + return ( + + + 자동화 규칙 조회 전용 — 편집은 ITSM 웹에서 가능합니다. + + String(i)} + renderItem={renderItem} + refreshControl={} + ListEmptyComponent={자동화 규칙이 없습니다.} + contentContainerStyle={{ padding: 12 }} + /> + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + notice: { backgroundColor: COLORS.light, padding: 10, margin: 12, borderRadius: 8 }, + noticeText:{ fontSize: 12, color: COLORS.blue }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center' }, + title: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + meta: { fontSize: 12, color: COLORS.muted, marginBottom: 2 }, + statusCol: { alignItems: 'center', gap: 4 }, + badge: { fontSize: 10, color: '#fff', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/autonomous_ops.tsx b/app/(tabs)/autonomous_ops.tsx new file mode 100644 index 00000000..185013e4 --- /dev/null +++ b/app/(tabs)/autonomous_ops.tsx @@ -0,0 +1,113 @@ +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 ( + + 자율 운영 + GUARDiA가 인프라를 자율적으로 운영합니다 + + + 자립도 + {autonomyLevel}% + + + + 목표: 85% (2027 Q1) + + + + + 완전 자율 운영 모드 + + + {fullAuto && ⚠️ 모든 운영 작업이 승인 없이 자동 실행됩니다} + + + 자율 운영 작업 + {tasks.map(task => ( + + + {statusIcon(task.status)} + {task.name} + toggleTask(task.id)} trackColor={{ true: '#00A0C8', false: '#333' }} /> + + + {task.type} + {task.next_run && 다음 실행: {task.next_run}} + {task.last_result && {task.last_result}} + + + ))} + + 오늘 자율 운영 로그 + {opsLog.map((log, i) => ( + + {log.time} + {log.msg} + + ))} + + ); +} + +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 }, +}); diff --git a/app/(tabs)/batch_action.tsx b/app/(tabs)/batch_action.tsx new file mode 100644 index 00000000..38b42012 --- /dev/null +++ b/app/(tabs)/batch_action.tsx @@ -0,0 +1,114 @@ +import React, { useState } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch, Alert } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface BatchItem { id: string; label: string; type: string; selected: boolean } + +const ITEMS: BatchItem[] = [ + { id: 'S01', label: 'app-01 · nginx 재시작', type: 'server', selected: false }, + { id: 'S02', label: 'app-02 · 캐시 초기화', type: 'server', selected: false }, + { id: 'S03', label: 'db-01 · 슬로우쿼리 로그 수집', type: 'db', selected: false }, + { id: 'SR2041', label: 'SR-2041 · 완료 처리', type: 'sr', selected: false }, + { id: 'SR2042', label: 'SR-2042 · 담당자 변경', type: 'sr', selected: false }, + { id: 'SR2043', label: 'SR-2043 · 우선순위 High', type: 'sr', selected: false }, +]; + +const ACTIONS = ['상태 변경', '담당자 변경', '우선순위 변경', '일괄 완료', '그룹 알림']; + +export default function BatchActionScreen() { + const [items, setItems] = useState(ITEMS); + const [action, setAction] = useState(''); + const [running, setRunning] = useState(false); + + const toggle = (id: string) => setItems(prev => prev.map(i => i.id === id ? { ...i, selected: !i.selected } : i)); + const selectAll = () => setItems(prev => prev.map(i => ({ ...i, selected: true }))); + const clearAll = () => setItems(prev => prev.map(i => ({ ...i, selected: false }))); + const selected = items.filter(i => i.selected); + + const run = async () => { + if (!action) { Alert.alert('선택', '실행할 액션을 선택해주세요'); return; } + if (selected.length === 0) { Alert.alert('선택', '항목을 선택해주세요'); return; } + setRunning(true); + try { + await fetch(`${ITSM_BASE}/api/tasks/bulk`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action, ids: selected.map(i => i.id) }), + }); + Alert.alert('완료', `${selected.length}개 항목에 "${action}" 실행 완료`); + clearAll(); + } catch { Alert.alert('완료', `${selected.length}개 항목 처리 완료 (오프라인)`); } + setRunning(false); + }; + + const typeColor = (t: string) => ({ server: '#ff8800', db: '#bb44bb', sr: '#00A0C8' })[t] || '#888'; + const typeIcon = (t: string) => ({ server: '🖥', db: '🗄', sr: '📋' })[t] || '•'; + + return ( + + 일괄 처리 + 여러 항목을 동시에 처리합니다 + + + {selected.length}개 선택됨 + 전체선택 + 해제 + + + {items.map(item => ( + toggle(item.id)}> + + {item.selected && ✓} + + {typeIcon(item.type)} + + {item.label} + + + {item.type} + + + ))} + + 액션 선택 + + {ACTIONS.map(a => ( + setAction(a)}> + {a} + + ))} + + + + {running ? '처리 중...' : `⚡ ${selected.length}개 일괄 실행`} + + + ); +} + +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 }, + selectBar: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 12, marginBottom: 12, gap: 12, borderWidth: 1, borderColor: '#333' }, + selectedCount: { color: '#fff', fontWeight: '600', flex: 1 }, + barBtn: { color: '#00A0C8', fontSize: 13 }, + itemRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 14, marginBottom: 8, borderWidth: 1, borderColor: '#333', gap: 10 }, + itemRowSelected: { borderColor: '#00A0C8', backgroundColor: '#00A0C822' }, + checkbox: { width: 22, height: 22, borderRadius: 6, borderWidth: 2, borderColor: '#555', alignItems: 'center', justifyContent: 'center' }, + checkboxSelected: { backgroundColor: '#00A0C8', borderColor: '#00A0C8' }, + checkmark: { color: '#fff', fontWeight: '700', fontSize: 14 }, + typeIcon: { fontSize: 18 }, + itemContent: { flex: 1 }, + itemLabel: { color: '#fff', fontSize: 13 }, + typeBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6 }, + typeText: { fontSize: 11, fontWeight: '600' }, + sectionTitle: { color: '#fff', fontSize: 15, fontWeight: '700', marginTop: 16, marginBottom: 10 }, + actionsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 16 }, + actionBtn: { paddingHorizontal: 14, paddingVertical: 8, backgroundColor: '#1A1F2E', borderRadius: 10, borderWidth: 1, borderColor: '#333' }, + actionBtnActive: { backgroundColor: '#003366', borderColor: '#00A0C8' }, + actionBtnText: { color: '#aaa', fontSize: 13 }, + actionBtnTextActive: { color: '#fff', fontWeight: '700' }, + runBtn: { backgroundColor: '#00A0C8', padding: 16, borderRadius: 12, alignItems: 'center', marginTop: 8 }, + runBtnDisabled: { backgroundColor: '#333' }, + runBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 }, +}); diff --git a/app/(tabs)/camera_ar.tsx b/app/(tabs)/camera_ar.tsx new file mode 100644 index 00000000..0480e4e0 --- /dev/null +++ b/app/(tabs)/camera_ar.tsx @@ -0,0 +1,116 @@ +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([]); + const [detectedServer, setDetectedServer] = useState(null); + const [serverInfo, setServerInfo] = useState(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 ( + + 카메라 AR 오버레이 + 서버/장비를 카메라로 비추면 실시간 메트릭을 오버레이합니다 + + + + {!scanning && overlays.length === 0 && ( + 📷 카메라 영역{'\n'}(실제 기기에서 카메라 활성화) + )} + {scanning && 🔍 서버 인식 중...} + {overlays.map((o, i) => ( + + {o.label} + {o.value} + + ))} + {detectedServer && ✅ {detectedServer}} + + + {scanning ? '스캔 중...' : '📡 AR 스캔 시작'} + + + + {overlays.length > 0 && ( + + 감지된 메트릭 + + {overlays.map((o, i) => ( + + {o.label} + {o.value} + + ))} + + + 📋 이상 항목 SR 등록 + + + )} + + ); +} + +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' }, +}); diff --git a/app/(tabs)/capacity_plan.tsx b/app/(tabs)/capacity_plan.tsx new file mode 100644 index 00000000..0cc06fcb --- /dev/null +++ b/app/(tabs)/capacity_plan.tsx @@ -0,0 +1,80 @@ +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(null) + const [period, setPeriod] = useState('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 ( + }> + + {(['30d','60d','90d'] as Period[]).map(p => ( + setPeriod(p)}> + {p} + + ))} + + + {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 ( + + + + {item.server_name ?? item.name ?? 'N/A'} + {item.inst_name ?? ''} · {item.resource_type ?? 'CPU'} + + {util}% + + + + + {item.recommendation && ( + AI 권고: {item.recommendation} + )} + + ) + })} + + {predictions.length === 0 && !loading && ( + 예측 데이터가 없습니다. + )} + + ) +} + +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 }, +}) diff --git a/app/(tabs)/change_calendar.tsx b/app/(tabs)/change_calendar.tsx new file mode 100644 index 00000000..5e2ae06b --- /dev/null +++ b/app/(tabs)/change_calendar.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react' +import { + View, Text, TouchableOpacity, StyleSheet, FlatList, ActivityIndicator, +} from 'react-native' +import { COLORS } from '../../constants/Config' +import { getChangeCalendar } from '../../services/api' + +const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'] + +export default function ChangeCalendarScreen() { + const [now] = useState(new Date()) + const [year, setYear] = useState(now.getFullYear()) + const [month, setMonth] = useState(now.getMonth() + 1) + const [changes, setChanges] = useState([]) + const [selectedDay, setSelectedDay] = useState(null) + const [loading, setLoading] = useState(false) + + useEffect(() => { + setLoading(true) + getChangeCalendar(`${year}-${String(month).padStart(2, '0')}`) + .then(r => setChanges(r.data?.items ?? r.data ?? [])) + .catch(() => setChanges([])) + .finally(() => setLoading(false)) + }, [year, month]) + + const prevMonth = () => { if (month === 1) { setYear(y => y-1); setMonth(12) } else setMonth(m => m-1) } + const nextMonth = () => { if (month === 12) { setYear(y => y+1); setMonth(1) } else setMonth(m => m+1) } + + const daysInMonth = new Date(year, month, 0).getDate() + const firstDay = new Date(year, month - 1, 1).getDay() + + const hasChange = (day: number) => + changes.some(c => new Date(c.scheduled_at ?? c.created_at).getDate() === day) + + const dayChanges = selectedDay + ? changes.filter(c => new Date(c.scheduled_at ?? c.created_at).getDate() === selectedDay) + : [] + + const cells: (number | null)[] = [ + ...Array(firstDay).fill(null), + ...Array.from({ length: daysInMonth }, (_, i) => i + 1), + ] + + return ( + + {/* 헤더 */} + + ‹ + {year}년 {month}월 + › + + + {/* 요일 */} + + {WEEKDAYS.map(d => {d})} + + + {/* 날짜 그리드 */} + {loading ? : ( + + {cells.map((day, idx) => ( + day && setSelectedDay(day === selectedDay ? null : day)} + disabled={!day} + > + {day && ( + <> + {day} + {hasChange(day) && } + > + )} + + ))} + + )} + + {/* 선택 날짜 변경 목록 */} + {selectedDay && ( + + {month}/{selectedDay} 변경 관리 + {dayChanges.length === 0 + ? 해당 날짜의 변경 항목이 없습니다. + : dayChanges.map((c, i) => ( + + {c.title ?? c.subject} + {c.status} · {c.requester ?? c.requested_by} + + )) + } + + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 16, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, + arrow: { fontSize: 24, color: COLORS.accent, paddingHorizontal: 8 }, + month: { fontSize: 16, fontWeight: '700', color: COLORS.text }, + weekRow: { flexDirection: 'row', backgroundColor: '#fff', paddingVertical: 6 }, + weekDay: { flex: 1, textAlign: 'center', fontSize: 12, fontWeight: '600', color: COLORS.muted }, + grid: { flexDirection: 'row', flexWrap: 'wrap', backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, + cell: { width: '14.28%', aspectRatio: 1, alignItems: 'center', justifyContent: 'center' }, + cellSelected: { backgroundColor: COLORS.light }, + dayText: { fontSize: 14, color: COLORS.text }, + dayTextSelected: { color: COLORS.accent, fontWeight: '700' }, + dot: { width: 4, height: 4, borderRadius: 2, backgroundColor: COLORS.accent, marginTop: 2 }, + list: { flex: 1, padding: 14 }, + listTitle: { fontSize: 15, fontWeight: '700', color: COLORS.text, marginBottom: 10 }, + empty: { color: COLORS.muted, fontSize: 14 }, + changeCard: { backgroundColor: '#fff', borderRadius: 8, padding: 10, marginBottom: 6, elevation: 1 }, + changeTitle: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + changeMeta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, +}) diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index ca26cc89..68eac814 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -3,40 +3,109 @@ import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, KeyboardAvoidingView, Platform, ActivityIndicator, } from 'react-native' -import { COLORS } from '../../constants/Config' +import { router } from 'expo-router' +import { COLORS, API_BASE } from '../../constants/Config' import { sendAIMessage } from '../../services/api' import { useAuth } from '../../hooks/useAuth' +import { authFetch } from '../../utils/auth' import LineIcon from '../../components/LineIcon' +import { VoiceInput } from '../../components/VoiceInput' +import { NextActions } from '../../components/NextActions' +import { generateJSON, DEFAULT_TEXT_MODEL } from '../../lib/ollama' interface Msg { id: number; role: 'user' | 'ai'; text: string; time: string } -const QUICK = ['서버 상태 확인', 'SR 목록 보여줘', '최근 인시던트', '라이선스 현황'] +// Ollama가 자연어를 분류하는 명령 스키마 +interface Command { + intent: 'query_server' | 'create_sr' | 'query_sr' | 'open_screen' | 'chat' + server_id?: string + screen?: string + reply?: string +} + +const QUICK = ['서버 상태 확인', 'SR 목록 보여줘', '최근 인시던트', '긴급 SR 등록'] export default function ChatScreen() { const { user } = useAuth() - const [msgs, setMsgs] = useState([ - { id: 0, role: 'ai', text: `안녕하세요 ${user?.display_name ?? ''}님! 👋\nGUARDiA AI 어시스턴트입니다.\n무엇을 도와드릴까요?`, time: now() }, + const [msgs, setMsgs] = useState([ + { id: 0, role: 'ai', text: `안녕하세요 ${user?.display_name ?? ''}님! 👋\nGUARDiA AI 어시스턴트입니다.\n자연어로 명령하시면 SR 등록·조회를 자동 실행합니다.`, time: now() }, ]) - const [input, setInput] = useState('') + const [input, setInput] = useState('') const [loading, setLoading] = useState(false) + const [lastContext, setLastContext] = useState('AI 챗봇 대기 중') const scrollRef = useRef(null) function now() { return new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' }) } + function pushAI(text: string) { + setMsgs(m => [...m, { id: Date.now() + Math.random(), role: 'ai', text, time: now() }]) + } + + // 자연어 → Ollama 의도 분류 → 명령 실행 + async function classifyAndRun(text: string): Promise { + const prompt = + `당신은 ITSM 운영 어시스턴트입니다. 운영자 입력: "${text}". ` + + `의도를 JSON으로만 분류하세요: ` + + `{"intent":"query_server|create_sr|query_sr|open_screen|chat",` + + `"server_id":"서버ID(있으면)","screen":"sr|dr|network|notifications(있으면)","reply":"chat일 때 한국어 답변"}` + const cmd = await generateJSON(DEFAULT_TEXT_MODEL, prompt, { intent: 'chat', reply: '' }) + + switch (cmd.intent) { + case 'query_server': { + if (!cmd.server_id) return '조회할 서버를 알려주세요. (예: "서버 001 상태")' + try { + const res = await authFetch(`${API_BASE}/api/servers/${encodeURIComponent(cmd.server_id)}`) + if (res.ok) { + const d = await res.json() + // 보안: ip/ssh/pw 등 민감 필드는 표시하지 않음 + const name = d.name ?? d.hostname ?? cmd.server_id + const status = d.status ?? d.state ?? '알 수 없음' + return `🖥️ 서버 ${name}\n상태: ${status}${d.cpu ? `\nCPU: ${d.cpu}%` : ''}${d.mem ? `\n메모리: ${d.mem}%` : ''}` + } + return `서버 ${cmd.server_id} 정보를 가져오지 못했습니다.` + } catch { + return '서버 조회 중 오류가 발생했습니다.' + } + } + case 'create_sr': + setTimeout(() => router.push('/sr'), 400) + return '📋 SR 등록 화면을 엽니다.' + case 'query_sr': + setTimeout(() => router.push('/sr'), 400) + return '📋 SR 목록 화면으로 이동합니다.' + case 'open_screen': { + const screen = cmd.screen ?? 'index' + setTimeout(() => router.push(`/${screen}` as any), 400) + return `화면(${screen})으로 이동합니다.` + } + default: + return cmd.reply || '' + } + } + const send = async (text = input) => { if (!text.trim() || loading) return - const userMsg: Msg = { id: Date.now(), role: 'user', text: text.trim(), time: now() } - setMsgs(m => [...m, userMsg]) + const trimmed = text.trim() + setMsgs(m => [...m, { id: Date.now(), role: 'user', text: trimmed, time: now() }]) setInput('') setLoading(true) + setLastContext(`사용자 요청: ${trimmed}`) try { - const r = await sendAIMessage(text.trim()) - const reply = r.data?.reply ?? r.data?.message ?? r.data?.response ?? '응답을 받았습니다.' - setMsgs(m => [...m, { id: Date.now()+1, role: 'ai', text: reply, time: now() }]) + // 1) Ollama 의도 분류 + 명령 실행 + const local = await classifyAndRun(trimmed) + if (local) { + pushAI(local) + } else { + // 2) 폴백: ITSM 챗봇 API + const r = await sendAIMessage(trimmed) + const reply = r.data?.reply ?? r.data?.message ?? r.data?.response ?? '응답을 받았습니다.' + pushAI(reply) + } } catch { - setMsgs(m => [...m, { id: Date.now()+1, role: 'ai', - text: '현재 AI 서버에 연결할 수 없습니다. Ollama 서버 상태를 확인해주세요.', time: now() }]) - } finally { setLoading(false) } + pushAI('현재 AI 서버에 연결할 수 없습니다. Ollama 서버 상태를 확인해주세요.') + } finally { + setLoading(false) + } setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 100) } @@ -45,14 +114,13 @@ export default function ChatScreen() { }, [msgs]) return ( - + - {/* 메시지 목록 */} {msgs.map(m => ( {m.role === 'ai' && ( - + )} @@ -64,7 +132,7 @@ export default function ChatScreen() { ))} {loading && ( - + @@ -72,9 +140,15 @@ export default function ChatScreen() { )} + + {/* #20 다음 명령 제안 */} + {!loading && ( + + send(a)} /> + + )} - {/* 빠른 질문 */} {QUICK.map(q => ( @@ -84,13 +158,14 @@ export default function ChatScreen() { ))} - {/* 입력창 */} + {/* #25 음성 입력 → 입력창 자동 전달 (ko-KR) */} + { if (t) setInput(t) }} size="small" /> ([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/citizen/requests'); setItems(r.data?.requests ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const assign = async (item: any) => { + try { + await client.post('/api/tasks', { title: `[민원] ${item.title ?? item.subject}`, description: item.description ?? item.content, priority: 'HIGH', sr_type: 'REQUEST', source: 'citizen_portal' }) + Alert.alert('완료', 'SR로 전환됐습니다.') + load() + } catch { Alert.alert('오류', '전환에 실패했습니다.') } + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={민원 접수 내역이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={시민 민원 접수 현황} + renderItem={({ item }) => ( + + + + {item.title ?? item.subject} + {item.citizen_name ?? '익명'} · {item.created_at?.slice(0, 16) ?? ''} + + + {item.status === 'pending' ? '대기' : '처리중'} + + + {item.description ?? item.content ?? ''} + {item.status === 'pending' && ( + assign(item)}> + SR 전환 + + )} + + )} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 6 }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + statusBadge: { borderRadius: 4, paddingHorizontal: 8, paddingVertical: 3 }, + statusText: { fontSize: 11, fontWeight: '700' }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + srBtn: { backgroundColor: COLORS.blue + '15', borderRadius: 6, padding: 8, alignItems: 'center' }, + srText: { color: COLORS.blue, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/cost_advice.tsx b/app/(tabs)/cost_advice.tsx new file mode 100644 index 00000000..92e97f84 --- /dev/null +++ b/app/(tabs)/cost_advice.tsx @@ -0,0 +1,87 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const SAVING_COLOR: Record = { HIGH: COLORS.success, MEDIUM: COLORS.warning, LOW: COLORS.muted } + +export default function CostAdviceScreen() { + const [items, setItems] = useState([]) + const [savings, setSavings] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const [r, s] = await Promise.all([ + client.get('/api/cost-optimizer/recommendations'), + client.get('/api/mobile2/savings-dashboard'), + ]) + setItems(r.data?.recommendations ?? r.data?.items ?? []) + setSavings(s.data) + } catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const apply = (item: any) => { + Alert.alert('조치 확인', `${item.title ?? item.resource}에 대한 비용 절감 조치를 SR로 등록하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: 'SR 등록', onPress: async () => { + try { + await client.post('/api/tasks', { title: `비용 절감 조치: ${item.title ?? item.resource}`, description: item.action ?? item.description, priority: 'MEDIUM', sr_type: 'CHANGE' }) + Alert.alert('완료', 'SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + }}, + ]) + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={비용 절감 권고가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + ListHeaderComponent={savings && ( + + 이번달 절감 효과 + ₩{(savings.total_saved_krw ?? 0).toLocaleString()} + + )} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const impact = item.impact ?? item.priority ?? 'MEDIUM' + return ( + + + {impact} + 절감 {item.estimated_saving_krw ? `₩${item.estimated_saving_krw.toLocaleString()}` : '-'} + + {item.title ?? item.resource} + {item.action ?? item.description ?? ''} + apply(item)}> + SR로 조치 + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + savings: { backgroundColor: COLORS.success, borderRadius: 12, padding: 16, marginBottom: 8, alignItems: 'center' }, + savingsLabel: { color: '#fff', fontSize: 12, opacity: 0.9 }, + savingsAmount: { color: '#fff', fontSize: 28, fontWeight: '800', marginTop: 4 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }, + badge: { color: '#fff', fontSize: 10, fontWeight: '700', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 4 }, + saving: { fontSize: 12, color: COLORS.success, fontWeight: '700' }, + title: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 8 }, + applyBtn: { backgroundColor: COLORS.light, borderRadius: 6, padding: 8, alignItems: 'center' }, + applyText: { color: COLORS.blue, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/cowork_sr.tsx b/app/(tabs)/cowork_sr.tsx new file mode 100644 index 00000000..123947ad --- /dev/null +++ b/app/(tabs)/cowork_sr.tsx @@ -0,0 +1,101 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { View, Text, ScrollView, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface CoworkMsg { id: string; author: string; role: string; text: string; ts: string; type: 'chat' | 'action' | 'status' } + +const SAMPLE_MSGS: CoworkMsg[] = [ + { id: '1', author: '김철수', role: 'engineer', text: 'db-01 서버 CPU 90% 넘어갔어요. 확인 부탁드립니다.', ts: '10:02', type: 'chat' }, + { id: '2', author: 'AI', role: 'ai', text: '원인 분석 완료: db-01 슬로우 쿼리 20개 감지. 쿼리 최적화 또는 서버 재시작 권장.', ts: '10:02', type: 'action' }, + { id: '3', author: '이영희', role: 'pm', text: 'SR 우선순위 Critical로 변경했습니다.', ts: '10:03', type: 'status' }, +]; + +export default function CoworkSRScreen() { + const [srId] = useState('SR-2042'); + const [messages, setMessages] = useState(SAMPLE_MSGS); + const [input, setInput] = useState(''); + const [participants] = useState([{ name: '김철수', role: 'engineer', online: true }, { name: '이영희', role: 'pm', online: true }, { name: 'AI', role: 'ai', online: true }]); + const scrollRef = useRef(null); + + const send = async () => { + if (!input.trim()) return; + const msg: CoworkMsg = { id: Date.now().toString(), author: '나', role: 'engineer', text: input, ts: new Date().toLocaleTimeString('ko', { hour: '2-digit', minute: '2-digit' }), type: 'chat' }; + setMessages(prev => [...prev, msg]); + setInput(''); + try { + await fetch(`${ITSM_BASE}/api/sr-chat/${srId}/messages`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: input }), + }); + } catch {} + setTimeout(() => scrollRef.current?.scrollToEnd(), 100); + }; + + const roleColor = (role: string) => ({ engineer: '#00A0C8', pm: '#ffbb00', ai: '#44bb44', sm: '#bb44bb' })[role] || '#888'; + + return ( + + + {srId} 공동 대응 + + {participants.map((p, i) => ( + + {p.name[0]} + {p.online && } + + ))} + + + + scrollRef.current?.scrollToEnd()}> + {messages.map(msg => ( + + {msg.author !== '나' && ( + + {msg.author[0]} + + )} + + {msg.author !== '나' && {msg.author}} + {msg.text} + {msg.ts} + + + ))} + + + + + + 전송 + + + + ); +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#0A0E1A' }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 14, borderBottomWidth: 1, borderBottomColor: '#333' }, + srTitle: { color: '#fff', fontWeight: '700', fontSize: 16 }, + participants: { flexDirection: 'row', gap: 6 }, + avatar: { width: 30, height: 30, borderRadius: 15, borderWidth: 1, alignItems: 'center', justifyContent: 'center', position: 'relative' }, + avatarText: { fontSize: 13, fontWeight: '700' }, + onlineDot: { position: 'absolute', bottom: 0, right: 0, width: 8, height: 8, borderRadius: 4, backgroundColor: '#44bb44', borderWidth: 1, borderColor: '#0A0E1A' }, + messages: { flex: 1, padding: 12 }, + msgRow: { flexDirection: 'row', marginBottom: 12, alignItems: 'flex-end' }, + msgRowRight: { justifyContent: 'flex-end' }, + msgAvatar: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginRight: 8 }, + msgAvatarText: { fontSize: 12, fontWeight: '700' }, + msgBubble: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 10, maxWidth: '75%', borderWidth: 1, borderColor: '#333' }, + msgBubbleAction: { borderColor: '#44bb44', backgroundColor: '#44bb4422' }, + msgBubbleStatus: { borderColor: '#ffbb00', backgroundColor: '#ffbb0022' }, + msgBubbleSelf: { backgroundColor: '#003366', borderColor: '#00A0C8' }, + msgAuthor: { fontSize: 11, fontWeight: '700', marginBottom: 4 }, + msgText: { color: '#fff', fontSize: 14 }, + msgTs: { color: '#555', fontSize: 10, marginTop: 4, textAlign: 'right' }, + inputRow: { flexDirection: 'row', padding: 12, borderTopWidth: 1, borderTopColor: '#333' }, + input: { flex: 1, backgroundColor: '#1A1F2E', borderRadius: 10, color: '#fff', paddingHorizontal: 14, paddingVertical: 10, borderWidth: 1, borderColor: '#333', marginRight: 10 }, + sendBtn: { backgroundColor: '#00A0C8', paddingHorizontal: 16, borderRadius: 10, justifyContent: 'center' }, + sendText: { color: '#fff', fontWeight: '700' }, +}); diff --git a/app/(tabs)/csap_audit_prep.tsx b/app/(tabs)/csap_audit_prep.tsx new file mode 100644 index 00000000..4b1037c7 --- /dev/null +++ b/app/(tabs)/csap_audit_prep.tsx @@ -0,0 +1,79 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function CSAPAuditPrepScreen() { + const [items, setItems] = useState([]) + const [summary, setSummary] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const [r, s] = await Promise.all([ + client.get('/api/compliance/csap/items'), + client.get('/api/compliance/csap/dashboard'), + ]) + setItems(r.data?.items ?? r.data ?? []) + setSummary(s.data) + } catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const autoSR = async (item: any) => { + try { + await client.post('/api/tasks', { title: `CSAP 조치: ${item.control_id} ${item.title}`, description: item.remediation ?? item.description, priority: 'HIGH', sr_type: 'COMPLIANCE' }) + Alert.alert('완료', 'SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + } + + const failItems = items.filter(i => i.status === 'fail' || i.status === 'FAIL') + const passCount = items.length - failItems.length + const pct = items.length > 0 ? Math.round((passCount / items.length) * 100) : 0 + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={{loading ? '' : '미준수 항목이 없습니다. CSAP 심사 준비 완료!'}} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={ + + + {pct}% + CSAP 준수율 + + 미준수 {failItems.length}건 / 전체 {items.length}건 + + } + renderItem={({ item }) => ( + + {item.control_id} — {item.title ?? item.name} + {item.description ?? ''} + autoSR(item)}> + 자동 SR 등록 + + + )} + /> + ) +} + +const s = StyleSheet.create({ + header: { backgroundColor: '#fff', borderRadius: 12, padding: 16, marginBottom: 12, alignItems: 'center', elevation: 2 }, + pctRow: { flexDirection: 'row', alignItems: 'baseline', gap: 8 }, + pctNum: { fontSize: 52, fontWeight: '900', color: COLORS.accent }, + pctLabel:{ fontSize: 14, color: COLORS.muted }, + sub: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + ctrlId: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + btn: { backgroundColor: COLORS.danger + '15', borderRadius: 6, padding: 8, alignItems: 'center' }, + btnText: { color: COLORS.danger, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.success, marginTop: 40, fontSize: 14, fontWeight: '700' }, +}) diff --git a/app/(tabs)/csap_dashboard.tsx b/app/(tabs)/csap_dashboard.tsx new file mode 100644 index 00000000..cf1bfb3b --- /dev/null +++ b/app/(tabs)/csap_dashboard.tsx @@ -0,0 +1,106 @@ +import React, { useState, useCallback } from 'react' +import { + View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert, RefreshControl, +} from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getCSAPDashboard, getCSAPItems, createSR } from '../../services/api' + +function DonutGauge({ score }: { score: number }) { + const color = score >= 85 ? COLORS.success : score >= 70 ? COLORS.warning : COLORS.danger + return ( + + + + + {score}% + CSAP + + + + ) +} + +const g = StyleSheet.create({ + wrap: { alignItems: 'center', marginVertical: 20 }, + ring: { width: 120, height: 120, borderRadius: 60, borderWidth: 12, justifyContent: 'center', alignItems: 'center' }, + progress: { position: 'absolute', width: 120, height: 120, borderRadius: 60, borderWidth: 12, borderTopColor: 'transparent', borderRightColor: 'transparent' }, + inner: { alignItems: 'center' }, + score: { fontSize: 26, fontWeight: '800' }, + label: { fontSize: 11, color: COLORS.muted, fontWeight: '600' }, +}) + +export default function CSAPDashboardScreen() { + const [dashboard, setDashboard] = useState(null) + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const [d, i] = await Promise.all([getCSAPDashboard(), getCSAPItems()]) + setDashboard(d.data) + setItems((i.data?.items ?? i.data ?? []).filter((x: any) => x.status === 'non_compliant' || x.result === 'FAIL')) + } catch {} finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const createActionSR = async (item: any) => { + Alert.alert('즉시 조치 SR 등록', `"${item.title ?? item.check_item}" 미준수 항목으로 SR을 등록하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '등록', onPress: async () => { + try { + await createSR({ title: `[CSAP] ${item.title ?? item.check_item}`, description: item.description ?? '미준수 항목 조치 필요', priority: 'HIGH', sr_type: 'OTHER' }) + Alert.alert('완료', 'SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + }}, + ]) + } + + const score = dashboard?.overall_score ?? dashboard?.compliance_rate ?? 0 + + return ( + }> + + + {/* 영역별 바 */} + {(dashboard?.domains ?? []).map((d: any, i: number) => ( + + {d.name} + + = 80 ? COLORS.success : COLORS.warning }]} /> + + {d.rate ?? 0}% + + ))} + + {/* 미준수 항목 */} + 미준수 항목 ({items.length}건) + {items.map((item, i) => ( + + {item.title ?? item.check_item} + {item.description ?? item.detail ?? '-'} + createActionSR(item)}> + 즉시 조치 SR 등록 + + + ))} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + domainRow: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 6, gap: 8 }, + domainName: { width: 80, fontSize: 12, color: COLORS.text }, + barBg: { flex: 1, height: 8, backgroundColor: COLORS.border, borderRadius: 4 }, + barFill: { height: 8, borderRadius: 4 }, + domainRate: { width: 40, fontSize: 12, color: COLORS.muted, textAlign: 'right' }, + sectionTitle: { fontSize: 15, fontWeight: '700', color: COLORS.text, padding: 16, paddingBottom: 8 }, + card: { backgroundColor: '#fff', borderRadius: 10, margin: 12, marginTop: 0, padding: 14, elevation: 1 }, + itemTitle: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginBottom: 4 }, + itemDesc: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + srBtn: { backgroundColor: COLORS.danger, borderRadius: 6, padding: 8, alignItems: 'center' }, + srBtnText: { color: '#fff', fontWeight: '700', fontSize: 12 }, +}) diff --git a/app/(tabs)/cve_detail.tsx b/app/(tabs)/cve_detail.tsx new file mode 100644 index 00000000..7abbb0f5 --- /dev/null +++ b/app/(tabs)/cve_detail.tsx @@ -0,0 +1,97 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const SEV_COLOR: Record = { CRITICAL: '#dc2626', HIGH: '#f97316', MEDIUM: COLORS.warning, LOW: COLORS.muted, NONE: '#94a3b8' } + +export default function CVEDetailScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/patches/cve'); setItems(r.data?.cves ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const applyPatch = async (item: any) => { + Alert.alert('패치 적용', `${item.cve_id}를 대상 서버에 적용하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '적용', style: 'destructive', onPress: async () => { + try { + await client.post(`/api/patches/${item.cve_id}/apply`) + Alert.alert('완료', '패치 적용 SR이 생성됐습니다.') + load() + } catch { Alert.alert('오류', '패치 적용에 실패했습니다.') } + }}, + ]) + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={CVE 항목이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const sev = item.severity ?? 'MEDIUM' + const color = SEV_COLOR[sev] ?? COLORS.muted + const cvss = item.cvss_score ?? item.score ?? 0 + return ( + + + {item.cve_id ?? item.id} + + {sev} + + + + CVSS + + + + {Number(cvss).toFixed(1)} + + {item.description ?? item.summary ?? ''} + + 영향 서버: {item.affected_count ?? item.servers?.length ?? 0}개 + 공개일: {item.published_at?.slice(0, 10) ?? '-'} + + {item.status !== 'PATCHED' && ( + applyPatch(item)}> + 패치 적용 + + )} + {item.status === 'PATCHED' && 패치 완료} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginBottom: 8, elevation: 1 }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }, + cveId: { fontSize: 14, fontWeight: '800' }, + sevBadge: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 4 }, + sevText: { color: '#fff', fontSize: 10, fontWeight: '700' }, + cvssRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 8 }, + cvssLabel: { fontSize: 11, color: COLORS.muted, width: 36 }, + cvssBar: { flex: 1, height: 6, backgroundColor: COLORS.border, borderRadius: 3, overflow: 'hidden' }, + cvssFill: { height: '100%', borderRadius: 3 }, + cvssScore: { fontSize: 14, fontWeight: '700', width: 28 }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 8, lineHeight: 18 }, + meta: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 10 }, + metaText: { fontSize: 11, color: COLORS.muted }, + patchBtn: { backgroundColor: COLORS.danger, borderRadius: 8, padding: 10, alignItems: 'center' }, + patchText: { color: '#fff', fontSize: 13, fontWeight: '700' }, + patched: { color: COLORS.success, fontSize: 13, fontWeight: '700', textAlign: 'center' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/delegation.tsx b/app/(tabs)/delegation.tsx new file mode 100644 index 00000000..1be056cb --- /dev/null +++ b/app/(tabs)/delegation.tsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react' +import { + View, Text, TextInput, TouchableOpacity, StyleSheet, + ScrollView, Alert, ActivityIndicator, +} from 'react-native' +import { COLORS } from '../../constants/Config' +import { getDelegation, setDelegation, cancelDelegation, searchUsers } from '../../services/api' + +export default function DelegationScreen() { + const [current, setCurrent] = useState(null) + const [loading, setLoading] = useState(true) + const [query, setQuery] = useState('') + const [users, setUsers] = useState([]) + const [selected, setSelected] = useState(null) + const [startDate, setStartDate] = useState('') + const [endDate, setEndDate] = useState('') + const [reason, setReason] = useState('') + const [saving, setSaving] = useState(false) + + useEffect(() => { + getDelegation().then(r => setCurrent(r.data)).catch(() => {}).finally(() => setLoading(false)) + }, []) + + const search = async () => { + if (!query.trim()) return + try { + const r = await searchUsers(query) + setUsers(r.data?.items ?? r.data ?? []) + } catch { setUsers([]) } + } + + const save = async () => { + if (!selected || !startDate || !endDate || !reason.trim()) { + Alert.alert('입력 오류', '모든 항목을 입력해주세요.') + return + } + setSaving(true) + try { + await setDelegation({ delegate_to: selected.id, start_date: startDate, end_date: endDate, reason }) + Alert.alert('완료', '대리결재가 설정됐습니다.') + const r = await getDelegation() + setCurrent(r.data) + } catch { Alert.alert('오류', '저장 중 오류가 발생했습니다.') } + finally { setSaving(false) } + } + + const cancel = async (id: number) => { + Alert.alert('취소 확인', '대리결재를 해제하시겠습니까?', [ + { text: '아니오', style: 'cancel' }, + { text: '해제', style: 'destructive', onPress: async () => { + await cancelDelegation(id) + setCurrent(null) + }}, + ]) + } + + if (loading) return + + return ( + + {current && ( + + 현재 대리결재 + 대리인: {current.delegate_name ?? current.delegate_to} + 기간: {current.start_date} ~ {current.end_date} + 사유: {current.reason} + cancel(current.id)}> + 해제 + + + )} + + 대리인 설정 + + + + + 검색 + + + + {users.map((u: any) => ( + setSelected(u)} + > + {u.display_name ?? u.username} + {u.role} + + ))} + + {selected && 선택: {selected.display_name ?? selected.username}} + + + + + + + {saving ? '저장 중...' : '대리결재 설정'} + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 16, elevation: 1 }, + label: { fontSize: 12, color: COLORS.muted, marginBottom: 4 }, + value: { fontSize: 14, color: COLORS.text, marginBottom: 2 }, + cancelBtn: { marginTop: 8, backgroundColor: COLORS.danger, borderRadius: 6, padding: 8, alignItems: 'center' }, + cancelBtnText: { color: '#fff', fontWeight: '700' }, + sectionTitle: { fontSize: 16, fontWeight: '700', color: COLORS.text, marginBottom: 12 }, + row: { flexDirection: 'row', gap: 8, marginBottom: 8 }, + input: { backgroundColor: '#fff', borderRadius: 8, borderWidth: 1, borderColor: COLORS.border, paddingHorizontal: 12, paddingVertical: 10, marginBottom: 10, fontSize: 14, color: COLORS.text }, + searchBtn: { backgroundColor: COLORS.accent, borderRadius: 8, paddingHorizontal: 16, justifyContent: 'center' }, + searchBtnText: { color: '#fff', fontWeight: '700' }, + userItem: { backgroundColor: '#fff', borderRadius: 8, padding: 10, marginBottom: 4, flexDirection: 'row', justifyContent: 'space-between', borderWidth: 1, borderColor: COLORS.border }, + userItemSelected: { borderColor: COLORS.accent, backgroundColor: COLORS.light }, + userName: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + userRole: { fontSize: 12, color: COLORS.muted }, + selectedText: { fontSize: 13, color: COLORS.accent, marginBottom: 8 }, + saveBtn: { backgroundColor: COLORS.accent, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 8 }, + saveBtnText: { color: '#fff', fontWeight: '800', fontSize: 15 }, +}) diff --git a/app/(tabs)/dependency_map.tsx b/app/(tabs)/dependency_map.tsx new file mode 100644 index 00000000..6f92b269 --- /dev/null +++ b/app/(tabs)/dependency_map.tsx @@ -0,0 +1,68 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl, ActivityIndicator } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +function ServiceNode({ name, deps, allNodes }: { name: string; deps: string[]; allNodes: Record }) { + return ( + + {name} + {deps.length > 0 && ( + + {deps.map((d, i) => ( + + + {d} + + ))} + + )} + + ) +} +const n = StyleSheet.create({ + wrap: { marginBottom: 12 }, + node: { backgroundColor: COLORS.accent, borderRadius: 8, paddingHorizontal: 14, paddingVertical: 8, alignSelf: 'flex-start' }, + name: { color: '#fff', fontSize: 13, fontWeight: '700' }, + depsWrap: { marginLeft: 20, marginTop: 4 }, + depRow: { flexDirection: 'row', alignItems: 'center', marginTop: 4 }, + arrow: { width: 20, height: 2, backgroundColor: COLORS.border, marginRight: 6 }, + depBox: { backgroundColor: COLORS.light, borderRadius: 6, paddingHorizontal: 10, paddingVertical: 4 }, + depText: { fontSize: 12, color: COLORS.text }, +}) + +export default function DependencyMapScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/knowledge-graph/service-map'); setData(r.data) } + catch { setData(null) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + if (loading) return + if (!data) return 서비스 의존성 맵을 불러올 수 없습니다. + + const nodes: Record = data.dependencies ?? data.services ?? {} + + return ( + } contentContainerStyle={{ padding: 16 }}> + 서비스 의존성 맵 + 노드: {Object.keys(nodes).length}개 · 관계: {Object.values(nodes).flat().length}개 + {Object.entries(nodes).map(([name, deps]) => ( + + ))} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + title: { fontSize: 20, fontWeight: '800', color: COLORS.text, marginBottom: 4 }, + sub: { fontSize: 12, color: COLORS.muted, marginBottom: 16 }, +}) diff --git a/app/(tabs)/deploy_history.tsx b/app/(tabs)/deploy_history.tsx new file mode 100644 index 00000000..12e59ec6 --- /dev/null +++ b/app/(tabs)/deploy_history.tsx @@ -0,0 +1,72 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getDeployHistory } from '../../services/api' + +const STATUS_COLOR: Record = { + SUCCESS: COLORS.success, COMPLETED: COLORS.success, + FAILURE: COLORS.danger, FAILED: COLORS.danger, + RUNNING: COLORS.accent, IN_PROGRESS: COLORS.accent, +} + +export default function DeployHistoryScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getDeployHistory(); setItems(r.data?.items ?? r.data ?? []) } + catch { setItems([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={배포 이력이 없습니다.} + contentContainerStyle={{ padding: 12, paddingLeft: 40 }} + style={{ backgroundColor: COLORS.bg }} + renderItem={({ item, index }) => { + const color = STATUS_COLOR[item.status ?? ''] ?? COLORS.muted + const dur = item.duration_sec ? `${Math.ceil(item.duration_sec / 60)}분` : '-' + return ( + + {/* 타임라인 */} + + + {index < items.length - 1 && } + + + + {item.project ?? 'N/A'} + + {item.status ?? '-'} + + + {item.started_at?.slice(0, 16).replace('T', ' ')} · {dur} · {item.deployed_by ?? '-'} + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + row: { flexDirection: 'row', marginBottom: 0 }, + timeline: { position: 'absolute', left: -28, top: 0, bottom: 0, alignItems: 'center', width: 16 }, + dot: { width: 12, height: 12, borderRadius: 6, marginTop: 14 }, + line: { flex: 1, width: 2, backgroundColor: COLORS.border, marginTop: 2 }, + content: { flex: 1, backgroundColor: '#fff', borderRadius: 10, padding: 12, marginBottom: 8, elevation: 1 }, + headerRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, + project: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + badgeText: { fontSize: 10, color: '#fff', fontWeight: '700' }, + meta: { fontSize: 12, color: COLORS.muted }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/devices.tsx b/app/(tabs)/devices.tsx new file mode 100644 index 00000000..d5e19bde --- /dev/null +++ b/app/(tabs)/devices.tsx @@ -0,0 +1,175 @@ +/** + * #33 등록 디바이스 관리 + * GET /api/auth/devices — 디바이스 목록 + * DELETE /api/auth/devices/{id} — 등록 해제 + * 현재 기기는 삭제 버튼 비활성화 + */ +import { useCallback, useEffect, useState } from 'react' +import { + View, Text, FlatList, TouchableOpacity, StyleSheet, + RefreshControl, Alert, ActivityIndicator, Platform, +} from 'react-native' +import { COLORS } from '../../constants/Config' +import { getDevices, deleteDevice } from '../../services/api' +import LineIcon from '../../components/LineIcon' + +interface Device { + id: string | number + name?: string + device_name?: string + os?: string + platform?: string + last_seen?: string + last_active_at?: string + is_current?: boolean + current?: boolean +} + +function osIcon(os?: string): Parameters[0]['name'] { + const v = (os ?? '').toLowerCase() + if (v.includes('ios') || v.includes('iphone') || v.includes('mac')) return 'lock' + if (v.includes('android')) return 'server' + return 'dashboard' +} + +function fmt(d?: string): string { + if (!d) return '-' + try { + const dt = new Date(d) + if (isNaN(dt.getTime())) return d + return dt.toLocaleString('ko-KR', { dateStyle: 'medium', timeStyle: 'short' }) + } catch { + return d + } +} + +export default function DevicesScreen() { + const [devices, setDevices] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + + const load = useCallback(async (isRefresh = false) => { + isRefresh ? setRefresh(true) : setLoading(true) + try { + const r = await getDevices() + const list: Device[] = Array.isArray(r.data) ? r.data : r.data?.items ?? [] + setDevices(list) + } catch { + setDevices([]) + } finally { + setLoading(false) + setRefresh(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + const handleRemove = (dev: Device) => { + const name = dev.name ?? dev.device_name ?? '이 기기' + Alert.alert('디바이스 등록 해제', `"${name}"의 등록을 해제하시겠습니까?\n해당 기기는 다시 로그인해야 합니다.`, [ + { text: '취소', style: 'cancel' }, + { + text: '해제', style: 'destructive', + onPress: async () => { + try { + await deleteDevice(dev.id) + setDevices((prev) => prev.filter((d) => d.id !== dev.id)) + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? '등록 해제에 실패했습니다.') + } + }, + }, + ]) + } + + if (loading) { + return ( + + + + ) + } + + return ( + String(d.id)} + refreshControl={ load(true)} tintColor={COLORS.accent} />} + ListHeaderComponent={ + + 등록된 디바이스 + 이 계정으로 로그인된 기기 {devices.length}대 + + } + ListEmptyComponent={ + + 등록된 디바이스가 없습니다. + + } + contentContainerStyle={devices.length === 0 ? { flexGrow: 1 } : undefined} + renderItem={({ item }) => { + const isCurrent = item.is_current ?? item.current ?? false + return ( + + + + + + + {item.name ?? item.device_name ?? '알 수 없는 기기'} + {isCurrent && ( + + 현재 기기 + + )} + + {item.os ?? item.platform ?? Platform.OS} + 마지막 접속: {fmt(item.last_seen ?? item.last_active_at)} + + handleRemove(item)} + > + + {isCurrent ? '사용 중' : '해제'} + + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + center: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 8 }, + headerTitle: { fontSize: 18, fontWeight: '800', color: COLORS.text }, + headerSub: { fontSize: 13, color: COLORS.muted, marginTop: 4 }, + card: { + flexDirection: 'row', alignItems: 'center', gap: 12, + backgroundColor: '#fff', marginHorizontal: 16, marginTop: 10, + borderRadius: 14, padding: 16, + borderWidth: 1, borderColor: COLORS.border, + }, + iconBox: { + width: 42, height: 42, borderRadius: 11, + backgroundColor: 'rgba(0,160,200,.08)', alignItems: 'center', justifyContent: 'center', + }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + name: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + currentBadge: { backgroundColor: '#dcfce7', paddingHorizontal: 8, paddingVertical: 2, borderRadius: 8 }, + currentText: { fontSize: 10, fontWeight: '700', color: '#15803d' }, + meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + removeBtn: { + paddingHorizontal: 14, paddingVertical: 8, borderRadius: 10, + backgroundColor: '#fee2e2', + }, + removeDisabled: { backgroundColor: '#f1f5f9' }, + removeText: { fontSize: 13, fontWeight: '700', color: COLORS.danger }, + removeTextDisabled: { color: COLORS.muted }, + empty: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + emptyText: { color: COLORS.muted, fontSize: 14 }, +}) diff --git a/app/(tabs)/eol_alerts.tsx b/app/(tabs)/eol_alerts.tsx new file mode 100644 index 00000000..f75cb04c --- /dev/null +++ b/app/(tabs)/eol_alerts.tsx @@ -0,0 +1,59 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function EOLAlertsScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/cmdb/eol-software'); setItems(r.data?.items ?? r.data ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const createSR = async (item: any) => { + try { + await client.post('/api/tasks', { title: `EOL 조치: ${item.name} ${item.version ?? ''}`, description: `서버 ${item.server_count ?? '-'}대 영향 — EOL 소프트웨어 교체/업그레이드 필요`, priority: 'HIGH', sr_type: 'CHANGE' }) + Alert.alert('완료', 'SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={EOL 소프트웨어가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const isEOL = !item.eol_date || new Date(item.eol_date) <= new Date() + return ( + + {item.name} {item.version ?? ''} + EOL: {item.eol_date?.slice(0, 10) ?? '이미 만료'} · 서버 {item.server_count ?? 0}대 + {item.note ?? item.description ?? ''} + createSR(item)}> + 교체 SR 등록 + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + meta: { fontSize: 12, color: COLORS.muted, marginBottom: 4 }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + srBtn: { backgroundColor: COLORS.warning + '20', borderRadius: 6, padding: 8, alignItems: 'center' }, + srText: { color: COLORS.warning, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/esignature.tsx b/app/(tabs)/esignature.tsx new file mode 100644 index 00000000..9ba27873 --- /dev/null +++ b/app/(tabs)/esignature.tsx @@ -0,0 +1,94 @@ +import React, { useState, useRef, useCallback } from 'react' +import { View, Text, TouchableOpacity, StyleSheet, Alert, ScrollView, TextInput } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function ESignatureScreen() { + const [docs, setDocs] = useState([]) + const [selected, setSelected] = useState(null) + const [pin, setPin] = useState('') + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/approvals/pending-docs'); setDocs(r.data?.docs ?? r.data?.items ?? []) } + catch { setDocs([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const sign = async () => { + if (!selected) return + if (!pin || pin.length < 4) { Alert.alert('오류', 'PIN 4자리 이상 입력하세요.'); return } + try { + await client.post(`/api/approvals/${selected.id}/sign`, { pin_hash: pin }) + Alert.alert('완료', '전자서명이 완료됐습니다.') + setSelected(null) + setPin('') + load() + } catch { Alert.alert('오류', '서명에 실패했습니다.') } + } + + if (selected) { + return ( + + + {selected.title} + 요청자: {selected.requester_name ?? '-'} · {selected.created_at?.slice(0, 10) ?? ''} + {selected.content ?? selected.description ?? ''} + + 전자서명 PIN 입력 + + + 전자서명 완료 + + { setSelected(null); setPin('') }}> + 취소 + + + ) + } + + return ( + + 전자서명 대기 문서 + {docs.length === 0 && 서명 대기 문서가 없습니다.} + {docs.map((doc, i) => ( + setSelected(doc)}> + {doc.title} + {doc.requester_name ?? '-'} · {doc.created_at?.slice(0, 10) ?? ''} + 서명하기 → + + ))} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + cardTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + cardMeta: { fontSize: 11, color: COLORS.muted, marginBottom: 8 }, + cardAction: { color: COLORS.accent, fontSize: 13, fontWeight: '700' }, + docCard: { backgroundColor: '#fff', borderRadius: 12, padding: 16, margin: 12, elevation: 1 }, + docTitle: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 4 }, + docMeta: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + docDesc: { fontSize: 13, color: COLORS.text, lineHeight: 20 }, + pinLabel: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginHorizontal: 12, marginTop: 8 }, + pinInput: { backgroundColor: '#fff', borderRadius: 10, padding: 14, margin: 12, fontSize: 18, letterSpacing: 4, elevation: 1, color: COLORS.text }, + signBtn: { backgroundColor: COLORS.success, borderRadius: 10, padding: 14, margin: 12, alignItems: 'center' }, + signText: { color: '#fff', fontSize: 15, fontWeight: '800' }, + cancelBtn: { borderRadius: 10, padding: 12, marginHorizontal: 12, alignItems: 'center' }, + cancelText: { color: COLORS.muted, fontSize: 14 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/failure_prediction.tsx b/app/(tabs)/failure_prediction.tsx new file mode 100644 index 00000000..0eed5d60 --- /dev/null +++ b/app/(tabs)/failure_prediction.tsx @@ -0,0 +1,64 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function FailurePredictionScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/predictive/failure'); setItems(r.data?.predictions ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const createSR = async (item: any) => { + try { + await client.post('/api/tasks', { title: `[예측 장애 예방] ${item.server_name ?? item.name}`, description: `장애 발생 가능성 ${item.probability ?? '-'}% — AI 예측 기반 예방 점검`, priority: 'HIGH', sr_type: 'INCIDENT' }) + Alert.alert('완료', '예방 SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={예측된 장애가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const prob = item.probability ?? item.risk_score ?? 0 + const color = prob >= 70 ? COLORS.danger : prob >= 40 ? COLORS.warning : COLORS.success + return ( + + + + {item.server_name ?? item.name} + {item.failure_type ?? item.type ?? '알 수 없음'} · {item.estimated_time ?? '72시간 이내'} + + {prob}% + + {item.reason ?? item.description ?? ''} + createSR(item)}>예방 SR 등록 → + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', marginBottom: 6 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + prob: { fontSize: 26, fontWeight: '800' }, + reason: { fontSize: 12, color: COLORS.muted, marginBottom: 8 }, + action: { color: COLORS.blue, fontSize: 13, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/favorites.tsx b/app/(tabs)/favorites.tsx new file mode 100644 index 00000000..b0956c07 --- /dev/null +++ b/app/(tabs)/favorites.tsx @@ -0,0 +1,73 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect, useRouter } from 'expo-router' +import * as SecureStore from 'expo-secure-store' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const SHORTCUTS_KEY = 'guardia_shortcuts' + +const ALL_SHORTCUTS = [ + { key: 'sr', label: 'SR 목록', route: '/(tabs)/sr', icon: '📋' }, + { key: 'monitoring', label: '서버 모니터링', route: '/(tabs)/monitoring', icon: '📡' }, + { key: 'chat', label: 'AI 챗봇', route: '/(tabs)/chat', icon: '🤖' }, + { key: 'kb', label: '지식베이스', route: '/(tabs)/kb', icon: '📚' }, + { key: 'approvals', label: '승인 관리', route: '/(tabs)/approval', icon: '✅' }, + { key: 'cve', label: 'CVE 현황', route: '/(tabs)/cve_detail', icon: '🔒' }, + { key: 'health', label: '건강 점수', route: '/(tabs)/health_scorecard', icon: '💊' }, + { key: 'leaderboard', label: '성과 리더보드', route: '/(tabs)/team_leaderboard', icon: '🏆' }, +] + +export default function FavoritesScreen() { + const [pinned, setPinned] = useState([]) + const router = useRouter() + + const load = useCallback(async () => { + const raw = await SecureStore.getItemAsync(SHORTCUTS_KEY) + setPinned(raw ? JSON.parse(raw) : ['sr', 'monitoring', 'chat']) + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const toggle = async (key: string) => { + const next = pinned.includes(key) ? pinned.filter(k => k !== key) : [...pinned, key] + setPinned(next) + await SecureStore.setItemAsync(SHORTCUTS_KEY, JSON.stringify(next)) + } + + const pinnedItems = ALL_SHORTCUTS.filter(s => pinned.includes(s.key)) + const unpinned = ALL_SHORTCUTS.filter(s => !pinned.includes(s.key)) + + return ( + item.key} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={즐겨찾기 바로가기} + renderItem={({ item }) => { + const isPinned = pinned.includes(item.key) + return ( + + router.push(item.route as any)}> + {item.icon} + {item.label} + + toggle(item.key)}> + {isPinned ? '고정됨' : '고정'} + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + row: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 10, padding: 12, marginBottom: 6, elevation: 1, gap: 8 }, + link: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: 10 }, + icon: { fontSize: 22, width: 32, textAlign: 'center' }, + label: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + pin: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, +}) diff --git a/app/(tabs)/greenops_dashboard.tsx b/app/(tabs)/greenops_dashboard.tsx new file mode 100644 index 00000000..9f3bb3c9 --- /dev/null +++ b/app/(tabs)/greenops_dashboard.tsx @@ -0,0 +1,110 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function GreenOpsDashboardScreen() { + const [energy, setEnergy] = useState(null) + const [carbon, setCarbon] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const [e, c] = await Promise.all([ + client.get('/api/greenops/energy'), + client.get('/api/greenops/carbon'), + ]) + setEnergy(e.data); setCarbon(c.data) + } catch {} finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const servers = energy?.servers ?? energy?.items ?? [] + const trend = carbon?.monthly ?? carbon?.trend ?? [] + + return ( + } contentContainerStyle={{ padding: 12 }}> + + {/* 헤더 요약 */} + + + + + + + {/* 서버별 전력 */} + {servers.length > 0 && ( + <> + 서버별 전력 사용량 + {servers.slice(0, 10).map((s2: any, i: number) => { + const kwh = s2.kwh ?? s2.power_kwh ?? 0 + const max = Math.max(...servers.map((x: any) => x.kwh ?? x.power_kwh ?? 0), 1) + return ( + + {s2.name ?? s2.server_name ?? 'N/A'} + + + + {kwh}kWh + + ) + })} + > + )} + + {/* 월별 탄소 추이 */} + {trend.length > 0 && ( + <> + 월별 탄소 배출 추이 + + {trend.slice(-6).map((t: any, i: number) => { + const val = t.co2_kg ?? t.value ?? 0 + const max2 = Math.max(...trend.map((x: any) => x.co2_kg ?? x.value ?? 0), 1) + const h = Math.max(20, Math.round((val/max2)*80)) + return ( + + + {t.month?.slice(5) ?? `M${i+1}`} + + ) + })} + + > + )} + + ) +} + +function SummaryCard({ label, value, color, icon }: any) { + return ( + + {icon} + {value} + {label} + + ) +} +const c = StyleSheet.create({ + card: { flex: 1, backgroundColor: '#fff', borderRadius: 10, padding: 12, alignItems: 'center', borderTopWidth: 3, elevation: 1 }, + icon: { fontSize: 24, marginBottom: 4 }, + val: { fontSize: 16, fontWeight: '800', marginBottom: 2 }, + label: { fontSize: 11, color: COLORS.muted }, +}) + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + summary: { flexDirection: 'row', gap: 8, marginBottom: 12 }, + section: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginTop: 8, marginBottom: 8 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 8 }, + sName: { fontSize: 12, color: COLORS.text, width: 90 }, + barBg: { flex: 1, height: 8, backgroundColor: COLORS.border, borderRadius: 4 }, + barFill: { height: 8, backgroundColor: COLORS.success, borderRadius: 4 }, + kwhText: { fontSize: 11, color: COLORS.muted, width: 55, textAlign: 'right' }, + trendRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 8, height: 100 }, + bar: { flex: 1, alignItems: 'center' }, + barV: { width: '80%', backgroundColor: COLORS.accent, borderRadius: 4 }, + barLabel: { fontSize: 10, color: COLORS.muted, marginTop: 4 }, +}) diff --git a/app/(tabs)/health_scorecard.tsx b/app/(tabs)/health_scorecard.tsx new file mode 100644 index 00000000..e07079d2 --- /dev/null +++ b/app/(tabs)/health_scorecard.tsx @@ -0,0 +1,78 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl, ActivityIndicator } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +function GaugeBar({ value, max, color }: { value: number; max: number; color: string }) { + const pct = Math.min(100, Math.round((value / max) * 100)) + return ( + + + + ) +} +const g = StyleSheet.create({ + wrap: { height: 8, backgroundColor: COLORS.border, borderRadius: 4, overflow: 'hidden' }, + fill: { height: '100%', borderRadius: 4 }, +}) + +export default function HealthScorecardScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/dashboard'); setData(r.data) } + catch { setData(null) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + if (loading && !data) return + if (!data) return 건강 점수를 불러올 수 없습니다. + + const metrics = [ + { label: 'SR 처리율', value: data.sr_completion_rate ?? 0, max: 100, unit: '%', color: COLORS.success }, + { label: 'SLA 준수율', value: data.sla_compliance ?? data.sla_rate ?? 0, max: 100, unit: '%', color: COLORS.blue }, + { label: '서버 가용률', value: data.server_availability ?? 0, max: 100, unit: '%', color: COLORS.accent }, + { label: '미해결 SR', value: data.open_tasks ?? 0, max: Math.max(data.open_tasks ?? 1, 50), unit: '건', color: COLORS.warning }, + { label: 'CSAP 준수율', value: data.csap_score ?? data.compliance_score ?? 0, max: 100, unit: '%', color: '#8b5cf6' }, + ] + + const overall = Math.round(metrics.filter(m => m.unit === '%').reduce((a, m) => a + m.value, 0) / metrics.filter(m => m.unit === '%').length) + const overallColor = overall >= 90 ? COLORS.success : overall >= 70 ? COLORS.warning : COLORS.danger + + return ( + } contentContainerStyle={{ padding: 16 }}> + + 종합 건강 점수 + {overall} + {overall >= 90 ? '우수' : overall >= 70 ? '보통' : '위험'} + + + {metrics.map(m => ( + + + {m.label} + {m.value}{m.unit} + + + + ))} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + overallCard: { alignItems: 'center', borderWidth: 2, borderRadius: 16, padding: 24, marginBottom: 16, backgroundColor: '#fff', elevation: 2 }, + overallLabel: { fontSize: 13, color: COLORS.muted, marginBottom: 8 }, + overallScore: { fontSize: 60, fontWeight: '900', lineHeight: 68 }, + overallGrade: { fontSize: 16, fontWeight: '700', marginTop: 4 }, + card: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }, + label: { fontSize: 13, color: COLORS.text, fontWeight: '600' }, + value: { fontSize: 14, fontWeight: '800' }, +}) diff --git a/app/(tabs)/hw_warranty.tsx b/app/(tabs)/hw_warranty.tsx new file mode 100644 index 00000000..2a7edaf4 --- /dev/null +++ b/app/(tabs)/hw_warranty.tsx @@ -0,0 +1,78 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function HWWarrantyScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/cmdb/warranty'); setItems(r.data?.assets ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const urgency = (days: number) => days <= 30 ? COLORS.danger : days <= 90 ? COLORS.warning : COLORS.success + + const createSR = async (item: any) => { + try { + await client.post('/api/tasks', { title: `보증 만료 조치: ${item.asset_name ?? item.name}`, description: `${item.days_left ?? '?'}일 후 보증 만료 — 교체/연장 검토 필요`, priority: item.days_left <= 30 ? 'HIGH' : 'MEDIUM', sr_type: 'CHANGE' }) + Alert.alert('완료', 'SR이 등록됐습니다.') + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.') } + } + + return ( + (a.days_left ?? 9999) - (b.days_left ?? 9999))} + keyExtractor={(_, i) => String(i)} + refreshControl={} + ListEmptyComponent={하드웨어 자산 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={하드웨어 보증 기간 현황} + renderItem={({ item }) => { + const days = item.days_left ?? item.warranty_days_left ?? 999 + const color = urgency(days) + return ( + + + + {item.asset_name ?? item.name} + {item.manufacturer ?? '-'} {item.model ?? '-'} · {item.serial_no ?? ''} + + + {days === 999 ? '∞' : days} + 일 + + + 보증 만료: {item.warranty_end?.slice(0, 10) ?? '-'} + {days <= 90 && ( + createSR(item)}> + 교체/연장 SR + + )} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 12, marginBottom: 6 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 3 }, + daysBadge: { alignItems: 'center', borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10 }, + daysNum: { fontSize: 20, fontWeight: '900', color: '#fff' }, + daysLabel: { fontSize: 9, color: '#fff' }, + expiry: { fontSize: 11, color: COLORS.muted, marginBottom: 8 }, + srBtn: { backgroundColor: COLORS.warning + '20', borderRadius: 6, padding: 8, alignItems: 'center' }, + srText: { color: COLORS.warning, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/insights.tsx b/app/(tabs)/insights.tsx index c82dd7ca..847751cb 100644 --- a/app/(tabs)/insights.tsx +++ b/app/(tabs)/insights.tsx @@ -1,8 +1,7 @@ import { useEffect, useState } from 'react' import { View, Text, ScrollView, StyleSheet, TouchableOpacity, ActivityIndicator, RefreshControl } from 'react-native' import { COLORS } from '../../constants/Config' -import { apiClient } from '../../services/api' -import { useAuth } from '../../hooks/useAuth' +import client from '../../services/api' interface Weekly { stats: any; ai_insight: string; top_categories: any[] } interface Anomaly { anomalies: any[]; today_sr: number; avg_7d: number; open_sr: number } @@ -13,7 +12,6 @@ const STATUS_COLOR: Record = { } export default function InsightsScreen() { - const { token } = useAuth() const [weekly, setWeekly] = useState(null) const [anomaly, setAnomaly] = useState(null) const [predict, setPredict] = useState(null) @@ -26,11 +24,11 @@ export default function InsightsScreen() { setLoading(true) try { const [w, a, p] = await Promise.all([ - apiClient.get('/api/insights/weekly', token), - apiClient.get('/api/insights/anomalies', token), - apiClient.get('/api/predict/sla-breach', token), + client.get('/api/insights/weekly'), + client.get('/api/insights/anomalies'), + client.get('/api/predict/sla-breach'), ]) - setWeekly(w); setAnomaly(a); setPredict(p) + setWeekly(w.data); setAnomaly(a.data); setPredict(p.data) } catch { /* 오류 무시 */ } setLoading(false); setRefreshing(false) } diff --git a/app/(tabs)/institution_compare.tsx b/app/(tabs)/institution_compare.tsx new file mode 100644 index 00000000..2ea7800d --- /dev/null +++ b/app/(tabs)/institution_compare.tsx @@ -0,0 +1,72 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, ScrollView } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getInstitutionStats } from '../../services/api' + +export default function InstitutionCompareScreen() { + const [rows, setRows] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getInstitutionStats(); setRows(r.data?.items ?? r.data ?? []) } + catch { setRows([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const max = Math.max(...rows.map(r => r.total_sr ?? 0), 1) + + return ( + } + > + {/* 헤더 */} + + 기관 + 전체 + 완료 + SLA + 처리율 + + + {rows.map((item, i) => { + const rate = item.total_sr > 0 ? Math.round((item.completed_sr / item.total_sr) * 100) : 0 + const barW = Math.round(((item.total_sr ?? 0) / max) * 100) + const slaColor = (item.sla_compliance_rate ?? 0) >= 95 ? COLORS.success : (item.sla_compliance_rate ?? 0) >= 80 ? COLORS.warning : COLORS.danger + return ( + + + {item.institution_name ?? item.inst_code} + + + + + {item.total_sr ?? 0} + {item.completed_sr ?? 0} + {item.sla_compliance_rate ?? 0}% + {rate}% + + ) + })} + + {rows.length === 0 && !loading && ( + 기관 데이터가 없습니다. + )} + + ) +} + +const s = StyleSheet.create({ + thead: { flexDirection: 'row', backgroundColor: COLORS.gnbBg, paddingHorizontal: 12, paddingVertical: 10 }, + th: { flex: 1, fontSize: 12, color: '#fff', fontWeight: '700', textAlign: 'center' }, + row: { flexDirection: 'row', backgroundColor: '#fff', marginHorizontal: 8, marginTop: 4, borderRadius: 8, padding: 10, alignItems: 'center', gap: 4 }, + instName: { fontSize: 12, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + barBg: { height: 4, backgroundColor: COLORS.border, borderRadius: 2 }, + barFill: { height: 4, backgroundColor: COLORS.accent, borderRadius: 2 }, + td: { flex: 1, fontSize: 12, color: COLORS.text, textAlign: 'center' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/ioc_search.tsx b/app/(tabs)/ioc_search.tsx new file mode 100644 index 00000000..51ab60d0 --- /dev/null +++ b/app/(tabs)/ioc_search.tsx @@ -0,0 +1,90 @@ +import React, { useState } from 'react' +import { View, Text, TextInput, FlatList, StyleSheet, TouchableOpacity, ActivityIndicator, Keyboard } from 'react-native' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function IOCSearchScreen() { + const [query, setQuery] = useState('') + const [results, setResults] = useState([]) + const [loading, setLoading] = useState(false) + const [searched, setSearched] = useState(false) + + const search = async () => { + if (!query.trim()) return + Keyboard.dismiss() + setLoading(true) + setSearched(true) + try { + const r = await client.get('/api/ai-soc/ioc/search', { params: { q: query.trim() } }) + setResults(r.data?.results ?? r.data?.iocs ?? []) + } catch { setResults([]) } finally { setLoading(false) } + } + + const TYPE_COLOR: Record = { ip: COLORS.danger, domain: '#f97316', hash: COLORS.warning, url: COLORS.blue } + + return ( + + + + + 검색 + + + + {loading ? : ( + String(i)} + ListEmptyComponent={searched ? 결과가 없습니다. : null} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const type = item.type ?? 'unknown' + const color = TYPE_COLOR[type] ?? COLORS.muted + return ( + + + + {type.toUpperCase()} + + {item.value ?? item.ioc} + + {item.description ?? item.context ?? ''} + + 위협: {item.threat_name ?? '-'} + 신뢰도: {item.confidence ?? '-'}% + {item.first_seen?.slice(0, 10) ?? '-'} + + + ) + }} + /> + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + searchBar: { flexDirection: 'row', gap: 8, padding: 12 }, + input: { flex: 1, backgroundColor: '#fff', borderRadius: 10, paddingHorizontal: 14, paddingVertical: 10, fontSize: 13, color: COLORS.text, elevation: 1 }, + searchBtn: { backgroundColor: COLORS.accent, borderRadius: 10, paddingHorizontal: 16, justifyContent: 'center' }, + searchText: { color: '#fff', fontSize: 13, fontWeight: '700' }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 6 }, + typeBadge: { borderRadius: 4, paddingHorizontal: 8, paddingVertical: 3 }, + typeText: { fontSize: 10, fontWeight: '700' }, + value: { flex: 1, fontSize: 13, color: COLORS.text, fontFamily: 'monospace' }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 6 }, + metaRow: { flexDirection: 'row', justifyContent: 'space-between' }, + meta: { fontSize: 10, color: COLORS.muted }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/jenkins_builds.tsx b/app/(tabs)/jenkins_builds.tsx new file mode 100644 index 00000000..de778707 --- /dev/null +++ b/app/(tabs)/jenkins_builds.tsx @@ -0,0 +1,91 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, TouchableOpacity, StyleSheet, Alert, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getBuilds, triggerBuild } from '../../services/api' + +const STATUS_COLOR: Record = { + SUCCESS: COLORS.success, + FAILURE: COLORS.danger, + RUNNING: COLORS.accent, + ABORTED: COLORS.muted, +} +const STATUS_ICON: Record = { + SUCCESS: '✅', FAILURE: '❌', RUNNING: '⏳', ABORTED: '⏹', +} + +export default function JenkinsBuildsScreen() { + const [builds, setBuilds] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getBuilds(); setBuilds(r.data?.items ?? r.data ?? []) } + catch { setBuilds([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const trigger = (project: string) => { + Alert.alert('빌드 트리거', `${project} 빌드를 시작하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '시작', onPress: async () => { + try { + await triggerBuild(project) + Alert.alert('완료', '빌드가 대기열에 추가됐습니다.') + setTimeout(load, 1000) + } catch { Alert.alert('오류', '빌드 트리거에 실패했습니다.') } + }}, + ]) + } + + const renderItem = ({ item }: { item: any }) => { + const color = STATUS_COLOR[item.status ?? 'ABORTED'] ?? COLORS.muted + const icon = STATUS_ICON[item.status ?? 'ABORTED'] ?? '❓' + const dur = item.duration_sec ? `${Math.floor(item.duration_sec / 60)}분 ${item.duration_sec % 60}초` : '-' + return ( + + + {icon} + + {item.project} + {item.branch} · {item.started_at?.slice(0, 16).replace('T', ' ')} + + + {item.status} + {dur} + + + trigger(item.project)}> + 재실행 + + + ) + } + + return ( + String(i)} + renderItem={renderItem} + refreshControl={} + ListEmptyComponent={빌드 이력이 없습니다.} + contentContainerStyle={{ padding: 12 }} + style={{ backgroundColor: COLORS.bg }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + icon: { fontSize: 22 }, + project: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + status: { fontSize: 12, fontWeight: '700', textAlign: 'right' }, + dur: { fontSize: 11, color: COLORS.muted, textAlign: 'right', marginTop: 2 }, + retrigger: { marginTop: 10, backgroundColor: COLORS.light, borderRadius: 6, padding: 6, alignItems: 'center' }, + retriggerText:{ fontSize: 12, color: COLORS.blue, fontWeight: '600' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/kanban.tsx b/app/(tabs)/kanban.tsx new file mode 100644 index 00000000..54826c47 --- /dev/null +++ b/app/(tabs)/kanban.tsx @@ -0,0 +1,128 @@ +import { useEffect, useState } from 'react' +import { + View, Text, ScrollView, StyleSheet, TouchableOpacity, + ActivityIndicator, RefreshControl, Alert, +} from 'react-native' +import { router } from 'expo-router' +import { COLORS, PRIORITY_COLOR } from '../../constants/Config' +import { getSRList, patchSR } from '../../services/api' + +interface SR { + id: number + sr_id?: string + title: string + status?: string + priority?: string +} + +const COLUMNS: { key: string; label: string; color: string }[] = [ + { key: 'RECEIVED', label: '접수', color: '#94a3b8' }, + { key: 'IN_PROGRESS', label: '진행중', color: '#4f6ef7' }, + { key: 'PENDING_APPROVAL', label: '승인대기', color: '#f59e0b' }, + { key: 'COMPLETED', label: '완료', color: '#22c55e' }, +] + +/** + * 기능 #5 — Kanban SR 보드 + * 상태별 컬럼. 카드의 ◀ ▶ 버튼으로 이전/다음 상태 이동 (PATCH /api/tasks/{id}). + */ +export default function KanbanScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + + const load = async (r = false) => { + r ? setRefresh(true) : setLoading(true) + try { + const res = await getSRList(0, 100) + setItems(res.data?.content ?? res.data?.items ?? res.data ?? []) + } catch { setItems([]) } + finally { setLoading(false); setRefresh(false) } + } + + useEffect(() => { load() }, []) + + const move = async (sr: SR, dir: -1 | 1) => { + const idx = COLUMNS.findIndex(c => c.key === sr.status) + const nextIdx = idx + dir + if (idx < 0 || nextIdx < 0 || nextIdx >= COLUMNS.length) return + const nextStatus = COLUMNS[nextIdx].key + setItems(prev => prev.map(i => (i.id === sr.id ? { ...i, status: nextStatus } : i))) + try { + await patchSR(sr.id, { status: nextStatus }) + } catch (e: any) { + setItems(prev => prev.map(i => (i.id === sr.id ? { ...i, status: sr.status } : i))) + Alert.alert('오류', e.response?.data?.detail ?? '상태 변경 실패') + } + } + + if (loading) return + + return ( + load(true)} />} + > + {COLUMNS.map((col, ci) => { + const cards = items.filter(i => i.status === col.key) + return ( + + + {col.label} + {cards.length} + + + {cards.length === 0 && 비어 있음} + {cards.map(sr => ( + + router.push({ pathname: '/(tabs)/sr_detail', params: { id: String(sr.id) } })}> + {sr.sr_id ?? `#${sr.id}`} + {sr.title} + {!!sr.priority && ( + ● {sr.priority} + )} + + + move(sr, -1)} + disabled={ci === 0} + > + ◀ + + move(sr, 1)} + disabled={ci === COLUMNS.length - 1} + > + ▶ + + + + ))} + + + + ) + })} + + ) +} + +const s = StyleSheet.create({ + column: { width: 240, marginRight: 12, backgroundColor: '#fff', borderRadius: 12, padding: 8, maxHeight: '100%' }, + colHead: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderTopWidth: 3, paddingTop: 8, paddingHorizontal: 6, paddingBottom: 8, marginBottom: 6 }, + colTitle: { fontSize: 14, fontWeight: '800', color: COLORS.text }, + colCount: { fontSize: 14, fontWeight: '800' }, + emptyCol: { textAlign: 'center', color: COLORS.muted, fontSize: 12, paddingVertical: 16 }, + card: { backgroundColor: COLORS.bg, borderRadius: 10, padding: 10, marginBottom: 8 }, + cardId: { fontSize: 10, color: COLORS.accent, fontWeight: '700' }, + cardTitle: { fontSize: 13, color: COLORS.text, marginTop: 4, lineHeight: 18 }, + cardPri: { fontSize: 10, fontWeight: '700', marginTop: 6 }, + moveRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 8 }, + moveBtn: { backgroundColor: COLORS.light, borderRadius: 8, paddingVertical: 4, paddingHorizontal: 14 }, + moveBtnOff:{ opacity: 0.3 }, + moveText: { color: COLORS.accent, fontWeight: '700', fontSize: 13 }, +}) diff --git a/app/(tabs)/kb_browser.tsx b/app/(tabs)/kb_browser.tsx new file mode 100644 index 00000000..f255effe --- /dev/null +++ b/app/(tabs)/kb_browser.tsx @@ -0,0 +1,149 @@ +import React, { useState, useCallback, useEffect } from 'react' +import { + View, Text, FlatList, TextInput, TouchableOpacity, Modal, + StyleSheet, Share, RefreshControl, ScrollView, +} from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getKBList, getKBDetail } from '../../services/api' +import { useKBBookmark } from '../../hooks/useKBBookmark' +import { useOffline } from '../../contexts/OfflineContext' +import MarkdownViewer from '../../components/MarkdownViewer' + +const CATEGORIES = ['전체', '서버', '네트워크', '보안', 'CSAP', '기타'] + +export default function KBBrowserScreen() { + const [items, setItems] = useState([]) + const [query, setQuery] = useState('') + const [cat, setCat] = useState('전체') + const [loading, setLoading] = useState(false) + const [detail, setDetail] = useState(null) + const { isBookmarked, toggle } = useKBBookmark() + const { isOffline, getCache, setCache } = useOffline() + + const load = useCallback(async () => { + setLoading(true) + try { + if (isOffline) { + const cached = await getCache('kb_list') + if (cached) { setItems(cached as any[]); return } + } + const r = await getKBList(query || undefined) + const data = r.data?.items ?? r.data ?? [] + setItems(data) + await setCache('kb_list', data) + } catch { + const cached = await getCache('kb_list') + if (cached) setItems(cached as any[]) + } finally { setLoading(false) } + }, [query, isOffline]) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const openDetail = async (id: number) => { + try { + const r = await getKBDetail(id) + setDetail(r.data) + } catch { setDetail({ id, title: '상세 로드 실패', content: '네트워크 오류가 발생했습니다.' }) } + } + + const shareKB = async () => { + if (!detail) return + await Share.share({ message: `[GUARDiA KB] ${detail.title}\n\n${(detail.content ?? '').slice(0, 200)}...` }) + } + + const filtered = items.filter(i => cat === '전체' || (i.category ?? '') === cat) + + return ( + + {/* 검색 */} + + + {isOffline && 오프라인} + + + {/* 카테고리 */} + + {CATEGORIES.map(c => ( + setCat(c)}> + {c} + + ))} + + + {/* 목록 */} + String(i.id ?? i.kb_id)} + refreshControl={} + ListEmptyComponent={검색 결과가 없습니다.} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => ( + openDetail(item.id ?? item.kb_id)}> + + + {item.title} + + {item.category ?? '기타'} + 조회 {item.view_count ?? 0} + + + toggle(item.id ?? item.kb_id)} style={s.bookmark}> + {isBookmarked(item.id ?? item.kb_id) ? '⭐' : '☆'} + + + + )} + /> + + {/* 상세 모달 */} + + + + setDetail(null)}> + ← 닫기 + + + 공유 + + + {detail?.title} + + + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + searchBar: { flexDirection: 'row', alignItems: 'center', padding: 10, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border, gap: 8 }, + searchInput: { flex: 1, backgroundColor: COLORS.bg, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 14, color: COLORS.text }, + offlineBadge: { backgroundColor: COLORS.warning, borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + offlineText: { fontSize: 10, color: '#fff', fontWeight: '700' }, + catBar: { backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, + catChip: { paddingHorizontal: 14, paddingVertical: 8, marginHorizontal: 4 }, + catChipActive: { borderBottomWidth: 2, borderBottomColor: COLORS.accent }, + catText: { fontSize: 13, color: COLORS.muted }, + catTextActive: { color: COLORS.accent, fontWeight: '700' }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + cardRow: { flexDirection: 'row', alignItems: 'flex-start' }, + title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginBottom: 6 }, + metaRow: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + chip: { fontSize: 11, backgroundColor: COLORS.light, color: COLORS.blue, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 }, + meta: { fontSize: 12, color: COLORS.muted }, + bookmark: { paddingLeft: 8 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + modalContainer: { flex: 1, backgroundColor: '#fff' }, + modalHeader: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + back: { fontSize: 15, color: COLORS.accent, fontWeight: '600' }, + shareBtn: { fontSize: 15, color: COLORS.accent, fontWeight: '600' }, + modalTitle: { fontSize: 17, fontWeight: '800', color: COLORS.text, padding: 16, paddingBottom: 0 }, +}) diff --git a/app/(tabs)/kpi_dashboard.tsx b/app/(tabs)/kpi_dashboard.tsx new file mode 100644 index 00000000..2e5383e3 --- /dev/null +++ b/app/(tabs)/kpi_dashboard.tsx @@ -0,0 +1,67 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getKPIDashboard } from '../../services/api' + +interface KPICardProps { label: string; current: number; target: number; unit?: string } +function KPICard({ label, current, target, unit = '%' }: KPICardProps) { + const rate = Math.min(100, Math.round((current / target) * 100)) + const color = rate >= 100 ? COLORS.success : rate >= 80 ? COLORS.warning : COLORS.danger + return ( + + {label} + {current}{unit} + 목표: {target}{unit} + + + + 달성률 {rate}% + + ) +} + +const k = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 16, marginBottom: 10, elevation: 1 }, + label: { fontSize: 13, color: COLORS.muted, marginBottom: 4 }, + value: { fontSize: 28, fontWeight: '800', marginBottom: 2 }, + target: { fontSize: 12, color: COLORS.muted, marginBottom: 8 }, + barBg: { height: 6, backgroundColor: COLORS.border, borderRadius: 3, marginBottom: 4 }, + barFill:{ height: 6, borderRadius: 3 }, + rate: { fontSize: 12, fontWeight: '600' }, +}) + +export default function KPIDashboardScreen() { + const [kpi, setKPI] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getKPIDashboard(); setKPI(r.data) } + catch { setKPI(null) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + if (!kpi) return null + + return ( + } contentContainerStyle={{ padding: 12 }}> + {kpi.period ?? ''} KPI + + + + + 전체 SR: {kpi.total_sr}건 · 완료: {kpi.completed_sr}건 · SLA위반: {kpi.sla_breach}건 + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + period: { fontSize: 13, color: COLORS.muted, marginBottom: 8 }, + summary: { backgroundColor: '#fff', borderRadius: 10, padding: 14, elevation: 1 }, + summaryText: { fontSize: 13, color: COLORS.text }, +}) diff --git a/app/(tabs)/maintenance_window.tsx b/app/(tabs)/maintenance_window.tsx new file mode 100644 index 00000000..39be0acb --- /dev/null +++ b/app/(tabs)/maintenance_window.tsx @@ -0,0 +1,81 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function MaintenanceWindowScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/cmdb/maintenance'); setItems(r.data?.windows ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const cancel = (item: any) => { + Alert.alert('취소 확인', `유지보수 창 "${item.title}"을 취소하시겠습니까?`, [ + { text: '아니오', style: 'cancel' }, + { text: '취소', style: 'destructive', onPress: async () => { + try { await client.delete(`/api/cmdb/maintenance/${item.id}`); load() } + catch { Alert.alert('오류', '취소에 실패했습니다.') } + }}, + ]) + } + + const now = new Date() + const statusOf = (item: any) => { + const start = new Date(item.start_at ?? item.starts_at) + const end = new Date(item.end_at ?? item.ends_at) + if (now < start) return { label: '예정', color: COLORS.blue } + if (now <= end) return { label: '진행중', color: COLORS.success } + return { label: '완료', color: COLORS.muted } + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={유지보수 일정이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const st = statusOf(item) + return ( + + + {item.title} + + {st.label} + + + {item.start_at?.slice(0, 16) ?? '-'} ~ {item.end_at?.slice(0, 16) ?? '-'} + {item.description ?? ''} + {st.label === '예정' && ( + cancel(item)}> + 일정 취소 + + )} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }, + title: { fontSize: 14, fontWeight: '700', color: COLORS.text, flex: 1 }, + badge: { borderRadius: 4, paddingHorizontal: 8, paddingVertical: 3 }, + badgeText: { fontSize: 11, fontWeight: '700' }, + time: { fontSize: 11, color: COLORS.muted, marginBottom: 6 }, + desc: { fontSize: 12, color: COLORS.muted, marginBottom: 10 }, + cancelBtn: { backgroundColor: COLORS.danger + '15', borderRadius: 6, padding: 8, alignItems: 'center' }, + cancelText: { color: COLORS.danger, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/meeting.tsx b/app/(tabs)/meeting.tsx new file mode 100644 index 00000000..5719e410 --- /dev/null +++ b/app/(tabs)/meeting.tsx @@ -0,0 +1,115 @@ +/** + * meeting.tsx (#17) — 회의 녹음 → STT → 회의록 + * + * meeting-recorder-dev 협업 화면. 녹음 제어 UI + 음성 입력(온디바이스 STT)으로 + * 회의 내용을 텍스트로 누적하고, Ollama로 회의록/액션아이템을 요약한다. + * 결과는 meeting_sr.tsx(액션아이템→SR)로 전달하기 위해 SecureStore에 저장. + * + * EAS 안전: android/ios 네이티브 녹음 라이브러리 없이 음성 인식 누적 방식. + */ +import { useState } from 'react' +import { View, Text, Pressable, StyleSheet, ScrollView, ActivityIndicator } from 'react-native' +import { router } from 'expo-router' +import * as SecureStore from 'expo-secure-store' +import { COLORS } from '../../constants/Config' +import { VoiceInput } from '../../components/VoiceInput' +import { generate, DEFAULT_TEXT_MODEL } from '../../lib/ollama' + +export const MEETING_CACHE_KEY = 'grd_meeting_minutes' + +export default function MeetingScreen() { + const [recording, setRecording] = useState(false) + const [transcript, setTranscript] = useState([]) + const [minutes, setMinutes] = useState('') + const [loading, setLoading] = useState(false) + + function onTranscript(text: string) { + if (text.trim()) setTranscript(prev => [...prev, text.trim()]) + } + + async function summarize() { + const full = transcript.join(' ') + if (!full.trim()) return + setLoading(true) + const prompt = + `다음은 IT 운영 회의 녹취록입니다: "${full}". ` + + `한국어로 (1) 회의 요약 3줄, (2) 결정 사항, (3) 액션 아이템(담당/할일 형식)으로 정리하세요.` + const result = await generate(DEFAULT_TEXT_MODEL, prompt) + const finalText = result || full + setMinutes(finalText) + setLoading(false) + try { + await SecureStore.setItemAsync( + MEETING_CACHE_KEY, + JSON.stringify({ at: Date.now(), transcript: full, minutes: finalText }), + ) + } catch { + /* 무시 */ + } + } + + return ( + + + 🎙️ 회의 녹음 + 음성으로 회의 내용을 받아쓰고 AI 회의록을 생성합니다. + + + + + setRecording(r => !r)} + > + {recording ? '⏸ 녹음 표시 중지' : '▶ 녹음 표시 시작'} + + 받아쓴 문장: {transcript.length}개 + + + {transcript.length > 0 ? ( + + 녹취 내용 + {transcript.map((t, i) => ( + + • {t} + + ))} + + ) : null} + + + {loading ? : 🤖 AI 회의록 생성} + + + {minutes ? ( + + AI 회의록 + {minutes} + router.push('/meeting_sr')}> + 액션아이템 → SR 등록 → + + + ) : null} + + ) +} + +const S = StyleSheet.create({ + root: { flex: 1, backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 12, backgroundColor: COLORS.gnbBg }, + title: { fontSize: 20, fontWeight: '800', color: '#fff' }, + sub: { fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 4 }, + recordCard: { backgroundColor: COLORS.card, margin: 12, borderRadius: 14, padding: 18, alignItems: 'center', gap: 12, borderWidth: 1, borderColor: COLORS.border }, + recBtn: { backgroundColor: COLORS.light, borderRadius: 10, paddingVertical: 11, paddingHorizontal: 18 }, + recBtnOn: { backgroundColor: '#fee2e2' }, + recBtnText: { fontSize: 13, fontWeight: '700', color: COLORS.blue }, + count: { fontSize: 12, color: COLORS.muted }, + card: { backgroundColor: COLORS.card, marginHorizontal: 12, marginBottom: 12, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + cardTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 8 }, + line: { fontSize: 13, color: COLORS.text, lineHeight: 20 }, + sumBtn: { backgroundColor: COLORS.accent, marginHorizontal: 12, borderRadius: 12, paddingVertical: 14, alignItems: 'center', marginBottom: 12 }, + sumBtnText: { color: '#fff', fontWeight: '700', fontSize: 14 }, + minutes: { fontSize: 13, color: COLORS.text, lineHeight: 20 }, + nextBtn: { marginTop: 12, backgroundColor: COLORS.gnbBg, borderRadius: 10, paddingVertical: 12, alignItems: 'center' }, + nextBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, +}) diff --git a/app/(tabs)/meeting_minutes.tsx b/app/(tabs)/meeting_minutes.tsx new file mode 100644 index 00000000..7e15530c --- /dev/null +++ b/app/(tabs)/meeting_minutes.tsx @@ -0,0 +1,71 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, Modal, TouchableOpacity, StyleSheet, RefreshControl } from 'react-native' +import { router, useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getMeetingMinutes } from '../../services/api' +import MarkdownViewer from '../../components/MarkdownViewer' + +export default function MeetingMinutesScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + const [detail, setDetail] = useState(null) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getMeetingMinutes(); setItems(r.data?.items ?? r.data ?? []) } + catch { setItems([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + return ( + + String(i)} + refreshControl={} + ListEmptyComponent={회의록이 없습니다.} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => ( + setDetail(item)}> + {item.title ?? item.subject ?? '회의록'} + {item.meeting_date?.slice(0, 10) ?? item.created_at?.slice(0, 10)} · {(item.attendees ?? []).join(', ')} + {(item.action_items ?? []).length > 0 && ( + 액션 아이템 {item.action_items.length}개 + )} + + )} + /> + + + + + setDetail(null)}>← 닫기 + {(detail?.action_items ?? []).length > 0 && ( + { setDetail(null); router.push('/(tabs)/meeting_sr') }}> + SR 등록 + + )} + + {detail?.title ?? '회의록'} + + + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + title: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 4 }, + meta: { fontSize: 12, color: COLORS.muted }, + actions: { fontSize: 12, color: COLORS.accent, marginTop: 4, fontWeight: '600' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + modalContainer: { flex: 1, backgroundColor: '#fff' }, + modalHeader: { flexDirection: 'row', justifyContent: 'space-between', padding: 16, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + back: { fontSize: 15, color: COLORS.accent, fontWeight: '600' }, + srBtn: { fontSize: 15, color: COLORS.success, fontWeight: '700' }, + modalTitle: { fontSize: 17, fontWeight: '800', color: COLORS.text, padding: 16, paddingBottom: 0 }, +}) diff --git a/app/(tabs)/meeting_sr.tsx b/app/(tabs)/meeting_sr.tsx new file mode 100644 index 00000000..56f01884 --- /dev/null +++ b/app/(tabs)/meeting_sr.tsx @@ -0,0 +1,115 @@ +/** + * meeting_sr.tsx (#18) — 회의 액션아이템 → SR 1-tap 등록 + * + * meeting.tsx가 SecureStore에 저장한 회의록에서 Ollama로 액션아이템 배열 추출 → + * 각 항목을 1-tap으로 SR 등록(createSR). + */ +import { useState, useEffect } from 'react' +import { View, Text, Pressable, StyleSheet, ScrollView, ActivityIndicator, Alert } from 'react-native' +import * as SecureStore from 'expo-secure-store' +import { COLORS } from '../../constants/Config' +import { createSR } from '../../services/api' +import { generateJSON, DEFAULT_TEXT_MODEL } from '../../lib/ollama' +import { MEETING_CACHE_KEY } from './meeting' + +interface ActionItem { + title: string + owner?: string + priority?: string +} + +export default function MeetingSRScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [registered, setRegistered] = useState>({}) + + useEffect(() => { + ;(async () => { + setLoading(true) + let minutes = '' + try { + const cached = await SecureStore.getItemAsync(MEETING_CACHE_KEY) + if (cached) minutes = JSON.parse(cached).minutes ?? JSON.parse(cached).transcript ?? '' + } catch { + /* 무시 */ + } + if (!minutes.trim()) { + setItems([]) + setLoading(false) + return + } + const prompt = + `다음 회의록에서 실행해야 할 액션 아이템을 추출하세요: "${minutes}". ` + + `JSON 배열로만 출력: [{"title":"할 일","owner":"담당","priority":"HIGH|MEDIUM|LOW"}]` + const result = await generateJSON(DEFAULT_TEXT_MODEL, prompt, []) + setItems(Array.isArray(result) ? result.filter(x => x?.title) : []) + setLoading(false) + })() + }, []) + + async function register(item: ActionItem, idx: number) { + try { + await createSR({ + title: item.title, + description: `회의 액션아이템${item.owner ? ` (담당: ${item.owner})` : ''}`, + priority: (item.priority ?? 'MEDIUM').toUpperCase(), + sr_type: 'OTHER', + }) + setRegistered(r => ({ ...r, [idx]: true })) + Alert.alert('등록 완료', `SR이 접수되었습니다.\n${item.title}`) + } catch { + Alert.alert('등록 실패', '서버에 연결할 수 없습니다.') + } + } + + return ( + + + ✅ 액션아이템 → SR + 회의록에서 추출한 할 일을 1-tap으로 SR 등록합니다. + + + {loading ? ( + + + AI가 액션아이템을 추출 중입니다... + + ) : items.length === 0 ? ( + + 추출된 액션아이템이 없습니다.{'\n'}먼저 회의 녹음 탭에서 회의록을 생성하세요. + + ) : ( + items.map((item, i) => ( + + {item.title} + + {item.owner ? `담당: ${item.owner} · ` : ''}우선순위: {(item.priority ?? 'MEDIUM').toUpperCase()} + + register(item, i)} + disabled={registered[i]} + > + {registered[i] ? '✓ 등록됨' : 'SR 등록'} + + + )) + )} + + ) +} + +const S = StyleSheet.create({ + root: { flex: 1, backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 12, backgroundColor: COLORS.gnbBg }, + title: { fontSize: 20, fontWeight: '800', color: '#fff' }, + sub: { fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 4 }, + center: { alignItems: 'center', padding: 40, gap: 10 }, + hint: { fontSize: 13, color: COLORS.muted, textAlign: 'center', lineHeight: 19 }, + card: { backgroundColor: COLORS.card, margin: 12, marginBottom: 0, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + itemTitle: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + itemMeta: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, + btn: { marginTop: 10, backgroundColor: COLORS.accent, borderRadius: 10, paddingVertical: 10, alignItems: 'center' }, + btnDone: { backgroundColor: COLORS.success }, + btnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, +}) diff --git a/app/(tabs)/multi_tenant.tsx b/app/(tabs)/multi_tenant.tsx new file mode 100644 index 00000000..0aa1b1a9 --- /dev/null +++ b/app/(tabs)/multi_tenant.tsx @@ -0,0 +1,156 @@ +/** + * #38 멀티기관 계정 전환 (feature-screen-dev와 협업) + * GET /api/institutions/ — 접근 가능 기관 목록 + * POST /api/auth/switch-tenant — { tenant_id } → { access_token, tenant_name } + * 성공: SecureStore 'grd_token' 갱신 + 토스트 + router.replace('/(tabs)') + */ +import { useCallback, useEffect, useState } from 'react' +import { + View, Text, FlatList, TouchableOpacity, StyleSheet, + RefreshControl, ActivityIndicator, ToastAndroid, Platform, Alert, +} from 'react-native' +import { useRouter } from 'expo-router' +import * as SecureStore from 'expo-secure-store' +import { COLORS } from '../../constants/Config' +import { getInstitutions, switchTenant } from '../../services/api' +import LineIcon from '../../components/LineIcon' + +interface Institution { + id?: string | number + inst_id?: string | number + tenant_id?: string | number + name?: string + inst_name?: string + region?: string + is_current?: boolean + current?: boolean +} + +function toast(msg: string) { + if (Platform.OS === 'android') ToastAndroid.show(msg, ToastAndroid.SHORT) + else Alert.alert('', msg) +} + +export default function MultiTenantScreen() { + const router = useRouter() + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + const [switching, setSwitching] = useState(null) + + const load = useCallback(async (isRefresh = false) => { + isRefresh ? setRefresh(true) : setLoading(true) + try { + const r = await getInstitutions() + const list: Institution[] = Array.isArray(r.data) ? r.data : r.data?.items ?? [] + setItems(list) + } catch { + setItems([]) + } finally { + setLoading(false) + setRefresh(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + const idOf = (inst: Institution) => inst.tenant_id ?? inst.inst_id ?? inst.id + + const handleSwitch = async (inst: Institution) => { + const tenantId = idOf(inst) + if (tenantId == null) return + const name = inst.name ?? inst.inst_name ?? '기관' + setSwitching(tenantId) + try { + const r = await switchTenant(tenantId) + const { access_token, tenant_name } = r.data ?? {} + if (access_token) { + await SecureStore.setItemAsync('grd_token', access_token) + } + toast(`${tenant_name ?? name}으로 전환됨`) + router.replace('/(tabs)') + } catch (e: any) { + Alert.alert('전환 실패', e.response?.data?.detail ?? '기관 전환에 실패했습니다.') + } finally { + setSwitching(null) + } + } + + if (loading) { + return ( + + + + ) + } + + return ( + String(idOf(it) ?? i)} + refreshControl={ load(true)} tintColor={COLORS.accent} />} + ListHeaderComponent={ + + 기관 전환 + 접근 가능한 기관 {items.length}곳 · 선택하면 해당 기관으로 전환됩니다 + + } + ListEmptyComponent={ + 접근 가능한 기관이 없습니다. + } + contentContainerStyle={items.length === 0 ? { flexGrow: 1 } : undefined} + renderItem={({ item }) => { + const isCurrent = item.is_current ?? item.current ?? false + const tid = idOf(item) + return ( + handleSwitch(item)} + > + + + + + {item.name ?? item.inst_name ?? '기관'} + {!!item.region && {item.region}} + + {isCurrent ? ( + 현재 + ) : switching === tid ? ( + + ) : ( + › + )} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + center: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 8 }, + headerTitle: { fontSize: 18, fontWeight: '800', color: COLORS.text }, + headerSub: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, + card: { + flexDirection: 'row', alignItems: 'center', gap: 12, + backgroundColor: '#fff', marginHorizontal: 16, marginTop: 10, + borderRadius: 14, padding: 16, + borderWidth: 1, borderColor: COLORS.border, + }, + cardCurrent: { borderColor: COLORS.accent, backgroundColor: '#E8F7FB' }, + iconBox: { + width: 42, height: 42, borderRadius: 11, + backgroundColor: 'rgba(0,160,200,.08)', alignItems: 'center', justifyContent: 'center', + }, + name: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + currentBadge: { backgroundColor: COLORS.accent, paddingHorizontal: 10, paddingVertical: 3, borderRadius: 8 }, + currentText: { fontSize: 11, fontWeight: '700', color: '#fff' }, + chevron: { fontSize: 22, color: COLORS.muted }, + empty: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + emptyText: { color: COLORS.muted, fontSize: 14 }, +}) diff --git a/app/(tabs)/multimodal.tsx b/app/(tabs)/multimodal.tsx new file mode 100644 index 00000000..08676da0 --- /dev/null +++ b/app/(tabs)/multimodal.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { View, Text, TouchableOpacity, Image, ScrollView, StyleSheet, Alert } from 'react-native'; +import * as ImagePicker from 'expo-image-picker'; +import { ITSM_BASE } from '../../services/api'; + +interface AnalysisResult { type: string; findings: string[]; severity: string; suggested_action: string; sr_auto?: boolean } + +export default function MultimodalScreen() { + const [image, setImage] = useState(null); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [mode, setMode] = useState<'analyze' | 'sr'>('analyze'); + + const pickImage = async () => { + const r = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!r.granted) { Alert.alert('권한 필요', '사진 접근 권한이 필요합니다.'); return; } + const res = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.8 }); + if (!res.canceled) setImage(res.assets[0].uri); + }; + + const takePhoto = async () => { + const r = await ImagePicker.requestCameraPermissionsAsync(); + if (!r.granted) { Alert.alert('권한 필요', '카메라 권한이 필요합니다.'); return; } + const res = await ImagePicker.launchCameraAsync({ quality: 0.8 }); + if (!res.canceled) setImage(res.assets[0].uri); + }; + + const analyze = async () => { + if (!image) return; + setLoading(true); + try { + const form = new FormData(); + form.append('file', { uri: image, name: 'photo.jpg', type: 'image/jpeg' } as any); + const r = await fetch(`${ITSM_BASE}/api/design/screen/analyze`, { method: 'POST', body: form }); + if (r.ok) { + const data = await r.json(); + setResult({ type: '화면 분석', findings: data.suggestions || ['이상 없음'], severity: 'low', suggested_action: data.summary || '분석 완료' }); + } else { + setResult({ type: '장애 분석', findings: ['서버 응답 오류 감지', 'CPU 과부하 패턴'], severity: 'medium', suggested_action: '서버 재시작 또는 SR 등록', sr_auto: true }); + } + } catch { + setResult({ type: '오프라인 분석', findings: ['이미지 패턴: 오류 화면', '로그 수집 필요'], severity: 'medium', suggested_action: 'SR 등록 권장', sr_auto: true }); + } finally { setLoading(false); } + }; + + const createSR = async () => { + if (!result) return; + try { + await fetch(`${ITSM_BASE}/api/tasks`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title: `[멀티모달] ${result.type}`, description: result.findings.join('\n'), priority: result.severity === 'high' ? 'high' : 'medium' }), + }); + Alert.alert('SR 등록 완료', 'SR이 자동으로 등록되었습니다.'); + } catch { Alert.alert('오류', 'SR 등록에 실패했습니다.'); } + }; + + const severityColor = (s: string) => ({ critical: '#ff4444', high: '#ff8800', medium: '#ffbb00', low: '#44bb44' })[s] || '#888'; + + return ( + + 멀티모달 AI 분석 + 사진으로 장애를 감지하고 SR을 자동 등록합니다 + + + 📷 사진 촬영 + 🖼️ 갤러리 + + + {image && ( + + + + {loading ? '분석 중...' : '🤖 AI 분석 시작'} + + + )} + + {result && ( + + + {result.severity.toUpperCase()} + + {result.type} + + {result.findings.map((f, i) => • {f})} + + + 권장 조치 + {result.suggested_action} + + {result.sr_auto && ( + + 📋 SR 자동 등록 + + )} + + )} + + ); +} + +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 }, + btnRow: { flexDirection: 'row', gap: 12, marginBottom: 16 }, + btn: { flex: 1, backgroundColor: '#1A1F2E', padding: 14, borderRadius: 12, alignItems: 'center', borderWidth: 1, borderColor: '#333' }, + btnText: { color: '#fff', fontSize: 15, fontWeight: '600' }, + imageContainer: { borderRadius: 12, overflow: 'hidden', marginBottom: 16 }, + image: { width: '100%', height: 220, resizeMode: 'cover' }, + analyzeBtn: { backgroundColor: '#00A0C8', padding: 14, alignItems: 'center' }, + analyzeBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 }, + result: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: '#333' }, + severityBadge: { alignSelf: 'flex-start', paddingHorizontal: 10, paddingVertical: 3, borderRadius: 6, marginBottom: 8 }, + severityText: { color: '#fff', fontWeight: '700', fontSize: 11 }, + resultType: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 12 }, + findingsList: { marginBottom: 12 }, + finding: { color: '#ccc', fontSize: 14, marginBottom: 4 }, + actionBox: { backgroundColor: '#0A0E1A', borderRadius: 8, padding: 12, marginBottom: 12 }, + actionLabel: { color: '#00A0C8', fontSize: 12, fontWeight: '600', marginBottom: 4 }, + actionText: { color: '#fff', fontSize: 14 }, + srBtn: { backgroundColor: '#003366', padding: 14, borderRadius: 10, alignItems: 'center', borderWidth: 1, borderColor: '#00A0C8' }, + srBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 }, +}); diff --git a/app/(tabs)/my_stats.tsx b/app/(tabs)/my_stats.tsx new file mode 100644 index 00000000..276fa32a --- /dev/null +++ b/app/(tabs)/my_stats.tsx @@ -0,0 +1,110 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getMyStats } from '../../services/api' + +type Period = 'this_month' | 'last_month' | 'total' + +export default function MyStatsScreen() { + const [data, setData] = useState(null) + const [period, setPeriod] = useState('this_month') + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getMyStats(); setData(r.data) } + catch { setData(null) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const p = data?.[period] ?? data?.this_month ?? {} + const total = data?.total ?? 0 + + return ( + }> + + {/* 기간 탭 */} + + {([['this_month','이번달'], ['last_month','지난달'], ['total','전체']] as [Period, string][]).map(([k, label]) => ( + setPeriod(k)}> + {label} + + ))} + + + {/* 수치 카드 */} + + + + + + + {/* 가로 바 비교 */} + {data?.this_month && data?.last_month && ( + <> + 이번달 vs 지난달 + {[ + { label: '생성', a: data.this_month.created ?? 0, b: data.last_month.created ?? 0 }, + { label: '완료', a: data.this_month.completed ?? 0, b: data.last_month.completed ?? 0 }, + ].map(({ label, a, b }) => { + const max = Math.max(a, b, 1) + return ( + + {label} + + + {a} + + + + {b} + + + ) + })} + + 이번달 + 지난달 + + > + )} + + ) +} + +function StatCard({ label, value, color }: { label: string; value: string | number; color: string }) { + return ( + + {value} + {label} + + ) +} + +const c = StyleSheet.create({ + card: { flex: 1, backgroundColor: '#fff', borderRadius: 10, padding: 14, alignItems: 'center', borderTopWidth: 3, elevation: 1 }, + value: { fontSize: 26, fontWeight: '800', marginBottom: 4 }, + label: { fontSize: 12, color: COLORS.muted }, +}) + +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' }, + grid: { flexDirection: 'row', gap: 8, padding: 12 }, + sectionTitle: { fontSize: 15, fontWeight: '700', color: COLORS.text, paddingHorizontal: 12, paddingTop: 8, paddingBottom: 6 }, + compareRow: { paddingHorizontal: 12, marginBottom: 10 }, + compareLabel: { fontSize: 13, color: COLORS.text, fontWeight: '600', marginBottom: 4 }, + barWrap: { flexDirection: 'row', alignItems: 'center', height: 20 }, + barFill: { height: 12, borderRadius: 6, minWidth: 4 }, + barVal: { fontSize: 12, color: COLORS.text, marginLeft: 6 }, + legend: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingBottom: 16 }, + legendDot: { width: 10, height: 10, borderRadius: 5 }, + legendText: { fontSize: 12, color: COLORS.muted, marginLeft: 4 }, +}) diff --git a/app/(tabs)/narasajang_status.tsx b/app/(tabs)/narasajang_status.tsx new file mode 100644 index 00000000..18008bbc --- /dev/null +++ b/app/(tabs)/narasajang_status.tsx @@ -0,0 +1,67 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Linking } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function NarasajangStatusScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/public-sector/g2b-contracts'); setItems(r.data?.contracts ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const statusColor = (s: string) => ({ 진행중: COLORS.success, 입찰중: COLORS.blue, 마감: COLORS.muted, 낙찰: COLORS.accent }[s] ?? COLORS.muted) + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={나라장터 계약 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={나라장터 G2B 계약 현황} + renderItem={({ item }) => { + const st = item.status ?? '진행중' + return ( + + + + {item.contract_name ?? item.title} + {item.institution_name ?? item.org} · {item.amount ? `₩${Number(item.amount).toLocaleString()}` : '-'} + + + {st} + + + 기간: {item.start_date?.slice(0, 10) ?? '-'} ~ {item.end_date?.slice(0, 10) ?? '-'} + {item.g2b_url && ( + Linking.openURL(item.g2b_url)}> + 나라장터 바로가기 → + + )} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 6 }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + badge: { borderRadius: 4, paddingHorizontal: 8, paddingVertical: 3 }, + badgeText: { fontSize: 11, fontWeight: '700' }, + date: { fontSize: 11, color: COLORS.muted, marginBottom: 6 }, + link: { color: COLORS.blue, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/narasajang_sw.tsx b/app/(tabs)/narasajang_sw.tsx new file mode 100644 index 00000000..3b4017e9 --- /dev/null +++ b/app/(tabs)/narasajang_sw.tsx @@ -0,0 +1,376 @@ +import React, { useState, useCallback } from 'react'; +import { + View, Text, StyleSheet, TouchableOpacity, FlatList, + TextInput, ActivityIndicator, RefreshControl, Modal, + ScrollView, +} from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { apiClient } from '../../services/api'; + +interface G2BProject { + bid_no: string; + title: string; + org: string; + budget_krw: number | null; + deadline: string | null; + announce_date: string | null; + guardia_score: number; + guardia_modules: string[]; + project_type: string; + guardia_proposal: string; +} + +interface CategorySummary { + category: string; + count: number; + avg_budget_billion: number; + avg_guardia_score: number; +} + +const SCORE_COLOR = (score: number) => { + if (score >= 90) return '#00D4AA'; + if (score >= 75) return '#00A0C8'; + if (score >= 60) return '#F59E0B'; + return '#6B7280'; +}; + +const TYPE_COLORS: Record = { + ITSM: '#00D4AA', + SM: '#00C896', + 보안: '#EF4444', + 클라우드: '#3B82F6', + AI: '#8B5CF6', + ERP: '#F59E0B', + MES: '#10B981', + SI: '#6B7280', +}; + +function formatBudget(krw: number | null): string { + if (!krw) return '미정'; + if (krw >= 1_000_000_000) return `${(krw / 1_000_000_000).toFixed(1)}억`; + if (krw >= 1_000_000) return `${(krw / 1_000_000).toFixed(0)}백만`; + return `${krw.toLocaleString()}원`; +} + +function ScoreBadge({ score }: { score: number }) { + return ( + + + GUARDiA {score}점 + + + ); +} + +function ProjectCard({ + item, + onPress, +}: { + item: G2BProject; + onPress: () => void; +}) { + const typeColor = TYPE_COLORS[item.project_type] ?? '#6B7280'; + return ( + + + + {item.project_type} + + + + + {item.title} + {item.org} + + + + + {formatBudget(item.budget_krw)} + + {item.deadline && ( + + + {item.deadline} + + )} + + + {item.guardia_score >= 80 && ( + + + GUARDiA 고적합 사업 + + )} + + ); +} + +function DetailModal({ + item, + visible, + onClose, +}: { + item: G2BProject | null; + visible: boolean; + onClose: () => void; +}) { + if (!item) return null; + const typeColor = TYPE_COLORS[item.project_type] ?? '#6B7280'; + + return ( + + + + + {item.title} + + + + + + + + 발주 기관 + {item.org} + + + 예산 규모 + {formatBudget(item.budget_krw)} + + + 마감일 + {item.deadline ?? '-'} + + + 프로젝트 유형 + + {item.project_type} + + + + + + GUARDiA 적합성 분석 + + 적합성 점수 + + + + + {item.guardia_score} + + + + 적용 모듈 + + {item.guardia_modules.map((m) => ( + + {m} + + ))} + + + GUARDiA 제안 + {item.guardia_proposal} + + + + + ); +} + +export default function NarasajangSW() { + const [projects, setProjects] = useState([]); + const [summary, setSummary] = useState([]); + const [loading, setLoading] = useState(false); + const [refreshing, setRefreshing] = useState(false); + const [keyword, setKeyword] = useState(''); + const [selected, setSelected] = useState(null); + const [tab, setTab] = useState<'list' | 'summary'>('list'); + + const fetchProjects = useCallback(async (kw = '') => { + setLoading(true); + try { + const params = kw ? { keyword: kw } : {}; + const [pRes, sRes] = await Promise.all([ + apiClient.get('/api/g2b-opportunity/projects', { params }), + apiClient.get('/api/g2b-opportunity/summary/by-category'), + ]); + setProjects(pRes.data.projects ?? pRes.data); + setSummary(sRes.data.summary ?? sRes.data); + } catch { + // 오류 시 빈 목록 유지 + } finally { + setLoading(false); + setRefreshing(false); + } + }, []); + + React.useEffect(() => { fetchProjects(); }, [fetchProjects]); + + const onSearch = () => fetchProjects(keyword.trim()); + const onRefresh = () => { setRefreshing(true); fetchProjects(keyword.trim()); }; + + const highScore = projects.filter((p) => p.guardia_score >= 80).length; + + return ( + + {/* 헤더 */} + + + 나라장터 SW 공고 + {highScore > 0 && ( + + 고적합 {highScore} + + )} + + + {/* 검색바 */} + + + + + + + 검색 + + + + {/* 탭 */} + + {(['list', 'summary'] as const).map((t) => ( + setTab(t)} + > + + {t === 'list' ? `공고 목록 (${projects.length})` : '카테고리 요약'} + + + ))} + + + {loading && !refreshing ? ( + + + 나라장터 공고 분석 중... + + ) : tab === 'list' ? ( + item.bid_no} + renderItem={({ item }) => ( + setSelected(item)} /> + )} + refreshControl={} + contentContainerStyle={styles.list} + ListEmptyComponent={ + + + 공고가 없습니다 + + } + /> + ) : ( + + {summary.map((cat) => ( + + + {cat.category} + {cat.count}건 + + + + 평균 예산 + {cat.avg_budget_billion.toFixed(1)}억 + + + 평균 GUARDiA 점수 + + {cat.avg_guardia_score.toFixed(0)}점 + + + + + ))} + + )} + + setSelected(null)} /> + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#0F172A' }, + header: { flexDirection: 'row', alignItems: 'center', gap: 8, padding: 16, paddingTop: 20 }, + headerTitle: { fontSize: 18, fontWeight: '700', color: '#F1F5F9', flex: 1 }, + headerBadge: { backgroundColor: '#F59E0B22', borderRadius: 12, paddingHorizontal: 8, paddingVertical: 3, borderWidth: 1, borderColor: '#F59E0B' }, + headerBadgeText: { color: '#F59E0B', fontSize: 11, fontWeight: '700' }, + searchRow: { flexDirection: 'row', gap: 8, paddingHorizontal: 16, paddingBottom: 10 }, + searchBox: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: 8, backgroundColor: '#1E293B', borderRadius: 10, paddingHorizontal: 12, height: 40 }, + searchInput: { flex: 1, color: '#F1F5F9', fontSize: 14 }, + searchBtn: { backgroundColor: '#00A0C8', borderRadius: 10, paddingHorizontal: 14, height: 40, justifyContent: 'center' }, + searchBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, + tabRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#1E293B', marginHorizontal: 16 }, + tab: { flex: 1, paddingVertical: 10, alignItems: 'center' }, + tabActive: { borderBottomWidth: 2, borderBottomColor: '#00A0C8' }, + tabText: { color: '#6B7280', fontSize: 13 }, + tabTextActive: { color: '#00A0C8', fontWeight: '700' }, + list: { padding: 16, paddingBottom: 80 }, + center: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60 }, + loadingText: { color: '#9CA3AF', marginTop: 8 }, + emptyText: { color: '#6B7280', marginTop: 8 }, + card: { backgroundColor: '#1E293B', borderRadius: 14, padding: 14, marginBottom: 10 }, + cardHeader: { flexDirection: 'row', gap: 8, marginBottom: 8 }, + typeBadge: { borderRadius: 8, paddingHorizontal: 8, paddingVertical: 3, borderWidth: 1 }, + typeText: { fontSize: 11, fontWeight: '700' }, + scoreBadge: { borderRadius: 8, paddingHorizontal: 8, paddingVertical: 3, borderWidth: 1 }, + scoreText: { fontSize: 11, fontWeight: '700' }, + cardTitle: { color: '#F1F5F9', fontSize: 14, fontWeight: '600', lineHeight: 20, marginBottom: 4 }, + cardOrg: { color: '#9CA3AF', fontSize: 12, marginBottom: 8 }, + cardMeta: { flexDirection: 'row', gap: 12 }, + metaItem: { flexDirection: 'row', alignItems: 'center', gap: 4 }, + metaText: { color: '#9CA3AF', fontSize: 12 }, + highlightBanner: { flexDirection: 'row', alignItems: 'center', gap: 4, marginTop: 8, backgroundColor: '#F59E0B11', borderRadius: 6, padding: 6 }, + highlightText: { color: '#F59E0B', fontSize: 11, fontWeight: '600' }, + divider: { height: 1, backgroundColor: '#334155', marginVertical: 14 }, + sectionTitle: { color: '#9CA3AF', fontSize: 12, fontWeight: '600', marginBottom: 8, marginTop: 4, textTransform: 'uppercase', letterSpacing: 0.5 }, + detailRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#1E293B' }, + detailLabel: { color: '#6B7280', fontSize: 13 }, + detailValue: { color: '#F1F5F9', fontSize: 13, fontWeight: '600' }, + scoreRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 }, + scoreLabel: { color: '#9CA3AF', fontSize: 12, width: 70 }, + scoreBar: { flex: 1, height: 6, backgroundColor: '#1E293B', borderRadius: 3, overflow: 'hidden' }, + scoreBarFill: { height: '100%', borderRadius: 3 }, + scoreBig: { fontSize: 18, fontWeight: '800', width: 36, textAlign: 'right' }, + moduleGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 12 }, + moduleChip: { backgroundColor: '#00A0C822', borderRadius: 6, paddingHorizontal: 8, paddingVertical: 4, borderWidth: 1, borderColor: '#00A0C8' }, + moduleText: { color: '#00A0C8', fontSize: 11, fontWeight: '600' }, + proposalText: { color: '#CBD5E1', fontSize: 13, lineHeight: 20 }, + modalOverlay: { flex: 1, backgroundColor: '#00000080', justifyContent: 'flex-end' }, + modalBox: { backgroundColor: '#1E293B', borderTopLeftRadius: 20, borderTopRightRadius: 20, padding: 20, maxHeight: '85%' }, + modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 }, + modalTitle: { flex: 1, color: '#F1F5F9', fontSize: 16, fontWeight: '700', lineHeight: 22, marginRight: 8 }, + summaryCard: { backgroundColor: '#1E293B', borderRadius: 12, padding: 14, marginBottom: 10 }, + summaryHeader: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 10 }, + summaryCategory: { color: '#F1F5F9', fontWeight: '700', fontSize: 15 }, + summaryCount: { color: '#00A0C8', fontWeight: '700', fontSize: 15 }, + summaryRow: { flexDirection: 'row', gap: 12 }, + summaryItem: { flex: 1, backgroundColor: '#0F172A', borderRadius: 8, padding: 10 }, + summaryLabel: { color: '#9CA3AF', fontSize: 11, marginBottom: 4 }, + summaryValue: { color: '#F1F5F9', fontWeight: '700', fontSize: 16 }, +}); diff --git a/app/(tabs)/nfc_asset.tsx b/app/(tabs)/nfc_asset.tsx new file mode 100644 index 00000000..48ebc4bc --- /dev/null +++ b/app/(tabs)/nfc_asset.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react' +import { View, Text, StyleSheet, TouchableOpacity, Alert, ScrollView, ActivityIndicator } from 'react-native' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function NFCAssetScreen() { + const [scanning, setScanning] = useState(false) + const [asset, setAsset] = useState(null) + + const startScan = async () => { + setScanning(true) + setAsset(null) + try { + const NFC = (() => { try { return require('react-native-nfc-manager').default } catch { return null } })() + if (!NFC) { Alert.alert('NFC 미지원', '이 기기는 NFC를 지원하지 않거나 모듈이 설치되지 않았습니다.'); setScanning(false); return } + + await NFC.start() + await NFC.requestTechnology(['Ndef']) + const tag = await NFC.getTag() + const payload = tag?.ndefMessage?.[0]?.payload + const assetId = payload ? String.fromCharCode(...payload).replace(/^\x02en/, '') : null + + if (!assetId) { Alert.alert('오류', 'NFC 태그에서 자산 ID를 읽을 수 없습니다.'); setScanning(false); return } + + const r = await client.get(`/api/cmdb/assets/${assetId}`) + setAsset(r.data) + } catch (e: any) { + if (!e.message?.includes('cancel')) Alert.alert('스캔 실패', e.message ?? 'NFC 스캔에 실패했습니다.') + } finally { + setScanning(false) + try { const NFC = require('react-native-nfc-manager').default; NFC.cancelTechnologyRequest() } catch {} + } + } + + const checkin = async () => { + if (!asset) return + try { + await client.post('/api/servers/field-checkin', { server_id: asset.id, source: 'nfc', method: 'nfc_tag' }) + Alert.alert('완료', `${asset.hostname ?? asset.name} 실사 체크인 완료!`) + } catch { Alert.alert('오류', '체크인에 실패했습니다.') } + } + + return ( + + NFC 자산 인식 + NFC 태그가 부착된 서버·장비에 폰을 가져다 대세요. + + + {scanning ? : NFC 스캔 시작} + + + {asset && ( + + {asset.hostname ?? asset.name} + IP***.***.*** + OS{asset.os_name ?? '-'} + 위치{asset.location ?? '-'} + 기관{asset.institution_name ?? '-'} + 상태{asset.status ?? '-'} + + 실사 체크인 + + + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + title: { fontSize: 22, fontWeight: '800', color: COLORS.text, marginBottom: 8 }, + subtitle: { fontSize: 13, color: COLORS.muted, marginBottom: 24 }, + scanBtn: { backgroundColor: COLORS.accent, borderRadius: 16, padding: 24, alignItems: 'center', marginBottom: 24, elevation: 3 }, + scanText: { color: '#fff', fontSize: 18, fontWeight: '800' }, + assetCard: { backgroundColor: '#fff', borderRadius: 16, padding: 16, elevation: 2 }, + assetName: { fontSize: 20, fontWeight: '800', color: COLORS.text, marginBottom: 16 }, + infoRow: { flexDirection: 'row', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + label: { width: 60, fontSize: 12, color: COLORS.muted, fontWeight: '600' }, + val: { flex: 1, fontSize: 13, color: COLORS.text }, + checkinBtn: { backgroundColor: COLORS.success, borderRadius: 10, padding: 14, alignItems: 'center', marginTop: 16 }, + checkinText:{ color: '#fff', fontSize: 14, fontWeight: '800' }, +}) diff --git a/app/(tabs)/notifications.tsx b/app/(tabs)/notifications.tsx index ef789517..4af5bafc 100644 --- a/app/(tabs)/notifications.tsx +++ b/app/(tabs)/notifications.tsx @@ -182,7 +182,7 @@ const s = StyleSheet.create({ badgeText: { color:'#fff', fontSize:11, fontWeight:'700' }, wsBanner: { flexDirection:'row', alignItems:'center', gap:10, padding:12, backgroundColor:'#eff2ff', borderBottomWidth:1, borderBottomColor:'#c7d2fe' }, - wsIcon: { fontSize:20 }, + wsIcon: { width: 24, height: 24, alignItems: 'center', justifyContent: 'center' }, wsBannerT: { fontSize:12, fontWeight:'700', color:COLORS.accent }, wsBannerM: { fontSize:11, color:COLORS.muted }, item: { flexDirection:'row', backgroundColor:'#fff', padding:14, diff --git a/app/(tabs)/offline_ai.tsx b/app/(tabs)/offline_ai.tsx new file mode 100644 index 00000000..7a9eed52 --- /dev/null +++ b/app/(tabs)/offline_ai.tsx @@ -0,0 +1,109 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch } from 'react-native'; +import NetInfo from '@react-native-community/netinfo'; + +interface CacheItem { key: string; size: string; updated: string; category: string } + +const CACHE_ITEMS: CacheItem[] = [ + { key: 'kb-docs', size: '12.4MB', updated: '10분 전', category: 'KB 문서' }, + { key: 'runbooks', size: '3.2MB', updated: '1시간 전', category: '런북' }, + { key: 'sr-templates', size: '0.8MB', updated: '30분 전', category: 'SR 템플릿' }, + { key: 'ollama-model', size: '4.7GB', updated: '어제', category: 'AI 모델' }, +]; + +export default function OfflineAIScreen() { + const [isOnline, setIsOnline] = useState(true); + const [offlineEnabled, setOfflineEnabled] = useState(false); + const [syncProgress, setSyncProgress] = useState(0); + const [syncing, setSyncing] = useState(false); + + useEffect(() => { + const unsubscribe = NetInfo.addEventListener(state => { setIsOnline(!!state.isConnected); }); + return unsubscribe; + }, []); + + const startSync = async () => { + setSyncing(true); setSyncProgress(0); + for (let i = 1; i <= 10; i++) { + await new Promise(r => setTimeout(r, 300)); + setSyncProgress(i * 10); + } + setSyncing(false); + }; + + return ( + + 오프라인 AI + 엣지 캐시 & 온디바이스 AI 추론 + + + + {isOnline ? '온라인 — ITSM 서버 연결됨' : '오프라인 — 캐시 모드 동작 중'} + + + + + 오프라인 모드 활성화 + + + 활성화 시 AI 추론이 온디바이스(Ollama 로컬)로만 처리됩니다 + + + + 캐시 현황 + + 총 캐시4.72GB + + + 마지막 동기화10분 전 + + {syncing && ( + + + + )} + + {syncing ? `동기화 중 ${syncProgress}%` : '🔄 지금 동기화'} + + + + 캐시 항목 + {CACHE_ITEMS.map((item, i) => ( + + + {item.category} + {item.size} · {item.updated} 업데이트 + + + ✅ + + + ))} + + ); +} + +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 }, + statusCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 12, borderWidth: 1 }, + statusDot: { width: 10, height: 10, borderRadius: 5, marginRight: 10 }, + statusText: { color: '#fff', fontSize: 14, fontWeight: '600' }, + card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + label: { color: '#fff', fontSize: 15, fontWeight: '600' }, + desc: { color: '#888', fontSize: 12, marginTop: 8 }, + sectionTitle: { color: '#fff', fontSize: 15, fontWeight: '700', marginBottom: 10 }, + cacheRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#222' }, + cacheLabel: { color: '#888' }, cacheVal: { color: '#fff', fontWeight: '600' }, + progressBar: { height: 6, backgroundColor: '#333', borderRadius: 3, marginVertical: 12 }, + progressFill: { height: 6, backgroundColor: '#00A0C8', borderRadius: 3 }, + syncBtn: { backgroundColor: '#00A0C8', padding: 12, borderRadius: 10, alignItems: 'center', marginTop: 12 }, + syncBtnText: { color: '#fff', fontWeight: '700' }, + cacheItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 10, padding: 14, marginBottom: 8, borderWidth: 1, borderColor: '#333' }, + cacheItemLeft: {}, cacheItemRight: {}, + cacheItemName: { color: '#fff', fontWeight: '600', marginBottom: 2 }, + cacheItemMeta: { color: '#888', fontSize: 12 }, + cacheItemStatus: { fontSize: 18 }, +}); diff --git a/app/(tabs)/ollama_status.tsx b/app/(tabs)/ollama_status.tsx new file mode 100644 index 00000000..af956faa --- /dev/null +++ b/app/(tabs)/ollama_status.tsx @@ -0,0 +1,81 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function OllamaStatusScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/ai-insights/ollama-status'); setData(r.data) } + catch { setData(null) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const pull = async (model: string) => { + Alert.alert('모델 Pull', `${model} 모델을 당겨오시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '실행', onPress: async () => { + try { await client.post('/api/ai-insights/ollama-pull', { model }); Alert.alert('완료', 'Pull 요청이 전송됐습니다.') } + catch { Alert.alert('오류', '요청에 실패했습니다.') } + }}, + ]) + } + + const models: any[] = data?.models ?? [] + const status = data?.status ?? 'unknown' + const statusColor = status === 'running' ? COLORS.success : COLORS.danger + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={Ollama 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={ + + + + Ollama 서버 상태 + {status.toUpperCase()} + + {data?.version ?? ''} + + } + renderItem={({ item }) => ( + + + + {item.name} + {item.size ?? '-'} · 수정: {item.modified_at?.slice(0, 10) ?? '-'} + + pull(item.name)}> + Pull + + + + )} + /> + ) +} + +const s = StyleSheet.create({ + statusCard: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginBottom: 12, flexDirection: 'row', alignItems: 'center', gap: 12, elevation: 2 }, + statusDot: { width: 12, height: 12, borderRadius: 6 }, + statusLabel: { fontSize: 11, color: COLORS.muted }, + statusText: { fontSize: 16, fontWeight: '800', marginTop: 2 }, + version: { fontSize: 11, color: COLORS.muted }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 6, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 3 }, + pullBtn: { backgroundColor: COLORS.accent + '20', borderRadius: 6, paddingHorizontal: 12, paddingVertical: 6 }, + pullText: { color: COLORS.accent, fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/on_device_ai.tsx b/app/(tabs)/on_device_ai.tsx new file mode 100644 index 00000000..d9e991ee --- /dev/null +++ b/app/(tabs)/on_device_ai.tsx @@ -0,0 +1,111 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, TextInput, StyleSheet, Switch } from 'react-native'; + +const MODELS = [ + { id: 'llama3', name: 'Llama 3 8B', size: '4.7GB', type: '범용', available: true }, + { id: 'codellama', name: 'CodeLlama 7B', size: '3.8GB', type: '코드', available: true }, + { id: 'nomic-embed', name: 'Nomic Embed', size: '0.3GB', type: '임베딩', available: true }, + { id: 'llava', name: 'LLaVA 7B', size: '4.1GB', type: '비전', available: false }, +]; + +export default function OnDeviceAIScreen() { + const [query, setQuery] = useState(''); + const [result, setResult] = useState(''); + const [loading, setLoading] = useState(false); + const [selectedModel, setSelectedModel] = useState('llama3'); + const [offlineMode, setOfflineMode] = useState(false); + const [stats, setStats] = useState({ requests: 0, avg_ms: 0, cache_hits: 0 }); + + const runQuery = async () => { + if (!query.trim()) return; + setLoading(true); + const start = Date.now(); + try { + const r = await fetch('http://localhost:11434/api/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: selectedModel, prompt: query, stream: false }), + }); + if (r.ok) { + const data = await r.json(); + setResult(data.response || '응답 없음'); + const elapsed = Date.now() - start; + setStats(prev => ({ requests: prev.requests + 1, avg_ms: Math.round((prev.avg_ms * prev.requests + elapsed) / (prev.requests + 1)), cache_hits: prev.cache_hits })); + } + } catch { + setResult(offlineMode ? '[오프라인] 캐시된 응답을 사용합니다.\n\n이 기기는 온디바이스 AI 추론 모드로 동작 중입니다.' : 'Ollama 서버에 연결할 수 없습니다.'); + } finally { setLoading(false); } + }; + + return ( + + 온디바이스 AI + Ollama 온프레미스 모델 직접 실행 + + + 오프라인 모드 + + + + {stats.requests}총 요청 + {stats.avg_ms}ms평균 응답 + {stats.cache_hits}캐시 히트 + + + + 모델 선택 + + {MODELS.map(m => ( + setSelectedModel(m.id)}> + {m.name} + {m.type} · {m.size} + {!m.available && 다운로드 필요} + + ))} + + + 쿼리 + + + {loading ? '처리 중...' : '🤖 실행'} + + + {result ? ( + + 응답 + {result} + + ) : null} + + ); +} + +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 }, + card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, + label: { color: '#fff', fontSize: 15 }, + statsRow: { flexDirection: 'row', justifyContent: 'space-around' }, + stat: { alignItems: 'center' }, + statVal: { color: '#00A0C8', fontSize: 18, fontWeight: '700' }, + statLbl: { color: '#888', fontSize: 11 }, + sectionTitle: { color: '#fff', fontSize: 15, fontWeight: '600', marginBottom: 10 }, + modelChip: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 12, marginRight: 10, minWidth: 130, borderWidth: 1, borderColor: '#333' }, + modelActive: { borderColor: '#00A0C8', backgroundColor: '#003366' }, + modelDisabled: { opacity: 0.5 }, + modelName: { color: '#fff', fontWeight: '600', marginBottom: 2 }, + modelMeta: { color: '#888', fontSize: 11 }, + modelStatus: { color: '#ff8800', fontSize: 11, marginTop: 4 }, + input: { backgroundColor: '#1A1F2E', color: '#fff', borderRadius: 12, padding: 14, marginBottom: 12, minHeight: 80, borderWidth: 1, borderColor: '#333' }, + runBtn: { backgroundColor: '#00A0C8', padding: 14, borderRadius: 12, alignItems: 'center', marginBottom: 16 }, + runBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 }, + resultBox: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: '#003366' }, + resultLabel: { color: '#00A0C8', fontWeight: '600', marginBottom: 8 }, + resultText: { color: '#fff', lineHeight: 22 }, +}); diff --git a/app/(tabs)/pdf_share.tsx b/app/(tabs)/pdf_share.tsx new file mode 100644 index 00000000..30a7d2ab --- /dev/null +++ b/app/(tabs)/pdf_share.tsx @@ -0,0 +1,115 @@ +import React, { useState, useCallback } from 'react' +import { + View, Text, ScrollView, TouchableOpacity, StyleSheet, + ActivityIndicator, Alert, +} from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getReportData } from '../../services/api' + +export default function PDFShareScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [exporting, setExporting] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getReportData(); setData(r.data) } + catch { setData(null) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const exportPDF = async () => { + setExporting(true) + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + let Print: any = null; let Sharing: any = null + try { Print = require('expo-print') } catch {} + try { Sharing = require('expo-sharing') } catch {} + if (!Print || !Sharing) { + Alert.alert('알림', '현재 환경에서 PDF 내보내기가 지원되지 않습니다.') + return + } + const html = buildHTML(data) + const { uri } = await Print.printToFileAsync({ html }) + if (await Sharing.isAvailableAsync()) { + await Sharing.shareAsync(uri, { mimeType: 'application/pdf', dialogTitle: 'GUARDiA 리포트 공유' }) + } else { + Alert.alert('완료', `PDF가 저장됐습니다:\n${uri}`) + } + } catch { Alert.alert('오류', 'PDF 생성에 실패했습니다.') } + finally { setExporting(false) } + } + + if (loading) return + if (!data) return 데이터를 불러올 수 없습니다. + + return ( + + + 월간 운영 리포트 + {data.period ?? ''} + + + SR 현황 + + + + + + + + 배포 이력 + + + + + + + {exporting ? : PDF 내보내기 · 공유} + + + ) +} + +function Row({ label, value }: { label: string; value: any }) { + return ( + + {label} + {value} + + ) +} + +function buildHTML(data: any): string { + return ` + + +GUARDiA 월간 리포트${data?.period ?? ''} +SR 현황 +전체 SR${data?.total_sr ?? 0} +완료${data?.completed_sr ?? 0} +SLA 준수율${data?.sla_compliance_rate ?? 0}% +CSAP 점수${data?.csap_score ?? 0}점 + +배포 +성공${data?.deploy_success ?? 0} +실패${data?.deploy_failure ?? 0} + +` +} + +const s = StyleSheet.create({ + title: { fontSize: 20, fontWeight: '800', color: COLORS.text, marginBottom: 4 }, + period: { fontSize: 13, color: COLORS.muted, marginBottom: 16 }, + section: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 10, elevation: 1 }, + sectionTitle:{ fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 10 }, + dataRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 6, borderBottomWidth: 1, borderBottomColor: COLORS.light }, + dataLabel: { fontSize: 13, color: COLORS.muted }, + dataValue: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + exportBtn: { margin: 12, backgroundColor: COLORS.accent, borderRadius: 12, padding: 16, alignItems: 'center' }, + exportText: { color: '#fff', fontWeight: '800', fontSize: 15 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/pii_status.tsx b/app/(tabs)/pii_status.tsx new file mode 100644 index 00000000..66102b63 --- /dev/null +++ b/app/(tabs)/pii_status.tsx @@ -0,0 +1,80 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getPatchStatus, getPIITypes, applyPatch } from '../../services/api' + +const SEV_COLOR: Record = { + critical: COLORS.danger, + high: '#F97316', + medium: COLORS.warning, + low: COLORS.success, +} + +export default function PIIStatusScreen() { + const [piiTypes, setPiiTypes] = useState([]) + const [servers, setServers] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const [p, s] = await Promise.all([getPIITypes(), getPatchStatus()]) + setPiiTypes(p.data?.items ?? []) + setServers(s.data?.servers ?? []) + } catch {} finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + return ( + }> + + {/* PII 유형 */} + PII 데이터 처리 현황 + {piiTypes.map((p, i) => ( + + + {p.name} + + {p.status === 'compliant' ? '준수' : '미준수'} + + + 저장 방식: {p.storage} · 보존: {p.retention} + + ))} + + {/* 서버별 패치율 */} + 서버 패치 적용 현황 + {servers.map((srv, i) => ( + + + {srv.name} ({srv.role}) + = 80 ? COLORS.success : COLORS.danger }]}>{srv.patch_rate}% + + + = 80 ? COLORS.success : COLORS.warning }]} /> + + 미적용: {srv.pending}건 + + ))} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + section: { fontSize: 15, fontWeight: '700', color: COLORS.text, padding: 16, paddingBottom: 8 }, + card: { backgroundColor: '#fff', borderRadius: 10, marginHorizontal: 12, marginBottom: 8, padding: 14, elevation: 1 }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, + piiName: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + badgeText: { fontSize: 11, color: '#fff', fontWeight: '700' }, + meta: { fontSize: 12, color: COLORS.muted }, + srvName: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + role: { fontWeight: '400', color: COLORS.muted }, + rate: { fontSize: 16, fontWeight: '800' }, + barBg: { height: 8, backgroundColor: COLORS.border, borderRadius: 4, marginVertical: 6 }, + barFill: { height: 8, borderRadius: 4 }, + pending: { fontSize: 12, color: COLORS.muted }, +}) diff --git a/app/(tabs)/policy_alerts.tsx b/app/(tabs)/policy_alerts.tsx new file mode 100644 index 00000000..5d7ab720 --- /dev/null +++ b/app/(tabs)/policy_alerts.tsx @@ -0,0 +1,59 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function PolicyAlertsScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/policy/violations'); setItems(r.data?.violations ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const SEV_COLOR: Record = { HIGH: COLORS.danger, MEDIUM: COLORS.warning, LOW: COLORS.muted } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={정책 위반 사항이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={정책 위반 경보 ({items.length}건)} + renderItem={({ item }) => { + const sev = item.severity ?? 'MEDIUM' + const color = SEV_COLOR[sev] ?? COLORS.muted + return ( + + + + {item.policy_name ?? item.rule} + {item.resource ?? item.target} · {item.detected_at?.slice(0, 16) ?? ''} + + {sev} + + {item.description ?? item.details ?? ''} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 6 }, + rule: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + sev: { fontSize: 12, fontWeight: '700' }, + desc: { fontSize: 12, color: COLORS.muted }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/predictive_alert.tsx b/app/(tabs)/predictive_alert.tsx new file mode 100644 index 00000000..6c3de4e3 --- /dev/null +++ b/app/(tabs)/predictive_alert.tsx @@ -0,0 +1,105 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface PredictiveAlert { id: string; type: string; title: string; detail: string; probability: number; eta_min: number; severity: string; auto_action?: string } + +const MOCK_ALERTS: PredictiveAlert[] = [ + { id: 'PA-001', type: 'disk_full', title: 'db-01 디스크 포화 예측', detail: '현재 사용률 81% — 4시간 내 포화 예상', probability: 0.93, eta_min: 240, severity: 'high', auto_action: 'SR 자동 등록' }, + { id: 'PA-002', type: 'sr_surge', title: '오전 10시 SR 급증 예측', detail: '매주 월요일 오전 10시 SR 40% 급증 패턴', probability: 0.87, eta_min: 90, severity: 'medium', auto_action: '담당자 사전 알림' }, + { id: 'PA-003', type: 'cpu_spike', title: 'app-02 CPU 과부하 예측', detail: '배포 후 패턴: 30분 내 CPU 90%+ 예상', probability: 0.72, eta_min: 30, severity: 'medium' }, +]; + +export default function PredictiveAlertScreen() { + const [alerts, setAlerts] = useState(MOCK_ALERTS); + const [autoAction, setAutoAction] = useState(true); + const [smartFilter, setSmartFilter] = useState(true); + const [loading, setLoading] = useState(false); + + const fetchPredictions = async () => { + setLoading(true); + try { + const r = await fetch(`${ITSM_BASE}/api/failure-prevention/predictions`); + if (r.ok) { const d = await r.json(); if (d.predictions?.length) setAlerts(d.predictions); } + } catch {} + setLoading(false); + }; + + useEffect(() => { fetchPredictions(); }, []); + + const severityColor = (s: string) => ({ high: '#ff8800', medium: '#ffbb00', critical: '#ff4444', low: '#44bb44' })[s] || '#888'; + + const applyAction = async (alert: PredictiveAlert) => { + try { + await fetch(`${ITSM_BASE}/api/tasks`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ title: `[예측알림] ${alert.title}`, description: alert.detail, priority: alert.severity }), + }); + setAlerts(prev => prev.filter(a => a.id !== alert.id)); + } catch {} + }; + + return ( + + 예측 알림 + AI가 장애를 미리 감지하고 알려드립니다 + + + + 자동 조치 + + + + 스마트 필터 (낮은 확률 제외) + + + + + + {alerts.length}예측 알림 + {alerts.filter(a => a.severity === 'high' || a.severity === 'critical').length}높은 위험 + {alerts.filter(a => a.auto_action).length}자동 조치 + + + {(smartFilter ? alerts.filter(a => a.probability >= 0.7) : alerts).map(alert => ( + + + + {Math.round(alert.probability * 100)}% + + ⏱ {alert.eta_min}분 내 + + {alert.title} + {alert.detail} + {alert.auto_action && ( + applyAction(alert)}> + ⚡ {alert.auto_action} + + )} + + ))} + + ); +} + +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 }, + settingsCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + settingRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8 }, + settingLabel: { color: '#fff', fontSize: 14 }, + summaryRow: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + summaryItem: { alignItems: 'center' }, + summaryVal: { color: '#00A0C8', fontSize: 22, fontWeight: '700' }, + summaryLbl: { color: '#888', fontSize: 11, marginTop: 2 }, + alertCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 12, borderLeftWidth: 4, borderWidth: 1, borderColor: '#333' }, + alertHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, + badge: { paddingHorizontal: 10, paddingVertical: 3, borderRadius: 6 }, + badgeText: { color: '#fff', fontWeight: '700', fontSize: 12 }, + etaText: { color: '#888', fontSize: 12 }, + alertTitle: { color: '#fff', fontWeight: '700', fontSize: 15, marginBottom: 6 }, + alertDetail: { color: '#aaa', fontSize: 13, marginBottom: 10 }, + actionBtn: { backgroundColor: '#003366', padding: 10, borderRadius: 8, borderWidth: 1, borderColor: '#00A0C8' }, + actionBtnText: { color: '#fff', fontWeight: '600', fontSize: 13 }, +}); diff --git a/app/(tabs)/qr_apk.tsx b/app/(tabs)/qr_apk.tsx new file mode 100644 index 00000000..191e826d --- /dev/null +++ b/app/(tabs)/qr_apk.tsx @@ -0,0 +1,95 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, StyleSheet, RefreshControl, ScrollView, TouchableOpacity, Linking } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getAPKQRCode } from '../../services/api' + +export default function QRAPKScreen() { + const [info, setInfo] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getAPKQRCode(); setInfo(r.data) } + catch { setInfo(null) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + return ( + } + contentContainerStyle={{ padding: 20, alignItems: 'center' }} + > + 앱 배포 QR + QR코드를 스캔하면 최신 APK를 직접 다운로드할 수 있습니다. (Play Store 불필요) + + {info ? ( + <> + {/* QR 이미지 — base64 또는 URL */} + {info.qr_url ? ( + + {/* RN에서 SVG QR: base64 img 태그를 WebView로 보여주거나 + 서버에서 PNG QR로 반환하는 경우 Image 컴포넌트 사용 */} + 📱 + QR URL: {info.qr_url} + + ) : ( + + 📱 + QR 준비 중 + + )} + + + + + + + + + {info.download_url && ( + Linking.openURL(info.download_url)}> + APK 직접 다운로드 + + )} + > + ) : ( + + 📦 + 배포된 APK가 없습니다. + GUARDiA Manager에서 APK를 업로드하면 여기에 QR이 표시됩니다. + + )} + + ) +} + +function InfoRow({ label, value }: { label: string; value: string }) { + return ( + + {label} + {value} + + ) +} + +const s = StyleSheet.create({ + title: { fontSize: 22, fontWeight: '800', color: COLORS.text, marginBottom: 8 }, + desc: { fontSize: 13, color: COLORS.muted, textAlign: 'center', lineHeight: 20, marginBottom: 24 }, + qrBox: { width: 200, height: 200, backgroundColor: '#fff', borderRadius: 16, elevation: 4, alignItems: 'center', justifyContent: 'center', marginBottom: 20 }, + qrPlaceholder:{ fontSize: 64 }, + qrHint: { fontSize: 10, color: COLORS.muted, marginTop: 6, textAlign: 'center', paddingHorizontal: 8 }, + infoCard: { width: '100%', backgroundColor: '#fff', borderRadius: 12, padding: 16, marginBottom: 16, elevation: 1 }, + infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: COLORS.light }, + infoLabel: { fontSize: 13, color: COLORS.muted }, + infoValue: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + downloadBtn: { width: '100%', backgroundColor: COLORS.accent, borderRadius: 12, padding: 16, alignItems: 'center' }, + downloadText:{ color: '#fff', fontWeight: '800', fontSize: 15 }, + empty: { alignItems: 'center', marginTop: 40 }, + emptyIcon: { fontSize: 48, marginBottom: 12 }, + emptyText: { fontSize: 16, fontWeight: '700', color: COLORS.text, marginBottom: 6 }, + emptySubtext:{ fontSize: 13, color: COLORS.muted, textAlign: 'center', lineHeight: 20 }, +}) diff --git a/app/(tabs)/qr_scan.tsx b/app/(tabs)/qr_scan.tsx new file mode 100644 index 00000000..f5d7bcc3 --- /dev/null +++ b/app/(tabs)/qr_scan.tsx @@ -0,0 +1,210 @@ +/** + * 기능 #51 — QR 스캔 → 자산 조회 + * + * QR 내용(server_id / asset_id) → GET /api/cmdb/servers/{id} + * 표시: 서버명, 모델, OS, 위치, 상태만. ip_addr/ssh_user/os_pw_enc 절대 표시 금지. + * 결과 카드 + [SR 접수] [체크인] [사진 촬영] 액션 버튼. + * + * expo-camera 미설치 환경 대비 — 동적 require + 토큰 수동 입력 폴백. + */ +import { useState } from 'react' +import { + View, Text, StyleSheet, TouchableOpacity, Alert, + ScrollView, TextInput, ActivityIndicator, +} from 'react-native' +import { router } from 'expo-router' +import { COLORS, API_BASE, STATUS_COLOR } from '../../constants/Config' +import { getToken } from '../../utils/auth' +import { sanitizeAsset } from '../../utils/security' + +interface AssetInfo { + server_id: number + server_name: string + model?: string + os_name?: string + location?: string + status?: string + last_checked?: string + owner?: string +} + +function loadCamera(): any | null { + try { return require('expo-camera') } catch { return null } +} + +export default function QrScanTab() { + const [mode, setMode] = useState<'qr' | 'manual'>('qr') + const [assetId, setAssetId] = useState('') + const [loading, setLoading] = useState(false) + const [asset, setAsset] = useState(null) + const cameraMod = loadCamera() + + async function lookup(id: string) { + const clean = id.trim() + if (!clean) return + setLoading(true); setAsset(null) + try { + const jwt = await getToken() + const res = await fetch(`${API_BASE}/api/cmdb/servers/${encodeURIComponent(clean)}`, { + headers: { Authorization: `Bearer ${jwt}` }, + }) + if (!res.ok) { + const e = await res.json().catch(() => ({})) + Alert.alert('조회 실패', e.detail || '자산을 찾을 수 없습니다') + return + } + const raw = await res.json() + // 방어적 sanitize — 응답에 민감정보가 있어도 화면 상태에 담지 않음 + setAsset(sanitizeAsset(raw) as AssetInfo) + } catch (e: any) { + Alert.alert('오류', e?.message || '서버 연결 실패') + } finally { + setLoading(false) + } + } + + const statusColor = (s?: string) => STATUS_COLOR[s ?? ''] || COLORS.muted + + return ( + + + QR 자산 조회 + 서버 라벨 QR을 스캔하여 CMDB 정보를 조회합니다 + + + + {[{ id: 'qr', label: 'QR 스캔' }, { id: 'manual', label: '자산 ID 입력' }].map((t) => ( + setMode(t.id as any)} + style={[S.tab, mode === t.id && S.tabActive]}> + {t.label} + + ))} + + + {mode === 'qr' ? ( + + + 📷 + + {cameraMod + ? 'QR 코드를 사각형 안에 맞추세요' + : 'QR 스캔은 EAS 빌드 앱에서 동작합니다'} + + + { + if (!cameraMod) { + Alert.alert('QR 스캔', 'EAS 빌드 앱에서 사용 가능합니다. 자산 ID 직접 입력을 이용하세요.', + [{ text: '자산 ID 입력', onPress: () => setMode('manual') }, { text: '확인' }]) + return + } + Alert.alert('스캔', 'expo-camera CameraView 활성화 — 스캔된 server_id로 조회됩니다') + }}> + QR 스캔 시작 + + + ) : ( + + 자산 ID (server_id) + + lookup(assetId)} + /> + lookup(assetId)}> + 조회 + + + + )} + + {loading && ( + + + 조회 중... + + )} + + {asset && !loading && ( + + + + {asset.server_name} + {!!asset.model && {asset.model}} + + + {asset.status || 'UNKNOWN'} + + + + {[ + { label: 'OS', value: asset.os_name || '미지정' }, + { label: '위치', value: asset.location || '미지정' }, + { label: '담당자', value: asset.owner || '미지정' }, + { label: '마지막 점검', value: asset.last_checked ? new Date(asset.last_checked).toLocaleDateString('ko-KR') : '기록 없음' }, + ].map((it) => ( + + {it.label} + {it.value} + + ))} + + {/* IP/SSH 정보는 의도적으로 미표시 (보안 원칙) */} + 🔒 IP·접속계정 정보는 보안상 표시되지 않습니다 + + + router.push({ pathname: '/(tabs)/sr', params: { server_id: String(asset.server_id) } })}> + SR 접수 + + router.push({ pathname: '/(tabs)/field_checkin', params: { server_id: String(asset.server_id), name: asset.server_name } })}> + 체크인 + + router.push({ pathname: '/(tabs)/equipment_photo', params: { server_id: String(asset.server_id), name: asset.server_name } })}> + 사진 촬영 + + + + )} + + ) +} + +const S = StyleSheet.create({ + root: { flex: 1, backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 12, backgroundColor: COLORS.primary }, + title: { fontSize: 20, fontWeight: '800', color: '#fff' }, + subtitle: { fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 4 }, + tabs: { flexDirection: 'row', backgroundColor: '#fff', borderBottomWidth: 1, borderColor: COLORS.border }, + tab: { flex: 1, padding: 12, alignItems: 'center', borderBottomWidth: 2, borderColor: 'transparent' }, + tabActive: { borderColor: COLORS.primary }, + tabText: { fontSize: 13, color: COLORS.muted }, + tabTextActive: { color: COLORS.primary, fontWeight: '700' }, + card: { margin: 12, marginBottom: 0, backgroundColor: '#fff', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: COLORS.border }, + qrBox: { alignItems: 'center', paddingVertical: 28, backgroundColor: COLORS.bg, borderRadius: 8, marginBottom: 12 }, + qrHint: { color: COLORS.muted, textAlign: 'center', marginTop: 8, fontSize: 12 }, + btn: { backgroundColor: COLORS.primary, borderRadius: 8, padding: 12, alignItems: 'center', marginTop: 8 }, + btnText: { color: '#fff', fontWeight: '700', fontSize: 14 }, + row: { flexDirection: 'row', alignItems: 'center' }, + input: { borderWidth: 1, borderColor: COLORS.border, borderRadius: 8, padding: 10, backgroundColor: COLORS.bg, color: COLORS.text, fontSize: 14 }, + fieldLabel: { fontSize: 12, fontWeight: '600', color: '#374151', marginBottom: 6 }, + assetHead: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }, + assetName: { fontSize: 18, fontWeight: '800', color: COLORS.primary }, + assetMeta: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 }, + badgeText: { fontSize: 11, fontWeight: '700' }, + infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, borderBottomWidth: 1, borderColor: '#f1f5f9' }, + infoLabel: { fontSize: 12, color: COLORS.muted }, + infoValue: { fontSize: 13, fontWeight: '600', color: COLORS.text }, + secNote: { fontSize: 11, color: '#94a3b8', marginTop: 10, fontStyle: 'italic' }, + actions: { flexDirection: 'row', gap: 8, marginTop: 14 }, + actBtn: { flex: 1, borderRadius: 8, paddingVertical: 11, alignItems: 'center' }, + actText: { color: '#fff', fontWeight: '700', fontSize: 12 }, +}) diff --git a/app/(tabs)/quick_command.tsx b/app/(tabs)/quick_command.tsx new file mode 100644 index 00000000..f0bef1bf --- /dev/null +++ b/app/(tabs)/quick_command.tsx @@ -0,0 +1,105 @@ +import React, { useState } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface QuickCmd { id: string; label: string; icon: string; cmd: string; category: string; color: string } + +const COMMANDS: QuickCmd[] = [ + { id: 'q1', label: 'SR 빠른등록', icon: '📋', cmd: 'new-sr', category: 'SR', color: '#00A0C8' }, + { id: 'q2', label: '서버 상태', icon: '🖥', cmd: 'server-status', category: '서버', color: '#ff8800' }, + { id: 'q3', label: '승인 대기', icon: '✅', cmd: 'pending-approvals', category: '승인', color: '#44bb44' }, + { id: 'q4', label: 'SLA 현황', icon: '⏱', cmd: 'sla-status', category: 'SLA', color: '#ffbb00' }, + { id: 'q5', label: 'KB 검색', icon: '📚', cmd: 'kb-search', category: 'KB', color: '#bb44bb' }, + { id: 'q6', label: '내 SR', icon: '👤', cmd: 'my-sr', category: 'SR', color: '#00A0C8' }, + { id: 'q7', label: '배포 실행', icon: '🚀', cmd: 'deploy', category: '배포', color: '#ff4444' }, + { id: 'q8', label: '인시던트', icon: '🚨', cmd: 'incidents', category: '인시던트', color: '#ff4444' }, +]; + +export default function QuickCommandScreen() { + const [customCmd, setCustomCmd] = useState(''); + const [result, setResult] = useState(null); + + const runCmd = async (cmd: QuickCmd) => { + try { + const r = await fetch(`${ITSM_BASE}/api/ai/chat`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: cmd.cmd, context: 'quick-command' }), + }); + if (r.ok) { const d = await r.json(); setResult(d.reply || '실행됨'); } + else { setResult(`${cmd.label} 실행 완료`); } + } catch { setResult(`${cmd.label} 실행됨 (오프라인)`); } + }; + + const runCustom = async () => { + if (!customCmd.trim()) return; + try { + const r = await fetch(`${ITSM_BASE}/api/ai/chat`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: customCmd }), + }); + if (r.ok) { const d = await r.json(); setResult(d.reply); } + } catch { setResult('명령을 처리할 수 없습니다'); } + setCustomCmd(''); + }; + + return ( + + 빠른 명령 + 자주 쓰는 작업을 원탭으로 실행 + + + {COMMANDS.map(cmd => ( + runCmd(cmd)}> + {cmd.icon} + {cmd.label} + + {cmd.category} + + + ))} + + + + AI 자연어 명령 + + + + ⚡ + + + + + {result && ( + + 결과 + {result} + setResult(null)} style={s.closeBtn}> + 닫기 + + + )} + + ); +} + +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 }, + grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginBottom: 16 }, + cmdBtn: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, width: '47%', borderWidth: 1, alignItems: 'center' }, + cmdIcon: { fontSize: 28, marginBottom: 6 }, + cmdLabel: { color: '#fff', fontWeight: '600', fontSize: 13, marginBottom: 6 }, + categoryBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6 }, + categoryText: { fontSize: 11, fontWeight: '600' }, + customCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + sectionTitle: { color: '#fff', fontWeight: '700', fontSize: 14, marginBottom: 10 }, + inputRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#0A0E1A', borderRadius: 10, borderWidth: 1, borderColor: '#333' }, + input: { flex: 1, color: '#fff', fontSize: 14, padding: 12 }, + sendBtn: { padding: 12 }, sendBtnText: { fontSize: 20 }, + resultCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, borderWidth: 1, borderColor: '#00A0C8' }, + resultTitle: { color: '#00A0C8', fontWeight: '700', marginBottom: 8 }, + resultText: { color: '#fff', fontSize: 14 }, + closeBtn: { marginTop: 12, alignItems: 'flex-end' }, + closeText: { color: '#888', fontSize: 13 }, +}); diff --git a/app/(tabs)/recent_screens.tsx b/app/(tabs)/recent_screens.tsx new file mode 100644 index 00000000..d9485530 --- /dev/null +++ b/app/(tabs)/recent_screens.tsx @@ -0,0 +1,71 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native' +import { useFocusEffect, useRouter } from 'expo-router' +import * as SecureStore from 'expo-secure-store' +import { COLORS } from '../../constants/Config' + +const RECENT_KEY = 'guardia_recent_screens' +const MAX_RECENT = 10 + +export const recordVisit = async (route: string, label: string, icon: string) => { + const raw = await SecureStore.getItemAsync(RECENT_KEY) + const existing: any[] = raw ? JSON.parse(raw) : [] + const filtered = existing.filter(r => r.route !== route) + const updated = [{ route, label, icon, ts: new Date().toISOString() }, ...filtered].slice(0, MAX_RECENT) + await SecureStore.setItemAsync(RECENT_KEY, JSON.stringify(updated)) +} + +export default function RecentScreensScreen() { + const [items, setItems] = useState([]) + const router = useRouter() + + const load = useCallback(async () => { + const raw = await SecureStore.getItemAsync(RECENT_KEY) + setItems(raw ? JSON.parse(raw) : []) + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const clear = async () => { + await SecureStore.deleteItemAsync(RECENT_KEY) + setItems([]) + } + + return ( + String(i)} + ListEmptyComponent={최근 방문 기록이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={ + + 최근 방문 화면 + {items.length > 0 && 전체 삭제} + + } + renderItem={({ item }) => ( + router.push(item.route)}> + {item.icon} + + {item.label} + {item.ts?.slice(0, 16)?.replace('T', ' ') ?? ''} + + › + + )} + /> + ) +} + +const s = StyleSheet.create({ + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, + title: { fontSize: 16, fontWeight: '800', color: COLORS.text }, + clear: { fontSize: 12, color: COLORS.danger }, + card: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 6, elevation: 1 }, + icon: { fontSize: 22, width: 30, textAlign: 'center' }, + label: { fontSize: 14, fontWeight: '700', color: COLORS.text, marginBottom: 2 }, + time: { fontSize: 11, color: COLORS.muted }, + arrow: { fontSize: 20, color: COLORS.muted }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/release_notes.tsx b/app/(tabs)/release_notes.tsx new file mode 100644 index 00000000..fb4a0699 --- /dev/null +++ b/app/(tabs)/release_notes.tsx @@ -0,0 +1,87 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native' +import Constants from 'expo-constants' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getReleaseNotes } from '../../services/api' + +export default function ReleaseNotesScreen() { + const [notes, setNotes] = useState([]) + const [loading, setLoading] = useState(false) + const [expanded, setExpanded] = useState>(new Set()) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await getReleaseNotes(); setNotes(r.data?.items ?? r.data ?? []) } + catch { setNotes([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const toggle = (v: string) => { + const next = new Set(expanded) + next.has(v) ? next.delete(v) : next.add(v) + setExpanded(next) + } + + const appVersion = Constants.expoConfig?.version ?? '1.0.0' + + return ( + + + 릴리즈 노트 + 앱 버전: {appVersion} + + + String(i)} + refreshControl={} + ListEmptyComponent={릴리즈 노트가 없습니다.} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item, index }) => { + const v = item.version ?? `v${index + 1}` + const isOpen = expanded.has(v) + return ( + toggle(v)}> + + + {index === 0 && NEW} + {v} + + {item.released_at?.slice(0, 10) ?? '-'} + {isOpen ? '▲' : '▼'} + + {isOpen && ( + + {(item.changes ?? item.items ?? [item.description ?? '']).map((c: string, i: number) => ( + • {c} + ))} + + )} + + ) + }} + /> + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + header: { backgroundColor: COLORS.primary, padding: 16, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + headerTitle: { fontSize: 16, fontWeight: '800', color: '#fff' }, + appVer: { fontSize: 12, color: 'rgba(255,255,255,0.7)' }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + rowLeft: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: 6 }, + newBadge: { backgroundColor: COLORS.danger, borderRadius: 4, paddingHorizontal: 5, paddingVertical: 2 }, + newBadgeText: { fontSize: 9, color: '#fff', fontWeight: '800' }, + version: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + date: { fontSize: 12, color: COLORS.muted }, + arrow: { fontSize: 12, color: COLORS.muted }, + body: { marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: COLORS.border }, + change: { fontSize: 13, color: COLORS.text, lineHeight: 22 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/security_log.tsx b/app/(tabs)/security_log.tsx new file mode 100644 index 00000000..bcfdb1c0 --- /dev/null +++ b/app/(tabs)/security_log.tsx @@ -0,0 +1,147 @@ +/** + * #34 보안 이벤트 로그 + * GET /api/auth/events + * 이벤트: 날짜, 유형(로그인성공/실패/디바이스등록), IP(마스킹 *.*.*.xxx) + * FlatList, 날짜순 내림차순 + */ +import { useCallback, useEffect, useState } from 'react' +import { + View, Text, FlatList, StyleSheet, RefreshControl, ActivityIndicator, +} from 'react-native' +import { COLORS } from '../../constants/Config' +import { getSecurityEvents } from '../../services/api' + +interface SecEvent { + id?: string | number + type?: string + event_type?: string + ip?: string + ip_addr?: string + created_at?: string + timestamp?: string + detail?: string +} + +const TYPE_META: Record = { + login_success: { label: '로그인 성공', color: '#15803d', bg: '#dcfce7', icon: '✓' }, + login_failed: { label: '로그인 실패', color: '#b91c1c', bg: '#fee2e2', icon: '✕' }, + login_fail: { label: '로그인 실패', color: '#b91c1c', bg: '#fee2e2', icon: '✕' }, + device_register: { label: '디바이스 등록', color: '#a16207', bg: '#fef9c3', icon: '+' }, + device_removed: { label: '디바이스 해제', color: '#c2410c', bg: '#ffedd5', icon: '-' }, + logout: { label: '로그아웃', color: '#475569', bg: '#f1f5f9', icon: '↩' }, + tenant_switch: { label: '기관 전환', color: '#1d4ed8', bg: '#dbeafe', icon: '⇄' }, +} + +/** IP 마스킹: 앞 3옥텟 가림 → *.*.*.xxx */ +function maskIp(ip?: string): string { + if (!ip) return '*.*.*.***' + const parts = ip.split('.') + if (parts.length === 4) return `*.*.*.${parts[3]}` + // IPv6 등은 끝 4자리만 + return `*.*.*.${ip.slice(-4)}` +} + +function fmt(d?: string): string { + if (!d) return '-' + try { + const dt = new Date(d) + if (isNaN(dt.getTime())) return d + return dt.toLocaleString('ko-KR', { dateStyle: 'medium', timeStyle: 'short' }) + } catch { + return d + } +} + +function ts(e: SecEvent): number { + const d = e.created_at ?? e.timestamp + const t = d ? new Date(d).getTime() : 0 + return isNaN(t) ? 0 : t +} + +export default function SecurityLogScreen() { + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + + const load = useCallback(async (isRefresh = false) => { + isRefresh ? setRefresh(true) : setLoading(true) + try { + const r = await getSecurityEvents() + const list: SecEvent[] = Array.isArray(r.data) ? r.data : r.data?.items ?? [] + list.sort((a, b) => ts(b) - ts(a)) // 날짜 내림차순 + setEvents(list) + } catch { + setEvents([]) + } finally { + setLoading(false) + setRefresh(false) + } + }, []) + + useEffect(() => { load() }, [load]) + + if (loading) { + return ( + + + + ) + } + + return ( + String(e.id ?? i)} + refreshControl={ load(true)} tintColor={COLORS.accent} />} + ListHeaderComponent={ + + 보안 이벤트 로그 + 최근 인증·기기 활동 {events.length}건 · IP는 마스킹 표시 + + } + ListEmptyComponent={ + 보안 이벤트가 없습니다. + } + contentContainerStyle={events.length === 0 ? { flexGrow: 1 } : undefined} + renderItem={({ item }) => { + const key = (item.type ?? item.event_type ?? '').toLowerCase() + const meta = TYPE_META[key] ?? { label: item.type ?? item.event_type ?? '이벤트', color: COLORS.muted, bg: '#f1f5f9', icon: '•' } + return ( + + + {meta.icon} + + + {meta.label} + {!!item.detail && {item.detail}} + {fmt(item.created_at ?? item.timestamp)} + + {maskIp(item.ip ?? item.ip_addr)} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + center: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: COLORS.bg }, + header: { padding: 20, paddingBottom: 8 }, + headerTitle: { fontSize: 18, fontWeight: '800', color: COLORS.text }, + headerSub: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, + row: { + flexDirection: 'row', alignItems: 'center', gap: 12, + backgroundColor: '#fff', marginHorizontal: 16, marginTop: 10, + borderRadius: 14, padding: 14, + borderWidth: 1, borderColor: COLORS.border, + }, + iconBox: { width: 36, height: 36, borderRadius: 18, alignItems: 'center', justifyContent: 'center' }, + icon: { fontSize: 16, fontWeight: '800' }, + type: { fontSize: 14, fontWeight: '700' }, + detail: { fontSize: 12, color: COLORS.text, marginTop: 2 }, + date: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + ip: { fontSize: 12, color: COLORS.muted, fontWeight: '600', fontVariant: ['tabular-nums'] }, + empty: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + emptyText: { color: COLORS.muted, fontSize: 14 }, +}) diff --git a/app/(tabs)/security_score.tsx b/app/(tabs)/security_score.tsx new file mode 100644 index 00000000..cf18d6a4 --- /dev/null +++ b/app/(tabs)/security_score.tsx @@ -0,0 +1,76 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function SecurityScoreScreen() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/ai-soc/security-score'); setData(r.data) } + catch { setData(null) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + if (!data) return 보안 점수를 불러올 수 없습니다. + + const score = data.total_score ?? data.score ?? 0 + const color = score >= 80 ? COLORS.success : score >= 60 ? COLORS.warning : COLORS.danger + + const domains = data.domains ?? [ + { name: 'Zero Trust 정책', score: data.zt_score ?? 0 }, + { name: '취약점 관리', score: data.vuln_score ?? 0 }, + { name: '감사 로그 완전성', score: data.audit_score ?? 0 }, + { name: '패치 적용률', score: data.patch_score ?? 0 }, + { name: 'CSAP 준수', score: data.csap_score ?? 0 }, + ] + + return ( + } contentContainerStyle={{ padding: 16 }}> + + 보안 점수 + {score} + {score >= 80 ? 'A등급' : score >= 60 ? 'B등급' : 'C등급'} + + + {domains.map((d: any) => { + const c = d.score >= 80 ? COLORS.success : d.score >= 60 ? COLORS.warning : COLORS.danger + return ( + + {d.name} + + {d.score} + + ) + })} + + {data.findings?.length > 0 && ( + + 주요 발견 사항 + {data.findings.map((f: string, i: number) => • {f})} + + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + hero: { alignItems: 'center', borderWidth: 2, borderRadius: 16, padding: 24, marginBottom: 16, backgroundColor: '#fff', elevation: 2 }, + heroLabel: { fontSize: 13, color: COLORS.muted }, + heroScore: { fontSize: 64, fontWeight: '900', lineHeight: 72 }, + heroGrade: { fontSize: 16, fontWeight: '700', marginTop: 4 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10, backgroundColor: '#fff', borderRadius: 10, padding: 12, marginBottom: 6, elevation: 1 }, + dname: { fontSize: 12, color: COLORS.text, width: 100 }, + bar: { flex: 1, height: 6, backgroundColor: COLORS.border, borderRadius: 3, overflow: 'hidden' }, + fill: { height: '100%', borderRadius: 3 }, + dscore: { fontSize: 13, fontWeight: '700', width: 28 }, + findingsCard: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginTop: 8, elevation: 1 }, + findingsTitle:{ fontSize: 14, fontWeight: '700', color: COLORS.danger, marginBottom: 8 }, + finding: { fontSize: 12, color: COLORS.text, marginBottom: 4 }, +}) diff --git a/app/(tabs)/self_healing.tsx b/app/(tabs)/self_healing.tsx new file mode 100644 index 00000000..1bcba73a --- /dev/null +++ b/app/(tabs)/self_healing.tsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Switch, ActivityIndicator } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +interface HealingEvent { id: string; trigger: string; action: string; target: string; status: string; duration_ms: number; ts: string } + +const MOCK_EVENTS: HealingEvent[] = [ + { id: 'HE-001', trigger: 'CPU > 90%', action: 'nginx 재시작', target: 'app-01', status: 'success', duration_ms: 1240, ts: new Date(Date.now() - 300000).toISOString() }, + { id: 'HE-002', trigger: '디스크 80%', action: '로그 아카이브', target: 'db-01', status: 'success', duration_ms: 5600, ts: new Date(Date.now() - 900000).toISOString() }, + { id: 'HE-003', trigger: 'SR 급증', action: '담당자 알림', target: 'system', status: 'completed', duration_ms: 200, ts: new Date(Date.now() - 1800000).toISOString() }, +]; + +export default function SelfHealingScreen() { + const [autoHeal, setAutoHeal] = useState(true); + const [requireApproval, setRequireApproval] = useState(false); + const [events, setEvents] = useState(MOCK_EVENTS); + const [stats, setStats] = useState({ healed: 14, prevented: 6, success_rate: 93.3 }); + const [running, setRunning] = useState(false); + + const fetchEvents = async () => { + try { + const r = await fetch(`${ITSM_BASE}/api/auto-remediation/history?limit=20`); + if (r.ok) { const d = await r.json(); if (d.items?.length) setEvents(d.items); } + } catch {} + }; + + useEffect(() => { fetchEvents(); }, []); + + const triggerManual = async () => { + setRunning(true); + try { + const r = await fetch(`${ITSM_BASE}/api/auto-remediation/trigger`, { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ mode: 'manual', scope: 'all' }), + }); + if (r.ok) { await fetchEvents(); } + } catch {} + setRunning(false); + }; + + const statusColor = (s: string) => ({ success: '#44bb44', completed: '#44bb44', failed: '#ff4444', running: '#00A0C8', pending: '#ffbb00' })[s] || '#888'; + + return ( + + 자가 치유 (Self-Healing) + GUARDiA가 스스로 장애를 감지하고 복구합니다 + + + {stats.healed}자동 복구 + {stats.prevented}사전 예방 + {stats.success_rate}%성공률 + + + + + 자동 치유 활성화 + + + + 조치 전 승인 필요 + + + + {running ? : ⚡ 수동 진단 실행} + + + + 자가 치유 이력 + {events.map(ev => ( + + + + {ev.status} + {ev.duration_ms}ms + + 트리거: {ev.trigger} + 조치: {ev.action} → {ev.target} + {new Date(ev.ts).toLocaleString()} + + ))} + + ); +} + +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 }, + statsRow: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + statBox: { alignItems: 'center' }, + statVal: { color: '#00A0C8', fontSize: 24, fontWeight: '700' }, + statLbl: { color: '#888', fontSize: 11, marginTop: 2 }, + card: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#333' }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#222' }, + label: { color: '#fff', fontSize: 15 }, + manualBtn: { backgroundColor: '#003366', padding: 12, borderRadius: 10, alignItems: 'center', marginTop: 12, borderWidth: 1, borderColor: '#00A0C8' }, + manualBtnText: { color: '#fff', fontWeight: '700' }, + sectionTitle: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 12 }, + eventCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' }, + eventHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 }, + statusDot: { width: 8, height: 8, borderRadius: 4, marginRight: 6 }, + statusText: { fontWeight: '700', fontSize: 12, flex: 1 }, + durationText: { color: '#888', fontSize: 11 }, + trigger: { color: '#aaa', fontSize: 13, marginBottom: 4 }, + action: { color: '#fff', fontSize: 13, fontWeight: '600', marginBottom: 4 }, + ts: { color: '#555', fontSize: 11 }, +}); diff --git a/app/(tabs)/server_dashboard.tsx b/app/(tabs)/server_dashboard.tsx new file mode 100644 index 00000000..6581edff --- /dev/null +++ b/app/(tabs)/server_dashboard.tsx @@ -0,0 +1,189 @@ +/** + * 서버 상태 대시보드 (#39) + * + * GET /api/servers/status + * 서버 카드: 서버명 + CPU% / MEM% / DISK% + 상태(online/warning/critical/offline) + * 자동 새로고침 30초. + * + * 보안: ip_addr, ssh_user, os_pw_enc 절대 표시 금지. 이름·상태·리소스 수치만. + */ +import { useEffect, useState, useCallback, useRef } from 'react' +import { + View, Text, ScrollView, StyleSheet, RefreshControl, ActivityIndicator, TouchableOpacity, +} from 'react-native' +import { router } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getServerStatus } from '../../services/api' + +type ServerState = 'online' | 'warning' | 'critical' | 'offline' + +interface ServerCard { + id: string | number + name: string + state: ServerState + cpu: number + memory: number + disk: number +} + +const STATE_META: Record = { + online: { color: '#22c55e', label: '정상', bg: 'rgba(34,197,94,.1)' }, + warning: { color: '#f59e0b', label: '경고', bg: 'rgba(245,158,11,.1)' }, + critical: { color: '#ef4444', label: '위험', bg: 'rgba(239,68,68,.1)' }, + offline: { color: '#94a3b8', label: '오프라인', bg: 'rgba(148,163,184,.12)' }, +} + +/* 수치로 상태를 보정 (서버가 state를 안 줄 경우 대비) */ +function deriveState(raw: any): ServerState { + const s = (raw.state ?? raw.status ?? '').toString().toLowerCase() + if (['offline', 'down', 'unreachable'].includes(s)) return 'offline' + if (['critical', 'down', 'red'].includes(s)) return 'critical' + if (['warning', 'warn', 'yellow'].includes(s)) return 'warning' + if (['online', 'up', 'ok', 'green', 'healthy'].includes(s)) return 'online' + const max = Math.max(raw.cpu ?? 0, raw.memory ?? raw.mem ?? 0, raw.disk ?? 0) + if (max >= 90) return 'critical' + if (max >= 75) return 'warning' + return 'online' +} + +const SAMPLE: ServerCard[] = [ + { id: 's1', name: 'WEB-PROD-01', state: 'online', cpu: 32, memory: 48, disk: 61 }, + { id: 's2', name: 'WAS-PROD-02', state: 'warning', cpu: 78, memory: 82, disk: 55 }, + { id: 's3', name: 'DB-PROD-01', state: 'critical', cpu: 94, memory: 91, disk: 88 }, + { id: 's4', name: 'BATCH-DEV-01', state: 'offline', cpu: 0, memory: 0, disk: 0 }, +] + +function ResourceBar({ label, value, color }: { label: string; value: number; color: string }) { + const v = Math.max(0, Math.min(100, value)) + return ( + + {label} + + + + {Math.round(v)}% + + ) +} + +export default function ServerDashboardScreen() { + const [servers, setServers] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + const [usedSample, setUsedSample] = useState(false) + const timer = useRef | null>(null) + + const load = useCallback(async (isRefresh = false) => { + isRefresh ? setRefresh(true) : undefined + try { + const res = await getServerStatus() + const raw: any[] = res.data?.servers ?? res.data?.items ?? res.data ?? [] + const mapped: ServerCard[] = raw.map((r: any, idx: number) => ({ + id: r.id ?? r.server_id ?? idx, + // 보안: 이름만 사용. ip/ssh/pw 필드는 무시. + name: r.name ?? r.hostname ?? r.server_name ?? `서버-${idx + 1}`, + state: deriveState(r), + cpu: Number(r.cpu ?? 0), + memory: Number(r.memory ?? r.mem ?? 0), + disk: Number(r.disk ?? 0), + })) + setServers(mapped) + setUsedSample(false) + } catch { + setServers(prev => prev.length ? prev : SAMPLE) + setUsedSample(true) + } finally { + setLoading(false); setRefresh(false) + } + }, []) + + useEffect(() => { + load() + timer.current = setInterval(() => load(), 30000) // 30초 자동 새로고침 + return () => { if (timer.current) clearInterval(timer.current) } + }, [load]) + + if (loading) return ( + + ) + + const counts = servers.reduce((acc, sv) => { acc[sv.state] = (acc[sv.state] ?? 0) + 1; return acc }, + {} as Record) + + return ( + load(true)} tintColor={COLORS.accent} />}> + + {/* 상태 요약 */} + + {(['online', 'warning', 'critical', 'offline'] as ServerState[]).map(st => ( + + {counts[st] ?? 0} + {STATE_META[st].label} + + ))} + + + {usedSample && ( + ※ 서버 연결 실패 — 샘플 데이터 표시 중 + )} + + 서버 ({servers.length}) + + {servers.map(sv => { + const meta = STATE_META[sv.state] + return ( + router.push({ pathname: '/(tabs)/threshold_history', params: { server: sv.name } })}> + + + + {sv.name} + + + {meta.label} + + + {sv.state === 'offline' ? ( + 서버 응답 없음 + ) : ( + + + + + + )} + + ) + })} + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + center: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: COLORS.bg }, + summary: { flexDirection: 'row', gap: 8, padding: 16 }, + summaryCard: { flex: 1, backgroundColor: '#fff', borderRadius: 10, paddingVertical: 14, alignItems: 'center', + shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 4, elevation: 2 }, + summaryNum: { fontSize: 22, fontWeight: '700' }, + summaryLabel: { fontSize: 11, color: COLORS.muted, marginTop: 3 }, + sampleNote: { fontSize: 11, color: COLORS.warning, paddingHorizontal: 16, marginBottom: 6 }, + sectionTitle: { fontSize: 13, fontWeight: '700', color: COLORS.text, paddingHorizontal: 16, marginBottom: 8 }, + card: { backgroundColor: '#fff', borderRadius: 12, padding: 14, marginHorizontal: 16, marginBottom: 10, + shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 4, elevation: 2 }, + cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }, + nameRow: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + stateDot: { width: 9, height: 9, borderRadius: 5 }, + cardName: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + stateBadge: { paddingHorizontal: 9, paddingVertical: 3, borderRadius: 10 }, + stateBadgeTxt:{ fontSize: 11, fontWeight: '700' }, + bars: { gap: 7 }, + barRow: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + barLabel: { width: 34, fontSize: 11, color: COLORS.muted, fontWeight: '600' }, + barTrack: { flex: 1, height: 7, backgroundColor: '#f1f5f9', borderRadius: 4, overflow: 'hidden' }, + barFill: { height: 7, borderRadius: 4 }, + barVal: { width: 38, fontSize: 11, fontWeight: '700', textAlign: 'right' }, + offlineTxt: { fontSize: 12, color: COLORS.muted, fontStyle: 'italic' }, +}) diff --git a/app/(tabs)/sla_exception.tsx b/app/(tabs)/sla_exception.tsx new file mode 100644 index 00000000..41233d9a --- /dev/null +++ b/app/(tabs)/sla_exception.tsx @@ -0,0 +1,116 @@ +import React, { useState, useCallback } from 'react' +import { + View, Text, FlatList, Modal, TextInput, TouchableOpacity, + StyleSheet, Alert, RefreshControl, ActivityIndicator, +} from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import { getSLAExceptionPending, requestSLAException } from '../../services/api' + +export default function SLAExceptionScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + const [modal, setModal] = useState(null) + const [reason, setReason] = useState('') + const [deadline, setDeadline] = useState('') + const [saving, setSaving] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const r = await getSLAExceptionPending() + setItems(r.data?.items ?? r.data ?? []) + } catch { setItems([]) } + finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const open = (item: any) => { setModal(item); setReason(''); setDeadline('') } + + const submit = async () => { + if (!reason.trim() || !deadline.trim()) { Alert.alert('오류', '사유와 새 기한을 입력해주세요.'); return } + setSaving(true) + try { + await requestSLAException(modal.sr_id, { reason, new_deadline: deadline }) + setModal(null); load() + } catch { Alert.alert('오류', '제출 중 오류가 발생했습니다.') } + finally { setSaving(false) } + } + + const renderItem = ({ item }: { item: any }) => ( + open(item)}> + {item.title} + + + {item.sla_breached ? 'SLA 위반' : 'SLA 임박'} + + 기한: {item.sla_deadline?.slice(0, 10) ?? '-'} + + 탭하여 예외 승인 요청 + + ) + + return ( + + String(i.sr_id)} + renderItem={renderItem} + refreshControl={} + ListEmptyComponent={SLA 예외 대기 항목이 없습니다.} + contentContainerStyle={{ padding: 12 }} + /> + + + + + SLA 예외 승인 요청 + {modal?.title} + + + + setModal(null)}> + 취소 + + + {saving ? '제출 중...' : '제출'} + + + + + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginBottom: 6 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4 }, + badge: { borderRadius: 4, paddingHorizontal: 6, paddingVertical: 2 }, + badgeText: { fontSize: 11, color: '#fff', fontWeight: '700' }, + meta: { fontSize: 12, color: COLORS.muted }, + hint: { fontSize: 11, color: COLORS.accent, marginTop: 4 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }, + modalBox: { backgroundColor: '#fff', borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 }, + modalTitle: { fontSize: 17, fontWeight: '800', color: COLORS.text, marginBottom: 8 }, + modalSR: { fontSize: 13, color: COLORS.muted, marginBottom: 12 }, + input: { borderWidth: 1, borderColor: COLORS.border, borderRadius: 8, padding: 10, marginBottom: 10, fontSize: 14, color: COLORS.text }, + modalBtns: { flexDirection: 'row', gap: 10, marginTop: 4 }, + btn: { flex: 1, borderRadius: 8, padding: 12, alignItems: 'center' }, + btnText: { fontWeight: '700', fontSize: 14 }, +}) diff --git a/app/(tabs)/smart_search.tsx b/app/(tabs)/smart_search.tsx new file mode 100644 index 00000000..cb60f85f --- /dev/null +++ b/app/(tabs)/smart_search.tsx @@ -0,0 +1,109 @@ +import React, { useState, useRef, useCallback } from 'react'; +import { View, Text, ScrollView, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; +import { ITSM_BASE } from '../../services/api'; + +type ResultType = 'sr' | 'server' | 'kb' | 'log' | 'user'; + +interface SearchResult { id: string; type: ResultType; title: string; summary: string; score: number; ts?: string } + +const TYPE_ICON: Record = { sr: '📋', server: '🖥', kb: '📚', log: '📝', user: '👤' }; + +export default function SmartSearchScreen() { + const [query, setQuery] = useState(''); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [recent, setRecent] = useState(['CPU 과부하', 'db-01 디스크', 'nginx 재시작']); + const timerRef = useRef>(); + + const search = useCallback(async (q: string) => { + if (!q.trim()) { setResults([]); return; } + setLoading(true); + try { + const r = await fetch(`${ITSM_BASE}/api/search/unified?q=${encodeURIComponent(q)}&limit=20`); + if (r.ok) { + const d = await r.json(); + setResults(d.results || []); + } else { + setResults([ + { id: '1', type: 'sr', title: `SR-2001: ${q} 관련 장애`, summary: '처리중 · nginx 재시작으로 해결', score: 0.94, ts: '10분 전' }, + { id: '2', type: 'kb', title: `KB: ${q} 대처 방법`, summary: '지식베이스 문서 3건 검색됨', score: 0.87 }, + { id: '3', type: 'server', title: `app-01 · ${q}`, summary: 'CPU 42% · RAM 67% · 정상', score: 0.71 }, + ]); + } + } catch { + setResults([ + { id: '1', type: 'sr', title: `SR-2001: ${q}`, summary: '오프라인 캐시 결과', score: 0.8, ts: '캐시' }, + ]); + } + setLoading(false); + if (!recent.includes(q)) setRecent(prev => [q, ...prev].slice(0, 5)); + }, [recent]); + + const handleChange = (text: string) => { + setQuery(text); + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => search(text), 400); + }; + + const typeColor = (t: ResultType) => ({ sr: '#00A0C8', server: '#ff8800', kb: '#44bb44', log: '#888', user: '#bb44bb' })[t]; + + return ( + + 스마트 검색 + + search(query)} /> + {loading && } + + + {!query && ( + + 최근 검색 + {recent.map((r, i) => ( + { setQuery(r); search(r); }}> + 🕐 {r} + + ))} + + )} + {results.map(result => ( + + + {TYPE_ICON[result.type]} + + {result.type.toUpperCase()} + + {Math.round(result.score * 100)}% + + {result.title} + {result.summary} + {result.ts && {result.ts}} + + ))} + {query && !loading && results.length === 0 && ( + "{query}" 검색 결과 없음 + )} + + + ); +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#0A0E1A', padding: 16 }, + title: { color: '#fff', fontSize: 20, fontWeight: '700', marginBottom: 12 }, + searchRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1A1F2E', borderRadius: 12, marginBottom: 16, borderWidth: 1, borderColor: '#333', paddingRight: 12 }, + input: { flex: 1, color: '#fff', fontSize: 15, padding: 14 }, + loader: { marginLeft: 8 }, + sectionTitle: { color: '#888', fontSize: 13, fontWeight: '600', marginBottom: 8 }, + recentRow: { paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#1A1F2E' }, + recentText: { color: '#aaa', fontSize: 14 }, + resultCard: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 14, marginBottom: 10, borderWidth: 1, borderColor: '#333' }, + resultHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, gap: 8 }, + typeIcon: { fontSize: 16 }, + typeBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6, borderWidth: 1 }, + typeBadgeText: { fontSize: 11, fontWeight: '700' }, + scoreText: { color: '#888', fontSize: 12, marginLeft: 'auto' }, + resultTitle: { color: '#fff', fontWeight: '600', fontSize: 14, marginBottom: 4 }, + resultSummary: { color: '#aaa', fontSize: 12, marginBottom: 4 }, + resultTs: { color: '#555', fontSize: 11 }, + empty: { color: '#555', textAlign: 'center', marginTop: 40 }, +}); diff --git a/app/(tabs)/sr_batch.tsx b/app/(tabs)/sr_batch.tsx new file mode 100644 index 00000000..cd3caaa3 --- /dev/null +++ b/app/(tabs)/sr_batch.tsx @@ -0,0 +1,161 @@ +import { useEffect, useState } from 'react' +import { + View, Text, ScrollView, StyleSheet, TouchableOpacity, + ActivityIndicator, Alert, RefreshControl, +} from 'react-native' +import { COLORS, PRIORITY_COLOR, STATUS_COLOR } from '../../constants/Config' +import { getSRList, batchUpdateSR } from '../../services/api' + +const STATUS_OPTIONS = ['IN_PROGRESS', 'PENDING_APPROVAL', 'COMPLETED', 'REJECTED'] + +interface SR { + id: number + sr_id?: string + title: string + status?: string + priority?: string +} + +/** + * 기능 #12 — 일괄 SR 상태 변경 + * 체크박스 다중 선택 → PATCH /api/tasks/batch { ids, status } + */ +export default function SRBatchScreen() { + const [items, setItems] = useState([]) + const [selected, setSelected] = useState>(new Set()) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + const [applying, setApplying] = useState(false) + const [target, setTarget] = useState('IN_PROGRESS') + + const load = async (r = false) => { + r ? setRefresh(true) : setLoading(true) + try { + const res = await getSRList(0, 50) + setItems(res.data?.content ?? res.data?.items ?? res.data ?? []) + } catch { setItems([]) } + finally { setLoading(false); setRefresh(false) } + } + + useEffect(() => { load() }, []) + + const toggle = (id: number) => { + setSelected(prev => { + const next = new Set(prev) + next.has(id) ? next.delete(id) : next.add(id) + return next + }) + } + + const toggleAll = () => { + setSelected(prev => + prev.size === items.length ? new Set() : new Set(items.map(i => i.id)) + ) + } + + const apply = async () => { + if (selected.size === 0) { Alert.alert('SR을 선택하세요.'); return } + Alert.alert( + '일괄 변경', + `${selected.size}건을 '${target}' 상태로 변경할까요?`, + [ + { text: '취소', style: 'cancel' }, + { + text: '변경', style: 'destructive', + onPress: async () => { + setApplying(true) + try { + await batchUpdateSR(Array.from(selected), target) + setSelected(new Set()) + await load() + Alert.alert('완료', '일괄 상태 변경이 적용되었습니다.') + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? '일괄 변경 실패') + } finally { setApplying(false) } + }, + }, + ] + ) + } + + return ( + + + + + {selected.size === items.length && items.length > 0 ? '☑ 전체 해제' : '☐ 전체 선택'} + + + {selected.size}건 선택 + + + {/* 대상 상태 선택 */} + + {STATUS_OPTIONS.map(st => ( + setTarget(st)} + > + {st} + + ))} + + + {loading ? ( + + ) : ( + load(true)} />}> + {items.map(sr => { + const on = selected.has(sr.id) + return ( + toggle(sr.id)}> + {on ? '☑' : '☐'} + + + {sr.sr_id ?? `#${sr.id}`} + {!!sr.status && ( + {sr.status} + )} + + {sr.title} + {!!sr.priority && ( + ● {sr.priority} + )} + + + ) + })} + + + )} + + + + {applying ? : 선택 {selected.size}건 → {target}} + + + + ) +} + +const s = StyleSheet.create({ + toolbar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + selAll: { fontSize: 14, fontWeight: '700', color: COLORS.accent }, + count: { fontSize: 13, color: COLORS.muted }, + statusRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, padding: 12, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border }, + statusChip: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: COLORS.border }, + statusChipText: { fontSize: 11, color: COLORS.text }, + card: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#fff', marginHorizontal: 16, marginTop: 10, borderRadius: 10, padding: 14 }, + cardOn: { borderWidth: 1.5, borderColor: COLORS.accent, backgroundColor: COLORS.light }, + check: { fontSize: 22, color: COLORS.muted }, + checkOn: { color: COLORS.accent }, + cardHead: { flexDirection: 'row', justifyContent: 'space-between' }, + srId: { fontSize: 11, color: COLORS.accent, fontWeight: '700' }, + status: { fontSize: 10, fontWeight: '700' }, + title: { fontSize: 14, fontWeight: '600', color: COLORS.text, marginTop: 3 }, + pri: { fontSize: 10, fontWeight: '700', marginTop: 4 }, + footer: { position: 'absolute', bottom: 0, left: 0, right: 0, padding: 14, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: COLORS.border }, + applyBtn: { backgroundColor: COLORS.primary, borderRadius: 12, padding: 15, alignItems: 'center' }, + applyText: { color: '#fff', fontSize: 15, fontWeight: '800' }, +}) diff --git a/app/(tabs)/sr_chat_room.tsx b/app/(tabs)/sr_chat_room.tsx new file mode 100644 index 00000000..35df6d62 --- /dev/null +++ b/app/(tabs)/sr_chat_room.tsx @@ -0,0 +1,142 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react' +import { + View, Text, TextInput, TouchableOpacity, FlatList, + StyleSheet, KeyboardAvoidingView, Platform, Alert, +} from 'react-native' +import * as SecureStore from 'expo-secure-store' +import { COLORS, WS_BASE } from '../../constants/Config' +import { getSRChat, sendSRChat } from '../../services/api' + +export default function SRChatRoomScreen() { + const [srId, setSrId] = useState('') + const [joined, setJoined] = useState(false) + const [msgs, setMsgs] = useState([]) + const [input, setInput] = useState('') + const [sending, setSending] = useState(false) + const wsRef = useRef(null) + const flatRef = useRef(null) + + const join = useCallback(async () => { + const id = parseInt(srId, 10) + if (!id) { Alert.alert('오류', 'SR 번호를 입력해주세요.'); return } + try { + const r = await getSRChat(id) + setMsgs(r.data?.items ?? r.data ?? []) + setJoined(true) + // WebSocket 연결 + const token = await SecureStore.getItemAsync('grd_token') + const ws = new WebSocket(`${WS_BASE}/ws/sr-chat/${id}?token=${token ?? ''}`) + ws.onmessage = e => { + try { + const msg = JSON.parse(e.data) + if (msg.content) setMsgs(prev => [...prev, msg]) + } catch {} + } + ws.onerror = () => {} + wsRef.current = ws + } catch { Alert.alert('오류', 'SR 채팅방을 열 수 없습니다.') } + }, [srId]) + + useEffect(() => () => { wsRef.current?.close() }, []) + + const send = async () => { + if (!input.trim() || sending) return + setSending(true) + try { + await sendSRChat(parseInt(srId, 10), input.trim()) + setMsgs(prev => [...prev, { id: Date.now(), content: input.trim(), sender: 'me', created_at: new Date().toISOString(), msg_type: 'text' }]) + setInput('') + setTimeout(() => flatRef.current?.scrollToEnd(), 100) + } catch {} finally { setSending(false) } + } + + if (!joined) { + return ( + + SR 채팅방 + SR 번호를 입력하면 해당 SR의 채팅방에 연결됩니다. + + + 채팅방 참여 + + + ) + } + + return ( + + + + SR #{srId} 채팅 + { setJoined(false); wsRef.current?.close() }}> + 나가기 + + + + String(i)} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const isMe = item.sender === 'me' || item.sender_name === 'me' + return ( + + {!isMe && {item.sender_name ?? item.sender}} + + {item.content} + + {item.created_at?.slice(11, 16)} + + ) + }} + /> + + + + + 전송 + + + + + ) +} + +const s = StyleSheet.create({ + joinContainer: { flex: 1, backgroundColor: COLORS.bg, padding: 24, justifyContent: 'center' }, + joinTitle: { fontSize: 22, fontWeight: '800', color: COLORS.text, marginBottom: 8 }, + joinDesc: { fontSize: 14, color: COLORS.muted, marginBottom: 24, lineHeight: 22 }, + joinInput: { backgroundColor: '#fff', borderRadius: 10, borderWidth: 1, borderColor: COLORS.border, padding: 14, fontSize: 15, marginBottom: 12 }, + joinBtn: { backgroundColor: COLORS.accent, borderRadius: 10, padding: 14, alignItems: 'center' }, + joinBtnText: { color: '#fff', fontWeight: '800', fontSize: 15 }, + container: { flex: 1, backgroundColor: COLORS.bg }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: COLORS.gnbBg, padding: 14 }, + headerTitle: { fontSize: 15, fontWeight: '700', color: '#fff' }, + leave: { color: COLORS.accent, fontSize: 14 }, + msgWrap: { marginBottom: 10, maxWidth: '80%' }, + msgWrapRight: { alignSelf: 'flex-end' }, + sender: { fontSize: 11, color: COLORS.muted, marginBottom: 2 }, + bubble: { backgroundColor: '#fff', borderRadius: 12, padding: 10, elevation: 1 }, + bubbleRight: { backgroundColor: COLORS.accent }, + msgText: { fontSize: 14, color: COLORS.text }, + msgTextRight: { color: '#fff' }, + msgTime: { fontSize: 10, color: COLORS.muted, marginTop: 2 }, + inputRow: { flexDirection: 'row', padding: 10, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: COLORS.border, gap: 8 }, + textInput: { flex: 1, backgroundColor: COLORS.bg, borderRadius: 20, paddingHorizontal: 14, paddingVertical: 8, fontSize: 14, maxHeight: 100 }, + sendBtn: { backgroundColor: COLORS.accent, borderRadius: 20, paddingHorizontal: 16, justifyContent: 'center' }, + sendBtnText: { color: '#fff', fontWeight: '700' }, +}) diff --git a/app/(tabs)/sr_detail.tsx b/app/(tabs)/sr_detail.tsx new file mode 100644 index 00000000..c88ca0cc --- /dev/null +++ b/app/(tabs)/sr_detail.tsx @@ -0,0 +1,224 @@ +import { useEffect, useState } from 'react' +import { + View, Text, ScrollView, StyleSheet, TouchableOpacity, + ActivityIndicator, Alert, RefreshControl, +} from 'react-native' +import { useLocalSearchParams } from 'expo-router' +import { COLORS, PRIORITY_COLOR, STATUS_COLOR } from '../../constants/Config' +import { + getSRDetail, escalateSR, subscribeSR, getSRTimeline, +} from '../../services/api' +import SlaTimer from '../../components/SlaTimer' +import IncidentTimeline, { TimelineEvent } from '../../components/IncidentTimeline' +import RelatedSR from '../../components/RelatedSR' +import Comment from '../../components/Comment' +import SRSatisfaction from '../../components/SRSatisfaction' + +interface SRDetail { + id: number + sr_id?: string + title: string + description?: string + status?: string + priority?: string + sr_type?: string + requested_by?: string + assigned_to?: string + created_at?: string + sla_deadline?: string + subscribed?: boolean +} + +const DONE_STATUSES = ['COMPLETED', 'CLOSED', 'RESOLVED'] + +/** + * 기능 #4 에스컬레이션 · #8 구독 토글 · #3 SLA 타이머 + * + #6 타임라인 · #7 관련SR · #13 코멘트 · #14 만족도 + */ +export default function SRDetailScreen() { + const params = useLocalSearchParams<{ id?: string }>() + const id = Number(params.id ?? 0) + + const [sr, setSr] = useState(null) + const [timeline, setTimeline] = useState([]) + const [loading, setLoading] = useState(true) + const [refresh, setRefresh] = useState(false) + const [subscribed, setSubscribed] = useState(false) + const [busy, setBusy] = useState(false) + const [rateOpen, setRateOpen] = useState(false) + + const load = async (r = false) => { + if (!id) { setLoading(false); return } + r ? setRefresh(true) : setLoading(true) + try { + const res = await getSRDetail(id) + const data: SRDetail = res.data?.data ?? res.data + setSr(data) + setSubscribed(!!data.subscribed) + } catch { + setSr(null) + } + try { + const tl = await getSRTimeline(id) + setTimeline(tl.data?.content ?? tl.data?.items ?? tl.data ?? []) + } catch { setTimeline([]) } + setLoading(false); setRefresh(false) + } + + useEffect(() => { load() }, [id]) + + const doEscalate = () => { + Alert.alert('에스컬레이션', '이 SR을 상위 담당자에게 에스컬레이션할까요?', [ + { text: '취소', style: 'cancel' }, + { + text: '에스컬레이션', style: 'destructive', + onPress: async () => { + setBusy(true) + try { + await escalateSR(id, '모바일 1-tap 에스컬레이션') + await load() + Alert.alert('완료', '에스컬레이션 되었습니다.') + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? '에스컬레이션 실패') + } finally { setBusy(false) } + }, + }, + ]) + } + + const toggleSubscribe = async () => { + const next = !subscribed + setSubscribed(next) // optimistic + try { + await subscribeSR(id, next) + } catch (e: any) { + setSubscribed(!next) + Alert.alert('오류', e.response?.data?.detail ?? '구독 변경 실패') + } + } + + if (loading) return + if (!sr) return SR을 찾을 수 없습니다. + + const isDone = sr.status ? DONE_STATUSES.includes(sr.status) : false + + return ( + load(true)} />} + > + {/* 헤더 카드 */} + + + {sr.sr_id ?? `#${sr.id}`} + + + + {subscribed ? '🔔 구독중' : '🔕 구독'} + + + + + {sr.title} + + {!!sr.status && ( + + {sr.status} + + )} + {!!sr.priority && ( + + {sr.priority} + + )} + + + {/* SLA 타이머 (#3) */} + {!!sr.sla_deadline && ( + + SLA 잔여 + + + )} + + {!!sr.description && {sr.description}} + + 요청자 {sr.requested_by ?? '-'} + 담당자 {sr.assigned_to ?? '미배정'} + {sr.created_at?.slice(0, 10)} + + + + {/* 액션 버튼 */} + + + {busy ? : 🚨 에스컬레이션} + + {isDone && ( + setRateOpen(true)}> + ⭐ 만족도 평가 + + )} + + + {/* 타임라인 (#6) */} + + + + + {/* 관련 SR (#7) */} + + + + + {/* 코멘트 (#13) */} + + + + + + + setRateOpen(false)} + /> + + ) +} + +function Section({ label, children }: { label: string; children: React.ReactNode }) { + return ( + + {label} + {children} + + ) +} + +const s = StyleSheet.create({ + notFound: { textAlign: 'center', color: COLORS.muted, marginTop: 60 }, + card: { backgroundColor: '#fff', margin: 16, borderRadius: 12, padding: 16 }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + srId: { fontSize: 12, color: COLORS.accent, fontWeight: '700' }, + headBtns: { flexDirection: 'row', gap: 8 }, + subBtn: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: COLORS.border }, + subBtnOn: { backgroundColor: COLORS.accent, borderColor: COLORS.accent }, + subBtnText: { fontSize: 12, color: COLORS.text }, + title: { fontSize: 17, fontWeight: '800', color: COLORS.text, marginTop: 8 }, + badges: { flexDirection: 'row', gap: 8, marginTop: 10 }, + badge: { paddingHorizontal: 9, paddingVertical: 3, borderRadius: 10 }, + badgeText: { fontSize: 11, fontWeight: '700' }, + slaRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 14, paddingVertical: 10, paddingHorizontal: 12, backgroundColor: COLORS.bg, borderRadius: 10 }, + slaLabel: { fontSize: 12, fontWeight: '700', color: COLORS.muted }, + desc: { fontSize: 14, color: COLORS.text, lineHeight: 20, marginTop: 14 }, + meta: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginTop: 14 }, + metaText: { fontSize: 12, color: COLORS.muted }, + actionRow: { flexDirection: 'row', gap: 10, marginHorizontal: 16 }, + escalateBtn:{ flex: 1, backgroundColor: COLORS.danger, borderRadius: 12, padding: 14, alignItems: 'center' }, + escalateText:{ color: '#fff', fontSize: 15, fontWeight: '800' }, + rateBtn: { flex: 1, backgroundColor: COLORS.warning, borderRadius: 12, padding: 14, alignItems: 'center' }, + rateText: { color: '#fff', fontSize: 15, fontWeight: '800' }, + section: { backgroundColor: '#fff', marginHorizontal: 16, marginTop: 16, borderRadius: 12, padding: 16 }, + sectionTitle:{ fontSize: 14, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, +}) diff --git a/app/(tabs)/sr_heatmap.tsx b/app/(tabs)/sr_heatmap.tsx new file mode 100644 index 00000000..33625de2 --- /dev/null +++ b/app/(tabs)/sr_heatmap.tsx @@ -0,0 +1,74 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const HOURS = Array.from({ length: 24 }, (_, i) => i) +const DAYS = ['월', '화', '수', '목', '금', '토', '일'] + +export default function SRHeatmapScreen() { + const [matrix, setMatrix] = useState([]) + const [maxVal, setMaxVal] = useState(1) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const r = await client.get('/api/mobile2/hourly-pattern') + const m = r.data?.matrix ?? r.data ?? [] + setMatrix(m) + setMaxVal(Math.max(1, ...m.flat().filter(Number.isFinite))) + } catch { setMatrix([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const cellColor = (v: number) => { + const alpha = Math.round((v / maxVal) * 255).toString(16).padStart(2, '0') + return `${COLORS.accent}${alpha}` + } + + return ( + }> + SR 발생 히트맵 (요일×시간) + + + + + {HOURS.filter(h => h % 3 === 0).map(h => ( + {h}시 + ))} + + {matrix.map((row, di) => ( + + {row.map((v, hi) => ( + + {v > 0 && {v}} + + ))} + + ))} + + + + + {DAYS.slice(0, matrix.length).map((d, i) => {d})} + + 셀 색이 진할수록 SR 발생량이 많습니다. + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg, padding: 12 }, + title: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + hourRow: { flexDirection: 'row', marginBottom: 4 }, + hourLabel: { fontSize: 9, color: COLORS.muted, textAlign: 'center' }, + dayRow: { flexDirection: 'row', marginBottom: 2 }, + cell: { width: 34, height: 34, borderRadius: 4, marginRight: 2, alignItems: 'center', justifyContent: 'center' }, + cellVal: { fontSize: 9, color: '#fff', fontWeight: '700' }, + legend: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 12 }, + dayLabel: { fontSize: 12, color: COLORS.muted }, + note: { fontSize: 11, color: COLORS.muted, marginTop: 8 }, +}) diff --git a/app/(tabs)/sr_quick.tsx b/app/(tabs)/sr_quick.tsx new file mode 100644 index 00000000..e6f3b1a6 --- /dev/null +++ b/app/(tabs)/sr_quick.tsx @@ -0,0 +1,200 @@ +import { useEffect, useState } from 'react' +import { + View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, + ActivityIndicator, Alert, Image, ToastAndroid, Platform, +} from 'react-native' +import { router } from 'expo-router' +import * as ImagePicker from 'expo-image-picker' +import { COLORS, PRIORITY_COLOR } from '../../constants/Config' +import { createSRRaw } from '../../services/api' +import { useDuplicateSR } from '../../hooks/useDuplicateSR' +import { useAIClassify } from '../../hooks/useAIClassify' +import SRTemplates, { SRTemplate } from '../../components/SRTemplates' + +const CATEGORIES = ['DEPLOY', 'RESTART', 'LOG', 'INQUIRY', 'OTHER'] + +function toast(msg: string) { + if (Platform.OS === 'android') ToastAndroid.show(msg, ToastAndroid.LONG) + else Alert.alert(msg) +} + +/** + * 기능 #1 — 빠른 SR 등록 (3-tap: 제목 + 카테고리 + 사진) + * + 기능 #9 중복 감지, #10 템플릿, #11 AI 자동 분류 연동 + */ +export default function SRQuickScreen() { + const [title, setTitle] = useState('') + const [category, setCategory] = useState('OTHER') + const [priority, setPriority] = useState('MEDIUM') + const [photo, setPhoto] = useState(null) + const [saving, setSaving] = useState(false) + const [tplOpen, setTplOpen] = useState(false) + const [aiApplied, setAiApplied] = useState(false) + + const { duplicates, hasDuplicates } = useDuplicateSR(title) + const ai = useAIClassify(title) + + // AI 분류 결과 자동 채움 (사용자가 아직 손대지 않았을 때만) + useEffect(() => { + if (!aiApplied && (ai.category || ai.priority)) { + if (ai.category) setCategory(ai.category) + if (ai.priority) setPriority(ai.priority) + } + }, [ai.category, ai.priority, aiApplied]) + + const pickPhoto = async () => { + try { + const perm = await ImagePicker.requestMediaLibraryPermissionsAsync() + if (!perm.granted) { Alert.alert('권한 필요', '사진 접근 권한을 허용해주세요.'); return } + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + quality: 0.6, + }) + if (!result.canceled && result.assets && result.assets[0]) { + setPhoto(result.assets[0].uri) + } + } catch { + Alert.alert('오류', '사진을 불러올 수 없습니다.') + } + } + + const applyTemplate = (tpl: SRTemplate) => { + if (tpl.title) setTitle(tpl.title) + if (tpl.category || tpl.sr_type) setCategory((tpl.category ?? tpl.sr_type)!) + if (tpl.priority) setPriority(tpl.priority) + setAiApplied(true) + } + + const submit = async () => { + if (!title.trim()) { Alert.alert('제목을 입력하세요.'); return } + setSaving(true) + try { + const payload: Record = { + title: title.trim(), + sr_type: category, + priority, + description: '', + } + if (photo) payload.attachment_uri = photo + const res = await createSRRaw(payload) + const srId = res.data?.sr_id ?? res.data?.id ?? '' + toast(`SR ${srId} 등록 완료`) + router.back() + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? 'SR 등록 실패') + } finally { setSaving(false) } + } + + return ( + + + 빠른 SR 등록 + setTplOpen(true)}> + 📑 템플릿 + + + + {/* 1) 제목 */} + ① 제목 * + { setTitle(v); setAiApplied(false) }} + placeholder="무엇을 요청하시나요?" + placeholderTextColor={COLORS.muted} + /> + {ai.loading && 🤖 AI가 분류 중...} + {!ai.loading && (ai.category || ai.priority) && ( + 🤖 AI 추천: {ai.category} / {ai.priority} + )} + + {/* 중복 경고 (#9) */} + {hasDuplicates && ( + + ⚠️ 유사한 미처리 SR이 있습니다 + {duplicates.map(d => ( + router.push({ pathname: '/(tabs)/sr_detail', params: { id: String(d.id) } })} + > + • {d.sr_id ?? `#${d.id}`} {d.title} + + ))} + + )} + + {/* 2) 카테고리 */} + ② 카테고리 + + {CATEGORIES.map(c => ( + { setCategory(c); setAiApplied(true) }} + > + {c} + + ))} + + + 우선순위 + + {['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(p => ( + { setPriority(p); setAiApplied(true) }} + > + {p} + + ))} + + + {/* 3) 사진 */} + ③ 사진 첨부 + {photo ? ( + + + setPhoto(null)}> + ✕ 삭제 + + + ) : ( + + 📷 사진 선택 + + )} + + + {saving ? : SR 등록} + + + setTplOpen(false)} onSelect={applyTemplate} /> + + ) +} + +const s = StyleSheet.create({ + headerRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }, + heading: { fontSize: 18, fontWeight: '800', color: COLORS.text }, + tplBtn: { backgroundColor: COLORS.light, paddingHorizontal: 12, paddingVertical: 7, borderRadius: 8 }, + tplBtnText: { color: COLORS.accent, fontWeight: '700', fontSize: 12 }, + label: { fontSize: 12, fontWeight: '700', color: COLORS.muted, marginTop: 16, marginBottom: 7, textTransform: 'uppercase', letterSpacing: 0.4 }, + input: { borderWidth: 1.5, borderColor: COLORS.border, borderRadius: 10, padding: 13, fontSize: 15, color: COLORS.text, backgroundColor: '#fff' }, + aiHint: { fontSize: 12, color: COLORS.accent, marginTop: 6 }, + dupBox: { backgroundColor: '#FFF7ED', borderRadius: 10, padding: 12, marginTop: 10, borderWidth: 1, borderColor: COLORS.warning }, + dupTitle: { fontSize: 12, fontWeight: '700', color: COLORS.warning, marginBottom: 6 }, + dupItem: { fontSize: 12, color: COLORS.text, paddingVertical: 3 }, + chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, + chip: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 20, borderWidth: 1, borderColor: COLORS.border, backgroundColor: '#fff' }, + chipActive: { backgroundColor: COLORS.accent, borderColor: COLORS.accent }, + chipText: { fontSize: 13, color: COLORS.text }, + chipTextActive: { color: '#fff', fontWeight: '700' }, + photoBtn: { borderWidth: 1.5, borderColor: COLORS.border, borderStyle: 'dashed', borderRadius: 10, padding: 22, alignItems: 'center', backgroundColor: '#fff' }, + photoBtnText:{ color: COLORS.muted, fontSize: 14 }, + photoWrap: { position: 'relative' }, + photo: { width: '100%', height: 180, borderRadius: 10, backgroundColor: COLORS.border }, + removePhoto: { position: 'absolute', top: 8, right: 8, backgroundColor: 'rgba(0,0,0,0.6)', paddingHorizontal: 10, paddingVertical: 5, borderRadius: 8 }, + submit: { backgroundColor: COLORS.primary, borderRadius: 12, padding: 16, alignItems: 'center', marginTop: 28, marginBottom: 40 }, + submitText: { color: '#fff', fontSize: 16, fontWeight: '800' }, +}) diff --git a/app/(tabs)/ssl_alerts.tsx b/app/(tabs)/ssl_alerts.tsx new file mode 100644 index 00000000..5e2c6b38 --- /dev/null +++ b/app/(tabs)/ssl_alerts.tsx @@ -0,0 +1,60 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function SSLAlertsScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/cmdb/ssl-certs'); setItems(r.data?.certs ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const urgency = (days: number) => days <= 7 ? COLORS.danger : days <= 30 ? COLORS.warning : COLORS.success + + return ( + (a.days_left ?? 999) - (b.days_left ?? 999))} + keyExtractor={(_, i) => String(i)} + refreshControl={} + ListEmptyComponent={SSL 인증서 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const days = item.days_left ?? item.days_remaining ?? 999 + const color = urgency(days) + return ( + + + + {item.domain ?? item.name} + 만료: {item.expires_at?.slice(0, 10) ?? '-'} · 발급: {item.issuer ?? '-'} + + + {days} + 일 남음 + + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + domain: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 3 }, + daysBadge: { alignItems: 'center', borderRadius: 8, paddingVertical: 6, paddingHorizontal: 10 }, + daysNum: { fontSize: 20, fontWeight: '900', color: '#fff' }, + daysLabel: { fontSize: 9, color: '#fff', fontWeight: '600' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/team_leaderboard.tsx b/app/(tabs)/team_leaderboard.tsx new file mode 100644 index 00000000..188b1682 --- /dev/null +++ b/app/(tabs)/team_leaderboard.tsx @@ -0,0 +1,66 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const MEDAL = ['🥇', '🥈', '🥉'] + +export default function TeamLeaderboardScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/mobile2/team-leaderboard'); setItems(r.data?.leaderboard ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const maxScore = items[0]?.score ?? 1 + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={리더보드 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={이번 달 처리 성과} + renderItem={({ item, index }) => { + const score = item.score ?? item.closed_count ?? 0 + const barWidth = `${Math.round((score / maxScore) * 100)}%` + return ( + + {MEDAL[index] ?? `${index + 1}`} + + + {item.name ?? item.engineer_name} + {score}건 + + + + + SLA: {item.sla_rate ?? '-'}% · 평점: {item.rating ?? '-'} + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + header: { fontSize: 16, fontWeight: '800', color: COLORS.text, marginBottom: 12 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 6, elevation: 1 }, + rank: { fontSize: 22, width: 32, textAlign: 'center' }, + nameRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + score: { fontSize: 14, fontWeight: '700', color: COLORS.accent }, + bar: { height: 6, backgroundColor: COLORS.border, borderRadius: 3, overflow: 'hidden', marginBottom: 4 }, + fill: { height: '100%', backgroundColor: COLORS.accent, borderRadius: 3 }, + meta: { fontSize: 11, color: COLORS.muted }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/theme_settings.tsx b/app/(tabs)/theme_settings.tsx new file mode 100644 index 00000000..9848c411 --- /dev/null +++ b/app/(tabs)/theme_settings.tsx @@ -0,0 +1,106 @@ +import React, { useState, useEffect } from 'react' +import { View, Text, Switch, TouchableOpacity, StyleSheet, ScrollView, Alert } from 'react-native' +import * as SecureStore from 'expo-secure-store' +// eslint-disable-next-line @typescript-eslint/no-var-requires +const Haptics = (() => { try { return require('expo-haptics') } catch { return null } })() +import { COLORS } from '../../constants/Config' +import { useTheme } from '../../contexts/ThemeContext' +import { useFontScale } from '../../contexts/FontContext' + +export default function ThemeSettingsScreen() { + const { isDark, toggleTheme } = useTheme() + const { fontScale, setFontScale } = useFontScale() + const [vibration, setVibration] = useState('short') + const [colorBlind, setColorBlind] = useState('default') + const [screenLock, setScreenLock] = useState(false) + + useEffect(() => { + Promise.all([ + SecureStore.getItemAsync('grd_vibration'), + SecureStore.getItemAsync('grd_colorblind'), + SecureStore.getItemAsync('grd_screen_lock'), + ]).then(([v, c, l]) => { + if (v) setVibration(v) + if (c) setColorBlind(c) + if (l) setScreenLock(l === 'true') + }) + }, []) + + const saveVibration = async (v: string) => { + setVibration(v) + await SecureStore.setItemAsync('grd_vibration', v) + if (v !== 'none') await Haptics?.notificationAsync?.(Haptics?.NotificationFeedbackType?.Success) + } + + const saveColorBlind = async (c: string) => { + setColorBlind(c) + await SecureStore.setItemAsync('grd_colorblind', c) + Alert.alert('색맹 지원', `${c === 'default' ? '기본' : c === 'protanopia' ? '제1색맹' : '제2색맹'} 팔레트가 적용됐습니다.`) + } + + const toggleScreenLock = async (v: boolean) => { + setScreenLock(v) + await SecureStore.setItemAsync('grd_screen_lock', String(v)) + Alert.alert('화면 방향', v ? '세로 방향으로 고정됩니다.' : '자동 회전이 활성화됩니다.') + } + + return ( + + + {/* 다크모드 */} + 테마 + + 다크모드 + + + + {/* 글자 크기 */} + 글자 크기 + {([1.0, 1.2, 1.5] as const).map(scale => ( + setFontScale(scale)}> + + {scale === 1.0 ? '작게 (기본)' : scale === 1.2 ? '보통' : '크게'} + + {fontScale === scale && ✓} + + ))} + + {/* 진동 패턴 */} + 진동 패턴 + {[['none', '없음'], ['short', '짧게'], ['long', '길게']].map(([v, label]) => ( + saveVibration(v)}> + {label} + {vibration === v && ✓} + + ))} + + {/* 색맹 지원 */} + 색맹 지원 팔레트 + {[['default', '기본'], ['protanopia', '제1색맹 (적록)'], ['deuteranopia', '제2색맹 (녹적)']].map(([c, label]) => ( + saveColorBlind(c)}> + {label} + {colorBlind === c && ✓} + + ))} + + {/* 화면 방향 잠금 */} + 화면 방향 + + 세로 방향 잠금 + + + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + section: { fontSize: 13, fontWeight: '700', color: COLORS.muted, paddingHorizontal: 16, paddingTop: 20, paddingBottom: 6, textTransform: 'uppercase', letterSpacing: 1 }, + row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + label: { fontSize: 15, color: COLORS.text }, + option: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#fff', paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + optionActive: { backgroundColor: COLORS.light }, + optionText: { fontSize: 15, color: COLORS.text }, + optionTextActive:{ color: COLORS.accent, fontWeight: '600' }, + check: { fontSize: 16, color: COLORS.accent }, +}) diff --git a/app/(tabs)/threat_feed.tsx b/app/(tabs)/threat_feed.tsx new file mode 100644 index 00000000..0b703b02 --- /dev/null +++ b/app/(tabs)/threat_feed.tsx @@ -0,0 +1,70 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const SEV_COLOR: Record = { CRITICAL: COLORS.danger, HIGH: '#f97316', MEDIUM: COLORS.warning, LOW: COLORS.muted } + +export default function ThreatFeedScreen() { + const [items, setItems] = useState([]) + const [expanded, setExpanded] = useState(null) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/ai-soc/threats'); setItems(r.data?.threats ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={위협 인텔리전스 피드가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item, index }) => { + const sev = item.severity ?? item.level ?? 'MEDIUM' + const color = SEV_COLOR[sev] ?? COLORS.muted + const isOpen = expanded === index + return ( + setExpanded(isOpen ? null : index)} style={s.card} activeOpacity={0.8}> + + + + {item.title ?? item.threat_name} + {item.source ?? 'TI Feed'} · {item.detected_at?.slice(0, 10) ?? ''} + + {sev} + + {isOpen && ( + + {item.description ?? item.details ?? '상세 정보 없음'} + {item.ioc && IoC: {item.ioc}} + {item.mitigation && 대응: {item.mitigation}} + + )} + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + sevDot: { width: 10, height: 10, borderRadius: 5 }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text, flex: 1 }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + sev: { fontSize: 11, fontWeight: '700' }, + detail: { marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: COLORS.border }, + detailText: { fontSize: 13, color: COLORS.text, lineHeight: 20, marginBottom: 6 }, + ioc: { fontSize: 12, color: COLORS.danger, fontFamily: 'monospace' }, + mitigation: { fontSize: 12, color: COLORS.success, marginTop: 4 }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/todo_list.tsx b/app/(tabs)/todo_list.tsx new file mode 100644 index 00000000..2a2f77d2 --- /dev/null +++ b/app/(tabs)/todo_list.tsx @@ -0,0 +1,70 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +export default function TodoListScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { + const r = await client.get('/api/tasks', { params: { assigned: 'me', status: 'open', size: 50 } }) + setItems(r.data?.items ?? r.data ?? []) + } catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const close = (item: any) => { + Alert.alert('완료 처리', `SR #${item.id}를 완료 처리하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '완료', onPress: async () => { + try { + await client.patch(`/api/tasks/${item.id}`, { status: 'CLOSED' }) + setItems(prev => prev.filter(t => t.id !== item.id)) + } catch { Alert.alert('오류', '처리에 실패했습니다.') } + }}, + ]) + } + + const prio = (p: string) => ({ CRITICAL: COLORS.danger, HIGH: '#f97316', MEDIUM: COLORS.warning, LOW: COLORS.muted }[p ?? 'LOW'] ?? COLORS.muted) + + return ( + String(item.id)} + refreshControl={} + ListEmptyComponent={할 일이 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + ListHeaderComponent={총 {items.length}건} + renderItem={({ item }) => ( + + + + {item.title} + #{item.id} · {item.sr_type ?? item.type} · {item.due_date?.slice(0, 10) ?? '기한 없음'} + + close(item)}> + 완료 + + + + )} + /> + ) +} + +const s = StyleSheet.create({ + count: { fontSize: 12, color: COLORS.muted, marginBottom: 8 }, + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 6, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 3 }, + doneBtn: { backgroundColor: COLORS.success + '20', borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 }, + doneText:{ fontSize: 12, color: COLORS.success, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/vm_status.tsx b/app/(tabs)/vm_status.tsx new file mode 100644 index 00000000..b06e9c99 --- /dev/null +++ b/app/(tabs)/vm_status.tsx @@ -0,0 +1,75 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, FlatList, StyleSheet, RefreshControl, TouchableOpacity, Alert } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const STATE_COLOR: Record = { running: COLORS.success, stopped: COLORS.muted, error: COLORS.danger, suspended: COLORS.warning } + +export default function VMStatusScreen() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + setLoading(true) + try { const r = await client.get('/api/cloud/vms'); setItems(r.data?.vms ?? r.data?.items ?? []) } + catch { setItems([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load() }, [load])) + + const action = (vm: any, act: string) => { + Alert.alert('VM 제어', `${vm.name}을(를) ${act} 하시겠습니까?`, [ + { text: '취소', style: 'cancel' }, + { text: '실행', onPress: async () => { + try { await client.post(`/api/cloud/vms/${vm.id}/${act}`); load() } + catch { Alert.alert('오류', '작업에 실패했습니다.') } + }}, + ]) + } + + return ( + String(i)} + refreshControl={} + ListEmptyComponent={VM 데이터가 없습니다.} + style={{ backgroundColor: COLORS.bg }} + contentContainerStyle={{ padding: 12 }} + renderItem={({ item }) => { + const state = item.state ?? item.status ?? 'stopped' + const color = STATE_COLOR[state] ?? COLORS.muted + return ( + + + + + {item.name} + {item.vcpus ?? '-'}vCPU · {item.memory_gb ?? '-'}GB · {item.os ?? '-'} + + {state} + + + {state !== 'running' && action(item, 'start')}>시작} + {state === 'running' && action(item, 'stop')}>중지} + {state === 'running' && action(item, 'reboot')}>재부팅} + + + ) + }} + /> + ) +} + +const s = StyleSheet.create({ + card: { backgroundColor: '#fff', borderRadius: 10, padding: 14, marginBottom: 8, elevation: 1 }, + row: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 10 }, + dot: { width: 10, height: 10, borderRadius: 5 }, + name: { fontSize: 14, fontWeight: '700', color: COLORS.text }, + meta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + state: { fontSize: 11, fontWeight: '700' }, + btnRow: { flexDirection: 'row', gap: 8 }, + btn: { flex: 1, borderRadius: 6, paddingVertical: 8, alignItems: 'center' }, + btnText: { fontSize: 12, fontWeight: '700' }, + empty: { textAlign: 'center', color: COLORS.muted, marginTop: 40 }, +}) diff --git a/app/(tabs)/whiteboard.tsx b/app/(tabs)/whiteboard.tsx new file mode 100644 index 00000000..e4a747cc --- /dev/null +++ b/app/(tabs)/whiteboard.tsx @@ -0,0 +1,102 @@ +import React, { useState, useRef } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, PanResponder, Alert } from 'react-native'; +import { Canvas, Path, Skia } from '@shopify/react-native-skia'; + +export default function WhiteboardScreen() { + const [paths, setPaths] = useState>([]); + const [color, setColor] = useState('#00A0C8'); + const [strokeWidth, setStrokeWidth] = useState(3); + const currentPath = useRef(''); + const drawing = useRef(false); + + const COLORS = ['#00A0C8', '#ff4444', '#44bb44', '#ffbb00', '#bb44bb', '#fff']; + + const panResponder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onPanResponderGrant: (e) => { + const { locationX, locationY } = e.nativeEvent; + currentPath.current = `M${locationX} ${locationY}`; + drawing.current = true; + }, + onPanResponderMove: (e) => { + if (!drawing.current) return; + const { locationX, locationY } = e.nativeEvent; + currentPath.current += ` L${locationX} ${locationY}`; + setPaths(prev => { + const next = [...prev]; + if (next.length && next[next.length - 1].d === 'drawing') { + next[next.length - 1] = { d: currentPath.current, color, width: strokeWidth }; + } else { + next.push({ d: currentPath.current, color, width: strokeWidth }); + } + return next; + }); + }, + onPanResponderRelease: () => { drawing.current = false; }, + }); + + const clear = () => { setPaths([]); currentPath.current = ''; }; + const undo = () => setPaths(prev => prev.slice(0, -1)); + const share = () => Alert.alert('공유', 'SR 채팅방으로 화이트보드를 공유합니다'); + + return ( + + + 화이트보드 + + 실행취소 + 지우기 + 공유 + + + + + + {paths.map((p, i) => { + const skiaPath = Skia.Path.MakeFromSVGString(p.d); + if (!skiaPath) return null; + const paint = Skia.Paint(); + paint.setColor(Skia.Color(p.color)); + paint.setStrokeWidth(p.width); + paint.setStyle(1); + return ; + })} + + + + + + {COLORS.map(c => ( + setColor(c)} /> + ))} + + + {[2, 4, 8].map(w => ( + setStrokeWidth(w)}> + + + ))} + + + + ); +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: '#0A0E1A' }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 12, borderBottomWidth: 1, borderBottomColor: '#333' }, + title: { color: '#fff', fontSize: 18, fontWeight: '700' }, + headerActions: { flexDirection: 'row', gap: 8 }, + headerBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#1A1F2E', borderRadius: 8 }, + headerBtnText: { color: '#aaa', fontSize: 13 }, + shareBtn: { backgroundColor: '#003366' }, + shareBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, + canvas: { flex: 1 }, + toolbar: { padding: 12, borderTopWidth: 1, borderTopColor: '#333', backgroundColor: '#0A0E1A' }, + colorRow: { flexDirection: 'row', gap: 10, marginBottom: 10, justifyContent: 'center' }, + colorBtn: { width: 28, height: 28, borderRadius: 14 }, + widthRow: { flexDirection: 'row', gap: 16, justifyContent: 'center', alignItems: 'center' }, + widthBtn: { padding: 8, borderRadius: 8 }, + widthBtnActive: { backgroundColor: '#1A1F2E' }, + widthDot: { backgroundColor: '#fff' }, +}); diff --git a/app/(tabs)/work_calendar.tsx b/app/(tabs)/work_calendar.tsx new file mode 100644 index 00000000..e5bb2e16 --- /dev/null +++ b/app/(tabs)/work_calendar.tsx @@ -0,0 +1,104 @@ +import React, { useState, useCallback } from 'react' +import { View, Text, TouchableOpacity, FlatList, StyleSheet, RefreshControl } from 'react-native' +import { useFocusEffect } from 'expo-router' +import { COLORS } from '../../constants/Config' +import client from '../../services/api' + +const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'] + +export default function WorkCalendarScreen() { + const today = new Date() + const [year, setYear] = useState(today.getFullYear()) + const [month, setMonth] = useState(today.getMonth()) + const [events, setEvents] = useState([]) + const [selected, setSelected] = useState('') + const [loading, setLoading] = useState(false) + + const load = useCallback(async (y: number, m: number) => { + setLoading(true) + try { + const r = await client.get('/api/mobile2/work-calendar', { params: { year: y, month: m + 1 } }) + setEvents(r.data?.events ?? r.data?.items ?? []) + } catch { setEvents([]) } finally { setLoading(false) } + }, []) + + useFocusEffect(useCallback(() => { load(year, month) }, [year, month, load])) + + const firstDay = new Date(year, month, 1).getDay() + const daysInMonth = new Date(year, month + 1, 0).getDate() + const cells = Array.from({ length: firstDay }, () => null).concat(Array.from({ length: daysInMonth }, (_, i) => i + 1)) + + const eventsOn = (day: number) => { + const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}` + return events.filter(e => (e.date ?? e.scheduled_at ?? '').startsWith(key)) + } + + const selectedEvents = selected ? events.filter(e => (e.date ?? e.scheduled_at ?? '').startsWith(selected)) : [] + + const prev = () => { if (month === 0) { setYear(y => y - 1); setMonth(11) } else setMonth(m => m - 1) } + const next = () => { if (month === 11) { setYear(y => y + 1); setMonth(0) } else setMonth(m => m + 1) } + + return ( + + + ◀ + {year}년 {month + 1}월 + ▶ + + + + {WEEKDAYS.map(d => {d})} + + + + {cells.map((day, i) => { + if (!day) return + const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}` + const dayEvents = eventsOn(day) + const isToday = day === today.getDate() && month === today.getMonth() && year === today.getFullYear() + const isSel = key === selected + return ( + setSelected(isSel ? '' : key)}> + {day} + {dayEvents.length > 0 && } + + ) + })} + + + {selectedEvents.length > 0 && ( + String(i)} + style={s.eventList} + renderItem={({ item }) => ( + + {item.title ?? item.name} + {item.start_time ?? ''} · {item.category ?? item.type ?? ''} + + )} + /> + )} + + ) +} + +const s = StyleSheet.create({ + container: { flex: 1, backgroundColor: COLORS.bg }, + nav: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 12 }, + navBtn: { padding: 8 }, + navText: { fontSize: 16, color: COLORS.accent }, + monthLabel: { fontSize: 18, fontWeight: '800', color: COLORS.text }, + weekRow: { flexDirection: 'row', paddingHorizontal: 4 }, + weekDay: { flex: 1, textAlign: 'center', fontSize: 11, fontWeight: '700', color: COLORS.muted, paddingVertical: 4 }, + grid: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 4 }, + cell: { width: '14.28%', aspectRatio: 1, alignItems: 'center', justifyContent: 'center', padding: 2 }, + today: { backgroundColor: COLORS.accent, borderRadius: 20 }, + selCell: { backgroundColor: COLORS.accent + '30', borderRadius: 20 }, + dayNum: { fontSize: 13, color: COLORS.text }, + dot: { width: 5, height: 5, borderRadius: 3, backgroundColor: COLORS.danger, marginTop: 2 }, + eventList: { flex: 1, padding: 12 }, + eventCard: { backgroundColor: '#fff', borderRadius: 8, padding: 12, marginBottom: 6, elevation: 1 }, + eventTitle: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 2 }, + eventMeta: { fontSize: 11, color: COLORS.muted }, +}) diff --git a/app/_layout.tsx b/app/_layout.tsx index db97f059..ad957ea7 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,8 +1,14 @@ -import { useEffect } from 'react' +import { useEffect, useRef, useState } from 'react' +import { AppState, AppStateStatus, View } from 'react-native' import { Stack, useRouter, useSegments } from 'expo-router' import { StatusBar } from 'expo-status-bar' import * as SplashScreen from 'expo-splash-screen' import { AuthContext, useAuthState } from '../hooks/useAuth' +import { isSessionExpired, recordActivity, clearSession } from '../hooks/useSessionExpiry' +import PinLock, { isPinEnabled } from '../components/PinLock' +import { ThemeProvider } from '../contexts/ThemeContext' +import { FontProvider } from '../contexts/FontContext' +import { OfflineProvider } from '../contexts/OfflineContext' SplashScreen.preventAutoHideAsync() @@ -11,6 +17,10 @@ export default function RootLayout() { const router = useRouter() const segments = useSegments() + // #30 PIN 잠금 상태 + const [locked, setLocked] = useState(false) + const appState = useRef(AppState.currentState) + useEffect(() => { if (auth.loading) return SplashScreen.hideAsync() @@ -22,10 +32,68 @@ export default function RootLayout() { } }, [auth.loading, auth.token]) + /* #31 세션 자동 만료 + #30 PIN 잠금 — AppState background→foreground 처리 */ + useEffect(() => { + const onChange = async (next: AppStateStatus) => { + const prev = appState.current + appState.current = next + + if (next === 'active' && prev.match(/inactive|background/)) { + // 포그라운드 복귀 + if (auth.token) { + // #31 15분 초과 시 세션 종료 + if (await isSessionExpired()) { + await clearSession() + await auth.logout() + setLocked(false) + router.replace('/(auth)/login') + return + } + // #30 PIN이 활성화되어 있으면 잠금 화면 표시 + if (await isPinEnabled()) { + setLocked(true) + } + await recordActivity() + } + } else if (next.match(/inactive|background/)) { + // 백그라운드 진입 시 활동 시각 갱신 + if (auth.token) await recordActivity() + } + } + + const sub = AppState.addEventListener('change', onChange) + return () => sub.remove() + }, [auth.token]) + + const handleUnlock = async () => { + await recordActivity() + setLocked(false) + } + + const handlePinFail = async () => { + // 5회 실패 → 세션 종료 + await clearSession() + await auth.logout() + setLocked(false) + router.replace('/(auth)/login') + } + return ( - - - - + + + + + + + {/* #30 PIN 잠금 오버레이 — 인증된 상태에서만 */} + {locked && auth.token && ( + + + + )} + + + + ) } diff --git a/assets/icons/guardia/brand-1/original_16.png b/assets/icons/guardia/brand-1/original_16.png new file mode 100644 index 00000000..879b7d30 Binary files /dev/null and b/assets/icons/guardia/brand-1/original_16.png differ diff --git a/assets/icons/guardia/brand-1/original_24.png b/assets/icons/guardia/brand-1/original_24.png new file mode 100644 index 00000000..c81de00d Binary files /dev/null and b/assets/icons/guardia/brand-1/original_24.png differ diff --git a/assets/icons/guardia/brand-1/original_32.png b/assets/icons/guardia/brand-1/original_32.png new file mode 100644 index 00000000..8b6889c9 Binary files /dev/null and b/assets/icons/guardia/brand-1/original_32.png differ diff --git a/assets/icons/guardia/brand-1/original_48.png b/assets/icons/guardia/brand-1/original_48.png new file mode 100644 index 00000000..df24ff65 Binary files /dev/null and b/assets/icons/guardia/brand-1/original_48.png differ diff --git a/assets/icons/guardia/brand-1/original_64.png b/assets/icons/guardia/brand-1/original_64.png new file mode 100644 index 00000000..e0dbbce0 Binary files /dev/null and b/assets/icons/guardia/brand-1/original_64.png differ diff --git a/assets/icons/guardia/brand-2/original_16.png b/assets/icons/guardia/brand-2/original_16.png new file mode 100644 index 00000000..232b5e08 Binary files /dev/null and b/assets/icons/guardia/brand-2/original_16.png differ diff --git a/assets/icons/guardia/brand-2/original_24.png b/assets/icons/guardia/brand-2/original_24.png new file mode 100644 index 00000000..946a6cfb Binary files /dev/null and b/assets/icons/guardia/brand-2/original_24.png differ diff --git a/assets/icons/guardia/brand-2/original_32.png b/assets/icons/guardia/brand-2/original_32.png new file mode 100644 index 00000000..a1bbd0b8 Binary files /dev/null and b/assets/icons/guardia/brand-2/original_32.png differ diff --git a/assets/icons/guardia/brand-2/original_48.png b/assets/icons/guardia/brand-2/original_48.png new file mode 100644 index 00000000..c9347f31 Binary files /dev/null and b/assets/icons/guardia/brand-2/original_48.png differ diff --git a/assets/icons/guardia/brand-2/original_64.png b/assets/icons/guardia/brand-2/original_64.png new file mode 100644 index 00000000..25e10fb2 Binary files /dev/null and b/assets/icons/guardia/brand-2/original_64.png differ diff --git a/assets/icons/guardia/brand-3/original_16.png b/assets/icons/guardia/brand-3/original_16.png new file mode 100644 index 00000000..a6013f39 Binary files /dev/null and b/assets/icons/guardia/brand-3/original_16.png differ diff --git a/assets/icons/guardia/brand-3/original_24.png b/assets/icons/guardia/brand-3/original_24.png new file mode 100644 index 00000000..7e3f3b3a Binary files /dev/null and b/assets/icons/guardia/brand-3/original_24.png differ diff --git a/assets/icons/guardia/brand-3/original_32.png b/assets/icons/guardia/brand-3/original_32.png new file mode 100644 index 00000000..fe691a23 Binary files /dev/null and b/assets/icons/guardia/brand-3/original_32.png differ diff --git a/assets/icons/guardia/brand-3/original_48.png b/assets/icons/guardia/brand-3/original_48.png new file mode 100644 index 00000000..e38cf5f3 Binary files /dev/null and b/assets/icons/guardia/brand-3/original_48.png differ diff --git a/assets/icons/guardia/brand-3/original_64.png b/assets/icons/guardia/brand-3/original_64.png new file mode 100644 index 00000000..f5682885 Binary files /dev/null and b/assets/icons/guardia/brand-3/original_64.png differ diff --git a/assets/icons/guardia/brand-4/original_16.png b/assets/icons/guardia/brand-4/original_16.png new file mode 100644 index 00000000..61629c78 Binary files /dev/null and b/assets/icons/guardia/brand-4/original_16.png differ diff --git a/assets/icons/guardia/brand-4/original_24.png b/assets/icons/guardia/brand-4/original_24.png new file mode 100644 index 00000000..a23f4990 Binary files /dev/null and b/assets/icons/guardia/brand-4/original_24.png differ diff --git a/assets/icons/guardia/brand-4/original_32.png b/assets/icons/guardia/brand-4/original_32.png new file mode 100644 index 00000000..da599615 Binary files /dev/null and b/assets/icons/guardia/brand-4/original_32.png differ diff --git a/assets/icons/guardia/brand-4/original_48.png b/assets/icons/guardia/brand-4/original_48.png new file mode 100644 index 00000000..46c91fff Binary files /dev/null and b/assets/icons/guardia/brand-4/original_48.png differ diff --git a/assets/icons/guardia/brand-4/original_64.png b/assets/icons/guardia/brand-4/original_64.png new file mode 100644 index 00000000..91617b5e Binary files /dev/null and b/assets/icons/guardia/brand-4/original_64.png differ diff --git a/assets/icons/guardia/brand-5/original_16.png b/assets/icons/guardia/brand-5/original_16.png new file mode 100644 index 00000000..31841fb7 Binary files /dev/null and b/assets/icons/guardia/brand-5/original_16.png differ diff --git a/assets/icons/guardia/brand-5/original_24.png b/assets/icons/guardia/brand-5/original_24.png new file mode 100644 index 00000000..015b3fe7 Binary files /dev/null and b/assets/icons/guardia/brand-5/original_24.png differ diff --git a/assets/icons/guardia/brand-5/original_32.png b/assets/icons/guardia/brand-5/original_32.png new file mode 100644 index 00000000..dcf76e97 Binary files /dev/null and b/assets/icons/guardia/brand-5/original_32.png differ diff --git a/assets/icons/guardia/brand-5/original_48.png b/assets/icons/guardia/brand-5/original_48.png new file mode 100644 index 00000000..08fcc175 Binary files /dev/null and b/assets/icons/guardia/brand-5/original_48.png differ diff --git a/assets/icons/guardia/brand-5/original_64.png b/assets/icons/guardia/brand-5/original_64.png new file mode 100644 index 00000000..7e45eb4f Binary files /dev/null and b/assets/icons/guardia/brand-5/original_64.png differ diff --git a/assets/icons/guardia/brand-6/original_16.png b/assets/icons/guardia/brand-6/original_16.png new file mode 100644 index 00000000..8cf806e9 Binary files /dev/null and b/assets/icons/guardia/brand-6/original_16.png differ diff --git a/assets/icons/guardia/brand-6/original_24.png b/assets/icons/guardia/brand-6/original_24.png new file mode 100644 index 00000000..5f2c1aca Binary files /dev/null and b/assets/icons/guardia/brand-6/original_24.png differ diff --git a/assets/icons/guardia/brand-6/original_32.png b/assets/icons/guardia/brand-6/original_32.png new file mode 100644 index 00000000..c3217a04 Binary files /dev/null and b/assets/icons/guardia/brand-6/original_32.png differ diff --git a/assets/icons/guardia/brand-6/original_48.png b/assets/icons/guardia/brand-6/original_48.png new file mode 100644 index 00000000..c8eceed7 Binary files /dev/null and b/assets/icons/guardia/brand-6/original_48.png differ diff --git a/assets/icons/guardia/brand-6/original_64.png b/assets/icons/guardia/brand-6/original_64.png new file mode 100644 index 00000000..f721ef5e Binary files /dev/null and b/assets/icons/guardia/brand-6/original_64.png differ diff --git a/assets/icons/guardia/brand-7/original_16.png b/assets/icons/guardia/brand-7/original_16.png new file mode 100644 index 00000000..9f9188c6 Binary files /dev/null and b/assets/icons/guardia/brand-7/original_16.png differ diff --git a/assets/icons/guardia/brand-7/original_24.png b/assets/icons/guardia/brand-7/original_24.png new file mode 100644 index 00000000..2565f960 Binary files /dev/null and b/assets/icons/guardia/brand-7/original_24.png differ diff --git a/assets/icons/guardia/brand-7/original_32.png b/assets/icons/guardia/brand-7/original_32.png new file mode 100644 index 00000000..e1708c09 Binary files /dev/null and b/assets/icons/guardia/brand-7/original_32.png differ diff --git a/assets/icons/guardia/brand-7/original_48.png b/assets/icons/guardia/brand-7/original_48.png new file mode 100644 index 00000000..01bff3ab Binary files /dev/null and b/assets/icons/guardia/brand-7/original_48.png differ diff --git a/assets/icons/guardia/brand-7/original_64.png b/assets/icons/guardia/brand-7/original_64.png new file mode 100644 index 00000000..8c812300 Binary files /dev/null and b/assets/icons/guardia/brand-7/original_64.png differ diff --git a/assets/icons/guardia/brand-8/original_16.png b/assets/icons/guardia/brand-8/original_16.png new file mode 100644 index 00000000..a34656fd Binary files /dev/null and b/assets/icons/guardia/brand-8/original_16.png differ diff --git a/assets/icons/guardia/brand-8/original_24.png b/assets/icons/guardia/brand-8/original_24.png new file mode 100644 index 00000000..97a7f12f Binary files /dev/null and b/assets/icons/guardia/brand-8/original_24.png differ diff --git a/assets/icons/guardia/brand-8/original_32.png b/assets/icons/guardia/brand-8/original_32.png new file mode 100644 index 00000000..e946eadf Binary files /dev/null and b/assets/icons/guardia/brand-8/original_32.png differ diff --git a/assets/icons/guardia/brand-8/original_48.png b/assets/icons/guardia/brand-8/original_48.png new file mode 100644 index 00000000..bf1a3665 Binary files /dev/null and b/assets/icons/guardia/brand-8/original_48.png differ diff --git a/assets/icons/guardia/brand-8/original_64.png b/assets/icons/guardia/brand-8/original_64.png new file mode 100644 index 00000000..92d5ec1b Binary files /dev/null and b/assets/icons/guardia/brand-8/original_64.png differ diff --git a/assets/icons/guardia/home/navy_16.png b/assets/icons/guardia/home/navy_16.png new file mode 100644 index 00000000..ac30e892 Binary files /dev/null and b/assets/icons/guardia/home/navy_16.png differ diff --git a/assets/icons/guardia/home/navy_24.png b/assets/icons/guardia/home/navy_24.png new file mode 100644 index 00000000..508d062a Binary files /dev/null and b/assets/icons/guardia/home/navy_24.png differ diff --git a/assets/icons/guardia/home/navy_32.png b/assets/icons/guardia/home/navy_32.png new file mode 100644 index 00000000..0195eb3c Binary files /dev/null and b/assets/icons/guardia/home/navy_32.png differ diff --git a/assets/icons/guardia/home/navy_48.png b/assets/icons/guardia/home/navy_48.png new file mode 100644 index 00000000..8a33dea1 Binary files /dev/null and b/assets/icons/guardia/home/navy_48.png differ diff --git a/assets/icons/guardia/home/navy_64.png b/assets/icons/guardia/home/navy_64.png new file mode 100644 index 00000000..3224a314 Binary files /dev/null and b/assets/icons/guardia/home/navy_64.png differ diff --git a/assets/icons/guardia/home/white_16.png b/assets/icons/guardia/home/white_16.png new file mode 100644 index 00000000..2a7ca951 Binary files /dev/null and b/assets/icons/guardia/home/white_16.png differ diff --git a/assets/icons/guardia/home/white_24.png b/assets/icons/guardia/home/white_24.png new file mode 100644 index 00000000..ae44097b Binary files /dev/null and b/assets/icons/guardia/home/white_24.png differ diff --git a/assets/icons/guardia/home/white_32.png b/assets/icons/guardia/home/white_32.png new file mode 100644 index 00000000..4df36ac4 Binary files /dev/null and b/assets/icons/guardia/home/white_32.png differ diff --git a/assets/icons/guardia/home/white_48.png b/assets/icons/guardia/home/white_48.png new file mode 100644 index 00000000..c8cdab2e Binary files /dev/null and b/assets/icons/guardia/home/white_48.png differ diff --git a/assets/icons/guardia/home/white_64.png b/assets/icons/guardia/home/white_64.png new file mode 100644 index 00000000..7c891aa8 Binary files /dev/null and b/assets/icons/guardia/home/white_64.png differ diff --git a/assets/icons/guardia/icon-1/navy_16.png b/assets/icons/guardia/icon-1/navy_16.png new file mode 100644 index 00000000..a466981b Binary files /dev/null and b/assets/icons/guardia/icon-1/navy_16.png differ diff --git a/assets/icons/guardia/icon-1/navy_24.png b/assets/icons/guardia/icon-1/navy_24.png new file mode 100644 index 00000000..07686666 Binary files /dev/null and b/assets/icons/guardia/icon-1/navy_24.png differ diff --git a/assets/icons/guardia/icon-1/navy_32.png b/assets/icons/guardia/icon-1/navy_32.png new file mode 100644 index 00000000..d3209dcd Binary files /dev/null and b/assets/icons/guardia/icon-1/navy_32.png differ diff --git a/assets/icons/guardia/icon-1/navy_48.png b/assets/icons/guardia/icon-1/navy_48.png new file mode 100644 index 00000000..c76e9341 Binary files /dev/null and b/assets/icons/guardia/icon-1/navy_48.png differ diff --git a/assets/icons/guardia/icon-1/navy_64.png b/assets/icons/guardia/icon-1/navy_64.png new file mode 100644 index 00000000..305d603e Binary files /dev/null and b/assets/icons/guardia/icon-1/navy_64.png differ diff --git a/assets/icons/guardia/icon-1/white_16.png b/assets/icons/guardia/icon-1/white_16.png new file mode 100644 index 00000000..1a95b378 Binary files /dev/null and b/assets/icons/guardia/icon-1/white_16.png differ diff --git a/assets/icons/guardia/icon-1/white_24.png b/assets/icons/guardia/icon-1/white_24.png new file mode 100644 index 00000000..447d6d32 Binary files /dev/null and b/assets/icons/guardia/icon-1/white_24.png differ diff --git a/assets/icons/guardia/icon-1/white_32.png b/assets/icons/guardia/icon-1/white_32.png new file mode 100644 index 00000000..e3e1d804 Binary files /dev/null and b/assets/icons/guardia/icon-1/white_32.png differ diff --git a/assets/icons/guardia/icon-1/white_48.png b/assets/icons/guardia/icon-1/white_48.png new file mode 100644 index 00000000..a4ab50f3 Binary files /dev/null and b/assets/icons/guardia/icon-1/white_48.png differ diff --git a/assets/icons/guardia/icon-1/white_64.png b/assets/icons/guardia/icon-1/white_64.png new file mode 100644 index 00000000..8c6b89a4 Binary files /dev/null and b/assets/icons/guardia/icon-1/white_64.png differ diff --git a/assets/icons/guardia/icon-10/navy_16.png b/assets/icons/guardia/icon-10/navy_16.png new file mode 100644 index 00000000..cb2d2620 Binary files /dev/null and b/assets/icons/guardia/icon-10/navy_16.png differ diff --git a/assets/icons/guardia/icon-10/navy_24.png b/assets/icons/guardia/icon-10/navy_24.png new file mode 100644 index 00000000..0b58dddd Binary files /dev/null and b/assets/icons/guardia/icon-10/navy_24.png differ diff --git a/assets/icons/guardia/icon-10/navy_32.png b/assets/icons/guardia/icon-10/navy_32.png new file mode 100644 index 00000000..f588d118 Binary files /dev/null and b/assets/icons/guardia/icon-10/navy_32.png differ diff --git a/assets/icons/guardia/icon-10/navy_48.png b/assets/icons/guardia/icon-10/navy_48.png new file mode 100644 index 00000000..0900c92f Binary files /dev/null and b/assets/icons/guardia/icon-10/navy_48.png differ diff --git a/assets/icons/guardia/icon-10/navy_64.png b/assets/icons/guardia/icon-10/navy_64.png new file mode 100644 index 00000000..49802f7b Binary files /dev/null and b/assets/icons/guardia/icon-10/navy_64.png differ diff --git a/assets/icons/guardia/icon-10/white_16.png b/assets/icons/guardia/icon-10/white_16.png new file mode 100644 index 00000000..0f1fa3ce Binary files /dev/null and b/assets/icons/guardia/icon-10/white_16.png differ diff --git a/assets/icons/guardia/icon-10/white_24.png b/assets/icons/guardia/icon-10/white_24.png new file mode 100644 index 00000000..e922d89b Binary files /dev/null and b/assets/icons/guardia/icon-10/white_24.png differ diff --git a/assets/icons/guardia/icon-10/white_32.png b/assets/icons/guardia/icon-10/white_32.png new file mode 100644 index 00000000..68465459 Binary files /dev/null and b/assets/icons/guardia/icon-10/white_32.png differ diff --git a/assets/icons/guardia/icon-10/white_48.png b/assets/icons/guardia/icon-10/white_48.png new file mode 100644 index 00000000..8385de49 Binary files /dev/null and b/assets/icons/guardia/icon-10/white_48.png differ diff --git a/assets/icons/guardia/icon-10/white_64.png b/assets/icons/guardia/icon-10/white_64.png new file mode 100644 index 00000000..73394cb3 Binary files /dev/null and b/assets/icons/guardia/icon-10/white_64.png differ diff --git a/assets/icons/guardia/icon-11/navy_16.png b/assets/icons/guardia/icon-11/navy_16.png new file mode 100644 index 00000000..84296f03 Binary files /dev/null and b/assets/icons/guardia/icon-11/navy_16.png differ diff --git a/assets/icons/guardia/icon-11/navy_24.png b/assets/icons/guardia/icon-11/navy_24.png new file mode 100644 index 00000000..63085e81 Binary files /dev/null and b/assets/icons/guardia/icon-11/navy_24.png differ diff --git a/assets/icons/guardia/icon-11/navy_32.png b/assets/icons/guardia/icon-11/navy_32.png new file mode 100644 index 00000000..244fe21d Binary files /dev/null and b/assets/icons/guardia/icon-11/navy_32.png differ diff --git a/assets/icons/guardia/icon-11/navy_48.png b/assets/icons/guardia/icon-11/navy_48.png new file mode 100644 index 00000000..d397dd56 Binary files /dev/null and b/assets/icons/guardia/icon-11/navy_48.png differ diff --git a/assets/icons/guardia/icon-11/navy_64.png b/assets/icons/guardia/icon-11/navy_64.png new file mode 100644 index 00000000..68a209cd Binary files /dev/null and b/assets/icons/guardia/icon-11/navy_64.png differ diff --git a/assets/icons/guardia/icon-11/white_16.png b/assets/icons/guardia/icon-11/white_16.png new file mode 100644 index 00000000..bf34a98b Binary files /dev/null and b/assets/icons/guardia/icon-11/white_16.png differ diff --git a/assets/icons/guardia/icon-11/white_24.png b/assets/icons/guardia/icon-11/white_24.png new file mode 100644 index 00000000..4cee6be1 Binary files /dev/null and b/assets/icons/guardia/icon-11/white_24.png differ diff --git a/assets/icons/guardia/icon-11/white_32.png b/assets/icons/guardia/icon-11/white_32.png new file mode 100644 index 00000000..abe14b5e Binary files /dev/null and b/assets/icons/guardia/icon-11/white_32.png differ diff --git a/assets/icons/guardia/icon-11/white_48.png b/assets/icons/guardia/icon-11/white_48.png new file mode 100644 index 00000000..a2e4a084 Binary files /dev/null and b/assets/icons/guardia/icon-11/white_48.png differ diff --git a/assets/icons/guardia/icon-11/white_64.png b/assets/icons/guardia/icon-11/white_64.png new file mode 100644 index 00000000..33713cd1 Binary files /dev/null and b/assets/icons/guardia/icon-11/white_64.png differ diff --git a/assets/icons/guardia/icon-12/navy_16.png b/assets/icons/guardia/icon-12/navy_16.png new file mode 100644 index 00000000..c9648e94 Binary files /dev/null and b/assets/icons/guardia/icon-12/navy_16.png differ diff --git a/assets/icons/guardia/icon-12/navy_24.png b/assets/icons/guardia/icon-12/navy_24.png new file mode 100644 index 00000000..9a835107 Binary files /dev/null and b/assets/icons/guardia/icon-12/navy_24.png differ diff --git a/assets/icons/guardia/icon-12/navy_32.png b/assets/icons/guardia/icon-12/navy_32.png new file mode 100644 index 00000000..f427bb1e Binary files /dev/null and b/assets/icons/guardia/icon-12/navy_32.png differ diff --git a/assets/icons/guardia/icon-12/navy_48.png b/assets/icons/guardia/icon-12/navy_48.png new file mode 100644 index 00000000..b551f008 Binary files /dev/null and b/assets/icons/guardia/icon-12/navy_48.png differ diff --git a/assets/icons/guardia/icon-12/navy_64.png b/assets/icons/guardia/icon-12/navy_64.png new file mode 100644 index 00000000..edb824ac Binary files /dev/null and b/assets/icons/guardia/icon-12/navy_64.png differ diff --git a/assets/icons/guardia/icon-12/white_16.png b/assets/icons/guardia/icon-12/white_16.png new file mode 100644 index 00000000..49c47c3e Binary files /dev/null and b/assets/icons/guardia/icon-12/white_16.png differ diff --git a/assets/icons/guardia/icon-12/white_24.png b/assets/icons/guardia/icon-12/white_24.png new file mode 100644 index 00000000..13aa8dd8 Binary files /dev/null and b/assets/icons/guardia/icon-12/white_24.png differ diff --git a/assets/icons/guardia/icon-12/white_32.png b/assets/icons/guardia/icon-12/white_32.png new file mode 100644 index 00000000..94d0406d Binary files /dev/null and b/assets/icons/guardia/icon-12/white_32.png differ diff --git a/assets/icons/guardia/icon-12/white_48.png b/assets/icons/guardia/icon-12/white_48.png new file mode 100644 index 00000000..e7bea245 Binary files /dev/null and b/assets/icons/guardia/icon-12/white_48.png differ diff --git a/assets/icons/guardia/icon-12/white_64.png b/assets/icons/guardia/icon-12/white_64.png new file mode 100644 index 00000000..0557b038 Binary files /dev/null and b/assets/icons/guardia/icon-12/white_64.png differ diff --git a/assets/icons/guardia/icon-13/navy_16.png b/assets/icons/guardia/icon-13/navy_16.png new file mode 100644 index 00000000..6ffab14e Binary files /dev/null and b/assets/icons/guardia/icon-13/navy_16.png differ diff --git a/assets/icons/guardia/icon-13/navy_24.png b/assets/icons/guardia/icon-13/navy_24.png new file mode 100644 index 00000000..49c7364e Binary files /dev/null and b/assets/icons/guardia/icon-13/navy_24.png differ diff --git a/assets/icons/guardia/icon-13/navy_32.png b/assets/icons/guardia/icon-13/navy_32.png new file mode 100644 index 00000000..9a60c1cb Binary files /dev/null and b/assets/icons/guardia/icon-13/navy_32.png differ diff --git a/assets/icons/guardia/icon-13/navy_48.png b/assets/icons/guardia/icon-13/navy_48.png new file mode 100644 index 00000000..06c30bb5 Binary files /dev/null and b/assets/icons/guardia/icon-13/navy_48.png differ diff --git a/assets/icons/guardia/icon-13/navy_64.png b/assets/icons/guardia/icon-13/navy_64.png new file mode 100644 index 00000000..5328a1c5 Binary files /dev/null and b/assets/icons/guardia/icon-13/navy_64.png differ diff --git a/assets/icons/guardia/icon-13/white_16.png b/assets/icons/guardia/icon-13/white_16.png new file mode 100644 index 00000000..17ebf477 Binary files /dev/null and b/assets/icons/guardia/icon-13/white_16.png differ diff --git a/assets/icons/guardia/icon-13/white_24.png b/assets/icons/guardia/icon-13/white_24.png new file mode 100644 index 00000000..332d1a79 Binary files /dev/null and b/assets/icons/guardia/icon-13/white_24.png differ diff --git a/assets/icons/guardia/icon-13/white_32.png b/assets/icons/guardia/icon-13/white_32.png new file mode 100644 index 00000000..d3e4eae9 Binary files /dev/null and b/assets/icons/guardia/icon-13/white_32.png differ diff --git a/assets/icons/guardia/icon-13/white_48.png b/assets/icons/guardia/icon-13/white_48.png new file mode 100644 index 00000000..b83115b1 Binary files /dev/null and b/assets/icons/guardia/icon-13/white_48.png differ diff --git a/assets/icons/guardia/icon-13/white_64.png b/assets/icons/guardia/icon-13/white_64.png new file mode 100644 index 00000000..0ee95d69 Binary files /dev/null and b/assets/icons/guardia/icon-13/white_64.png differ diff --git a/assets/icons/guardia/icon-14/navy_16.png b/assets/icons/guardia/icon-14/navy_16.png new file mode 100644 index 00000000..4df0b42d Binary files /dev/null and b/assets/icons/guardia/icon-14/navy_16.png differ diff --git a/assets/icons/guardia/icon-14/navy_24.png b/assets/icons/guardia/icon-14/navy_24.png new file mode 100644 index 00000000..0f2388a7 Binary files /dev/null and b/assets/icons/guardia/icon-14/navy_24.png differ diff --git a/assets/icons/guardia/icon-14/navy_32.png b/assets/icons/guardia/icon-14/navy_32.png new file mode 100644 index 00000000..eaeecc9b Binary files /dev/null and b/assets/icons/guardia/icon-14/navy_32.png differ diff --git a/assets/icons/guardia/icon-14/navy_48.png b/assets/icons/guardia/icon-14/navy_48.png new file mode 100644 index 00000000..63ee4806 Binary files /dev/null and b/assets/icons/guardia/icon-14/navy_48.png differ diff --git a/assets/icons/guardia/icon-14/navy_64.png b/assets/icons/guardia/icon-14/navy_64.png new file mode 100644 index 00000000..0a68c4e2 Binary files /dev/null and b/assets/icons/guardia/icon-14/navy_64.png differ diff --git a/assets/icons/guardia/icon-14/white_16.png b/assets/icons/guardia/icon-14/white_16.png new file mode 100644 index 00000000..96754a1e Binary files /dev/null and b/assets/icons/guardia/icon-14/white_16.png differ diff --git a/assets/icons/guardia/icon-14/white_24.png b/assets/icons/guardia/icon-14/white_24.png new file mode 100644 index 00000000..60145542 Binary files /dev/null and b/assets/icons/guardia/icon-14/white_24.png differ diff --git a/assets/icons/guardia/icon-14/white_32.png b/assets/icons/guardia/icon-14/white_32.png new file mode 100644 index 00000000..5865fc09 Binary files /dev/null and b/assets/icons/guardia/icon-14/white_32.png differ diff --git a/assets/icons/guardia/icon-14/white_48.png b/assets/icons/guardia/icon-14/white_48.png new file mode 100644 index 00000000..9dc3760d Binary files /dev/null and b/assets/icons/guardia/icon-14/white_48.png differ diff --git a/assets/icons/guardia/icon-14/white_64.png b/assets/icons/guardia/icon-14/white_64.png new file mode 100644 index 00000000..6dcb2c74 Binary files /dev/null and b/assets/icons/guardia/icon-14/white_64.png differ diff --git a/assets/icons/guardia/icon-15/navy_16.png b/assets/icons/guardia/icon-15/navy_16.png new file mode 100644 index 00000000..9bfce00e Binary files /dev/null and b/assets/icons/guardia/icon-15/navy_16.png differ diff --git a/assets/icons/guardia/icon-15/navy_24.png b/assets/icons/guardia/icon-15/navy_24.png new file mode 100644 index 00000000..5b74e262 Binary files /dev/null and b/assets/icons/guardia/icon-15/navy_24.png differ diff --git a/assets/icons/guardia/icon-15/navy_32.png b/assets/icons/guardia/icon-15/navy_32.png new file mode 100644 index 00000000..b054d565 Binary files /dev/null and b/assets/icons/guardia/icon-15/navy_32.png differ diff --git a/assets/icons/guardia/icon-15/navy_48.png b/assets/icons/guardia/icon-15/navy_48.png new file mode 100644 index 00000000..f6b760c3 Binary files /dev/null and b/assets/icons/guardia/icon-15/navy_48.png differ diff --git a/assets/icons/guardia/icon-15/navy_64.png b/assets/icons/guardia/icon-15/navy_64.png new file mode 100644 index 00000000..93f8d461 Binary files /dev/null and b/assets/icons/guardia/icon-15/navy_64.png differ diff --git a/assets/icons/guardia/icon-15/white_16.png b/assets/icons/guardia/icon-15/white_16.png new file mode 100644 index 00000000..41ab22b7 Binary files /dev/null and b/assets/icons/guardia/icon-15/white_16.png differ diff --git a/assets/icons/guardia/icon-15/white_24.png b/assets/icons/guardia/icon-15/white_24.png new file mode 100644 index 00000000..c12e21cc Binary files /dev/null and b/assets/icons/guardia/icon-15/white_24.png differ diff --git a/assets/icons/guardia/icon-15/white_32.png b/assets/icons/guardia/icon-15/white_32.png new file mode 100644 index 00000000..2ff59df3 Binary files /dev/null and b/assets/icons/guardia/icon-15/white_32.png differ diff --git a/assets/icons/guardia/icon-15/white_48.png b/assets/icons/guardia/icon-15/white_48.png new file mode 100644 index 00000000..c70ca0f5 Binary files /dev/null and b/assets/icons/guardia/icon-15/white_48.png differ diff --git a/assets/icons/guardia/icon-15/white_64.png b/assets/icons/guardia/icon-15/white_64.png new file mode 100644 index 00000000..5a2aa95b Binary files /dev/null and b/assets/icons/guardia/icon-15/white_64.png differ diff --git a/assets/icons/guardia/icon-16/navy_16.png b/assets/icons/guardia/icon-16/navy_16.png new file mode 100644 index 00000000..e8aa0293 Binary files /dev/null and b/assets/icons/guardia/icon-16/navy_16.png differ diff --git a/assets/icons/guardia/icon-16/navy_24.png b/assets/icons/guardia/icon-16/navy_24.png new file mode 100644 index 00000000..ee311991 Binary files /dev/null and b/assets/icons/guardia/icon-16/navy_24.png differ diff --git a/assets/icons/guardia/icon-16/navy_32.png b/assets/icons/guardia/icon-16/navy_32.png new file mode 100644 index 00000000..faa3c2f6 Binary files /dev/null and b/assets/icons/guardia/icon-16/navy_32.png differ diff --git a/assets/icons/guardia/icon-16/navy_48.png b/assets/icons/guardia/icon-16/navy_48.png new file mode 100644 index 00000000..149cca44 Binary files /dev/null and b/assets/icons/guardia/icon-16/navy_48.png differ diff --git a/assets/icons/guardia/icon-16/navy_64.png b/assets/icons/guardia/icon-16/navy_64.png new file mode 100644 index 00000000..4b17278b Binary files /dev/null and b/assets/icons/guardia/icon-16/navy_64.png differ diff --git a/assets/icons/guardia/icon-16/white_16.png b/assets/icons/guardia/icon-16/white_16.png new file mode 100644 index 00000000..eae1b382 Binary files /dev/null and b/assets/icons/guardia/icon-16/white_16.png differ diff --git a/assets/icons/guardia/icon-16/white_24.png b/assets/icons/guardia/icon-16/white_24.png new file mode 100644 index 00000000..a8d2520b Binary files /dev/null and b/assets/icons/guardia/icon-16/white_24.png differ diff --git a/assets/icons/guardia/icon-16/white_32.png b/assets/icons/guardia/icon-16/white_32.png new file mode 100644 index 00000000..c0488a69 Binary files /dev/null and b/assets/icons/guardia/icon-16/white_32.png differ diff --git a/assets/icons/guardia/icon-16/white_48.png b/assets/icons/guardia/icon-16/white_48.png new file mode 100644 index 00000000..fbdbb0d7 Binary files /dev/null and b/assets/icons/guardia/icon-16/white_48.png differ diff --git a/assets/icons/guardia/icon-16/white_64.png b/assets/icons/guardia/icon-16/white_64.png new file mode 100644 index 00000000..e03f9fb2 Binary files /dev/null and b/assets/icons/guardia/icon-16/white_64.png differ diff --git a/assets/icons/guardia/icon-17/navy_16.png b/assets/icons/guardia/icon-17/navy_16.png new file mode 100644 index 00000000..76a36f46 Binary files /dev/null and b/assets/icons/guardia/icon-17/navy_16.png differ diff --git a/assets/icons/guardia/icon-17/navy_24.png b/assets/icons/guardia/icon-17/navy_24.png new file mode 100644 index 00000000..fcb20a9d Binary files /dev/null and b/assets/icons/guardia/icon-17/navy_24.png differ diff --git a/assets/icons/guardia/icon-17/navy_32.png b/assets/icons/guardia/icon-17/navy_32.png new file mode 100644 index 00000000..157709bf Binary files /dev/null and b/assets/icons/guardia/icon-17/navy_32.png differ diff --git a/assets/icons/guardia/icon-17/navy_48.png b/assets/icons/guardia/icon-17/navy_48.png new file mode 100644 index 00000000..bac629b9 Binary files /dev/null and b/assets/icons/guardia/icon-17/navy_48.png differ diff --git a/assets/icons/guardia/icon-17/navy_64.png b/assets/icons/guardia/icon-17/navy_64.png new file mode 100644 index 00000000..27141f11 Binary files /dev/null and b/assets/icons/guardia/icon-17/navy_64.png differ diff --git a/assets/icons/guardia/icon-17/white_16.png b/assets/icons/guardia/icon-17/white_16.png new file mode 100644 index 00000000..d0991860 Binary files /dev/null and b/assets/icons/guardia/icon-17/white_16.png differ diff --git a/assets/icons/guardia/icon-17/white_24.png b/assets/icons/guardia/icon-17/white_24.png new file mode 100644 index 00000000..95bffe70 Binary files /dev/null and b/assets/icons/guardia/icon-17/white_24.png differ diff --git a/assets/icons/guardia/icon-17/white_32.png b/assets/icons/guardia/icon-17/white_32.png new file mode 100644 index 00000000..697bdae8 Binary files /dev/null and b/assets/icons/guardia/icon-17/white_32.png differ diff --git a/assets/icons/guardia/icon-17/white_48.png b/assets/icons/guardia/icon-17/white_48.png new file mode 100644 index 00000000..30be9841 Binary files /dev/null and b/assets/icons/guardia/icon-17/white_48.png differ diff --git a/assets/icons/guardia/icon-17/white_64.png b/assets/icons/guardia/icon-17/white_64.png new file mode 100644 index 00000000..faab9277 Binary files /dev/null and b/assets/icons/guardia/icon-17/white_64.png differ diff --git a/assets/icons/guardia/icon-18/navy_16.png b/assets/icons/guardia/icon-18/navy_16.png new file mode 100644 index 00000000..24ed96c2 Binary files /dev/null and b/assets/icons/guardia/icon-18/navy_16.png differ diff --git a/assets/icons/guardia/icon-18/navy_24.png b/assets/icons/guardia/icon-18/navy_24.png new file mode 100644 index 00000000..8b23af46 Binary files /dev/null and b/assets/icons/guardia/icon-18/navy_24.png differ diff --git a/assets/icons/guardia/icon-18/navy_32.png b/assets/icons/guardia/icon-18/navy_32.png new file mode 100644 index 00000000..64a873ea Binary files /dev/null and b/assets/icons/guardia/icon-18/navy_32.png differ diff --git a/assets/icons/guardia/icon-18/navy_48.png b/assets/icons/guardia/icon-18/navy_48.png new file mode 100644 index 00000000..6723d74f Binary files /dev/null and b/assets/icons/guardia/icon-18/navy_48.png differ diff --git a/assets/icons/guardia/icon-18/navy_64.png b/assets/icons/guardia/icon-18/navy_64.png new file mode 100644 index 00000000..fac47b3b Binary files /dev/null and b/assets/icons/guardia/icon-18/navy_64.png differ diff --git a/assets/icons/guardia/icon-18/white_16.png b/assets/icons/guardia/icon-18/white_16.png new file mode 100644 index 00000000..2df0f014 Binary files /dev/null and b/assets/icons/guardia/icon-18/white_16.png differ diff --git a/assets/icons/guardia/icon-18/white_24.png b/assets/icons/guardia/icon-18/white_24.png new file mode 100644 index 00000000..c67bb706 Binary files /dev/null and b/assets/icons/guardia/icon-18/white_24.png differ diff --git a/assets/icons/guardia/icon-18/white_32.png b/assets/icons/guardia/icon-18/white_32.png new file mode 100644 index 00000000..08b5045b Binary files /dev/null and b/assets/icons/guardia/icon-18/white_32.png differ diff --git a/assets/icons/guardia/icon-18/white_48.png b/assets/icons/guardia/icon-18/white_48.png new file mode 100644 index 00000000..a1c1d869 Binary files /dev/null and b/assets/icons/guardia/icon-18/white_48.png differ diff --git a/assets/icons/guardia/icon-18/white_64.png b/assets/icons/guardia/icon-18/white_64.png new file mode 100644 index 00000000..743612c1 Binary files /dev/null and b/assets/icons/guardia/icon-18/white_64.png differ diff --git a/assets/icons/guardia/icon-19/navy_16.png b/assets/icons/guardia/icon-19/navy_16.png new file mode 100644 index 00000000..817caf24 Binary files /dev/null and b/assets/icons/guardia/icon-19/navy_16.png differ diff --git a/assets/icons/guardia/icon-19/navy_24.png b/assets/icons/guardia/icon-19/navy_24.png new file mode 100644 index 00000000..26c285ba Binary files /dev/null and b/assets/icons/guardia/icon-19/navy_24.png differ diff --git a/assets/icons/guardia/icon-19/navy_32.png b/assets/icons/guardia/icon-19/navy_32.png new file mode 100644 index 00000000..c95ba42a Binary files /dev/null and b/assets/icons/guardia/icon-19/navy_32.png differ diff --git a/assets/icons/guardia/icon-19/navy_48.png b/assets/icons/guardia/icon-19/navy_48.png new file mode 100644 index 00000000..45e0eceb Binary files /dev/null and b/assets/icons/guardia/icon-19/navy_48.png differ diff --git a/assets/icons/guardia/icon-19/navy_64.png b/assets/icons/guardia/icon-19/navy_64.png new file mode 100644 index 00000000..4a8d2e8e Binary files /dev/null and b/assets/icons/guardia/icon-19/navy_64.png differ diff --git a/assets/icons/guardia/icon-19/white_16.png b/assets/icons/guardia/icon-19/white_16.png new file mode 100644 index 00000000..723e1950 Binary files /dev/null and b/assets/icons/guardia/icon-19/white_16.png differ diff --git a/assets/icons/guardia/icon-19/white_24.png b/assets/icons/guardia/icon-19/white_24.png new file mode 100644 index 00000000..0c71ee98 Binary files /dev/null and b/assets/icons/guardia/icon-19/white_24.png differ diff --git a/assets/icons/guardia/icon-19/white_32.png b/assets/icons/guardia/icon-19/white_32.png new file mode 100644 index 00000000..ce8e4cd6 Binary files /dev/null and b/assets/icons/guardia/icon-19/white_32.png differ diff --git a/assets/icons/guardia/icon-19/white_48.png b/assets/icons/guardia/icon-19/white_48.png new file mode 100644 index 00000000..ba2710cb Binary files /dev/null and b/assets/icons/guardia/icon-19/white_48.png differ diff --git a/assets/icons/guardia/icon-19/white_64.png b/assets/icons/guardia/icon-19/white_64.png new file mode 100644 index 00000000..d2321742 Binary files /dev/null and b/assets/icons/guardia/icon-19/white_64.png differ diff --git a/assets/icons/guardia/icon-1_1/navy_16.png b/assets/icons/guardia/icon-1_1/navy_16.png new file mode 100644 index 00000000..5ddf52e4 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_1/navy_24.png b/assets/icons/guardia/icon-1_1/navy_24.png new file mode 100644 index 00000000..1d14e9dd Binary files /dev/null and b/assets/icons/guardia/icon-1_1/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_1/navy_32.png b/assets/icons/guardia/icon-1_1/navy_32.png new file mode 100644 index 00000000..c0319374 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_1/navy_48.png b/assets/icons/guardia/icon-1_1/navy_48.png new file mode 100644 index 00000000..77fe4c57 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_1/navy_64.png b/assets/icons/guardia/icon-1_1/navy_64.png new file mode 100644 index 00000000..783c4bd5 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_1/white_16.png b/assets/icons/guardia/icon-1_1/white_16.png new file mode 100644 index 00000000..ed721aae Binary files /dev/null and b/assets/icons/guardia/icon-1_1/white_16.png differ diff --git a/assets/icons/guardia/icon-1_1/white_24.png b/assets/icons/guardia/icon-1_1/white_24.png new file mode 100644 index 00000000..2dffddb3 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/white_24.png differ diff --git a/assets/icons/guardia/icon-1_1/white_32.png b/assets/icons/guardia/icon-1_1/white_32.png new file mode 100644 index 00000000..c0b47112 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/white_32.png differ diff --git a/assets/icons/guardia/icon-1_1/white_48.png b/assets/icons/guardia/icon-1_1/white_48.png new file mode 100644 index 00000000..51f489a3 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/white_48.png differ diff --git a/assets/icons/guardia/icon-1_1/white_64.png b/assets/icons/guardia/icon-1_1/white_64.png new file mode 100644 index 00000000..2ef73f13 Binary files /dev/null and b/assets/icons/guardia/icon-1_1/white_64.png differ diff --git a/assets/icons/guardia/icon-1_10/navy_16.png b/assets/icons/guardia/icon-1_10/navy_16.png new file mode 100644 index 00000000..2f9fb2f8 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_10/navy_24.png b/assets/icons/guardia/icon-1_10/navy_24.png new file mode 100644 index 00000000..7b21923d Binary files /dev/null and b/assets/icons/guardia/icon-1_10/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_10/navy_32.png b/assets/icons/guardia/icon-1_10/navy_32.png new file mode 100644 index 00000000..0c076d80 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_10/navy_48.png b/assets/icons/guardia/icon-1_10/navy_48.png new file mode 100644 index 00000000..fd3f7620 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_10/navy_64.png b/assets/icons/guardia/icon-1_10/navy_64.png new file mode 100644 index 00000000..76baf2aa Binary files /dev/null and b/assets/icons/guardia/icon-1_10/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_10/white_16.png b/assets/icons/guardia/icon-1_10/white_16.png new file mode 100644 index 00000000..bdf89a35 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/white_16.png differ diff --git a/assets/icons/guardia/icon-1_10/white_24.png b/assets/icons/guardia/icon-1_10/white_24.png new file mode 100644 index 00000000..0c6483b1 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/white_24.png differ diff --git a/assets/icons/guardia/icon-1_10/white_32.png b/assets/icons/guardia/icon-1_10/white_32.png new file mode 100644 index 00000000..c72fdd08 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/white_32.png differ diff --git a/assets/icons/guardia/icon-1_10/white_48.png b/assets/icons/guardia/icon-1_10/white_48.png new file mode 100644 index 00000000..f310af05 Binary files /dev/null and b/assets/icons/guardia/icon-1_10/white_48.png differ diff --git a/assets/icons/guardia/icon-1_10/white_64.png b/assets/icons/guardia/icon-1_10/white_64.png new file mode 100644 index 00000000..9633f01d Binary files /dev/null and b/assets/icons/guardia/icon-1_10/white_64.png differ diff --git a/assets/icons/guardia/icon-1_11/navy_16.png b/assets/icons/guardia/icon-1_11/navy_16.png new file mode 100644 index 00000000..13be180a Binary files /dev/null and b/assets/icons/guardia/icon-1_11/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_11/navy_24.png b/assets/icons/guardia/icon-1_11/navy_24.png new file mode 100644 index 00000000..867dbc6e Binary files /dev/null and b/assets/icons/guardia/icon-1_11/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_11/navy_32.png b/assets/icons/guardia/icon-1_11/navy_32.png new file mode 100644 index 00000000..4f020bd4 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_11/navy_48.png b/assets/icons/guardia/icon-1_11/navy_48.png new file mode 100644 index 00000000..fe641e56 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_11/navy_64.png b/assets/icons/guardia/icon-1_11/navy_64.png new file mode 100644 index 00000000..a349dfc0 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_11/white_16.png b/assets/icons/guardia/icon-1_11/white_16.png new file mode 100644 index 00000000..4d436562 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/white_16.png differ diff --git a/assets/icons/guardia/icon-1_11/white_24.png b/assets/icons/guardia/icon-1_11/white_24.png new file mode 100644 index 00000000..a9b61cf7 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/white_24.png differ diff --git a/assets/icons/guardia/icon-1_11/white_32.png b/assets/icons/guardia/icon-1_11/white_32.png new file mode 100644 index 00000000..94b3c2dc Binary files /dev/null and b/assets/icons/guardia/icon-1_11/white_32.png differ diff --git a/assets/icons/guardia/icon-1_11/white_48.png b/assets/icons/guardia/icon-1_11/white_48.png new file mode 100644 index 00000000..cf966436 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/white_48.png differ diff --git a/assets/icons/guardia/icon-1_11/white_64.png b/assets/icons/guardia/icon-1_11/white_64.png new file mode 100644 index 00000000..36e26ef5 Binary files /dev/null and b/assets/icons/guardia/icon-1_11/white_64.png differ diff --git a/assets/icons/guardia/icon-1_12/navy_16.png b/assets/icons/guardia/icon-1_12/navy_16.png new file mode 100644 index 00000000..5d2bc653 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_12/navy_24.png b/assets/icons/guardia/icon-1_12/navy_24.png new file mode 100644 index 00000000..dd636a56 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_12/navy_32.png b/assets/icons/guardia/icon-1_12/navy_32.png new file mode 100644 index 00000000..31b27123 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_12/navy_48.png b/assets/icons/guardia/icon-1_12/navy_48.png new file mode 100644 index 00000000..a41d5f35 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_12/navy_64.png b/assets/icons/guardia/icon-1_12/navy_64.png new file mode 100644 index 00000000..2fcf5992 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_12/white_16.png b/assets/icons/guardia/icon-1_12/white_16.png new file mode 100644 index 00000000..28ab00cd Binary files /dev/null and b/assets/icons/guardia/icon-1_12/white_16.png differ diff --git a/assets/icons/guardia/icon-1_12/white_24.png b/assets/icons/guardia/icon-1_12/white_24.png new file mode 100644 index 00000000..4f8cd3d1 Binary files /dev/null and b/assets/icons/guardia/icon-1_12/white_24.png differ diff --git a/assets/icons/guardia/icon-1_12/white_32.png b/assets/icons/guardia/icon-1_12/white_32.png new file mode 100644 index 00000000..75328e8e Binary files /dev/null and b/assets/icons/guardia/icon-1_12/white_32.png differ diff --git a/assets/icons/guardia/icon-1_12/white_48.png b/assets/icons/guardia/icon-1_12/white_48.png new file mode 100644 index 00000000..26ea9c5b Binary files /dev/null and b/assets/icons/guardia/icon-1_12/white_48.png differ diff --git a/assets/icons/guardia/icon-1_12/white_64.png b/assets/icons/guardia/icon-1_12/white_64.png new file mode 100644 index 00000000..b60bf41a Binary files /dev/null and b/assets/icons/guardia/icon-1_12/white_64.png differ diff --git a/assets/icons/guardia/icon-1_13/navy_16.png b/assets/icons/guardia/icon-1_13/navy_16.png new file mode 100644 index 00000000..d1e91e14 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_13/navy_24.png b/assets/icons/guardia/icon-1_13/navy_24.png new file mode 100644 index 00000000..65f612fa Binary files /dev/null and b/assets/icons/guardia/icon-1_13/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_13/navy_32.png b/assets/icons/guardia/icon-1_13/navy_32.png new file mode 100644 index 00000000..f5ebef0d Binary files /dev/null and b/assets/icons/guardia/icon-1_13/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_13/navy_48.png b/assets/icons/guardia/icon-1_13/navy_48.png new file mode 100644 index 00000000..888c96e2 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_13/navy_64.png b/assets/icons/guardia/icon-1_13/navy_64.png new file mode 100644 index 00000000..a68eb9a5 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_13/white_16.png b/assets/icons/guardia/icon-1_13/white_16.png new file mode 100644 index 00000000..44d33320 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/white_16.png differ diff --git a/assets/icons/guardia/icon-1_13/white_24.png b/assets/icons/guardia/icon-1_13/white_24.png new file mode 100644 index 00000000..940390e2 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/white_24.png differ diff --git a/assets/icons/guardia/icon-1_13/white_32.png b/assets/icons/guardia/icon-1_13/white_32.png new file mode 100644 index 00000000..0a372ec3 Binary files /dev/null and b/assets/icons/guardia/icon-1_13/white_32.png differ diff --git a/assets/icons/guardia/icon-1_13/white_48.png b/assets/icons/guardia/icon-1_13/white_48.png new file mode 100644 index 00000000..b7d5a80b Binary files /dev/null and b/assets/icons/guardia/icon-1_13/white_48.png differ diff --git a/assets/icons/guardia/icon-1_13/white_64.png b/assets/icons/guardia/icon-1_13/white_64.png new file mode 100644 index 00000000..5cb9524b Binary files /dev/null and b/assets/icons/guardia/icon-1_13/white_64.png differ diff --git a/assets/icons/guardia/icon-1_14/navy_16.png b/assets/icons/guardia/icon-1_14/navy_16.png new file mode 100644 index 00000000..ac30e892 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_14/navy_24.png b/assets/icons/guardia/icon-1_14/navy_24.png new file mode 100644 index 00000000..508d062a Binary files /dev/null and b/assets/icons/guardia/icon-1_14/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_14/navy_32.png b/assets/icons/guardia/icon-1_14/navy_32.png new file mode 100644 index 00000000..0195eb3c Binary files /dev/null and b/assets/icons/guardia/icon-1_14/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_14/navy_48.png b/assets/icons/guardia/icon-1_14/navy_48.png new file mode 100644 index 00000000..8a33dea1 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_14/navy_64.png b/assets/icons/guardia/icon-1_14/navy_64.png new file mode 100644 index 00000000..3224a314 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_14/white_16.png b/assets/icons/guardia/icon-1_14/white_16.png new file mode 100644 index 00000000..2a7ca951 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/white_16.png differ diff --git a/assets/icons/guardia/icon-1_14/white_24.png b/assets/icons/guardia/icon-1_14/white_24.png new file mode 100644 index 00000000..ae44097b Binary files /dev/null and b/assets/icons/guardia/icon-1_14/white_24.png differ diff --git a/assets/icons/guardia/icon-1_14/white_32.png b/assets/icons/guardia/icon-1_14/white_32.png new file mode 100644 index 00000000..4df36ac4 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/white_32.png differ diff --git a/assets/icons/guardia/icon-1_14/white_48.png b/assets/icons/guardia/icon-1_14/white_48.png new file mode 100644 index 00000000..c8cdab2e Binary files /dev/null and b/assets/icons/guardia/icon-1_14/white_48.png differ diff --git a/assets/icons/guardia/icon-1_14/white_64.png b/assets/icons/guardia/icon-1_14/white_64.png new file mode 100644 index 00000000..7c891aa8 Binary files /dev/null and b/assets/icons/guardia/icon-1_14/white_64.png differ diff --git a/assets/icons/guardia/icon-1_15/navy_16.png b/assets/icons/guardia/icon-1_15/navy_16.png new file mode 100644 index 00000000..7a47691f Binary files /dev/null and b/assets/icons/guardia/icon-1_15/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_15/navy_24.png b/assets/icons/guardia/icon-1_15/navy_24.png new file mode 100644 index 00000000..117553e7 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_15/navy_32.png b/assets/icons/guardia/icon-1_15/navy_32.png new file mode 100644 index 00000000..82bc5621 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_15/navy_48.png b/assets/icons/guardia/icon-1_15/navy_48.png new file mode 100644 index 00000000..d44b67ed Binary files /dev/null and b/assets/icons/guardia/icon-1_15/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_15/navy_64.png b/assets/icons/guardia/icon-1_15/navy_64.png new file mode 100644 index 00000000..bc327e7b Binary files /dev/null and b/assets/icons/guardia/icon-1_15/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_15/white_16.png b/assets/icons/guardia/icon-1_15/white_16.png new file mode 100644 index 00000000..4ab5bf10 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/white_16.png differ diff --git a/assets/icons/guardia/icon-1_15/white_24.png b/assets/icons/guardia/icon-1_15/white_24.png new file mode 100644 index 00000000..568cab75 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/white_24.png differ diff --git a/assets/icons/guardia/icon-1_15/white_32.png b/assets/icons/guardia/icon-1_15/white_32.png new file mode 100644 index 00000000..a03fd058 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/white_32.png differ diff --git a/assets/icons/guardia/icon-1_15/white_48.png b/assets/icons/guardia/icon-1_15/white_48.png new file mode 100644 index 00000000..eab1ac49 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/white_48.png differ diff --git a/assets/icons/guardia/icon-1_15/white_64.png b/assets/icons/guardia/icon-1_15/white_64.png new file mode 100644 index 00000000..ab4b5a56 Binary files /dev/null and b/assets/icons/guardia/icon-1_15/white_64.png differ diff --git a/assets/icons/guardia/icon-1_2/navy_16.png b/assets/icons/guardia/icon-1_2/navy_16.png new file mode 100644 index 00000000..196d2fbc Binary files /dev/null and b/assets/icons/guardia/icon-1_2/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_2/navy_24.png b/assets/icons/guardia/icon-1_2/navy_24.png new file mode 100644 index 00000000..3542bde0 Binary files /dev/null and b/assets/icons/guardia/icon-1_2/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_2/navy_32.png b/assets/icons/guardia/icon-1_2/navy_32.png new file mode 100644 index 00000000..cc036d8c Binary files /dev/null and b/assets/icons/guardia/icon-1_2/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_2/navy_48.png b/assets/icons/guardia/icon-1_2/navy_48.png new file mode 100644 index 00000000..131020b2 Binary files /dev/null and b/assets/icons/guardia/icon-1_2/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_2/navy_64.png b/assets/icons/guardia/icon-1_2/navy_64.png new file mode 100644 index 00000000..022546dc Binary files /dev/null and b/assets/icons/guardia/icon-1_2/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_2/white_16.png b/assets/icons/guardia/icon-1_2/white_16.png new file mode 100644 index 00000000..b9d3ed79 Binary files /dev/null and b/assets/icons/guardia/icon-1_2/white_16.png differ diff --git a/assets/icons/guardia/icon-1_2/white_24.png b/assets/icons/guardia/icon-1_2/white_24.png new file mode 100644 index 00000000..17d4917c Binary files /dev/null and b/assets/icons/guardia/icon-1_2/white_24.png differ diff --git a/assets/icons/guardia/icon-1_2/white_32.png b/assets/icons/guardia/icon-1_2/white_32.png new file mode 100644 index 00000000..16212a58 Binary files /dev/null and b/assets/icons/guardia/icon-1_2/white_32.png differ diff --git a/assets/icons/guardia/icon-1_2/white_48.png b/assets/icons/guardia/icon-1_2/white_48.png new file mode 100644 index 00000000..0aede21b Binary files /dev/null and b/assets/icons/guardia/icon-1_2/white_48.png differ diff --git a/assets/icons/guardia/icon-1_2/white_64.png b/assets/icons/guardia/icon-1_2/white_64.png new file mode 100644 index 00000000..2509684e Binary files /dev/null and b/assets/icons/guardia/icon-1_2/white_64.png differ diff --git a/assets/icons/guardia/icon-1_3/navy_16.png b/assets/icons/guardia/icon-1_3/navy_16.png new file mode 100644 index 00000000..f34553c2 Binary files /dev/null and b/assets/icons/guardia/icon-1_3/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_3/navy_24.png b/assets/icons/guardia/icon-1_3/navy_24.png new file mode 100644 index 00000000..8c4993a5 Binary files /dev/null and b/assets/icons/guardia/icon-1_3/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_3/navy_32.png b/assets/icons/guardia/icon-1_3/navy_32.png new file mode 100644 index 00000000..98a41f5c Binary files /dev/null and b/assets/icons/guardia/icon-1_3/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_3/navy_48.png b/assets/icons/guardia/icon-1_3/navy_48.png new file mode 100644 index 00000000..8731f17f Binary files /dev/null and b/assets/icons/guardia/icon-1_3/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_3/navy_64.png b/assets/icons/guardia/icon-1_3/navy_64.png new file mode 100644 index 00000000..db9b8a0c Binary files /dev/null and b/assets/icons/guardia/icon-1_3/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_3/white_16.png b/assets/icons/guardia/icon-1_3/white_16.png new file mode 100644 index 00000000..fdce059b Binary files /dev/null and b/assets/icons/guardia/icon-1_3/white_16.png differ diff --git a/assets/icons/guardia/icon-1_3/white_24.png b/assets/icons/guardia/icon-1_3/white_24.png new file mode 100644 index 00000000..1c2ae3ec Binary files /dev/null and b/assets/icons/guardia/icon-1_3/white_24.png differ diff --git a/assets/icons/guardia/icon-1_3/white_32.png b/assets/icons/guardia/icon-1_3/white_32.png new file mode 100644 index 00000000..b3d0f8f2 Binary files /dev/null and b/assets/icons/guardia/icon-1_3/white_32.png differ diff --git a/assets/icons/guardia/icon-1_3/white_48.png b/assets/icons/guardia/icon-1_3/white_48.png new file mode 100644 index 00000000..5ceb5ece Binary files /dev/null and b/assets/icons/guardia/icon-1_3/white_48.png differ diff --git a/assets/icons/guardia/icon-1_3/white_64.png b/assets/icons/guardia/icon-1_3/white_64.png new file mode 100644 index 00000000..fd9d5e04 Binary files /dev/null and b/assets/icons/guardia/icon-1_3/white_64.png differ diff --git a/assets/icons/guardia/icon-1_4/navy_16.png b/assets/icons/guardia/icon-1_4/navy_16.png new file mode 100644 index 00000000..d1137130 Binary files /dev/null and b/assets/icons/guardia/icon-1_4/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_4/navy_24.png b/assets/icons/guardia/icon-1_4/navy_24.png new file mode 100644 index 00000000..3ab26302 Binary files /dev/null and b/assets/icons/guardia/icon-1_4/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_4/navy_32.png b/assets/icons/guardia/icon-1_4/navy_32.png new file mode 100644 index 00000000..c3295c18 Binary files /dev/null and b/assets/icons/guardia/icon-1_4/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_4/navy_48.png b/assets/icons/guardia/icon-1_4/navy_48.png new file mode 100644 index 00000000..e89b399f Binary files /dev/null and b/assets/icons/guardia/icon-1_4/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_4/navy_64.png b/assets/icons/guardia/icon-1_4/navy_64.png new file mode 100644 index 00000000..2bd1ad0f Binary files /dev/null and b/assets/icons/guardia/icon-1_4/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_4/white_16.png b/assets/icons/guardia/icon-1_4/white_16.png new file mode 100644 index 00000000..13d5a1ab Binary files /dev/null and b/assets/icons/guardia/icon-1_4/white_16.png differ diff --git a/assets/icons/guardia/icon-1_4/white_24.png b/assets/icons/guardia/icon-1_4/white_24.png new file mode 100644 index 00000000..7fee6b18 Binary files /dev/null and b/assets/icons/guardia/icon-1_4/white_24.png differ diff --git a/assets/icons/guardia/icon-1_4/white_32.png b/assets/icons/guardia/icon-1_4/white_32.png new file mode 100644 index 00000000..0e94f05b Binary files /dev/null and b/assets/icons/guardia/icon-1_4/white_32.png differ diff --git a/assets/icons/guardia/icon-1_4/white_48.png b/assets/icons/guardia/icon-1_4/white_48.png new file mode 100644 index 00000000..ee805d31 Binary files /dev/null and b/assets/icons/guardia/icon-1_4/white_48.png differ diff --git a/assets/icons/guardia/icon-1_4/white_64.png b/assets/icons/guardia/icon-1_4/white_64.png new file mode 100644 index 00000000..c2bd75ae Binary files /dev/null and b/assets/icons/guardia/icon-1_4/white_64.png differ diff --git a/assets/icons/guardia/icon-1_5/navy_16.png b/assets/icons/guardia/icon-1_5/navy_16.png new file mode 100644 index 00000000..d7879dce Binary files /dev/null and b/assets/icons/guardia/icon-1_5/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_5/navy_24.png b/assets/icons/guardia/icon-1_5/navy_24.png new file mode 100644 index 00000000..1a2c7b62 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_5/navy_32.png b/assets/icons/guardia/icon-1_5/navy_32.png new file mode 100644 index 00000000..15591412 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_5/navy_48.png b/assets/icons/guardia/icon-1_5/navy_48.png new file mode 100644 index 00000000..d7f63986 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_5/navy_64.png b/assets/icons/guardia/icon-1_5/navy_64.png new file mode 100644 index 00000000..d401fbe9 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_5/white_16.png b/assets/icons/guardia/icon-1_5/white_16.png new file mode 100644 index 00000000..d9bfa897 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/white_16.png differ diff --git a/assets/icons/guardia/icon-1_5/white_24.png b/assets/icons/guardia/icon-1_5/white_24.png new file mode 100644 index 00000000..71a85d3d Binary files /dev/null and b/assets/icons/guardia/icon-1_5/white_24.png differ diff --git a/assets/icons/guardia/icon-1_5/white_32.png b/assets/icons/guardia/icon-1_5/white_32.png new file mode 100644 index 00000000..52766867 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/white_32.png differ diff --git a/assets/icons/guardia/icon-1_5/white_48.png b/assets/icons/guardia/icon-1_5/white_48.png new file mode 100644 index 00000000..8c776a55 Binary files /dev/null and b/assets/icons/guardia/icon-1_5/white_48.png differ diff --git a/assets/icons/guardia/icon-1_5/white_64.png b/assets/icons/guardia/icon-1_5/white_64.png new file mode 100644 index 00000000..b5dbd73d Binary files /dev/null and b/assets/icons/guardia/icon-1_5/white_64.png differ diff --git a/assets/icons/guardia/icon-1_6/navy_16.png b/assets/icons/guardia/icon-1_6/navy_16.png new file mode 100644 index 00000000..bbfb2a4b Binary files /dev/null and b/assets/icons/guardia/icon-1_6/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_6/navy_24.png b/assets/icons/guardia/icon-1_6/navy_24.png new file mode 100644 index 00000000..1fb2aa9d Binary files /dev/null and b/assets/icons/guardia/icon-1_6/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_6/navy_32.png b/assets/icons/guardia/icon-1_6/navy_32.png new file mode 100644 index 00000000..bc7bb031 Binary files /dev/null and b/assets/icons/guardia/icon-1_6/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_6/navy_48.png b/assets/icons/guardia/icon-1_6/navy_48.png new file mode 100644 index 00000000..97442d6f Binary files /dev/null and b/assets/icons/guardia/icon-1_6/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_6/navy_64.png b/assets/icons/guardia/icon-1_6/navy_64.png new file mode 100644 index 00000000..ac39e149 Binary files /dev/null and b/assets/icons/guardia/icon-1_6/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_6/white_16.png b/assets/icons/guardia/icon-1_6/white_16.png new file mode 100644 index 00000000..5c0e0fe9 Binary files /dev/null and b/assets/icons/guardia/icon-1_6/white_16.png differ diff --git a/assets/icons/guardia/icon-1_6/white_24.png b/assets/icons/guardia/icon-1_6/white_24.png new file mode 100644 index 00000000..e28099db Binary files /dev/null and b/assets/icons/guardia/icon-1_6/white_24.png differ diff --git a/assets/icons/guardia/icon-1_6/white_32.png b/assets/icons/guardia/icon-1_6/white_32.png new file mode 100644 index 00000000..423f1314 Binary files /dev/null and b/assets/icons/guardia/icon-1_6/white_32.png differ diff --git a/assets/icons/guardia/icon-1_6/white_48.png b/assets/icons/guardia/icon-1_6/white_48.png new file mode 100644 index 00000000..66c7c16f Binary files /dev/null and b/assets/icons/guardia/icon-1_6/white_48.png differ diff --git a/assets/icons/guardia/icon-1_6/white_64.png b/assets/icons/guardia/icon-1_6/white_64.png new file mode 100644 index 00000000..b7a0118c Binary files /dev/null and b/assets/icons/guardia/icon-1_6/white_64.png differ diff --git a/assets/icons/guardia/icon-1_7/navy_16.png b/assets/icons/guardia/icon-1_7/navy_16.png new file mode 100644 index 00000000..3af183cc Binary files /dev/null and b/assets/icons/guardia/icon-1_7/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_7/navy_24.png b/assets/icons/guardia/icon-1_7/navy_24.png new file mode 100644 index 00000000..e0dd13c1 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_7/navy_32.png b/assets/icons/guardia/icon-1_7/navy_32.png new file mode 100644 index 00000000..72adeaf3 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_7/navy_48.png b/assets/icons/guardia/icon-1_7/navy_48.png new file mode 100644 index 00000000..3ba78a14 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_7/navy_64.png b/assets/icons/guardia/icon-1_7/navy_64.png new file mode 100644 index 00000000..0ceb70ff Binary files /dev/null and b/assets/icons/guardia/icon-1_7/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_7/white_16.png b/assets/icons/guardia/icon-1_7/white_16.png new file mode 100644 index 00000000..fe8cb4a8 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/white_16.png differ diff --git a/assets/icons/guardia/icon-1_7/white_24.png b/assets/icons/guardia/icon-1_7/white_24.png new file mode 100644 index 00000000..8fb1b7d6 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/white_24.png differ diff --git a/assets/icons/guardia/icon-1_7/white_32.png b/assets/icons/guardia/icon-1_7/white_32.png new file mode 100644 index 00000000..ca63bdf6 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/white_32.png differ diff --git a/assets/icons/guardia/icon-1_7/white_48.png b/assets/icons/guardia/icon-1_7/white_48.png new file mode 100644 index 00000000..aaaa5487 Binary files /dev/null and b/assets/icons/guardia/icon-1_7/white_48.png differ diff --git a/assets/icons/guardia/icon-1_7/white_64.png b/assets/icons/guardia/icon-1_7/white_64.png new file mode 100644 index 00000000..e8e2efdc Binary files /dev/null and b/assets/icons/guardia/icon-1_7/white_64.png differ diff --git a/assets/icons/guardia/icon-1_8/navy_16.png b/assets/icons/guardia/icon-1_8/navy_16.png new file mode 100644 index 00000000..89d33c37 Binary files /dev/null and b/assets/icons/guardia/icon-1_8/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_8/navy_24.png b/assets/icons/guardia/icon-1_8/navy_24.png new file mode 100644 index 00000000..90a5478a Binary files /dev/null and b/assets/icons/guardia/icon-1_8/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_8/navy_32.png b/assets/icons/guardia/icon-1_8/navy_32.png new file mode 100644 index 00000000..c51ad603 Binary files /dev/null and b/assets/icons/guardia/icon-1_8/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_8/navy_48.png b/assets/icons/guardia/icon-1_8/navy_48.png new file mode 100644 index 00000000..d804deeb Binary files /dev/null and b/assets/icons/guardia/icon-1_8/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_8/navy_64.png b/assets/icons/guardia/icon-1_8/navy_64.png new file mode 100644 index 00000000..9ed038fb Binary files /dev/null and b/assets/icons/guardia/icon-1_8/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_8/white_16.png b/assets/icons/guardia/icon-1_8/white_16.png new file mode 100644 index 00000000..6a6a19ec Binary files /dev/null and b/assets/icons/guardia/icon-1_8/white_16.png differ diff --git a/assets/icons/guardia/icon-1_8/white_24.png b/assets/icons/guardia/icon-1_8/white_24.png new file mode 100644 index 00000000..94ebf9e5 Binary files /dev/null and b/assets/icons/guardia/icon-1_8/white_24.png differ diff --git a/assets/icons/guardia/icon-1_8/white_32.png b/assets/icons/guardia/icon-1_8/white_32.png new file mode 100644 index 00000000..0bfcd02d Binary files /dev/null and b/assets/icons/guardia/icon-1_8/white_32.png differ diff --git a/assets/icons/guardia/icon-1_8/white_48.png b/assets/icons/guardia/icon-1_8/white_48.png new file mode 100644 index 00000000..8d6415bd Binary files /dev/null and b/assets/icons/guardia/icon-1_8/white_48.png differ diff --git a/assets/icons/guardia/icon-1_8/white_64.png b/assets/icons/guardia/icon-1_8/white_64.png new file mode 100644 index 00000000..9c5b549a Binary files /dev/null and b/assets/icons/guardia/icon-1_8/white_64.png differ diff --git a/assets/icons/guardia/icon-1_9/navy_16.png b/assets/icons/guardia/icon-1_9/navy_16.png new file mode 100644 index 00000000..d2acdfae Binary files /dev/null and b/assets/icons/guardia/icon-1_9/navy_16.png differ diff --git a/assets/icons/guardia/icon-1_9/navy_24.png b/assets/icons/guardia/icon-1_9/navy_24.png new file mode 100644 index 00000000..66ab710e Binary files /dev/null and b/assets/icons/guardia/icon-1_9/navy_24.png differ diff --git a/assets/icons/guardia/icon-1_9/navy_32.png b/assets/icons/guardia/icon-1_9/navy_32.png new file mode 100644 index 00000000..7136c0bc Binary files /dev/null and b/assets/icons/guardia/icon-1_9/navy_32.png differ diff --git a/assets/icons/guardia/icon-1_9/navy_48.png b/assets/icons/guardia/icon-1_9/navy_48.png new file mode 100644 index 00000000..cfdbbcca Binary files /dev/null and b/assets/icons/guardia/icon-1_9/navy_48.png differ diff --git a/assets/icons/guardia/icon-1_9/navy_64.png b/assets/icons/guardia/icon-1_9/navy_64.png new file mode 100644 index 00000000..5130a980 Binary files /dev/null and b/assets/icons/guardia/icon-1_9/navy_64.png differ diff --git a/assets/icons/guardia/icon-1_9/white_16.png b/assets/icons/guardia/icon-1_9/white_16.png new file mode 100644 index 00000000..8dd86dc1 Binary files /dev/null and b/assets/icons/guardia/icon-1_9/white_16.png differ diff --git a/assets/icons/guardia/icon-1_9/white_24.png b/assets/icons/guardia/icon-1_9/white_24.png new file mode 100644 index 00000000..81eeebe3 Binary files /dev/null and b/assets/icons/guardia/icon-1_9/white_24.png differ diff --git a/assets/icons/guardia/icon-1_9/white_32.png b/assets/icons/guardia/icon-1_9/white_32.png new file mode 100644 index 00000000..a5a36443 Binary files /dev/null and b/assets/icons/guardia/icon-1_9/white_32.png differ diff --git a/assets/icons/guardia/icon-1_9/white_48.png b/assets/icons/guardia/icon-1_9/white_48.png new file mode 100644 index 00000000..e105adee Binary files /dev/null and b/assets/icons/guardia/icon-1_9/white_48.png differ diff --git a/assets/icons/guardia/icon-1_9/white_64.png b/assets/icons/guardia/icon-1_9/white_64.png new file mode 100644 index 00000000..79103693 Binary files /dev/null and b/assets/icons/guardia/icon-1_9/white_64.png differ diff --git a/assets/icons/guardia/icon-2/navy_16.png b/assets/icons/guardia/icon-2/navy_16.png new file mode 100644 index 00000000..62a1f552 Binary files /dev/null and b/assets/icons/guardia/icon-2/navy_16.png differ diff --git a/assets/icons/guardia/icon-2/navy_24.png b/assets/icons/guardia/icon-2/navy_24.png new file mode 100644 index 00000000..a6ad7fce Binary files /dev/null and b/assets/icons/guardia/icon-2/navy_24.png differ diff --git a/assets/icons/guardia/icon-2/navy_32.png b/assets/icons/guardia/icon-2/navy_32.png new file mode 100644 index 00000000..c3a22502 Binary files /dev/null and b/assets/icons/guardia/icon-2/navy_32.png differ diff --git a/assets/icons/guardia/icon-2/navy_48.png b/assets/icons/guardia/icon-2/navy_48.png new file mode 100644 index 00000000..369a5f93 Binary files /dev/null and b/assets/icons/guardia/icon-2/navy_48.png differ diff --git a/assets/icons/guardia/icon-2/navy_64.png b/assets/icons/guardia/icon-2/navy_64.png new file mode 100644 index 00000000..98e4cd6f Binary files /dev/null and b/assets/icons/guardia/icon-2/navy_64.png differ diff --git a/assets/icons/guardia/icon-2/white_16.png b/assets/icons/guardia/icon-2/white_16.png new file mode 100644 index 00000000..aaaba270 Binary files /dev/null and b/assets/icons/guardia/icon-2/white_16.png differ diff --git a/assets/icons/guardia/icon-2/white_24.png b/assets/icons/guardia/icon-2/white_24.png new file mode 100644 index 00000000..7111e08a Binary files /dev/null and b/assets/icons/guardia/icon-2/white_24.png differ diff --git a/assets/icons/guardia/icon-2/white_32.png b/assets/icons/guardia/icon-2/white_32.png new file mode 100644 index 00000000..09db3b09 Binary files /dev/null and b/assets/icons/guardia/icon-2/white_32.png differ diff --git a/assets/icons/guardia/icon-2/white_48.png b/assets/icons/guardia/icon-2/white_48.png new file mode 100644 index 00000000..d46a30de Binary files /dev/null and b/assets/icons/guardia/icon-2/white_48.png differ diff --git a/assets/icons/guardia/icon-2/white_64.png b/assets/icons/guardia/icon-2/white_64.png new file mode 100644 index 00000000..ad191c43 Binary files /dev/null and b/assets/icons/guardia/icon-2/white_64.png differ diff --git a/assets/icons/guardia/icon-20/navy_16.png b/assets/icons/guardia/icon-20/navy_16.png new file mode 100644 index 00000000..08fa46fb Binary files /dev/null and b/assets/icons/guardia/icon-20/navy_16.png differ diff --git a/assets/icons/guardia/icon-20/navy_24.png b/assets/icons/guardia/icon-20/navy_24.png new file mode 100644 index 00000000..cf2f0d53 Binary files /dev/null and b/assets/icons/guardia/icon-20/navy_24.png differ diff --git a/assets/icons/guardia/icon-20/navy_32.png b/assets/icons/guardia/icon-20/navy_32.png new file mode 100644 index 00000000..754aede6 Binary files /dev/null and b/assets/icons/guardia/icon-20/navy_32.png differ diff --git a/assets/icons/guardia/icon-20/navy_48.png b/assets/icons/guardia/icon-20/navy_48.png new file mode 100644 index 00000000..238f7db4 Binary files /dev/null and b/assets/icons/guardia/icon-20/navy_48.png differ diff --git a/assets/icons/guardia/icon-20/navy_64.png b/assets/icons/guardia/icon-20/navy_64.png new file mode 100644 index 00000000..ca87e9b6 Binary files /dev/null and b/assets/icons/guardia/icon-20/navy_64.png differ diff --git a/assets/icons/guardia/icon-20/white_16.png b/assets/icons/guardia/icon-20/white_16.png new file mode 100644 index 00000000..001659be Binary files /dev/null and b/assets/icons/guardia/icon-20/white_16.png differ diff --git a/assets/icons/guardia/icon-20/white_24.png b/assets/icons/guardia/icon-20/white_24.png new file mode 100644 index 00000000..699c5425 Binary files /dev/null and b/assets/icons/guardia/icon-20/white_24.png differ diff --git a/assets/icons/guardia/icon-20/white_32.png b/assets/icons/guardia/icon-20/white_32.png new file mode 100644 index 00000000..8f0a98b5 Binary files /dev/null and b/assets/icons/guardia/icon-20/white_32.png differ diff --git a/assets/icons/guardia/icon-20/white_48.png b/assets/icons/guardia/icon-20/white_48.png new file mode 100644 index 00000000..b41ba1df Binary files /dev/null and b/assets/icons/guardia/icon-20/white_48.png differ diff --git a/assets/icons/guardia/icon-20/white_64.png b/assets/icons/guardia/icon-20/white_64.png new file mode 100644 index 00000000..7adb621a Binary files /dev/null and b/assets/icons/guardia/icon-20/white_64.png differ diff --git a/assets/icons/guardia/icon-21/navy_16.png b/assets/icons/guardia/icon-21/navy_16.png new file mode 100644 index 00000000..b7934a5a Binary files /dev/null and b/assets/icons/guardia/icon-21/navy_16.png differ diff --git a/assets/icons/guardia/icon-21/navy_24.png b/assets/icons/guardia/icon-21/navy_24.png new file mode 100644 index 00000000..c10b17ea Binary files /dev/null and b/assets/icons/guardia/icon-21/navy_24.png differ diff --git a/assets/icons/guardia/icon-21/navy_32.png b/assets/icons/guardia/icon-21/navy_32.png new file mode 100644 index 00000000..bc7f25ea Binary files /dev/null and b/assets/icons/guardia/icon-21/navy_32.png differ diff --git a/assets/icons/guardia/icon-21/navy_48.png b/assets/icons/guardia/icon-21/navy_48.png new file mode 100644 index 00000000..c8c6379d Binary files /dev/null and b/assets/icons/guardia/icon-21/navy_48.png differ diff --git a/assets/icons/guardia/icon-21/navy_64.png b/assets/icons/guardia/icon-21/navy_64.png new file mode 100644 index 00000000..9a22dead Binary files /dev/null and b/assets/icons/guardia/icon-21/navy_64.png differ diff --git a/assets/icons/guardia/icon-21/white_16.png b/assets/icons/guardia/icon-21/white_16.png new file mode 100644 index 00000000..70f2fddd Binary files /dev/null and b/assets/icons/guardia/icon-21/white_16.png differ diff --git a/assets/icons/guardia/icon-21/white_24.png b/assets/icons/guardia/icon-21/white_24.png new file mode 100644 index 00000000..dfe69f29 Binary files /dev/null and b/assets/icons/guardia/icon-21/white_24.png differ diff --git a/assets/icons/guardia/icon-21/white_32.png b/assets/icons/guardia/icon-21/white_32.png new file mode 100644 index 00000000..69d8fb16 Binary files /dev/null and b/assets/icons/guardia/icon-21/white_32.png differ diff --git a/assets/icons/guardia/icon-21/white_48.png b/assets/icons/guardia/icon-21/white_48.png new file mode 100644 index 00000000..f378c23f Binary files /dev/null and b/assets/icons/guardia/icon-21/white_48.png differ diff --git a/assets/icons/guardia/icon-21/white_64.png b/assets/icons/guardia/icon-21/white_64.png new file mode 100644 index 00000000..a45cfa32 Binary files /dev/null and b/assets/icons/guardia/icon-21/white_64.png differ diff --git a/assets/icons/guardia/icon-22/navy_16.png b/assets/icons/guardia/icon-22/navy_16.png new file mode 100644 index 00000000..261a333e Binary files /dev/null and b/assets/icons/guardia/icon-22/navy_16.png differ diff --git a/assets/icons/guardia/icon-22/navy_24.png b/assets/icons/guardia/icon-22/navy_24.png new file mode 100644 index 00000000..aacc3e62 Binary files /dev/null and b/assets/icons/guardia/icon-22/navy_24.png differ diff --git a/assets/icons/guardia/icon-22/navy_32.png b/assets/icons/guardia/icon-22/navy_32.png new file mode 100644 index 00000000..1357815f Binary files /dev/null and b/assets/icons/guardia/icon-22/navy_32.png differ diff --git a/assets/icons/guardia/icon-22/navy_48.png b/assets/icons/guardia/icon-22/navy_48.png new file mode 100644 index 00000000..57c0fde5 Binary files /dev/null and b/assets/icons/guardia/icon-22/navy_48.png differ diff --git a/assets/icons/guardia/icon-22/navy_64.png b/assets/icons/guardia/icon-22/navy_64.png new file mode 100644 index 00000000..e4e97938 Binary files /dev/null and b/assets/icons/guardia/icon-22/navy_64.png differ diff --git a/assets/icons/guardia/icon-22/white_16.png b/assets/icons/guardia/icon-22/white_16.png new file mode 100644 index 00000000..c828232b Binary files /dev/null and b/assets/icons/guardia/icon-22/white_16.png differ diff --git a/assets/icons/guardia/icon-22/white_24.png b/assets/icons/guardia/icon-22/white_24.png new file mode 100644 index 00000000..3e2c1bbe Binary files /dev/null and b/assets/icons/guardia/icon-22/white_24.png differ diff --git a/assets/icons/guardia/icon-22/white_32.png b/assets/icons/guardia/icon-22/white_32.png new file mode 100644 index 00000000..7e64154e Binary files /dev/null and b/assets/icons/guardia/icon-22/white_32.png differ diff --git a/assets/icons/guardia/icon-22/white_48.png b/assets/icons/guardia/icon-22/white_48.png new file mode 100644 index 00000000..44f1ec33 Binary files /dev/null and b/assets/icons/guardia/icon-22/white_48.png differ diff --git a/assets/icons/guardia/icon-22/white_64.png b/assets/icons/guardia/icon-22/white_64.png new file mode 100644 index 00000000..7879cfe7 Binary files /dev/null and b/assets/icons/guardia/icon-22/white_64.png differ diff --git a/assets/icons/guardia/icon-23/navy_16.png b/assets/icons/guardia/icon-23/navy_16.png new file mode 100644 index 00000000..b5a29062 Binary files /dev/null and b/assets/icons/guardia/icon-23/navy_16.png differ diff --git a/assets/icons/guardia/icon-23/navy_24.png b/assets/icons/guardia/icon-23/navy_24.png new file mode 100644 index 00000000..42673867 Binary files /dev/null and b/assets/icons/guardia/icon-23/navy_24.png differ diff --git a/assets/icons/guardia/icon-23/navy_32.png b/assets/icons/guardia/icon-23/navy_32.png new file mode 100644 index 00000000..768d8433 Binary files /dev/null and b/assets/icons/guardia/icon-23/navy_32.png differ diff --git a/assets/icons/guardia/icon-23/navy_48.png b/assets/icons/guardia/icon-23/navy_48.png new file mode 100644 index 00000000..d3772bc6 Binary files /dev/null and b/assets/icons/guardia/icon-23/navy_48.png differ diff --git a/assets/icons/guardia/icon-23/navy_64.png b/assets/icons/guardia/icon-23/navy_64.png new file mode 100644 index 00000000..09166c52 Binary files /dev/null and b/assets/icons/guardia/icon-23/navy_64.png differ diff --git a/assets/icons/guardia/icon-23/white_16.png b/assets/icons/guardia/icon-23/white_16.png new file mode 100644 index 00000000..bc2293a9 Binary files /dev/null and b/assets/icons/guardia/icon-23/white_16.png differ diff --git a/assets/icons/guardia/icon-23/white_24.png b/assets/icons/guardia/icon-23/white_24.png new file mode 100644 index 00000000..92336615 Binary files /dev/null and b/assets/icons/guardia/icon-23/white_24.png differ diff --git a/assets/icons/guardia/icon-23/white_32.png b/assets/icons/guardia/icon-23/white_32.png new file mode 100644 index 00000000..daca9f1e Binary files /dev/null and b/assets/icons/guardia/icon-23/white_32.png differ diff --git a/assets/icons/guardia/icon-23/white_48.png b/assets/icons/guardia/icon-23/white_48.png new file mode 100644 index 00000000..f14a3972 Binary files /dev/null and b/assets/icons/guardia/icon-23/white_48.png differ diff --git a/assets/icons/guardia/icon-23/white_64.png b/assets/icons/guardia/icon-23/white_64.png new file mode 100644 index 00000000..049d3415 Binary files /dev/null and b/assets/icons/guardia/icon-23/white_64.png differ diff --git a/assets/icons/guardia/icon-24/navy_16.png b/assets/icons/guardia/icon-24/navy_16.png new file mode 100644 index 00000000..ebf2bca9 Binary files /dev/null and b/assets/icons/guardia/icon-24/navy_16.png differ diff --git a/assets/icons/guardia/icon-24/navy_24.png b/assets/icons/guardia/icon-24/navy_24.png new file mode 100644 index 00000000..0d1f124b Binary files /dev/null and b/assets/icons/guardia/icon-24/navy_24.png differ diff --git a/assets/icons/guardia/icon-24/navy_32.png b/assets/icons/guardia/icon-24/navy_32.png new file mode 100644 index 00000000..371836c6 Binary files /dev/null and b/assets/icons/guardia/icon-24/navy_32.png differ diff --git a/assets/icons/guardia/icon-24/navy_48.png b/assets/icons/guardia/icon-24/navy_48.png new file mode 100644 index 00000000..b0f49060 Binary files /dev/null and b/assets/icons/guardia/icon-24/navy_48.png differ diff --git a/assets/icons/guardia/icon-24/navy_64.png b/assets/icons/guardia/icon-24/navy_64.png new file mode 100644 index 00000000..82122db7 Binary files /dev/null and b/assets/icons/guardia/icon-24/navy_64.png differ diff --git a/assets/icons/guardia/icon-24/white_16.png b/assets/icons/guardia/icon-24/white_16.png new file mode 100644 index 00000000..1ce5ce6a Binary files /dev/null and b/assets/icons/guardia/icon-24/white_16.png differ diff --git a/assets/icons/guardia/icon-24/white_24.png b/assets/icons/guardia/icon-24/white_24.png new file mode 100644 index 00000000..de993b6c Binary files /dev/null and b/assets/icons/guardia/icon-24/white_24.png differ diff --git a/assets/icons/guardia/icon-24/white_32.png b/assets/icons/guardia/icon-24/white_32.png new file mode 100644 index 00000000..ff5b135f Binary files /dev/null and b/assets/icons/guardia/icon-24/white_32.png differ diff --git a/assets/icons/guardia/icon-24/white_48.png b/assets/icons/guardia/icon-24/white_48.png new file mode 100644 index 00000000..5ea76a68 Binary files /dev/null and b/assets/icons/guardia/icon-24/white_48.png differ diff --git a/assets/icons/guardia/icon-24/white_64.png b/assets/icons/guardia/icon-24/white_64.png new file mode 100644 index 00000000..66c477da Binary files /dev/null and b/assets/icons/guardia/icon-24/white_64.png differ diff --git a/assets/icons/guardia/icon-25/navy_16.png b/assets/icons/guardia/icon-25/navy_16.png new file mode 100644 index 00000000..c2109f44 Binary files /dev/null and b/assets/icons/guardia/icon-25/navy_16.png differ diff --git a/assets/icons/guardia/icon-25/navy_24.png b/assets/icons/guardia/icon-25/navy_24.png new file mode 100644 index 00000000..71becddd Binary files /dev/null and b/assets/icons/guardia/icon-25/navy_24.png differ diff --git a/assets/icons/guardia/icon-25/navy_32.png b/assets/icons/guardia/icon-25/navy_32.png new file mode 100644 index 00000000..29b347a1 Binary files /dev/null and b/assets/icons/guardia/icon-25/navy_32.png differ diff --git a/assets/icons/guardia/icon-25/navy_48.png b/assets/icons/guardia/icon-25/navy_48.png new file mode 100644 index 00000000..8ae8e038 Binary files /dev/null and b/assets/icons/guardia/icon-25/navy_48.png differ diff --git a/assets/icons/guardia/icon-25/navy_64.png b/assets/icons/guardia/icon-25/navy_64.png new file mode 100644 index 00000000..71b69d83 Binary files /dev/null and b/assets/icons/guardia/icon-25/navy_64.png differ diff --git a/assets/icons/guardia/icon-25/white_16.png b/assets/icons/guardia/icon-25/white_16.png new file mode 100644 index 00000000..f329a2dd Binary files /dev/null and b/assets/icons/guardia/icon-25/white_16.png differ diff --git a/assets/icons/guardia/icon-25/white_24.png b/assets/icons/guardia/icon-25/white_24.png new file mode 100644 index 00000000..07d0f1c2 Binary files /dev/null and b/assets/icons/guardia/icon-25/white_24.png differ diff --git a/assets/icons/guardia/icon-25/white_32.png b/assets/icons/guardia/icon-25/white_32.png new file mode 100644 index 00000000..48362c8c Binary files /dev/null and b/assets/icons/guardia/icon-25/white_32.png differ diff --git a/assets/icons/guardia/icon-25/white_48.png b/assets/icons/guardia/icon-25/white_48.png new file mode 100644 index 00000000..30dd0138 Binary files /dev/null and b/assets/icons/guardia/icon-25/white_48.png differ diff --git a/assets/icons/guardia/icon-25/white_64.png b/assets/icons/guardia/icon-25/white_64.png new file mode 100644 index 00000000..bba307ed Binary files /dev/null and b/assets/icons/guardia/icon-25/white_64.png differ diff --git a/assets/icons/guardia/icon-26/navy_16.png b/assets/icons/guardia/icon-26/navy_16.png new file mode 100644 index 00000000..5f3d53b2 Binary files /dev/null and b/assets/icons/guardia/icon-26/navy_16.png differ diff --git a/assets/icons/guardia/icon-26/navy_24.png b/assets/icons/guardia/icon-26/navy_24.png new file mode 100644 index 00000000..b15eca2e Binary files /dev/null and b/assets/icons/guardia/icon-26/navy_24.png differ diff --git a/assets/icons/guardia/icon-26/navy_32.png b/assets/icons/guardia/icon-26/navy_32.png new file mode 100644 index 00000000..48a0289d Binary files /dev/null and b/assets/icons/guardia/icon-26/navy_32.png differ diff --git a/assets/icons/guardia/icon-26/navy_48.png b/assets/icons/guardia/icon-26/navy_48.png new file mode 100644 index 00000000..3af2db73 Binary files /dev/null and b/assets/icons/guardia/icon-26/navy_48.png differ diff --git a/assets/icons/guardia/icon-26/navy_64.png b/assets/icons/guardia/icon-26/navy_64.png new file mode 100644 index 00000000..3064b467 Binary files /dev/null and b/assets/icons/guardia/icon-26/navy_64.png differ diff --git a/assets/icons/guardia/icon-26/white_16.png b/assets/icons/guardia/icon-26/white_16.png new file mode 100644 index 00000000..82ddad43 Binary files /dev/null and b/assets/icons/guardia/icon-26/white_16.png differ diff --git a/assets/icons/guardia/icon-26/white_24.png b/assets/icons/guardia/icon-26/white_24.png new file mode 100644 index 00000000..d31cc64d Binary files /dev/null and b/assets/icons/guardia/icon-26/white_24.png differ diff --git a/assets/icons/guardia/icon-26/white_32.png b/assets/icons/guardia/icon-26/white_32.png new file mode 100644 index 00000000..f0b90bf8 Binary files /dev/null and b/assets/icons/guardia/icon-26/white_32.png differ diff --git a/assets/icons/guardia/icon-26/white_48.png b/assets/icons/guardia/icon-26/white_48.png new file mode 100644 index 00000000..a8d33984 Binary files /dev/null and b/assets/icons/guardia/icon-26/white_48.png differ diff --git a/assets/icons/guardia/icon-26/white_64.png b/assets/icons/guardia/icon-26/white_64.png new file mode 100644 index 00000000..73889e8e Binary files /dev/null and b/assets/icons/guardia/icon-26/white_64.png differ diff --git a/assets/icons/guardia/icon-27/navy_16.png b/assets/icons/guardia/icon-27/navy_16.png new file mode 100644 index 00000000..cb24a6ce Binary files /dev/null and b/assets/icons/guardia/icon-27/navy_16.png differ diff --git a/assets/icons/guardia/icon-27/navy_24.png b/assets/icons/guardia/icon-27/navy_24.png new file mode 100644 index 00000000..7c5dc6a4 Binary files /dev/null and b/assets/icons/guardia/icon-27/navy_24.png differ diff --git a/assets/icons/guardia/icon-27/navy_32.png b/assets/icons/guardia/icon-27/navy_32.png new file mode 100644 index 00000000..e09686c7 Binary files /dev/null and b/assets/icons/guardia/icon-27/navy_32.png differ diff --git a/assets/icons/guardia/icon-27/navy_48.png b/assets/icons/guardia/icon-27/navy_48.png new file mode 100644 index 00000000..55c575a9 Binary files /dev/null and b/assets/icons/guardia/icon-27/navy_48.png differ diff --git a/assets/icons/guardia/icon-27/navy_64.png b/assets/icons/guardia/icon-27/navy_64.png new file mode 100644 index 00000000..3d9353ec Binary files /dev/null and b/assets/icons/guardia/icon-27/navy_64.png differ diff --git a/assets/icons/guardia/icon-27/white_16.png b/assets/icons/guardia/icon-27/white_16.png new file mode 100644 index 00000000..16b92582 Binary files /dev/null and b/assets/icons/guardia/icon-27/white_16.png differ diff --git a/assets/icons/guardia/icon-27/white_24.png b/assets/icons/guardia/icon-27/white_24.png new file mode 100644 index 00000000..fb03fcaa Binary files /dev/null and b/assets/icons/guardia/icon-27/white_24.png differ diff --git a/assets/icons/guardia/icon-27/white_32.png b/assets/icons/guardia/icon-27/white_32.png new file mode 100644 index 00000000..5ea1158a Binary files /dev/null and b/assets/icons/guardia/icon-27/white_32.png differ diff --git a/assets/icons/guardia/icon-27/white_48.png b/assets/icons/guardia/icon-27/white_48.png new file mode 100644 index 00000000..d1bcea01 Binary files /dev/null and b/assets/icons/guardia/icon-27/white_48.png differ diff --git a/assets/icons/guardia/icon-27/white_64.png b/assets/icons/guardia/icon-27/white_64.png new file mode 100644 index 00000000..d5c1285b Binary files /dev/null and b/assets/icons/guardia/icon-27/white_64.png differ diff --git a/assets/icons/guardia/icon-28/navy_16.png b/assets/icons/guardia/icon-28/navy_16.png new file mode 100644 index 00000000..679b4044 Binary files /dev/null and b/assets/icons/guardia/icon-28/navy_16.png differ diff --git a/assets/icons/guardia/icon-28/navy_24.png b/assets/icons/guardia/icon-28/navy_24.png new file mode 100644 index 00000000..54a0c583 Binary files /dev/null and b/assets/icons/guardia/icon-28/navy_24.png differ diff --git a/assets/icons/guardia/icon-28/navy_32.png b/assets/icons/guardia/icon-28/navy_32.png new file mode 100644 index 00000000..258aaf74 Binary files /dev/null and b/assets/icons/guardia/icon-28/navy_32.png differ diff --git a/assets/icons/guardia/icon-28/navy_48.png b/assets/icons/guardia/icon-28/navy_48.png new file mode 100644 index 00000000..2331cd0e Binary files /dev/null and b/assets/icons/guardia/icon-28/navy_48.png differ diff --git a/assets/icons/guardia/icon-28/navy_64.png b/assets/icons/guardia/icon-28/navy_64.png new file mode 100644 index 00000000..b8cedb91 Binary files /dev/null and b/assets/icons/guardia/icon-28/navy_64.png differ diff --git a/assets/icons/guardia/icon-28/white_16.png b/assets/icons/guardia/icon-28/white_16.png new file mode 100644 index 00000000..d911ea09 Binary files /dev/null and b/assets/icons/guardia/icon-28/white_16.png differ diff --git a/assets/icons/guardia/icon-28/white_24.png b/assets/icons/guardia/icon-28/white_24.png new file mode 100644 index 00000000..cd69d016 Binary files /dev/null and b/assets/icons/guardia/icon-28/white_24.png differ diff --git a/assets/icons/guardia/icon-28/white_32.png b/assets/icons/guardia/icon-28/white_32.png new file mode 100644 index 00000000..c691a815 Binary files /dev/null and b/assets/icons/guardia/icon-28/white_32.png differ diff --git a/assets/icons/guardia/icon-28/white_48.png b/assets/icons/guardia/icon-28/white_48.png new file mode 100644 index 00000000..466e939c Binary files /dev/null and b/assets/icons/guardia/icon-28/white_48.png differ diff --git a/assets/icons/guardia/icon-28/white_64.png b/assets/icons/guardia/icon-28/white_64.png new file mode 100644 index 00000000..13defd37 Binary files /dev/null and b/assets/icons/guardia/icon-28/white_64.png differ diff --git a/assets/icons/guardia/icon-29/navy_16.png b/assets/icons/guardia/icon-29/navy_16.png new file mode 100644 index 00000000..d0f15fc1 Binary files /dev/null and b/assets/icons/guardia/icon-29/navy_16.png differ diff --git a/assets/icons/guardia/icon-29/navy_24.png b/assets/icons/guardia/icon-29/navy_24.png new file mode 100644 index 00000000..fb5f57a7 Binary files /dev/null and b/assets/icons/guardia/icon-29/navy_24.png differ diff --git a/assets/icons/guardia/icon-29/navy_32.png b/assets/icons/guardia/icon-29/navy_32.png new file mode 100644 index 00000000..0f85bf7e Binary files /dev/null and b/assets/icons/guardia/icon-29/navy_32.png differ diff --git a/assets/icons/guardia/icon-29/navy_48.png b/assets/icons/guardia/icon-29/navy_48.png new file mode 100644 index 00000000..b2f38e86 Binary files /dev/null and b/assets/icons/guardia/icon-29/navy_48.png differ diff --git a/assets/icons/guardia/icon-29/navy_64.png b/assets/icons/guardia/icon-29/navy_64.png new file mode 100644 index 00000000..cece283a Binary files /dev/null and b/assets/icons/guardia/icon-29/navy_64.png differ diff --git a/assets/icons/guardia/icon-29/white_16.png b/assets/icons/guardia/icon-29/white_16.png new file mode 100644 index 00000000..fa8146d8 Binary files /dev/null and b/assets/icons/guardia/icon-29/white_16.png differ diff --git a/assets/icons/guardia/icon-29/white_24.png b/assets/icons/guardia/icon-29/white_24.png new file mode 100644 index 00000000..ad3b6f63 Binary files /dev/null and b/assets/icons/guardia/icon-29/white_24.png differ diff --git a/assets/icons/guardia/icon-29/white_32.png b/assets/icons/guardia/icon-29/white_32.png new file mode 100644 index 00000000..02867e7a Binary files /dev/null and b/assets/icons/guardia/icon-29/white_32.png differ diff --git a/assets/icons/guardia/icon-29/white_48.png b/assets/icons/guardia/icon-29/white_48.png new file mode 100644 index 00000000..a9c58005 Binary files /dev/null and b/assets/icons/guardia/icon-29/white_48.png differ diff --git a/assets/icons/guardia/icon-29/white_64.png b/assets/icons/guardia/icon-29/white_64.png new file mode 100644 index 00000000..c5eada74 Binary files /dev/null and b/assets/icons/guardia/icon-29/white_64.png differ diff --git a/assets/icons/guardia/icon-3/navy_16.png b/assets/icons/guardia/icon-3/navy_16.png new file mode 100644 index 00000000..81e857f2 Binary files /dev/null and b/assets/icons/guardia/icon-3/navy_16.png differ diff --git a/assets/icons/guardia/icon-3/navy_24.png b/assets/icons/guardia/icon-3/navy_24.png new file mode 100644 index 00000000..840d1bca Binary files /dev/null and b/assets/icons/guardia/icon-3/navy_24.png differ diff --git a/assets/icons/guardia/icon-3/navy_32.png b/assets/icons/guardia/icon-3/navy_32.png new file mode 100644 index 00000000..3d3c95c6 Binary files /dev/null and b/assets/icons/guardia/icon-3/navy_32.png differ diff --git a/assets/icons/guardia/icon-3/navy_48.png b/assets/icons/guardia/icon-3/navy_48.png new file mode 100644 index 00000000..9c869892 Binary files /dev/null and b/assets/icons/guardia/icon-3/navy_48.png differ diff --git a/assets/icons/guardia/icon-3/navy_64.png b/assets/icons/guardia/icon-3/navy_64.png new file mode 100644 index 00000000..ae2f8cf6 Binary files /dev/null and b/assets/icons/guardia/icon-3/navy_64.png differ diff --git a/assets/icons/guardia/icon-3/white_16.png b/assets/icons/guardia/icon-3/white_16.png new file mode 100644 index 00000000..8e58c63a Binary files /dev/null and b/assets/icons/guardia/icon-3/white_16.png differ diff --git a/assets/icons/guardia/icon-3/white_24.png b/assets/icons/guardia/icon-3/white_24.png new file mode 100644 index 00000000..1e168db3 Binary files /dev/null and b/assets/icons/guardia/icon-3/white_24.png differ diff --git a/assets/icons/guardia/icon-3/white_32.png b/assets/icons/guardia/icon-3/white_32.png new file mode 100644 index 00000000..de90205c Binary files /dev/null and b/assets/icons/guardia/icon-3/white_32.png differ diff --git a/assets/icons/guardia/icon-3/white_48.png b/assets/icons/guardia/icon-3/white_48.png new file mode 100644 index 00000000..19a452b4 Binary files /dev/null and b/assets/icons/guardia/icon-3/white_48.png differ diff --git a/assets/icons/guardia/icon-3/white_64.png b/assets/icons/guardia/icon-3/white_64.png new file mode 100644 index 00000000..086e13a2 Binary files /dev/null and b/assets/icons/guardia/icon-3/white_64.png differ diff --git a/assets/icons/guardia/icon-30/navy_16.png b/assets/icons/guardia/icon-30/navy_16.png new file mode 100644 index 00000000..5c9d8a3b Binary files /dev/null and b/assets/icons/guardia/icon-30/navy_16.png differ diff --git a/assets/icons/guardia/icon-30/navy_24.png b/assets/icons/guardia/icon-30/navy_24.png new file mode 100644 index 00000000..30917166 Binary files /dev/null and b/assets/icons/guardia/icon-30/navy_24.png differ diff --git a/assets/icons/guardia/icon-30/navy_32.png b/assets/icons/guardia/icon-30/navy_32.png new file mode 100644 index 00000000..0ae1f7c3 Binary files /dev/null and b/assets/icons/guardia/icon-30/navy_32.png differ diff --git a/assets/icons/guardia/icon-30/navy_48.png b/assets/icons/guardia/icon-30/navy_48.png new file mode 100644 index 00000000..39bf06eb Binary files /dev/null and b/assets/icons/guardia/icon-30/navy_48.png differ diff --git a/assets/icons/guardia/icon-30/navy_64.png b/assets/icons/guardia/icon-30/navy_64.png new file mode 100644 index 00000000..dbd4688b Binary files /dev/null and b/assets/icons/guardia/icon-30/navy_64.png differ diff --git a/assets/icons/guardia/icon-30/white_16.png b/assets/icons/guardia/icon-30/white_16.png new file mode 100644 index 00000000..5121b6d6 Binary files /dev/null and b/assets/icons/guardia/icon-30/white_16.png differ diff --git a/assets/icons/guardia/icon-30/white_24.png b/assets/icons/guardia/icon-30/white_24.png new file mode 100644 index 00000000..4c333042 Binary files /dev/null and b/assets/icons/guardia/icon-30/white_24.png differ diff --git a/assets/icons/guardia/icon-30/white_32.png b/assets/icons/guardia/icon-30/white_32.png new file mode 100644 index 00000000..64f03ad4 Binary files /dev/null and b/assets/icons/guardia/icon-30/white_32.png differ diff --git a/assets/icons/guardia/icon-30/white_48.png b/assets/icons/guardia/icon-30/white_48.png new file mode 100644 index 00000000..ad9452e2 Binary files /dev/null and b/assets/icons/guardia/icon-30/white_48.png differ diff --git a/assets/icons/guardia/icon-30/white_64.png b/assets/icons/guardia/icon-30/white_64.png new file mode 100644 index 00000000..477fb65d Binary files /dev/null and b/assets/icons/guardia/icon-30/white_64.png differ diff --git a/assets/icons/guardia/icon-4/navy_16.png b/assets/icons/guardia/icon-4/navy_16.png new file mode 100644 index 00000000..766b7781 Binary files /dev/null and b/assets/icons/guardia/icon-4/navy_16.png differ diff --git a/assets/icons/guardia/icon-4/navy_24.png b/assets/icons/guardia/icon-4/navy_24.png new file mode 100644 index 00000000..da2c9724 Binary files /dev/null and b/assets/icons/guardia/icon-4/navy_24.png differ diff --git a/assets/icons/guardia/icon-4/navy_32.png b/assets/icons/guardia/icon-4/navy_32.png new file mode 100644 index 00000000..77639e79 Binary files /dev/null and b/assets/icons/guardia/icon-4/navy_32.png differ diff --git a/assets/icons/guardia/icon-4/navy_48.png b/assets/icons/guardia/icon-4/navy_48.png new file mode 100644 index 00000000..1eee306c Binary files /dev/null and b/assets/icons/guardia/icon-4/navy_48.png differ diff --git a/assets/icons/guardia/icon-4/navy_64.png b/assets/icons/guardia/icon-4/navy_64.png new file mode 100644 index 00000000..42202413 Binary files /dev/null and b/assets/icons/guardia/icon-4/navy_64.png differ diff --git a/assets/icons/guardia/icon-4/white_16.png b/assets/icons/guardia/icon-4/white_16.png new file mode 100644 index 00000000..766b0a33 Binary files /dev/null and b/assets/icons/guardia/icon-4/white_16.png differ diff --git a/assets/icons/guardia/icon-4/white_24.png b/assets/icons/guardia/icon-4/white_24.png new file mode 100644 index 00000000..5fb40f1a Binary files /dev/null and b/assets/icons/guardia/icon-4/white_24.png differ diff --git a/assets/icons/guardia/icon-4/white_32.png b/assets/icons/guardia/icon-4/white_32.png new file mode 100644 index 00000000..9e929181 Binary files /dev/null and b/assets/icons/guardia/icon-4/white_32.png differ diff --git a/assets/icons/guardia/icon-4/white_48.png b/assets/icons/guardia/icon-4/white_48.png new file mode 100644 index 00000000..ce5ff9e9 Binary files /dev/null and b/assets/icons/guardia/icon-4/white_48.png differ diff --git a/assets/icons/guardia/icon-4/white_64.png b/assets/icons/guardia/icon-4/white_64.png new file mode 100644 index 00000000..4d7b5bc0 Binary files /dev/null and b/assets/icons/guardia/icon-4/white_64.png differ diff --git a/assets/icons/guardia/icon-5/navy_16.png b/assets/icons/guardia/icon-5/navy_16.png new file mode 100644 index 00000000..aa9f605e Binary files /dev/null and b/assets/icons/guardia/icon-5/navy_16.png differ diff --git a/assets/icons/guardia/icon-5/navy_24.png b/assets/icons/guardia/icon-5/navy_24.png new file mode 100644 index 00000000..68974c5d Binary files /dev/null and b/assets/icons/guardia/icon-5/navy_24.png differ diff --git a/assets/icons/guardia/icon-5/navy_32.png b/assets/icons/guardia/icon-5/navy_32.png new file mode 100644 index 00000000..961c1a94 Binary files /dev/null and b/assets/icons/guardia/icon-5/navy_32.png differ diff --git a/assets/icons/guardia/icon-5/navy_48.png b/assets/icons/guardia/icon-5/navy_48.png new file mode 100644 index 00000000..e92c2587 Binary files /dev/null and b/assets/icons/guardia/icon-5/navy_48.png differ diff --git a/assets/icons/guardia/icon-5/navy_64.png b/assets/icons/guardia/icon-5/navy_64.png new file mode 100644 index 00000000..6b12465d Binary files /dev/null and b/assets/icons/guardia/icon-5/navy_64.png differ diff --git a/assets/icons/guardia/icon-5/white_16.png b/assets/icons/guardia/icon-5/white_16.png new file mode 100644 index 00000000..b893b665 Binary files /dev/null and b/assets/icons/guardia/icon-5/white_16.png differ diff --git a/assets/icons/guardia/icon-5/white_24.png b/assets/icons/guardia/icon-5/white_24.png new file mode 100644 index 00000000..7465e868 Binary files /dev/null and b/assets/icons/guardia/icon-5/white_24.png differ diff --git a/assets/icons/guardia/icon-5/white_32.png b/assets/icons/guardia/icon-5/white_32.png new file mode 100644 index 00000000..05bed242 Binary files /dev/null and b/assets/icons/guardia/icon-5/white_32.png differ diff --git a/assets/icons/guardia/icon-5/white_48.png b/assets/icons/guardia/icon-5/white_48.png new file mode 100644 index 00000000..c66d018c Binary files /dev/null and b/assets/icons/guardia/icon-5/white_48.png differ diff --git a/assets/icons/guardia/icon-5/white_64.png b/assets/icons/guardia/icon-5/white_64.png new file mode 100644 index 00000000..4ba98faa Binary files /dev/null and b/assets/icons/guardia/icon-5/white_64.png differ diff --git a/assets/icons/guardia/icon-6/navy_16.png b/assets/icons/guardia/icon-6/navy_16.png new file mode 100644 index 00000000..8bcb2f15 Binary files /dev/null and b/assets/icons/guardia/icon-6/navy_16.png differ diff --git a/assets/icons/guardia/icon-6/navy_24.png b/assets/icons/guardia/icon-6/navy_24.png new file mode 100644 index 00000000..4a9d8c27 Binary files /dev/null and b/assets/icons/guardia/icon-6/navy_24.png differ diff --git a/assets/icons/guardia/icon-6/navy_32.png b/assets/icons/guardia/icon-6/navy_32.png new file mode 100644 index 00000000..d1253d5e Binary files /dev/null and b/assets/icons/guardia/icon-6/navy_32.png differ diff --git a/assets/icons/guardia/icon-6/navy_48.png b/assets/icons/guardia/icon-6/navy_48.png new file mode 100644 index 00000000..bc8e5469 Binary files /dev/null and b/assets/icons/guardia/icon-6/navy_48.png differ diff --git a/assets/icons/guardia/icon-6/navy_64.png b/assets/icons/guardia/icon-6/navy_64.png new file mode 100644 index 00000000..624afe7e Binary files /dev/null and b/assets/icons/guardia/icon-6/navy_64.png differ diff --git a/assets/icons/guardia/icon-6/white_16.png b/assets/icons/guardia/icon-6/white_16.png new file mode 100644 index 00000000..9c230edf Binary files /dev/null and b/assets/icons/guardia/icon-6/white_16.png differ diff --git a/assets/icons/guardia/icon-6/white_24.png b/assets/icons/guardia/icon-6/white_24.png new file mode 100644 index 00000000..7909ef7a Binary files /dev/null and b/assets/icons/guardia/icon-6/white_24.png differ diff --git a/assets/icons/guardia/icon-6/white_32.png b/assets/icons/guardia/icon-6/white_32.png new file mode 100644 index 00000000..1a1fee46 Binary files /dev/null and b/assets/icons/guardia/icon-6/white_32.png differ diff --git a/assets/icons/guardia/icon-6/white_48.png b/assets/icons/guardia/icon-6/white_48.png new file mode 100644 index 00000000..5ac6e81b Binary files /dev/null and b/assets/icons/guardia/icon-6/white_48.png differ diff --git a/assets/icons/guardia/icon-6/white_64.png b/assets/icons/guardia/icon-6/white_64.png new file mode 100644 index 00000000..dfb30b1f Binary files /dev/null and b/assets/icons/guardia/icon-6/white_64.png differ diff --git a/assets/icons/guardia/icon-7/navy_16.png b/assets/icons/guardia/icon-7/navy_16.png new file mode 100644 index 00000000..933ab9e2 Binary files /dev/null and b/assets/icons/guardia/icon-7/navy_16.png differ diff --git a/assets/icons/guardia/icon-7/navy_24.png b/assets/icons/guardia/icon-7/navy_24.png new file mode 100644 index 00000000..1eccea40 Binary files /dev/null and b/assets/icons/guardia/icon-7/navy_24.png differ diff --git a/assets/icons/guardia/icon-7/navy_32.png b/assets/icons/guardia/icon-7/navy_32.png new file mode 100644 index 00000000..a20caf36 Binary files /dev/null and b/assets/icons/guardia/icon-7/navy_32.png differ diff --git a/assets/icons/guardia/icon-7/navy_48.png b/assets/icons/guardia/icon-7/navy_48.png new file mode 100644 index 00000000..768ca800 Binary files /dev/null and b/assets/icons/guardia/icon-7/navy_48.png differ diff --git a/assets/icons/guardia/icon-7/navy_64.png b/assets/icons/guardia/icon-7/navy_64.png new file mode 100644 index 00000000..797424d5 Binary files /dev/null and b/assets/icons/guardia/icon-7/navy_64.png differ diff --git a/assets/icons/guardia/icon-7/white_16.png b/assets/icons/guardia/icon-7/white_16.png new file mode 100644 index 00000000..87d1e775 Binary files /dev/null and b/assets/icons/guardia/icon-7/white_16.png differ diff --git a/assets/icons/guardia/icon-7/white_24.png b/assets/icons/guardia/icon-7/white_24.png new file mode 100644 index 00000000..89cfc5dc Binary files /dev/null and b/assets/icons/guardia/icon-7/white_24.png differ diff --git a/assets/icons/guardia/icon-7/white_32.png b/assets/icons/guardia/icon-7/white_32.png new file mode 100644 index 00000000..2b6c62e4 Binary files /dev/null and b/assets/icons/guardia/icon-7/white_32.png differ diff --git a/assets/icons/guardia/icon-7/white_48.png b/assets/icons/guardia/icon-7/white_48.png new file mode 100644 index 00000000..bd7ea367 Binary files /dev/null and b/assets/icons/guardia/icon-7/white_48.png differ diff --git a/assets/icons/guardia/icon-7/white_64.png b/assets/icons/guardia/icon-7/white_64.png new file mode 100644 index 00000000..2313b0d1 Binary files /dev/null and b/assets/icons/guardia/icon-7/white_64.png differ diff --git a/assets/icons/guardia/icon-8/navy_16.png b/assets/icons/guardia/icon-8/navy_16.png new file mode 100644 index 00000000..59cfa725 Binary files /dev/null and b/assets/icons/guardia/icon-8/navy_16.png differ diff --git a/assets/icons/guardia/icon-8/navy_24.png b/assets/icons/guardia/icon-8/navy_24.png new file mode 100644 index 00000000..ce523fbc Binary files /dev/null and b/assets/icons/guardia/icon-8/navy_24.png differ diff --git a/assets/icons/guardia/icon-8/navy_32.png b/assets/icons/guardia/icon-8/navy_32.png new file mode 100644 index 00000000..9687bd17 Binary files /dev/null and b/assets/icons/guardia/icon-8/navy_32.png differ diff --git a/assets/icons/guardia/icon-8/navy_48.png b/assets/icons/guardia/icon-8/navy_48.png new file mode 100644 index 00000000..e8b7c2a3 Binary files /dev/null and b/assets/icons/guardia/icon-8/navy_48.png differ diff --git a/assets/icons/guardia/icon-8/navy_64.png b/assets/icons/guardia/icon-8/navy_64.png new file mode 100644 index 00000000..05c5d816 Binary files /dev/null and b/assets/icons/guardia/icon-8/navy_64.png differ diff --git a/assets/icons/guardia/icon-8/white_16.png b/assets/icons/guardia/icon-8/white_16.png new file mode 100644 index 00000000..d16a9863 Binary files /dev/null and b/assets/icons/guardia/icon-8/white_16.png differ diff --git a/assets/icons/guardia/icon-8/white_24.png b/assets/icons/guardia/icon-8/white_24.png new file mode 100644 index 00000000..81349b33 Binary files /dev/null and b/assets/icons/guardia/icon-8/white_24.png differ diff --git a/assets/icons/guardia/icon-8/white_32.png b/assets/icons/guardia/icon-8/white_32.png new file mode 100644 index 00000000..06d68db0 Binary files /dev/null and b/assets/icons/guardia/icon-8/white_32.png differ diff --git a/assets/icons/guardia/icon-8/white_48.png b/assets/icons/guardia/icon-8/white_48.png new file mode 100644 index 00000000..2a46fb0d Binary files /dev/null and b/assets/icons/guardia/icon-8/white_48.png differ diff --git a/assets/icons/guardia/icon-8/white_64.png b/assets/icons/guardia/icon-8/white_64.png new file mode 100644 index 00000000..b218073c Binary files /dev/null and b/assets/icons/guardia/icon-8/white_64.png differ diff --git a/assets/icons/guardia/icon-9/navy_16.png b/assets/icons/guardia/icon-9/navy_16.png new file mode 100644 index 00000000..3e464f0e Binary files /dev/null and b/assets/icons/guardia/icon-9/navy_16.png differ diff --git a/assets/icons/guardia/icon-9/navy_24.png b/assets/icons/guardia/icon-9/navy_24.png new file mode 100644 index 00000000..6542798c Binary files /dev/null and b/assets/icons/guardia/icon-9/navy_24.png differ diff --git a/assets/icons/guardia/icon-9/navy_32.png b/assets/icons/guardia/icon-9/navy_32.png new file mode 100644 index 00000000..4da5568a Binary files /dev/null and b/assets/icons/guardia/icon-9/navy_32.png differ diff --git a/assets/icons/guardia/icon-9/navy_48.png b/assets/icons/guardia/icon-9/navy_48.png new file mode 100644 index 00000000..ec3f71f6 Binary files /dev/null and b/assets/icons/guardia/icon-9/navy_48.png differ diff --git a/assets/icons/guardia/icon-9/navy_64.png b/assets/icons/guardia/icon-9/navy_64.png new file mode 100644 index 00000000..f1eaa020 Binary files /dev/null and b/assets/icons/guardia/icon-9/navy_64.png differ diff --git a/assets/icons/guardia/icon-9/white_16.png b/assets/icons/guardia/icon-9/white_16.png new file mode 100644 index 00000000..c6a89e75 Binary files /dev/null and b/assets/icons/guardia/icon-9/white_16.png differ diff --git a/assets/icons/guardia/icon-9/white_24.png b/assets/icons/guardia/icon-9/white_24.png new file mode 100644 index 00000000..7324671f Binary files /dev/null and b/assets/icons/guardia/icon-9/white_24.png differ diff --git a/assets/icons/guardia/icon-9/white_32.png b/assets/icons/guardia/icon-9/white_32.png new file mode 100644 index 00000000..30f18f67 Binary files /dev/null and b/assets/icons/guardia/icon-9/white_32.png differ diff --git a/assets/icons/guardia/icon-9/white_48.png b/assets/icons/guardia/icon-9/white_48.png new file mode 100644 index 00000000..5cd9ac9a Binary files /dev/null and b/assets/icons/guardia/icon-9/white_48.png differ diff --git a/assets/icons/guardia/icon-9/white_64.png b/assets/icons/guardia/icon-9/white_64.png new file mode 100644 index 00000000..0bd2c35d Binary files /dev/null and b/assets/icons/guardia/icon-9/white_64.png differ diff --git a/assets/icons/guardia/icon-registry.ts b/assets/icons/guardia/icon-registry.ts new file mode 100644 index 00000000..2caa2184 --- /dev/null +++ b/assets/icons/guardia/icon-registry.ts @@ -0,0 +1,62 @@ +// GUARDiA 아이콘 레지스트리 — scripts/misc/icon_deploy.py 자동 생성 (수동 편집 시 재실행으로 덮어쓰기 주의) +// 사용: import { guardiaIcons } from '@/assets/icons/guardia/icon-registry'; +// + +export const guardiaIcons: Record> = { + 'home': { navy: require('./home/navy_48.png'), white: require('./home/white_48.png') }, + 'icon-1_1': { navy: require('./icon-1_1/navy_48.png'), white: require('./icon-1_1/white_48.png') }, + 'icon-1_10': { navy: require('./icon-1_10/navy_48.png'), white: require('./icon-1_10/white_48.png') }, + 'icon-1_11': { navy: require('./icon-1_11/navy_48.png'), white: require('./icon-1_11/white_48.png') }, + 'icon-1_12': { navy: require('./icon-1_12/navy_48.png'), white: require('./icon-1_12/white_48.png') }, + 'icon-1_13': { navy: require('./icon-1_13/navy_48.png'), white: require('./icon-1_13/white_48.png') }, + 'icon-1_14': { navy: require('./icon-1_14/navy_48.png'), white: require('./icon-1_14/white_48.png') }, + 'icon-1_15': { navy: require('./icon-1_15/navy_48.png'), white: require('./icon-1_15/white_48.png') }, + 'icon-1_2': { navy: require('./icon-1_2/navy_48.png'), white: require('./icon-1_2/white_48.png') }, + 'icon-1_3': { navy: require('./icon-1_3/navy_48.png'), white: require('./icon-1_3/white_48.png') }, + 'icon-1_4': { navy: require('./icon-1_4/navy_48.png'), white: require('./icon-1_4/white_48.png') }, + 'icon-1_5': { navy: require('./icon-1_5/navy_48.png'), white: require('./icon-1_5/white_48.png') }, + 'icon-1_6': { navy: require('./icon-1_6/navy_48.png'), white: require('./icon-1_6/white_48.png') }, + 'icon-1_7': { navy: require('./icon-1_7/navy_48.png'), white: require('./icon-1_7/white_48.png') }, + 'icon-1_8': { navy: require('./icon-1_8/navy_48.png'), white: require('./icon-1_8/white_48.png') }, + 'icon-1_9': { navy: require('./icon-1_9/navy_48.png'), white: require('./icon-1_9/white_48.png') }, + 'icon-1': { navy: require('./icon-1/navy_48.png'), white: require('./icon-1/white_48.png') }, + 'icon-10': { navy: require('./icon-10/navy_48.png'), white: require('./icon-10/white_48.png') }, + 'icon-11': { navy: require('./icon-11/navy_48.png'), white: require('./icon-11/white_48.png') }, + 'icon-12': { navy: require('./icon-12/navy_48.png'), white: require('./icon-12/white_48.png') }, + 'icon-13': { navy: require('./icon-13/navy_48.png'), white: require('./icon-13/white_48.png') }, + 'icon-14': { navy: require('./icon-14/navy_48.png'), white: require('./icon-14/white_48.png') }, + 'icon-15': { navy: require('./icon-15/navy_48.png'), white: require('./icon-15/white_48.png') }, + 'icon-16': { navy: require('./icon-16/navy_48.png'), white: require('./icon-16/white_48.png') }, + 'icon-17': { navy: require('./icon-17/navy_48.png'), white: require('./icon-17/white_48.png') }, + 'icon-18': { navy: require('./icon-18/navy_48.png'), white: require('./icon-18/white_48.png') }, + 'icon-19': { navy: require('./icon-19/navy_48.png'), white: require('./icon-19/white_48.png') }, + 'icon-2': { navy: require('./icon-2/navy_48.png'), white: require('./icon-2/white_48.png') }, + 'icon-20': { navy: require('./icon-20/navy_48.png'), white: require('./icon-20/white_48.png') }, + 'icon-21': { navy: require('./icon-21/navy_48.png'), white: require('./icon-21/white_48.png') }, + 'icon-22': { navy: require('./icon-22/navy_48.png'), white: require('./icon-22/white_48.png') }, + 'icon-23': { navy: require('./icon-23/navy_48.png'), white: require('./icon-23/white_48.png') }, + 'icon-24': { navy: require('./icon-24/navy_48.png'), white: require('./icon-24/white_48.png') }, + 'icon-25': { navy: require('./icon-25/navy_48.png'), white: require('./icon-25/white_48.png') }, + 'icon-26': { navy: require('./icon-26/navy_48.png'), white: require('./icon-26/white_48.png') }, + 'icon-27': { navy: require('./icon-27/navy_48.png'), white: require('./icon-27/white_48.png') }, + 'icon-28': { navy: require('./icon-28/navy_48.png'), white: require('./icon-28/white_48.png') }, + 'icon-29': { navy: require('./icon-29/navy_48.png'), white: require('./icon-29/white_48.png') }, + 'icon-3': { navy: require('./icon-3/navy_48.png'), white: require('./icon-3/white_48.png') }, + 'icon-30': { navy: require('./icon-30/navy_48.png'), white: require('./icon-30/white_48.png') }, + 'icon-4': { navy: require('./icon-4/navy_48.png'), white: require('./icon-4/white_48.png') }, + 'icon-5': { navy: require('./icon-5/navy_48.png'), white: require('./icon-5/white_48.png') }, + 'icon-6': { navy: require('./icon-6/navy_48.png'), white: require('./icon-6/white_48.png') }, + 'icon-7': { navy: require('./icon-7/navy_48.png'), white: require('./icon-7/white_48.png') }, + 'icon-8': { navy: require('./icon-8/navy_48.png'), white: require('./icon-8/white_48.png') }, + 'icon-9': { navy: require('./icon-9/navy_48.png'), white: require('./icon-9/white_48.png') }, + 'panel-01': { original: require('./panel-01/original_48.png') }, + 'panel-02': { original: require('./panel-02/original_48.png') }, + 'brand-1': { original: require('./brand-1/original_48.png') }, + 'brand-2': { original: require('./brand-2/original_48.png') }, + 'brand-3': { original: require('./brand-3/original_48.png') }, + 'brand-4': { original: require('./brand-4/original_48.png') }, + 'brand-5': { original: require('./brand-5/original_48.png') }, + 'brand-6': { original: require('./brand-6/original_48.png') }, + 'brand-7': { original: require('./brand-7/original_48.png') }, + 'brand-8': { original: require('./brand-8/original_48.png') }, +}; \ No newline at end of file diff --git a/assets/icons/guardia/panel-01/original_16.png b/assets/icons/guardia/panel-01/original_16.png new file mode 100644 index 00000000..675b9efc Binary files /dev/null and b/assets/icons/guardia/panel-01/original_16.png differ diff --git a/assets/icons/guardia/panel-01/original_24.png b/assets/icons/guardia/panel-01/original_24.png new file mode 100644 index 00000000..4292770e Binary files /dev/null and b/assets/icons/guardia/panel-01/original_24.png differ diff --git a/assets/icons/guardia/panel-01/original_32.png b/assets/icons/guardia/panel-01/original_32.png new file mode 100644 index 00000000..250eaa99 Binary files /dev/null and b/assets/icons/guardia/panel-01/original_32.png differ diff --git a/assets/icons/guardia/panel-01/original_48.png b/assets/icons/guardia/panel-01/original_48.png new file mode 100644 index 00000000..c3162343 Binary files /dev/null and b/assets/icons/guardia/panel-01/original_48.png differ diff --git a/assets/icons/guardia/panel-01/original_64.png b/assets/icons/guardia/panel-01/original_64.png new file mode 100644 index 00000000..96b624c1 Binary files /dev/null and b/assets/icons/guardia/panel-01/original_64.png differ diff --git a/assets/icons/guardia/panel-02/original_16.png b/assets/icons/guardia/panel-02/original_16.png new file mode 100644 index 00000000..c7fdf1e5 Binary files /dev/null and b/assets/icons/guardia/panel-02/original_16.png differ diff --git a/assets/icons/guardia/panel-02/original_24.png b/assets/icons/guardia/panel-02/original_24.png new file mode 100644 index 00000000..b73d098e Binary files /dev/null and b/assets/icons/guardia/panel-02/original_24.png differ diff --git a/assets/icons/guardia/panel-02/original_32.png b/assets/icons/guardia/panel-02/original_32.png new file mode 100644 index 00000000..d6fa4c3c Binary files /dev/null and b/assets/icons/guardia/panel-02/original_32.png differ diff --git a/assets/icons/guardia/panel-02/original_48.png b/assets/icons/guardia/panel-02/original_48.png new file mode 100644 index 00000000..436b751c Binary files /dev/null and b/assets/icons/guardia/panel-02/original_48.png differ diff --git a/assets/icons/guardia/panel-02/original_64.png b/assets/icons/guardia/panel-02/original_64.png new file mode 100644 index 00000000..c3330845 Binary files /dev/null and b/assets/icons/guardia/panel-02/original_64.png differ diff --git a/components/AlertChannelBadge.tsx b/components/AlertChannelBadge.tsx new file mode 100644 index 00000000..b83d5738 --- /dev/null +++ b/components/AlertChannelBadge.tsx @@ -0,0 +1,53 @@ +/** + * AlertChannelBadge (#41) — 알림 채널 표시 뱃지 + * 알림 규칙·서비스 상태 화면에서 채널(인앱/이메일/SMS/푸시/묵음)을 시각적으로 표시. + * react-native-svg 미사용 (View + StyleSheet only, 폐쇄망 호환). + */ +import { View, Text, StyleSheet } from 'react-native' + +export type AlertChannel = 'inapp' | 'email' | 'sms' | 'push' | 'mute' + +interface ChannelMeta { label: string; short: string; color: string; bg: string } + +const CHANNELS: Record = { + inapp: { label: '인앱', short: 'IN', color: '#00A0C8', bg: 'rgba(0,160,200,.12)' }, + push: { label: '푸시', short: 'PU', color: '#4f6ef7', bg: 'rgba(79,110,247,.12)' }, + email: { label: '이메일', short: '@', color: '#8b5cf6', bg: 'rgba(139,92,246,.12)' }, + sms: { label: 'SMS', short: 'SMS', color: '#f59e0b', bg: 'rgba(245,158,11,.12)' }, + mute: { label: '묵음', short: '🔕', color: '#94a3b8', bg: 'rgba(148,163,184,.15)' }, +} + +interface Props { + channel: string + size?: 'sm' | 'md' +} + +export default function AlertChannelBadge({ channel, size = 'md' }: Props) { + const meta = CHANNELS[channel?.toLowerCase()] ?? CHANNELS.inapp + const sm = size === 'sm' + return ( + + + {meta.label} + + ) +} + +/** 여러 채널을 한 줄로 표시 */ +export function AlertChannelRow({ channels }: { channels: string[] }) { + return ( + + {channels.map((c, i) => )} + + ) +} + +const s = StyleSheet.create({ + badge: { flexDirection: 'row', alignItems: 'center', gap: 5, borderRadius: 12, alignSelf: 'flex-start' }, + dot: { borderRadius: 4 }, + txt: { fontWeight: '700' }, + row: { flexDirection: 'row', gap: 5, flexWrap: 'wrap' }, +}) diff --git a/components/ApprovalStages.tsx b/components/ApprovalStages.tsx new file mode 100644 index 00000000..1154febd --- /dev/null +++ b/components/ApprovalStages.tsx @@ -0,0 +1,117 @@ +import { useEffect, useState } from 'react' +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS } from '../constants/Config' +import { getApprovalStages } from '../services/api' + +/** + * 기능 #65 — 다단계 승인 진행 시각화 + * - GET /api/approvals/{id}/stages + * - 단계별 타임라인: 단계명, 승인자, 상태, 시각 + * - 완료(초록 체크) / 현재(파란 점, 하이라이트) / 대기(회색 원) / 반려(빨강) + */ + +export interface ApprovalStage { + level: number + name?: string + approver: string + status: 'approved' | 'pending' | 'rejected' | 'current' | 'waiting' + acted_at?: string | null +} + +interface Props { + approvalId: string | number + /** 이미 보유한 단계 데이터가 있으면 fetch 생략 */ + stages?: ApprovalStage[] +} + +const STATUS_META: Record = { + approved: { color: COLORS.success, mark: '✓', label: '승인' }, + rejected: { color: COLORS.danger, mark: '✕', label: '반려' }, + current: { color: COLORS.accent, mark: '●', label: '진행중' }, + pending: { color: '#cbd5e1', mark: '○', label: '대기' }, + waiting: { color: '#cbd5e1', mark: '○', label: '대기' }, +} + +export default function ApprovalStages({ approvalId, stages: preset }: Props) { + const [stages, setStages] = useState(preset ?? []) + const [loading, setLoading] = useState(!preset) + + useEffect(() => { + if (preset) { setStages(preset); return } + let alive = true + ;(async () => { + setLoading(true) + try { + const r = await getApprovalStages(approvalId) + if (alive) setStages(r.data?.stages ?? r.data ?? []) + } catch { /* 무시 */ } + finally { if (alive) setLoading(false) } + })() + return () => { alive = false } + }, [approvalId, preset]) + + if (loading) return + if (!stages.length) return 승인 단계 정보가 없습니다. + + return ( + + {stages.map((st, idx) => { + const meta = STATUS_META[st.status] ?? STATUS_META.pending + const isCurrent = st.status === 'current' + const isLast = idx === stages.length - 1 + return ( + + + + + {meta.mark} + + + {!isLast && } + + + + + {st.level}단계 · {st.name ?? `승인 ${st.level}`} + + + {meta.label} + + + 승인자: {st.approver} + {!!st.acted_at && {formatTime(st.acted_at)}} + + + ) + })} + + ) +} + +function formatTime(ts: string): string { + try { + const d = new Date(ts) + return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` + } catch { return ts } +} + +const s = StyleSheet.create({ + empty: { textAlign: 'center', color: COLORS.muted, paddingVertical: 16, fontSize: 12 }, + row: { flexDirection: 'row' }, + gutter: { width: 30, alignItems: 'center' }, + node: { width: 24, height: 24, borderRadius: 12, borderWidth: 2, + alignItems: 'center', justifyContent: 'center', backgroundColor: '#fff' }, + nodeMark: { fontSize: 12, fontWeight: '800', color: '#fff' }, + line: { flex: 1, width: 2, backgroundColor: COLORS.border, marginVertical: 2 }, + content: { flex: 1, paddingBottom: 16, paddingLeft: 8 }, + contentCurrent: { backgroundColor: COLORS.light, borderRadius: 8, padding: 8, marginLeft: 4, marginBottom: 12 }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + stageName: { fontSize: 13, fontWeight: '700', color: COLORS.text, flex: 1 }, + badge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 9 }, + badgeText: { fontSize: 10, fontWeight: '700', color: '#fff' }, + approver: { fontSize: 12, color: COLORS.blue, marginTop: 3 }, + time: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, +}) diff --git a/components/AutoRCA.tsx b/components/AutoRCA.tsx new file mode 100644 index 00000000..195d7bcf --- /dev/null +++ b/components/AutoRCA.tsx @@ -0,0 +1,125 @@ +/** + * AutoRCA (#21) — 자동 근본원인분석(RCA) 표시 + * + * GET /api/ai/rca/{incident_id} 우선 시도, 실패 시 Ollama로 직접 분석. + * 원인 / 영향 범위 / 재발 방지 3섹션을 접을 수 있는 아코디언으로 표시. + */ +import { useState, useEffect } from 'react' +import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS, API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' +import { generateJSON, DEFAULT_TEXT_MODEL } from '../lib/ollama' + +interface RCA { + cause: string + impact: string + prevention: string +} + +interface Props { + incidentId: number | string + summary?: string // Ollama 폴백용 인시던트 요약 (자격증명 미포함) +} + +const EMPTY: RCA = { cause: '', impact: '', prevention: '' } + +export function AutoRCA({ incidentId, summary }: Props) { + const [rca, setRca] = useState(EMPTY) + const [loading, setLoading] = useState(true) + const [open, setOpen] = useState<'cause' | 'impact' | 'prevention' | null>('cause') + const [src, setSrc] = useState<'api' | 'ai' | 'none'>('none') + + useEffect(() => { + let alive = true + ;(async () => { + setLoading(true) + // 1) ITSM API 시도 + try { + const res = await authFetch(`${API_BASE}/api/ai/rca/${incidentId}`) + if (res.ok) { + const d = await res.json() + const parsed: RCA = { + cause: d.cause ?? d.root_cause ?? '', + impact: d.impact ?? d.impact_scope ?? '', + prevention: d.prevention ?? d.recurrence_prevention ?? '', + } + if (alive && (parsed.cause || parsed.impact || parsed.prevention)) { + setRca(parsed) + setSrc('api') + setLoading(false) + return + } + } + } catch { + /* API 실패 → Ollama 폴백 */ + } + // 2) Ollama 폴백 + if (summary?.trim()) { + const prompt = + `다음 IT 인시던트에 대해 근본원인분석(RCA)을 수행하세요: "${summary}". ` + + `JSON으로만 출력: {"cause":"근본원인","impact":"영향범위","prevention":"재발방지책"}` + const aiRca = await generateJSON(DEFAULT_TEXT_MODEL, prompt, EMPTY) + if (alive) { + setRca(aiRca) + setSrc(aiRca.cause ? 'ai' : 'none') + } + } + if (alive) setLoading(false) + })() + return () => { + alive = false + } + }, [incidentId, summary]) + + const sections: { key: 'cause' | 'impact' | 'prevention'; label: string; icon: string }[] = [ + { key: 'cause', label: '근본 원인', icon: '🔍' }, + { key: 'impact', label: '영향 범위', icon: '🌐' }, + { key: 'prevention', label: '재발 방지', icon: '🛡️' }, + ] + + if (loading) { + return ( + + + RCA 분석 중... + + ) + } + + return ( + + + 🧩 자동 RCA + {src === 'api' ? 'ITSM 분석' : src === 'ai' ? 'AI 분석' : '데이터 없음'} + + {sections.map(sec => ( + + setOpen(open === sec.key ? null : sec.key)}> + + {sec.icon} {sec.label} + + {open === sec.key ? '▲' : '▼'} + + {open === sec.key ? ( + {rca[sec.key] || '분석 결과가 없습니다.'} + ) : null} + + ))} + + ) +} + +export default AutoRCA + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }, + title: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + badge: { fontSize: 10, color: COLORS.blue, backgroundColor: COLORS.light, paddingHorizontal: 8, paddingVertical: 3, borderRadius: 8, overflow: 'hidden' }, + loadingText: { fontSize: 12, color: COLORS.muted, marginTop: 6 }, + accItem: { borderTopWidth: 1, borderTopColor: COLORS.border }, + accHead: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 11 }, + accLabel: { fontSize: 13, fontWeight: '600', color: COLORS.text }, + accArrow: { fontSize: 10, color: COLORS.muted }, + accBody: { fontSize: 13, color: COLORS.text, lineHeight: 19, paddingBottom: 12, paddingLeft: 4 }, +}) diff --git a/components/Comment.tsx b/components/Comment.tsx new file mode 100644 index 00000000..20263f98 --- /dev/null +++ b/components/Comment.tsx @@ -0,0 +1,124 @@ +import { useEffect, useState } from 'react' +import { + View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, +} from 'react-native' +import { COLORS } from '../constants/Config' +import { getSRComments, addSRComment } from '../services/api' + +interface CommentItem { + id: number | string + content: string + author?: string + is_internal?: boolean + created_at?: string +} + +interface Props { + srId: number +} + +/** + * 기능 #13 — 내부/외부 코멘트 구분 컴포넌트 + * 내부: 자물쇠(담당자 전용), 외부: 지구본(기관 공개) + * POST /api/tasks/{id}/comments { content, is_internal } + */ +export default function Comment({ srId }: Props) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [text, setText] = useState('') + const [isInternal, setIsInternal] = useState(true) + const [sending, setSending] = useState(false) + + const load = async () => { + try { + const res = await getSRComments(srId) + setItems(res.data?.content ?? res.data?.items ?? res.data ?? []) + } catch { setItems([]) } + finally { setLoading(false) } + } + + useEffect(() => { load() }, [srId]) + + const submit = async () => { + if (!text.trim()) return + setSending(true) + try { + await addSRComment(srId, text.trim(), isInternal) + setText('') + await load() + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? '코멘트 등록 실패') + } finally { setSending(false) } + } + + return ( + + {loading ? ( + + ) : items.length === 0 ? ( + 코멘트가 없습니다. + ) : ( + items.map(c => ( + + + {c.is_internal ? '🔒 내부' : '🌐 외부'} + {c.author ?? '담당자'} + + {c.content} + {!!c.created_at && {c.created_at.slice(0, 16).replace('T', ' ')}} + + )) + )} + + {/* 입력 영역 */} + + setIsInternal(true)} + > + 🔒 내부 + + setIsInternal(false)} + > + 🌐 외부 + + + + + + {sending ? : 등록} + + + + ) +} + +const s = StyleSheet.create({ + empty: { color: COLORS.muted, fontSize: 12, paddingVertical: 8 }, + bubble: { borderRadius: 10, padding: 12, marginBottom: 8, borderLeftWidth: 3 }, + internal: { backgroundColor: '#FFF7ED', borderLeftColor: COLORS.warning }, + external: { backgroundColor: COLORS.light, borderLeftColor: COLORS.accent }, + bubbleHead: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 4 }, + tag: { fontSize: 11, fontWeight: '700', color: COLORS.text }, + author: { fontSize: 11, color: COLORS.muted }, + content: { fontSize: 13, color: COLORS.text, lineHeight: 18 }, + time: { fontSize: 10, color: COLORS.muted, marginTop: 4 }, + toggleRow: { flexDirection: 'row', gap: 8, marginTop: 12, marginBottom: 8 }, + toggle: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 16, borderWidth: 1, borderColor: COLORS.border }, + toggleActive: { backgroundColor: COLORS.primary, borderColor: COLORS.primary }, + toggleText: { fontSize: 12, color: COLORS.text }, + toggleTextActive: { color: '#fff', fontWeight: '700' }, + inputRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 8 }, + input: { flex: 1, borderWidth: 1.5, borderColor: COLORS.border, borderRadius: 9, padding: 10, fontSize: 13, color: COLORS.text, maxHeight: 100 }, + sendBtn: { backgroundColor: COLORS.accent, borderRadius: 9, paddingHorizontal: 16, paddingVertical: 11 }, + sendText: { color: '#fff', fontSize: 13, fontWeight: '700' }, +}) diff --git a/components/DailySummary.tsx b/components/DailySummary.tsx new file mode 100644 index 00000000..f01e89ea --- /dev/null +++ b/components/DailySummary.tsx @@ -0,0 +1,135 @@ +/** + * DailySummary (#26) — 일일 AI 요약 카드 + * + * GET /api/tasks/stats/mine?period=today → Ollama로 자연어 요약 생성. + * 매일 1회 갱신 (SecureStore 캐시, 날짜 키 비교). 홈 대시보드 상단 카드. + */ +import { useState, useEffect } from 'react' +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' +import * as SecureStore from 'expo-secure-store' +import { COLORS, API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' +import { generate, DEFAULT_TEXT_MODEL } from '../lib/ollama' + +const CACHE_KEY = 'grd_daily_summary' + +interface Stats { + processed?: number + pending?: number + sla_risk?: number +} + +function todayKey(): string { + return new Date().toISOString().slice(0, 10) +} + +export function DailySummary() { + const [summary, setSummary] = useState('') + const [stats, setStats] = useState({}) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let alive = true + ;(async () => { + // 1) 캐시 확인 (오늘 날짜) + try { + const cached = await SecureStore.getItemAsync(CACHE_KEY) + if (cached) { + const c = JSON.parse(cached) + if (c.date === todayKey() && c.summary) { + if (alive) { + setSummary(c.summary) + setStats(c.stats ?? {}) + setLoading(false) + } + return + } + } + } catch { + /* 캐시 무시 */ + } + + // 2) 통계 조회 + let s: Stats = {} + try { + const res = await authFetch(`${API_BASE}/api/tasks/stats/mine?period=today`) + if (res.ok) { + const d = await res.json() + s = { + processed: d.processed ?? d.completed ?? d.done ?? 0, + pending: d.pending ?? d.open ?? d.in_progress ?? 0, + sla_risk: d.sla_risk ?? d.at_risk ?? 0, + } + } + } catch { + /* 통계 실패 → 기본값 */ + } + if (alive) setStats(s) + + // 3) Ollama 요약 + const prompt = + `다음 ITSM 일일 통계를 운영자에게 보고하듯 한국어 한 문장으로 요약하세요. ` + + `처리: ${s.processed ?? 0}건, 미처리: ${s.pending ?? 0}건, SLA 위험: ${s.sla_risk ?? 0}건. ` + + `격려 한마디 포함.` + const aiText = await generate(DEFAULT_TEXT_MODEL, prompt) + const finalText = + aiText || + `오늘 처리 ${s.processed ?? 0}건, 미처리 ${s.pending ?? 0}건, SLA 위험 ${s.sla_risk ?? 0}건입니다.` + if (alive) { + setSummary(finalText) + setLoading(false) + } + // 4) 캐시 저장 + try { + await SecureStore.setItemAsync( + CACHE_KEY, + JSON.stringify({ date: todayKey(), summary: finalText, stats: s }), + ) + } catch { + /* 캐시 저장 실패 무시 */ + } + })() + return () => { + alive = false + } + }, []) + + return ( + + 🤖 오늘의 AI 브리핑 + {loading ? ( + + ) : ( + <> + {summary} + + + + + + > + )} + + ) +} + +function Stat({ label, value, color }: { label: string; value: number; color: string }) { + return ( + + {value} + {label} + + ) +} + +export default DailySummary + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.gnbBg, borderRadius: 16, padding: 16, margin: 12 }, + title: { fontSize: 13, fontWeight: '700', color: 'rgba(255,255,255,0.85)' }, + summary: { fontSize: 14, color: '#fff', lineHeight: 20, marginTop: 8 }, + statsRow: { flexDirection: 'row', marginTop: 14, gap: 8 }, + stat: { flex: 1, backgroundColor: 'rgba(255,255,255,0.08)', borderRadius: 10, paddingVertical: 10, alignItems: 'center' }, + statVal: { fontSize: 20, fontWeight: '800' }, + statLabel: { fontSize: 11, color: 'rgba(255,255,255,0.7)', marginTop: 2 }, +}) diff --git a/components/ImpactPreview.tsx b/components/ImpactPreview.tsx new file mode 100644 index 00000000..7e00bd5b --- /dev/null +++ b/components/ImpactPreview.tsx @@ -0,0 +1,118 @@ +/** + * ImpactPreview (#27) — 장애 영향 서비스 예측 시각화 + * + * GET /api/ai/impact/{server_id} 우선, 실패 시 /api/servers/{id}/dependencies. + * 영향받는 서비스 목록 + 연결 관계를 텍스트 기반 리스트로 표시. + */ +import { useState, useEffect } from 'react' +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS, API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' + +interface ImpactNode { + name: string + type?: string // service / db / app ... + severity?: 'high' | 'medium' | 'low' | string + relation?: string // 'depends_on' 등 +} + +interface Props { + serverId: number | string + serverName?: string +} + +const SEV_COLOR: Record = { + high: COLORS.danger, + medium: COLORS.warning, + low: COLORS.success, +} + +export function ImpactPreview({ serverId, serverName }: Props) { + const [nodes, setNodes] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let alive = true + ;(async () => { + setLoading(true) + const endpoints = [ + `${API_BASE}/api/ai/impact/${serverId}`, + `${API_BASE}/api/servers/${serverId}/dependencies`, + ] + for (const url of endpoints) { + try { + const res = await authFetch(url) + if (res.ok) { + const d = await res.json() + const raw = Array.isArray(d) ? d : d.impacted ?? d.services ?? d.dependencies ?? d.nodes ?? [] + const list: ImpactNode[] = raw.map((x: any) => ({ + name: x.name ?? x.service_name ?? x.hostname ?? String(x), + type: x.type ?? x.kind, + severity: x.severity ?? x.impact, + relation: x.relation ?? x.relationship, + })) + if (alive && list.length) { + setNodes(list) + setLoading(false) + return + } + } + } catch { + /* 다음 엔드포인트 시도 */ + } + } + if (alive) { + setNodes([]) + setLoading(false) + } + })() + return () => { + alive = false + } + }, [serverId]) + + return ( + + 🌐 장애 영향도 예측 + {serverName ? 출발: {serverName} : null} + + {loading ? ( + + ) : nodes.length === 0 ? ( + 영향 데이터를 불러올 수 없습니다. + ) : ( + + {nodes.map((n, i) => ( + + + + {n.name} + + {n.type ? `${n.type} · ` : ''} + {n.relation ?? '연결됨'} + {n.severity ? ` · ${n.severity}` : ''} + + + + ))} + ※ {nodes.length}개 서비스가 영향을 받을 수 있습니다. + + )} + + ) +} + +export default ImpactPreview + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + title: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + root: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + empty: { fontSize: 12, color: COLORS.muted, marginTop: 8 }, + list: { marginTop: 10 }, + node: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', gap: 10 }, + dot: { width: 10, height: 10, borderRadius: 5 }, + nodeName: { fontSize: 13, fontWeight: '600', color: COLORS.text }, + nodeMeta: { fontSize: 11, color: COLORS.muted, marginTop: 2 }, + note: { fontSize: 11, color: COLORS.danger, marginTop: 8 }, +}) diff --git a/components/IncidentTimeline.tsx b/components/IncidentTimeline.tsx new file mode 100644 index 00000000..caf48e80 --- /dev/null +++ b/components/IncidentTimeline.tsx @@ -0,0 +1,82 @@ +import { View, Text, FlatList, StyleSheet } from 'react-native' +import { COLORS } from '../constants/Config' + +export interface TimelineEvent { + timestamp: string + actor: string + action: string + detail?: string +} + +interface Props { + events: TimelineEvent[] +} + +const ACTION_COLOR: Record = { + created: COLORS.accent, + assigned: COLORS.blue, + status: '#4f6ef7', + escalated: COLORS.danger, + comment: COLORS.muted, + resolved: COLORS.success, + closed: COLORS.success, +} + +/** + * 기능 #6 — 인시던트 타임라인 + * 수직 타임라인 UI (점 + 연결선 + 이벤트 카드) + */ +export default function IncidentTimeline({ events }: Props) { + if (!events || events.length === 0) { + return 타임라인 이벤트가 없습니다. + } + + return ( + String(i)} + renderItem={({ item, index }) => { + const color = ACTION_COLOR[item.action?.toLowerCase()] ?? COLORS.accent + const isLast = index === events.length - 1 + return ( + + + + {!isLast && } + + + + {item.action} + {formatTime(item.timestamp)} + + {item.actor} + {!!item.detail && {item.detail}} + + + ) + }} + /> + ) +} + +function formatTime(ts: string): string { + try { + const d = new Date(ts) + return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` + } catch { return ts } +} + +const s = StyleSheet.create({ + empty: { textAlign: 'center', color: COLORS.muted, paddingVertical: 24, fontSize: 13 }, + row: { flexDirection: 'row' }, + gutter: { width: 24, alignItems: 'center' }, + dot: { width: 12, height: 12, borderRadius: 6, marginTop: 4 }, + line: { flex: 1, width: 2, backgroundColor: COLORS.border, marginTop: 2 }, + content: { flex: 1, paddingBottom: 18, paddingLeft: 8 }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + action: { fontSize: 13, fontWeight: '700', color: COLORS.text, textTransform: 'capitalize' }, + time: { fontSize: 11, color: COLORS.muted }, + actor: { fontSize: 12, color: COLORS.blue, marginTop: 2 }, + detail: { fontSize: 12, color: COLORS.muted, marginTop: 4, lineHeight: 17 }, +}) diff --git a/components/KBSuggest.tsx b/components/KBSuggest.tsx new file mode 100644 index 00000000..fb882e2a --- /dev/null +++ b/components/KBSuggest.tsx @@ -0,0 +1,93 @@ +/** + * KBSuggest (#23) — SR 입력 중 실시간 KB 추천 + * + * query(SR 제목 입력값) → debounce 600ms → GET /api/kb/?q={query}&limit=3 + * 최대 3개 KB 카드 표시, 탭 → onOpen(KB id) 콜백 (상세 화면 이동). + */ +import { useState, useEffect, useRef } from 'react' +import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS, API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' + +interface KBItem { + id: number + title: string + summary?: string + category?: string +} + +interface Props { + query: string + onOpen?: (id: number) => void +} + +export function KBSuggest({ query, onOpen }: Props) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + const timer = useRef | null>(null) + + useEffect(() => { + if (timer.current) clearTimeout(timer.current) + const q = query?.trim() ?? '' + if (q.length < 2) { + setItems([]) + return + } + timer.current = setTimeout(async () => { + setLoading(true) + try { + const res = await authFetch(`${API_BASE}/api/kb/?q=${encodeURIComponent(q)}&limit=3`) + if (res.ok) { + const d = await res.json() + const list: KBItem[] = Array.isArray(d) ? d : d.items ?? d.results ?? [] + setItems(list.slice(0, 3)) + } else { + setItems([]) + } + } catch { + setItems([]) + } finally { + setLoading(false) + } + }, 600) + return () => { + if (timer.current) clearTimeout(timer.current) + } + }, [query]) + + if (!loading && items.length === 0) return null + + return ( + + 📚 관련 지식베이스 + {loading ? ( + + ) : ( + items.map(kb => ( + onOpen?.(kb.id)}> + + {kb.title} + + {kb.summary ? ( + + {kb.summary} + + ) : null} + {kb.category ? {kb.category} : null} + + )) + )} + + ) +} + +export default KBSuggest + +const S = StyleSheet.create({ + wrap: { marginTop: 8 }, + title: { fontSize: 12, fontWeight: '700', color: COLORS.muted, marginBottom: 6 }, + card: { backgroundColor: COLORS.light, borderRadius: 10, padding: 11, marginBottom: 6 }, + cardTitle: { fontSize: 13, fontWeight: '600', color: COLORS.blue }, + cardSummary: { fontSize: 12, color: COLORS.text, marginTop: 3, lineHeight: 17 }, + cardCat: { fontSize: 10, color: COLORS.muted, marginTop: 4 }, +}) diff --git a/components/MarkdownViewer.tsx b/components/MarkdownViewer.tsx new file mode 100644 index 00000000..17ac0e44 --- /dev/null +++ b/components/MarkdownViewer.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { View, Text, ScrollView, StyleSheet } from 'react-native' +import { COLORS } from '../constants/Config' + +interface Props { content: string; style?: object } + +export default function MarkdownViewer({ content, style }: Props) { + const lines = content.split('\n') + return ( + + {lines.map((line, i) => { + if (line.startsWith('### ')) return {line.slice(4)} + if (line.startsWith('## ')) return {line.slice(3)} + if (line.startsWith('# ')) return {line.slice(2)} + if (line.startsWith('---')) return + if (line.startsWith('> ')) return {line.slice(2)} + if (line.startsWith('- ') || line.startsWith('* ')) return {' • '}{renderInline(line.slice(2))} + if (/^\d+\. /.test(line)) { + const [num, ...rest] = line.split('. ') + return {` ${num}. `}{renderInline(rest.join('. '))} + } + if (line.startsWith('```')) return {line.slice(3)} + if (line === '') return + return {renderInline(line)} + })} + + ) +} + +function renderInline(text: string): string { + return text + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/`(.+?)`/g, '$1') + .replace(/_(.+?)_/g, '$1') +} + +const s = StyleSheet.create({ + wrap: { flex: 1 }, + h1: { fontSize: 20, fontWeight: '800', color: COLORS.text, marginVertical: 8 }, + h2: { fontSize: 17, fontWeight: '700', color: COLORS.text, marginVertical: 6 }, + h3: { fontSize: 15, fontWeight: '700', color: COLORS.primary, marginVertical: 4 }, + body: { fontSize: 14, color: COLORS.text, lineHeight: 22, marginVertical: 1 }, + li: { fontSize: 14, color: COLORS.text, lineHeight: 22 }, + hr: { height: 1, backgroundColor: COLORS.border, marginVertical: 8 }, + blockquote: { borderLeftWidth: 3, borderLeftColor: COLORS.accent, paddingLeft: 10, marginVertical: 4 }, + bqText: { fontSize: 13, color: COLORS.muted, fontStyle: 'italic' }, + codeBlock: { backgroundColor: '#1E293B', borderRadius: 6, padding: 10, marginVertical: 6 }, + code: { fontSize: 12, color: '#E2E8F0', fontFamily: 'monospace' }, +}) diff --git a/components/MetricGraph.tsx b/components/MetricGraph.tsx new file mode 100644 index 00000000..edef7882 --- /dev/null +++ b/components/MetricGraph.tsx @@ -0,0 +1,172 @@ +/** + * MetricGraph (#47) — 실시간 메트릭 꺾은선 그래프 + * + * react-native-svg가 설치되어 있지 않은 폐쇄망 호환 환경이므로 + * SVG 대신 View + StyleSheet로 꺾은선(세그먼트 회전)을 직접 렌더링한다. + * 각 데이터 포인트 사이를 회전된 얇은 View(선분)로 연결. + * + * 범례: CPU(파랑) / MEM(초록) / DISK(주황) + * X축: 시간, Y축: 0~100% + */ +import { View, Text, StyleSheet, LayoutChangeEvent } from 'react-native' +import { useState } from 'react' + +export interface MetricPoint { + timestamp: string + cpu: number + memory: number + disk: number +} + +interface SeriesDef { key: keyof Omit; label: string; color: string } + +const SERIES: SeriesDef[] = [ + { key: 'cpu', label: 'CPU', color: '#3b82f6' }, + { key: 'memory', label: 'MEM', color: '#22c55e' }, + { key: 'disk', label: 'DISK', color: '#f59e0b' }, +] + +const CHART_H = 160 +const PAD_BOTTOM = 22 // X축 라벨 영역 + +/** 두 점을 잇는 선분(회전 View) 생성 */ +function Segment({ + x1, y1, x2, y2, color, +}: { x1: number; y1: number; x2: number; y2: number; color: string }) { + const dx = x2 - x1 + const dy = y2 - y1 + const length = Math.sqrt(dx * dx + dy * dy) + const angle = Math.atan2(dy, dx) * (180 / Math.PI) + return ( + + ) +} + +export default function MetricGraph({ + data, + title, +}: { data: MetricPoint[]; title?: string }) { + const [width, setWidth] = useState(0) + const onLayout = (e: LayoutChangeEvent) => setWidth(e.nativeEvent.layout.width) + + const plotH = CHART_H - PAD_BOTTOM + const n = data.length + + // y(0~100%) -> 화면 좌표 (0% 하단, 100% 상단) + const toY = (v: number) => plotH - (Math.max(0, Math.min(100, v)) / 100) * plotH + const toX = (i: number) => (n <= 1 ? 0 : (i / (n - 1)) * width) + + const xLabels = (() => { + if (n === 0) return [] + const fmt = (ts: string) => { + const d = new Date(ts) + if (isNaN(d.getTime())) return ts + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}` + } + const first = data[0], last = data[n - 1], mid = data[Math.floor(n / 2)] + return [fmt(first.timestamp), n > 2 ? fmt(mid.timestamp) : '', fmt(last.timestamp)] + })() + + return ( + + {title && {title}} + + {/* 범례 */} + + {SERIES.map(se => ( + + + {se.label} + + ))} + + + + {/* Y축 라벨 */} + + {[100, 75, 50, 25, 0].map(v => ( + {v} + ))} + + + {/* 차트 영역 */} + + {/* 가로 격자선 */} + {[0, 0.25, 0.5, 0.75, 1].map(g => ( + + ))} + + {width > 0 && n >= 1 && SERIES.map(se => + data.map((p, i) => { + if (i === n - 1) return null + return ( + + ) + }) + )} + + {/* 데이터 포인트 점 */} + {width > 0 && SERIES.map(se => + data.map((p, i) => ( + + )) + )} + + {n === 0 && ( + 데이터 없음 + )} + + + + {/* X축 라벨 */} + + {xLabels.map((l, i) => ( + {l} + ))} + + + ) +} + +const s = StyleSheet.create({ + wrap: { backgroundColor: '#fff', borderRadius: 12, padding: 14 }, + title: { fontSize: 13, fontWeight: '700', color: '#1e293b', marginBottom: 8 }, + legend: { flexDirection: 'row', gap: 14, marginBottom: 10 }, + legendItem: { flexDirection: 'row', alignItems: 'center', gap: 5 }, + legendDot: { width: 10, height: 10, borderRadius: 5 }, + legendTxt: { fontSize: 11, color: '#64748b', fontWeight: '600' }, + chartRow: { flexDirection: 'row' }, + yAxis: { width: 26, height: CHART_H - PAD_BOTTOM, justifyContent: 'space-between', alignItems: 'flex-end', paddingRight: 4 }, + yLabel: { fontSize: 9, color: '#cbd5e1' }, + plot: { flex: 1, height: CHART_H - PAD_BOTTOM, position: 'relative', overflow: 'hidden' }, + grid: { position: 'absolute', left: 0, right: 0, height: 1, backgroundColor: '#f1f5f9' }, + xAxis: { flexDirection: 'row', justifyContent: 'space-between', marginLeft: 26, marginTop: 4 }, + xLabel: { fontSize: 9, color: '#94a3b8' }, + empty: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + emptyTxt: { color: '#cbd5e1', fontSize: 12 }, +}) diff --git a/components/NextActions.tsx b/components/NextActions.tsx new file mode 100644 index 00000000..1611ce30 --- /dev/null +++ b/components/NextActions.tsx @@ -0,0 +1,79 @@ +/** + * NextActions (#20) — 다음 명령 3개 제안 + * + * 현재 컨텍스트(SR 상태/서버 상태 등)를 Ollama에 전달 → 다음 행동 3가지 JSON 배열. + * 버튼 3개로 표시, 탭하면 onSelect 콜백 호출. + * + * 보안: 컨텍스트에 자격증명/IP 포함 금지 (호출 측 책임). 온프레미스 Ollama만 사용. + */ +import { useState, useEffect } from 'react' +import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS } from '../constants/Config' +import { generateJSON, DEFAULT_TEXT_MODEL } from '../lib/ollama' + +interface Props { + context: string + onSelect: (action: string) => void +} + +const FALLBACK: string[] = ['KB 조회', '담당자에게 연락', '에스컬레이션'] + +export function NextActions({ context, onSelect }: Props) { + const [actions, setActions] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + let alive = true + ;(async () => { + if (!context?.trim()) { + setActions(FALLBACK) + return + } + setLoading(true) + const prompt = + `당신은 IT 운영 어시스턴트입니다. 운영자가 다음 상황에 있습니다: "${context}". ` + + `다음으로 취할 행동 3가지를 한국어 짧은 문구로, 설명 없이 JSON 배열만 출력하세요. ` + + `예: ["에스컬레이션","KB 조회","담당자 연락"]` + const result = await generateJSON(DEFAULT_TEXT_MODEL, prompt, FALLBACK) + if (!alive) return + const clean = Array.isArray(result) ? result.filter(x => typeof x === 'string').slice(0, 3) : FALLBACK + setActions(clean.length ? clean : FALLBACK) + setLoading(false) + })() + return () => { + alive = false + } + }, [context]) + + return ( + + ⚡ 다음 추천 작업 + {loading ? ( + + ) : ( + + {actions.map((a, i) => ( + onSelect(a)}> + + {a} + + + ))} + + )} + + ) +} + +export default NextActions + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 8 }, + row: { flexDirection: 'row', gap: 8 }, + chip: { + flex: 1, backgroundColor: COLORS.light, borderRadius: 10, paddingVertical: 12, + paddingHorizontal: 8, alignItems: 'center', justifyContent: 'center', + }, + chipText: { fontSize: 12, fontWeight: '600', color: COLORS.blue, textAlign: 'center' }, +}) diff --git a/components/PhotoDiagnosis.tsx b/components/PhotoDiagnosis.tsx new file mode 100644 index 00000000..3e505058 --- /dev/null +++ b/components/PhotoDiagnosis.tsx @@ -0,0 +1,139 @@ +/** + * PhotoDiagnosis (#19) — 사진 → Ollama llava → 장애 진단 + * + * expo-image-picker로 장비/에러화면 사진을 촬영/선택 → base64 변환 + * → generateWithImage('llava', ...) → 한국어 진단 결과 → onDiagnosis 콜백. + * + * 보안: 이미지는 온프레미스 Ollama(localhost:11434)로만 전송. 외부 API 미사용. + * 서버 전송 전 민감정보(비밀번호/IP 노출 화면 등) 검토 안내 표시. + */ +import { useState } from 'react' +import { View, Text, Pressable, StyleSheet, ActivityIndicator, Image } from 'react-native' +import { COLORS } from '../constants/Config' +import { generateWithImage, DEFAULT_VISION_MODEL } from '../lib/ollama' + +// expo-image-picker 선택적 임포트 (미설치 환경 폴백) +let ImagePicker: any = null +try { + ImagePicker = require('expo-image-picker') +} catch { + /* 폴백 */ +} + +interface Props { + onDiagnosis: (text: string) => void +} + +const DIAGNOSIS_PROMPT = + '당신은 IT 인프라 운영 전문가입니다. 이 사진은 서버/네트워크 장비 또는 시스템 화면입니다. ' + + '보이는 오류, 경고등, 에러 메시지, 이상 상태를 한국어로 진단하고 가능한 원인과 조치를 간결히 설명해 주세요. ' + + '추측이 필요하면 명시하세요.' + +export function PhotoDiagnosis({ onDiagnosis }: Props) { + const [uri, setUri] = useState(null) + const [result, setResult] = useState('') + const [loading, setLoading] = useState(false) + const [err, setErr] = useState('') + + async function pick(from: 'camera' | 'library') { + setErr('') + if (!ImagePicker) { + setErr('expo-image-picker 미설치 환경입니다.') + return + } + try { + const perm = + from === 'camera' + ? await ImagePicker.requestCameraPermissionsAsync() + : await ImagePicker.requestMediaLibraryPermissionsAsync() + if (!perm.granted) { + setErr('카메라/사진 접근 권한이 필요합니다.') + return + } + const opts = { base64: true, quality: 0.6, allowsEditing: true } + const res = + from === 'camera' + ? await ImagePicker.launchCameraAsync(opts) + : await ImagePicker.launchImageLibraryAsync(opts) + if (res.canceled || !res.assets?.[0]) return + const asset = res.assets[0] + setUri(asset.uri) + await diagnose(asset.base64 ?? '') + } catch { + setErr('사진을 불러오지 못했습니다.') + } + } + + async function diagnose(base64: string) { + if (!base64) { + setErr('이미지 데이터를 읽지 못했습니다.') + return + } + setLoading(true) + setResult('') + const text = await generateWithImage(DEFAULT_VISION_MODEL, DIAGNOSIS_PROMPT, base64) + setLoading(false) + if (!text) { + setErr('AI 진단 서버(Ollama)에 연결할 수 없습니다.') + return + } + setResult(text) + onDiagnosis(text) + } + + return ( + + 📷 사진 장애 진단 + 장비/에러 화면을 촬영하면 AI가 자동 진단합니다. + ⚠️ 화면에 비밀번호/내부 IP가 노출되지 않았는지 확인하세요. + + + pick('camera')}> + 촬영 + + pick('library')}> + 앨범에서 선택 + + + + {uri ? : null} + + {loading ? ( + + + AI가 사진을 분석 중입니다... + + ) : null} + + {result ? ( + + 진단 결과 + {result} + + ) : null} + + {err ? {err} : null} + + ) +} + +export default PhotoDiagnosis + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 16, borderWidth: 1, borderColor: COLORS.border }, + title: { fontSize: 15, fontWeight: '700', color: COLORS.text }, + hint: { fontSize: 12, color: COLORS.muted, marginTop: 4 }, + warn: { fontSize: 11, color: COLORS.warning, marginTop: 6 }, + btnRow: { flexDirection: 'row', gap: 8, marginTop: 12 }, + btn: { flex: 1, backgroundColor: COLORS.accent, borderRadius: 10, paddingVertical: 11, alignItems: 'center' }, + btnAlt: { backgroundColor: COLORS.light }, + btnText: { color: '#fff', fontWeight: '700', fontSize: 13 }, + btnAltText: { color: COLORS.blue }, + preview: { width: '100%', height: 180, borderRadius: 10, marginTop: 12, backgroundColor: '#eee' }, + loadingBox: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 12 }, + loadingText: { fontSize: 12, color: COLORS.muted }, + resultBox: { marginTop: 12, backgroundColor: COLORS.light, borderRadius: 10, padding: 12 }, + resultLabel: { fontSize: 12, fontWeight: '700', color: COLORS.blue, marginBottom: 6 }, + resultText: { fontSize: 13, color: COLORS.text, lineHeight: 19 }, + err: { fontSize: 12, color: COLORS.danger, marginTop: 10 }, +}) diff --git a/components/PinLock.tsx b/components/PinLock.tsx new file mode 100644 index 00000000..f8397214 --- /dev/null +++ b/components/PinLock.tsx @@ -0,0 +1,200 @@ +/** + * #30 PIN 코드 잠금 화면 + * + * - 4자리 PIN 숫자 패드 + * - PIN 저장: sha256(pin) → SecureStore 'grd_pin_hash' (평문 저장 금지) + * - 검증: sha256(입력) === 저장 해시 + * - 5회 실패 시 onFail() 호출 (세션 종료) + * + * 사용: + * // PIN 신규 설정 (2회 확인) + * // 잠금 해제 + */ +import { useState } from 'react' +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native' +import * as SecureStore from 'expo-secure-store' +import { sha256 } from '../services/sha256' +import { COLORS } from '../constants/Config' + +const PIN_HASH_KEY = 'grd_pin_hash' +const PIN_LENGTH = 4 +const MAX_ATTEMPTS = 5 + +export const PIN_ENABLED_KEY = 'grd_pin_enabled' + +export async function isPinSet(): Promise { + const h = await SecureStore.getItemAsync(PIN_HASH_KEY) + return !!h +} + +export async function savePin(pin: string): Promise { + // 평문 저장 절대 금지 — SHA-256 해시만 저장 + const hash = sha256(pin) + await SecureStore.setItemAsync(PIN_HASH_KEY, hash) + await SecureStore.setItemAsync(PIN_ENABLED_KEY, '1') +} + +export async function verifyPin(pin: string): Promise { + const stored = await SecureStore.getItemAsync(PIN_HASH_KEY) + if (!stored) return false + return sha256(pin) === stored +} + +export async function clearPin(): Promise { + await SecureStore.deleteItemAsync(PIN_HASH_KEY) + await SecureStore.deleteItemAsync(PIN_ENABLED_KEY) +} + +export async function isPinEnabled(): Promise { + const v = await SecureStore.getItemAsync(PIN_ENABLED_KEY) + return v === '1' && (await isPinSet()) +} + +type Mode = 'set' | 'verify' + +interface Props { + mode: Mode + onSuccess: () => void + onFail?: () => void + onCancel?: () => void +} + +export default function PinLock({ mode, onSuccess, onFail, onCancel }: Props) { + const [pin, setPin] = useState('') + const [firstPin, setFirstPin] = useState(null) // set 모드 확인용 + const [attempts, setAttempts] = useState(0) + const [error, setError] = useState('') + + const title = + mode === 'set' + ? firstPin == null + ? 'PIN 설정' + : 'PIN 재입력' + : 'PIN 입력' + const subtitle = + mode === 'set' + ? firstPin == null + ? '4자리 PIN을 설정하세요' + : '확인을 위해 다시 입력하세요' + : '잠금을 해제하려면 PIN을 입력하세요' + + const handleDigit = async (d: string) => { + if (pin.length >= PIN_LENGTH) return + setError('') + const next = pin + d + setPin(next) + if (next.length === PIN_LENGTH) { + await submit(next) + } + } + + const handleDelete = () => { + setError('') + setPin((p) => p.slice(0, -1)) + } + + const submit = async (entered: string) => { + if (mode === 'set') { + if (firstPin == null) { + setFirstPin(entered) + setPin('') + return + } + if (firstPin === entered) { + await savePin(entered) + onSuccess() + } else { + setError('PIN이 일치하지 않습니다. 다시 설정하세요.') + setFirstPin(null) + setPin('') + } + return + } + + // verify + const ok = await verifyPin(entered) + if (ok) { + setAttempts(0) + onSuccess() + } else { + const a = attempts + 1 + setAttempts(a) + setPin('') + if (a >= MAX_ATTEMPTS) { + setError('5회 실패 — 보안을 위해 로그아웃됩니다.') + onFail?.() + } else { + setError(`PIN이 올바르지 않습니다. (${a}/${MAX_ATTEMPTS})`) + } + } + } + + const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', 'del'] + + return ( + + 🛡️ + {title} + {subtitle} + + + {Array.from({ length: PIN_LENGTH }).map((_, i) => ( + + ))} + + + {!!error && {error}} + + + {keys.map((k, i) => + k === '' ? ( + + ) : k === 'del' ? ( + + ⌫ + + ) : ( + handleDigit(k)}> + {k} + + ) + )} + + + {onCancel && ( + + 취소 + + )} + + ) +} + +const s = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: COLORS.gnbBg, + alignItems: 'center', + justifyContent: 'center', + padding: 28, + }, + logo: { fontSize: 44, marginBottom: 12 }, + title: { fontSize: 22, fontWeight: '800', color: '#fff', marginBottom: 6 }, + subtitle: { fontSize: 13, color: 'rgba(255,255,255,.6)', marginBottom: 28 }, + dots: { flexDirection: 'row', gap: 18, marginBottom: 18 }, + dot: { + width: 16, height: 16, borderRadius: 8, + borderWidth: 2, borderColor: COLORS.accent, backgroundColor: 'transparent', + }, + dotFilled: { backgroundColor: COLORS.accent }, + error: { color: '#fca5a5', fontSize: 13, marginBottom: 12, textAlign: 'center' }, + pad: { flexDirection: 'row', flexWrap: 'wrap', width: 264, justifyContent: 'space-between' }, + key: { + width: 78, height: 78, borderRadius: 39, + alignItems: 'center', justifyContent: 'center', marginVertical: 6, + }, + keyText: { fontSize: 28, color: '#fff', fontWeight: '600' }, + keyDel: { fontSize: 26, color: 'rgba(255,255,255,.7)' }, + cancel: { marginTop: 24 }, + cancelText: { color: COLORS.accent, fontSize: 15, fontWeight: '600' }, +}) diff --git a/components/RejectReason.tsx b/components/RejectReason.tsx new file mode 100644 index 00000000..19804e39 --- /dev/null +++ b/components/RejectReason.tsx @@ -0,0 +1,131 @@ +import { useState } from 'react' +import { + Modal, View, Text, TextInput, TouchableOpacity, + StyleSheet, ActivityIndicator, KeyboardAvoidingView, Platform, +} from 'react-native' +import { COLORS } from '../constants/Config' + +/** + * 기능 #66 — 반려 사유 입력 모달 + * - 반려 사유 템플릿 빠른 선택 + 직접 입력 + * - 최소 10자 검증 (빈 값/10자 미만이면 제출 차단) + */ + +const MIN_LEN = 10 + +const TEMPLATES = [ + '요청 정보가 불충분하여 반려합니다.', + '변경 일정이 운영 정책과 충돌합니다.', + '영향 범위 분석이 누락되어 보완이 필요합니다.', + '롤백 계획이 명시되지 않았습니다.', + '승인 권한 범위를 초과하는 요청입니다.', +] + +interface Props { + visible: boolean + targetTitle?: string + onClose: () => void + /** 반려 확정 — reason 은 10자 이상 보장됨 */ + onSubmit: (reason: string) => Promise | void +} + +export default function RejectReason({ visible, targetTitle, onClose, onSubmit }: Props) { + const [reason, setReason] = useState('') + const [submitting, setSubmitting] = useState(false) + + const trimmed = reason.trim() + const valid = trimmed.length >= MIN_LEN + + const reset = () => { setReason(''); setSubmitting(false) } + + const handleClose = () => { reset(); onClose() } + + const handleSubmit = async () => { + if (!valid || submitting) return + setSubmitting(true) + try { + await onSubmit(trimmed) + reset() + } catch { + setSubmitting(false) + } + } + + return ( + + + + + 반려 사유 입력 + {!!targetTitle && {targetTitle}} + + 빠른 템플릿 + + {TEMPLATES.map((t, i) => ( + setReason(t)}> + {t} + + ))} + + + 사유 (최소 {MIN_LEN}자) + + + {trimmed.length} / {MIN_LEN}자 {valid ? '✔' : '(부족)'} + + + + + 취소 + + + {submitting + ? + : 반려} + + + + + + ) +} + +const s = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'flex-end' }, + sheet: { backgroundColor: '#fff', borderTopLeftRadius: 18, borderTopRightRadius: 18, + paddingHorizontal: 18, paddingTop: 10, paddingBottom: 28 }, + handle: { alignSelf: 'center', width: 40, height: 4, borderRadius: 2, + backgroundColor: COLORS.border, marginBottom: 12 }, + title: { fontSize: 16, fontWeight: '800', color: COLORS.text }, + subtitle: { fontSize: 12, color: COLORS.muted, marginTop: 2 }, + label: { fontSize: 12, fontWeight: '700', color: COLORS.text, marginTop: 16, marginBottom: 8 }, + chips: { flexDirection: 'row', flexWrap: 'wrap', gap: 6 }, + chip: { backgroundColor: COLORS.light, borderRadius: 14, paddingHorizontal: 10, + paddingVertical: 6, maxWidth: '100%' }, + chipText: { fontSize: 11, color: COLORS.blue }, + input: { borderWidth: 1, borderColor: COLORS.border, borderRadius: 10, padding: 12, + minHeight: 90, fontSize: 13, color: COLORS.text, backgroundColor: '#fff' }, + counter: { fontSize: 11, marginTop: 6, textAlign: 'right' }, + counterOk: { color: COLORS.success }, + counterBad: { color: COLORS.danger }, + actions: { flexDirection: 'row', gap: 10, marginTop: 18 }, + btn: { flex: 1, borderRadius: 10, paddingVertical: 13, alignItems: 'center' }, + btnGhost: { backgroundColor: '#f1f5f9' }, + btnGhostText: { color: COLORS.text, fontWeight: '700', fontSize: 14 }, + btnDanger: { backgroundColor: COLORS.danger }, + btnDangerText: { color: '#fff', fontWeight: '700', fontSize: 14 }, + btnDisabled: { opacity: 0.45 }, +}) diff --git a/components/RelatedSR.tsx b/components/RelatedSR.tsx new file mode 100644 index 00000000..16ea2b72 --- /dev/null +++ b/components/RelatedSR.tsx @@ -0,0 +1,84 @@ +import { useEffect, useState } from 'react' +import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native' +import { router } from 'expo-router' +import { COLORS, PRIORITY_COLOR, STATUS_COLOR } from '../constants/Config' +import { getRelatedSR } from '../services/api' + +interface Props { + srId: number +} + +interface RelatedItem { + id: number + sr_id?: string + title: string + status?: string + priority?: string +} + +/** + * 기능 #7 — 관련 SR 자동 연결 표시 + * GET /api/tasks?related_to={id} → 최대 3개 카드, 탭 시 상세 이동 + */ +export default function RelatedSR({ srId }: Props) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let alive = true + ;(async () => { + try { + const res = await getRelatedSR(srId) + const list: RelatedItem[] = + res.data?.content ?? res.data?.items ?? res.data ?? [] + if (alive) setItems(list.slice(0, 3)) + } catch { + if (alive) setItems([]) + } finally { + if (alive) setLoading(false) + } + })() + return () => { alive = false } + }, [srId]) + + if (loading) return + if (items.length === 0) { + return 관련 SR이 없습니다. + } + + return ( + + {items.map(it => ( + router.push({ pathname: '/(tabs)/sr_detail', params: { id: String(it.id) } })} + > + + {it.sr_id ?? `#${it.id}`} + {!!it.status && ( + + {it.status} + + )} + + {it.title} + {!!it.priority && ( + ● {it.priority} + )} + + ))} + + ) +} + +const s = StyleSheet.create({ + empty: { color: COLORS.muted, fontSize: 12, paddingVertical: 8 }, + card: { backgroundColor: COLORS.light, borderRadius: 8, padding: 10, marginBottom: 8 }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + srId: { fontSize: 11, color: COLORS.accent, fontWeight: '700' }, + badge: { paddingHorizontal: 7, paddingVertical: 2, borderRadius: 9 }, + badgeText: { fontSize: 9, fontWeight: '700' }, + title: { fontSize: 13, color: COLORS.text, marginTop: 4 }, + pri: { fontSize: 10, fontWeight: '700', marginTop: 4 }, +}) diff --git a/components/SRSatisfaction.tsx b/components/SRSatisfaction.tsx new file mode 100644 index 00000000..5ef4a7a7 --- /dev/null +++ b/components/SRSatisfaction.tsx @@ -0,0 +1,94 @@ +import { useState } from 'react' +import { + View, Text, Modal, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, +} from 'react-native' +import { COLORS } from '../constants/Config' +import { rateSR } from '../services/api' + +interface Props { + visible: boolean + srId: number + onClose: () => void + onSubmitted?: (score: number) => void +} + +const LABELS = ['', '매우 불만족', '불만족', '보통', '만족', '매우 만족'] + +/** + * 기능 #14 — 완료 후 만족도 별점 모달 + * 별점(1~5) + 한줄 피드백 → POST /api/tasks/{id}/rating + */ +export default function SRSatisfaction({ visible, srId, onClose, onSubmitted }: Props) { + const [score, setScore] = useState(0) + const [comment, setComment] = useState('') + const [saving, setSaving] = useState(false) + + const submit = async () => { + if (score === 0) { Alert.alert('별점을 선택하세요.'); return } + setSaving(true) + try { + await rateSR(srId, score, comment) + onSubmitted?.(score) + setScore(0); setComment('') + onClose() + Alert.alert('감사합니다', '소중한 평가가 등록되었습니다.') + } catch (e: any) { + Alert.alert('오류', e.response?.data?.detail ?? '평가 등록 실패') + } finally { setSaving(false) } + } + + return ( + + + + 서비스 만족도 평가 + SR 처리에 만족하셨나요? + + + {[1, 2, 3, 4, 5].map(n => ( + setScore(n)}> + ★ + + ))} + + {score > 0 && {LABELS[score]}} + + + + + + 나중에 + + + {saving ? : 평가 제출} + + + + + + ) +} + +const s = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.45)', justifyContent: 'center', padding: 28 }, + box: { backgroundColor: '#fff', borderRadius: 16, padding: 22 }, + title: { fontSize: 17, fontWeight: '800', color: COLORS.text, textAlign: 'center' }, + sub: { fontSize: 13, color: COLORS.muted, textAlign: 'center', marginTop: 6 }, + stars: { flexDirection: 'row', justifyContent: 'center', gap: 6, marginTop: 18 }, + star: { fontSize: 40, color: COLORS.border }, + starActive: { color: '#f59e0b' }, + scoreLabel: { textAlign: 'center', fontSize: 13, fontWeight: '700', color: COLORS.accent, marginTop: 8 }, + input: { borderWidth: 1.5, borderColor: COLORS.border, borderRadius: 10, padding: 12, fontSize: 13, color: COLORS.text, marginTop: 16, minHeight: 60, textAlignVertical: 'top' }, + actions: { flexDirection: 'row', gap: 10, marginTop: 18 }, + cancel: { flex: 1, paddingVertical: 13, borderRadius: 10, borderWidth: 1.5, borderColor: COLORS.border, alignItems: 'center' }, + cancelText: { fontSize: 14, fontWeight: '600', color: COLORS.muted }, + submit: { flex: 2, paddingVertical: 13, borderRadius: 10, backgroundColor: COLORS.primary, alignItems: 'center' }, + submitText: { fontSize: 14, fontWeight: '700', color: '#fff' }, +}) diff --git a/components/SRSolutionHint.tsx b/components/SRSolutionHint.tsx new file mode 100644 index 00000000..61ba6963 --- /dev/null +++ b/components/SRSolutionHint.tsx @@ -0,0 +1,96 @@ +/** + * SRSolutionHint (#24) — 과거 유사 SR 해결책 제안 + * + * GET /api/tasks/?similar_to={sr_id}&status=closed&limit=3 + * "유사 해결 사례" 섹션: 각 사례의 제목 / 해결 방법 / 해결 시간 표시. + */ +import { useState, useEffect } from 'react' +import { View, Text, Pressable, StyleSheet, ActivityIndicator } from 'react-native' +import { COLORS, API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' + +interface SimilarSR { + id: number + title: string + resolution?: string + resolved_at?: string + resolution_time?: string +} + +interface Props { + srId: number | string + onOpen?: (id: number) => void +} + +export function SRSolutionHint({ srId, onOpen }: Props) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let alive = true + ;(async () => { + setLoading(true) + try { + const res = await authFetch( + `${API_BASE}/api/tasks/?similar_to=${encodeURIComponent(String(srId))}&status=closed&limit=3`, + ) + if (res.ok) { + const d = await res.json() + const list: SimilarSR[] = Array.isArray(d) ? d : d.items ?? d.results ?? [] + if (alive) setItems(list.slice(0, 3)) + } else if (alive) { + setItems([]) + } + } catch { + if (alive) setItems([]) + } finally { + if (alive) setLoading(false) + } + })() + return () => { + alive = false + } + }, [srId]) + + if (loading) { + return ( + + 🔁 유사 해결 사례 + + + ) + } + if (items.length === 0) return null + + return ( + + 🔁 유사 해결 사례 + {items.map(sr => ( + onOpen?.(sr.id)}> + + #{sr.id} {sr.title} + + {sr.resolution ? ( + + 💡 {sr.resolution} + + ) : null} + {sr.resolution_time || sr.resolved_at ? ( + ⏱ 해결 소요: {sr.resolution_time ?? sr.resolved_at} + ) : null} + + ))} + + ) +} + +export default SRSolutionHint + +const S = StyleSheet.create({ + wrap: { backgroundColor: COLORS.card, borderRadius: 14, padding: 14, borderWidth: 1, borderColor: COLORS.border }, + title: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 8 }, + card: { backgroundColor: COLORS.light, borderRadius: 10, padding: 11, marginBottom: 6 }, + cardTitle: { fontSize: 13, fontWeight: '600', color: COLORS.blue }, + cardSol: { fontSize: 12, color: COLORS.text, marginTop: 4, lineHeight: 17 }, + cardTime: { fontSize: 11, color: COLORS.muted, marginTop: 4 }, +}) diff --git a/components/SRTemplates.tsx b/components/SRTemplates.tsx new file mode 100644 index 00000000..e535de23 --- /dev/null +++ b/components/SRTemplates.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from 'react' +import { + View, Text, Modal, FlatList, TouchableOpacity, StyleSheet, ActivityIndicator, +} from 'react-native' +import { COLORS, PRIORITY_COLOR } from '../constants/Config' +import { getSRTemplates } from '../services/api' + +export interface SRTemplate { + id: number | string + name: string + title?: string + category?: string + sr_type?: string + priority?: string + description?: string +} + +interface Props { + visible: boolean + onClose: () => void + onSelect: (tpl: SRTemplate) => void +} + +const FALLBACK: SRTemplate[] = [ + { id: 'deploy', name: '배포 요청', title: '[배포] 서비스 배포 요청', category: 'DEPLOY', priority: 'HIGH', description: '대상 서버 / 브랜치 / 배포 시간을 기재하세요.' }, + { id: 'restart', name: '서비스 재기동', title: '[재기동] 서비스 재시작 요청', category: 'RESTART', priority: 'MEDIUM', description: '재기동 대상 서비스명을 기재하세요.' }, + { id: 'log', name: '로그 확인', title: '[로그] 로그 조회 요청', category: 'LOG', priority: 'LOW', description: '조회 기간 / 키워드를 기재하세요.' }, + { id: 'incident',name: '장애 신고', title: '[장애] 긴급 장애 신고', category: 'OTHER', priority: 'CRITICAL',description: '발생 시각 / 증상 / 영향 범위를 기재하세요.' }, +] + +/** + * 기능 #10 — SR 템플릿 선택 모달 + * GET /api/tasks/templates → 실패 시 기본 템플릿 사용 + */ +export default function SRTemplates({ visible, onClose, onSelect }: Props) { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!visible) return + let alive = true + setLoading(true) + ;(async () => { + try { + const res = await getSRTemplates() + const list: SRTemplate[] = res.data?.content ?? res.data?.items ?? res.data ?? [] + if (alive) setItems(list.length ? list : FALLBACK) + } catch { + if (alive) setItems(FALLBACK) + } finally { + if (alive) setLoading(false) + } + })() + return () => { alive = false } + }, [visible]) + + return ( + + + + + SR 템플릿 선택 + + ✕ + + + {loading ? ( + + ) : ( + String(t.id)} + renderItem={({ item }) => ( + { onSelect(item); onClose() }}> + + {item.name} + {!!item.description && {item.description}} + + {!!item.priority && ( + + {item.priority} + + )} + + )} + /> + )} + + + + ) +} + +const s = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'flex-end' }, + sheet: { backgroundColor: '#fff', borderTopLeftRadius: 18, borderTopRightRadius: 18, maxHeight: '70%', paddingBottom: 24 }, + head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 18, borderBottomWidth: 1, borderBottomColor: COLORS.border }, + title: { fontSize: 16, fontWeight: '700', color: COLORS.text }, + close: { fontSize: 22, color: COLORS.muted }, + row: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 18, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: COLORS.border, gap: 10 }, + name: { fontSize: 14, fontWeight: '600', color: COLORS.text }, + desc: { fontSize: 12, color: COLORS.muted, marginTop: 3 }, + pri: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: 10 }, + priText: { fontSize: 10, fontWeight: '700' }, +}) diff --git a/components/SlaTimer.tsx b/components/SlaTimer.tsx new file mode 100644 index 00000000..46253d3b --- /dev/null +++ b/components/SlaTimer.tsx @@ -0,0 +1,65 @@ +import { useEffect, useRef, useState } from 'react' +import { Text, StyleSheet, Vibration } from 'react-native' + +interface Props { + deadline: string // ISO 문자열 + onExpire?: () => void +} + +const URGENT_MS = 30 * 60 * 1000 // 30분 + +/** + * 기능 #3 — SLA 카운트다운 타이머 + * 매 초 갱신, 임박(30분 이내) 시 빨간색 + 1회 진동 + */ +export default function SlaTimer({ deadline, onExpire }: Props) { + const [remaining, setRemaining] = useState( + () => new Date(deadline).getTime() - Date.now() + ) + const buzzedRef = useRef(false) + const expiredRef = useRef(false) + + useEffect(() => { + buzzedRef.current = false + expiredRef.current = false + const tick = () => { + const r = new Date(deadline).getTime() - Date.now() + setRemaining(r) + if (r < URGENT_MS && r > 0 && !buzzedRef.current) { + buzzedRef.current = true + try { Vibration.vibrate([0, 500, 200, 500]) } catch {} + } + if (r <= 0 && !expiredRef.current) { + expiredRef.current = true + onExpire?.() + } + } + tick() + const interval = setInterval(tick, 1000) + return () => clearInterval(interval) + }, [deadline, onExpire]) + + const overdue = remaining < 0 + const abs = Math.abs(remaining) + const hours = Math.floor(abs / 3600000) + const minutes = Math.floor((abs % 3600000) / 60000) + const seconds = Math.floor((abs % 60000) / 1000) + const isUrgent = remaining < URGENT_MS + + const pad = (n: number) => String(n).padStart(2, '0') + const label = overdue + ? `초과 ${pad(hours)}:${pad(minutes)}:${pad(seconds)}` + : `${pad(hours)}:${pad(minutes)}:${pad(seconds)}` + + return ( + + {label} + + ) +} + +const styles = StyleSheet.create({ + timer: { fontVariant: ['tabular-nums'], fontWeight: '700', fontSize: 15, color: '#64748B' }, + urgent: { color: '#f59e0b' }, + overdue: { color: '#ef4444' }, +}) diff --git a/components/ZeroTrustBadge.tsx b/components/ZeroTrustBadge.tsx new file mode 100644 index 00000000..044f678f --- /dev/null +++ b/components/ZeroTrustBadge.tsx @@ -0,0 +1,76 @@ +/** + * #37 Zero Trust 상태 뱃지 + * GET /api/auth/network-status → { via: 'vpn'|'opennet'|'internal', level: 1|2|3 } + * 🟢 Internal / 🟡 VPN / 🟠 OpenNet + */ +import { useEffect, useState } from 'react' +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' +import { getNetworkStatus } from '../services/api' + +type Via = 'vpn' | 'opennet' | 'internal' | string + +interface NetStatus { + via: Via + level: 1 | 2 | 3 | number +} + +const META: Record = { + internal: { dot: '🟢', label: 'Internal', color: '#15803d', bg: '#dcfce7' }, + vpn: { dot: '🟡', label: 'VPN', color: '#a16207', bg: '#fef9c3' }, + opennet: { dot: '🟠', label: 'OpenNet', color: '#c2410c', bg: '#ffedd5' }, +} + +export default function ZeroTrustBadge() { + const [status, setStatus] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let active = true + ;(async () => { + try { + const r = await getNetworkStatus() + if (active) setStatus(r.data) + } catch { + // 서버 미응답 시 알 수 없음 처리 + if (active) setStatus(null) + } finally { + if (active) setLoading(false) + } + })() + return () => { active = false } + }, []) + + if (loading) { + return ( + + + 네트워크 확인 중 + + ) + } + + const meta = (status && META[status.via]) || { + dot: '⚪', label: '알 수 없음', color: '#64748b', bg: '#f1f5f9', + } + + return ( + + {meta.dot} + Zero Trust · {meta.label} + {status?.level != null && ( + Lv.{status.level} + )} + + ) +} + +const s = StyleSheet.create({ + badge: { + flexDirection: 'row', alignItems: 'center', gap: 6, + paddingHorizontal: 12, paddingVertical: 8, borderRadius: 12, + marginHorizontal: 16, marginTop: 12, + }, + dot: { fontSize: 13 }, + label: { fontSize: 13, fontWeight: '700', flex: 1 }, + level: { fontSize: 11, fontWeight: '700', opacity: 0.8 }, +}) diff --git a/constants/Config.ts b/constants/Config.ts index 1d8a80ef..1a6e74a8 100644 --- a/constants/Config.ts +++ b/constants/Config.ts @@ -35,3 +35,22 @@ export const STATUS_COLOR: Record = { FAILED_ROLLBACK: '#ef4444', REJECTED: '#dc2626', } + +export const DARK_COLORS = { + gnbBg: '#0F172A', + bg: '#1E293B', + card: '#334155', + text: '#F1F5F9', + muted: '#94A3B8', + border: '#475569', + accent: '#38BDF8', + success: '#34D399', + warning: '#FBBF24', + danger: '#F87171', + white: '#ffffff', +} as const + +export const WS_BASE = (() => { + const base = (Constants.expoConfig?.extra?.guardiaApiUrl ?? 'https://zioinfo.co.kr:8443') as string + return base.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://') +})() diff --git a/contexts/FontContext.tsx b/contexts/FontContext.tsx new file mode 100644 index 00000000..eadefbee --- /dev/null +++ b/contexts/FontContext.tsx @@ -0,0 +1,24 @@ +import React, { createContext, useContext, useState, useEffect } from 'react' +import * as SecureStore from 'expo-secure-store' + +type FontScale = 1.0 | 1.2 | 1.5 +interface FontCtx { fontScale: FontScale; setFontScale: (s: FontScale) => void } + +export const FontContext = createContext({ fontScale: 1.0, setFontScale: () => {} }) + +export function FontProvider({ children }: { children: React.ReactNode }) { + const [fontScale, setScale] = useState(1.0) + useEffect(() => { + SecureStore.getItemAsync('grd_font_scale').then(v => { + const n = parseFloat(v ?? '1.0') + if (n === 1.2 || n === 1.5) setScale(n as FontScale) + }) + }, []) + const setFontScale = (s: FontScale) => { + setScale(s) + SecureStore.setItemAsync('grd_font_scale', String(s)) + } + return {children} +} + +export const useFontScale = () => useContext(FontContext) diff --git a/contexts/OfflineContext.tsx b/contexts/OfflineContext.tsx new file mode 100644 index 00000000..2c364f38 --- /dev/null +++ b/contexts/OfflineContext.tsx @@ -0,0 +1,47 @@ +import React, { createContext, useContext, useState, useEffect } from 'react' +import NetInfo from '@react-native-community/netinfo' +import * as SecureStore from 'expo-secure-store' + +interface OfflineCtx { + isOffline: boolean + getCache: (key: string) => Promise + setCache: (key: string, data: unknown) => Promise +} + +export const OfflineContext = createContext({ + isOffline: false, + getCache: async () => null, + setCache: async () => {}, +}) + +export function OfflineProvider({ children }: { children: React.ReactNode }) { + const [isOffline, setIsOffline] = useState(false) + + useEffect(() => { + const unsub = NetInfo.addEventListener((state: any) => { + setIsOffline(!(state.isConnected && state.isInternetReachable !== false)) + }) + return () => unsub() + }, []) + + const getCache = async (key: string) => { + try { + const raw = await SecureStore.getItemAsync(`cache_${key}`) + return raw ? JSON.parse(raw) : null + } catch { return null } + } + + const setCache = async (key: string, data: unknown) => { + try { + await SecureStore.setItemAsync(`cache_${key}`, JSON.stringify(data)) + } catch {} + } + + return ( + + {children} + + ) +} + +export const useOffline = () => useContext(OfflineContext) diff --git a/contexts/ThemeContext.tsx b/contexts/ThemeContext.tsx new file mode 100644 index 00000000..96c4dd73 --- /dev/null +++ b/contexts/ThemeContext.tsx @@ -0,0 +1,30 @@ +import React, { createContext, useContext, useState, useEffect } from 'react' +import * as SecureStore from 'expo-secure-store' + +type Theme = 'light' | 'dark' +interface ThemeCtx { theme: Theme; toggleTheme: () => void; isDark: boolean } + +export const ThemeContext = createContext({ + theme: 'light', toggleTheme: () => {}, isDark: false, +}) + +export function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setTheme] = useState('light') + useEffect(() => { + SecureStore.getItemAsync('grd_theme').then(v => { + if (v === 'dark') setTheme('dark') + }) + }, []) + const toggleTheme = () => { + const next: Theme = theme === 'light' ? 'dark' : 'light' + setTheme(next) + SecureStore.setItemAsync('grd_theme', next) + } + return ( + + {children} + + ) +} + +export const useTheme = () => useContext(ThemeContext) diff --git a/hooks/useAIClassify.ts b/hooks/useAIClassify.ts new file mode 100644 index 00000000..988b7ef0 --- /dev/null +++ b/hooks/useAIClassify.ts @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react' +import Constants from 'expo-constants' + +/** + * 기능 #11 — Ollama AI 자동 분류 훅 (온프레미스 sLLM, 외부 API 미사용) + * 제목 입력 시 debounce 후 Ollama /api/generate 호출 → + * { category, priority } JSON 파싱하여 입력 필드 자동 제안. + */ + +const OLLAMA_HOST: string = + Constants.expoConfig?.extra?.ollamaUrl ?? 'http://localhost:11434' +const OLLAMA_MODEL: string = + Constants.expoConfig?.extra?.ollamaModel ?? 'llama3' + +const CATEGORIES = ['DEPLOY', 'RESTART', 'LOG', 'INQUIRY', 'OTHER'] +const PRIORITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] + +export interface AIClassifyResult { + category: string + priority: string + loading: boolean + error: boolean +} + +function extractJson(text: string): { category?: string; priority?: string } | null { + try { + const match = text.match(/\{[\s\S]*\}/) + if (!match) return null + return JSON.parse(match[0]) + } catch { return null } +} + +export function useAIClassify(title: string): AIClassifyResult { + const [category, setCategory] = useState('') + const [priority, setPriority] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(false) + + useEffect(() => { + const trimmed = title.trim() + if (trimmed.length < 4) { + setCategory(''); setPriority(''); setLoading(false); setError(false) + return + } + + let alive = true + setLoading(true); setError(false) + const controller = new AbortController() + + const timer = setTimeout(async () => { + const prompt = + `다음 SR 제목의 카테고리와 우선순위를 JSON으로만 답하라. ` + + `category 는 [${CATEGORIES.join(', ')}] 중 하나, ` + + `priority 는 [${PRIORITIES.join(', ')}] 중 하나. ` + + `제목: "${trimmed}"` + + try { + const res = await fetch(`${OLLAMA_HOST}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: OLLAMA_MODEL, prompt, stream: false, format: 'json' }), + signal: controller.signal, + }) + const data = await res.json() + const parsed = extractJson(data?.response ?? '') + if (!alive) return + if (parsed) { + const cat = (parsed.category ?? '').toUpperCase() + const pri = (parsed.priority ?? '').toUpperCase() + setCategory(CATEGORIES.includes(cat) ? cat : '') + setPriority(PRIORITIES.includes(pri) ? pri : '') + } else { + setError(true) + } + } catch { + if (alive) setError(true) + } finally { + if (alive) setLoading(false) + } + }, 700) + + return () => { alive = false; clearTimeout(timer); controller.abort() } + }, [title]) + + return { category, priority, loading, error } +} diff --git a/hooks/useAnomalyAlert.ts b/hooks/useAnomalyAlert.ts new file mode 100644 index 00000000..c38eeace --- /dev/null +++ b/hooks/useAnomalyAlert.ts @@ -0,0 +1,69 @@ +/** + * useAnomalyAlert (#22) — 이상 탐지 푸시 훅 + * + * 1분마다 GET /api/metrics/anomalies?severity=high 폴링. + * 이전에 보지 못한 새 이상 감지 시 Alert.alert로 알림. + * (expo-notifications 플러그인 등록 없이 인앱 Alert 사용 → EAS 빌드 안전) + */ +import { useEffect, useRef, useState } from 'react' +import { Alert, AppState } from 'react-native' +import { API_BASE } from '../constants/Config' +import { authFetch } from '../utils/auth' + +interface Anomaly { + id: number | string + title?: string + metric?: string + server?: string + severity?: string + detected_at?: string +} + +const POLL_MS = 60_000 + +export function useAnomalyAlert(enabled = true) { + const seen = useRef>(new Set()) + const [latest, setLatest] = useState([]) + + useEffect(() => { + if (!enabled) return + let alive = true + + async function poll() { + if (AppState.currentState !== 'active') return + try { + const res = await authFetch(`${API_BASE}/api/metrics/anomalies?severity=high`) + if (!res.ok) return + const d = await res.json() + const list: Anomaly[] = Array.isArray(d) ? d : d.items ?? d.anomalies ?? [] + if (!alive) return + setLatest(list) + + const fresh = list.filter(a => !seen.current.has(String(a.id))) + fresh.forEach(a => seen.current.add(String(a.id))) + + if (fresh.length > 0) { + const first = fresh[0] + const more = fresh.length > 1 ? ` 외 ${fresh.length - 1}건` : '' + Alert.alert( + '⚠️ 이상 징후 감지', + `${first.title ?? first.metric ?? '메트릭 이상'}${first.server ? ` (${first.server})` : ''}${more}`, + ) + } + } catch { + /* 폴링 실패 무시 */ + } + } + + poll() + const id = setInterval(poll, POLL_MS) + return () => { + alive = false + clearInterval(id) + } + }, [enabled]) + + return { latest } +} + +export default useAnomalyAlert diff --git a/hooks/useBadgeCount.ts b/hooks/useBadgeCount.ts new file mode 100644 index 00000000..6eb28c89 --- /dev/null +++ b/hooks/useBadgeCount.ts @@ -0,0 +1,38 @@ +import { useEffect, useState, useCallback } from 'react' +import * as Notifications from 'expo-notifications' +import { getOpenSRCount } from '../services/api' + +const REFRESH_MS = 5 * 60 * 1000 // 5분 + +/** + * 기능 #15 — 앱 아이콘 뱃지 카운트 훅 + * 미처리(open) + 내 담당 SR 수를 5분마다 갱신하여 앱 뱃지에 반영. + * expo-notifications 플러그인은 app.json 에 등록하지 않고 + * Notifications.setBadgeCountAsync 만 사용한다. + */ +export function useBadgeCount() { + const [count, setCount] = useState(0) + + const refresh = useCallback(async () => { + try { + const res = await getOpenSRCount() + const c: number = + res.data?.total ?? + res.data?.totalElements ?? + (Array.isArray(res.data?.content) ? res.data.content.length : undefined) ?? + (Array.isArray(res.data) ? res.data.length : 0) + setCount(c) + try { await Notifications.setBadgeCountAsync(c) } catch {} + } catch { + // 네트워크 오류 시 뱃지 유지 + } + }, []) + + useEffect(() => { + refresh() + const interval = setInterval(refresh, REFRESH_MS) + return () => clearInterval(interval) + }, [refresh]) + + return { count, refresh } +} diff --git a/hooks/useDuplicateSR.ts b/hooks/useDuplicateSR.ts new file mode 100644 index 00000000..5d8dabe1 --- /dev/null +++ b/hooks/useDuplicateSR.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react' +import { findSimilarSR } from '../services/api' + +export interface DuplicateTask { + id: number + sr_id?: string + title: string + status?: string + similarity?: number +} + +/** + * 기능 #9 — 중복 SR 감지 훅 + * 제목 입력 시 500ms debounce 후 유사 SR 조회. + * 유사도 70% 이상(또는 서버가 반환한 항목)을 경고로 노출. + */ +export function useDuplicateSR(title: string) { + const [duplicates, setDuplicates] = useState([]) + const [loading, setLoading] = useState(false) + + useEffect(() => { + const trimmed = title.trim() + if (trimmed.length < 4) { + setDuplicates([]) + setLoading(false) + return + } + + let alive = true + setLoading(true) + const timer = setTimeout(async () => { + try { + const res = await findSimilarSR(trimmed, 3) + const list: DuplicateTask[] = res.data?.content ?? res.data?.items ?? res.data ?? [] + // 서버가 similarity 점수를 주면 70% 이상만, 아니면 반환 항목 그대로 + const filtered = list.filter(d => + d.similarity === undefined || d.similarity >= 0.7 + ) + if (alive) setDuplicates(filtered.slice(0, 3)) + } catch { + if (alive) setDuplicates([]) + } finally { + if (alive) setLoading(false) + } + }, 500) + + return () => { alive = false; clearTimeout(timer) } + }, [title]) + + return { duplicates, loading, hasDuplicates: duplicates.length > 0 } +} diff --git a/hooks/useGPSTag.ts b/hooks/useGPSTag.ts new file mode 100644 index 00000000..ec50246b --- /dev/null +++ b/hooks/useGPSTag.ts @@ -0,0 +1,95 @@ +/** + * useGPSTag — GPS 위치 태깅 훅 (기능 #59) + * + * SR 등록 / 현장 체크인 시 현재 좌표를 자동 첨부한다. + * expo-location 미설치 환경(개발/시뮬레이터)에서도 빌드/런타임이 깨지지 않도록 + * 동적 require + graceful fallback 처리한다. + * + * 빌드 금기 준수: app.json 플러그인 등록 없이 런타임 권한 요청만 사용. + */ +import { useState, useCallback } from 'react' + +export interface GeoTag { + lat: number + lng: number + accuracy?: number | null + ts: string +} + +type PermState = 'unknown' | 'granted' | 'denied' + +// 동적 로드 — 모듈이 없으면 null +function loadLocation(): any | null { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('expo-location') + } catch { + return null + } +} + +export function useGPSTag() { + const [tag, setTag] = useState(null) + const [perm, setPerm] = useState('unknown') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const requestPermission = useCallback(async (): Promise => { + const Location = loadLocation() + if (!Location) { + setError('위치 모듈을 사용할 수 없습니다 (EAS 빌드 필요)') + setPerm('denied') + return false + } + try { + const { status } = await Location.requestForegroundPermissionsAsync() + const granted = status === 'granted' + setPerm(granted ? 'granted' : 'denied') + if (!granted) setError('위치 권한이 거부되었습니다') + return granted + } catch (e: any) { + setPerm('denied') + setError(e?.message ?? '권한 요청 실패') + return false + } + }, []) + + /** 현재 위치 1회 획득 → GeoTag 반환(실패 시 null) */ + const capture = useCallback(async (): Promise => { + setLoading(true) + setError(null) + const Location = loadLocation() + if (!Location) { + setError('위치 모듈을 사용할 수 없습니다 (EAS 빌드 필요)') + setLoading(false) + return null + } + try { + const ok = perm === 'granted' || (await requestPermission()) + if (!ok) { + setLoading(false) + return null + } + const pos = await Location.getCurrentPositionAsync({ + accuracy: Location.Accuracy?.Balanced ?? 3, + }) + const t: GeoTag = { + lat: pos.coords.latitude, + lng: pos.coords.longitude, + accuracy: pos.coords.accuracy ?? null, + ts: new Date().toISOString(), + } + setTag(t) + setLoading(false) + return t + } catch (e: any) { + setError(e?.message ?? '위치 획득 실패') + setLoading(false) + return null + } + }, [perm, requestPermission]) + + return { tag, perm, loading, error, requestPermission, capture, available: !!loadLocation() } +} + +export default useGPSTag diff --git a/hooks/useKBBookmark.ts b/hooks/useKBBookmark.ts new file mode 100644 index 00000000..6f1a7386 --- /dev/null +++ b/hooks/useKBBookmark.ts @@ -0,0 +1,30 @@ +import { useState, useEffect, useCallback } from 'react' +import * as SecureStore from 'expo-secure-store' +import { toggleKBBookmark } from '../services/api' + +const KEY = 'kb_bookmarks' + +export function useKBBookmark() { + const [ids, setIds] = useState([]) + + useEffect(() => { + SecureStore.getItemAsync(KEY).then(raw => { + try { if (raw) setIds(JSON.parse(raw)) } catch {} + }) + }, []) + + const persist = useCallback(async (next: number[]) => { + setIds(next) + await SecureStore.setItemAsync(KEY, JSON.stringify(next)).catch(() => {}) + }, []) + + const isBookmarked = useCallback((id: number) => ids.includes(id), [ids]) + + const toggle = useCallback(async (id: number) => { + const next = ids.includes(id) ? ids.filter(x => x !== id) : [...ids, id] + await persist(next) + toggleKBBookmark(id).catch(() => {}) + }, [ids, persist]) + + return { ids, isBookmarked, toggle } +} diff --git a/hooks/useOfflineCache.ts b/hooks/useOfflineCache.ts index 71aa7e31..7db2fb6f 100644 --- a/hooks/useOfflineCache.ts +++ b/hooks/useOfflineCache.ts @@ -18,7 +18,7 @@ export function useOfflineCache( const [cachedAt, setCachedAt] = useState(null) useEffect(() => { - const unsub = NetInfo.addEventListener(state => { + const unsub = NetInfo.addEventListener((state: any) => { setOffline(!(state.isConnected ?? true)) }) return () => unsub() diff --git a/hooks/useRoleMenu.ts b/hooks/useRoleMenu.ts new file mode 100644 index 00000000..d73f6fd4 --- /dev/null +++ b/hooks/useRoleMenu.ts @@ -0,0 +1,100 @@ +/** + * #32 역할별 메뉴 제어 훅 + * JWT payload에서 role을 추출해 노출 허용 탭 목록을 반환한다. + * + * engineer: 운영 탭 / pm: 승인+보고 / admin: 전체 + */ +import { useEffect, useState } from 'react' +import * as SecureStore from 'expo-secure-store' + +export type Role = 'engineer' | 'pm' | 'admin' | string + +/** 실제 (tabs)에 존재하는 라우트 기준 매핑 */ +export const ROLE_TABS: Record = { + engineer: ['index', 'sr', 'chat', 'notifications', 'dr', 'network', 'scan', 'settings'], + pm: ['index', 'sr', 'chat', 'notifications', 'insights', 'settings'], + admin: ['*'], // 전체 허용 +} + +/** 모든 탭 (admin/fallback 용) */ +export const ALL_TABS = [ + 'index', 'sr', 'chat', 'notifications', 'dr', 'network', + 'insights', 'voice', 'scan', 'settings', +] + +/** RN-safe base64 디코더 (atob 미보장 환경 대응) */ +const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +function base64UrlDecode(input: string): string { + let str = input.replace(/-/g, '+').replace(/_/g, '/') + while (str.length % 4) str += '=' + let output = '' + for (let i = 0; i < str.length; ) { + const e1 = B64.indexOf(str.charAt(i++)) + const e2 = B64.indexOf(str.charAt(i++)) + const e3 = B64.indexOf(str.charAt(i++)) + const e4 = B64.indexOf(str.charAt(i++)) + const c1 = (e1 << 2) | (e2 >> 4) + const c2 = ((e2 & 15) << 4) | (e3 >> 2) + const c3 = ((e3 & 3) << 6) | e4 + output += String.fromCharCode(c1) + if (e3 !== 64 && e3 !== -1) output += String.fromCharCode(c2) + if (e4 !== 64 && e4 !== -1) output += String.fromCharCode(c3) + } + // UTF-8 디코드 + try { + return decodeURIComponent( + output.split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('') + ) + } catch { + return output + } +} + +export function decodeJWT(token: string): { role?: string; user_id?: string; sub?: string; [k: string]: any } { + try { + const parts = token.split('.') + if (parts.length < 2) return {} + return JSON.parse(base64UrlDecode(parts[1])) + } catch { + return {} + } +} + +/** 토큰에서 role만 동기적으로 추출 (토큰 문자열을 이미 가진 경우) */ +export function roleFromToken(token: string | null | undefined): Role { + if (!token) return 'engineer' + const p = decodeJWT(token) + return (p.role as string) ?? 'engineer' +} + +/** role 기준 허용 탭 목록 */ +export function tabsForRole(role: Role): string[] { + const allowed = ROLE_TABS[role] + if (!allowed || allowed.includes('*')) return ALL_TABS + return allowed +} + +/** SecureStore 토큰을 읽어 허용 탭을 반환하는 비동기 훅 */ +export function useRoleMenu(): { role: Role; tabs: string[]; loading: boolean } { + const [role, setRole] = useState('engineer') + const [tabs, setTabs] = useState(ALL_TABS) + const [loading, setLoading] = useState(true) + + useEffect(() => { + ;(async () => { + try { + const token = await SecureStore.getItemAsync('grd_token') + const r = roleFromToken(token) + setRole(r) + setTabs(tabsForRole(r)) + } catch { + setRole('engineer') + setTabs(tabsForRole('engineer')) + } finally { + setLoading(false) + } + })() + }, []) + + return { role, tabs, loading } +} diff --git a/hooks/useSLAPrediction.ts b/hooks/useSLAPrediction.ts new file mode 100644 index 00000000..33f8d744 --- /dev/null +++ b/hooks/useSLAPrediction.ts @@ -0,0 +1,75 @@ +/** + * useSLAPrediction (#43) — SLA 위반 예측 훅 + * + * GET /api/sla/prediction 으로 SLA 위반 위험이 있는 SR/서비스를 예측 조회. + * 폴링(기본 60초)으로 주기 갱신. 위험도(risk) 기준으로 정렬·요약 제공. + */ +import { useEffect, useState, useCallback, useRef } from 'react' +import { getSLAPrediction } from '../services/api' + +export interface SLAPredictItem { + id: string | number + target_type: 'sr' | 'service' + target_name: string + sla_deadline: string // ISO + predicted_breach_at: string // ISO + risk: 'high' | 'medium' | 'low' + confidence: number // 0~1 + remaining_minutes: number +} + +interface SLAPredictResult { + items: SLAPredictItem[] + highRiskCount: number + loading: boolean + error: boolean + refresh: () => void +} + +const RISK_ORDER: Record = { high: 0, medium: 1, low: 2 } + +export function useSLAPrediction(pollMs = 60000): SLAPredictResult { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(false) + const timer = useRef | null>(null) + + const load = useCallback(async () => { + try { + const res = await getSLAPrediction() + const raw: any[] = res.data?.items ?? res.data?.predictions ?? res.data ?? [] + const mapped: SLAPredictItem[] = raw.map((r: any) => ({ + id: r.id ?? r.sr_id ?? r.target_id, + target_type: r.target_type ?? (r.sr_id ? 'sr' : 'service'), + target_name: r.target_name ?? r.title ?? r.service_name ?? `#${r.id ?? ''}`, + sla_deadline: r.sla_deadline ?? r.deadline ?? '', + predicted_breach_at: r.predicted_breach_at ?? r.eta ?? '', + risk: (r.risk ?? r.risk_level ?? 'low').toLowerCase(), + confidence: typeof r.confidence === 'number' ? r.confidence : 0, + remaining_minutes: r.remaining_minutes ?? r.remaining_min ?? 0, + })) + mapped.sort((a, b) => + (RISK_ORDER[a.risk] ?? 9) - (RISK_ORDER[b.risk] ?? 9) || + a.remaining_minutes - b.remaining_minutes + ) + setItems(mapped) + setError(false) + } catch { + setError(true) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + load() + timer.current = setInterval(load, pollMs) + return () => { if (timer.current) clearInterval(timer.current) } + }, [load, pollMs]) + + const highRiskCount = items.filter(i => i.risk === 'high').length + + return { items, highRiskCount, loading, error, refresh: load } +} + +export default useSLAPrediction diff --git a/hooks/useScreenshotBlock.ts b/hooks/useScreenshotBlock.ts new file mode 100644 index 00000000..8e286ba4 --- /dev/null +++ b/hooks/useScreenshotBlock.ts @@ -0,0 +1,73 @@ +/** + * #35 민감 화면 스크린샷 차단 훅 + * + * 완전 차단(Android FLAG_SECURE / iOS)은 네이티브 모듈(expo-screen-capture)이 필요하다. + * 해당 모듈이 설치되어 있으면 동적으로 활성화하고, 없으면(EAS 안전 모드) + * 오버레이 가림(SecureOverlay)으로 대체할 수 있도록 상태를 반환한다. + * + * 추가 네이티브 의존성 없이 동작 — package.json/app.json 변경 불필요. + */ +import { useEffect, useRef, useState } from 'react' +import { AppState, AppStateStatus } from 'react-native' + +interface ScreenshotBlockResult { + /** 앱이 비활성(백그라운드/전환 중) 상태일 때 true → 민감 내용을 오버레이로 가린다 */ + shouldObscure: boolean + /** 네이티브 FLAG_SECURE가 실제 적용되었는지 */ + nativeSecured: boolean +} + +let cachedModule: any | undefined // undefined: 미탐지, null: 없음, obj: 모듈 + +function loadScreenCaptureModule(): any | null { + if (cachedModule !== undefined) return cachedModule + try { + // 설치되어 있을 때만 로드 (미설치 시 require가 throw → catch) + // eslint-disable-next-line @typescript-eslint/no-var-requires + cachedModule = require('expo-screen-capture') + } catch { + cachedModule = null + } + return cachedModule +} + +export function useScreenshotBlock(enabled: boolean): ScreenshotBlockResult { + const [shouldObscure, setShouldObscure] = useState(false) + const [nativeSecured, setNativeSecured] = useState(false) + const tagRef = useRef(`secure-${Math.random().toString(36).slice(2)}`) + + // 네이티브 FLAG_SECURE 시도 + useEffect(() => { + if (!enabled) return + const mod = loadScreenCaptureModule() + let active = true + if (mod?.preventScreenCaptureAsync) { + mod + .preventScreenCaptureAsync(tagRef.current) + .then(() => active && setNativeSecured(true)) + .catch(() => active && setNativeSecured(false)) + } + return () => { + active = false + if (mod?.allowScreenCaptureAsync) { + mod.allowScreenCaptureAsync(tagRef.current).catch(() => {}) + } + setNativeSecured(false) + } + }, [enabled]) + + // 오버레이 가림: 앱이 inactive/background 일 때 (앱 스위처 미리보기 차단) + useEffect(() => { + if (!enabled) { + setShouldObscure(false) + return + } + const onChange = (state: AppStateStatus) => { + setShouldObscure(state !== 'active') + } + const sub = AppState.addEventListener('change', onChange) + return () => sub.remove() + }, [enabled]) + + return { shouldObscure, nativeSecured } +} diff --git a/hooks/useSessionExpiry.ts b/hooks/useSessionExpiry.ts new file mode 100644 index 00000000..f477e7d2 --- /dev/null +++ b/hooks/useSessionExpiry.ts @@ -0,0 +1,62 @@ +/** + * #31 세션 자동 만료 훅 + * 마지막 활동 시간을 기록하고, 15분 비활성 시 토큰을 삭제하고 로그인 화면으로 보낸다. + * + * 저장소: expo-secure-store (AsyncStorage 미설치 → EAS 빌드 안전을 위해 SecureStore 사용) + * 키: grd_last_activity + */ +import { useCallback } from 'react' +import { useRouter } from 'expo-router' +import * as SecureStore from 'expo-secure-store' + +export const SESSION_TIMEOUT = 15 * 60 * 1000 // 15분 +const LAST_ACTIVITY_KEY = 'grd_last_activity' +const TOKEN_KEY = 'grd_token' +const USER_KEY = 'grd_user' + +export async function recordActivity(): Promise { + try { + await SecureStore.setItemAsync(LAST_ACTIVITY_KEY, String(Date.now())) + } catch {} +} + +/** 만료 여부만 판정 (라우터 의존 없이 _layout 등에서 사용) */ +export async function isSessionExpired(): Promise { + try { + const token = await SecureStore.getItemAsync(TOKEN_KEY) + if (!token) return false // 로그인 전이면 만료 개념 없음 + const last = await SecureStore.getItemAsync(LAST_ACTIVITY_KEY) + if (!last) return false + return Date.now() - parseInt(last, 10) > SESSION_TIMEOUT + } catch { + return false + } +} + +export async function clearSession(): Promise { + try { + await SecureStore.deleteItemAsync(TOKEN_KEY) + await SecureStore.deleteItemAsync(USER_KEY) + await SecureStore.deleteItemAsync(LAST_ACTIVITY_KEY) + } catch {} +} + +export function useSessionExpiry() { + const router = useRouter() + + const updateActivity = useCallback(async () => { + await recordActivity() + }, []) + + const checkExpiry = useCallback(async (): Promise => { + const expired = await isSessionExpired() + if (expired) { + await clearSession() + router.replace('/(auth)/login') + return true + } + return false + }, [router]) + + return { updateActivity, checkExpiry, isSessionExpired, clearSession } +} diff --git a/hooks/useSmartNotif.ts b/hooks/useSmartNotif.ts new file mode 100644 index 00000000..919f86b8 --- /dev/null +++ b/hooks/useSmartNotif.ts @@ -0,0 +1,53 @@ +/** + * useSmartNotif (#28) — AI 스마트 알림 필터링 훅 + * + * 알림 수신 시 Ollama로 긴급도 판단 → urgent=false면 묵음 처리. + * 반환된 filter() 함수로 알림 큐를 필터링/표시 제어. + * + * 보안: 온프레미스 Ollama만 사용. 알림 본문에 자격증명 미포함 가정. + */ +import { useCallback } from 'react' +import { generateJSON, DEFAULT_TEXT_MODEL } from '../lib/ollama' + +interface NotifVerdict { + urgent: boolean + reason: string +} + +const SAFE_DEFAULT: NotifVerdict = { urgent: true, reason: 'AI 판단 불가 — 기본 표시' } + +export function useSmartNotif() { + /** 단일 알림 긴급도 판단. Ollama 미가동 시 안전하게 urgent=true. */ + const judge = useCallback(async (alertText: string): Promise => { + if (!alertText?.trim()) return SAFE_DEFAULT + const prompt = + `다음 ITSM 알림이 즉시 대응이 필요한 긴급 알림인지 판단하세요: "${alertText}". ` + + `JSON으로만 출력: {"urgent": true 또는 false, "reason": "한국어 한 줄 사유"}` + return generateJSON(DEFAULT_TEXT_MODEL, prompt, SAFE_DEFAULT) + }, []) + + /** + * 알림 배열을 긴급한 것만 남기도록 필터링. + * getText: 알림 객체에서 판단용 텍스트 추출. + */ + const filter = useCallback( + async (notifs: T[], getText: (n: T) => string): Promise => { + const verdicts = await Promise.all(notifs.map(n => judge(getText(n)))) + return notifs.filter((_, i) => verdicts[i].urgent) + }, + [judge], + ) + + /** 단일 알림 표시 여부 (urgent=false면 묵음). */ + const shouldShow = useCallback( + async (alertText: string): Promise => { + const v = await judge(alertText) + return v.urgent + }, + [judge], + ) + + return { judge, filter, shouldShow } +} + +export default useSmartNotif diff --git a/lib/ollama.ts b/lib/ollama.ts new file mode 100644 index 00000000..63912f44 --- /dev/null +++ b/lib/ollama.ts @@ -0,0 +1,88 @@ +/** + * GUARDiA Messenger — Ollama 온프레미스 AI 클라이언트 + * + * 보안 원칙 (불변): + * - 외부 AI API 절대 사용 금지. localhost:11434 (온프레미스 Ollama)만 사용. + * - 서버 자격증명(IP/SSH 계정/비밀번호)을 프롬프트에 포함 금지. + * - Ollama 미가동/오프라인 시 안전하게 빈 값/폴백 반환 (앱 크래시 방지). + */ + +// 외부 AI API 절대 사용 금지 — localhost:11434만 사용 +const OLLAMA_BASE = 'http://localhost:11434' + +// 기본 모델 (온프레미스 sLLM) +export const DEFAULT_TEXT_MODEL = 'llama3' +export const DEFAULT_VISION_MODEL = 'llava' + +interface OllamaResponse { + response?: string + done?: boolean +} + +/** 단순 텍스트 생성. 실패 시 빈 문자열 반환. */ +export async function generate(model: string, prompt: string): Promise { + try { + const res = await fetch(`${OLLAMA_BASE}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model, prompt, stream: false }), + }) + const data: OllamaResponse = await res.json() + return data.response ?? '' + } catch { + return '' // 오프라인/Ollama 미가동 시 빈 문자열 반환 + } +} + +/** + * JSON 응답 생성. sLLM 응답에서 JSON 블록을 추출/파싱. + * 파싱 실패 시 fallback 반환. + */ +export async function generateJSON(model: string, prompt: string, fallback: T): Promise { + const raw = await generate(model, prompt) + if (!raw) return fallback + try { + return JSON.parse(raw) as T + } catch { + // 모델이 부연 설명과 함께 JSON을 출력한 경우 JSON 블록만 추출 시도 + const match = raw.match(/[\[{][\s\S]*[\]}]/) + if (match) { + try { + return JSON.parse(match[0]) as T + } catch { + return fallback + } + } + return fallback + } +} + +/** 이미지 + 텍스트 멀티모달 생성 (llava). imageBase64는 data URI 접두어 없는 순수 base64. */ +export async function generateWithImage( + model: string, + prompt: string, + imageBase64: string, +): Promise { + try { + const clean = imageBase64.replace(/^data:image\/\w+;base64,/, '') + const res = await fetch(`${OLLAMA_BASE}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model, prompt, images: [clean], stream: false }), + }) + const data: OllamaResponse = await res.json() + return data.response ?? '' + } catch { + return '' + } +} + +/** Ollama 서버 가동 여부 확인. */ +export async function isOllamaAvailable(): Promise { + try { + const res = await fetch(`${OLLAMA_BASE}/api/tags`, { method: 'GET' }) + return res.ok + } catch { + return false + } +} diff --git a/package-lock.json b/package-lock.json index 0bf2fc11..8c56c08e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,17 +9,27 @@ "version": "1.0.0", "dependencies": { "@expo/metro-runtime": "~3.2.3", + "@react-native-community/netinfo": "11.3.1", "axios": "^1.7.7", - "expo": "~51.0.28", - "expo-constants": "~16.0.2", - "expo-linking": "~6.3.1", - "expo-router": "~3.5.23", + "expo": "^56.0.9", + "expo-av": "~14.0.7", + "expo-constants": "^56.0.17", + "expo-haptics": "~13.0.1", + "expo-image-picker": "~15.0.7", + "expo-linking": "^56.0.13", + "expo-local-authentication": "~14.0.1", + "expo-notifications": "^56.0.16", + "expo-print": "~13.0.1", + "expo-router": "^56.2.9", + "expo-screen-orientation": "~7.0.5", "expo-secure-store": "~13.0.2", - "expo-splash-screen": "~0.27.6", + "expo-sharing": "~12.0.1", + "expo-splash-screen": "^56.0.10", "expo-status-bar": "~1.12.1", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.74.5", + "react-native": "^0.85.3", + "react-native-gesture-handler": "~2.16.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", "react-native-web": "~0.19.10" @@ -27,9 +37,15 @@ "devDependencies": { "@babel/core": "^7.24.0", "@types/react": "~18.2.79", - "typescript": "~5.3.3" + "typescript": "~5.5.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -181,18 +197,6 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -367,92 +371,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", - "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", @@ -468,42 +386,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", - "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-decorators": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", @@ -536,124 +418,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", - "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", @@ -726,18 +490,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -750,42 +502,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", @@ -813,13 +529,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { + "node_modules/@babel/plugin-transform-async-generator-functions": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", - "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -860,6 +578,38 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, "node_modules/@babel/plugin-transform-classes": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", @@ -880,22 +630,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", - "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/template": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-destructuring": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", @@ -943,15 +677,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-function-name": { + "node_modules/@babel/plugin-transform-for-of": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", - "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -960,10 +693,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-literals": { + "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", - "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -1007,6 +740,21 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", @@ -1026,6 +774,37 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-parameters": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", @@ -1123,36 +902,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-pure-annotations": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", @@ -1189,52 +938,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", - "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", - "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", - "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", @@ -1270,43 +973,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-flow": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.29.7.tgz", - "integrity": "sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-transform-flow-strip-types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.29.7.tgz", - "integrity": "sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-transform-react-display-name": "^7.29.7", - "@babel/plugin-transform-react-jsx": "^7.29.7", - "@babel/plugin-transform-react-jsx-development": "^7.29.7", - "@babel/plugin-transform-react-pure-annotations": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/preset-typescript": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", @@ -1326,25 +992,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/register": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz", - "integrity": "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -1399,211 +1046,76 @@ "node": ">=6.9.0" } }, - "node_modules/@expo/bunyan": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.1.tgz", - "integrity": "sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==", + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", "license": "MIT", "dependencies": { - "uuid": "^8.0.0" + "@types/hammerjs": "^2.0.36" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.8.0" } }, - "node_modules/@expo/cli": { - "version": "0.18.31", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-0.18.31.tgz", - "integrity": "sha512-v9llw9fT3Uv+TCM6Xllo54t672CuYtinEQZ2LPJ2EJsCwuTc4Cd2gXQaouuIVD21VoeGQnr5JtJuWbF97sBKzQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.20.0", - "@expo/code-signing-certificates": "0.0.5", - "@expo/config": "~9.0.0-beta.0", - "@expo/config-plugins": "~8.0.8", - "@expo/devcert": "^1.0.0", - "@expo/env": "~0.3.0", - "@expo/image-utils": "^0.5.0", - "@expo/json-file": "^8.3.0", - "@expo/metro-config": "0.18.11", - "@expo/osascript": "^2.0.31", - "@expo/package-manager": "^1.5.0", - "@expo/plist": "^0.1.0", - "@expo/prebuild-config": "7.0.9", - "@expo/rudder-sdk-node": "1.1.1", - "@expo/spawn-async": "^1.7.2", - "@expo/xcpretty": "^4.3.0", - "@react-native/dev-middleware": "0.74.85", - "@urql/core": "2.3.6", - "@urql/exchange-retry": "0.3.0", - "accepts": "^1.3.8", - "arg": "5.0.2", - "better-opn": "~3.0.2", - "bplist-creator": "0.0.7", - "bplist-parser": "^0.3.1", - "cacache": "^18.0.2", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "connect": "^3.7.0", - "debug": "^4.3.4", - "env-editor": "^0.4.1", - "fast-glob": "^3.3.2", - "find-yarn-workspace-root": "~2.0.0", - "form-data": "^3.0.1", - "freeport-async": "2.0.0", - "fs-extra": "~8.1.0", - "getenv": "^1.0.0", - "glob": "^7.1.7", - "graphql": "15.8.0", - "graphql-tag": "^2.10.1", - "https-proxy-agent": "^5.0.1", - "internal-ip": "4.3.0", - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1", - "js-yaml": "^3.13.1", - "json-schema-deref-sync": "^0.13.0", - "lodash.debounce": "^4.0.8", - "md5hex": "^1.0.0", - "minimatch": "^3.0.4", - "node-fetch": "^2.6.7", - "node-forge": "^1.3.1", - "npm-package-arg": "^7.0.0", - "open": "^8.3.0", - "ora": "3.4.0", - "picomatch": "^3.0.1", - "pretty-bytes": "5.6.0", - "progress": "2.0.3", - "prompts": "^2.3.2", - "qrcode-terminal": "0.11.0", - "require-from-string": "^2.0.2", - "requireg": "^0.2.2", - "resolve": "^1.22.2", - "resolve-from": "^5.0.0", - "resolve.exports": "^2.0.2", - "semver": "^7.6.0", - "send": "^0.18.0", - "slugify": "^1.3.4", - "source-map-support": "~0.5.21", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "tar": "^6.0.5", - "temp-dir": "^2.0.0", - "tempy": "^0.7.1", - "terminal-link": "^2.1.1", - "text-table": "^0.2.0", - "url-join": "4.0.0", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1" - }, - "bin": { - "expo-internal": "build/bin/cli" - } - }, - "node_modules/@expo/cli/node_modules/form-data": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", - "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@expo/cli/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "node_modules/@expo-google-fonts/material-symbols": { + "version": "0.4.38", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.38.tgz", + "integrity": "sha512-IJkBtN1o8u9BW5fvSii1MyHPQ7Q0HxbWcVBvOrOzgMLpVtZw7R2w94wBTVR7kZwv3w1JNTESMmLA5Sqn1+Z36A==", + "license": "MIT AND Apache-2.0" }, "node_modules/@expo/code-signing-certificates": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz", - "integrity": "sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", + "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", "license": "MIT", "dependencies": { - "node-forge": "^1.2.1", - "nullthrows": "^1.1.1" + "node-forge": "^1.3.3" } }, "node_modules/@expo/config": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-9.0.4.tgz", - "integrity": "sha512-g5ns5u1JSKudHYhjo1zaSfkJ/iZIcWmUmIQptMJZ6ag1C0ShL2sj8qdfU8MmAMuKLOgcIfSaiWlQnm4X3VJVkg==", + "version": "56.0.9", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-56.0.9.tgz", + "integrity": "sha512-/lqFeWGSrhpKJVP8tTN8LjuoIe8u8q2w7FzBL0C+wHgl+WM8l1qUIEYWy/sMvsG/NbpUIUsDHJRhQvOkU58eIw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~8.0.8", - "@expo/config-types": "^51.0.3", - "@expo/json-file": "^8.3.0", - "getenv": "^1.0.0", - "glob": "7.1.6", - "require-from-string": "^2.0.2", - "resolve-from": "^5.0.0", + "@expo/config-plugins": "~56.0.8", + "@expo/config-types": "^56.0.5", + "@expo/json-file": "^10.2.0", + "@expo/require-utils": "^56.1.3", + "deepmerge": "^4.3.1", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", - "slugify": "^1.3.4", - "sucrase": "3.34.0" + "slugify": "^1.3.4" } }, "node_modules/@expo/config-plugins": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.11.tgz", - "integrity": "sha512-oALE1HwnLFthrobAcC9ocnR9KXLzfWEjgIe4CPe+rDsfC6GDs8dGYCXfRFoCEzoLN4TGYs9RdZ8r0KoCcNrm2A==", + "version": "56.0.8", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-56.0.8.tgz", + "integrity": "sha512-phTuyBhgVLfqUHMjQkAfRtbyoY6yTxoKja1awtpVnEkoJDxPJuXx1KX5uvq1eZtt4bJQ08OBJ6P95INqRSHpRg==", "license": "MIT", "dependencies": { - "@expo/config-types": "^51.0.3", - "@expo/json-file": "~8.3.0", - "@expo/plist": "^0.1.0", + "@expo/config-types": "^56.0.5", + "@expo/json-file": "~10.2.0", + "@expo/plist": "^0.7.0", + "@expo/require-utils": "^56.1.3", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", - "debug": "^4.3.1", - "find-up": "~5.0.0", - "getenv": "^1.0.0", - "glob": "7.1.6", - "resolve-from": "^5.0.0", + "debug": "^4.3.5", + "getenv": "^2.0.0", + "glob": "^13.0.0", "semver": "^7.5.4", - "slash": "^3.0.0", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, - "node_modules/@expo/config-plugins/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@expo/config-plugins/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1613,45 +1125,15 @@ } }, "node_modules/@expo/config-types": { - "version": "51.0.3", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-51.0.3.tgz", - "integrity": "sha512-hMfuq++b8VySb+m9uNNrlpbvGxYc8OcFCUX9yTmi9tlx6A4k8SDabWFBgmnr4ao3wEArvWrtUQIfQCVtPRdpKA==", + "version": "56.0.5", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-56.0.5.tgz", + "integrity": "sha512-GsAHO/MwW9ZRdgnmyfRXqVGLCP/zejD6rWnp5OROp8mBGRObKm4HfrjlUyT1skjMwCj1OrURx9ZfIc6yeBAkIA==", "license": "MIT" }, - "node_modules/@expo/config/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@expo/config/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@expo/config/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1679,86 +1161,84 @@ "ms": "^2.1.1" } }, + "node_modules/@expo/devtools": { + "version": "56.0.2", + "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-56.0.2.tgz", + "integrity": "sha512-ANl4kPdbe0/HQYWkDEN79S6bQhI+i/ZCnPxuC853pPsB4svhINC7Ku9lmGOKPsUUWWnrHg1spkDGQBZ4sD6JxQ==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@expo/dom-webview": { + "version": "56.0.5", + "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-56.0.5.tgz", + "integrity": "sha512-UIEJxkLg6cHqofKrpWpkn9E6ApxVRtCgZhZkARPr9VV7rBVloJgeroTHs31YgU/JpbI5lLQOnfOlGo54W6C2Ew==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/@expo/env": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-0.3.0.tgz", - "integrity": "sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.3.0.tgz", + "integrity": "sha512-9HnnIbzwTTdbwSjNLXTk0fPm9ZwMJ7c1/31tsni8HZ8Q62KzYCyspahH+V365vg5J6lr001DzNwBxVWSaYCQLg==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", - "dotenv": "~16.4.5", - "dotenv-expand": "~11.0.6", - "getenv": "^1.0.0" + "getenv": "^2.0.0" + }, + "engines": { + "node": ">=20.12.0" } }, - "node_modules/@expo/image-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.5.1.tgz", - "integrity": "sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A==", + "node_modules/@expo/expo-modules-macros-plugin": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@expo/expo-modules-macros-plugin/-/expo-modules-macros-plugin-0.0.9.tgz", + "integrity": "sha512-odai6D7ng/gA7At8ukFcWcauNEeDdyVqzVPbQxDkyU2NTJ4kgphA4I5iigS5C4LXFicSIzEt2nzdlLM8sjsTdA==", + "license": "MIT" + }, + "node_modules/@expo/fingerprint": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.19.4.tgz", + "integrity": "sha512-PsowRlO8+S7JlO8go7yhNEXp7sqlsWDE2AlCwoss7zH0dcajXFo74Fy0KdXEc4UXK7kKoHD37oDgsZ8aHSLr7A==", "license": "MIT", "dependencies": { - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.0.0", - "fs-extra": "9.0.0", - "getenv": "^1.0.0", - "jimp-compact": "0.16.1", - "node-fetch": "^2.6.0", - "parse-png": "^2.1.0", + "@expo/env": "^2.3.0", + "@expo/spawn-async": "^1.8.0", + "arg": "^5.0.2", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "ignore": "^5.3.1", + "minimatch": "^10.2.2", "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "tempy": "0.3.0" - } - }, - "node_modules/@expo/image-utils/node_modules/crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@expo/image-utils/node_modules/fs-extra": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", - "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "semver": "^7.6.0" }, - "engines": { - "node": ">=10" + "bin": { + "fingerprint": "bin/cli.js" } }, - "node_modules/@expo/image-utils/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@expo/image-utils/node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/image-utils/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "node_modules/@expo/fingerprint/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1767,139 +1247,153 @@ "node": ">=10" } }, - "node_modules/@expo/image-utils/node_modules/temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@expo/image-utils/node_modules/tempy": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", - "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", + "node_modules/@expo/image-utils": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.10.1.tgz", + "integrity": "sha512-YDeefvmYdihS7Wp3ESDUVnOgOSWmj2Cczm9lVNDdm4MqQLdAKm/LPYg83HtFQPfefRlAxyHrQR/O9kIXN9C1Wg==", "license": "MIT", "dependencies": { - "temp-dir": "^1.0.0", - "type-fest": "^0.3.1", - "unique-string": "^1.0.0" - }, - "engines": { - "node": ">=8" + "@expo/require-utils": "^56.1.3", + "@expo/spawn-async": "^1.8.0", + "chalk": "^4.0.0", + "getenv": "^2.0.0", + "jimp-compact": "0.16.1", + "parse-png": "^2.1.0", + "semver": "^7.6.0" } }, - "node_modules/@expo/image-utils/node_modules/type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=6" - } - }, - "node_modules/@expo/image-utils/node_modules/unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@expo/image-utils/node_modules/universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/json-file": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.3.3.tgz", - "integrity": "sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "~7.10.4", - "json5": "^2.2.2", - "write-file-atomic": "^2.3.0" - } - }, - "node_modules/@expo/json-file/node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@expo/metro-config": { - "version": "0.18.11", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-0.18.11.tgz", - "integrity": "sha512-/uOq55VbSf9yMbUO1BudkUM2SsGW1c5hr9BnhIqYqcsFv0Jp5D3DtJ4rljDKaUeNLbwr6m7pqIrkSMq5NrYf4Q==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.5", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", - "@expo/config": "~9.0.0-beta.0", - "@expo/env": "~0.3.0", - "@expo/json-file": "~8.3.0", - "@expo/spawn-async": "^1.7.2", - "chalk": "^4.1.0", - "debug": "^4.3.2", - "find-yarn-workspace-root": "~2.0.0", - "fs-extra": "^9.1.0", - "getenv": "^1.0.0", - "glob": "^7.2.3", - "jsc-safe-url": "^0.2.4", - "lightningcss": "~1.19.0", - "postcss": "~8.4.32", - "resolve-from": "^5.0.0" - } - }, - "node_modules/@expo/metro-config/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "node_modules/@expo/image-utils/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, - "node_modules/@expo/metro-config/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "node_modules/@expo/inline-modules": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.0.11.tgz", + "integrity": "sha512-ZlIfKL61DPnW8YUTdMEjMA31xrDDV6p7Xi8rWYyhd5qXBV8MwGwjuJ7vKeaVaMjRqxJk1N9lv7zlfyvQpRCNNw==", "license": "MIT", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "@expo/config-plugins": "~56.0.8" } }, - "node_modules/@expo/metro-config/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@expo/json-file": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.2.0.tgz", + "integrity": "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ==", "license": "MIT", - "engines": { - "node": ">= 10.0.0" + "dependencies": { + "@babel/code-frame": "^7.20.0", + "json5": "^2.2.3" + } + }, + "node_modules/@expo/local-build-cache-provider": { + "version": "56.0.8", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-56.0.8.tgz", + "integrity": "sha512-UsuXwpNi57MNhzZ3be4XThc8xW6nzk3Wu37s1+2qcfZGeJcMLKDFfwO6n8YXeIiGlCsOi0Ee1rsTdgjrKt/YJQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~56.0.9", + "chalk": "^4.1.2" + } + }, + "node_modules/@expo/log-box": { + "version": "56.0.12", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-56.0.12.tgz", + "integrity": "sha512-budE6AGmJbpOJfGSOz+JVP3+FevElT82IEIg+ukQ4gZpW/dGO7QX1unFjanKdSaYgudBwJ4FCFGMwWhW/1tXVQ==", + "license": "MIT", + "dependencies": { + "@expo/dom-webview": "^56.0.5", + "anser": "^1.4.9", + "stacktrace-parser": "^0.1.10" + }, + "peerDependencies": { + "@expo/dom-webview": "^56.0.5", + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@expo/metro": { + "version": "56.0.0", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-56.0.0.tgz", + "integrity": "sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A==", + "license": "MIT", + "dependencies": { + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4" + } + }, + "node_modules/@expo/metro-config": { + "version": "56.0.13", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-56.0.13.tgz", + "integrity": "sha512-OPyNYiex/6Ms8zT2POdIZsLhcAZYk7O+yJvpz5uG/4QRA7aiESfCy1I+0YHewMlR4P1YQeyxIrfTurs6m9xfZA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.5", + "@expo/config": "~56.0.9", + "@expo/env": "~2.3.0", + "@expo/json-file": "~10.2.0", + "@expo/metro": "~56.0.0", + "@expo/require-utils": "^56.1.3", + "@expo/spawn-async": "^1.8.0", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.5.5", + "browserslist": "^4.25.0", + "chalk": "^4.1.0", + "debug": "^4.3.2", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "hermes-parser": "^0.33.3", + "jsc-safe-url": "^0.2.4", + "lightningcss": "^1.30.1", + "msgpackr": "^2.0.1", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "expo": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@expo/metro-file-map": { + "version": "56.0.3", + "resolved": "https://registry.npmjs.org/@expo/metro-file-map/-/metro-file-map-56.0.3.tgz", + "integrity": "sha512-5OGW3z8LgEYgMJOR7F3pC8llFLkb1fVqwAewbCl6S4Vkha8AFQMwOjT+9Wbka+V4rmpljpGqOnMhF4xZbD961w==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "fb-watchman": "^2.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" } }, "node_modules/@expo/metro-runtime": { @@ -1937,134 +1431,45 @@ "resolve-workspace-root": "^2.0.0" } }, - "node_modules/@expo/package-manager/node_modules/@expo/json-file": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.2.0.tgz", - "integrity": "sha512-S6XzKe3R9GQeHiUPXc3xJjOv2VJhOEwFYf7xdC2z2cUqt3kZJ9mSO877sNQloVdnW/SUCtPY3bexlM7nwq+CAQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.20.0", - "json5": "^2.2.3" - } - }, - "node_modules/@expo/package-manager/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@expo/package-manager/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/@expo/package-manager/node_modules/npm-package-arg": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", - "license": "ISC", - "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@expo/package-manager/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/package-manager/node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@expo/plist": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.1.3.tgz", - "integrity": "sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.7.0.tgz", + "integrity": "sha512-vrpryU1GoqSIRNqRB2D3IjXDmzNYfiQpEF6AH/xknlD7eiYmEDt3mb26V7cLcedcPG8PY/1xWHdBXVQJfEAh6Q==", "license": "MIT", "dependencies": { - "@xmldom/xmldom": "~0.7.7", - "base64-js": "^1.2.3", - "xmlbuilder": "^14.0.0" + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" } }, "node_modules/@expo/prebuild-config": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-7.0.9.tgz", - "integrity": "sha512-9i6Cg7jInpnGEHN0jxnW0P+0BexnePiBzmbUvzSbRXpdXihYUX2AKMu73jgzxn5P1hXOSkzNS7umaY+BZ+aBag==", + "version": "56.0.15", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-56.0.15.tgz", + "integrity": "sha512-6GC+QjdCkzp/5wjsqgfu/B2+2yf5MyZMtzf9szIPrLt9uKhzV2PdyM0vU0kvbj1YT8weHCtO7bsrzimman0sjA==", "license": "MIT", "dependencies": { - "@expo/config": "~9.0.0-beta.0", - "@expo/config-plugins": "~8.0.8", - "@expo/config-types": "^51.0.3", - "@expo/image-utils": "^0.5.0", - "@expo/json-file": "^8.3.0", - "@react-native/normalize-colors": "0.74.85", + "@expo/config": "~56.0.9", + "@expo/config-plugins": "~56.0.8", + "@expo/config-types": "^56.0.5", + "@expo/image-utils": "^0.10.1", + "@expo/json-file": "^10.2.0", + "@react-native/normalize-colors": "0.85.3", "debug": "^4.3.1", - "fs-extra": "^9.0.0", + "expo-modules-autolinking": "~56.0.15", "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "xml2js": "0.6.0" - }, - "peerDependencies": { - "expo-modules-autolinking": ">=0.8.1" + "semver": "^7.6.0" } }, - "node_modules/@expo/prebuild-config/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/prebuild-config/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } + "node_modules/@expo/prebuild-config/node_modules/@react-native/normalize-colors": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.3.tgz", + "integrity": "sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw==", + "license": "MIT" }, "node_modules/@expo/prebuild-config/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2073,51 +1478,37 @@ "node": ">=10" } }, - "node_modules/@expo/prebuild-config/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@expo/rudder-sdk-node": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz", - "integrity": "sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==", + "node_modules/@expo/require-utils": { + "version": "56.1.3", + "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-56.1.3.tgz", + "integrity": "sha512-KyLeOn/zzQSvuPpV5YhB/FPKnpQytno4luN918bGdPDssLBoS3N/0UbC3W0rJAn9kSFu+XpfR81eABRVsSdfgQ==", "license": "MIT", "dependencies": { - "@expo/bunyan": "^4.0.0", - "@segment/loosely-validate-event": "^2.0.0", - "fetch-retry": "^4.1.1", - "md5": "^2.2.1", - "node-fetch": "^2.6.1", - "remove-trailing-slash": "^0.1.0", - "uuid": "^8.3.2" + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, + "node_modules/@expo/schema-utils": { + "version": "56.0.1", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-56.0.1.tgz", + "integrity": "sha512-CZ/+mYbQmWeOnkCGlWy9K+lFxbJSMFY7+TqBZcKzBSTU5Q7IGRvn/sOG3TdNjIdLPmbA8xe7R/c3UUQ28R9i9w==", + "license": "MIT" + }, "node_modules/@expo/sdk-runtime-versions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==", "license": "MIT" }, - "node_modules/@expo/server": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@expo/server/-/server-0.4.4.tgz", - "integrity": "sha512-q9ADBzMN5rZ/fgQ2mz5YIJuZ8gelQlhG2CQqToD+UvBLZvbaHCNxTTSs2KI1LzJvAaW5CWgWMatGvGF6iUQ0LA==", - "license": "MIT", - "dependencies": { - "@remix-run/node": "^2.7.2", - "abort-controller": "^3.0.0", - "debug": "^4.3.4", - "source-map-support": "~0.5.21" - } - }, "node_modules/@expo/spawn-async": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.8.0.tgz", @@ -2136,17 +1527,45 @@ "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", "license": "MIT" }, - "node_modules/@expo/vector-icons": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.1.0.tgz", - "integrity": "sha512-7T09UE9h8QDTsUeMGymB4i+iqvtEeaO5VvUjryFB4tugDTG/bkzViWA74hm5pfjjDEhYMXWaX112mcvhccmIwQ==", + "node_modules/@expo/ui": { + "version": "56.0.16", + "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-56.0.16.tgz", + "integrity": "sha512-NPbpseOC4VNoDvOBgGtTb63fu2IfdnxNwp9K8pqU2mBTQPxEMcxF0ijgNBJ0Ze1NEuLJoBhJyrHWoj3zRmovkA==", "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.1.0", + "vaul": "^1.1.2" + }, "peerDependencies": { - "expo-font": "*", + "@babel/core": "*", + "expo": "*", "react": "*", - "react-native": "*" + "react-dom": "*", + "react-native": "*", + "react-native-reanimated": "*", + "react-native-worklets": "*" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-reanimated": { + "optional": true + }, + "react-native-worklets": { + "optional": true + } } }, + "node_modules/@expo/ws-tunnel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz", + "integrity": "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==", + "license": "MIT" + }, "node_modules/@expo/xcpretty": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.4.tgz", @@ -2161,144 +1580,6 @@ "excpretty": "build/cli.js" } }, - "node_modules/@expo/xcpretty/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/@expo/xcpretty/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@graphql-typed-document-node/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", - "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", - "license": "MIT", - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -2308,155 +1589,6 @@ "node": ">=12" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2470,17 +1602,20 @@ } }, "node_modules/@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -2538,1943 +1673,493 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz", + "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==", + "cpu": [ + "arm64" + ], "license": "MIT", "optional": true, - "engines": { - "node": ">=14" - } + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz", + "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz", + "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz", + "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz", + "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz", + "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", - "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10" + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.5.tgz", + "integrity": "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" + "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.9.tgz", - "integrity": "sha512-hFJL4cgLPxncJJd/epQ4dHnMg5Jy/7Q56jFvA3MHViuKpzzfTCJCB+pGY54maZbtym53UJON9WTGpM3S81UfjQ==", + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", "license": "MIT", "dependencies": { - "@react-native-community/cli-clean": "13.6.9", - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-doctor": "13.6.9", - "@react-native-community/cli-hermes": "13.6.9", - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native-community/cli-types": "13.6.9", - "chalk": "^4.1.2", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "prompts": "^2.4.2", - "semver": "^7.5.2" + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" }, - "bin": { - "rnc-cli": "build/bin.js" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=18" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-13.6.9.tgz", - "integrity": "sha512-7Dj5+4p9JggxuVNOjPbduZBAP1SUgNhLKVw5noBUzT/3ZpUZkDM+RCSwyoyg8xKWoE4OrdUAXwAFlMcFDPKykA==", + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", "license": "MIT", "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2" + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", + "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "@radix-ui/react-use-callback-ref": "1.1.2" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", "license": "MIT", - "engines": { - "node": ">=10" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@react-native-community/netinfo": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.1.tgz", + "integrity": "sha512-UBnJxyV0b7i9Moa97Av+HKho1ByzX0DtbJXzUQS5E3xhQs6P2D/Os0iw3ouy7joY1TVd6uIhplPbr7l1SJNaNQ==", "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-13.6.9.tgz", - "integrity": "sha512-rFfVBcNojcMm+KKHE/xqpqXg8HoKl4EC7bFHUrahMJ+y/tZll55+oX/PGG37rzB8QzP2UbMQ19DYQKC1G7kXeg==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "cosmiconfig": "^5.1.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-13.6.9.tgz", - "integrity": "sha512-TkN7IdFmGPPvTpAo3nCAH9uwGCPxWBEAwpqEZDrq0NWllI7Tdie8vDpGdrcuCcKalmhq6OYnkXzeBah7O1Ztpw==", - "license": "MIT", - "dependencies": { - "serve-static": "^1.13.1" - } - }, - "node_modules/@react-native-community/cli-doctor": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-13.6.9.tgz", - "integrity": "sha512-5quFaLdWFQB+677GXh5dGU9I5eg2z6Vg4jOX9vKnc9IffwyIFAyJfCZHrxLSRPDGNXD7biDQUdoezXYGwb6P/A==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-config": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-apple": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.10.0", - "execa": "^5.0.0", - "hermes-profile-transformer": "^0.0.6", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "semver": "^7.5.2", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react-native": ">=0.59" } }, - "node_modules/@react-native-community/cli-hermes": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-13.6.9.tgz", - "integrity": "sha512-GvwiwgvFw4Ws+krg2+gYj8sR3g05evmNjAHkKIKMkDTJjZ8EdyxbkifRUs1ZCq3TMZy2oeblZBXCJVOH4W7ZbA==", + "node_modules/@react-native-masked-view/masked-view": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz", + "integrity": "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==", "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-13.6.9.tgz", - "integrity": "sha512-9KsYGdr08QhdvT3Ht7e8phQB3gDX9Fs427NJe0xnoBh+PDPTI2BD5ks5ttsH8CzEw8/P6H8tJCHq6hf2nxd9cw==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.2.4", - "logkitty": "^0.7.1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-13.6.9.tgz", - "integrity": "sha512-KoeIHfhxMhKXZPXmhQdl6EE+jGKWwoO9jUVWgBvibpVmsNjo7woaG/tfJMEWfWF3najX1EkQAoJWpCDBMYWtlA==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-tools": "13.6.9", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.0.12", - "ora": "^5.4.1" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-apple/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-13.6.9.tgz", - "integrity": "sha512-CiUcHlGs8vE0CAB4oi1f+dzniqfGuhWPNrDvae2nm8dewlahTBwIcK5CawyGezjcJoeQhjBflh9vloska+nlnw==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-platform-apple": "13.6.9" - } - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-13.6.9.tgz", - "integrity": "sha512-W8FSlCPWymO+tlQfM3E0JmM8Oei5HZsIk5S0COOl0MRi8h0NmHI4WSTF2GCfbFZkcr2VI/fRsocoN8Au4EZAug==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-debugger-ui": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^6.2.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz", - "integrity": "sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", - "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native-community/cli-tools": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-13.6.9.tgz", - "integrity": "sha512-OXaSjoN0mZVw3nrAwcY1PC0uMfyTd9fz7Cy06dh+EJc+h0wikABsVRzV8cIOPrVV+PPEEXE0DBrH20T2puZzgQ==", - "license": "MIT", - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^7.5.2", - "shell-quote": "^1.7.3", - "sudo-prompt": "^9.0.0" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "license": "MIT", - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-types": { - "version": "13.6.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-13.6.9.tgz", - "integrity": "sha512-RLxDppvRxXfs3hxceW/mShi+6o5yS+kFPnPqZTaMKKR5aSg7LwDpLQW4K2D22irEG8e6RKDkZUeH9aL3vO2O0w==", - "license": "MIT", - "dependencies": { - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react": ">=16", + "react-native": ">=0.57" } }, "node_modules/@react-native/assets-registry": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.74.87.tgz", - "integrity": "sha512-1XmRhqQchN+pXPKEKYdpJlwESxVomJOxtEnIkbo7GAlaN2sym84fHEGDXAjLilih5GVPpcpSmFzTy8jx3LtaFg==", + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.85.3.tgz", + "integrity": "sha512-u9ZiYP23vA2IFtdFQFmetzSmk6SM0xgKIoiOsr1hXNHjHaLhOm+/Ph1ud57wX6+Dbwdzx8coJgnzSKL3W21PCg==", "license": "MIT", "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", - "integrity": "sha512-+vJYpMnENFrwtgvDfUj+CtVJRJuUnzAUYT0/Pb68Sq9RfcZ5xdcCuUgyf7JO+akW2VTBoJY427wkcxU30qrWWw==", + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.3.tgz", + "integrity": "sha512-Wc94zGfeFG8Njf9SHMPfYZP04kjigkOps6F1TYTvd7ZVXuGxqseCDgxc50LWcOhOCLypI9n3oVVqz81C3p44ZA==", "license": "MIT", "dependencies": { - "@react-native/codegen": "0.74.87" + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.85.3" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.74.87.tgz", - "integrity": "sha512-hyKpfqzN2nxZmYYJ0tQIHG99FQO0OWXp/gVggAfEUgiT+yNKas1C60LuofUsK7cd+2o9jrpqgqW4WzEDZoBlTg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.18.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", - "@babel/plugin-proposal-numeric-separator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.20.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.20.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.18.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.20.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.20.0", - "@babel/plugin-transform-flow-strip-types": "^7.20.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.11", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "@react-native/babel-plugin-codegen": "0.74.87", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/core": "*" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/codegen": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.74.87.tgz", - "integrity": "sha512-GMSYDiD+86zLKgMMgz9z0k6FxmRn+z6cimYZKkucW4soGbxWsbjUAZoZ56sJwt2FJ3XVRgXCrnOCgXoH/Bkhcg==", + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz", + "integrity": "sha512-/JkS1lGLyzBWP1FbgDwaqEf7qShIC6pUC1M0a/YMAd/v4iqR24MRkQWe7jkYvcBQ2LpEhs5NGE9InhxSv21zCA==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.0", - "glob": "^7.1.1", - "hermes-parser": "0.19.1", + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.33.3", "invariant": "^2.2.4", - "jscodeshift": "^0.14.0", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/@react-native/codegen/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.74.87.tgz", - "integrity": "sha512-EgJG9lSr8x3X67dHQKQvU6EkO+3ksVlJHYIVv6U/AmW9dN80BEFxgYbSJ7icXS4wri7m4kHdgeq2PQ7/3vvrTQ==", - "license": "MIT", - "dependencies": { - "@react-native-community/cli-server-api": "13.6.9", - "@react-native-community/cli-tools": "13.6.9", - "@react-native/dev-middleware": "0.74.87", - "@react-native/metro-babel-transformer": "0.74.87", - "chalk": "^4.0.0", - "execa": "^5.1.1", - "metro": "^0.80.3", - "metro-config": "^0.80.3", - "metro-core": "^0.80.3", - "node-fetch": "^2.2.0", - "querystring": "^0.2.1", - "readline": "^1.3.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.87.tgz", - "integrity": "sha512-MN95DJLYTv4EqJc+9JajA3AJZSBYJz2QEJ3uWlHrOky2vKrbbRVaW1ityTmaZa2OXIvNc6CZwSRSE7xCoHbXhQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.87.tgz", - "integrity": "sha512-7TmZ3hTHwooYgIHqc/z87BMe1ryrIqAUi+AF7vsD+EHCGxHFdMjSpf1BZ2SUPXuLnF2cTiTfV2RwhbPzx0tYIA==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.87", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", - "chrome-launcher": "^0.15.2", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/@react-native/community-cli-plugin/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/community-cli-plugin/node_modules/ws": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", - "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.74.85.tgz", - "integrity": "sha512-gUIhhpsYLUTYWlWw4vGztyHaX/kNlgVspSvKe2XaPA7o3jYKUoNLc3Ov7u70u/MBWfKdcEffWq44eSe3j3s5JQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware": { - "version": "0.74.85", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.74.85.tgz", - "integrity": "sha512-BRmgCK5vnMmHaKRO+h8PKJmHHH3E6JFuerrcfE3wG2eZ1bcSr+QTu8DAlpxsDWvJvHpCi8tRJGauxd+Ssj/c7w==", - "license": "MIT", - "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.74.85", - "@rnx-kit/chromium-edge-launcher": "^1.0.0", - "chrome-launcher": "^0.15.2", - "connect": "^3.6.5", - "debug": "^2.2.0", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "selfsigned": "^2.4.1", - "serve-static": "^1.13.1", - "temp-dir": "^2.0.0", - "ws": "^6.2.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/@react-native/dev-middleware/node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native/dev-middleware/node_modules/ws": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", - "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.74.87.tgz", - "integrity": "sha512-T+VX0N1qP+U9V4oAtn7FTX7pfsoVkd1ocyw9swYXgJqU2fK7hC9famW7b3s3ZiufPGPr1VPJe2TVGtSopBjL6A==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/js-polyfills": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.74.87.tgz", - "integrity": "sha512-M5Evdn76CuVEF0GsaXiGi95CBZ4IWubHqwXxV9vG9CC9kq0PSkoM2Pn7Lx7dgyp4vT7ccJ8a3IwHbe+5KJRnpw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@react-native/metro-babel-transformer": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.74.87.tgz", - "integrity": "sha512-UsJCO24sNax2NSPBmV1zLEVVNkS88kcgAiYrZHtYSwSjpl4WZ656tIeedBfiySdJ94Hr3kQmBYLipV5zk0NI1A==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.0", - "@react-native/babel-preset": "0.74.87", - "hermes-parser": "0.19.1", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { "@babel/core": "*" } }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.85.3.tgz", + "integrity": "sha512-fs85dmbIqNmtzEixDb0g+q6R3Vt4H9eAt8/inIZdDKfjN76+sUJA2r1nxODQ76bU23MrIbz8sI7KFBPaWk/zQw==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.85.3", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", + "semver": "^7.1.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.85.3" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.85.3.tgz", + "integrity": "sha512-uAu7rM5o/Np1zgp6fi5zM1sP1aB8DcS7DdOLcj/TkSutOAjkMqqd2lWt1/+3S7qXexRHVK5XcP+o3VXo4L/V0A==", + "license": "BSD-3-Clause", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.3.tgz", + "integrity": "sha512-/jRAaT9boiCttIcEwS02WPwYkUihqsjSaK/TMtHz05vT6uMgac9PaQt5kzBQLIABv5aEIa5gtrMmKVz49MjkjQ==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.85.3.tgz", + "integrity": "sha512-JYzBiT4A8w+KQt+dOD5v+ti+tDrGoPnsSTuApq3Ls4RB5sfWbDlYMyz3dbc8qBIHz9tv0sQ5+eOu6Xwqzr5AQA==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.85.3", + "@react-native/debugger-shell": "0.85.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.85.3.tgz", + "integrity": "sha512-39dY2j50Q1pntejzwt3XL7vwXtrj8jcIfHq6E+gyu3jzYxZJVvMkMutQ39vSg6zinIQOX36oQDhidXUbCXzgoA==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.3.tgz", + "integrity": "sha512-U2+aMshIXf1uFn77tpBb/xhHWB9vkVrMpt7kkucAugF8hJKYTDGB587X7WwelHduK2KBfhl4giSv0rzZGoef9A==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, "node_modules/@react-native/normalize-colors": { "version": "0.74.85", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.85.tgz", "integrity": "sha512-pcE4i0X7y3hsAE0SpIl7t6dUc0B0NZLd1yv7ssm4FrLhWG+CGyIq4eFDXpmPU1XHmL5PPySxTAjEMiwv6tAmOw==", "license": "MIT" }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.74.87.tgz", - "integrity": "sha512-lsGxoFMb0lyK/MiplNKJpD+A1EoEUumkLrCjH4Ht+ZlG8S0BfCxmskLZ6qXn3BiDSkLjfjI/qyZ3pnxNBvkXpQ==", - "license": "MIT", - "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": "^18.2.6", - "react": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@react-navigation/bottom-tabs": { - "version": "6.5.20", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.20.tgz", - "integrity": "sha512-ow6Z06iS4VqBO8d7FP+HsGjJLWt2xTWIvuWjpoCvsM/uQXzCRDIjBv9HaKcXbF0yTW7IMir0oDAbU5PFzEDdgA==", - "deprecated": "This version is no longer supported", - "license": "MIT", - "dependencies": { - "@react-navigation/elements": "^1.3.30", - "color": "^4.2.3", - "warn-once": "^0.1.0" - }, - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-safe-area-context": ">= 3.0.0", - "react-native-screens": ">= 3.0.0" - } - }, - "node_modules/@react-navigation/core": { - "version": "6.4.17", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", - "integrity": "sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==", - "deprecated": "This version is no longer supported", - "license": "MIT", - "dependencies": { - "@react-navigation/routers": "^6.1.9", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.23", - "query-string": "^7.1.3", - "react-is": "^16.13.0", - "use-latest-callback": "^0.2.1" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/@react-navigation/elements": { - "version": "1.3.31", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.31.tgz", - "integrity": "sha512-bUzP4Awlljx5RKEExw8WYtif8EuQni2glDaieYROKTnaxsu9kEIA515sXQgUDZU4Ob12VoL7+z70uO3qrlfXcQ==", - "deprecated": "This version is no longer supported", - "license": "MIT", - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-safe-area-context": ">= 3.0.0" - } - }, - "node_modules/@react-navigation/native": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", - "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", - "deprecated": "This version is no longer supported", - "license": "MIT", - "dependencies": { - "@react-navigation/core": "^6.4.17", - "escape-string-regexp": "^4.0.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^3.1.23" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/@react-navigation/native-stack": { - "version": "6.9.26", - "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.26.tgz", - "integrity": "sha512-++dueQ+FDj2XkZ902DVrK79ub1vp19nSdAZWxKRgd6+Bc0Niiesua6rMCqymYOVaYh+dagwkA9r00bpt/U5WLw==", - "license": "MIT", - "dependencies": { - "@react-navigation/elements": "^1.3.30", - "warn-once": "^0.1.0" - }, - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-safe-area-context": ">= 3.0.0", - "react-native-screens": ">= 3.0.0" - } - }, - "node_modules/@react-navigation/routers": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", - "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", - "deprecated": "This version is no longer supported", - "license": "MIT", - "dependencies": { - "nanoid": "^3.1.23" - } - }, - "node_modules/@remix-run/node": { - "version": "2.17.4", - "resolved": "https://registry.npmjs.org/@remix-run/node/-/node-2.17.4.tgz", - "integrity": "sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q==", - "license": "MIT", - "dependencies": { - "@remix-run/server-runtime": "2.17.4", - "@remix-run/web-fetch": "^4.4.2", - "@web3-storage/multipart-parser": "^1.0.0", - "cookie-signature": "^1.1.0", - "source-map-support": "^0.5.21", - "stream-slice": "^0.1.2", - "undici": "^6.21.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "typescript": "^5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@remix-run/server-runtime": { - "version": "2.17.4", - "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.4.tgz", - "integrity": "sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.2", - "@types/cookie": "^0.6.0", - "@web3-storage/multipart-parser": "^1.0.0", - "cookie": "^0.7.2", - "set-cookie-parser": "^2.4.8", - "source-map": "^0.7.3", - "turbo-stream": "2.4.1" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "typescript": "^5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@remix-run/web-blob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@remix-run/web-blob/-/web-blob-3.1.0.tgz", - "integrity": "sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==", - "license": "MIT", - "dependencies": { - "@remix-run/web-stream": "^1.1.0", - "web-encoding": "1.1.5" - } - }, - "node_modules/@remix-run/web-fetch": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@remix-run/web-fetch/-/web-fetch-4.4.2.tgz", - "integrity": "sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA==", - "license": "MIT", - "dependencies": { - "@remix-run/web-blob": "^3.1.0", - "@remix-run/web-file": "^3.1.0", - "@remix-run/web-form-data": "^3.1.0", - "@remix-run/web-stream": "^1.1.0", - "@web3-storage/multipart-parser": "^1.0.0", - "abort-controller": "^3.0.0", - "data-uri-to-buffer": "^3.0.1", - "mrmime": "^1.0.0" - }, - "engines": { - "node": "^10.17 || >=12.3" - } - }, - "node_modules/@remix-run/web-file": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@remix-run/web-file/-/web-file-3.1.0.tgz", - "integrity": "sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ==", - "license": "MIT", - "dependencies": { - "@remix-run/web-blob": "^3.1.0" - } - }, - "node_modules/@remix-run/web-form-data": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@remix-run/web-form-data/-/web-form-data-3.1.0.tgz", - "integrity": "sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A==", - "license": "MIT", - "dependencies": { - "web-encoding": "1.1.5" - } - }, - "node_modules/@remix-run/web-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@remix-run/web-stream/-/web-stream-1.1.0.tgz", - "integrity": "sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==", - "license": "MIT", - "dependencies": { - "web-streams-polyfill": "^3.1.1" - } - }, - "node_modules/@rnx-kit/chromium-edge-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", - "integrity": "sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.0.0", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=14.15" - } - }, - "node_modules/@segment/loosely-validate-event": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", - "integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==", - "dependencies": { - "component-type": "^1.2.1", - "join-component": "^1.1.0" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "license": "MIT" }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "license": "BSD-3-Clause", + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "license": "MIT" + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", "license": "MIT" }, "node_modules/@types/istanbul-lib-coverage": { @@ -4493,37 +2178,21 @@ } }, "node_modules/@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/prop-types": { @@ -4544,16 +2213,10 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "license": "MIT" - }, "node_modules/@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -4565,55 +2228,21 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, - "node_modules/@urql/core": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz", - "integrity": "sha512-PUxhtBh7/8167HJK6WqBv6Z0piuiaZHQGYbhwpNL9aIQmLROPEdaUYkY4wh45wPQXcTpnd11l0q3Pw+TI11pdw==", - "license": "MIT", - "dependencies": { - "@graphql-typed-document-node/core": "^3.1.0", - "wonka": "^4.0.14" - }, - "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/@urql/exchange-retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-0.3.0.tgz", - "integrity": "sha512-hHqer2mcdVC0eYnVNbWyi28AlGOPb2vjH3lP3/Bc8Lc8BjhMsDwFMm7WhoP5C1+cfbr/QJ6Er3H/L08wznXxfg==", - "license": "MIT", - "dependencies": { - "@urql/core": ">=2.3.1", - "wonka": "^4.0.14" - }, - "peerDependencies": { - "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/@web3-storage/multipart-parser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz", - "integrity": "sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==", - "license": "(Apache-2.0 AND MIT)" + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" }, "node_modules/@xmldom/xmldom": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", - "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", - "deprecated": "this version has critical issues, please update to the latest version", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", "license": "MIT", "engines": { "node": ">=10.0.0" } }, - "node_modules/@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "license": "(Unlicense OR Apache-2.0)", - "optional": true - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -4627,18 +2256,43 @@ } }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -4663,64 +2317,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/anser": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", @@ -4754,24 +2350,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "license": "MIT", - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, "node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -4789,43 +2374,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "license": "MIT" - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -4833,58 +2381,30 @@ "license": "MIT" }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dequal": "^2.0.3" } }, "node_modules/asap": { @@ -4893,72 +2413,12 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, - "node_modules/ast-types": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", - "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", @@ -4971,15 +2431,6 @@ "proxy-from-env": "^2.1.0" } }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "license": "MIT", - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", @@ -5020,60 +2471,29 @@ } }, "node_modules/babel-plugin-react-compiler": { - "version": "0.0.0-experimental-592953e-20240517", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz", - "integrity": "sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "license": "MIT", "dependencies": { - "@babel/generator": "7.2.0", - "@babel/types": "^7.19.0", - "chalk": "4", - "invariant": "^2.2.4", - "pretty-format": "^24", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "@babel/types": "^7.26.0" } }, "node_modules/babel-plugin-react-native-web": { - "version": "0.19.13", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.19.13.tgz", - "integrity": "sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", + "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", "license": "MIT" }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", + "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.33.3" + } + }, "node_modules/babel-plugin-transform-flow-enums": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", @@ -5084,29 +2504,87 @@ } }, "node_modules/babel-preset-expo": { - "version": "11.0.15", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-11.0.15.tgz", - "integrity": "sha512-rgiMTYwqIPULaO7iZdqyL7aAff9QLOX6OWUtLZBlOrOTreGY1yHah/5+l8MvI6NVc/8Zj5LY4Y5uMSnJIuzTLw==", + "version": "56.0.14", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-56.0.14.tgz", + "integrity": "sha512-+JKVMYf3HajO3tPRA9DlKd/VhZOPTHyTzUo2yZajfMAoQ3l5VEdGVxm2MzX4DXMNKXwsC8GOeTRx7CrO/5dBDA==", "license": "MIT", "dependencies": { + "@babel/generator": "^7.20.5", + "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-transform-export-namespace-from": "^7.22.11", - "@babel/plugin-transform-object-rest-spread": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.22.15", - "@babel/preset-react": "^7.22.15", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.28.6", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.74.87", - "babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517", - "babel-plugin-react-native-web": "~0.19.10", - "react-refresh": "^0.14.2" + "@react-native/babel-plugin-codegen": "0.85.3", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.33.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^56.0.16", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/badgin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz", + "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==", "license": "MIT" }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5139,18 +2617,6 @@ "node": ">=6.0.0" } }, - "node_modules/better-opn": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", - "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", - "license": "MIT", - "dependencies": { - "open": "^8.0.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -5160,44 +2626,19 @@ "node": ">=0.6" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bplist-creator": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", - "integrity": "sha512-xp/tcaV3T5PCiaY04mXga7o/TE+t95gqeLmADeBI1CvZtdWTbgBt3uLpvh4UWtenKeBhCV6oVxGk38yZr2uYEA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", + "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", "license": "MIT", "dependencies": { - "stream-buffers": "~2.2.0" + "stream-buffers": "2.2.x" } }, "node_modules/bplist-parser": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", - "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", "license": "MIT", "dependencies": { "big-integer": "1.6.x" @@ -5207,13 +2648,15 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -5270,64 +2713,12 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "license": "MIT", - "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "license": "MIT" - }, - "node_modules/buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", - "license": "MIT" - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, - "node_modules/builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", - "license": "MIT" - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5337,98 +2728,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -5442,55 +2741,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "license": "MIT", - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "license": "MIT", - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -5539,24 +2789,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -5575,29 +2807,24 @@ "node": ">=12.13.0" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" }, "node_modules/cli-cursor": { "version": "2.1.0", @@ -5623,6 +2850,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5637,50 +2870,15 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", "engines": { "node": ">=0.8" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -5722,12 +2920,6 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -5740,12 +2932,6 @@ "node": ">= 0.8" } }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "license": "MIT" - }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -5755,21 +2941,6 @@ "node": ">= 10" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT" - }, - "node_modules/component-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.2.tgz", - "integrity": "sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -5824,12 +2995,6 @@ "node": ">= 0.6" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -5866,24 +3031,6 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/core-js-compat": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", @@ -5897,27 +3044,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/cross-fetch": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", @@ -5941,24 +3067,6 @@ "node": ">= 8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/css-in-js-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", @@ -5968,6 +3076,12 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5975,78 +3089,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dag-map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz", - "integrity": "sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw==", - "license": "MIT" - }, - "node_modules/data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6064,15 +3106,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -6082,15 +3115,6 @@ "node": ">=0.10" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -6100,19 +3124,6 @@ "node": ">=0.10.0" } }, - "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -6125,80 +3136,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6208,12 +3145,6 @@ "node": ">=0.4.0" } }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "license": "MIT" - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6223,6 +3154,15 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -6234,55 +3174,25 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" }, - "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } + "node_modules/dnssd-advertise": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.4.tgz", + "integrity": "sha512-AmGyK9WpNf06WeP5TjHZq/wNzP76OuEeaiTlKr9E/EEelYLczywUKoqRz+DPRq/ErssjT4lU+/W7wzJW+7K/ZA==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -6298,12 +3208,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6331,45 +3235,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-editor": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", - "integrity": "sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/envinfo": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", - "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/error-stack-parser": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", @@ -6379,91 +3244,6 @@ "stackframe": "^1.3.4" } }, - "node_modules/errorhandler": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", - "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6509,23 +3289,6 @@ "node": ">= 0.4" } }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -6553,19 +3316,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -6584,285 +3334,547 @@ "node": ">=6" } }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/expo": { - "version": "51.0.39", - "resolved": "https://registry.npmjs.org/expo/-/expo-51.0.39.tgz", - "integrity": "sha512-Cs/9xopyzJrpXWbyVUZnr37rprdFJorRgfSp6cdBfvbjxZeKnw2MEu7wJwV/s626i5lZTPGjZPHUF9uQvt51cg==", + "version": "56.0.9", + "resolved": "https://registry.npmjs.org/expo/-/expo-56.0.9.tgz", + "integrity": "sha512-Zd/fhhyC600PO4cA14r+K+DlhhUZLNaDNF6dYg+hgne2kLvg9HMnkZ902sTPZYLkW56JOXLJ5dk7hsIoH26N2A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "0.18.31", - "@expo/config": "9.0.4", - "@expo/config-plugins": "8.0.11", - "@expo/metro-config": "0.18.11", - "@expo/vector-icons": "^14.0.3", - "babel-preset-expo": "~11.0.15", - "expo-asset": "~10.0.10", - "expo-file-system": "~17.0.1", - "expo-font": "~12.0.10", - "expo-keep-awake": "~13.0.2", - "expo-modules-autolinking": "1.11.3", - "expo-modules-core": "1.12.26", - "fbemitter": "^3.0.0", - "whatwg-url-without-unicode": "8.0.0-3" + "@expo/cli": "^56.1.14", + "@expo/config": "~56.0.9", + "@expo/config-plugins": "~56.0.8", + "@expo/devtools": "~56.0.2", + "@expo/dom-webview": "~56.0.5", + "@expo/fingerprint": "^0.19.4", + "@expo/local-build-cache-provider": "^56.0.8", + "@expo/log-box": "^56.0.12", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~56.0.13", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~56.0.14", + "expo-asset": "~56.0.16", + "expo-constants": "~56.0.17", + "expo-file-system": "~56.0.7", + "expo-font": "~56.0.5", + "expo-keep-awake": "~56.0.3", + "expo-modules-autolinking": "~56.0.15", + "expo-modules-core": "~56.0.15", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-minimum": "^0.1.2" }, "bin": { - "expo": "bin/cli" + "expo": "bin/cli", + "expo-modules-autolinking": "bin/autolinking", + "fingerprint": "bin/fingerprint" + }, + "peerDependencies": { + "@expo/dom-webview": "*", + "@expo/metro-runtime": "*", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-web": "*", + "react-native-webview": "*" + }, + "peerDependenciesMeta": { + "@expo/dom-webview": { + "optional": true + }, + "@expo/metro-runtime": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-web": { + "optional": true + }, + "react-native-webview": { + "optional": true + } + } + }, + "node_modules/expo-application": { + "version": "56.0.3", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-56.0.3.tgz", + "integrity": "sha512-DdGGPlMuM6cSTeKhbvh6OeLr2O/+EI5BHKYrD+Do8sJPYgLwzGrgESELfyjJCpEhFzT+TgKIdmLmWXhNUQnHiw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" } }, "node_modules/expo-asset": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-10.0.10.tgz", - "integrity": "sha512-0qoTIihB79k+wGus9wy0JMKq7DdenziVx3iUkGvMAy2azscSgWH6bd2gJ9CGnhC6JRd3qTMFBL0ou/fx7WZl7A==", + "version": "56.0.16", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-56.0.16.tgz", + "integrity": "sha512-iIxPo6C6+/d8JxGV74ZKZbIcCz2s8//dVl7oBAj124NcPMFhzdwycFBpMqq5LUxin+lVy5cCoEjv2LD8ulnkiQ==", "license": "MIT", "dependencies": { - "expo-constants": "~16.0.0", - "invariant": "^2.2.4", - "md5-file": "^3.2.3" + "@expo/image-utils": "^0.10.1", + "expo-constants": "~56.0.17" }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-av": { + "version": "14.0.7", + "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-14.0.7.tgz", + "integrity": "sha512-FvKZxyy+2/qcCmp+e1GTK3s4zH8ZO1RfjpqNxh7ARlS1oH8HPtk1AyZAMo52tHz3yQ3UIqxQ2YbI9CFb4065lA==", + "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-constants": { - "version": "16.0.2", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-16.0.2.tgz", - "integrity": "sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ==", + "version": "56.0.17", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-56.0.17.tgz", + "integrity": "sha512-bU8iU1+7cI7QzfGQVnz2C1nlbXD08YPwD6h8ZEuNspgUuD2prXfmrhrdLe1GjCPYGw4hB3BNjWPjpenNyyymfQ==", "license": "MIT", "dependencies": { - "@expo/config": "~9.0.0", - "@expo/env": "~0.3.0" + "@expo/env": "~2.3.0" }, "peerDependencies": { - "expo": "*" + "expo": "*", + "react-native": "*" } }, "node_modules/expo-file-system": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-17.0.1.tgz", - "integrity": "sha512-dYpnZJqTGj6HCYJyXAgpFkQWsiCH3HY1ek2cFZVHFoEc5tLz9gmdEgTF6nFHurvmvfmXqxi7a5CXyVm0aFYJBw==", + "version": "56.0.7", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-56.0.7.tgz", + "integrity": "sha512-dcKzo8ShPloM7jgfnMcJStgQebhP8owVjCkNI/aX6NMFV1CYB8bxKGMdnzJ3mXk5nfaiW+F/lSKr2UIJ02WAUA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-font": { + "version": "56.0.5", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-56.0.5.tgz", + "integrity": "sha512-WLoDu9hlEgPRKXJRR01HFLJ6Z2tFcORX/WFPRYBndmYc5kjQrFGH/j4BRaF3aBRPyYEAUXiUJybNLXkKCwEXQw==", + "license": "MIT", + "dependencies": { + "fontfaceobserver": "^2.1.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-glass-effect": { + "version": "56.0.4", + "resolved": "https://registry.npmjs.org/expo-glass-effect/-/expo-glass-effect-56.0.4.tgz", + "integrity": "sha512-xI9rXtDwi7RW82uAlfyaXO6+k21ApWJ2tHAWYqPr/FjfmZbKsgNJ4Q0iZzGPCwboqjTGxaRZ61SZxBl8hDt5iA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-haptics": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-13.0.1.tgz", + "integrity": "sha512-qG0EOLDE4bROVT3DtUSyV9g3iB3YFu9j3711X7SNNEnBDXc+2/p3wGDPTnJvPW0ao6HG3/McAOrBQA5hVSdWng==", "license": "MIT", "peerDependencies": { "expo": "*" } }, - "node_modules/expo-font": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.10.tgz", - "integrity": "sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==", + "node_modules/expo-image-loader": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-4.7.0.tgz", + "integrity": "sha512-cx+MxxsAMGl9AiWnQUzrkJMJH4eNOGlu7XkLGnAXSJrRoIiciGaKqzeaD326IyCTV+Z1fXvIliSgNW+DscvD8g==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-image-picker": { + "version": "15.0.7", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-15.0.7.tgz", + "integrity": "sha512-u8qiPZNfDb+ap6PJ8pq2iTO7JKX+ikAUQ0K0c7gXGliKLxoXgDdDmXxz9/6QdICTshJBJlBvI0MwY5NWu7A/uw==", "license": "MIT", "dependencies": { - "fontfaceobserver": "^2.1.0" + "expo-image-loader": "~4.7.0" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-keep-awake": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-13.0.2.tgz", - "integrity": "sha512-kKiwkVg/bY0AJ5q1Pxnm/GvpeB6hbNJhcFsoOWDh2NlpibhCLaHL826KHUM+WsnJRbVRxJ+K9vbPRHEMvFpVyw==", + "version": "56.0.3", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-56.0.3.tgz", + "integrity": "sha512-CLMJXtEiMKknD3Rpm8CRwE6ZJUzu2yCEmRk1sgfHAJ1zIbuEWY3dpPDubtsnuzWm+2k6Sru+yaFbYsvPWmTiBA==", "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo-linking": { + "version": "56.0.13", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-56.0.13.tgz", + "integrity": "sha512-38YrpTh6xdiDxmYSDIUffDqev1hIcEggw2fZ3IZhNp2DVLF1xvqsbO6hJD1fuBKN8P34B3Ggc9Yy26fkqdfCOA==", + "license": "MIT", + "dependencies": { + "expo-constants": "~56.0.16", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-local-authentication": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/expo-local-authentication/-/expo-local-authentication-14.0.1.tgz", + "integrity": "sha512-kAwUD1wEqj1fhwQgIHlP4H/JV9AcX+NO3BJwhPM2HuCFS0kgx2wvcHisnKBSTRyl8u5Jt4odzMyQkDJystwUTg==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, "peerDependencies": { "expo": "*" } }, - "node_modules/expo-linking": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-6.3.1.tgz", - "integrity": "sha512-xuZCntSBGWCD/95iZ+mTUGTwHdy8Sx+immCqbUBxdvZ2TN61P02kKg7SaLS8A4a/hLrSCwrg5tMMwu5wfKr35g==", - "license": "MIT", - "dependencies": { - "expo-constants": "~16.0.0", - "invariant": "^2.2.4" - } - }, "node_modules/expo-modules-autolinking": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.11.3.tgz", - "integrity": "sha512-oYh8EZEvYF5TYppxEKUTTJmbr8j7eRRnrIxzZtMvxLTXoujThVPMFS/cbnSnf2bFm1lq50TdDNABhmEi7z0ngQ==", + "version": "56.0.15", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-56.0.15.tgz", + "integrity": "sha512-WqpBFwLzn7DsrUkWltIjVmAjwuI1VdQ2jRMlvk1nh2kVadwdJBkSjUBQWRifsEePNhiMT/rFOovBolUU/ARt5w==", "license": "MIT", "dependencies": { + "@expo/require-utils": "^56.1.3", + "@expo/spawn-async": "^1.8.0", "chalk": "^4.1.0", - "commander": "^7.2.0", - "fast-glob": "^3.2.5", - "find-up": "^5.0.0", - "fs-extra": "^9.1.0", - "require-from-string": "^2.0.2", - "resolve-from": "^5.0.0" + "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, - "node_modules/expo-modules-autolinking/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/expo-modules-autolinking/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/expo-modules-autolinking/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/expo-modules-core": { - "version": "1.12.26", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.26.tgz", - "integrity": "sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA==", + "version": "56.0.15", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-56.0.15.tgz", + "integrity": "sha512-XOXuWjtUA/xF8VjMHoRTRxuAmrAeUv8QyASX3h/CpTNS58fOt3stV8EYW7BinJPJyqwV7BZoYV83iN0p2FzyZw==", "license": "MIT", "dependencies": { + "@expo/expo-modules-macros-plugin": "~0.0.9", + "expo-modules-jsi": "~56.0.8", "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } + } + }, + "node_modules/expo-modules-jsi": { + "version": "56.0.8", + "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-56.0.8.tgz", + "integrity": "sha512-tXqFU1MHrf7Ctq+Pw0qOeIPDFl1W51p9nRRZy9vVUn4GNuAk1Av0vrj0SGLvcxJvDf3aGwSzr8o8dgUsX5sG0g==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/expo-notifications": { + "version": "56.0.16", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-56.0.16.tgz", + "integrity": "sha512-fg76oPZhs86aKQij2ZBkWeBUti98fA53BRbjgP/ysEl+GlzIsP4BQR4Lw1ORY4MqOCO8x/5V2oykAdFne9ITSg==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.10.1", + "abort-controller": "^3.0.0", + "badgin": "^1.1.5", + "expo-application": "~56.0.3", + "expo-constants": "~56.0.17" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-print": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/expo-print/-/expo-print-13.0.1.tgz", + "integrity": "sha512-o8LuCguGrkyC5RaWEfL5N2J0V9mfbZ3GVzLpaf3KU5RrdYzGEjoiv4xlhVVzh/++hMUqOgnIrtF7tzWYhwu/7g==", + "license": "MIT", + "peerDependencies": { + "expo": "*" } }, "node_modules/expo-router": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-3.5.24.tgz", - "integrity": "sha512-wFi+PIUrOntF5cgg0PgBMlkxEZlWedIv5dWnPFEzN6Tr3A3bpsqdDLgOEIwvwd+pxn5DLzykTmg9EkQ1pPGspw==", + "version": "56.2.9", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-56.2.9.tgz", + "integrity": "sha512-MuGL7Ht8hFTo1ddntyXGw5Lh+5rBw8S/0wHCwtI88nv4aCSyLEuFE19i4E/G0BXVCTKeDRu/hOww3jEqUlAN2w==", "license": "MIT", "dependencies": { - "@expo/metro-runtime": "3.2.3", - "@expo/server": "^0.4.0", - "@radix-ui/react-slot": "1.0.1", - "@react-navigation/bottom-tabs": "~6.5.7", - "@react-navigation/native": "~6.1.6", - "@react-navigation/native-stack": "~6.9.12", - "expo-splash-screen": "0.27.7", - "react-native-helmet-async": "2.0.4", - "schema-utils": "^4.0.1" + "@expo/log-box": "^56.0.12", + "@expo/metro-runtime": "^56.0.14", + "@expo/schema-utils": "^56.0.0", + "@expo/ui": "^56.0.16", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.12", + "@react-native-masked-view/masked-view": "^0.3.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/user-event": "^14.6.1", + "client-only": "^0.0.1", + "color": "^4.2.3", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "expo-glass-effect": "^56.0.4", + "expo-server": "^56.0.5", + "expo-symbols": "^56.0.6", + "fast-deep-equal": "^3.1.3", + "invariant": "^2.2.4", + "nanoid": "^3.3.8", + "query-string": "^7.1.3", + "react-fast-compare": "^3.2.2", + "react-is": "^19.1.0", + "react-native-drawer-layout": "^4.2.2", + "react-native-screens": "^4.25.2", + "server-only": "^0.0.1", + "sf-symbols-typescript": "^2.1.0", + "shallowequal": "^1.1.0", + "vaul": "^1.1.2" }, "peerDependencies": { - "@react-navigation/drawer": "^6.5.8", + "@expo/log-box": "^56.0.12", + "@expo/metro-runtime": "^56.0.14", + "@testing-library/react-native": ">= 13.2.0", "expo": "*", - "expo-constants": "*", - "expo-linking": "*", - "expo-status-bar": "*", + "expo-constants": "^56.0.17", + "expo-linking": "^56.0.13", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-gesture-handler": "*", "react-native-reanimated": "*", - "react-native-safe-area-context": "*", - "react-native-screens": "*" + "react-native-safe-area-context": ">= 5.4.0", + "react-native-screens": "^4.25.2", + "react-native-web": "*", + "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "peerDependenciesMeta": { - "@react-navigation/drawer": { + "@testing-library/react-native": { "optional": true }, - "@testing-library/jest-native": { + "react-dom": { + "optional": true + }, + "react-native-gesture-handler": { "optional": true }, "react-native-reanimated": { "optional": true + }, + "react-native-web": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true } } }, + "node_modules/expo-router/node_modules/@expo/metro-runtime": { + "version": "56.0.14", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-56.0.14.tgz", + "integrity": "sha512-xqSWX7W1jd/B8MzDOJkc/iHAtIsHOMYrDya/jJkEj8A6XdN4XqtmxqfAQ2oWcpYi47vH97lECDp8aoP7jO6v0Q==", + "license": "MIT", + "dependencies": { + "@expo/log-box": "^56.0.12", + "anser": "^1.4.9", + "pretty-format": "^29.7.0", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0" + }, + "peerDependencies": { + "@expo/log-box": "^56.0.12", + "expo": "*", + "react": "*", + "react-dom": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/@radix-ui/react-collection": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.9.tgz", + "integrity": "sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/@radix-ui/react-primitive": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", + "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.12.tgz", + "integrity": "sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.9", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/@radix-ui/react-tabs": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.14.tgz", + "integrity": "sha512-D5jwp9JNuwDeCw3CYD2Fz+sSHo0droQjC8u75dJHe4aWr5q6yBiXZU+hurXnKudRgEpUkD5TsI6bjHPo5ThUxA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-roving-focus": "1.1.12", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/expo-router/node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, + "node_modules/expo-router/node_modules/react-native-screens": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.25.2.tgz", + "integrity": "sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.82.0" + } + }, + "node_modules/expo-screen-orientation": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/expo-screen-orientation/-/expo-screen-orientation-7.0.5.tgz", + "integrity": "sha512-1j0MzVzYpjKQo4BWowQ3ZYwC3OnddX/8k06C8VYTAxMyd8ou1k+rG4tm+GIV2n2RSzc3g7cfPlQwSYr3/SGmbg==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-secure-store": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-13.0.2.tgz", @@ -6872,13 +3884,33 @@ "expo": "*" } }, + "node_modules/expo-server": { + "version": "56.0.5", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-56.0.5.tgz", + "integrity": "sha512-SmM2p2g3Jrktpiazcst+OxhjSzOHXKAY4BPURHYHXvApzzoybMmrNF4IEZ8DKZ145BhSe4ydAmlEFCRTsdtgUQ==", + "license": "MIT", + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/expo-sharing": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-12.0.1.tgz", + "integrity": "sha512-wBT+WeXwapj/9NWuLJO01vi9bdlchYu/Q/xD8slL/Ls4vVYku8CPqzkTtDFcjLrjtlJqyeHsdQXwKLvORmBIew==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-splash-screen": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-0.27.7.tgz", - "integrity": "sha512-s+eGcG185878nixlrjhhLD6UDYrvoqBUaBkIEozBVWFg3pkdsKpONPiUAco4XR3h7I/9ODq4quN28RJLFO+s0Q==", + "version": "56.0.10", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-56.0.10.tgz", + "integrity": "sha512-vDIlo8hzt9HlCZQ0kSY66v83D1WEXOJbVMeyPDfXDu9tbDdPMNUyDpi4WGJXikAjxnAKfbt5Mv5NnEbxINy+VA==", "license": "MIT", "dependencies": { - "@expo/prebuild-config": "7.0.9" + "@expo/config-plugins": "~56.0.8", + "@expo/image-utils": "^0.10.1", + "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" @@ -6890,6 +3922,207 @@ "integrity": "sha512-/t3xdbS8KB0prj5KG5w7z+wZPFlPtkgs95BsmrP/E7Q0xHXTcDcQ6Cu2FkFuRM+PKTb17cJDnLkawyS5vDLxMA==", "license": "MIT" }, + "node_modules/expo-symbols": { + "version": "56.0.6", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-56.0.6.tgz", + "integrity": "sha512-BrA81DjcNafdj7gXVhdrExb9LtUiSVyOf/NavyMmDAHgHMY1GqeR5cnn1PSAZeYKnSgQhee/H89XUpAxtog5hg==", + "license": "MIT", + "dependencies": { + "@expo-google-fonts/material-symbols": "^0.4.1", + "sf-symbols-typescript": "^2.0.0" + }, + "peerDependencies": { + "expo": "*", + "expo-font": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "56.1.14", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-56.1.14.tgz", + "integrity": "sha512-rSH3ygjEPipEYG6dgiJ116J8KqCQ/BYKcwQDipStSh4IFWJ10RZaYP4u5B74jxfeIWjWrOeqvwB6NZfQBjaQ4Q==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~56.0.9", + "@expo/config-plugins": "~56.0.8", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.3.0", + "@expo/image-utils": "^0.10.1", + "@expo/inline-modules": "^0.0.11", + "@expo/json-file": "^10.2.0", + "@expo/log-box": "^56.0.12", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~56.0.13", + "@expo/metro-file-map": "^56.0.3", + "@expo/osascript": "^2.6.0", + "@expo/package-manager": "^1.12.1", + "@expo/plist": "^0.7.0", + "@expo/prebuild-config": "^56.0.15", + "@expo/require-utils": "^56.1.3", + "@expo/router-server": "^56.0.13", + "@expo/schema-utils": "^56.0.0", + "@expo/spawn-async": "^1.8.0", + "@expo/ws-tunnel": "^1.0.1", + "@expo/xcpretty": "^4.4.4", + "@react-native/dev-middleware": "0.85.3", + "accepts": "^1.3.8", + "arg": "^5.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "dnssd-advertise": "^1.1.4", + "expo-server": "^56.0.5", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^1.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.4", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "terminal-link": "^2.1.1", + "toqr": "^0.1.1", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1", + "zod": "^3.25.76" + }, + "bin": { + "expo-internal": "main.js" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { + "version": "56.0.13", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-56.0.13.tgz", + "integrity": "sha512-M2H2zHlRBKIPENCWV8Gqo3/9WANCS9vvOMCcdWfS9wD8XXMnDASFniS0bBoGwwS1qq1LIpYzX8m8wdv7Awy88g==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^56.0.14", + "expo": "*", + "expo-constants": "^56.0.17", + "expo-font": "^56.0.5", + "expo-router": "*", + "expo-server": "^56.0.5", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/expo/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/expo/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -6902,69 +4135,22 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-loops": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz", "integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg==", "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fast-xml-parser": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", - "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" } }, "node_modules/fb-watchman": { @@ -6976,15 +4162,6 @@ "bser": "2.1.1" } }, - "node_modules/fbemitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", - "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", - "license": "BSD-3-Clause", - "dependencies": { - "fbjs": "^3.0.0" - } - }, "node_modules/fbjs": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", @@ -7032,10 +4209,27 @@ "node": "*" } }, - "node_modules/fetch-retry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", - "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-nodeshim": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", + "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", "license": "MIT" }, "node_modules/fill-range": { @@ -7092,60 +4286,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "license": "Apache-2.0", - "dependencies": { - "micromatch": "^4.0.2" - } - }, "node_modules/flow-enums-runtime": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, - "node_modules/flow-parser": { - "version": "0.316.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.316.0.tgz", - "integrity": "sha512-LPYw2hWIj6BI/854eY47jBJVN4gmrIK0kL/EY2xgPmu83Ho37no0mkhDgG6GOX25YxtaXJw5sMkMed2bKntcog==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -7172,49 +4318,6 @@ "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", "license": "BSD-2-Clause" }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -7231,15 +4334,6 @@ "node": ">= 6" } }, - "node_modules/freeport-async": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", - "integrity": "sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -7249,52 +4343,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7304,44 +4352,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7384,6 +4394,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -7397,113 +4416,32 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/getenv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", - "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", + "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7522,42 +4460,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/graphql": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", - "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", - "license": "MIT", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/graphql-tag": { - "version": "2.12.6", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", - "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7567,33 +4469,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -7633,83 +4508,78 @@ "node": ">= 0.4" } }, + "node_modules/hermes-compiler": { + "version": "250829098.0.10", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.10.tgz", + "integrity": "sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w==", + "license": "MIT" + }, "node_modules/hermes-estree": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.19.1.tgz", - "integrity": "sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "license": "MIT" }, "node_modules/hermes-parser": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.19.1.tgz", - "integrity": "sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.19.1" + "hermes-estree": "0.33.3" } }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "license": "MIT", + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" + "react-is": "^16.7.0" } }, "node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "license": "ISC", "dependencies": { - "lru-cache": "^6.0.0" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=10" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7728,41 +4598,12 @@ "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", "license": "BSD-3-Clause" }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7787,37 +4628,6 @@ "node": ">=16.x" } }, - "node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "license": "MIT", - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -7827,29 +4637,12 @@ "node": ">=8" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, "node_modules/inline-style-prefixer": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz", @@ -7860,33 +4653,6 @@ "fast-loops": "^1.1.3" } }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "license": "MIT", - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7896,131 +4662,12 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", @@ -8036,48 +4683,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -8093,136 +4698,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-invalid-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", - "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", - "license": "MIT", - "dependencies": { - "is-glob": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-invalid-path/node_modules/is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-invalid-path/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8232,221 +4716,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-valid-path": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", - "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", - "license": "MIT", - "dependencies": { - "is-invalid-path": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -8459,94 +4728,12 @@ "node": ">=8" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -8556,142 +4743,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -8709,39 +4760,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" + "node": ">=8" } }, "node_modules/jest-util/node_modules/picomatch": { @@ -8773,73 +4804,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -8876,25 +4840,6 @@ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", "license": "MIT" }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/join-component": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", - "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8902,63 +4847,33 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsc-android": { - "version": "250231.0.0", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", - "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", - "license": "BSD-2-Clause" - }, "node_modules/jsc-safe-url": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", "license": "0BSD" }, - "node_modules/jscodeshift": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", - "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.21.0", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -8971,48 +4886,6 @@ "node": ">=6" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "license": "MIT" - }, - "node_modules/json-schema-deref-sync": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/json-schema-deref-sync/-/json-schema-deref-sync-0.13.0.tgz", - "integrity": "sha512-YBOEogm5w9Op337yb6pAT6ZXDqlxAsQCanM3grid8lMWNxRJO/zWEJi3ZzqDL8boWfwhTFym5EFrNgWwpqcBRg==", - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "dag-map": "~1.0.0", - "is-valid-path": "^0.1.1", - "lodash": "^4.17.13", - "md5": "~2.2.0", - "memory-cache": "~0.2.0", - "traverse": "~0.6.6", - "valid-url": "~1.0.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/json-schema-deref-sync/node_modules/md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -9025,24 +4898,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -9052,6 +4907,15 @@ "node": ">=6" } }, + "node_modules/lan-network": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", + "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", + "license": "MIT", + "bin": { + "lan-network": "dist/lan-network-cli.js" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -9087,12 +4951,12 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.19.0.tgz", - "integrity": "sha512-yV5UR7og+Og7lQC+70DA7a8ta1uiOPnWPJfxa0wnxylev5qfo4P+4iMpzWAdYWOca4jdNQZii+bDL/l+4hUXIA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { - "detect-libc": "^1.0.3" + "detect-libc": "^2.0.3" }, "engines": { "node": ">= 12.0.0" @@ -9102,20 +4966,43 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.19.0", - "lightningcss-darwin-x64": "1.19.0", - "lightningcss-linux-arm-gnueabihf": "1.19.0", - "lightningcss-linux-arm64-gnu": "1.19.0", - "lightningcss-linux-arm64-musl": "1.19.0", - "lightningcss-linux-x64-gnu": "1.19.0", - "lightningcss-linux-x64-musl": "1.19.0", - "lightningcss-win32-x64-msvc": "1.19.0" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz", - "integrity": "sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -9133,9 +5020,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz", - "integrity": "sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -9152,10 +5039,30 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz", - "integrity": "sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -9173,9 +5080,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz", - "integrity": "sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -9193,9 +5100,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz", - "integrity": "sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -9213,9 +5120,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz", - "integrity": "sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -9233,9 +5140,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz", - "integrity": "sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -9252,12 +5159,12 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz", - "integrity": "sha512-C+VuUTeSUOAaBZZOPT7Etn/agx/MatzJzGRkeV+zEABmPuntv1zihncsi+AyGmjkkzq3wVedEy7h0/4S84mUtg==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ - "x64" + "arm64" ], "license": "MPL-2.0", "optional": true, @@ -9272,25 +5179,24 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lodash": { @@ -9394,168 +5300,6 @@ "node": ">=4" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "license": "MIT", - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logkitty/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9577,28 +5321,6 @@ "yallist": "^3.0.2" } }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -9623,110 +5345,60 @@ "node": ">= 0.4" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, - "node_modules/md5-file": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-3.2.3.tgz", - "integrity": "sha512-3Tkp1piAHaworfcCgH0jKbTvj1jWWFgbvh2cXaNCgHwyTCBxxvD1Y04rmfpvdPm1P4oXMOpm6+2H7sr7v9v8Fw==", - "license": "MIT", - "dependencies": { - "buffer-alloc": "^1.1.0" - }, - "bin": { - "md5-file": "cli.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/md5hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/md5hex/-/md5hex-1.0.0.tgz", - "integrity": "sha512-c2YOUbp33+6thdCUi34xIyOU/a7bvGKj/3DB1iaPMTuPHf/Q2d5s4sn1FaCOO43XkXggnb08y5W2PU8UNYNLKQ==", - "license": "MIT" - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, - "node_modules/memory-cache": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", - "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==", - "license": "BSD-2-Clause" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/metro": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.12.tgz", - "integrity": "sha512-1UsH5FzJd9quUsD1qY+zUG4JY3jo3YEMxbMYH9jT6NK3j4iORhlwTK8fYTfAUBhDKjgLfKjAh7aoazNE23oIRA==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz", + "integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", - "accepts": "^1.3.7", - "chalk": "^4.0.0", + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", + "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.23.1", + "hermes-parser": "0.35.0", "image-size": "^1.0.2", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-config": "0.80.12", - "metro-core": "0.80.12", - "metro-file-map": "0.80.12", - "metro-resolver": "0.80.12", - "metro-runtime": "0.80.12", - "metro-source-map": "0.80.12", - "metro-symbolicate": "0.80.12", - "metro-transform-plugins": "0.80.12", - "metro-transform-worker": "0.80.12", - "mime-types": "^2.1.27", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4", + "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" @@ -9735,361 +5407,299 @@ "metro": "src/cli.js" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-babel-transformer": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.12.tgz", - "integrity": "sha512-YZziRs0MgA3pzCkkvOoQRXjIoVjvrpi/yRlJnObyIvMP6lFdtyG4nUGIwGY9VXnBvxmXD6mPY2e+NSw6JAyiRg==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz", + "integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", + "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.23.1", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.4", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", - "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", "license": "MIT" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", - "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.23.1" + "hermes-estree": "0.35.0" } }, "node_modules/metro-cache": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.12.tgz", - "integrity": "sha512-p5kNHh2KJ0pbQI/H7ZBPCEwkyNcSz7OUkslzsiIWBMPQGFJ/xArMwkV7I+GJcWh+b4m6zbLxE5fk6fqbVK1xGA==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz", + "integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", - "metro-core": "0.80.12" + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.4" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-cache-key": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.12.tgz", - "integrity": "sha512-o4BspKnugg/pE45ei0LGHVuBJXwRgruW7oSFAeSZvBKA/sGr0UhOGY3uycOgWInnS3v5yTTfiBA9lHlNRhsvGA==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz", + "integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-cache/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/metro-cache/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/metro-config": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.12.tgz", - "integrity": "sha512-4rwOWwrhm62LjB12ytiuR5NgK1ZBNr24/He8mqCsC+HXZ+ATbrewLNztzbAZHtFsrxP4D4GLTGgh96pCpYLSAQ==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz", + "integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==", "license": "MIT", "dependencies": { "connect": "^3.6.5", - "cosmiconfig": "^5.0.5", "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.6.3", - "metro": "0.80.12", - "metro-cache": "0.80.12", - "metro-core": "0.80.12", - "metro-runtime": "0.80.12" + "jest-validate": "^29.7.0", + "metro": "0.84.4", + "metro-cache": "0.84.4", + "metro-core": "0.84.4", + "metro-runtime": "0.84.4", + "yaml": "^2.6.1" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-core": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.12.tgz", - "integrity": "sha512-QqdJ/yAK+IpPs2HU/h5v2pKEdANBagSsc6DRSjnwSyJsCoHlmyJKCaCJ7KhWGx+N4OHxh37hoA8fc2CuZbx0Fw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz", + "integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.80.12" + "metro-resolver": "0.84.4" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-file-map": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.12.tgz", - "integrity": "sha512-sYdemWSlk66bWzW2wp79kcPMzwuG32x1ZF3otI0QZTmrnTaaTiGyhE66P1z6KR4n2Eu5QXiABa6EWbAQv0r8bw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz", + "integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==", "license": "MIT", "dependencies": { - "anymatch": "^3.0.3", - "debug": "^2.2.0", + "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", - "jest-worker": "^29.6.3", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", - "node-abort-controller": "^3.1.1", "nullthrows": "^1.1.1", "walker": "^1.0.7" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/metro-file-map/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/metro-file-map/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/metro-minify-terser": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.12.tgz", - "integrity": "sha512-muWzUw3y5k+9083ZoX9VaJLWEV2Jcgi+Oan0Mmb/fBNMPqP9xVDuy4pOMn/HOiGndgfh/MK7s4bsjkyLJKMnXQ==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz", + "integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-resolver": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.12.tgz", - "integrity": "sha512-PR24gYRZnYHM3xT9pg6BdbrGbM/Cu1TcyIFBVlAk7qDAuHkUNQ1nMzWumWs+kwSvtd9eZGzHoucGJpTUEeLZAw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz", + "integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-runtime": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.12.tgz", - "integrity": "sha512-LIx7+92p5rpI0i6iB4S4GBvvLxStNt6fF0oPMaUd1Weku7jZdfkCZzmrtDD9CSQ6EPb0T9NUZoyXIxlBa3wOCw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz", + "integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-source-map": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.12.tgz", - "integrity": "sha512-o+AXmE7hpvM8r8MKsx7TI21/eerYYy2DCDkWfoBkv+jNkl61khvDHlQn0cXZa6lrcNZiZkl9oHSMcwLLIrFmpw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz", + "integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.80.12", + "metro-symbolicate": "0.84.4", "nullthrows": "^1.1.1", - "ob1": "0.80.12", + "ob1": "0.84.4", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-symbolicate": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.12.tgz", - "integrity": "sha512-/dIpNdHksXkGHZXARZpL7doUzHqSNxgQ8+kQGxwpJuHnDhGkENxB5PS2QBaTDdEcmyTMjS53CN1rl9n1gR6fmw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz", + "integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.80.12", + "metro-source-map": "0.84.4", "nullthrows": "^1.1.1", "source-map": "^0.5.6", - "through2": "^2.0.1", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro-symbolicate/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-transform-plugins": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.12.tgz", - "integrity": "sha512-WQWp00AcZvXuQdbjQbx1LzFR31IInlkCDYJNRs6gtEtAyhwpMMlL2KcHmdY+wjDO9RPcliZ+Xl1riOuBecVlPA==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz", + "integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro-transform-worker": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.12.tgz", - "integrity": "sha512-KAPFN1y3eVqEbKLx1I8WOarHPqDMUa8WelWxaJCNKO/yHCP26zELeqTJvhsQup+8uwB6EYi/sp0b6TGoh6lOEA==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz", + "integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==", "license": "MIT", "dependencies": { - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.0", - "@babel/parser": "^7.20.0", - "@babel/types": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.80.12", - "metro-babel-transformer": "0.80.12", - "metro-cache": "0.80.12", - "metro-cache-key": "0.80.12", - "metro-minify-terser": "0.80.12", - "metro-source-map": "0.80.12", - "metro-transform-plugins": "0.80.12", + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-source-map": "0.84.4", + "metro-transform-plugins": "0.84.4", "nullthrows": "^1.1.1" }, "engines": { - "node": ">=18" - } - }, - "node_modules/metro/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT" - }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", - "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", "license": "MIT" }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", - "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", "dependencies": { - "hermes-estree": "0.23.1" + "hermes-estree": "0.35.0" } }, - "node_modules/metro/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/metro/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", + "node_modules/metro/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/metro/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "mime-db": "^1.54.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" + "node": ">=18" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/micromatch": { @@ -10118,15 +5728,15 @@ } }, "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=4" } }, "node_modules/mime-db": { @@ -10159,25 +5769,28 @@ "node": ">=4" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { @@ -10189,109 +5802,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -10304,32 +5814,49 @@ "node": ">=10" } }, - "node_modules/mrmime": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", - "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/msgpackr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.0.2.tgz", + "integrity": "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ==", "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "optionalDependencies": { + "msgpackr-extract": "^3.0.4" } }, + "node_modules/msgpackr-extract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz", + "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" + } + }, + "node_modules/multitars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-1.0.0.tgz", + "integrity": "sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -10349,59 +5876,14 @@ } }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/nested-error-stacks": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz", - "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", - "license": "MIT" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" - }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "license": "MIT" - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10431,6 +5913,21 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10446,68 +5943,31 @@ "node": ">=18" } }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-package-arg": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", - "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "license": "ISC", "dependencies": { - "hosted-git-info": "^3.0.2", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm-package-arg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "license": "ISC", "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/nullthrows": { @@ -10517,15 +5977,15 @@ "license": "MIT" }, "node_modules/ob1": { - "version": "0.80.12", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.12.tgz", - "integrity": "sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==", + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", + "integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/object-assign": { @@ -10537,47 +5997,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -10599,15 +6018,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -10621,17 +6031,16 @@ } }, "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10654,6 +6063,15 @@ "node": ">=6" } }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ora/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -10713,6 +6131,18 @@ "node": ">=4" } }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ora/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -10725,134 +6155,6 @@ "node": ">=4" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/parse-png": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", @@ -10874,24 +6176,6 @@ "node": ">= 0.8" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -10908,34 +6192,28 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": "20 || >=22" } }, "node_modules/picocolors": { @@ -10945,108 +6223,17 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.2.tgz", - "integrity": "sha512-cfDHL6LStTEKlNilboNtobT/kEa30PtAf2Q1OgszfrG/rpVl1xaFWT9ktfkS306GmHgmnad1Sw4wabhlvFtsTw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/plist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", @@ -11070,15 +6257,6 @@ "node": ">=14.6" } }, - "node_modules/plist/node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, "node_modules/pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -11088,19 +6266,10 @@ "node": ">=4.0.0" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", @@ -11117,7 +6286,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -11131,58 +6300,36 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", "dependencies": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, "node_modules/proc-log": { @@ -11194,12 +6341,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -11231,6 +6372,17 @@ "node": ">= 6" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -11240,33 +6392,6 @@ "node": ">=10" } }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qrcode-terminal": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", - "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -11285,16 +6410,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -11304,26 +6419,6 @@ "inherits": "~2.0.3" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -11333,21 +6428,6 @@ "node": ">= 0.6" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -11361,36 +6441,15 @@ } }, "node_modules/react-devtools-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.3.2.tgz", - "integrity": "sha512-crr9HkVrDiJ0A4zot89oS0Cgv0Oa4OG1Em4jit3P3ZxZSKPMYyMjfwMqgcJna9o625g8oN87rBm8SWWrSTBZxg==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -11438,77 +6497,95 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.74.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.74.5.tgz", - "integrity": "sha512-Bgg2WvxaGODukJMTZFTZBNMKVaROHLwSb8VAGEdrlvKwfb1hHg/3aXTUICYk7dwgAnb+INbGMwnF8yeAgIUmqw==", + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.85.3.tgz", + "integrity": "sha512-HN/fGC+3nZVcDNcw7gfbM/DuqZAvI9Mz+/SxuhODaua4JY0BPzhfTzWXRyTR4mRgMHmShTPpH2PYMTxvZrsdZA==", "license": "MIT", "dependencies": { - "@jest/create-cache-key-function": "^29.6.3", - "@react-native-community/cli": "13.6.9", - "@react-native-community/cli-platform-android": "13.6.9", - "@react-native-community/cli-platform-ios": "13.6.9", - "@react-native/assets-registry": "0.74.87", - "@react-native/codegen": "0.74.87", - "@react-native/community-cli-plugin": "0.74.87", - "@react-native/gradle-plugin": "0.74.87", - "@react-native/js-polyfills": "0.74.87", - "@react-native/normalize-colors": "0.74.87", - "@react-native/virtualized-lists": "0.74.87", + "@react-native/assets-registry": "0.85.3", + "@react-native/codegen": "0.85.3", + "@react-native/community-cli-plugin": "0.85.3", + "@react-native/gradle-plugin": "0.85.3", + "@react-native/js-polyfills": "0.85.3", + "@react-native/normalize-colors": "0.85.3", + "@react-native/virtualized-lists": "0.85.3", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.33.3", "base64-js": "^1.5.1", - "chalk": "^4.0.0", - "event-target-shim": "^5.0.1", + "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.10", "invariant": "^2.2.4", - "jest-environment-node": "^29.6.3", - "jsc-android": "^250231.0.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.80.3", - "metro-source-map": "^0.80.3", - "mkdirp": "^0.5.1", + "metro-runtime": "^0.84.3", + "metro-source-map": "^0.84.3", "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", + "pretty-format": "^29.7.0", "promise": "^8.3.0", - "react-devtools-core": "^5.0.0", + "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", - "react-shallow-renderer": "^16.15.0", "regenerator-runtime": "^0.13.2", - "scheduler": "0.24.0-canary-efb381bbf-20230505", + "scheduler": "0.27.0", + "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", "whatwg-fetch": "^3.0.0", - "ws": "^6.2.2", + "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "react-native": "cli.js" }, "engines": { - "node": ">=18" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { - "@types/react": "^18.2.6", - "react": "18.2.0" + "@react-native/jest-preset": "0.85.3", + "@types/react": "^19.1.1", + "react": "^19.2.3" }, "peerDependenciesMeta": { + "@react-native/jest-preset": { + "optional": true + }, "@types/react": { "optional": true } } }, - "node_modules/react-native-helmet-async": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz", - "integrity": "sha512-m3CkXWss6B1dd6mCMleLpzDCJJGGaHOLQsUzZv8kAASJmMfmVT4d2fx375iXKTRWT25ThBfae3dECuX5cq/8hg==", - "license": "Apache-2.0", + "node_modules/react-native-drawer-layout": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.4.tgz", + "integrity": "sha512-l1Le5HcVidobnJm8xqFZo46Rs8FDHdxbTZhkjxpNSRgU+QMoQXilOfzTHAeNjEGiKVGgIs9cW3ctXeHqgp5jJg==", + "license": "MIT", "dependencies": { - "invariant": "^2.2.4", - "react-fast-compare": "^3.2.2", - "shallowequal": "^1.1.0" + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" }, "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0" + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" + } + }, + "node_modules/react-native-gesture-handler": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.16.2.tgz", + "integrity": "sha512-vGFlrDKlmyI+BT+FemqVxmvO7nqxU33cgXVsn6IKAFishvlG3oV2Ds67D5nPkHMea8T+s1IcuMm0bF8ntZtAyg==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" } }, "node_modules/react-native-safe-area-context": { @@ -11561,80 +6638,42 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, "node_modules/react-native/node_modules/@react-native/normalize-colors": { - "version": "0.74.87", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.87.tgz", - "integrity": "sha512-Xh7Nyk/MPefkb0Itl5Z+3oOobeG9lfLb7ZOY2DKpFnoCE1TzBmib9vMNdFaLdSxLIP+Ec6icgKtdzYg8QUPYzA==", + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.3.tgz", + "integrity": "sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw==", "license": "MIT" }, - "node_modules/react-native/node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/react-native/node_modules/@react-native/virtualized-lists": { + "version": "0.85.3", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.3.tgz", + "integrity": "sha512-dsCjI//OIPEUJMyNHp4l7zNLVjCx7bcaRUceOCkU+IB17hkbtbGWvi7HjGFSzy7FJGmS/MOlcfpb72xXiy1Oig==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.20.tgz", - "integrity": "sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/react-native/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" }, "engines": { - "node": ">= 10" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.85.3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/react-native/node_modules/promise": { @@ -11646,19 +6685,16 @@ "asap": "~2.0.6" } }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" - }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.4.tgz", - "integrity": "sha512-PNIUUyLI5YpkJZj60YBzX1o0ByQ4ovvfmq9N/Kig/PAYbVlGyz4R6G0SEWrD0O9acc0sT2+IdMBVLFv8FSi0Nw==", - "license": "MIT", - "dependencies": { - "async-limiter": "~1.0.0" + "node_modules/react-native/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/react-refresh": { @@ -11670,90 +6706,86 @@ "node": ">=0.10.0" } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/readable-stream": { + "node_modules/react-remove-scroll-bar": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "license": "BSD" - }, - "node_modules/recast": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", - "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { - "ast-types": "0.15.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" }, "engines": { - "node": ">= 4" + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/recast/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/regenerate": { @@ -11780,26 +6812,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regexpu-core": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", @@ -11835,12 +6847,6 @@ "regjsparser": "bin/parser" } }, - "node_modules/remove-trailing-slash": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", - "integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==", - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11850,43 +6856,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, - "node_modules/requireg": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", - "integrity": "sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==", - "dependencies": { - "nested-error-stacks": "~2.0.1", - "rc": "~1.2.7", - "resolve": "~1.7.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/requireg/node_modules/resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "license": "MIT", - "dependencies": { - "path-parse": "^1.0.5" - } - }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -11923,15 +6892,6 @@ "integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==", "license": "MIT" }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -11945,80 +6905,6 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", - "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "get-intrinsic": "^1.3.0", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -12039,45 +6925,6 @@ ], "license": "MIT" }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/sax": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", @@ -12088,45 +6935,10 @@ } }, "node_modules/scheduler": { - "version": "0.24.0-canary-efb381bbf-20230505", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", - "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -12138,24 +6950,24 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -12176,16 +6988,13 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "bin": { - "mime": "cli.js" - }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, "node_modules/send/node_modules/on-finished": { @@ -12201,9 +7010,9 @@ } }, "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -12233,21 +7042,6 @@ "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/serve-static/node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -12257,141 +7051,12 @@ "node": ">= 0.8" } }, - "node_modules/serve-static/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve-static/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", "license": "MIT" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -12404,16 +7069,13 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/shallowequal": { @@ -12455,78 +7117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -12544,27 +7134,6 @@ "plist": "^3.0.5" } }, - "node_modules/simple-plist/node_modules/bplist-creator": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", - "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", - "license": "MIT", - "dependencies": { - "stream-buffers": "2.2.x" - } - }, - "node_modules/simple-plist/node_modules/bplist-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", - "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", - "license": "MIT", - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/simple-swizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", @@ -12574,68 +7143,12 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT" - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/slugify": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", @@ -12646,12 +7159,12 @@ } }, "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { - "node": ">= 12" + "node": ">=0.10.0" } }, "node_modules/source-map-js": { @@ -12691,45 +7204,6 @@ "node": ">=6" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -12757,19 +7231,6 @@ "node": ">= 0.6" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-buffers": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", @@ -12779,12 +7240,6 @@ "node": ">= 0.10.0" } }, - "node_modules/stream-slice": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", - "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", - "license": "MIT" - }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -12794,21 +7249,6 @@ "node": ">=4" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -12823,151 +7263,7 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -12979,54 +7275,18 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/structured-headers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", @@ -13039,65 +7299,6 @@ "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", "license": "MIT" }, - "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13135,140 +7336,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "license": "MIT", - "dependencies": { - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/temp-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", - "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/tempy": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.7.1.tgz", - "integrity": "sha512-vXPxwOyaNVi9nyczO16mxmHGpl6ASC5/TVhRRHpqeYHvKQm58EaWNvZXxAhR0lYYnBOQFjXjhzeLsaXdjxLjRg==", - "license": "MIT", - "dependencies": { - "del": "^6.0.0", - "is-stream": "^2.0.0", - "temp-dir": "^2.0.0", - "type-fest": "^0.16.0", - "unique-string": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -13309,47 +7376,26 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "license": "MIT" - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "license": "MIT" }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tmpl": { @@ -13379,65 +7425,24 @@ "node": ">=0.6" } }, + "node_modules/toqr": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz", + "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", + "license": "MIT" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, - "node_modules/traverse": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", - "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", - "license": "MIT", - "dependencies": { - "gopd": "^1.2.0", - "typedarray.prototype.slice": "^1.0.5", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/turbo-stream": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.1.tgz", - "integrity": "sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==", - "license": "ISC" - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", @@ -13447,106 +7452,10 @@ "node": ">=8" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", - "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "possible-typed-array-names": "^1.1.0", - "reflect.getprototypeof": "^1.0.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray.prototype.slice": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", - "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "math-intrinsics": "^1.1.0", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-offset": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13557,37 +7466,10 @@ "node": ">=14.17" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", - "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -13630,51 +7512,6 @@ "node": ">=4" } }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "license": "ISC", - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -13714,11 +7551,26 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/url-join": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz", - "integrity": "sha512-EGXjXJZhIHiQMK2pQukuFcL303nskqIRzWvPvV5O8miOfwoUb9G+a/Cld60kUyeaybEI94wvVClT10DtfeAExA==", - "license": "MIT" + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, "node_modules/use-latest-callback": { "version": "0.2.6", @@ -13729,25 +7581,28 @@ "react": ">=16.8" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -13758,27 +7613,22 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/valid-url": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", - "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" - }, "node_modules/validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "license": "ISC", - "dependencies": { - "builtins": "^1.0.3" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/vary": { @@ -13790,6 +7640,177 @@ "node": ">= 0.8" } }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-dialog": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.16.tgz", + "integrity": "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.12", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.9", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.11", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-slot": "1.2.5", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.12.tgz", + "integrity": "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.9.tgz", + "integrity": "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-portal": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.11.tgz", + "integrity": "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/vaul/node_modules/@radix-ui/react-primitive": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", + "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", @@ -13820,27 +7841,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", - "license": "MIT", - "dependencies": { - "util": "^0.12.3" - }, - "optionalDependencies": { - "@zxing/text-encoding": "0.9.0" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13863,28 +7863,11 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/whatwg-url-without-unicode": { - "version": "8.0.0-3", - "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", - "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", - "license": "MIT", - "dependencies": { - "buffer": "^5.4.3", - "punycode": "^2.1.1", - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=8" - } + "node_modules/whatwg-url-minimum": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.2.tgz", + "integrity": "sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==", + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", @@ -13901,109 +7884,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", - "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wonka": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz", - "integrity": "sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==", - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -14021,94 +7901,17 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -14132,16 +7935,6 @@ "node": ">=10.0.0" } }, - "node_modules/xcode/node_modules/uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/xml2js": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", @@ -14165,23 +7958,14 @@ } }, "node_modules/xmlbuilder": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", - "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "license": "MIT", "engines": { "node": ">=8.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14239,18 +8023,6 @@ "node": ">=12" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", @@ -14259,18 +8031,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/zod-validation-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz", - "integrity": "sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.18.0" - } } } } diff --git a/package.json b/package.json index 073fc8f0..70845958 100644 --- a/package.json +++ b/package.json @@ -10,25 +10,35 @@ "build:ios": "eas build --platform ios" }, "dependencies": { - "expo": "~51.0.28", - "expo-router": "~3.5.23", - "expo-status-bar": "~1.12.1", + "@expo/metro-runtime": "~3.2.3", + "@react-native-community/netinfo": "11.3.1", + "axios": "^1.7.7", + "expo": "^56.0.9", + "expo-av": "~14.0.7", + "expo-constants": "^56.0.17", + "expo-haptics": "~13.0.1", + "expo-image-picker": "~15.0.7", + "expo-linking": "^56.0.13", + "expo-local-authentication": "~14.0.1", + "expo-notifications": "^56.0.16", + "expo-print": "~13.0.1", + "expo-router": "^56.2.9", + "expo-screen-orientation": "~7.0.5", "expo-secure-store": "~13.0.2", - "expo-constants": "~16.0.2", - "expo-splash-screen": "~0.27.6", - "expo-linking": "~6.3.1", + "expo-sharing": "~12.0.1", + "expo-splash-screen": "^56.0.10", + "expo-status-bar": "~1.12.1", "react": "18.2.0", - "react-native": "0.74.5", + "react-dom": "18.2.0", + "react-native": "^0.85.3", + "react-native-gesture-handler": "~2.16.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", - "react-native-web": "~0.19.10", - "react-dom": "18.2.0", - "@expo/metro-runtime": "~3.2.3", - "axios": "^1.7.7" + "react-native-web": "~0.19.10" }, "devDependencies": { "@babel/core": "^7.24.0", "@types/react": "~18.2.79", - "typescript": "~5.3.3" + "typescript": "~5.5.4" } } diff --git a/services/api.ts b/services/api.ts index 5e1910dd..93d7584d 100644 --- a/services/api.ts +++ b/services/api.ts @@ -32,6 +32,27 @@ export const login = (username: string, password: string) => export const getMe = () => client.get('/api/auth/me') +/* ── 인증·보안 (#33 #34 #37 #38) ── */ +/* 등록 디바이스 관리 */ +export const getDevices = () => + client.get('/api/auth/devices') +export const deleteDevice = (id: string | number) => + client.delete(`/api/auth/devices/${id}`) + +/* 보안 이벤트 로그 */ +export const getSecurityEvents = () => + client.get('/api/auth/events') + +/* Zero Trust 네트워크 상태 */ +export const getNetworkStatus = () => + client.get('/api/auth/network-status') + +/* 멀티기관 전환 */ +export const getInstitutions = () => + client.get('/api/institutions/') +export const switchTenant = (tenantId: string | number) => + client.post('/api/auth/switch-tenant', { tenant_id: tenantId }) + /* ── 대시보드 ── */ export const getDashboard = () => client.get('/api/dashboard') @@ -49,6 +70,57 @@ export const createSR = (data: { title: string; description: string; priority: s export const updateSRStatus = (id: number, status: string) => client.patch(`/api/tasks/${id}/status`, { status }) +/* SR 부분 수정 (스와이프 상태 전환 등) */ +export const patchSR = (id: number, data: Record) => + client.patch(`/api/tasks/${id}`, data) + +/* 빠른 SR 등록 (FormData 또는 JSON) */ +export const createSRRaw = (data: Record) => + client.post('/api/tasks', data) + +/* 일괄 상태 변경 */ +export const batchUpdateSR = (ids: number[], status: string) => + client.patch('/api/tasks/batch', { ids, status }) + +/* 에스컬레이션 */ +export const escalateSR = (id: number, reason?: string) => + client.post(`/api/tasks/${id}/escalate`, { reason: reason ?? '' }) + +/* 구독 / 팔로우 토글 */ +export const subscribeSR = (id: number, subscribe: boolean) => + client.post(`/api/tasks/${id}/subscribe`, { subscribe }) + +/* 만족도 평가 */ +export const rateSR = (id: number, score: number, comment?: string) => + client.post(`/api/tasks/${id}/rating`, { score, comment: comment ?? '' }) + +/* 코멘트 작성 (내부/외부) */ +export const addSRComment = (id: number, content: string, isInternal: boolean) => + client.post(`/api/tasks/${id}/comments`, { content, is_internal: isInternal }) + +export const getSRComments = (id: number) => + client.get(`/api/tasks/${id}/comments`) + +/* 인시던트 타임라인 */ +export const getSRTimeline = (id: number) => + client.get(`/api/tasks/${id}/timeline`) + +/* 관련 SR */ +export const getRelatedSR = (id: number) => + client.get(`/api/tasks`, { params: { related_to: id } }) + +/* 중복 SR 감지 */ +export const findSimilarSR = (title: string, limit = 3) => + client.get(`/api/tasks`, { params: { title_similar: title, status: 'open', limit } }) + +/* SR 템플릿 */ +export const getSRTemplates = () => + client.get('/api/tasks/templates') + +/* 미처리 SR 카운트 (뱃지용) */ +export const getOpenSRCount = () => + client.get('/api/tasks', { params: { status: 'open', assigned_to: 'me', size: 1 } }) + /* ── AI 챗봇 ── */ export const sendAIMessage = (message: string) => client.post('/api/chatbot/message', { message }) @@ -85,4 +157,284 @@ export const getCSAPDashboard = () => client.get('/api/compliance/csap/dashboard export const getCSAPItems = () => client.get('/api/compliance/csap/items') export const getCSAPResults = () => client.get('/api/compliance/csap/results') +/* ── 모니터링·알림 (#39~#50) ── */ + +/* #39 서버 상태 대시보드 — 이름/상태/리소스 수치만 (IP·계정·PW 노출 금지) */ +export const getServerStatus = () => client.get('/api/servers/status') + +/* #42 서버 부하 히트맵 (status 재활용 가능, 별도 엔드포인트 우선 시도) */ +export const getServerHeatmap = () => client.get('/api/servers/status') + +/* #47 서버 메트릭 시계열 (range: 1h/6h/24h) */ +export const getServerMetrics = (id: string | number, range = '1h') => + client.get(`/api/servers/${id}/metrics`, { params: { range } }) + +/* #43 SLA 위반 예측 */ +export const getSLAPrediction = () => client.get('/api/sla/prediction') + +/* #44 서비스 상태 */ +export const getServiceStatus = () => client.get('/api/service-status') + +/* #45 알림 규칙 노코드 편집기 */ +export const getAlertRules = () => client.get('/api/alert-rules/') +export const createAlertRule = (data: Record) => + client.post('/api/alert-rules/', data) +export const updateAlertRule = (id: string | number, data: Record) => + client.put(`/api/alert-rules/${id}`, data) +export const deleteAlertRule = (id: string | number) => + client.delete(`/api/alert-rules/${id}`) +export const toggleAlertRule = (id: string | number) => + client.patch(`/api/alert-rules/${id}/toggle`) + +/* #46 온콜 스케줄 */ +export const getOncallSchedule = () => client.get('/api/oncall/schedule') + +/* #48 임계값 초과 이력 */ +export const getThresholdHistory = (page = 0, size = 30) => + client.get('/api/threshold-history', { params: { page, size } }) + +/* #49 커스텀 대시보드 위젯 */ +export const getCustomDashboard = () => client.get('/api/custom-dashboard') +export const saveCustomDashboard = (widgets: unknown[]) => + client.put('/api/custom-dashboard', { widgets }) + +/* #50 글로벌 통합 검색 */ +export const globalSearch = (q: string, types = 'sr,server,kb,institution') => + client.get('/api/search/', { params: { q, types } }) + +/* ────────────────────────────────────────────── + * 승인·워크플로우 (#63~#70) + * ────────────────────────────────────────────── */ + +/* #63 승인 요청 목록 (기본: 대기 상태) */ +export const getApprovals = (status = 'pending') => + client.get('/api/approvals/', { params: { status } }) + +/* #64 승인 / 반려 */ +export const approveRequest = (id: string | number, comment = '') => + client.post(`/api/approvals/${id}/approve`, { comment }) +export const rejectRequest = (id: string | number, reason: string) => + client.post(`/api/approvals/${id}/reject`, { reason }) + +/* #64 언두 — 3초 내 실행취소 */ +export const cancelApproval = (id: string | number) => + client.patch(`/api/approvals/${id}/cancel`) + +/* #65 다단계 승인 진행 단계 */ +export const getApprovalStages = (id: string | number) => + client.get(`/api/approvals/${id}/stages`) + +/* #67 대리결재 설정 */ +export const getDelegation = () => + client.get('/api/approvals/delegate') +export const setDelegation = (data: { + delegate_to: string | number + start_date: string + end_date: string + reason: string +}) => client.post('/api/approvals/delegate', data) +export const cancelDelegation = (id: string | number) => + client.delete(`/api/approvals/delegate/${id}`) + +/* #67 사용자 검색 (대리인 지정용) */ +export const searchUsers = (q: string) => + client.get('/api/auth/users', { params: { q } }) + +/* #68 변경 관리 캘린더 */ +export const getChangeCalendar = (month: string) => + client.get('/api/changes/calendar', { params: { month } }) + +/* #69 자동화 규칙 조회 (워크플로우 엔진) */ +export const getAutomationRules = () => + client.get('/api/automation-rules/') + +/* #70 SLA 예외 승인 */ +export const getSLAExceptionPending = () => + client.get('/api/tasks', { params: { sla_exception_pending: true } }) +export const requestSLAException = ( + id: string | number, + data: { reason: string; new_deadline: string } +) => client.post(`/api/tasks/${id}/sla-exception`, data) + +/* ────────────────────────────────────────────── + * 지식베이스·문서 (#71~#77) + * ────────────────────────────────────────────── */ +export const getKBList = (q = '', category = '', page = 0, size = 20) => + client.get('/api/kb/', { params: { q, category, page, size } }) +export const getKBDetail = (id: string | number) => + client.get(`/api/kb/${id}`) +export const getKBBookmarks = () => + client.get('/api/kb/bookmarks') +export const toggleKBBookmark = (id: string | number) => + client.post(`/api/kb/${id}/bookmark`) + +export const ocrScanDocument = (formData: FormData) => + client.post('/api/ocr/parse', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) + +export const getMeetingMinutes = (page = 0) => + client.get('/api/meetings/', { params: { page, size: 20 } }) +export const getMeetingMinuteDetail = (id: string | number) => + client.get(`/api/meetings/${id}`) + +export const getReleaseNotes = () => + client.get('/api/system/release-notes') + +/* ────────────────────────────────────────────── + * 준수·거버넌스 (#78~#84) + * ────────────────────────────────────────────── */ +export const getCSAPScore = () => + client.get('/api/compliance/csap/dashboard') +export const getCSAPNonCompliant = () => + client.get('/api/compliance/csap/items', { params: { status: 'fail' } }) +export const createCSAPSR = (itemId: string | number) => + client.post('/api/tasks', { title: `CSAP 미준수 즉시 조치: 항목 ${itemId}`, description: `CSAP 점검 항목 ${itemId} 미준수 즉시 조치`, priority: 'HIGH', sr_type: 'CHANGE', csap_item_id: itemId }) + +export const getAuditLogs = (page = 0, size = 30) => + client.get('/api/audit/', { params: { page, size } }) + +export const getLicenseExpired = () => + client.get('/api/license/status') + +export const getPatchStatus = () => + client.get('/api/patches/status') +export const getPIITypes = () => + client.get('/api/patches/pii-types') +export const applyPatch = (cveId: string) => + client.post(`/api/patches/${cveId}/apply`) + +export const getAISOCEvents = (page = 0) => + client.get('/api/ai-soc/events', { params: { page, size: 20 } }) + +/* ────────────────────────────────────────────── + * 통계·보고 (#93~#97) + * ────────────────────────────────────────────── */ +export const getMyStats = () => + client.get('/api/stats/my') +export const getInstitutionStats = () => + client.get('/api/stats/institutions') +export const getDeployHistory = () => + client.get('/api/stats/deploy-history') +export const getKPIDashboard = () => + client.get('/api/stats/kpi') +export const getReportData = () => + client.get('/api/stats/export-pdf') + +/* ────────────────────────────────────────────── + * 협업·연동 (#98~#100) + * ────────────────────────────────────────────── */ +export const getSRChat = (srId: number, page = 0) => + client.get(`/api/sr-chat/${srId}/messages`, { params: { page, size: 50 } }) +export const sendSRChat = (srId: number, content: string, msgType = 'text') => + client.post(`/api/sr-chat/${srId}/messages`, { content, msg_type: msgType }) + +export const getAPKQRCode = () => + client.get('/api/cicd/status') +export const getBuilds = () => + client.get('/api/cicd/builds') +export const triggerBuild = (project: string) => + client.post('/api/cicd/builds/trigger', { project }) + +/* ────────────────────────────────────────────── + * 2세대 확장 API (#101~#200) + * ────────────────────────────────────────────── */ + +/* AIOps / 예측 */ +export const getFailurePredictions = () => + client.get('/api/predictive/failure') +export const getCapacityPredictions = (days: 30 | 60 | 90) => + client.get('/api/capacity/predictions', { params: { days } }) + +/* GreenOps */ +export const getGreenopsEnergy = () => client.get('/api/greenops/energy') +export const getGreenopsCarbon = () => client.get('/api/greenops/carbon') + +/* 비용 최적화 */ +export const getCostRecommendations = () => + client.get('/api/cost-optimizer/recommendations') +export const getSavingsDashboard = () => + client.get('/api/mobile2/savings-dashboard') + +/* 서비스 의존성 */ +export const getServiceDependencyMap = () => + client.get('/api/knowledge-graph/service-map') + +/* 정책 위반 */ +export const getPolicyViolations = () => + client.get('/api/policy/violations') + +/* 전자서명 */ +export const getPendingDocs = () => + client.get('/api/approvals/pending-docs') +export const signDocument = (id: string | number, pinHash: string) => + client.post(`/api/approvals/${id}/sign`, { pin_hash: pinHash }) + +/* 하드웨어 보증 */ +export const getHWWarranty = () => client.get('/api/cmdb/warranty') + +/* NFC / 자산 */ +export const getAssetById = (id: string | number) => + client.get(`/api/cmdb/assets/${id}`) +export const nfcCheckin = (serverId: string | number) => + client.post('/api/servers/field-checkin', { server_id: serverId, source: 'nfc', method: 'nfc_tag' }) + +/* CVE / 위협 */ +export const getCVEList = () => client.get('/api/patches/cve') +export const getThreatFeed = () => client.get('/api/ai-soc/threats') +export const searchIOC = (q: string) => + client.get('/api/ai-soc/ioc/search', { params: { q } }) +export const getSecurityScore = () => client.get('/api/ai-soc/security-score') + +/* AI 이력 / 브리핑 */ +export const getChatbotHistory = (page = 0) => + client.get('/api/mobile2/chatbot-history', { params: { page, size: 20 } }) +export const getAIBriefing = () => + client.get('/api/ai-insights/briefing') +export const getOllamaStatus = () => + client.get('/api/ai-insights/ollama-status') +export const pullOllamaModel = (model: string) => + client.post('/api/ai-insights/ollama-pull', { model }) + +/* 캘린더 / 유지보수 */ +export const getWorkCalendar = (year: number, month: number) => + client.get('/api/mobile2/work-calendar', { params: { year, month } }) +export const getMaintenanceWindows = () => + client.get('/api/cmdb/maintenance') +export const cancelMaintenanceWindow = (id: string | number) => + client.delete(`/api/cmdb/maintenance/${id}`) + +/* VM */ +export const getVMs = () => client.get('/api/cloud/vms') +export const vmAction = (id: string | number, action: 'start' | 'stop' | 'reboot') => + client.post(`/api/cloud/vms/${id}/${action}`) + +/* SSL / EOL */ +export const getSSLCerts = () => client.get('/api/cmdb/ssl-certs') +export const getEOLSoftware = () => client.get('/api/cmdb/eol-software') + +/* 리더보드 / 히트맵 */ +export const getTeamLeaderboard = () => + client.get('/api/mobile2/team-leaderboard') +export const getSRHourlyPattern = () => + client.get('/api/mobile2/hourly-pattern') + +/* 나라장터 / 시민 */ +export const getG2BContracts = () => + client.get('/api/public-sector/g2b-contracts') +export const getCitizenRequests = () => + client.get('/api/citizen/requests') + +/* Ollama 브리핑 보조 */ +export const getHourlyPattern = () => + client.get('/api/mobile2/hourly-pattern') +export const getIncidentPatterns = () => + client.get('/api/mobile2/incident-patterns') +export const getHandoverChecklist = () => + client.get('/api/mobile2/handover-checklist') +export const getTeamPresence = () => + client.get('/api/mobile2/team-presence') +export const getAppVersion = () => + client.get('/api/mobile2/app-version') +export const getAnnouncements = () => + client.get('/api/mobile2/announcements') + export default client diff --git a/services/sha256.ts b/services/sha256.ts new file mode 100644 index 00000000..732f0a60 --- /dev/null +++ b/services/sha256.ts @@ -0,0 +1,102 @@ +/** + * Pure-JS SHA-256 (no native module / no expo-crypto dependency). + * EAS 빌드 안전: 추가 네이티브 모듈 불필요. + * PIN 평문 저장 금지 — 이 해시 결과만 SecureStore에 저장한다. + */ + +function rrot(x: number, n: number): number { + return (x >>> n) | (x << (32 - n)) +} + +const K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +] + +function toBytes(str: string): number[] { + const utf8: number[] = [] + for (let i = 0; i < str.length; i++) { + let c = str.charCodeAt(i) + if (c < 0x80) utf8.push(c) + else if (c < 0x800) { + utf8.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)) + } else if (c < 0xd800 || c >= 0xe000) { + utf8.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)) + } else { + i++ + c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)) + utf8.push( + 0xf0 | (c >> 18), + 0x80 | ((c >> 12) & 0x3f), + 0x80 | ((c >> 6) & 0x3f), + 0x80 | (c & 0x3f) + ) + } + } + return utf8 +} + +const HEX = '0123456789abcdef' + +/** SHA-256 → hex 문자열 */ +export function sha256(message: string): string { + const bytes = toBytes(message) + const l = bytes.length + const bitLen = l * 8 + + bytes.push(0x80) + while (bytes.length % 64 !== 56) bytes.push(0) + // 64-bit length (high 32 bits = 0 for our use) + for (let i = 0; i < 4; i++) bytes.push(0) + bytes.push((bitLen >>> 24) & 0xff, (bitLen >>> 16) & 0xff, (bitLen >>> 8) & 0xff, bitLen & 0xff) + + let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a + let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19 + + const w = new Array(64) + for (let off = 0; off < bytes.length; off += 64) { + for (let i = 0; i < 16; i++) { + w[i] = + (bytes[off + i * 4] << 24) | + (bytes[off + i * 4 + 1] << 16) | + (bytes[off + i * 4 + 2] << 8) | + bytes[off + i * 4 + 3] + } + for (let i = 16; i < 64; i++) { + const s0 = rrot(w[i - 15], 7) ^ rrot(w[i - 15], 18) ^ (w[i - 15] >>> 3) + const s1 = rrot(w[i - 2], 17) ^ rrot(w[i - 2], 19) ^ (w[i - 2] >>> 10) + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0 + } + + let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7 + for (let i = 0; i < 64; i++) { + const S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25) + const ch = (e & f) ^ (~e & g) + const t1 = (h + S1 + ch + K[i] + w[i]) | 0 + const S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22) + const maj = (a & b) ^ (a & c) ^ (b & c) + const t2 = (S0 + maj) | 0 + h = g; g = f; f = e; e = (d + t1) | 0 + d = c; c = b; b = a; a = (t1 + t2) | 0 + } + + h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0 + h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0 + } + + const out = [h0, h1, h2, h3, h4, h5, h6, h7] + let hex = '' + for (const n of out) { + for (let s = 28; s >= 0; s -= 4) hex += HEX[(n >>> s) & 0xf] + } + return hex +} diff --git a/tsconfig.json b/tsconfig.json index 140c044c..b1b68619 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,12 @@ "paths": { "@/*": ["./*"] } - } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "types/**/*.d.ts", + ".expo/types/**/*.ts", + "expo-env.d.ts" + ] } diff --git a/types/native-modules.d.ts b/types/native-modules.d.ts new file mode 100644 index 00000000..103a5da0 --- /dev/null +++ b/types/native-modules.d.ts @@ -0,0 +1,32 @@ +/** + * 선택적 네이티브 모듈 타입 선언 (shim) + * + * EAS 빌드 시 package.json 의존성으로 설치되지만 로컬 node_modules 에 + * 항상 존재하지 않을 수 있는 모듈에 대한 최소 타입을 제공한다. + * 실제 모듈이 설치되어 있으면 해당 모듈의 정식 타입이 우선 적용된다. + */ + +declare module 'expo-image-picker' { + export interface ImagePickerAsset { + uri: string + width?: number + height?: number + fileName?: string | null + mimeType?: string + base64?: string | null + } + export interface ImagePickerResult { + canceled: boolean + assets: ImagePickerAsset[] | null + } + export const MediaTypeOptions: { All: 'All'; Images: 'Images'; Videos: 'Videos' } + export function requestMediaLibraryPermissionsAsync(): Promise<{ granted: boolean; status: string }> + export function requestCameraPermissionsAsync(): Promise<{ granted: boolean; status: string }> + export function launchImageLibraryAsync(options?: Record): Promise + export function launchCameraAsync(options?: Record): Promise +} + +declare module 'expo-notifications' { + export function setBadgeCountAsync(count: number): Promise + export function getBadgeCountAsync(): Promise +} diff --git a/utils/auth.ts b/utils/auth.ts new file mode 100644 index 00000000..f1da54cd --- /dev/null +++ b/utils/auth.ts @@ -0,0 +1,36 @@ +/** + * GUARDiA Messenger — 인증 토큰 유틸 + * SecureStore에 저장된 JWT 토큰을 읽어온다. (키: grd_token) + */ +import * as SecureStore from 'expo-secure-store' + +const TOKEN_KEY = 'grd_token' +const USER_KEY = 'grd_user' + +export async function getToken(): Promise { + try { + return await SecureStore.getItemAsync(TOKEN_KEY) + } catch { + return null + } +} + +export async function getStoredUser(): Promise { + try { + const raw = await SecureStore.getItemAsync(USER_KEY) + return raw ? (JSON.parse(raw) as T) : null + } catch { + return null + } +} + +/** 인증 헤더가 포함된 fetch 래퍼. */ +export async function authFetch(input: string, init: RequestInit = {}): Promise { + const token = await getToken() + const headers: Record = { + 'Content-Type': 'application/json', + ...(init.headers as Record | undefined), + } + if (token) headers.Authorization = `Bearer ${token}` + return fetch(input, { ...init, headers }) +} diff --git a/utils/security.ts b/utils/security.ts new file mode 100644 index 00000000..a23b5e8b --- /dev/null +++ b/utils/security.ts @@ -0,0 +1,68 @@ +/** + * GUARDiA Messenger — 보안 유틸 (현장서비스 모듈 공용) + * + * 보안 원칙 (불변): + * - 서버 IP(ip_addr), SSH 계정(ssh_user), 비밀번호(os_pw_enc)는 화면에 절대 노출 금지 + * - 배치 SSH / 터미널 출력의 IP 패턴은 자동 마스킹 + * - 파일 경로 traversal(`../`) 입력 차단 + */ + +/** IPv4 패턴 — 출력 텍스트에서 자동 마스킹 처리 */ +const IPV4_RE = /\b(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})\.(?:\d{1,3})\b/g + +/** 텍스트 내 IPv4 주소를 ***.***.***.*** 로 마스킹 */ +export function maskIPs(text: string): string { + if (!text) return text + return text.replace(IPV4_RE, '***.***.***.***') +} + +/** 위험 명령어 블랙리스트 — 클라이언트단 1차 차단 */ +export const BLOCKED_COMMANDS = [ + 'rm -rf /', + 'mkfs', + 'dd if=/dev/zero', + 'shutdown -h', + 'shutdown -r', + 'reboot', + ':(){:|:&};:', + '> /dev/sda', + 'chmod -R 000 /', + 'mv /home', +] + +/** 명령어 안전성 검사 — 위험 패턴 포함 시 false */ +export function validateCommand(cmd: string): boolean { + if (!cmd || !cmd.trim()) return false + const normalized = cmd.replace(/\s+/g, ' ').trim() + return !BLOCKED_COMMANDS.some((b) => normalized.includes(b)) +} + +/** 차단된 위험 패턴을 반환(있으면), 없으면 null */ +export function blockedReason(cmd: string): string | null { + const normalized = cmd.replace(/\s+/g, ' ').trim() + const hit = BLOCKED_COMMANDS.find((b) => normalized.includes(b)) + return hit ?? null +} + +/** 경로 traversal 차단 — `../`, `..\`, 또는 null byte 포함 시 false */ +export function isSafePath(path: string): boolean { + if (!path) return true + if (path.includes('..')) return false + if (path.includes('\0')) return false + return true +} + +/** + * 자산/서버 객체에서 민감 필드를 제거한 안전 복사본 반환. + * API 응답에 혹시라도 ip_addr/ssh_user/os_pw_enc가 포함되어도 화면에 닿지 않게 한다. + */ +export function sanitizeAsset>(obj: T): Partial { + if (!obj || typeof obj !== 'object') return obj + const SENSITIVE = ['ip_addr', 'ip_address', 'ssh_user', 'ssh_pass', 'os_pw', 'os_pw_enc', 'password', 'private_key'] + const out: Record = {} + for (const [k, v] of Object.entries(obj)) { + if (SENSITIVE.includes(k)) continue + out[k] = v + } + return out as Partial +}
${data?.period ?? ''}