From 6e4607dc11cfdceea42909fee375f3e6592a58c4 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Fri, 19 Jun 2026 11:59:41 +0200 Subject: [PATCH 1/7] feat(chaining): event creation drawer (#6297) --- .../admin/components/chaining/logic/Logic.tsx | 19 +- .../ChainingFlowConfiguration.tsx | 67 ++++- .../logic/chaining_flow/LogicFlow.tsx | 22 +- .../logic/events/ConditionGroupBuilder.tsx | 182 +++++++++++++ .../logic/events/ConfigureEventDetail.tsx | 51 ++++ .../logic/events/EventConditionRow.tsx | 211 +++++++++++++++ .../logic/events/EventCreationForm.tsx | 252 ++++++++++++++++++ .../logic/events/LogicalOperatorSelect.tsx | 75 ++++++ .../chaining/logic/events/event-types.ts | 158 +++++++++++ .../chaining/logic/logic-flow-helpers.ts | 157 +++++++++-- .../admin/components/chaining/logic/types.ts | 11 +- openaev-front/src/utils/lang/de.json | 18 ++ openaev-front/src/utils/lang/en.json | 18 ++ openaev-front/src/utils/lang/es.json | 18 ++ openaev-front/src/utils/lang/fr.json | 18 ++ openaev-front/src/utils/lang/it.json | 18 ++ openaev-front/src/utils/lang/ja.json | 18 ++ openaev-front/src/utils/lang/ko.json | 18 ++ openaev-front/src/utils/lang/ru.json | 18 ++ openaev-front/src/utils/lang/zh.json | 18 ++ 20 files changed, 1325 insertions(+), 42 deletions(-) create mode 100644 openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx create mode 100644 openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx create mode 100644 openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx create mode 100644 openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx create mode 100644 openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx create mode 100644 openaev-front/src/admin/components/chaining/logic/events/event-types.ts diff --git a/openaev-front/src/admin/components/chaining/logic/Logic.tsx b/openaev-front/src/admin/components/chaining/logic/Logic.tsx index 1da1ddf087e..fe41bb347da 100644 --- a/openaev-front/src/admin/components/chaining/logic/Logic.tsx +++ b/openaev-front/src/admin/components/chaining/logic/Logic.tsx @@ -10,7 +10,7 @@ import type { import AddComponentButton, { type LogicContext } from './AddComponentButton'; import ChainingFlowConfiguration, { type DrawerView } from './chaining_flow/ChainingFlowConfiguration'; import LogicFlow from './chaining_flow/LogicFlow'; -import type { ActionMeta } from './types'; +import type { ActionMeta, EventMeta } from './types'; interface LogicProps { workflowId: string | undefined; @@ -32,6 +32,12 @@ const Logic = ({ workflowId, context }: LogicProps) => { meta: ActionMeta; } | null>(null); + // Event currently being edited + const [editingEvent, setEditingEvent] = useState<{ + eventId: string; + meta: EventMeta; + } | null>(null); + useEffect(() => { if (workflowId) { fetchValidAssets(workflowId).then((assets: ScopeAssetOutput[]) => { @@ -70,6 +76,14 @@ const Logic = ({ workflowId, context }: LogicProps) => { setDrawerView('actionDetail'); }, []); + const handleEditEvent = useCallback((eventId: string, meta: EventMeta) => { + setEditingEvent({ + eventId, + meta, + }); + setDrawerView('event'); + }, []); + // Loading state if (hasExistingData === null) { return null; @@ -89,6 +103,7 @@ const Logic = ({ workflowId, context }: LogicProps) => { workflowId={workflowId} onAddComponent={handleOpenDrawer} onEditStep={handleEditStep} + onEditEvent={handleEditEvent} /> ) : ( @@ -101,6 +116,8 @@ const Logic = ({ workflowId, context }: LogicProps) => { onDrawerViewChange={setDrawerView} editingStep={editingStep} onEditingStepChange={setEditingStep} + editingEvent={editingEvent} + onEditingEventChange={setEditingEvent} onStepCreated={handleStepCreated} /> diff --git a/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx b/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx index 18a66223ff5..e130da3a8e7 100644 --- a/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx +++ b/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx @@ -1,6 +1,11 @@ import { useCallback, useMemo, useState } from 'react'; -import { createStep, updateStep } from '../../../../../actions/chaining/chaining-actions'; +import { + createCondition, + createStep, + updateCondition, + updateStep, +} from '../../../../../actions/chaining/chaining-actions'; import { useFormatter } from '../../../../../components/i18n'; import type { ConditionCreateInput, @@ -12,9 +17,14 @@ import { MESSAGING$ } from '../../../../../utils/Environment'; import AddActionList from '../drawer/AddActionList'; import AddComponentDrawer from '../drawer/AddComponentDrawer'; import ConfigureActionDetail from '../drawer/ConfigureActionDetail'; -import type { ActionDetailData, ActionMeta } from '../types'; +import ConfigureEventDetail from '../events/ConfigureEventDetail'; +import { + conditionGroupsToApi, + type EventFormData, +} from '../events/event-types'; +import type { ActionDetailData, ActionMeta, EventMeta } from '../types'; -export type DrawerView = 'closed' | 'choose' | 'action' | 'actionDetail'; +export type DrawerView = 'closed' | 'choose' | 'action' | 'actionDetail' | 'event'; interface ChainingFlowConfigurationProps { workflowId: string | undefined; @@ -29,6 +39,14 @@ interface ChainingFlowConfigurationProps { stepId: string; meta: ActionMeta; } | null) => void; + editingEvent: { + eventId: string; + meta: EventMeta; + } | null; + onEditingEventChange: (event: { + eventId: string; + meta: EventMeta; + } | null) => void; onStepCreated: () => void; } @@ -39,6 +57,8 @@ const ChainingFlowConfiguration = ({ onDrawerViewChange, editingStep, onEditingStepChange, + editingEvent, + onEditingEventChange, onStepCreated, }: ChainingFlowConfigurationProps) => { const { t } = useFormatter(); @@ -100,14 +120,14 @@ const ChainingFlowConfiguration = ({ onDrawerViewChange('closed'); setSelectedAction(null); onEditingStepChange(null); - }, [onDrawerViewChange, onEditingStepChange]); + onEditingEventChange(null); + }, [onDrawerViewChange, onEditingStepChange, onEditingEventChange]); const handleSelectComponent = (type: 'action' | 'event') => { if (type === 'action') { onDrawerViewChange('action'); } else { - // TODO: handle event creation drawers - onDrawerViewChange('closed'); + onDrawerViewChange('event'); } }; @@ -214,7 +234,32 @@ const ChainingFlowConfiguration = ({ }); }; - // -- Events (future drawers will go here) -- + // -- Events -- + const handleSaveEvent = async (data: EventFormData) => { + if (!workflowId) return; + + const apiConditions = conditionGroupsToApi(data.conditionGroups, data.groupOperators); + const event = { + event_name: data.name, + event_description: data.description || undefined, + event_workflow_id: workflowId, + event_conditions: apiConditions, + }; + + try { + if (editingEvent) { + await updateCondition(editingEvent.eventId, event); + MESSAGING$.notifySuccess(t('Event updated successfully.')); + } else { + await createCondition(event); + MESSAGING$.notifySuccess(t('Event added successfully.')); + } + handleCloseAll(); + onStepCreated(); + } catch { + MESSAGING$.notifyError(t('Failed to create event.')); + } + }; // Resolve the action to show in ConfigureActionDetail const activeAction = editingAction ?? selectedAction; @@ -243,6 +288,14 @@ const ChainingFlowConfiguration = ({ onBackToRoot={handleCloseAll} onSave={handleSaveActionDetail} /> + ); }; diff --git a/openaev-front/src/admin/components/chaining/logic/chaining_flow/LogicFlow.tsx b/openaev-front/src/admin/components/chaining/logic/chaining_flow/LogicFlow.tsx index 77a767fdf02..443bec74b09 100644 --- a/openaev-front/src/admin/components/chaining/logic/chaining_flow/LogicFlow.tsx +++ b/openaev-front/src/admin/components/chaining/logic/chaining_flow/LogicFlow.tsx @@ -45,6 +45,7 @@ interface LogicFlowProps { reloadTrigger?: number; onAddComponent: () => void; onEditStep?: (stepId: string, meta: ActionMeta) => void; + onEditEvent?: (eventId: string, meta: EventMeta) => void; } const proOptions = { @@ -57,7 +58,7 @@ const proOptions = { * grouped into MITRE tactic columns. Supports connecting events to actions, editing, * deleting nodes, and adding new components. */ -const LogicFlow = ({ workflowId, reloadTrigger, onAddComponent, onEditStep }: LogicFlowProps) => { +const LogicFlow = ({ workflowId, reloadTrigger, onAddComponent, onEditStep, onEditEvent }: LogicFlowProps) => { const { t } = useFormatter(); const theme = useTheme(); @@ -82,7 +83,7 @@ const LogicFlow = ({ workflowId, reloadTrigger, onAddComponent, onEditStep }: Lo // Extra metadata per node (keyed by node id) const [actionMetas, setActionMetas] = useState>({}); - const [_eventMetas, setEventMetas] = useState>({}); + const [eventMetas, setEventMetas] = useState>({}); // Graph loading state — true until first data load completes const [loading, setLoading] = useState(true); @@ -303,12 +304,19 @@ const LogicFlow = ({ workflowId, reloadTrigger, onAddComponent, onEditStep }: Lo * @param nodeId the step ID to edit * @param _type the node type (unused) */ - const editNode = useCallback((nodeId: string, _type: string) => { - const meta = actionMetas[nodeId]; - if (meta && onEditStep) { - onEditStep(nodeId, meta); + const editNode = useCallback((nodeId: string, type: string) => { + if (type === 'action') { + const meta = actionMetas[nodeId]; + if (meta && onEditStep) { + onEditStep(nodeId, meta); + } + } else if (type === 'event') { + const meta = eventMetas[nodeId]; + if (meta && onEditEvent) { + onEditEvent(nodeId, meta); + } } - }, [actionMetas, onEditStep]); + }, [actionMetas, eventMetas, onEditStep, onEditEvent]); /** * Enrich all nodes with edit/delete callbacks so custom node components can trigger actions. diff --git a/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx b/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx new file mode 100644 index 00000000000..270668928e0 --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx @@ -0,0 +1,182 @@ +import { Draggable, Droppable } from '@hello-pangea/dnd'; +import { AddOutlined, DeleteOutline } from '@mui/icons-material'; +import { Button } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { type FunctionComponent } from 'react'; + +import { useFormatter } from '../../../../../components/i18n'; +import { + type ConditionGroup, + createEmptyCondition, + type EventCondition, + type LogicalOperator, +} from './event-types'; +import EventConditionRow from './EventConditionRow'; +import LogicalOperatorSelect from './LogicalOperatorSelect'; + +interface Props { + group: ConditionGroup; + onUpdate: (updated: ConditionGroup) => void; + onDelete?: () => void; +} + +const ConditionGroupBuilder: FunctionComponent = ({ + group, + onUpdate, + onDelete, +}) => { + const { t } = useFormatter(); + const theme = useTheme(); + + const handleOperatorChange = (operator: LogicalOperator) => { + onUpdate({ + ...group, + operator, + }); + }; + + const handleAddCondition = () => { + onUpdate({ + ...group, + conditions: [...group.conditions, createEmptyCondition()], + }); + }; + + const handleUpdateCondition = (index: number, updated: EventCondition) => { + const newConditions = [...group.conditions]; + newConditions[index] = updated; + onUpdate({ + ...group, + conditions: newConditions, + }); + }; + + const handleDeleteCondition = (index: number) => { + const newConditions = group.conditions.filter((_, i) => i !== index); + onUpdate({ + ...group, + conditions: newConditions, + }); + }; + + const handleUpdateSubGroup = (index: number, updated: ConditionGroup) => { + const newSubGroups = [...group.subGroups]; + newSubGroups[index] = updated; + onUpdate({ + ...group, + subGroups: newSubGroups, + }); + }; + + const handleDeleteSubGroup = (index: number) => { + const newSubGroups = group.subGroups.filter((_, i) => i !== index); + onUpdate({ + ...group, + subGroups: newSubGroups, + }); + }; + + return ( +
+
+
+ + {onDelete && ( + + )} +
+ +
+ + {/* Droppable conditions list */} + + {(provided, snapshot) => ( +
+ {group.conditions.map((condition, index) => ( + + {(providedDrag, snapshotDrag) => ( +
+ handleUpdateCondition(index, updated)} + onDelete={() => handleDeleteCondition(index)} + canDelete={group.conditions.length > 1} + /> +
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+ + {/* Sub-groups */} + {group.subGroups.map((subGroup, index) => ( + handleUpdateSubGroup(index, updated)} + onDelete={() => handleDeleteSubGroup(index)} + /> + ))} + +
+ ); +}; + +export default ConditionGroupBuilder; diff --git a/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx b/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx new file mode 100644 index 00000000000..adfd3dec53e --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx @@ -0,0 +1,51 @@ +import { type FunctionComponent } from 'react'; + +import Drawer from '../../../../../components/common/Drawer'; +import { useFormatter } from '../../../../../components/i18n'; +import DrawerBreadcrumb from '../../../common/DrawerBreadcrumb'; +import type { EventFormData } from './event-types'; +import EventCreationForm from './EventCreationForm'; + +interface Props { + open: boolean; + onClose: () => void; + onBack: () => void; + onSave: (data: EventFormData) => void; + initialData?: EventFormData; + isEditing?: boolean; +} + +const ConfigureEventDetail: FunctionComponent = ({ + open, + onClose, + onBack, + onSave, + initialData, + isEditing = false, +}) => { + const { t } = useFormatter(); + + return ( + +
+ + +
+
+ ); +}; + +export default ConfigureEventDetail; diff --git a/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx b/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx new file mode 100644 index 00000000000..9b96a47f94b --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx @@ -0,0 +1,211 @@ +import { type DraggableProvidedDragHandleProps } from '@hello-pangea/dnd'; +import { DeleteOutline, DragHandleOutlined } from '@mui/icons-material'; +import { + Box, + FormControl, + IconButton, + InputLabel, + MenuItem, + Select, + type SelectChangeEvent, + Switch, + TextField, + Typography, +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { type FunctionComponent } from 'react'; + +import { useFormatter } from '../../../../../components/i18n'; +import { + CASE_SENSITIVE_OPERATORS, + COMPARISON_OPERATORS, + type ComparisonOperator, + CONDITION_KEY_TYPES, + type ConditionKeyType, + type EventCondition, + formatConditionKeyLabel, + OPERATOR_LABELS, + UNARY_OPERATORS, +} from './event-types'; + +interface Props { + condition: EventCondition; + dragHandleProps?: DraggableProvidedDragHandleProps | null; + onUpdate: (updated: EventCondition) => void; + onDelete: () => void; + canDelete: boolean; +} + +const EventConditionRow: FunctionComponent = ({ + condition, + dragHandleProps, + onUpdate, + onDelete, + canDelete, +}) => { + const { t } = useFormatter(); + const theme = useTheme(); + + const handleFieldChange = (e: SelectChangeEvent) => { + onUpdate({ + ...condition, + field: e.target.value, + }); + }; + + const handleOperatorChange = (e: SelectChangeEvent) => { + const newOp = e.target.value; + const updated: EventCondition = { + ...condition, + operator: newOp, + }; + if (UNARY_OPERATORS.includes(newOp)) { + updated.value = ''; + } + onUpdate(updated); + }; + + const handleValueChange = (value: string) => { + onUpdate({ + ...condition, + value, + }); + }; + + const handleCaseSensitiveToggle = () => { + onUpdate({ + ...condition, + caseSensitive: !condition.caseSensitive, + }); + }; + + const showValue = !UNARY_OPERATORS.includes(condition.operator); + const showCaseSensitive = CASE_SENSITIVE_OPERATORS.includes(condition.operator); + + return ( + + {/* Drag handle icon */} + + + + + {/* Field to check */} + + {t('Field to Check')} + + label={t('Field to Check')} + value={condition.field} + onChange={handleFieldChange} + > + {CONDITION_KEY_TYPES.map(key => ( + + {formatConditionKeyLabel(key)} + + ))} + + + + {/* Operator */} + + {t('Operator')} + + label={t('Operator')} + value={condition.operator} + onChange={handleOperatorChange} + > + {COMPARISON_OPERATORS.map(op => ( + + {t(OPERATOR_LABELS[op])} + + ))} + + + + {/* Expected value */} + {showValue && ( + handleValueChange(e.target.value)} + sx={{ flex: 1 }} + /> + )} + {!showValue && } + + {/* Case sensitivity toggle + delete — always pushed to the right */} + + {showCaseSensitive && ( +
+ + + {t('Aa')} + +
+ )} + + {/* Delete button — only visible when more than one condition */} + {canDelete && ( + + + + )} +
+
+ ); +}; + +export default EventConditionRow; diff --git a/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx b/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx new file mode 100644 index 00000000000..e642ba957ad --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx @@ -0,0 +1,252 @@ +import { DragDropContext, type DropResult } from '@hello-pangea/dnd'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { AddOutlined } from '@mui/icons-material'; +import { Box, Button, Typography } from '@mui/material'; +import { type FunctionComponent, useCallback, useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import TextFieldController from '../../../../../components/fields/TextFieldController'; +import { useFormatter } from '../../../../../components/i18n'; +import ConditionGroupBuilder from './ConditionGroupBuilder'; +import { + type ConditionGroup, + createEmptyGroup, + type EventFormData, + isEventFormValid, + type LogicalOperator, +} from './event-types'; +import LogicalOperatorSelect from './LogicalOperatorSelect'; + +// Defined outside the component so the resolver reference is stable across renders +const eventBaseSchema = z.object({ + event_name: z.string().min(1), + event_description: z.string(), +}); +type EventBaseInput = z.infer; + +interface EventCreationFormProps { + onSubmit: (data: EventFormData) => void; + onCancel: () => void; + initialData?: EventFormData; + submitLabel?: string; +} + +const EventCreationForm: FunctionComponent = ({ + onSubmit, + onCancel, + initialData, + submitLabel, +}) => { + const { t } = useFormatter(); + const methods = useForm({ + mode: 'onChange', + resolver: zodResolver(eventBaseSchema), + defaultValues: { + event_name: initialData?.name ?? '', + event_description: initialData?.description ?? '', + }, + }); + + const { handleSubmit, formState: { isValid: isFormValid } } = methods; + + // -- Condition groups as local state -- + const [groupOperators, setGroupOperators] = useState( + initialData?.groupOperators ?? [], + ); + const [conditionGroups, setConditionGroups] = useState( + initialData?.conditionGroups ?? [createEmptyGroup('AND')], + ); + + const handleUpdateGroup = useCallback((index: number, updatedGroup: ConditionGroup) => { + setConditionGroups(prev => prev.map((group, i) => (i === index ? updatedGroup : group))); + }, []); + + const handleDeleteGroup = useCallback((index: number) => { + setConditionGroups(prev => prev.filter((_, i) => i !== index)); + setGroupOperators(prev => prev.filter((_, i) => i !== Math.max(0, index - 1))); + }, []); + + const handleAddConditionGroup = useCallback(() => { + setConditionGroups(prev => [...prev, createEmptyGroup('AND')]); + setGroupOperators(prev => [...prev, 'AND']); + }, []); + + const handleUpdateGroupOperator = useCallback((gapIndex: number, op: LogicalOperator) => { + setGroupOperators(prev => prev.map((o, i) => (i === gapIndex ? op : o))); + }, []); + + const cloneGroup = (conditionGroup: ConditionGroup): ConditionGroup => ({ + ...conditionGroup, + conditions: [...conditionGroup.conditions], + subGroups: conditionGroup.subGroups.map(cloneGroup), + }); + + // Find a group by id at any depth (top-level + sub-groups) + const findGroup = (groups: ConditionGroup[], id: string): ConditionGroup | undefined => { + for (const g of groups) { + if (g.id === id) return g; + const found = findGroup(g.subGroups, id); + if (found) return found; + } + return undefined; + }; + + // Move a condition between groups (or reorder within the same group) + const handleDragEnd = useCallback((result: DropResult) => { + if (!result.destination) return; + const { droppableId: sourceId, index: sourceIdx } = result.source; + const { droppableId: destinationId, index: destinationIdx } = result.destination; + if (sourceId === destinationId && sourceIdx === destinationIdx) return; + + const next = conditionGroups.map(cloneGroup); + const srcGroup = findGroup(next, sourceId); + const dstGroup = findGroup(next, destinationId); + if (!srcGroup || !dstGroup) return; + + const [moved] = srcGroup.conditions.splice(sourceIdx, 1); // remove from source + dstGroup.conditions.splice(destinationIdx, 0, moved); // add in destination + + // Remove any top-level group that became empty after the drag + const nonEmptyIndices: number[] = []; + const cleaned = next.filter((g, i) => { + const keep = g.conditions.length > 0 || g.subGroups.length > 0; + if (keep) nonEmptyIndices.push(i); + return keep; + }); + + // Keep only the operators between groups that both survived + setConditionGroups(cleaned.length > 0 ? cleaned : next); + if (cleaned.length < next.length) { + setGroupOperators(prev => prev.filter((_, i) => nonEmptyIndices.includes(i))); + } + }, [conditionGroups]); + + const conditionsValid = isEventFormValid({ + name: 'placeholder', + description: '', + groupOperators, + conditionGroups, + }); + const canSubmit = isFormValid && conditionsValid; + + const onValid = (base: EventBaseInput) => { + onSubmit({ + name: base.event_name.trim(), + description: (base.event_description ?? '').trim(), + groupOperators, + conditionGroups, + }); + }; + + return ( + + + + + + + + + {t('Trigger Conditions')} + + + {t('Event conditions are based on data produced by Actions. To access more options in the "Field to inspect", consider adding additional.')} + + + + + + + + {conditionGroups.map((group, index) => ( + + {index > 0 && ( + + handleUpdateGroupOperator(index - 1, op)} + /> + + )} + handleUpdateGroup(index, updated)} + onDelete={conditionGroups.length > 1 ? () => handleDeleteGroup(index) : undefined} + /> + + ))} + + + + + + + + + + + ); +}; + +export default EventCreationForm; diff --git a/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx b/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx new file mode 100644 index 00000000000..d94c82d7056 --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx @@ -0,0 +1,75 @@ +import { ArrowDropDown } from '@mui/icons-material'; +import { Button, Menu, MenuItem } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { type FunctionComponent, useState } from 'react'; + +import { useFormatter } from '../../../../../components/i18n'; +import type { LogicalOperator } from './event-types'; + +interface Props { + value: LogicalOperator; + onChange: (value: LogicalOperator) => void; +} + +const LogicalOperatorSelect: FunctionComponent = ({ value, onChange }) => { + const { t } = useFormatter(); + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + + const handleOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); + const handleClose = () => setAnchorEl(null); + const handleSelect = (operator: LogicalOperator) => { + onChange(operator); + handleClose(); + }; + + return ( + <> + + + handleSelect('AND')} + > + {t('And')} + + handleSelect('OR')} + > + {t('Or')} + + + + ); +}; + +export default LogicalOperatorSelect; diff --git a/openaev-front/src/admin/components/chaining/logic/events/event-types.ts b/openaev-front/src/admin/components/chaining/logic/events/event-types.ts new file mode 100644 index 00000000000..0f7f624c926 --- /dev/null +++ b/openaev-front/src/admin/components/chaining/logic/events/event-types.ts @@ -0,0 +1,158 @@ +import type { ConditionCreateInput } from '../../../../../utils/api-types'; +import { CONDITION_KEY_TYPES } from '../forms/MapperConditionRow'; + +export { CONDITION_KEY_TYPES }; +export type ConditionKeyType = typeof CONDITION_KEY_TYPES[number]; + +/** 'admin_username' → 'Admin username' */ +export const formatConditionKeyLabel = (value: string): string => value + .replace(/_/g, ' ') + .replace(/^./, c => c.toUpperCase()); + +// -- Operators available for conditions -- +export const COMPARISON_OPERATORS = [ + 'EQ', 'NEQ', 'IS_NULL', 'IS_NOT_NULL', + 'GT', 'GTE', 'LT', 'LTE', 'IN', 'NIN', +] as const; + +export type ComparisonOperator = typeof COMPARISON_OPERATORS[number]; + +// Operators that don't require a value +export const UNARY_OPERATORS: ComparisonOperator[] = ['IS_NULL', 'IS_NOT_NULL']; + +// Operators where case sensitivity is relevant +export const CASE_SENSITIVE_OPERATORS: ComparisonOperator[] = ['EQ', 'NEQ', 'IN', 'NIN']; + +// -- Operator labels -- +export const OPERATOR_LABELS: Record = { + EQ: 'Equals', + NEQ: 'Not equals', + IS_NULL: 'IsNull', + IS_NOT_NULL: 'IsNotNull', + GT: 'Greater than', + GTE: 'Greater than or equals', + LT: 'Less than', + LTE: 'Less than or equals', + IN: 'Contains', + NIN: 'Not contains', +}; + +// -- Condition (leaf node) -- +export interface EventCondition { + id: string; + field: ConditionKeyType; + operator: ComparisonOperator; + value: string; + caseSensitive: boolean; +} + +// -- Condition group (AND/OR container, can be nested) -- +export type LogicalOperator = 'AND' | 'OR'; + +export interface ConditionGroup { + id: string; + operator: LogicalOperator; + conditions: EventCondition[]; + subGroups: ConditionGroup[]; +} + +// -- Event form data -- +export interface EventFormData { + name: string; + description: string; + groupOperators: LogicalOperator[]; + conditionGroups: ConditionGroup[]; +} + +// -- Validation helpers -- +export const isConditionValid = (condition: EventCondition): boolean => { + if (!condition.field) return false; + if (!condition.operator) return false; + return UNARY_OPERATORS.includes(condition.operator) || !!condition.value.trim(); +}; + +export const isGroupValid = (group: ConditionGroup): boolean => { + const hasValidConditions = group.conditions.length > 0 + && group.conditions.every(isConditionValid); + const hasValidSubGroups = group.subGroups.length === 0 + || group.subGroups.every(isGroupValid); + return (hasValidConditions || group.subGroups.length > 0) && hasValidSubGroups; +}; + +export const isEventFormValid = (data: EventFormData): boolean => { + if (!data.name.trim()) return false; + if (data.conditionGroups.length === 0) return false; + return data.conditionGroups.every(isGroupValid); +}; + +// -- Conversion helpers -- +/** + * Converts the form's condition tree into the flat array expected by the API. + */ +export const conditionGroupsToApi = ( + groups: ConditionGroup[], + groupOperators: LogicalOperator[] = [], +): ConditionCreateInput[] => { + // Local counter: resets each call so IDs stay predictable + let tempIdCounter = 0; + const nextTempId = () => `temp_${++tempIdCounter}`; + + const result: ConditionCreateInput[] = []; + + const processGroup = (group: ConditionGroup, parentTempId?: string) => { + // 1. Emit the logical node for this group + const groupTempId = nextTempId(); + result.push({ + condition_temporary_id: groupTempId, + condition_temporary_id_condition_parent: parentTempId, + condition_type: group.operator, + }); + + // 2. Emit each leaf condition, parented to the group node + group.conditions.forEach(cond => + result.push({ + condition_temporary_id: nextTempId(), + condition_temporary_id_condition_parent: groupTempId, + condition_type: cond.operator as ConditionCreateInput['condition_type'], + condition_key_type: cond.field, + // Unary operators (IS_NULL / IS_NOT_NULL) need no value + condition_value: UNARY_OPERATORS.includes(cond.operator) ? undefined : cond.value, + }), + ); + + // 3. Recurse into nested sub-groups + group.subGroups.forEach(subGroup => processGroup(subGroup, groupTempId)); + }; + + if (groups.length === 1) { + // Single group → it is the root, no wrapper needed + processGroup(groups[0]); + return result; + } + + // Multiple groups → emit a root logical node that wraps them all + const rootTempId = nextTempId(); + result.push({ + condition_temporary_id: rootTempId, + condition_type: groupOperators[0] ?? 'AND', + }); + + groups.forEach(group => processGroup(group, rootTempId)); + + return result; +}; + +export const createEmptyCondition = (): EventCondition => ({ + id: crypto.randomUUID(), + field: 'text', + operator: 'IN', + value: '', + caseSensitive: true, +}); + +export const createEmptyGroup = (operator: LogicalOperator = 'AND'): ConditionGroup => ({ + id: crypto.randomUUID(), + operator, + conditions: [createEmptyCondition()], + subGroups: [], +}); diff --git a/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts b/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts index 6f2464d7288..11bf8fb64fe 100644 --- a/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts +++ b/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts @@ -1,8 +1,17 @@ import { type Edge, MarkerType, type Node } from '@xyflow/react'; import { directFetchInjectorContract } from '../../../../actions/InjectorContracts'; -import type { EventOutput, KillChainPhase, StepOutput } from '../../../../utils/api-types'; +import type { ConditionOutput, EventOutput, KillChainPhase, StepOutput } from '../../../../utils/api-types'; import type { ContractElement } from '../../../../utils/api-types-custom'; +import { + type ComparisonOperator, + type ConditionGroup, + type ConditionKeyType, + createEmptyCondition, + createEmptyGroup, + type EventCondition, + type LogicalOperator, +} from './events/event-types'; import type { ActionMeta, EventMeta } from './types'; // Layout design tokens for tactic groups (px) @@ -88,6 +97,126 @@ export const buildActionMetas = (steps: StepOutput[]): Record { + if (allConditions.length === 0) { + return { + groups: [createEmptyGroup('AND')], + groupOperators: [], + }; + } + + const LOGICAL_TYPES: Set = new Set(['AND', 'OR']); + + // Index by ID for O(1) lookup + const byId: Record = {}; + for (const c of allConditions) { + if (c.condition_id) byId[c.condition_id] = c; + } + + // Group children by parent_id + const childrenOf: Record = {}; + for (const c of allConditions) { + const parentId = c.condition_parent_id ?? '__root__'; + childrenOf[parentId] = childrenOf[parentId] ?? []; + childrenOf[parentId].push(c); + } + + const buildGroup = (groupNode: ConditionOutput): ConditionGroup => { + const groupId = groupNode.condition_id ?? crypto.randomUUID(); + const children = childrenOf[groupId] ?? []; + const conditions: EventCondition[] = []; + const subGroups: ConditionGroup[] = []; + + for (const child of children) { + const isLogical = LOGICAL_TYPES.has(child.condition_type ?? ''); + if (isLogical) { + subGroups.push(buildGroup(child)); + } else { + conditions.push({ + id: child.condition_id ?? crypto.randomUUID(), + field: (child.condition_key_type as ConditionKeyType) ?? 'text', + operator: (child.condition_type as ComparisonOperator) ?? 'IN', + value: child.condition_value ?? '', + caseSensitive: true, + }); + } + } + + return { + id: groupId, + operator: (groupNode.condition_type as LogicalOperator) ?? 'AND', + conditions: conditions.length > 0 ? conditions : [createEmptyCondition()], + subGroups, + }; + }; + + // Find roots: conditions with no parent + const roots = allConditions.filter(c => !c.condition_parent_id); + + if (roots.length === 0) { + return { + groups: [createEmptyGroup('AND')], + groupOperators: [], + }; + } + + // Single root logical node → its children are top-level groups + if (roots.length === 1 && LOGICAL_TYPES.has(roots[0].condition_type ?? '')) { + const rootNode = roots[0]; + const rootId = rootNode.condition_id ?? ''; + const topChildren = childrenOf[rootId] ?? []; + + const topGroups = topChildren.filter(c => LOGICAL_TYPES.has(c.condition_type ?? '')); + const topConditions = topChildren.filter(c => !LOGICAL_TYPES.has(c.condition_type ?? '')); + + if (topGroups.length > 0) { + // Multiple groups under root: rootNode operator goes between them + const groups = topGroups.map(g => buildGroup(g)); + const groupOperators: LogicalOperator[] = groups.slice(1).map( + () => (rootNode.condition_type as LogicalOperator) ?? 'AND', + ); + return { + groups, + groupOperators, + }; + } + + // Root has direct leaf conditions → single group + const group: ConditionGroup = { + id: rootId, + operator: (rootNode.condition_type as LogicalOperator) ?? 'AND', + conditions: topConditions.map(c => ({ + id: c.condition_id ?? crypto.randomUUID(), + field: (c.condition_key_type as ConditionKeyType) ?? 'text', + operator: (c.condition_type as ComparisonOperator) ?? 'IN', + value: c.condition_value ?? '', + caseSensitive: true, + })), + subGroups: [], + }; + return { + groups: [group], + groupOperators: [], + }; + } + + // Multiple roots (each a logical group) + const groups = roots.filter(r => LOGICAL_TYPES.has(r.condition_type ?? '')).map(buildGroup); + return { + groups: groups.length > 0 ? groups : [createEmptyGroup('AND')], + groupOperators: groups.slice(1).map(() => 'AND' as LogicalOperator), + }; +}; + /** * Parse events API response into EventMeta records and preliminary event nodes. */ @@ -99,26 +228,16 @@ export const buildEventData = (events: EventOutput[]): { const eventNodes: Node[] = events.map((e, i) => { const allConditions = e.event_conditions ?? []; - const rootCondition = allConditions.find(c => !c.condition_parent_id); - const leafConditions = allConditions - .filter(c => !!c.condition_parent_id) - .map(c => ({ - condition_type: (c.condition_type as string) ?? 'EQ', - condition_key_type: (c.condition_key_type as string) ?? 'status', - condition_value: c.condition_value ?? '', - })); + const { groups, groupOperators } = reconstructConditionGroups(allConditions); eventMetas[e.event_id] = { - event_name: e.event_name ?? '', - event_description: e.event_description ?? '', - root_logical_type: (rootCondition?.condition_type as string) ?? 'AND', - conditions: leafConditions.length > 0 - ? leafConditions - : [{ - condition_type: 'EQ', - condition_key_type: 'status', - condition_value: 'SUCCESS', - }], + eventId: e.event_id, + formData: { + name: e.event_name ?? '', + description: e.event_description ?? '', + groupOperators, + conditionGroups: groups, + }, }; return { diff --git a/openaev-front/src/admin/components/chaining/logic/types.ts b/openaev-front/src/admin/components/chaining/logic/types.ts index 5cd654bff52..9bc971b63b0 100644 --- a/openaev-front/src/admin/components/chaining/logic/types.ts +++ b/openaev-front/src/admin/components/chaining/logic/types.ts @@ -1,6 +1,7 @@ import type { EndpointOutput } from '../../../../utils/api-types'; import type { ContractElement } from '../../../../utils/api-types-custom'; import type { FieldLink } from './drawer/InjectDataFieldItem'; +import type { EventFormData } from './events/event-types'; import type { MapperConditionRow } from './forms/MapperConditionRow'; export interface LogicAction { @@ -41,12 +42,6 @@ export interface ActionMeta { } export interface EventMeta { - event_name: string; - event_description: string; - root_logical_type: string; - conditions: Array<{ - condition_type: string; - condition_key_type: string; - condition_value: string; - }>; + eventId: string; + formData: EventFormData; } diff --git a/openaev-front/src/utils/lang/de.json b/openaev-front/src/utils/lang/de.json index 4a6724554ee..8368d1bade1 100644 --- a/openaev-front/src/utils/lang/de.json +++ b/openaev-front/src/utils/lang/de.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "Ein Dokument, das in einer Nutzlast verwendet wird, kann nicht gelöscht werden.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "Eine Voraussetzungsprüfung ist fehlgeschlagen, bevor der Hauptbefehl ausgeführt werden konnte. Überprüfen Sie die Abhängigkeiten und stellen Sie sicher, dass sie auf dem Ziel erfüllt sind.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "Eine Simulation wird auf der Grundlage dieses Szenarios gestartet und beginnt sofort. Sind Sie sicher, dass Sie fortfahren möchten?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "Ihr Import enthält Injektionen, die nur mit der Tageszeit (z.B.: 9:30) ausgelöst werden, aber es ist kein Startdatum für dieses Szenario angegeben. Bitte geben Sie ein Startdatum für dieses Szenario an.", "Accent color": "Akzentfarbe", "Accept": "Akzeptieren", @@ -82,13 +83,17 @@ "Add challenges": "Herausforderungen hinzufügen", "Add component": "Komponente hinzufügen", "Add Component": "Komponente hinzufügen", + "Add Condition": "Bedingung hinzufügen", "Add condition": "Bedingung hinzufügen", + "Add Condition Group": "Bedingungsgruppe hinzufügen", "Add document": "Dokument hinzufügen", "Add documents": "Dokumente hinzufügen", "Add documents in this challenge": "Dokumente in dieser Herausforderung hinzufügen", "Add documents in this inject": "Dokumente zu dieser Injektion hinzufügen", "Add documents in this media pressure": "Dokumente in diesem Mediendruck hinzufügen", "Add documents in this media pressure article": "Dokumente in diesem Mediendruckartikel hinzufügen", + "Add Event": "Ereignis hinzufügen", + "Add Events": "Ereignisse hinzufügen", "Add expectation in this inject": "Erwartung in dieser Injektion hinzufügen", "Add expectations": "Erwartungen hinzufügen", "Add filter": "Filter hinzufügen", @@ -161,6 +166,7 @@ "Analyzing results…": "Analyse der Ergebnisse..", "AND": "AND", "and": "und", + "And": "Und", "and in our": "und in unserem", "And many more features...": "Und viele weitere Funktionen...", "And/or import documents (.txt .pdf)": "Und/oder Dokumente importieren (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Definition", "Delegation": "Delegation", "Delete": "Löschen", + "Delete condition": "Bedingung löschen", "Delete test": "Test löschen", "Delete variable": "Variable löschen", "DELETE_ASSESSMENT": "Bewertung löschen", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "Es ist ein unbekannter Fehler aufgetreten.", "Evaluate": "Bewerten Sie", "Event": "Ereignis", + "Event added successfully.": "Ereignis erfolgreich hinzugefügt.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Ereignisbedingungen basieren auf Daten, die von Aktionen erzeugt werden. Um auf weitere Optionen im Feld „Zu prüfendes Feld“ zugreifen zu können, sollten Sie zusätzliche Felder hinzufügen.", + "Event updated successfully.": "Ereignis wurde erfolgreich aktualisiert.", "every_fem_plural": "Alle", "every_fem_singular": "Jede", "every_masc_plural": "Alle", @@ -887,6 +897,7 @@ "Expectations of ": "Erwartungen von", "Expected score": "Erwartetes Ergebnis", "Expected score:": "Erwartetes Ergebnis:", + "Expected Value": "Erwarteter Wert", "Experiment valuable threat management resources in the XTM Hub": "Experimentieren Sie mit wertvollen Bedrohungsmanagement-Ressourcen im XTM Hub", "Expiration date": "Verfallsdatum", "Expiration time": "Verfallszeit", @@ -911,6 +922,7 @@ "Failed": "Fehlgeschlagen", "failed": "failed", "Failed to copy to clipboard": "Kopieren in die Zwischenablage fehlgeschlagen", + "Failed to create event.": "Ereignis konnte nicht erstellt werden.", "Failed to delete vulnerability.": "Schwachstelle konnte nicht gelöscht werden.", "Failed to import CSV file": "Import der CSV-Datei fehlgeschlagen", "Failed to update vulnerability.": "Aktualisierung der Sicherheitslücke fehlgeschlagen.", @@ -944,6 +956,7 @@ "faq.support.title": "Unterstützung", "faq.usage.title": "Verwendung", "Field": "Feld", + "Field to Check": "Zu prüfendes Feld", "File": "Datei", "File Drop": "Datei Drop", "File drop file": "File Drop Datei", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "Operativ", + "Operator": "Operator", "Optional": "Optional", "Options": "Optionen", "OR": "OR", "or": "oder", + "Or": "Oder", "Order": "Bestellung", "Organization": "Organisation", "Organizations": "Organisationen", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Aus der Injektion entfernen", "Remove from the media pressure": "Aus dem Mediendruck entfernen", "Remove from the team": "Aus dem Team entfernen", + "Remove group": "Gruppe entfernen", "RENEW": "RENEW", "Replace": "Ersetzen", "Replay all tests": "Alle Tests wiederholen", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Tracking Gesamterfolg", "Trigger": "Auslöser", "Trigger after": "Auslöser nach", + "Trigger Conditions": "Auslösebedingungen", "Trigger now": "Auslöser jetzt", "Trigger on": "Auslöser am", "Trigger time": "Auslösungszeit", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Einen Endpunkt aktualisieren", "Update connector instance": "Update der Konnektorinstanz", + "Update Event": "Ereignis aktualisieren", "Update event": "Ereignis aktualisieren", "Update inject comment": "Injektionskommentar aktualisieren", "Update objects": "Objekte aktualisieren", diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index 2575f66061c..6d3bc029bb8 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "A document used in a payload can't be deleted.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "Your import contains injects triggered only with the time of the day (e.g.: 9:30) but there is no launch date specified for this scenario. Please specify a launch date for this scenario.", "Accent color": "Accent color", "Accept": "Accept", @@ -82,13 +83,17 @@ "Add challenges": "Add challenges", "Add component": "Add component", "Add Component": "Add Component", + "Add Condition": "Add Condition", "Add condition": "Add condition", + "Add Condition Group": "Add Condition Group", "Add document": "Add document", "Add documents": "Add documents", "Add documents in this challenge": "Add documents in this challenge", "Add documents in this inject": "Add documents in this inject", "Add documents in this media pressure": "Add documents in this media pressure", "Add documents in this media pressure article": "Add documents in this media pressure article", + "Add Event": "Add Event", + "Add Events": "Add Events", "Add expectation in this inject": "Add expectation in this inject", "Add expectations": "Add expectations", "Add filter": "Add filter", @@ -161,6 +166,7 @@ "Analyzing results…": "Analyzing results…", "AND": "AND", "and": "and", + "And": "And", "and in our": "and in our", "And many more features...": "And many more features...", "And/or import documents (.txt .pdf)": "And/or import documents (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Definition", "Delegation": "Delegation", "Delete": "Delete", + "Delete condition": "Delete condition", "Delete test": "Delete test", "Delete variable": "Delete variable", "DELETE_ASSESSMENT": "Delete assessment", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "An unknown error has occurred.", "Evaluate": "Evaluate", "Event": "Event", + "Event added successfully.": "Event added successfully.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.", + "Event updated successfully.": "Event updated successfully.", "every_fem_plural": "Every", "every_fem_singular": "Every", "every_masc_plural": "Every", @@ -887,6 +897,7 @@ "Expectations of ": "Expectations of ", "Expected score": "Expected score", "Expected score:": "Expected score:", + "Expected Value": "Expected Value", "Experiment valuable threat management resources in the XTM Hub": "Experiment valuable threat management resources in the XTM Hub", "Expiration date": "Expiration date", "Expiration time": "Expiration time", @@ -911,6 +922,7 @@ "Failed": "Failed", "failed": "failed", "Failed to copy to clipboard": "Failed to copy to clipboard", + "Failed to create event.": "Failed to create event.", "Failed to delete vulnerability.": "Failed to delete vulnerability.", "Failed to import CSV file": "Failed to import CSV file", "Failed to update vulnerability.": "Failed to update vulnerability.", @@ -944,6 +956,7 @@ "faq.support.title": "Support", "faq.usage.title": "Usage", "Field": "Field", + "Field to Check": "Field to Check", "File": "File", "File Drop": "File Drop", "File drop file": "File drop file", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "Operational", + "Operator": "Operator", "Optional": "Optional", "Options": "Options", "OR": "OR", "or": "or", + "Or": "Or", "Order": "Order", "Organization": "Organization", "Organizations": "Organizations", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Remove from the inject", "Remove from the media pressure": "Remove from the media pressure", "Remove from the team": "Remove from the team", + "Remove group": "Remove group", "RENEW": "RENEW", "Replace": "Replace", "Replay all tests": "Replay all tests", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Tracking Total Success", "Trigger": "Trigger", "Trigger after": "Trigger after", + "Trigger Conditions": "Trigger Conditions", "Trigger now": "Trigger now", "Trigger on": "Trigger on", "Trigger time": "Trigger time", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Update an endpoint", "Update connector instance": "Update connector instance", + "Update Event": "Update Event", "Update event": "Update event", "Update inject comment": "Update inject comment", "Update objects": "Update objects", diff --git a/openaev-front/src/utils/lang/es.json b/openaev-front/src/utils/lang/es.json index 18fee1e4853..d53221de2a7 100644 --- a/openaev-front/src/utils/lang/es.json +++ b/openaev-front/src/utils/lang/es.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "Un documento utilizado en una carga útil no puede eliminarse.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "Una verificación de requisito previo falló antes de ejecutar el comando principal. Revise las dependencias y asegúrese de que se cumplan en el objetivo.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "Se lanzará una simulación basada en este escenario y comenzará inmediatamente. ¿Está seguro de que desea continuar?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "Su importación contiene inyecciones activadas sólo con la hora del día (por ejemplo: 9:30) pero no se ha especificado una fecha de lanzamiento para este escenario. Por favor, especifique una fecha de lanzamiento para este escenario.", "Accent color": "Color de acento", "Accept": "Aceptar", @@ -82,13 +83,17 @@ "Add challenges": "Añadir retos", "Add component": "Añadir componente", "Add Component": "Añadir Componente", + "Add Condition": "Añadir condición", "Add condition": "Añadir condición", + "Add Condition Group": "Añadir grupo de condiciones", "Add document": "Añadir documento", "Add documents": "Añadir documentos", "Add documents in this challenge": "Añadir documentos en este reto", "Add documents in this inject": "Añadir documentos en este reto", "Add documents in this media pressure": "Añadir documentos en este medio de prensa", "Add documents in this media pressure article": "Añadir documentos en este artículo", + "Add Event": "Añadir evento", + "Add Events": "Añadir eventos", "Add expectation in this inject": "Añadir expectativa en esta inyección", "Add expectations": "Añadir expectativas", "Add filter": "Añadir filtro", @@ -161,6 +166,7 @@ "Analyzing results…": "Analizando los resultados..", "AND": "Y", "and": "y", + "And": "Y", "and in our": "y en nuestro", "And many more features...": "Y muchas más funciones...", "And/or import documents (.txt .pdf)": "Y / o importar documentos (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Definición", "Delegation": "Delegación", "Delete": "Borrar", + "Delete condition": "Eliminar condición", "Delete test": "Borrar prueba", "Delete variable": "Borrar variable", "DELETE_ASSESSMENT": "Borrar evaluación", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "Se ha producido un error desconocido.", "Evaluate": "Evalúe", "Event": "Evento", + "Event added successfully.": "Evento añadido correctamente.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Las condiciones de los eventos se basan en los datos generados por las acciones. Para acceder a más opciones en el «Campo a inspeccionar», considera añadir más.", + "Event updated successfully.": "Evento actualizado correctamente.", "every_fem_plural": "Cada", "every_fem_singular": "Cada", "every_masc_plural": "Cada", @@ -887,6 +897,7 @@ "Expectations of ": "Expectativas de", "Expected score": "Puntuación esperada", "Expected score:": "Puntuación esperada", + "Expected Value": "Valor esperado", "Experiment valuable threat management resources in the XTM Hub": "Experimentar valiosos recursos de gestión de amenazas en el XTM Hub", "Expiration date": "Fecha de caducidad", "Expiration time": "Hora de caducidad", @@ -911,6 +922,7 @@ "Failed": "Fallado", "failed": "fallado", "Failed to copy to clipboard": "Error al copiar al portapapeles", + "Failed to create event.": "No se ha podido crear el evento.", "Failed to delete vulnerability.": "Error al eliminar vulnerabilidad.", "Failed to import CSV file": "Error al importar archivo CSV", "Failed to update vulnerability.": "Error al actualizar vulnerabilidad.", @@ -944,6 +956,7 @@ "faq.support.title": "Soporte", "faq.usage.title": "Uso", "Field": "Campo", + "Field to Check": "Campo a comprobar", "File": "Archivo", "File Drop": "Archivo Drop", "File drop file": "File drop file", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "Operativa", + "Operator": "Operador", "Optional": "Opcional", "Options": "Opciones", "OR": "O", "or": "o", + "Or": "O", "Order": "Pedir", "Organization": "Organización", "Organizations": "Organizaciones", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Eliminar de la inyección", "Remove from the media pressure": "Retirar de la presión del medio", "Remove from the team": "Quitar del equipo", + "Remove group": "Eliminar grupo", "RENEW": "RENOVAR", "Replace": "Sustituir", "Replay all tests": "Reproducir todas las pruebas", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Seguimiento del éxito total", "Trigger": "Disparador", "Trigger after": "Activar después de", + "Trigger Conditions": "Condiciones de activación", "Trigger now": "Disparar ahora", "Trigger on": "Activar en", "Trigger time": "Tiempo de disparo", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Actualizar un endpoint", "Update connector instance": "Actualizar instancia de conector", + "Update Event": "Actualizar evento", "Update event": "Evento de actualización", "Update inject comment": "Actualizar un comentario de inyección", "Update objects": "Actualizar objetos", diff --git a/openaev-front/src/utils/lang/fr.json b/openaev-front/src/utils/lang/fr.json index fbcd6eee6ca..e0a968b7ed3 100644 --- a/openaev-front/src/utils/lang/fr.json +++ b/openaev-front/src/utils/lang/fr.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "Un document utilisé dans une charge utile ne peut pas être supprimé.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "Un prérequis a échoué avant l'exécution de la commande principale. Vérifiez les dépendances et assurez-vous qu'elles sont satisfaites sur la cible.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "Une simulation sera lancée à partir de ce scénario et commencera immédiatement. Êtes-vous sûr de vouloir continuer ?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "L'import contient des injects qui se déclenche à une certaine heure de la journée (ex: 9h30) mais le scenario n'a pas de date de départ. Veuillez spécifier une date de départ.", "Accent color": "Couleur d'accentuation", "Accept": "Accepter", @@ -82,13 +83,17 @@ "Add challenges": "Ajouter des challenges", "Add component": "Ajouter un composant", "Add Component": "Ajouter un composant", + "Add Condition": "Ajouter une condition", "Add condition": "Ajouter une condition", + "Add Condition Group": "Ajouter un groupe de conditions", "Add document": "Ajouter un document", "Add documents": "Ajouter des documents", "Add documents in this challenge": "Ajouter un document à ce challenge", "Add documents in this inject": "Ajouter des documents dans ce stimuli", "Add documents in this media pressure": "Ajouter des documents à cette pression médiatique", "Add documents in this media pressure article": "Ajouter des documents dans cet article de pression médiatique", + "Add Event": "Ajouter un événement", + "Add Events": "Ajouter des événements", "Add expectation in this inject": "Ajouter des attendus dans ce stimuli", "Add expectations": "Ajouter des attendus", "Add filter": "Ajout d'un filtre", @@ -161,6 +166,7 @@ "Analyzing results…": "Analyse des résultats..", "AND": "ET", "and": "et", + "And": "Et", "and in our": "et dans notre", "And many more features...": "Et bien d'autres fonctionnalités...", "And/or import documents (.txt .pdf)": "Et/ou importer des documents (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Définition", "Delegation": "Délégation", "Delete": "Supprimer", + "Delete condition": "Supprimer une condition", "Delete test": "Supprimer le test", "Delete variable": "Supprimer une variable", "DELETE_ASSESSMENT": "Supprimer les évaluations", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "Une erreur inconnue s'est produite.", "Evaluate": "Evaluer", "Event": "Événement", + "Event added successfully.": "Événement ajouté avec succès.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Les conditions d’événement reposent sur les données générées par les actions. Pour accéder à davantage d’options dans le champ « Champ à vérifier », pensez à en ajouter d’autres.", + "Event updated successfully.": "Événement mis à jour avec succès.", "every_fem_plural": "Toutes les", "every_fem_singular": "Chaque", "every_masc_plural": "Tous les", @@ -887,6 +897,7 @@ "Expectations of ": "Attendus de ", "Expected score": "Score attendu", "Expected score:": "Score attendu :", + "Expected Value": "Valeur attendue", "Experiment valuable threat management resources in the XTM Hub": "Expérimenter les ressources de gestion des menaces dans le XTM Hub", "Expiration date": "Date d'expiration", "Expiration time": "Temps d'expiration", @@ -911,6 +922,7 @@ "Failed": "Echoué", "failed": "Échoué", "Failed to copy to clipboard": "Échec de la copie dans le presse-papiers", + "Failed to create event.": "Échec de la création de l’événement.", "Failed to delete vulnerability.": "Échec de la suppression de la vulnérabilité.", "Failed to import CSV file": "Échec de l'importation du fichier CSV", "Failed to update vulnerability.": "Échec de la mise à jour de la vulnérabilité.", @@ -944,6 +956,7 @@ "faq.support.title": "Support", "faq.usage.title": "Utilisation", "Field": "Champ", + "Field to Check": "Champ à vérifier", "File": "Fichier", "File Drop": "Dépôt de fichier", "File drop file": "Fichier de dépose", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "Opérationnel", + "Operator": "Opérateur", "Optional": "Optionnel", "Options": "Options", "OR": "OU", "or": "ou", + "Or": "Ou", "Order": "Ordre", "Organization": "Organisation", "Organizations": "Organisations", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Retirer du stimuli", "Remove from the media pressure": "Supprimer de la pression médiatique", "Remove from the team": "Retirer de l'équipe", + "Remove group": "Supprimer le groupe", "RENEW": "RENOUVELER", "Replace": "Remplacer", "Replay all tests": "Rejouer tous les tests", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Nombre total de succès", "Trigger": "Déclencheur", "Trigger after": "Se déclenche après", + "Trigger Conditions": "Conditions de déclenchement", "Trigger now": "Déclencher maintenant", "Trigger on": "Déclenchement sur", "Trigger time": "Heure de lancement", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Mise à jour d'un endpoint", "Update connector instance": "Mise à jour de l'instance du connecteur", + "Update Event": "Mettre à jour l’événement", "Update event": "Événement de mise à jour", "Update inject comment": "Modifier le commentaire du stimuli", "Update objects": "Mettre à jour les objets", diff --git a/openaev-front/src/utils/lang/it.json b/openaev-front/src/utils/lang/it.json index aa5a8c21a74..f711eaa56f2 100644 --- a/openaev-front/src/utils/lang/it.json +++ b/openaev-front/src/utils/lang/it.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "Un documento usato in un payload non può essere cancellato.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "Verrà avviata una simulazione basata su questo scenario e inizierà immediatamente. Siete sicuri di voler procedere?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "L'importazione contiene iniezioni attivate solo con l'ora del giorno (ad es.: 9:30), ma non è stata specificata alcuna data di lancio per questo scenario. Specificare una data di avvio per questo scenario.", "Accent color": "Colore dell'accento", "Accept": "Accettare", @@ -82,13 +83,17 @@ "Add challenges": "Aggiungi sfide", "Add component": "Aggiungi componente", "Add Component": "Aggiungi componente", + "Add Condition": "Aggiungi condizione", "Add condition": "Aggiungi condizione", + "Add Condition Group": "Aggiungi gruppo di condizioni", "Add document": "Aggiungi documento", "Add documents": "Aggiungi documenti", "Add documents in this challenge": "Aggiungi documenti in questa sfida", "Add documents in this inject": "Aggiungi documenti in questo progetto", "Add documents in this media pressure": "Aggiungi documenti in questo media pressure", "Add documents in this media pressure article": "Aggiungi documenti in questo articolo di pressione mediatica", + "Add Event": "Aggiungi evento", + "Add Events": "Aggiungi eventi", "Add expectation in this inject": "Aggiungi l'aspettativa in questo articolo", "Add expectations": "Aggiungi aspettative", "Add filter": "Aggiungi filtro", @@ -161,6 +166,7 @@ "Analyzing results…": "Analisi dei risultati..", "AND": "E", "and": "e", + "And": "E", "and in our": "e nel nostro", "And many more features...": "E molte altre caratteristiche...", "And/or import documents (.txt .pdf)": "E/O importare documenti (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Definizione", "Delegation": "Delega", "Delete": "Eliminare", + "Delete condition": "Elimina condizione", "Delete test": "Test di cancellazione", "Delete variable": "Cancellare una variabile", "DELETE_ASSESSMENT": "Eliminare la valutazione", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "Si è verificato un errore sconosciuto.", "Evaluate": "Valutare", "Event": "Evento", + "Event added successfully.": "Evento aggiunto con successo.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Le condizioni degli eventi si basano sui dati generati dalle azioni. Per accedere a ulteriori opzioni nel campo “Campo da controllare”, valuta la possibilità di aggiungerne altri.", + "Event updated successfully.": "Evento aggiornato con successo.", "every_fem_plural": "Ogni", "every_fem_singular": "Ogni", "every_masc_plural": "Ogni", @@ -887,6 +897,7 @@ "Expectations of ": "Aspettative di", "Expected score": "Punteggio previsto", "Expected score:": "Punteggio previsto:", + "Expected Value": "Valore previsto", "Experiment valuable threat management resources in the XTM Hub": "Sperimentare risorse preziose per la gestione delle minacce nell'XTM Hub", "Expiration date": "Data di scadenza", "Expiration time": "Ora di scadenza", @@ -911,6 +922,7 @@ "Failed": "Fallito", "failed": "fallito", "Failed to copy to clipboard": "Impossibile copiare negli appunti", + "Failed to create event.": "Impossibile creare l’evento.", "Failed to delete vulnerability.": "Cancellazione fallita della vulnerabilità.", "Failed to import CSV file": "Impossibile importare il file CSV", "Failed to update vulnerability.": "Fallito l'aggiornamento della vulnerabilità.", @@ -944,6 +956,7 @@ "faq.support.title": "Supporto", "faq.usage.title": "Utilizzo", "Field": "Campo", + "Field to Check": "Campo da controllare", "File": "File", "File Drop": "File Drop", "File drop file": "File drop file", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "Ricerca aperta", "Operational": "Operativo", + "Operator": "Operatore", "Optional": "Opzionale", "Options": "Opzioni", "OR": "O", "or": "o", + "Or": "Oppure", "Order": "Ordine", "Organization": "Organizzazione", "Organizations": "Organizzazioni", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Rimuovere dall'oggetto", "Remove from the media pressure": "Rimuovere dalla pressione media", "Remove from the team": "Rimuovere dalla squadra", + "Remove group": "Rimuovi gruppo", "RENEW": "RINNOVARE", "Replace": "Sostituire", "Replay all tests": "Riproduzione di tutti i test", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Monitoraggio del successo totale", "Trigger": "Innesco", "Trigger after": "Innesco dopo", + "Trigger Conditions": "Condizioni di attivazione", "Trigger now": "Innesco ora", "Trigger on": "Attivazione su", "Trigger time": "Tempo di attivazione", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Aggiornare un endpoint", "Update connector instance": "Aggiornare l'istanza del connettore", + "Update Event": "Aggiorna evento", "Update event": "Evento di aggiornamento", "Update inject comment": "Aggiornare il commento di iniezione", "Update objects": "Aggiornare gli oggetti", diff --git a/openaev-front/src/utils/lang/ja.json b/openaev-front/src/utils/lang/ja.json index b788268debe..2bd1836f24d 100644 --- a/openaev-front/src/utils/lang/ja.json +++ b/openaev-front/src/utils/lang/ja.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "ペイロードで使用されたドキュメントは削除できません。", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "メインコマンドの実行前に前提条件チェックが失敗しました。前提条件の依存関係を確認し、ターゲット上で満たされていることを確認してください。", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "このシナリオに基づいてシミュレーションが開始され、直ちに開始されます。本当に続行しますか?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "インポートファイルに時刻のみ(例:9:30)のインジェクトが含まれていますが、このシナリオには開始日が指定されていません。このシナリオの開始日を指定してください。", "Accent color": "アクセントカラー", "Accept": "承認する", @@ -82,13 +83,17 @@ "Add challenges": "課題を追加する", "Add component": "コンポーネントの追加", "Add Component": "コンポーネントの追加", + "Add Condition": "条件を追加", "Add condition": "条件追加", + "Add Condition Group": "条件グループを追加", "Add document": "文書の追加", "Add documents": "文書の追加", "Add documents in this challenge": "この課題に文書を追加する", "Add documents in this inject": "このインジェクトにドキュメントを追加する", "Add documents in this media pressure": "このメディアプレッシャーにドキュメントを追加する", "Add documents in this media pressure article": "このメディアプレッシャー記事にドキュメントを追加する", + "Add Event": "イベントを追加", + "Add Events": "イベントを追加", "Add expectation in this inject": "このインジェクトに期待値を追加する", "Add expectations": "期待値を追加", "Add filter": "フィルタを追加する", @@ -161,6 +166,7 @@ "Analyzing results…": "結果を分析する...", "AND": "アンド", "and": "そして", + "And": "AND", "and in our": "そして", "And many more features...": "その他にも...", "And/or import documents (.txt .pdf)": "文書のインポート (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "定義", "Delegation": "委任", "Delete": "削除", + "Delete condition": "条件を削除", "Delete test": "テストを削除", "Delete variable": "変数の削除", "DELETE_ASSESSMENT": "評価の削除", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "不明なエラーが発生しました。", "Evaluate": "評価する", "Event": "イベント", + "Event added successfully.": "イベントが正常に追加されました。", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "イベントの条件は、アクションによって生成されたデータに基づいています。「検査対象フィールド」でさらに多くのオプションを利用するには、追加を検討してください。", + "Event updated successfully.": "イベントの更新に成功しました。", "every_fem_plural": "毎", "every_fem_singular": "毎", "every_masc_plural": "毎", @@ -887,6 +897,7 @@ "Expectations of ": "への期待", "Expected score": "期待スコア", "Expected score:": "期待されるスコア", + "Expected Value": "期待値", "Experiment valuable threat management resources in the XTM Hub": "XTM ハブの貴重な脅威管理リソースの実験", "Expiration date": "有効期限", "Expiration time": "有効期限", @@ -911,6 +922,7 @@ "Failed": "失敗", "failed": "失敗した", "Failed to copy to clipboard": "クリップボードへのコピーに失敗しました", + "Failed to create event.": "イベントの作成に失敗しました。", "Failed to delete vulnerability.": "脆弱性の削除に失敗しました。", "Failed to import CSV file": "CSVファイルのインポートに失敗", "Failed to update vulnerability.": "脆弱性のアップデートに失敗した", @@ -944,6 +956,7 @@ "faq.support.title": "サポート", "faq.usage.title": "使用方法", "Field": "フィールド", + "Field to Check": "確認対象フィールド", "File": "ファイル", "File Drop": "ファイルドロップ", "File drop file": "ファイルドロップ", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "オープンサーチ", "Operational": "オペレーショナル", + "Operator": "演算子", "Optional": "オプション", "Options": "オプション", "OR": "または", "or": "または", + "Or": "または", "Order": "オーダー", "Organization": "組織名", "Organizations": "組織", @@ -1844,6 +1859,7 @@ "Remove from the inject": "インジェクトから削除する", "Remove from the media pressure": "メディアプレッシャーから取り除く", "Remove from the team": "チームから外す", + "Remove group": "グループを削除", "RENEW": "更新", "Replace": "入れ替える", "Replay all tests": "すべてのテストを再生する", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "成功の追跡", "Trigger": "トリガー", "Trigger after": "トリガー後", + "Trigger Conditions": "トリガー条件", "Trigger now": "今のトリガー", "Trigger on": "トリガー", "Trigger time": "トリガー時間", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "エンドポイントの更新", "Update connector instance": "コネクタインスタンスを更新する", + "Update Event": "イベントを更新", "Update event": "更新イベント", "Update inject comment": "インジェクトコメントの更新", "Update objects": "オブジェクトの更新", diff --git a/openaev-front/src/utils/lang/ko.json b/openaev-front/src/utils/lang/ko.json index 93ec2343a95..7e8fd5fe5aa 100644 --- a/openaev-front/src/utils/lang/ko.json +++ b/openaev-front/src/utils/lang/ko.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "페이로드에 사용된 문서는 삭제할 수 없습니다.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "이 시나리오에 따라 시뮬레이션이 시작되며 즉시 시작됩니다. 계속 진행하시겠습니까?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "가져오기에는 하루 중 시간(예: 9:30)으로만 트리거되는 인젝트가 포함되어 있지만 이 시나리오에 대해 지정된 시작 날짜가 없습니다. 이 시나리오에 대한 시작 날짜를 지정하세요.", "Accent color": "강조 색상", "Accept": "수락", @@ -82,13 +83,17 @@ "Add challenges": "챌린지 추가", "Add component": "컴포넌트 추가", "Add Component": "컴포넌트 추가", + "Add Condition": "조건 추가", "Add condition": "조건 추가", + "Add Condition Group": "조건 그룹 추가", "Add document": "문서 추가", "Add documents": "문서 추가", "Add documents in this challenge": "이 챌린지에 문서 추가", "Add documents in this inject": "이 주입에 문서 추가", "Add documents in this media pressure": "이 미디어 압력에 문서 추가", "Add documents in this media pressure article": "이 미디어 압력 문서에 문서 추가", + "Add Event": "이벤트 추가", + "Add Events": "이벤트 추가", "Add expectation in this inject": "이 인젝트에 기대치 추가", "Add expectations": "기대치 추가", "Add filter": "필터 추가", @@ -161,6 +166,7 @@ "Analyzing results…": "결과 분석 중..", "AND": "AND", "and": "및", + "And": "And", "and in our": "그리고 우리의", "And many more features...": "그리고 더 많은 기능...", "And/or import documents (.txt .pdf)": "그리고/또는 문서(.txt .pdf) 가져오기", @@ -595,6 +601,7 @@ "Definition": "정의", "Delegation": "위임", "Delete": "삭제", + "Delete condition": "조건 삭제", "Delete test": "테스트 삭제", "Delete variable": "변수 삭제", "DELETE_ASSESSMENT": "평가 삭제", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "알 수 없는 오류가 발생했습니다.", "Evaluate": "평가 중", "Event": "Event", + "Event added successfully.": "이벤트가 성공적으로 추가되었습니다.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "이벤트 조건은 액션에서 생성된 데이터를 기반으로 합니다. “검증할 필드”에서 더 많은 옵션을 이용하려면 추가 필드를 추가해 보세요.", + "Event updated successfully.": "이벤트가 성공적으로 업데이트되었습니다.", "every_fem_plural": "모든", "every_fem_singular": "모든", "every_masc_plural": "모든", @@ -887,6 +897,7 @@ "Expectations of ": "기대치", "Expected score": "예상 점수", "Expected score:": "예상 점수", + "Expected Value": "예상 값", "Experiment valuable threat management resources in the XTM Hub": "XTM Hub에서 귀중한 위협 관리 리소스 실험하기", "Expiration date": "만료 날짜", "Expiration time": "만료 시간", @@ -911,6 +922,7 @@ "Failed": "실패", "failed": "실패", "Failed to copy to clipboard": "클립보드에 복사하지 못했습니다", + "Failed to create event.": "이벤트 생성에 실패했습니다.", "Failed to delete vulnerability.": "취약점을 삭제하지 못했습니다.", "Failed to import CSV file": "CSV 파일을 가져오지 못했습니다", "Failed to update vulnerability.": "취약점을 업데이트하지 못했습니다.", @@ -944,6 +956,7 @@ "faq.support.title": "지원", "faq.usage.title": "사용법", "Field": "필드", + "Field to Check": "확인할 필드", "File": "파일", "File Drop": "파일 드롭", "File drop file": "파일 드롭 파일", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "운영 중", + "Operator": "연산자", "Optional": "선택 사항", "Options": "옵션", "OR": "OR", "or": "또는", + "Or": "또는", "Order": "주문", "Organization": "조직", "Organizations": "조직", @@ -1844,6 +1859,7 @@ "Remove from the inject": "인젝트에서 제거", "Remove from the media pressure": "미디어 압력에서 제거", "Remove from the team": "팀에서 제거", + "Remove group": "그룹 제거", "RENEW": "갱신", "Replace": "교체", "Replay all tests": "모든 테스트 재생", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "총 성공 추적", "Trigger": "트리거", "Trigger after": "트리거 후", + "Trigger Conditions": "트리거 조건", "Trigger now": "지금 트리거", "Trigger on": "트리거 켜기", "Trigger time": "트리거 시간", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "엔드포인트 업데이트", "Update connector instance": "커넥터 인스턴스 업데이트", + "Update Event": "이벤트 업데이트", "Update event": "이벤트 업데이트", "Update inject comment": "인젝트 댓글 업데이트", "Update objects": "개체 업데이트", diff --git a/openaev-front/src/utils/lang/ru.json b/openaev-front/src/utils/lang/ru.json index 35313c3b949..3ad3aed74b8 100644 --- a/openaev-front/src/utils/lang/ru.json +++ b/openaev-front/src/utils/lang/ru.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "Документ, используемый в полезной нагрузке, не может быть удален.", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "Симуляция будет запущена на основе этого сценария и начнется немедленно. Вы уверены, что хотите продолжить?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "Ваш импорт содержит инжекты, запускаемые только по времени суток (например, 9:30), но для этого сценария не указана дата запуска. Пожалуйста, укажите дату запуска для этого сценария.", "Accent color": "Цвет акцента", "Accept": "Принять", @@ -82,13 +83,17 @@ "Add challenges": "Добавить вызовы", "Add component": "Добавить компонент", "Add Component": "Добавить компонент", + "Add Condition": "Добавить условие", "Add condition": "Добавить условие", + "Add Condition Group": "Добавить группу условий", "Add document": "Добавить документ", "Add documents": "Добавить документы", "Add documents in this challenge": "Добавить документы в этом вызове", "Add documents in this inject": "Добавить документы в этот укол", "Add documents in this media pressure": "Добавьте документы в это медиадавление", "Add documents in this media pressure article": "Добавить документы в эту статью о давлении на СМИ", + "Add Event": "Добавить событие", + "Add Events": "Добавить события", "Add expectation in this inject": "Добавить ожидание в этом уколе", "Add expectations": "Добавить ожидания", "Add filter": "Добавить фильтр", @@ -161,6 +166,7 @@ "Analyzing results…": "Анализ результатов..", "AND": "И", "and": "и", + "And": "И", "and in our": "и в нашем", "And many more features...": "И многие другие возможности...", "And/or import documents (.txt .pdf)": "И/или импорт документов (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "Определение", "Delegation": "Делегирование", "Delete": "Удалить", + "Delete condition": "Удалить условие", "Delete test": "Удалить тест", "Delete variable": "Удалить переменную", "DELETE_ASSESSMENT": "Удалить оценку", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "Произошла неизвестная ошибка.", "Evaluate": "Оцените", "Event": "Событие", + "Event added successfully.": "Событие добавлено успешно.", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "Условия событий основаны на данных, генерируемых действиями. Чтобы получить доступ к дополнительным параметрам в поле «Проверяемое поле», добавьте дополнительные условия.", + "Event updated successfully.": "Событие успешно обновлено.", "every_fem_plural": "Каждые", "every_fem_singular": "Каждая", "every_masc_plural": "Каждые", @@ -887,6 +897,7 @@ "Expectations of ": "Ожидания", "Expected score": "Ожидаемый результат", "Expected score:": "Ожидаемый балл:", + "Expected Value": "Ожидаемое значение", "Experiment valuable threat management resources in the XTM Hub": "Экспериментируйте с ценными ресурсами управления угрозами в XTM Hub", "Expiration date": "Дата истечения срока действия", "Expiration time": "Время действия", @@ -911,6 +922,7 @@ "Failed": "Не удалось", "failed": "не удалось", "Failed to copy to clipboard": "Не удалось скопировать в буфер обмена", + "Failed to create event.": "Не удалось создать событие.", "Failed to delete vulnerability.": "Не удалось удалить уязвимость.", "Failed to import CSV file": "Не удалось импортировать CSV-файл", "Failed to update vulnerability.": "Не удалось обновить уязвимость.", @@ -944,6 +956,7 @@ "faq.support.title": "Поддержка", "faq.usage.title": "Использование", "Field": "Поле", + "Field to Check": "Проверяемое поле", "File": "Файл", "File Drop": "Падение файла", "File drop file": "Падение файла", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "Оперативная", + "Operator": "Оператор", "Optional": "Дополнительно", "Options": "Опции", "OR": "ИЛИ", "or": "или", + "Or": "Или", "Order": "Заказ", "Organization": "Организация", "Organizations": "Организации", @@ -1844,6 +1859,7 @@ "Remove from the inject": "Удалить из инъекции", "Remove from the media pressure": "Удалить из давления среды", "Remove from the team": "Удалить из команды", + "Remove group": "Удалить группу", "RENEW": "ОБНОВИТЬ", "Replace": "Заменить", "Replay all tests": "Воспроизвести все тесты", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "Отслеживание общего успеха", "Trigger": "Триггер", "Trigger after": "Триггер после", + "Trigger Conditions": "Условия срабатывания", "Trigger now": "Триггер сейчас", "Trigger on": "Включить триггер", "Trigger time": "Время срабатывания", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "Обновить конечную точку", "Update connector instance": "Обновить экземпляр коннектора", + "Update Event": "Обновить событие", "Update event": "Событие обновления", "Update inject comment": "Обновление комментария инъекции", "Update objects": "Обновление объектов", diff --git a/openaev-front/src/utils/lang/zh.json b/openaev-front/src/utils/lang/zh.json index fdaff656788..0a9d28c54ca 100644 --- a/openaev-front/src/utils/lang/zh.json +++ b/openaev-front/src/utils/lang/zh.json @@ -25,6 +25,7 @@ "A document used in a payload can't be deleted.": "有效载荷中使用的文件不能删除。", "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.": "A prerequisite check failed before the main command could run. Review prerequisite dependencies and ensure they are met on the target.", "A simulation will be launched based on this scenario and will start immediately. Are you sure you want to proceed?": "基于此场景的模拟将立即启动。您确定要继续吗?", + "Aa": "Aa", "ABSOLUTE_TIME_WITHOUT_START_DATE": "导入内容包含在一天中的某个时间(如上午 9:30)触发的注入,但方案没有开始日期。请指定开始日期.", "Accent color": "强调色", "Accept": "接受", @@ -82,13 +83,17 @@ "Add challenges": "添加挑战", "Add component": "添加组成部分", "Add Component": "添加组件", + "Add Condition": "添加条件", "Add condition": "添加条件", + "Add Condition Group": "添加条件组", "Add document": "添加文件", "Add documents": "添加文档", "Add documents in this challenge": "添加文档到这个挑战", "Add documents in this inject": "添加文档到这个注入", "Add documents in this media pressure": "将文档添加到媒体", "Add documents in this media pressure article": "添加文档到媒体文章", + "Add Event": "添加事件", + "Add Events": "添加事件", "Add expectation in this inject": "在注入中添加期望", "Add expectations": "添加期望", "Add filter": "添加过滤器", @@ -161,6 +166,7 @@ "Analyzing results…": "分析结果...", "AND": "和", "and": "和", + "And": "且", "and in our": "和我们的", "And many more features...": "还有更多功能...", "And/or import documents (.txt .pdf)": "和/或导入文件 (.txt .pdf)", @@ -595,6 +601,7 @@ "Definition": "定义", "Delegation": "授权", "Delete": "删除", + "Delete condition": "删除条件", "Delete test": "删除测试", "Delete variable": "删除变量", "DELETE_ASSESSMENT": "删除评估", @@ -838,6 +845,9 @@ "errors.UNKNOWN": "出现未知错误。", "Evaluate": "评估", "Event": "事件", + "Event added successfully.": "事件已成功添加。", + "Event conditions are based on data produced by Actions. To access more options in the \"Field to inspect\", consider adding additional.": "事件条件基于“操作”生成的数据。若要在“待检查字段”中访问更多选项,请考虑添加其他字段。", + "Event updated successfully.": "事件已成功更新。", "every_fem_plural": "每", "every_fem_singular": "每", "every_masc_plural": "每", @@ -887,6 +897,7 @@ "Expectations of ": "期望", "Expected score": "預期分數", "Expected score:": "预期得分:", + "Expected Value": "预期值", "Experiment valuable threat management resources in the XTM Hub": "在 XTM Hub 中体验宝贵的威胁管理资源", "Expiration date": "到期日期", "Expiration time": "过期时间", @@ -911,6 +922,7 @@ "Failed": "已失败", "failed": "已失败", "Failed to copy to clipboard": "复制到剪贴板失败", + "Failed to create event.": "创建事件失败。", "Failed to delete vulnerability.": "删除漏洞失败", "Failed to import CSV file": "导入 CSV 文件失败", "Failed to update vulnerability.": "更新漏洞失败。", @@ -944,6 +956,7 @@ "faq.support.title": "支持", "faq.usage.title": "使用方法", "Field": "字段", + "Field to Check": "待检查字段", "File": "文件", "File Drop": "文件投放", "File drop file": "文件放置文件", @@ -1652,10 +1665,12 @@ "OpenMTD": "OpenMTD", "opensearch": "OpenSearch", "Operational": "运营", + "Operator": "运算符", "Optional": "可选", "Options": "选项", "OR": "或", "or": "或", + "Or": "或", "Order": "订阅", "Organization": "组织", "Organizations": "组织", @@ -1844,6 +1859,7 @@ "Remove from the inject": "从注入移除", "Remove from the media pressure": "从媒体移除", "Remove from the team": "从团队移除", + "Remove group": "删除组", "RENEW": "更新", "Replace": "替换", "Replay all tests": "重放所有测试", @@ -2319,6 +2335,7 @@ "Tracking Total Success": "跟踪总成功", "Trigger": "触发", "Trigger after": "在之后触发", + "Trigger Conditions": "触发条件", "Trigger now": "现在触发", "Trigger on": "触发", "Trigger time": "触发器时间", @@ -2365,6 +2382,7 @@ "Update an asset": "Update an asset", "Update an endpoint": "更新端点", "Update connector instance": "更新连接器实例", + "Update Event": "更新事件", "Update event": "更新事件", "Update inject comment": "编辑刺激评论", "Update objects": "更新对象", From af28fe0d40185825bd6cd86a6b7856e6fef96a07 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Fri, 26 Jun 2026 16:43:47 +0200 Subject: [PATCH 2/7] feat(chaining): event creation drawer (#6297) --- .../openaev/api/chaining/ConditionMapper.java | 2 + .../chaining/dto/ConditionCreateInput.java | 5 +++ .../api/chaining/dto/ConditionOutput.java | 3 ++ .../V5_27__Add_condition_case_sensitive.java | 18 ++++++++ .../java/io/openaev/utils/ConditionUtils.java | 14 ++++-- .../admin/components/chaining/logic/Logic.tsx | 11 +++++ .../ChainingFlowConfiguration.tsx | 9 +++- .../logic/events/ConfigureEventDetail.tsx | 7 ++- .../logic/events/EventConditionRow.tsx | 45 ++++++++++--------- .../logic/events/EventCreationForm.tsx | 40 ++++++++++------- .../logic/events/LogicalOperatorSelect.tsx | 4 +- .../chaining/logic/events/event-types.ts | 17 ++++--- .../chaining/logic/logic-flow-helpers.ts | 12 ++--- .../scenario/logic/ScenarioLogic.tsx | 8 ++-- .../simulation/logic/SimulationLogic.tsx | 8 ++-- openaev-front/src/utils/api-types.d.ts | 3 ++ openaev-front/src/utils/lang/de.json | 9 ++++ openaev-front/src/utils/lang/en.json | 9 ++++ openaev-front/src/utils/lang/es.json | 9 ++++ openaev-front/src/utils/lang/fr.json | 9 ++++ openaev-front/src/utils/lang/it.json | 9 ++++ openaev-front/src/utils/lang/ja.json | 9 ++++ openaev-front/src/utils/lang/ko.json | 9 ++++ openaev-front/src/utils/lang/ru.json | 9 ++++ openaev-front/src/utils/lang/zh.json | 9 ++++ .../io/openaev/database/model/Condition.java | 5 +++ 26 files changed, 228 insertions(+), 64 deletions(-) create mode 100644 openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java diff --git a/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java b/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java index ae29958a40c..e948d3544d6 100644 --- a/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java +++ b/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java @@ -86,6 +86,7 @@ private static ConditionOutput toConditionOutput(Condition c) { .keySubtype(c.getKeySubtype()) .type(c.getType() != null ? c.getType().name() : null) .value(c.getValue()) + .caseSensitive(c.isCaseSensitive()) .conditionParentId(parentId) .mappingType(c.getMappingType()) .build(); @@ -120,6 +121,7 @@ public static Condition toCondition(ConditionCreateInput input, Condition condit .keySubtype(input.getKeySubtype()) .type(input.getType()) .value(input.getValue()) + .caseSensitive(input.getCaseSensitive() == null || input.getCaseSensitive()) .conditionParent(conditionParent) .mappingType(resolveMappingType(input)) .build(); diff --git a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java index ace1ee818ba..8122b04a314 100644 --- a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java +++ b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java @@ -44,6 +44,11 @@ public class ConditionCreateInput { @JsonProperty("condition_value") private String value; + /** Whether the comparison is case-sensitive (default: true) */ + @Schema(description = "Whether the comparison is case-sensitive") + @JsonProperty("condition_case_sensitive") + private Boolean caseSensitive; + /** Condition key: Property to be mapped */ @Schema(description = "Property to be mapped") @JsonProperty("condition_key") diff --git a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionOutput.java b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionOutput.java index 625c5c37a22..bd9700ad7a9 100644 --- a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionOutput.java +++ b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionOutput.java @@ -32,6 +32,9 @@ public class ConditionOutput { @JsonProperty("condition_value") private String value; + @JsonProperty("condition_case_sensitive") + private boolean caseSensitive; + @JsonProperty("condition_parent_id") private String conditionParentId; diff --git a/openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java b/openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java new file mode 100644 index 00000000000..486817be4ac --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java @@ -0,0 +1,18 @@ +package io.openaev.migration; + +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +@Component +public class V5_27__Add_condition_case_sensitive extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + try (Statement statement = context.getConnection().createStatement()) { + statement.execute( + "ALTER TABLE conditions ADD COLUMN IF NOT EXISTS condition_case_sensitive BOOLEAN NOT NULL DEFAULT TRUE"); + } + } +} diff --git a/openaev-api/src/main/java/io/openaev/utils/ConditionUtils.java b/openaev-api/src/main/java/io/openaev/utils/ConditionUtils.java index df631a90547..c0b45480685 100644 --- a/openaev-api/src/main/java/io/openaev/utils/ConditionUtils.java +++ b/openaev-api/src/main/java/io/openaev/utils/ConditionUtils.java @@ -82,6 +82,7 @@ public boolean evaluateLeafCondition(String actualValue, Condition filter) { return true; } String target = filter.getValue(); + boolean caseSensitive = filter.isCaseSensitive(); switch (type) { case IS_NULL: @@ -89,15 +90,22 @@ public boolean evaluateLeafCondition(String actualValue, Condition filter) { case IS_NOT_NULL: return actualValue != null; case EQ: - return actualValue != null && actualValue.equalsIgnoreCase(target); + return actualValue != null + && (caseSensitive ? actualValue.equals(target) : actualValue.equalsIgnoreCase(target)); case NEQ: - return actualValue != null && !actualValue.equalsIgnoreCase(target); + return actualValue != null + && (caseSensitive + ? !actualValue.equals(target) + : !actualValue.equalsIgnoreCase(target)); case IN, NIN: if (actualValue == null || target == null) { return false; } List targetList = Arrays.asList(target.split("\\s*,\\s*")); - boolean contains = targetList.stream().anyMatch(actualValue::equalsIgnoreCase); + boolean contains = + caseSensitive + ? targetList.stream().anyMatch(actualValue::equals) + : targetList.stream().anyMatch(actualValue::equalsIgnoreCase); return (type == ConditionType.IN) == contains; case GT, GTE, LT, LTE: return handleNumericComparison(actualValue, target, type); diff --git a/openaev-front/src/admin/components/chaining/logic/Logic.tsx b/openaev-front/src/admin/components/chaining/logic/Logic.tsx index fe41bb347da..f2768558f1b 100644 --- a/openaev-front/src/admin/components/chaining/logic/Logic.tsx +++ b/openaev-front/src/admin/components/chaining/logic/Logic.tsx @@ -22,6 +22,8 @@ const Logic = ({ workflowId, context }: LogicProps) => { const [validAssets, setValidAssets] = useState([]); // Track whether existing steps/events exist const [hasExistingData, setHasExistingData] = useState(null); + // Count of existing events (used to generate default names) + const [eventCount, setEventCount] = useState(0); // Key to force LogicFlow re-mount after adding a step const [refreshKey, setRefreshKey] = useState(0); // Drawer navigation state (shared with ChainingFlowConfiguration) @@ -56,6 +58,7 @@ const Logic = ({ workflowId, context }: LogicProps) => { const steps: StepOutput[] = stepsRes.data ?? []; const events: EventOutput[] = conditionsRes.data ?? []; setHasExistingData(steps.length > 0 || events.length > 0); + setEventCount(events.length); }); }, [workflowId]); @@ -64,6 +67,12 @@ const Logic = ({ workflowId, context }: LogicProps) => { setRefreshKey(k => k + 1); }, []); + const handleEventCreated = useCallback(() => { + setHasExistingData(true); + setEventCount(c => c + 1); + setRefreshKey(k => k + 1); + }, []); + const handleOpenDrawer = useCallback(() => { setDrawerView('choose'); }, []); @@ -119,6 +128,8 @@ const Logic = ({ workflowId, context }: LogicProps) => { editingEvent={editingEvent} onEditingEventChange={setEditingEvent} onStepCreated={handleStepCreated} + onEventCreated={handleEventCreated} + eventCount={eventCount} /> ); diff --git a/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx b/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx index e130da3a8e7..1b6d3d51e06 100644 --- a/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx +++ b/openaev-front/src/admin/components/chaining/logic/chaining_flow/ChainingFlowConfiguration.tsx @@ -48,6 +48,8 @@ interface ChainingFlowConfigurationProps { meta: EventMeta; } | null) => void; onStepCreated: () => void; + onEventCreated: () => void; + eventCount: number; } const ChainingFlowConfiguration = ({ @@ -60,6 +62,8 @@ const ChainingFlowConfiguration = ({ editingEvent, onEditingEventChange, onStepCreated, + onEventCreated, + eventCount, }: ChainingFlowConfigurationProps) => { const { t } = useFormatter(); @@ -253,11 +257,13 @@ const ChainingFlowConfiguration = ({ } else { await createCondition(event); MESSAGING$.notifySuccess(t('Event added successfully.')); + onEventCreated(); } handleCloseAll(); onStepCreated(); } catch { - MESSAGING$.notifyError(t('Failed to create event.')); + if (editingEvent) MESSAGING$.notifyError(t('Failed to update event.')); + else MESSAGING$.notifyError(t('Failed to create event.')); } }; @@ -295,6 +301,7 @@ const ChainingFlowConfiguration = ({ onSave={handleSaveEvent} initialData={editingEvent?.meta.formData} isEditing={!!editingEvent} + defaultEventName={!editingEvent ? `Event ${eventCount + 1}` : undefined} /> ); diff --git a/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx b/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx index adfd3dec53e..13f54ad79d8 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx +++ b/openaev-front/src/admin/components/chaining/logic/events/ConfigureEventDetail.tsx @@ -13,6 +13,7 @@ interface Props { onSave: (data: EventFormData) => void; initialData?: EventFormData; isEditing?: boolean; + defaultEventName?: string; } const ConfigureEventDetail: FunctionComponent = ({ @@ -22,6 +23,7 @@ const ConfigureEventDetail: FunctionComponent = ({ onSave, initialData, isEditing = false, + defaultEventName, }) => { const { t } = useFormatter(); @@ -29,12 +31,12 @@ const ConfigureEventDetail: FunctionComponent = ({
= ({ onCancel={onClose} initialData={initialData} submitLabel={isEditing ? t('Update Event') : t('Add Event')} + defaultName={defaultEventName} />
diff --git a/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx b/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx index 9b96a47f94b..668df763904 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx +++ b/openaev-front/src/admin/components/chaining/logic/events/EventConditionRow.tsx @@ -10,6 +10,7 @@ import { type SelectChangeEvent, Switch, TextField, + Tooltip, Typography, } from '@mui/material'; import { useTheme } from '@mui/material/styles'; @@ -162,28 +163,30 @@ const EventConditionRow: FunctionComponent = ({ }} > {showCaseSensitive && ( -
- - +
- {t('Aa')} - -
+ + + {t('Aa')} + +
+ )} {/* Delete button — only visible when more than one condition */} diff --git a/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx b/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx index e642ba957ad..38f72f6ea24 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx +++ b/openaev-front/src/admin/components/chaining/logic/events/EventCreationForm.tsx @@ -30,6 +30,7 @@ interface EventCreationFormProps { onCancel: () => void; initialData?: EventFormData; submitLabel?: string; + defaultName?: string; } const EventCreationForm: FunctionComponent = ({ @@ -37,13 +38,14 @@ const EventCreationForm: FunctionComponent = ({ onCancel, initialData, submitLabel, + defaultName, }) => { const { t } = useFormatter(); const methods = useForm({ mode: 'onChange', resolver: zodResolver(eventBaseSchema), defaultValues: { - event_name: initialData?.name ?? '', + event_name: initialData?.name ?? defaultName ?? '', event_description: initialData?.description ?? '', }, }); @@ -69,11 +71,14 @@ const EventCreationForm: FunctionComponent = ({ const handleAddConditionGroup = useCallback(() => { setConditionGroups(prev => [...prev, createEmptyGroup('AND')]); - setGroupOperators(prev => [...prev, 'AND']); + // Preserve the current operator so the new gap stays in sync with the others + setGroupOperators(prev => [...prev, prev[0] ?? 'AND']); }, []); const handleUpdateGroupOperator = useCallback((gapIndex: number, op: LogicalOperator) => { - setGroupOperators(prev => prev.map((o, i) => (i === gapIndex ? op : o))); + // The backend stores a single root operator for all groups. + // All gap operators must stay in sync — update every gap to the new value. + setGroupOperators(prev => prev.map(() => op)); }, []); const cloneGroup = (conditionGroup: ConditionGroup): ConditionGroup => ({ @@ -107,20 +112,25 @@ const EventCreationForm: FunctionComponent = ({ const [moved] = srcGroup.conditions.splice(sourceIdx, 1); // remove from source dstGroup.conditions.splice(destinationIdx, 0, moved); // add in destination - // Remove any top-level group that became empty after the drag - const nonEmptyIndices: number[] = []; - const cleaned = next.filter((g, i) => { - const keep = g.conditions.length > 0 || g.subGroups.length > 0; - if (keep) nonEmptyIndices.push(i); - return keep; - }); + // Remove top-level groups that became empty after the drag. + const emptyIndices = next + .map((_, i) => i) + .filter(i => next[i].conditions.length === 0 && next[i].subGroups.length === 0) + .reverse(); + + let groups = next; + let ops = groupOperators; - // Keep only the operators between groups that both survived - setConditionGroups(cleaned.length > 0 ? cleaned : next); - if (cleaned.length < next.length) { - setGroupOperators(prev => prev.filter((_, i) => nonEmptyIndices.includes(i))); + for (const k of emptyIndices) { + if (groups.length <= 1) break; // always keep at least one group + const opIndex = Math.max(0, k - 1); + groups = groups.filter((_, i) => i !== k); + ops = ops.filter((_, i) => i !== opIndex); } - }, [conditionGroups]); + + setConditionGroups(groups); + setGroupOperators(ops); + }, [conditionGroups, groupOperators]); const conditionsValid = isEventFormValid({ name: 'placeholder', diff --git a/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx b/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx index d94c82d7056..b972a03323c 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx +++ b/openaev-front/src/admin/components/chaining/logic/events/LogicalOperatorSelect.tsx @@ -1,7 +1,7 @@ import { ArrowDropDown } from '@mui/icons-material'; import { Button, Menu, MenuItem } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import { type FunctionComponent, useState } from 'react'; +import { type FunctionComponent, type MouseEvent, useState } from 'react'; import { useFormatter } from '../../../../../components/i18n'; import type { LogicalOperator } from './event-types'; @@ -16,7 +16,7 @@ const LogicalOperatorSelect: FunctionComponent = ({ value, onChange }) => const theme = useTheme(); const [anchorEl, setAnchorEl] = useState(null); - const handleOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); + const handleOpen = (e: MouseEvent) => setAnchorEl(e.currentTarget); const handleClose = () => setAnchorEl(null); const handleSelect = (operator: LogicalOperator) => { onChange(operator); diff --git a/openaev-front/src/admin/components/chaining/logic/events/event-types.ts b/openaev-front/src/admin/components/chaining/logic/events/event-types.ts index 0f7f624c926..ddd56f49666 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/event-types.ts +++ b/openaev-front/src/admin/components/chaining/logic/events/event-types.ts @@ -23,12 +23,12 @@ export const UNARY_OPERATORS: ComparisonOperator[] = ['IS_NULL', 'IS_NOT_NULL']; // Operators where case sensitivity is relevant export const CASE_SENSITIVE_OPERATORS: ComparisonOperator[] = ['EQ', 'NEQ', 'IN', 'NIN']; -// -- Operator labels -- +// -- Operator labels (function form so the extractor sees static t() calls) -- export const OPERATOR_LABELS: Record = { EQ: 'Equals', NEQ: 'Not equals', - IS_NULL: 'IsNull', - IS_NOT_NULL: 'IsNotNull', + IS_NULL: 'Is null', + IS_NOT_NULL: 'Is not null', GT: 'Greater than', GTE: 'Greater than or equals', LT: 'Less than', @@ -117,6 +117,7 @@ export const conditionGroupsToApi = ( condition_key_type: cond.field, // Unary operators (IS_NULL / IS_NOT_NULL) need no value condition_value: UNARY_OPERATORS.includes(cond.operator) ? undefined : cond.value, + condition_case_sensitive: cond.caseSensitive, }), ); @@ -130,7 +131,9 @@ export const conditionGroupsToApi = ( return result; } - // Multiple groups → emit a root logical node that wraps them all + // Multiple groups → emit a single root logical node wrapping them all. + // The backend stores one operator at the root, so all gap operators must be identical. + // EventCreationForm.handleUpdateGroupOperator keeps them in sync: groupOperators[0] is authoritative. const rootTempId = nextTempId(); result.push({ condition_temporary_id: rootTempId, @@ -142,8 +145,10 @@ export const conditionGroupsToApi = ( return result; }; +export const generateId = (): string => `tmp_${Date.now()}_${Math.random().toString(16).slice(2)}`; + export const createEmptyCondition = (): EventCondition => ({ - id: crypto.randomUUID(), + id: generateId(), field: 'text', operator: 'IN', value: '', @@ -151,7 +156,7 @@ export const createEmptyCondition = (): EventCondition => ({ }); export const createEmptyGroup = (operator: LogicalOperator = 'AND'): ConditionGroup => ({ - id: crypto.randomUUID(), + id: generateId(), operator, conditions: [createEmptyCondition()], subGroups: [], diff --git a/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts b/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts index 11bf8fb64fe..8338e8ff097 100644 --- a/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts +++ b/openaev-front/src/admin/components/chaining/logic/logic-flow-helpers.ts @@ -9,7 +9,7 @@ import { type ConditionKeyType, createEmptyCondition, createEmptyGroup, - type EventCondition, + type EventCondition, generateId, type LogicalOperator, } from './events/event-types'; import type { ActionMeta, EventMeta } from './types'; @@ -131,7 +131,7 @@ const reconstructConditionGroups = ( } const buildGroup = (groupNode: ConditionOutput): ConditionGroup => { - const groupId = groupNode.condition_id ?? crypto.randomUUID(); + const groupId = groupNode.condition_id ?? generateId(); const children = childrenOf[groupId] ?? []; const conditions: EventCondition[] = []; const subGroups: ConditionGroup[] = []; @@ -142,11 +142,11 @@ const reconstructConditionGroups = ( subGroups.push(buildGroup(child)); } else { conditions.push({ - id: child.condition_id ?? crypto.randomUUID(), + id: child.condition_id ?? generateId(), field: (child.condition_key_type as ConditionKeyType) ?? 'text', operator: (child.condition_type as ComparisonOperator) ?? 'IN', value: child.condition_value ?? '', - caseSensitive: true, + caseSensitive: child.condition_case_sensitive !== false, }); } } @@ -195,11 +195,11 @@ const reconstructConditionGroups = ( id: rootId, operator: (rootNode.condition_type as LogicalOperator) ?? 'AND', conditions: topConditions.map(c => ({ - id: c.condition_id ?? crypto.randomUUID(), + id: c.condition_id ?? generateId(), field: (c.condition_key_type as ConditionKeyType) ?? 'text', operator: (c.condition_type as ComparisonOperator) ?? 'IN', value: c.condition_value ?? '', - caseSensitive: true, + caseSensitive: c.condition_case_sensitive !== false, })), subGroups: [], }; diff --git a/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx b/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx index cce68c2653b..84cee7a0d69 100644 --- a/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx +++ b/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx @@ -3,14 +3,14 @@ import { useParams } from 'react-router'; import { type ScenariosHelper } from '../../../../../actions/scenarios/scenario-helper'; import { useHelper } from '../../../../../store'; import type { Scenario } from '../../../../../utils/api-types'; -// import Logic from '../../../chaining/logic/Logic'; -import LogicV1 from '../../../chaining/logic/LogicV1'; +import Logic from '../../../chaining/logic/Logic'; +// import LogicV1 from '../../../chaining/logic/LogicV1'; const ScenarioLogic = () => { const { scenarioId } = useParams() as { scenarioId: Scenario['scenario_id'] }; const { scenario } = useHelper((helper: ScenariosHelper) => ({ scenario: helper.getScenario(scenarioId) })); - // return ; - return ; + return ; + // return ; }; export default ScenarioLogic; diff --git a/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx b/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx index d32398c1e25..8d667fd9253 100644 --- a/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx +++ b/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx @@ -3,14 +3,14 @@ import { useParams } from 'react-router'; import { type ExercisesHelper } from '../../../../../actions/exercises/exercise-helper'; import { useHelper } from '../../../../../store'; import type { Exercise } from '../../../../../utils/api-types'; -// import Logic from '../../../chaining/logic/Logic'; -import LogicV1 from '../../../chaining/logic/LogicV1'; +import Logic from '../../../chaining/logic/Logic'; +// import LogicV1 from '../../../chaining/logic/LogicV1'; const SimulationLogic = () => { const { exerciseId } = useParams() as { exerciseId: Exercise['exercise_id'] }; const { exercise } = useHelper((helper: ExercisesHelper) => ({ exercise: helper.getExercise(exerciseId) })); - // return ; - return ; + return ; + // return ; }; export default SimulationLogic; diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index 1247224ae32..cff4d48a0fc 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -1569,6 +1569,8 @@ export interface ConditionCreateInput { | "DEPEND_ON"; /** Value to be compared */ condition_value?: string; + /** Whether the comparison is case-sensitive */ + condition_case_sensitive?: boolean; } export interface ConditionOutput { @@ -1611,6 +1613,7 @@ export interface ConditionOutput { condition_parent_id?: string; condition_type?: string; condition_value?: string; + condition_case_sensitive?: boolean; } export interface Configuration { diff --git a/openaev-front/src/utils/lang/de.json b/openaev-front/src/utils/lang/de.json index 8368d1bade1..8979c450351 100644 --- a/openaev-front/src/utils/lang/de.json +++ b/openaev-front/src/utils/lang/de.json @@ -336,6 +336,8 @@ "Canceled": "Abgebrochen", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "Dieses Team kann nicht gelöscht werden, da es noch verwendet wird. Bitte entfernen Sie zuerst seine Abhängigkeiten.", "capture-the-flag": "Erobere die Flagge", + "Case-insensitive": "Groß-/Kleinschreibung wird nicht berücksichtigt", + "Case-sensitive": "Groß-/Kleinschreibung wird berücksichtigt", "Catalog": "Katalog", "Categories and questions": "Kategorien und Fragen", "Category": "Kategorie", @@ -925,6 +927,7 @@ "Failed to create event.": "Ereignis konnte nicht erstellt werden.", "Failed to delete vulnerability.": "Schwachstelle konnte nicht gelöscht werden.", "Failed to import CSV file": "Import der CSV-Datei fehlgeschlagen", + "Failed to update event.": "Das Ereignis konnte nicht aktualisiert werden.", "Failed to update vulnerability.": "Aktualisierung der Sicherheitslücke fehlgeschlagen.", "Failure": "Fehlgeschlagen", "faq.components.title": "Komponenten", @@ -1040,6 +1043,8 @@ "Global score": "Globale Bewertung", "global-crisis": "Globale Krise", "Granted": "Gewährt", + "Greater than": "Größer als", + "Greater than or equals": "Größer als oder gleich", "Graphql_api": "GraphQL API", "Group": "Gruppe", "Groups": "Gruppen", @@ -1261,6 +1266,8 @@ "is": "ist", "is empty": "ist leer", "is not empty": "ist nicht leer", + "Is not null": "Ist nicht null", + "Is null": "Ist null", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "Erfahren Sie mehr über den Parser.", "Learn more information about how to setup simulation agents": "Erfahren Sie mehr über die Einrichtung von Simulationsagenten", "learn_more": "Mehr erfahren", + "Less than": "Kleiner als", + "Less than or equals": "Kleiner als oder gleich", "Lessons learned": "Gelernte Lektionen", "Lessons learned details": "Details zu den Lektionen", "Lessons learned objectives": "Ziele der Lessons Learned", diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index 6d3bc029bb8..2a52420294a 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -336,6 +336,8 @@ "Canceled": "Canceled", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "Cannot delete this team because it is still in use. Please remove its dependencies first.", "capture-the-flag": "Capture The Flag", + "Case-insensitive": "Case-insensitive", + "Case-sensitive": "Case-sensitive", "Catalog": "Catalog", "Categories and questions": "Categories and questions", "Category": "Category", @@ -925,6 +927,7 @@ "Failed to create event.": "Failed to create event.", "Failed to delete vulnerability.": "Failed to delete vulnerability.", "Failed to import CSV file": "Failed to import CSV file", + "Failed to update event.": "Failed to update event.", "Failed to update vulnerability.": "Failed to update vulnerability.", "Failure": "Failure", "faq.components.title": "Components", @@ -1040,6 +1043,8 @@ "Global score": "Global score", "global-crisis": "Global Crisis", "Granted": "Granted", + "Greater than": "Greater than", + "Greater than or equals": "Greater than or equals", "Graphql_api": "GraphQL API", "Group": "Group", "Groups": "Groups", @@ -1261,6 +1266,8 @@ "is": "is", "is empty": "is empty", "is not empty": "is not empty", + "Is not null": "Is not null", + "Is null": "Is null", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "Learn more about parser.", "Learn more information about how to setup simulation agents": "Learn more information about how to setup simulation agents", "learn_more": "Learn more", + "Less than": "Less than", + "Less than or equals": "Less than or equals", "Lessons learned": "Lessons learned", "Lessons learned details": "Lessons learned details", "Lessons learned objectives": "Lessons learned objectives", diff --git a/openaev-front/src/utils/lang/es.json b/openaev-front/src/utils/lang/es.json index d53221de2a7..6c8efaef7b1 100644 --- a/openaev-front/src/utils/lang/es.json +++ b/openaev-front/src/utils/lang/es.json @@ -336,6 +336,8 @@ "Canceled": "Cancelado", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "No se puede eliminar este equipo porque todavía está en uso. Primero, elimine sus dependencias.", "capture-the-flag": "Capturar la bandera", + "Case-insensitive": "Sin distinción entre mayúsculas y minúsculas", + "Case-sensitive": "Con distinción entre mayúsculas y minúsculas", "Catalog": "Catálogo", "Categories and questions": "Categorías y preguntas", "Category": "Categoría", @@ -925,6 +927,7 @@ "Failed to create event.": "No se ha podido crear el evento.", "Failed to delete vulnerability.": "Error al eliminar vulnerabilidad.", "Failed to import CSV file": "Error al importar archivo CSV", + "Failed to update event.": "No se ha podido actualizar el evento.", "Failed to update vulnerability.": "Error al actualizar vulnerabilidad.", "Failure": "Fallo", "faq.components.title": "Componentes", @@ -1041,6 +1044,8 @@ "global-crisis": "Crisis global", "Granted": "Concedido", "Graphql_api": "GraphQL API", + "Greater than": "Mayor que", + "Greater than or equals": "Mayor o igual que", "Group": "Grupo", "Groups": "Grupos", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "es", "is empty": "está vacío", "is not empty": "no está vacío", + "Is not null": "No es nulo", + "Is null": "Es nulo", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "NIMF", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "Más información sobre el analizador sintáctico.", "Learn more information about how to setup simulation agents": "Más información sobre cómo configurar los agentes de simulación", "learn_more": "Más información", + "Less than": "Menor que", + "Less than or equals": "Menor o igual que", "Lessons learned": "Lecciones aprendidas", "Lessons learned details": "Detalles de las lecciones aprendidas", "Lessons learned objectives": "Objetivos de las lecciones aprendidas", diff --git a/openaev-front/src/utils/lang/fr.json b/openaev-front/src/utils/lang/fr.json index e0a968b7ed3..ff0f5ae942c 100644 --- a/openaev-front/src/utils/lang/fr.json +++ b/openaev-front/src/utils/lang/fr.json @@ -336,6 +336,8 @@ "Canceled": "Annulé", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "Impossible de supprimer cette équipe car elle est toujours utilisée. Veuillez d'abord supprimer ses dépendances.", "capture-the-flag": "Capture de drapeau", + "Case-insensitive": "Sans distinction de majuscules/minuscules", + "Case-sensitive": "Avec distinction de majuscules/minuscules", "Catalog": "Catalogue", "Categories and questions": "Catégories et questions", "Category": "Catégorie", @@ -925,6 +927,7 @@ "Failed to create event.": "Échec de la création de l’événement.", "Failed to delete vulnerability.": "Échec de la suppression de la vulnérabilité.", "Failed to import CSV file": "Échec de l'importation du fichier CSV", + "Failed to update event.": "Échec de la mise à jour de l'événement.", "Failed to update vulnerability.": "Échec de la mise à jour de la vulnérabilité.", "Failure": "Echec", "faq.components.title": "Composants", @@ -1041,6 +1044,8 @@ "global-crisis": "Crise mondiale", "Granted": "Accordé", "Graphql_api": "GraphQL API", + "Greater than": "Supérieur à", + "Greater than or equals": "Supérieur ou égal à", "Group": "Groupe", "Groups": "Groupes", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "est", "is empty": "est vide", "is not empty": "n'est pas vide", + "Is not null": "N'est pas nul", + "Is null": "Est nul", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "En savoir plus sur l'analyseur.", "Learn more information about how to setup simulation agents": "En savoir plus sur la configuration des agents de simulation", "learn_more": "En savoir plus", + "Less than": "Inférieur à", + "Less than or equals": "Inférieur ou égal à", "Lessons learned": "Expérience", "Lessons learned details": "Détails des leçons apprises", "Lessons learned objectives": "Objectifs des leçons apprises", diff --git a/openaev-front/src/utils/lang/it.json b/openaev-front/src/utils/lang/it.json index f711eaa56f2..82500067746 100644 --- a/openaev-front/src/utils/lang/it.json +++ b/openaev-front/src/utils/lang/it.json @@ -336,6 +336,8 @@ "Canceled": "Annullato", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "Impossibile eliminare questo team perché è ancora in uso. Rimuovere prima le dipendenze.", "capture-the-flag": "Cattura la bandiera", + "Case-insensitive": "Senza distinzione tra maiuscole e minuscole", + "Case-sensitive": "Con distinzione tra maiuscole e minuscole", "Catalog": "Catalogo", "Categories and questions": "Categorie e domande", "Category": "Categoria", @@ -925,6 +927,7 @@ "Failed to create event.": "Impossibile creare l’evento.", "Failed to delete vulnerability.": "Cancellazione fallita della vulnerabilità.", "Failed to import CSV file": "Impossibile importare il file CSV", + "Failed to update event.": "Impossibile aggiornare l'evento.", "Failed to update vulnerability.": "Fallito l'aggiornamento della vulnerabilità.", "Failure": "Fallimento", "faq.components.title": "Componenti", @@ -1041,6 +1044,8 @@ "global-crisis": "Crisi globale", "Granted": "Concesso", "Graphql_api": "GraphQL API", + "Greater than": "Maggiore di", + "Greater than or equals": "Maggiore o uguale a", "Group": "Gruppo", "Groups": "Gruppi", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "è", "is empty": "è vuoto", "is not empty": "non è vuoto", + "Is not null": "Non è nullo", + "Is null": "È nullo", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "Per saperne di più sul parser.", "Learn more information about how to setup simulation agents": "Ulteriori informazioni su come impostare gli agenti di simulazione", "learn_more": "Per saperne di più", + "Less than": "Minore di", + "Less than or equals": "Minore o uguale a", "Lessons learned": "Lezioni apprese", "Lessons learned details": "Lezioni apprese in dettaglio", "Lessons learned objectives": "Obiettivi delle lezioni apprese", diff --git a/openaev-front/src/utils/lang/ja.json b/openaev-front/src/utils/lang/ja.json index 2bd1836f24d..e2804b7c884 100644 --- a/openaev-front/src/utils/lang/ja.json +++ b/openaev-front/src/utils/lang/ja.json @@ -336,6 +336,8 @@ "Canceled": "キャンセル", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "このチームは現在使用中のため削除できません。まず依存関係を削除してください。", "capture-the-flag": "キャプチャー・ザ・フラッグ", + "Case-insensitive": "大文字小文字を区別しない", + "Case-sensitive": "大文字小文字を区別する", "Catalog": "カタログ", "Categories and questions": "カテゴリーと質問", "Category": "カテゴリー", @@ -925,6 +927,7 @@ "Failed to create event.": "イベントの作成に失敗しました。", "Failed to delete vulnerability.": "脆弱性の削除に失敗しました。", "Failed to import CSV file": "CSVファイルのインポートに失敗", + "Failed to update event.": "イベントの更新に失敗しました。", "Failed to update vulnerability.": "脆弱性のアップデートに失敗した", "Failure": "失敗", "faq.components.title": "コンポーネント", @@ -1041,6 +1044,8 @@ "global-crisis": "グローバル危機", "Granted": "付与", "Graphql_api": "GraphQL API", + "Greater than": "より大きい", + "Greater than or equals": "以上", "Group": "グループ", "Groups": "グループ", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "は", "is empty": "は空", "is not empty": "は空でない", + "Is not null": "NULLではない", + "Is null": "NULLである", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "パーサーの詳細", "Learn more information about how to setup simulation agents": "シミュレーションエージェントの設定方法について詳しく知る", "learn_more": "もっと詳しく", + "Less than": "より小さい", + "Less than or equals": "以下", "Lessons learned": "学んだこと", "Lessons learned details": "教訓の詳細", "Lessons learned objectives": "学んだ目的", diff --git a/openaev-front/src/utils/lang/ko.json b/openaev-front/src/utils/lang/ko.json index 7e8fd5fe5aa..5d4e8076e3d 100644 --- a/openaev-front/src/utils/lang/ko.json +++ b/openaev-front/src/utils/lang/ko.json @@ -336,6 +336,8 @@ "Canceled": "취소됨", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "이 팀은 현재 사용 중이므로 삭제할 수 없습니다. 먼저 해당 팀의 종속 항목을 제거해 주세요.", "capture-the-flag": "깃발 캡처", + "Case-insensitive": "대소문자 구분 없음", + "Case-sensitive": "대소문자 구분 있음", "Catalog": "카탈로그", "Categories and questions": "카테고리 및 질문", "Category": "카테고리", @@ -925,6 +927,7 @@ "Failed to create event.": "이벤트 생성에 실패했습니다.", "Failed to delete vulnerability.": "취약점을 삭제하지 못했습니다.", "Failed to import CSV file": "CSV 파일을 가져오지 못했습니다", + "Failed to update event.": "이벤트 업데이트에 실패했습니다.", "Failed to update vulnerability.": "취약점을 업데이트하지 못했습니다.", "Failure": "실패", "faq.components.title": "구성 요소", @@ -1041,6 +1044,8 @@ "global-crisis": "글로벌 위기", "Granted": "부여됨", "Graphql_api": "GraphQL API", + "Greater than": "보다 크다", + "Greater than or equals": "보다 크거나 같다", "Group": "그룹", "Groups": "그룹", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "is", "is empty": "비어 있습니다", "is not empty": "비어 있지 않습니다", + "Is not null": "null이 아님", + "Is null": "null임", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "파서에 대해 자세히 알아보세요.", "Learn more information about how to setup simulation agents": "시뮬레이션 에이전트 설정 방법에 대해 자세히 알아보기", "learn_more": "자세히 알아보기", + "Less than": "보다 작다", + "Less than or equals": "보다 작거나 같다", "Lessons learned": "배운 교훈", "Lessons learned details": "교훈 세부 정보", "Lessons learned objectives": "교훈을 얻은 목표", diff --git a/openaev-front/src/utils/lang/ru.json b/openaev-front/src/utils/lang/ru.json index 3ad3aed74b8..95e5a2fd9e7 100644 --- a/openaev-front/src/utils/lang/ru.json +++ b/openaev-front/src/utils/lang/ru.json @@ -336,6 +336,8 @@ "Canceled": "Отменено", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "Удалить эту команду невозможно, так как она всё ещё используется. Пожалуйста, сначала удалите её зависимости.", "capture-the-flag": "Захват флага", + "Case-insensitive": "Без учета регистра", + "Case-sensitive": "С учетом регистра", "Catalog": "Каталог", "Categories and questions": "Категории и вопросы", "Category": "Категория", @@ -925,6 +927,7 @@ "Failed to create event.": "Не удалось создать событие.", "Failed to delete vulnerability.": "Не удалось удалить уязвимость.", "Failed to import CSV file": "Не удалось импортировать CSV-файл", + "Failed to update event.": "Не удалось обновить событие.", "Failed to update vulnerability.": "Не удалось обновить уязвимость.", "Failure": "Сбой", "faq.components.title": "Компоненты", @@ -1041,6 +1044,8 @@ "global-crisis": "Глобальный кризис", "Granted": "Предоставлено", "Graphql_api": "GraphQL API", + "Greater than": "Больше, чем", + "Greater than or equals": "Больше или равно", "Group": "Группа", "Groups": "Группы", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "это", "is empty": "пустой", "is not empty": "не пустой", + "Is not null": "Не равно null", + "Is null": "Равно null", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "Узнайте больше о парсере.", "Learn more information about how to setup simulation agents": "Узнайте больше о том, как настроить агентов симуляции", "learn_more": "Узнать больше", + "Less than": "Меньше, чем", + "Less than or equals": "Меньше или равно", "Lessons learned": "Извлеченные уроки", "Lessons learned details": "Подробности уроков", "Lessons learned objectives": "Цели извлеченных уроков", diff --git a/openaev-front/src/utils/lang/zh.json b/openaev-front/src/utils/lang/zh.json index 0a9d28c54ca..8c386dedd0f 100644 --- a/openaev-front/src/utils/lang/zh.json +++ b/openaev-front/src/utils/lang/zh.json @@ -336,6 +336,8 @@ "Canceled": "已取消", "Cannot delete this team because it is still in use. Please remove its dependencies first.": "由于该团队仍在被使用,因此无法删除。请先移除其依赖项。", "capture-the-flag": "获得flag", + "Case-insensitive": "不区分大小写", + "Case-sensitive": "区分大小写", "Catalog": "目录", "Categories and questions": "分类和问题", "Category": "分类", @@ -925,6 +927,7 @@ "Failed to create event.": "创建事件失败。", "Failed to delete vulnerability.": "删除漏洞失败", "Failed to import CSV file": "导入 CSV 文件失败", + "Failed to update event.": "更新事件失败。", "Failed to update vulnerability.": "更新漏洞失败。", "Failure": "失败", "faq.components.title": "组件", @@ -1041,6 +1044,8 @@ "global-crisis": "全局危机", "Granted": "已批准", "Graphql_api": "GraphQL API", + "Greater than": "大于", + "Greater than or equals": "大于或等于", "Group": "群组", "Groups": "组", "GT": "GT", @@ -1261,6 +1266,8 @@ "is": "是", "is empty": "为空", "is not empty": "不为空", + "Is not null": "不为空", + "Is null": "为空", "IS_NOT_NULL": "IS_NOT_NULL", "IS_NULL": "IS_NULL", "ISPM": "ISPM", @@ -1323,6 +1330,8 @@ "Learn more about parser.": "了解有关解析器的更多信息。", "Learn more information about how to setup simulation agents": "了解更多如何设置模拟agent", "learn_more": "了解更多", + "Less than": "小于", + "Less than or equals": "小于或等于", "Lessons learned": "经验教训", "Lessons learned details": "经验教训详情", "Lessons learned objectives": "经验教训目标", diff --git a/openaev-model/src/main/java/io/openaev/database/model/Condition.java b/openaev-model/src/main/java/io/openaev/database/model/Condition.java index 24205519aae..f3cb58bb29b 100644 --- a/openaev-model/src/main/java/io/openaev/database/model/Condition.java +++ b/openaev-model/src/main/java/io/openaev/database/model/Condition.java @@ -71,6 +71,11 @@ public class Condition implements Base { @Schema(description = "Value") private String value; + @Column(name = "condition_case_sensitive") + @Schema(description = "Whether the comparison is case-sensitive") + @Builder.Default + private boolean caseSensitive = true; + @Column(name = "condition_name") @Schema(description = "Name") private String name; From c65c61fa6a2197911897d1ab81dc60c324c95849 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Fri, 26 Jun 2026 17:01:35 +0200 Subject: [PATCH 3/7] feat(chaining): event creation drawer (#6297) --- openaev-front/src/utils/api-types.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index cff4d48a0fc..1e4ba0d7415 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -1506,6 +1506,8 @@ export interface Condition { /** Condition used to execute a step. Can be a Template or an Execution depending on the status of stepFrom. */ export interface ConditionCreateInput { + /** Whether the comparison is case-sensitive */ + condition_case_sensitive?: boolean; /** Property to be mapped */ condition_key?: string; /** Condition key subtype */ @@ -1569,11 +1571,10 @@ export interface ConditionCreateInput { | "DEPEND_ON"; /** Value to be compared */ condition_value?: string; - /** Whether the comparison is case-sensitive */ - condition_case_sensitive?: boolean; } export interface ConditionOutput { + condition_case_sensitive?: boolean; condition_id?: string; condition_key?: string; condition_key_subtype?: @@ -1613,7 +1614,6 @@ export interface ConditionOutput { condition_parent_id?: string; condition_type?: string; condition_value?: string; - condition_case_sensitive?: boolean; } export interface Configuration { From ba28515d64fd98c31465d95a13475a63856a2ba1 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Fri, 26 Jun 2026 17:18:52 +0200 Subject: [PATCH 4/7] feat(chaining): event creation drawer (#6297) --- ..._sensitive.java => V5_28__Add_condition_case_sensitive.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename openaev-api/src/main/java/io/openaev/migration/{V5_27__Add_condition_case_sensitive.java => V5_28__Add_condition_case_sensitive.java} (89%) diff --git a/openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java b/openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java similarity index 89% rename from openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java rename to openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java index 486817be4ac..6faf69b9be4 100644 --- a/openaev-api/src/main/java/io/openaev/migration/V5_27__Add_condition_case_sensitive.java +++ b/openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; @Component -public class V5_27__Add_condition_case_sensitive extends BaseJavaMigration { +public class V5_28__Add_condition_case_sensitive extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { From 45d7102dc24bf383987529ab37ab4c40a2710d9b Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Fri, 26 Jun 2026 17:49:42 +0200 Subject: [PATCH 5/7] feat(chaining): event creation drawer (#6297) --- .../service/chaining/ConditionServiceTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java b/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java index a67bc0da83f..7b15dfd2374 100644 --- a/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java @@ -1089,9 +1089,14 @@ void shouldLeaveMappingTypeNull_whenNonMapperCondition() { class IsFilterConditionValid { private Condition leaf(ConditionType type, String value) { + return leaf(type, value, true); + } + + private Condition leaf(ConditionType type, String value, boolean caseSensitive) { Condition c = new Condition(); c.setType(type); c.setValue(value); + c.setCaseSensitive(caseSensitive); return c; } @@ -1215,7 +1220,9 @@ void isNotNull_shouldReturnFalse_whenValueIsNull() { @Test void eq_shouldReturnTrue_whenValuesMatch_caseInsensitive() { - assertTrue(conditionUtils.isFilterConditionValid("Admin", leaf(ConditionType.EQ, "admin"))); + assertTrue( + conditionUtils.isFilterConditionValid( + "Admin", leaf(ConditionType.EQ, "admin", false))); } @Test @@ -1265,7 +1272,8 @@ void in_shouldReturnFalse_whenValueIsNotInTargetList() { @Test void in_shouldBeCaseInsensitive() { assertTrue( - conditionUtils.isFilterConditionValid("ADMIN", leaf(ConditionType.IN, "admin, root"))); + conditionUtils.isFilterConditionValid( + "ADMIN", leaf(ConditionType.IN, "admin, root", false))); } @Test From 0d8573343645c2aed8261ca60abc2cc11a971ff9 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Mon, 29 Jun 2026 10:41:39 +0200 Subject: [PATCH 6/7] feat(chaining): event creation drawer (#6297) --- .../openaev/api/chaining/ConditionMapper.java | 2 +- .../chaining/dto/ConditionCreateInput.java | 3 +- .../chaining/ConditionServiceTest.java | 3 +- .../logic/events/ConditionGroupBuilder.tsx | 45 +++++++------------ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java b/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java index e948d3544d6..13517262ae0 100644 --- a/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java +++ b/openaev-api/src/main/java/io/openaev/api/chaining/ConditionMapper.java @@ -121,7 +121,7 @@ public static Condition toCondition(ConditionCreateInput input, Condition condit .keySubtype(input.getKeySubtype()) .type(input.getType()) .value(input.getValue()) - .caseSensitive(input.getCaseSensitive() == null || input.getCaseSensitive()) + .caseSensitive(input.isCaseSensitive()) .conditionParent(conditionParent) .mappingType(resolveMappingType(input)) .build(); diff --git a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java index 8122b04a314..5f7ce6ca7bd 100644 --- a/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java +++ b/openaev-api/src/main/java/io/openaev/api/chaining/dto/ConditionCreateInput.java @@ -47,7 +47,8 @@ public class ConditionCreateInput { /** Whether the comparison is case-sensitive (default: true) */ @Schema(description = "Whether the comparison is case-sensitive") @JsonProperty("condition_case_sensitive") - private Boolean caseSensitive; + @Builder.Default + private boolean caseSensitive = true; /** Condition key: Property to be mapped */ @Schema(description = "Property to be mapped") diff --git a/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java b/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java index 7b15dfd2374..555ec6dea28 100644 --- a/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/service/chaining/ConditionServiceTest.java @@ -1221,8 +1221,7 @@ void isNotNull_shouldReturnFalse_whenValueIsNull() { @Test void eq_shouldReturnTrue_whenValuesMatch_caseInsensitive() { assertTrue( - conditionUtils.isFilterConditionValid( - "Admin", leaf(ConditionType.EQ, "admin", false))); + conditionUtils.isFilterConditionValid("Admin", leaf(ConditionType.EQ, "admin", false))); } @Test diff --git a/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx b/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx index 270668928e0..b7e8f8fd106 100644 --- a/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx +++ b/openaev-front/src/admin/components/chaining/logic/events/ConditionGroupBuilder.tsx @@ -1,6 +1,6 @@ import { Draggable, Droppable } from '@hello-pangea/dnd'; import { AddOutlined, DeleteOutline } from '@mui/icons-material'; -import { Button } from '@mui/material'; +import { Box, Button, Stack } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { type FunctionComponent } from 'react'; @@ -77,25 +77,14 @@ const ConditionGroupBuilder: FunctionComponent = ({ }; return ( -
-
-
+ + = ({ {t('Remove group')} )} -
+ -
+ {/* Droppable conditions list */} {(provided, snapshot) => ( -
= ({ {group.conditions.map((condition, index) => ( {(providedDrag, snapshotDrag) => ( -
= ({ onDelete={() => handleDeleteCondition(index)} canDelete={group.conditions.length > 1} /> -
+ )}
))} {provided.placeholder} -
+ )}
@@ -175,7 +162,7 @@ const ConditionGroupBuilder: FunctionComponent = ({ /> ))} -
+
); }; From d083a826a68d8956ce915f8fbd1147f7ba7d1254 Mon Sep 17 00:00:00 2001 From: Camille Roux Date: Mon, 29 Jun 2026 11:00:00 +0200 Subject: [PATCH 7/7] feat(chaining): event creation drawer (#6297) --- ...tive.java => V5_34__Add_condition_case_sensitive.java} | 2 +- .../components/scenarios/scenario/logic/ScenarioLogic.tsx | 8 ++++---- .../simulations/simulation/logic/SimulationLogic.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) rename openaev-api/src/main/java/io/openaev/migration/{V5_28__Add_condition_case_sensitive.java => V5_34__Add_condition_case_sensitive.java} (89%) diff --git a/openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java b/openaev-api/src/main/java/io/openaev/migration/V5_34__Add_condition_case_sensitive.java similarity index 89% rename from openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java rename to openaev-api/src/main/java/io/openaev/migration/V5_34__Add_condition_case_sensitive.java index 6faf69b9be4..45949ba98ad 100644 --- a/openaev-api/src/main/java/io/openaev/migration/V5_28__Add_condition_case_sensitive.java +++ b/openaev-api/src/main/java/io/openaev/migration/V5_34__Add_condition_case_sensitive.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; @Component -public class V5_28__Add_condition_case_sensitive extends BaseJavaMigration { +public class V5_34__Add_condition_case_sensitive extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { diff --git a/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx b/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx index 84cee7a0d69..cce68c2653b 100644 --- a/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx +++ b/openaev-front/src/admin/components/scenarios/scenario/logic/ScenarioLogic.tsx @@ -3,14 +3,14 @@ import { useParams } from 'react-router'; import { type ScenariosHelper } from '../../../../../actions/scenarios/scenario-helper'; import { useHelper } from '../../../../../store'; import type { Scenario } from '../../../../../utils/api-types'; -import Logic from '../../../chaining/logic/Logic'; -// import LogicV1 from '../../../chaining/logic/LogicV1'; +// import Logic from '../../../chaining/logic/Logic'; +import LogicV1 from '../../../chaining/logic/LogicV1'; const ScenarioLogic = () => { const { scenarioId } = useParams() as { scenarioId: Scenario['scenario_id'] }; const { scenario } = useHelper((helper: ScenariosHelper) => ({ scenario: helper.getScenario(scenarioId) })); - return ; - // return ; + // return ; + return ; }; export default ScenarioLogic; diff --git a/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx b/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx index 8d667fd9253..d32398c1e25 100644 --- a/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx +++ b/openaev-front/src/admin/components/simulations/simulation/logic/SimulationLogic.tsx @@ -3,14 +3,14 @@ import { useParams } from 'react-router'; import { type ExercisesHelper } from '../../../../../actions/exercises/exercise-helper'; import { useHelper } from '../../../../../store'; import type { Exercise } from '../../../../../utils/api-types'; -import Logic from '../../../chaining/logic/Logic'; -// import LogicV1 from '../../../chaining/logic/LogicV1'; +// import Logic from '../../../chaining/logic/Logic'; +import LogicV1 from '../../../chaining/logic/LogicV1'; const SimulationLogic = () => { const { exerciseId } = useParams() as { exerciseId: Exercise['exercise_id'] }; const { exercise } = useHelper((helper: ExercisesHelper) => ({ exercise: helper.getExercise(exerciseId) })); - return ; - // return ; + // return ; + return ; }; export default SimulationLogic;