Files
floci-ui/packages/frontend/src/components/ScheduledLambdaTriggerPanel.tsx
T
2026-09-30 17:43:42 -04:00

92 lines
5.8 KiB
TypeScript

import {useEffect, useState} from 'react'
import {CalendarClock, Loader2, Plus, Trash2} from 'lucide-react'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {createEventBridgeLambdaTrigger, deleteEventBridgeLambdaTrigger, listEventBridgeLambdaTriggers, type EventBridgeLambdaTriggerOverview} from '@/api/cloudProxyClient'
import type {CloudProvider} from '@/types/cloud'
import type {CloudResource} from '@/types/resource'
import {ServiceIcon} from '@/components/serviceIcons'
interface ScheduledLambdaTriggerPanelProps {
cloud: CloudProvider
resource?: CloudResource
runtimeReachable: boolean
}
export function ScheduledLambdaTriggerPanel({cloud, resource, runtimeReachable}: ScheduledLambdaTriggerPanelProps) {
const [ruleName, setRuleName] = useState('')
const [scheduleExpression, setScheduleExpression] = useState('rate(5 minutes)')
const [deleteTarget, setDeleteTarget] = useState<EventBridgeLambdaTriggerOverview | null>(null)
const queryClient = useQueryClient()
const triggersQuery = useQuery({
queryKey: ['eventbridge-lambda-triggers', cloud, resource?.id],
queryFn: ({signal}) => listEventBridgeLambdaTriggers(cloud, resource!.name, signal),
enabled: cloud === 'aws' && Boolean(resource) && runtimeReachable,
})
useEffect(() => {
setRuleName('')
setScheduleExpression('rate(5 minutes)')
setDeleteTarget(null)
}, [resource?.id])
const createMutation = useMutation({
mutationFn: () => createEventBridgeLambdaTrigger(cloud, resource!.name, {ruleName, scheduleExpression}),
onSuccess: () => {
setRuleName('')
queryClient.invalidateQueries({queryKey: ['eventbridge-lambda-triggers', cloud, resource?.id]})
},
})
const deleteMutation = useMutation({
mutationFn: (trigger: EventBridgeLambdaTriggerOverview) => deleteEventBridgeLambdaTrigger(cloud, resource!.name, trigger.id),
onSuccess: () => {
setDeleteTarget(null)
queryClient.invalidateQueries({queryKey: ['eventbridge-lambda-triggers', cloud, resource?.id]})
},
})
if (!resource || resource.service !== 'serverless' || resource.type !== 'lambda') return null
const canCreate = Boolean(ruleName.trim() && scheduleExpression.trim()) && !createMutation.isPending
return <section className="table-panel">
<div className="dynamic-stage-header">
<div>
<p className="eyebrow">Schedule</p>
<h3><CalendarClock size={15}/> Add scheduled trigger</h3>
<p className="muted compact-text">Invoke <code>{resource.name}</code> on an EventBridge schedule.</p>
</div>
</div>
<div className="resource-create-inline">
<form className="inspector-action-form apigateway-route-form" onSubmit={(event) => {
event.preventDefault()
if (canCreate) createMutation.mutate()
}}>
<label className="dynamic-field">
<span>Rule name</span>
<input className="input" value={ruleName} onChange={(event) => setRuleName(event.target.value)} placeholder="hourly-report" required/>
<small>A unique EventBridge rule name.</small>
</label>
<label className="dynamic-field">
<span>Schedule expression</span>
<input className="input" value={scheduleExpression} onChange={(event) => setScheduleExpression(event.target.value)} placeholder="rate(5 minutes)" required/>
<small>Use a rate or cron expression, for example <code>rate(1 hour)</code>.</small>
</label>
<button className="button primary apigateway-route-submit" type="submit" disabled={!canCreate}>
{createMutation.isPending ? <Loader2 size={13}/> : <Plus size={13}/>}
{createMutation.isPending ? 'Adding schedule' : 'Add schedule'}
</button>
</form>
{createMutation.isError && <p className="error-text compact-text">{createMutation.error instanceof Error ? createMutation.error.message : 'Unable to add scheduled trigger.'}</p>}
</div>
<div className="apigateway-route-explorer">
<div className="dynamic-stage-header"><div><p className="eyebrow">Configured Schedules</p><h3><ServiceIcon cloud="aws" iconKey="events" size={18}/> EventBridge rules</h3></div></div>
{triggersQuery.isLoading && <p className="muted compact-text">Loading scheduled triggers...</p>}
{triggersQuery.isError && <p className="error-text compact-text">{triggersQuery.error instanceof Error ? triggersQuery.error.message : 'Unable to load scheduled triggers.'}</p>}
{triggersQuery.data?.length === 0 && <p className="muted compact-text">No scheduled triggers configured for this Lambda.</p>}
{triggersQuery.data?.map((trigger) => <div className="apigateway-route-item" key={trigger.id}>
<div className="apigateway-route-select"><span className="apigateway-route-method">RULE</span><code>{trigger.ruleName}</code><small>{trigger.scheduleExpression}{trigger.state ? ` · ${trigger.state}` : ''}</small></div>
{deleteTarget?.id === trigger.id ? <div className="apigateway-route-confirm"><span>Remove schedule?</span><button className="button danger compact" type="button" disabled={deleteMutation.isPending} onClick={() => deleteMutation.mutate(trigger)}>Remove</button><button className="button compact" type="button" disabled={deleteMutation.isPending} onClick={() => setDeleteTarget(null)}>Cancel</button></div> : <button className="icon-btn" type="button" aria-label={`Remove schedule ${trigger.ruleName}`} onClick={() => setDeleteTarget(trigger)}><Trash2 size={13}/></button>}
</div>)}
</div>
</section>
}