feat: add SQS + EventBridge
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import {Activity, RefreshCw, Timer, TriangleAlert} from 'lucide-react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import {queryLogs} from '@/api/cloudProxyClient'
|
||||
import type {CloudProvider} from '@/types/cloud'
|
||||
|
||||
interface LambdaInvocationHistoryPanelProps {
|
||||
cloud: CloudProvider
|
||||
functionName: string
|
||||
runtimeReachable: boolean
|
||||
}
|
||||
|
||||
interface InvocationRecord {
|
||||
requestId: string
|
||||
timestamp: string
|
||||
durationMs: number
|
||||
errorType: string | null
|
||||
}
|
||||
|
||||
const HISTORY_QUERY = 'fields @timestamp, @message\n| filter @message like /REPORT RequestId:/\n| sort @timestamp desc\n| limit 20'
|
||||
|
||||
export function LambdaInvocationHistoryPanel({cloud, functionName, runtimeReachable}: LambdaInvocationHistoryPanelProps) {
|
||||
const history = useQuery({
|
||||
queryKey: ['lambda-invocation-history', cloud, functionName],
|
||||
queryFn: async () => {
|
||||
const endTime = Math.floor(Date.now() / 1000)
|
||||
try {
|
||||
const result = await queryLogs(cloud, `/aws/lambda/${functionName}`, {
|
||||
queryString: HISTORY_QUERY,
|
||||
startTime: endTime - 24 * 60 * 60,
|
||||
endTime,
|
||||
})
|
||||
return result.rows.map(toInvocationRecord).filter((record): record is InvocationRecord => record !== null)
|
||||
} catch (error) {
|
||||
if (isMissingLogGroup(error)) return []
|
||||
throw error
|
||||
}
|
||||
},
|
||||
enabled: cloud === 'aws' && runtimeReachable,
|
||||
})
|
||||
const records = history.data ?? []
|
||||
const errors = records.filter((record) => record.errorType !== null).length
|
||||
const averageDuration = records.length === 0 ? null : records.reduce((sum, record) => sum + record.durationMs, 0) / records.length
|
||||
|
||||
return <section className="table-panel">
|
||||
<div className="dynamic-stage-header">
|
||||
<div><p className="eyebrow">Observability</p><h3><Activity size={15}/> Invocation history</h3><p className="muted compact-text">Real Lambda platform records from the last 24 hours.</p></div>
|
||||
<button className="button" type="button" disabled={history.isFetching} onClick={() => history.refetch()}><RefreshCw size={13}/>{history.isFetching ? 'Refreshing' : 'Refresh'}</button>
|
||||
</div>
|
||||
<div className="apigateway-request-flow">
|
||||
<div className="apigateway-flow-step"><Timer size={16}/><div><strong>{averageDuration === null ? 'No duration' : `${averageDuration.toFixed(1)} ms`}</strong><small>Average duration</small></div></div>
|
||||
<div className="apigateway-flow-step"><TriangleAlert size={16}/><div><strong>{errors}</strong><small>Function errors</small></div></div>
|
||||
<div className="apigateway-flow-step"><Activity size={16}/><div><strong>{records.length}</strong><small>Recorded invocations</small></div></div>
|
||||
</div>
|
||||
{history.isError && <p className="error-text compact-text">{history.error instanceof Error ? history.error.message : 'Unable to load invocation history.'}</p>}
|
||||
{!history.isLoading && records.length === 0 && <p className="muted compact-text">No Lambda platform records yet. Invoke the function or trigger it from S3, then refresh.</p>}
|
||||
{records.length > 0 && <table className="table"><thead><tr><th>Time</th><th>Request ID</th><th>Duration</th><th>Outcome</th></tr></thead><tbody>{records.map((record) => <tr key={record.requestId}><td>{record.timestamp}</td><td><code>{record.requestId}</code></td><td>{record.durationMs.toFixed(2)} ms</td><td>{record.errorType ? <span className="error-text">{record.errorType}</span> : 'Success'}</td></tr>)}</tbody></table>}
|
||||
</section>
|
||||
}
|
||||
|
||||
function toInvocationRecord(row: Record<string, string>): InvocationRecord | null {
|
||||
const message = row['@message'] ?? ''
|
||||
const match = /REPORT RequestId: (\S+) Duration: ([\d.]+) ms.*?(?: Error Type: (.+))?$/.exec(message)
|
||||
if (!match) return null
|
||||
return {requestId: match[1], timestamp: row['@timestamp'] ?? '', durationMs: Number(match[2]), errorType: match[3] ?? null}
|
||||
}
|
||||
|
||||
function isMissingLogGroup(error: unknown): boolean {
|
||||
return error instanceof Error && /log group does not exist|resource_not_found/i.test(error.message)
|
||||
}
|
||||
Reference in New Issue
Block a user