feat: Cloudformation + RDS

This commit is contained in:
Zakaria
2026-09-30 17:43:42 -04:00
parent 548a15d7ec
commit 7764ea0dc1
411 changed files with 10182 additions and 1063 deletions
@@ -1,14 +1,17 @@
import {useEffect, useState} from 'react'
import {useEffect, useRef, useState} from 'react'
import {createPortal} from 'react-dom'
import {ChevronRight, Maximize2, Minimize2, RefreshCw, ScrollText} from 'lucide-react'
import {ChevronRight, Maximize2, Minimize2, Radio, RefreshCw, Search, ScrollText, Square} from 'lucide-react'
import {useQueries} from '@tanstack/react-query'
import {listChildCollections, listCollectionItems} from '@/api/cloudProxyClient'
import {filterLogEvents, listChildCollections, listCollectionItems} from '@/api/cloudProxyClient'
import {EmptyState} from '@/components/EmptyState'
import {ServiceIcon} from '@/components/serviceIcons'
import {formatBytes, formatDateTime} from '@/lib/format'
import {timeAgo} from '@/lib/utils'
import type {CloudProvider} from '@/types/cloud'
import type {CloudResource} from '@/types/resource'
const LIVE_TAIL_INTERVAL_MS = 2_000
interface LogsExplorerPanelProps {
cloud: CloudProvider
resource?: CloudResource
@@ -39,11 +42,31 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
const [streamCursors, setStreamCursors] = useState<Array<string | undefined>>([undefined])
const [eventCursors, setEventCursors] = useState<Array<string | undefined>>([undefined])
const [expanded, setExpanded] = useState(false)
const [eventMode, setEventMode] = useState<'stream' | 'filter'>('stream')
const [filterPattern, setFilterPattern] = useState('')
const [appliedFilterPattern, setAppliedFilterPattern] = useState('')
const [filteredEvents, setFilteredEvents] = useState<Awaited<ReturnType<typeof filterLogEvents>>['items']>([])
const [filterCursor, setFilterCursor] = useState<string | null>(null)
const [filterLoading, setFilterLoading] = useState(false)
const [filterError, setFilterError] = useState<string>()
const [liveTail, setLiveTail] = useState(false)
const filteredEventsRef = useRef(filteredEvents)
useEffect(() => {
filteredEventsRef.current = filteredEvents
}, [filteredEvents])
useEffect(() => {
setSelectedStreamId(undefined)
setStreamCursors([undefined])
setEventCursors([undefined])
setEventMode('stream')
setFilterPattern('')
setAppliedFilterPattern('')
setFilteredEvents([])
setFilterCursor(null)
setFilterError(undefined)
setLiveTail(false)
}, [cloud, groupId])
useEffect(() => {
@@ -86,13 +109,62 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
void firstEventPage?.refetch()
}
async function loadFilteredEvents(pattern: string, cursor?: string, append = false, signal?: AbortSignal) {
setFilterLoading(true)
setFilterError(undefined)
try {
const page = await filterLogEvents(cloud, groupId ?? '', {filterPattern: pattern || undefined}, cursor, signal)
setFilteredEvents((current) => append ? mergeEvents(current, page.items) : page.items)
setFilterCursor(page.nextCursor)
} catch (error) {
if (!signal?.aborted) setFilterError(error instanceof Error ? error.message : 'Unable to filter log events.')
} finally {
if (!signal?.aborted) setFilterLoading(false)
}
}
useEffect(() => {
if (!liveTail || !runtimeReachable || !groupId) return
const controller = new AbortController()
let timer: ReturnType<typeof setTimeout> | undefined
const poll = async () => {
try {
const latestTimestamp = filteredEventsRef.current.reduce((latest, event) => {
const timestamp = event.timestamp ? Date.parse(event.timestamp) : 0
return Math.max(latest, timestamp)
}, 0)
let cursor: string | undefined
do {
const page = await filterLogEvents(cloud, groupId, {
filterPattern: appliedFilterPattern || undefined,
startTime: latestTimestamp || undefined,
}, cursor, controller.signal)
setFilteredEvents((current) => mergeEvents(current, page.items))
cursor = page.nextCursor ?? undefined
} while (cursor && !controller.signal.aborted)
if (!controller.signal.aborted) timer = setTimeout(poll, LIVE_TAIL_INTERVAL_MS)
} catch (error) {
if (!controller.signal.aborted) {
setFilterError(error instanceof Error ? error.message : 'Live tail stopped after an error.')
setLiveTail(false)
}
}
}
void poll()
return () => {
controller.abort()
if (timer) clearTimeout(timer)
}
}, [appliedFilterPattern, cloud, groupId, liveTail, runtimeReachable])
if (!groupId) return null
const content = (
<section className="cosmos-panel">
<div className="cosmos-column">
<div className="cosmos-panel-header">
<ScrollText size={15}/>
<ServiceIcon cloud="aws" iconKey="logs" size={18}/>
<span>
<small>Streams</small>
<strong>{groupId}</strong>
@@ -150,7 +222,7 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
<div className="cosmos-column cosmos-column--wide">
<div className="cosmos-panel-header">
<ScrollText size={15}/>
<ServiceIcon cloud="aws" iconKey="logs" size={18}/>
<span>
<small>Events</small>
<strong>{selectedStreamId ?? 'Select a stream'}</strong>
@@ -158,24 +230,43 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
</span>
</div>
<div className="cosmos-toolbar">
<button className="button" type="button" disabled={!runtimeReachable || !selectedStreamId || firstEventPage?.isFetching} onClick={refreshEvents}>
<RefreshCw size={14}/>
Refresh
</button>
<div className="logs-mode-toggle" role="group" aria-label="Log event mode">
<button className={`button compact${eventMode === 'stream' ? ' active' : ''}`} type="button" onClick={() => { setEventMode('stream'); setLiveTail(false) }}>Selected stream</button>
<button className={`button compact${eventMode === 'filter' ? ' active' : ''}`} type="button" onClick={() => setEventMode('filter')}>Filter all streams</button>
</div>
{eventMode === 'stream' ? <button className="button" type="button" disabled={!runtimeReachable || !selectedStreamId || firstEventPage?.isFetching} onClick={refreshEvents}>
<RefreshCw size={14}/> Refresh
</button> : <form className="logs-filter-toolbar" onSubmit={(event) => {
event.preventDefault()
setAppliedFilterPattern(filterPattern.trim())
setLiveTail(false)
void loadFilteredEvents(filterPattern.trim())
}}>
<label><span className="sr-only">Filter pattern</span><input className="input logs-filter-pattern" aria-label="Filter pattern" value={filterPattern} onChange={(event) => setFilterPattern(event.target.value)} placeholder='Filter pattern, for example "ERROR"'/></label>
<button className="button" type="submit" disabled={!runtimeReachable || filterLoading}><Search size={14}/>{filterLoading ? 'Filtering' : 'Filter'}</button>
<button className={`button${liveTail ? ' danger' : ''}`} type="button" disabled={!runtimeReachable || filterLoading} onClick={() => {
if (!liveTail && appliedFilterPattern !== filterPattern.trim()) {
setAppliedFilterPattern(filterPattern.trim())
void loadFilteredEvents(filterPattern.trim())
}
setLiveTail((current) => !current)
}}>{liveTail ? <Square size={13}/> : <Radio size={13}/>} {liveTail ? 'Stop tail' : 'Live tail'}</button>
</form>}
</div>
{firstEventPage?.error instanceof Error && <div className="form-error">{firstEventPage.error.message}</div>}
{eventMode === 'stream' && firstEventPage?.error instanceof Error && <div className="form-error">{firstEventPage.error.message}</div>}
{eventMode === 'filter' && filterError && <div className="form-error">{filterError}</div>}
<div className="cosmos-items-table">
{!selectedStreamId && (
{eventMode === 'stream' && !selectedStreamId && (
<div className="empty compact"><h3>Select a stream</h3><p>Events are scoped to a log stream.</p></div>
)}
{selectedStreamId && !firstEventPage?.isLoading && events.length === 0 && (
{eventMode === 'stream' && selectedStreamId && !firstEventPage?.isLoading && events.length === 0 && (
<EmptyState
icon={ScrollText}
title="No log events"
description="Events written to this stream will appear here."
/>
)}
{selectedStreamId && (firstEventPage?.isLoading ? (
{eventMode === 'stream' && selectedStreamId && (firstEventPage?.isLoading ? (
<div className="muted padded">Loading log events</div>
) : events.length > 0 && (
<>
@@ -211,6 +302,12 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
)}
</>
))}
{eventMode === 'filter' && !filterLoading && filteredEvents.length === 0 && <EmptyState icon={Search} title="No matching events" description="Apply a filter pattern or start live tail to search every stream in this group."/>}
{eventMode === 'filter' && filteredEvents.length > 0 && <>
{liveTail && <div className="logs-live-status"><Radio size={12}/> Live tail active</div>}
<table className="table logs-events-table"><thead><tr><th>Timestamp</th><th>Log stream</th><th>Message</th></tr></thead><tbody>{filteredEvents.map((event) => <tr key={event.id}><td className="mono">{formatDateTime(event.timestamp) ?? '-'}</td><td className="mono logs-event-stream">{event.collectionId ?? '-'}</td><td className="mono">{String(event.body.message ?? '')}</td></tr>)}</tbody></table>
{filterCursor && !liveTail && <div style={{display: 'flex', justifyContent: 'center', padding: '12px 0'}}><button className="button" type="button" disabled={filterLoading} onClick={() => void loadFilteredEvents(appliedFilterPattern, filterCursor, true)}>{filterLoading ? 'Loading…' : 'Load more'}</button></div>}
</>}
</div>
</div>
</section>
@@ -219,7 +316,7 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
const summary = (
<section className="logs-query-panel">
<div className="logs-query-header">
<ScrollText size={15}/>
<ServiceIcon cloud="aws" iconKey="logs" size={18}/>
<span>
<small>Streams &amp; events</small>
<strong>{groupId}</strong>
@@ -243,3 +340,9 @@ export function LogsExplorerPanel({cloud, resource, runtimeReachable}: LogsExplo
document.body,
)
}
function mergeEvents(current: Awaited<ReturnType<typeof filterLogEvents>>['items'], incoming: Awaited<ReturnType<typeof filterLogEvents>>['items']) {
const byId = new Map(current.map((event) => [event.id, event]))
for (const event of incoming) byId.set(event.id, event)
return [...byId.values()].sort((left, right) => Date.parse(left.timestamp ?? '') - Date.parse(right.timestamp ?? ''))
}