diff --git a/backend/modules/soar/executor/notify.go b/backend/modules/soar/executor/notify.go index c4dc94798..491910a78 100644 --- a/backend/modules/soar/executor/notify.go +++ b/backend/modules/soar/executor/notify.go @@ -30,7 +30,7 @@ func (Notify) Type() string { return "notify" } type notifyParams struct { Message string `json:"message"` - Type string `json:"type,omitempty"` // INFO (default) | WARNING + Type string `json:"type,omitempty"` } func (n *Notify) Execute(ctx context.Context, exec *soardomain.SoarExecution) (json.RawMessage, error) { @@ -47,8 +47,11 @@ func (n *Notify) Execute(ctx context.Context, exec *soardomain.SoarExecution) (j return nil, errors.New("soar notify: message is required") } ntype := domain.TypeInfo - if p.Type == string(domain.TypeWarning) { + switch domain.NotificationType(p.Type) { + case domain.TypeWarning: ntype = domain.TypeWarning + case domain.TypeError: + ntype = domain.TypeError } if err := n.client.Notify(ctx, domain.SourceSystem, ntype, p.Message); err != nil { return nil, err diff --git a/backend/modules/soar/usecase/command_summary.go b/backend/modules/soar/usecase/command_summary.go index ab8ec0dc3..11b812e88 100644 --- a/backend/modules/soar/usecase/command_summary.go +++ b/backend/modules/soar/usecase/command_summary.go @@ -69,11 +69,14 @@ func summarizeNodeAction(executor string, params json.RawMessage) string { } return "" case "notify": - label := "notify (INFO)" - if g.Get("type").Str == string(notificationdomain.TypeWarning) { - label = "notify (WARNING)" + ntype := "INFO" + switch g.Get("type").Str { + case string(notificationdomain.TypeWarning): + ntype = "WARNING" + case string(notificationdomain.TypeError): + ntype = "ERROR" } - return label + ": " + g.Get("message").Str + return "notify (" + ntype + "): " + g.Get("message").Str case "incident": return "open incident: " + g.Get("name").Str case "conditional": diff --git a/frontend/src/features/soar/components/NodeInspector.tsx b/frontend/src/features/soar/components/NodeInspector.tsx index 602a3f00f..d1d2f134a 100644 --- a/frontend/src/features/soar/components/NodeInspector.tsx +++ b/frontend/src/features/soar/components/NodeInspector.tsx @@ -12,6 +12,7 @@ import { IncidentParamsEditor } from './IncidentParamsEditor' import { InsertFieldMenu } from './InsertFieldMenu' import { MailParamsEditor } from './MailParamsEditor' import { LLMParamsEditor } from './LLMParamsEditor' +import { NotifyParamsEditor } from './NotifyParamsEditor' interface Props { nodeId: string @@ -244,6 +245,16 @@ export function NodeInspector({ nodeId, node, nodes, readOnly, onRename, onChang /> )} + {node.executor === 'notify' && ( + onChange({ params: next })} + /> + )} + {(node.executor === 'llm_enrich' || node.executor === 'llm_action') && ( )} - {node.executor !== 'shell' && node.executor !== 'conditional' && node.executor !== 'http' && node.executor !== 'incident' && node.executor !== 'mail' && node.executor !== 'llm_enrich' && node.executor !== 'llm_action' && ( + {node.executor !== 'shell' && node.executor !== 'conditional' && node.executor !== 'http' && node.executor !== 'incident' && node.executor !== 'mail' && node.executor !== 'notify' && node.executor !== 'llm_enrich' && node.executor !== 'llm_action' && ( {!readOnly && (
diff --git a/frontend/src/features/soar/components/NotifyParamsEditor.tsx b/frontend/src/features/soar/components/NotifyParamsEditor.tsx new file mode 100644 index 000000000..5e31b11af --- /dev/null +++ b/frontend/src/features/soar/components/NotifyParamsEditor.tsx @@ -0,0 +1,95 @@ +import { useRef } from 'react' +import { useTranslation } from 'react-i18next' +import type { FlowNode } from '../types/soar.types' +import { InsertFieldMenu } from './InsertFieldMenu' + +interface NotifyParams { + message?: string + type?: string +} + +interface Props { + nodeId: string + nodes: Record + params: unknown + readOnly?: boolean + onChange: (params: NotifyParams) => void +} + +const LEVELS = ['INFO', 'WARNING', 'ERROR'] as const + +const SELECT = + 'h-8 rounded-md border border-input bg-background px-2 text-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring' + +export function NotifyParamsEditor({ nodeId, nodes, params, readOnly, onChange }: Props) { + const { t } = useTranslation() + const p = normalize(params) + const messageRef = useRef(null) + + const insertIntoMessage = (token: string) => { + const el = messageRef.current + const cur = p.message ?? '' + const start = el?.selectionStart ?? cur.length + const end = el?.selectionEnd ?? cur.length + const next = cur.slice(0, start) + token + cur.slice(end) + onChange({ ...p, message: next }) + requestAnimationFrame(() => { + const el2 = messageRef.current + if (!el2) return + el2.focus() + const pos = start + token.length + el2.setSelectionRange(pos, pos) + }) + } + + return ( +
+ + + + + {!readOnly && ( +
+ +
+ )} +