@@ -5,11 +5,14 @@ import { cn } from '@/shared/lib/utils'
55import { Button } from '@/shared/components/ui/button'
66import { Input } from '@/shared/components/ui/input'
77import { InfiniteScrollSentinel } from '@/shared/components/ui/infinite-scroll'
8+ import { presetRange , resolveRange , TimeRangePicker , type TimeRange } from '@/shared/components/ui/time-range-picker'
89import { useDateFormat } from '@/shared/lib/datetime'
10+ import { datasourcesHttpService } from '@/features/datasources/services/datasources-http.service'
911import { soarExecutionsService } from '../services/soar-executions.service'
10- import type { Execution , ExecutionStatus , ExecutionListQuery } from '../types/soar.types'
12+ import type { Execution , ExecutionOrigin , ExecutionStatus , ExecutionListQuery } from '../types/soar.types'
1113
1214const STATUSES : ( ExecutionStatus | 'all' ) [ ] = [ 'all' , 'EXECUTED' , 'PENDING' , 'FAILED' ]
15+ const ORIGINS : ( ExecutionOrigin | 'all' ) [ ] = [ 'all' , 'FLOW' , 'MANUAL' ]
1316const COLS = '90px minmax(160px,1.2fr) minmax(180px,1.6fr) 120px 150px 60px'
1417
1518const STATUS_META : Record < ExecutionStatus , { icon : typeof CheckCircle2 ; cls : string } > = {
@@ -24,6 +27,10 @@ export function ExecutionsView() {
2427 const [ search , setSearch ] = useState ( '' )
2528 const [ debounced , setDebounced ] = useState ( '' )
2629 const [ status , setStatus ] = useState < ExecutionStatus | 'all' > ( 'all' )
30+ const [ origin , setOrigin ] = useState < ExecutionOrigin | 'all' > ( 'FLOW' )
31+ const [ agent , setAgent ] = useState < string > ( '' )
32+ const [ agents , setAgents ] = useState < string [ ] > ( [ ] )
33+ const [ range , setRange ] = useState < TimeRange > ( presetRange ( '7d' ) )
2734 const [ items , setItems ] = useState < Execution [ ] > ( [ ] )
2835 const [ total , setTotal ] = useState ( 0 )
2936 const [ page , setPage ] = useState ( 0 )
@@ -39,15 +46,27 @@ export function ExecutionsView() {
3946 return ( ) => clearTimeout ( h )
4047 } , [ search ] )
4148
42- const query = useMemo < ExecutionListQuery > (
43- ( ) => ( {
49+ // Same source as FlowEditor / InteractiveConsole — Execution.agent stores the datasource name.
50+ useEffect ( ( ) => {
51+ datasourcesHttpService
52+ . list ( { page : 1 , size : 1000 , kind : 'agent' , sort : 'asset_name.asc' } )
53+ . then ( ( r ) => setAgents ( ( r . items ?? [ ] ) . map ( ( d ) => d . name ) . filter ( Boolean ) ) )
54+ . catch ( ( ) => { } )
55+ } , [ ] )
56+
57+ const query = useMemo < ExecutionListQuery > ( ( ) => {
58+ const { from, to } = resolveRange ( range )
59+ return {
4460 alertId : debounced || undefined ,
4561 status : status === 'all' ? undefined : status ,
62+ origin : origin === 'all' ? undefined : origin ,
63+ agent : agent || undefined ,
64+ startedAtFrom : from ?? undefined ,
65+ startedAtTo : to ,
4666 page,
4767 size : pageSize ,
48- } ) ,
49- [ debounced , status , page , pageSize ] ,
50- )
68+ }
69+ } , [ debounced , status , origin , agent , range , page , pageSize ] )
5170
5271 const load = useCallback ( ( ) => {
5372 setLoading ( true )
@@ -86,6 +105,42 @@ export function ExecutionsView() {
86105 </ button >
87106 ) ) }
88107 </ div >
108+ < div className = "inline-flex rounded-md border border-border p-0.5" >
109+ { ORIGINS . map ( ( o ) => (
110+ < button
111+ key = { o }
112+ onClick = { ( ) => {
113+ setOrigin ( o )
114+ setPage ( 0 )
115+ } }
116+ className = { cn ( 'rounded px-2.5 py-1 text-xs transition-colors' , origin === o ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:text-foreground' ) }
117+ >
118+ { o === 'all' ? t ( 'soar.executions.all' ) : t ( `soar.executionOrigin.${ o } ` ) }
119+ </ button >
120+ ) ) }
121+ </ div >
122+ < select
123+ value = { agent }
124+ onChange = { ( e ) => {
125+ setAgent ( e . target . value )
126+ setPage ( 0 )
127+ } }
128+ className = "h-9 cursor-pointer rounded-md border border-input bg-popover px-2 text-xs text-foreground"
129+ title = { t ( 'soar.executions.filters.source' ) }
130+ >
131+ < option value = "" > { t ( 'soar.executions.filters.allSources' ) } </ option >
132+ { agents . map ( ( a ) => (
133+ < option key = { a } value = { a } > { a } </ option >
134+ ) ) }
135+ </ select >
136+ < TimeRangePicker
137+ value = { range }
138+ onChange = { ( r ) => {
139+ setRange ( r )
140+ setPage ( 0 )
141+ } }
142+ align = "right"
143+ />
89144 < Button variant = "outline" size = "sm" onClick = { load } disabled = { loading } title = { t ( 'soar.refresh' ) } >
90145 < RefreshCw size = { 14 } className = { cn ( loading && 'animate-spin' ) } />
91146 </ Button >
0 commit comments