diff --git a/src/pages/common-apps/CommonAppLogsPage.tsx b/src/pages/common-apps/CommonAppLogsPage.tsx index 51b3cc3..e3c773e 100644 --- a/src/pages/common-apps/CommonAppLogsPage.tsx +++ b/src/pages/common-apps/CommonAppLogsPage.tsx @@ -24,6 +24,25 @@ function eventTone(type?: string) { return 'neutral'; } +const eventRanges = [ + { label: '1시간', value: '1h', milliseconds: 60 * 60 * 1000 }, + { label: '24시간', value: '24h', milliseconds: 24 * 60 * 60 * 1000 }, + { label: '전체', value: 'all', milliseconds: null } +]; + +function eventTime(event: RuntimeEvent) { + const value = event.lastTimestamp ?? event.firstTimestamp; + if (!value) return 0; + const time = new Date(value).getTime(); + return Number.isFinite(time) ? time : 0; +} + +function isEventInRange(event: RuntimeEvent, range: (typeof eventRanges)[number]) { + if (!range.milliseconds) return true; + const time = eventTime(event); + return time > 0 && time >= Date.now() - range.milliseconds; +} + function metricKey(namespace: string, pod: string) { return `${namespace}/${pod}`; } @@ -125,6 +144,7 @@ export function CommonAppLogsPage() { const [tail, setTail] = useState(100); const [activeTab, setActiveTab] = useState<'logs' | 'events'>('logs'); const [search, setSearch] = useState(''); + const [eventRange, setEventRange] = useState('1h'); const [loading, setLoading] = useState(true); const [logLoading, setLogLoading] = useState(false); const [error, setError] = useState(''); @@ -142,7 +162,9 @@ export function CommonAppLogsPage() { const componentResources = selectedComponent ? resources.filter((resource) => relatedToComponent(resource, selectedComponent)) : []; const loggableResources = componentResources.filter(isActiveLogTarget); const selectedMetric = selectedResource?.kind === 'Pod' ? metricByPod.get(metricKey(activeNamespace, selectedResource.name)) : undefined; + const selectedEventRange = eventRanges.find((range) => range.value === eventRange) ?? eventRanges[0]; const filteredEvents = events.filter((event) => { + if (!isEventInRange(event, selectedEventRange)) return false; const normalizedSearch = search.trim().toLowerCase(); const componentMatch = selectedComponent ? `${event.involvedObject ?? ''} ${event.message ?? ''}`.toLowerCase().includes(selectedComponent.workloadName.toLowerCase()) @@ -197,6 +219,26 @@ export function CommonAppLogsPage() { refresh(); }, []); + useEffect(() => { + if (activeTab !== 'events') return; + + let cancelled = false; + const loadEvents = () => { + loadRuntimeEvents(COMMON_APP_NAMESPACE) + .then((nextEvents) => { + if (!cancelled) setEvents(nextEvents); + }) + .catch(() => undefined); + }; + + loadEvents(); + const timer = window.setInterval(loadEvents, 10_000); + return () => { + cancelled = true; + window.clearInterval(timer); + }; + }, [activeTab]); + return ( <> ) : ( -
- - setSearch(event.target.value)} - /> +
+
+ {eventRanges.map((range) => ( + + ))} +
+
+ + setSearch(event.target.value)} + /> +
)}
diff --git a/src/pages/launcher/LauncherLogsPage.tsx b/src/pages/launcher/LauncherLogsPage.tsx index f5d335c..a3d211b 100644 --- a/src/pages/launcher/LauncherLogsPage.tsx +++ b/src/pages/launcher/LauncherLogsPage.tsx @@ -53,6 +53,25 @@ function eventTone(type?: string) { return 'neutral'; } +const eventRanges = [ + { label: '1시간', value: '1h', milliseconds: 60 * 60 * 1000 }, + { label: '24시간', value: '24h', milliseconds: 24 * 60 * 60 * 1000 }, + { label: '전체', value: 'all', milliseconds: null } +]; + +function eventTime(event: RuntimeEvent) { + const value = event.lastTimestamp ?? event.firstTimestamp; + if (!value) return 0; + const time = new Date(value).getTime(); + return Number.isFinite(time) ? time : 0; +} + +function isEventInRange(event: RuntimeEvent, range: (typeof eventRanges)[number]) { + if (!range.milliseconds) return true; + const time = eventTime(event); + return time > 0 && time >= Date.now() - range.milliseconds; +} + function metricKey(namespace: string, pod: string) { return `${namespace}/${pod}`; } @@ -155,6 +174,7 @@ export function LauncherLogsPage() { const [tail, setTail] = useState(100); const [activeTab, setActiveTab] = useState<'logs' | 'events'>('logs'); const [search, setSearch] = useState(''); + const [eventRange, setEventRange] = useState('1h'); const [loading, setLoading] = useState(true); const [logLoading, setLogLoading] = useState(false); const [error, setError] = useState(''); @@ -172,7 +192,9 @@ export function LauncherLogsPage() { const componentResources = selectedComponent ? resources.filter((resource) => relatedToComponent(resource, selectedComponent)) : []; const loggableResources = componentResources.filter(isActiveLogTarget); const selectedMetric = selectedResource?.kind === 'Pod' ? metricByPod.get(metricKey(activeNamespace, selectedResource.name)) : undefined; + const selectedEventRange = eventRanges.find((range) => range.value === eventRange) ?? eventRanges[0]; const filteredEvents = events.filter((event) => { + if (!isEventInRange(event, selectedEventRange)) return false; const normalizedSearch = search.trim().toLowerCase(); const componentText = `${event.involvedObject ?? ''} ${event.message ?? ''}`.toLowerCase(); const componentMatch = selectedComponent ? componentText.includes(selectedComponent.workloadName.toLowerCase()) : true; @@ -226,6 +248,26 @@ export function LauncherLogsPage() { refresh(); }, []); + useEffect(() => { + if (activeTab !== 'events') return; + + let cancelled = false; + const loadEvents = () => { + loadRuntimeEvents(LAUNCHER_NAMESPACE) + .then((nextEvents) => { + if (!cancelled) setEvents(nextEvents); + }) + .catch(() => undefined); + }; + + loadEvents(); + const timer = window.setInterval(loadEvents, 10_000); + return () => { + cancelled = true; + window.clearInterval(timer); + }; + }, [activeTab]); + return ( <> ) : ( -
- - setSearch(event.target.value)} - /> +
+
+ {eventRanges.map((range) => ( + + ))} +
+
+ + setSearch(event.target.value)} + /> +
)}
diff --git a/src/pages/runtime/RuntimeLogsPage.tsx b/src/pages/runtime/RuntimeLogsPage.tsx index 1e95c64..7bf9d4d 100644 --- a/src/pages/runtime/RuntimeLogsPage.tsx +++ b/src/pages/runtime/RuntimeLogsPage.tsx @@ -16,6 +16,25 @@ function eventTone(type?: string) { return 'neutral'; } +const eventRanges = [ + { label: '1시간', value: '1h', milliseconds: 60 * 60 * 1000 }, + { label: '24시간', value: '24h', milliseconds: 24 * 60 * 60 * 1000 }, + { label: '전체', value: 'all', milliseconds: null } +]; + +function eventTime(event: RuntimeEvent) { + const value = event.lastTimestamp ?? event.firstTimestamp; + if (!value) return 0; + const time = new Date(value).getTime(); + return Number.isFinite(time) ? time : 0; +} + +function isEventInRange(event: RuntimeEvent, range: (typeof eventRanges)[number]) { + if (!range.milliseconds) return true; + const time = eventTime(event); + return time > 0 && time >= Date.now() - range.milliseconds; +} + function apiKind(kind: string) { return kind.toLowerCase(); } @@ -128,6 +147,7 @@ export function RuntimeLogsPage() { const [tail, setTail] = useState(100); const [activeTab, setActiveTab] = useState<'logs' | 'events'>('logs'); const [search, setSearch] = useState(''); + const [eventRange, setEventRange] = useState('1h'); const [loading, setLoading] = useState(true); const [logLoading, setLogLoading] = useState(false); const [error, setError] = useState(''); @@ -147,7 +167,9 @@ export function RuntimeLogsPage() { }, [podMetrics]); const loggableResources = resources.filter(isActiveLogTarget); const selectedMetric = selectedResource?.kind === 'Pod' ? metricByPod.get(metricKey(activeNamespace, selectedResource.name)) : undefined; + const selectedEventRange = eventRanges.find((range) => range.value === eventRange) ?? eventRanges[0]; const filteredEvents = events.filter((event) => { + if (!isEventInRange(event, selectedEventRange)) return false; const normalizedSearch = search.trim().toLowerCase(); if (!normalizedSearch) return true; return `${event.type ?? ''} ${event.reason ?? ''} ${event.message ?? ''} ${event.involvedObject ?? ''}`.toLowerCase().includes(normalizedSearch); @@ -217,6 +239,26 @@ export function RuntimeLogsPage() { .finally(() => setLoading(false)); }, []); + useEffect(() => { + if (activeTab !== 'events' || !namespace.trim()) return; + + let cancelled = false; + const loadEvents = () => { + loadRuntimeEvents(namespace.trim()) + .then((nextEvents) => { + if (!cancelled) setEvents(nextEvents); + }) + .catch(() => undefined); + }; + + loadEvents(); + const timer = window.setInterval(loadEvents, 10_000); + return () => { + cancelled = true; + window.clearInterval(timer); + }; + }, [activeTab, namespace]); + return ( <> ) : ( -
- - setSearch(event.target.value)} - /> +
+
+ {eventRanges.map((range) => ( + + ))} +
+
+ + setSearch(event.target.value)} + /> +
)}