From 3fc550cb0b06dcb02c7e44c6c9de7e2b7697b9e6 Mon Sep 17 00:00:00 2001 From: fxai Date: Mon, 4 May 2026 08:21:59 +0200 Subject: [PATCH 1/6] feature(frontend): replaced blocked input fields for blue and spectator roles --- .../assessment/ActivityDetectionSection.vue | 148 ++++++--- .../assessment/ActivityEvaluation.vue | 10 +- .../components/assessment/ActivityForm.vue | 75 +++-- .../assessment/ActivityGeneralInfo.vue | 294 +++++++++++------- frontend/src/components/ui/ReadonlyField.vue | 19 ++ 5 files changed, 363 insertions(+), 183 deletions(-) create mode 100644 frontend/src/components/ui/ReadonlyField.vue diff --git a/frontend/src/components/assessment/ActivityDetectionSection.vue b/frontend/src/components/assessment/ActivityDetectionSection.vue index 6d62eb5..78ca662 100644 --- a/frontend/src/components/assessment/ActivityDetectionSection.vue +++ b/frontend/src/components/assessment/ActivityDetectionSection.vue @@ -2,6 +2,7 @@ import { ChevronDown } from 'lucide-vue-next'; import { computed } from 'vue'; import ActivityAssetsManager from '@/components/assessment/ActivityAssetsManager.vue'; +import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Collapsible, @@ -19,7 +20,9 @@ import { SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; +import { usePreferencesStore } from '@/stores/preferences'; import type { ActivityRead, AssetRead } from '@/types/utils'; +import { formatDateTime } from '@/utils/dateFormatter'; import { schemas } from '@/types/zod'; const props = defineProps<{ @@ -37,6 +40,17 @@ const formData = defineModel>('formData', { }); const severityOptions = schemas.ActivitySeverity.options; +const preferencesStore = usePreferencesStore(); + +// Formatted date strings for readonly display +function readonlyDate(value: string | null | undefined): string { + return formatDateTime( + value, + preferencesStore.effectiveTimezone, + preferencesStore.dateFormat, + preferencesStore.timeFormat, + ); +} // Writable computed properties for detection toggles const logged = computed({ @@ -139,17 +153,26 @@ const stakeholderNotificationCreated = computed({
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
- + +
{{ eventToAlertEvalStatus }}
{{ alertToStakeholderEvalStatus }} {{ alertSeverityEvalStatus }} {{ stakeholderSeverityEvalStatus }}

diff --git a/frontend/src/components/assessment/ActivityForm.vue b/frontend/src/components/assessment/ActivityForm.vue index e0c0557..d44d29d 100644 --- a/frontend/src/components/assessment/ActivityForm.vue +++ b/frontend/src/components/assessment/ActivityForm.vue @@ -10,6 +10,7 @@ import ActivityGeneralInfo from '@/components/assessment/ActivityGeneralInfo.vue import ActivityHistoryModal from '@/components/assessment/ActivityHistoryModal.vue'; import ConflictResolutionDialog from '@/components/assessment/ConflictResolutionDialog.vue'; import KnowledgeBaseModal from '@/components/assessment/KnowledgeBaseModal.vue'; +import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { @@ -50,7 +51,9 @@ import type { AssetRead, TagRead, } from '@/types/utils'; +import { formatDateTime } from '@/utils/dateFormatter'; import { schemas } from '@/types/zod'; +import { usePreferencesStore } from '@/stores/preferences'; const props = defineProps<{ activity: ActivityRead; @@ -107,6 +110,18 @@ const headerStateDisabled = computed( () => isSpectator.value || (isBlue.value && !stateEditable.value), ); +const preferencesStore = usePreferencesStore(); + +// Formatted date strings for readonly display +function readonlyDate(value: string | null | undefined): string { + return formatDateTime( + value, + preferencesStore.effectiveTimezone, + preferencesStore.dateFormat, + preferencesStore.timeFormat, + ); +} + const showKBModal = ref(false); const showHistoryModal = ref(false); const attachmentRefreshKey = ref(0); @@ -491,20 +506,24 @@ async function handleCloneActivity() {

- + +
diff --git a/frontend/src/components/assessment/ActivityGeneralInfo.vue b/frontend/src/components/assessment/ActivityGeneralInfo.vue index 5222e75..e9d9732 100644 --- a/frontend/src/components/assessment/ActivityGeneralInfo.vue +++ b/frontend/src/components/assessment/ActivityGeneralInfo.vue @@ -2,6 +2,7 @@ import { ChevronDown } from 'lucide-vue-next'; import { computed, onMounted, onUnmounted, ref } from 'vue'; import { toast } from 'vue-sonner'; +import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Collapsible, @@ -11,6 +12,7 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import MarkdownEditor from '@/components/ui/MarkdownEditor.vue'; +import ReadonlyField from '@/components/ui/ReadonlyField.vue'; import SearchableSelect from '@/components/ui/SearchableSelect.vue'; import { ScrollArea } from '@/components/ui/scroll-area'; import { @@ -81,6 +83,31 @@ const activityGroupOptions = computed(() => { })); }); +// Readonly display labels +const displayTactic = computed(() => { + if (!formData.value.mitre_tactic) return null; + const opt = availableTacticOptions.value.find( + (o) => o.value === formData.value.mitre_tactic, + ); + return opt?.label || formData.value.mitre_tactic; +}); + +const displayTechnique = computed(() => { + if (!formData.value.mitre_technique) return null; + const opt = availableTechniqueOptions.value.find( + (o) => o.value === formData.value.mitre_technique, + ); + return opt?.label || formData.value.mitre_technique; +}); + +const displayGroup = computed(() => { + if (!formData.value.activity_group_id) return null; + const opt = activityGroupOptions.value.find( + (o) => o.value === formData.value.activity_group_id, + ); + return opt?.label || formData.value.activity_group_id; +}); + // MITRE change handlers function handleTacticChange(newTacticId: string | null) { formData.value.mitre_tactic = newTacticId || undefined; @@ -212,67 +239,80 @@ onUnmounted(() => {
- + +
-
- - -
-
- - -
+ +
-
- - -
-
- - -
+ +
@@ -308,49 +348,70 @@ onUnmounted(() => {
- -
-
- - -
-
- - -
-
- - -
-
- - + +
+ +
@@ -363,25 +424,38 @@ onUnmounted(() => {
- -
-
- - + +
+ +
- + +import { Label } from '@/components/ui/label'; + +withDefaults(defineProps<{ + label?: string; + modelValue?: string | null; +}>(), { + modelValue: null, +}); + + + From 86489bcc3cd730e40f20fafbdf028d03eb1ec0c9 Mon Sep 17 00:00:00 2001 From: fxai Date: Mon, 4 May 2026 08:24:32 +0200 Subject: [PATCH 2/6] fix(frontend): remove hover effect for blue and spectator on markdown fields --- frontend/src/components/ui/MarkdownEditor.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ui/MarkdownEditor.vue b/frontend/src/components/ui/MarkdownEditor.vue index ce8739d..86e5d86 100644 --- a/frontend/src/components/ui/MarkdownEditor.vue +++ b/frontend/src/components/ui/MarkdownEditor.vue @@ -225,8 +225,8 @@ const handlePaste = async (event: ClipboardEvent) => {
From 29340f7b242726d9f715bed8fcbefa53ce42971c Mon Sep 17 00:00:00 2001 From: fxai Date: Mon, 4 May 2026 08:32:39 +0200 Subject: [PATCH 3/6] fix(frontend): changed edit and preview layout in markdown field --- frontend/src/components/ui/MarkdownEditor.vue | 115 +++++++++--------- 1 file changed, 60 insertions(+), 55 deletions(-) diff --git a/frontend/src/components/ui/MarkdownEditor.vue b/frontend/src/components/ui/MarkdownEditor.vue index 86e5d86..1d43196 100644 --- a/frontend/src/components/ui/MarkdownEditor.vue +++ b/frontend/src/components/ui/MarkdownEditor.vue @@ -2,7 +2,6 @@ import { ref, computed, nextTick, watch, onMounted, onUnmounted, type ComponentPublicInstance } from 'vue'; import { marked } from 'marked'; import DOMPurify from 'dompurify'; -import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Loader2, Eye, Pencil, Upload } from 'lucide-vue-next'; import { useVModel } from '@vueuse/core'; @@ -198,65 +197,71 @@ const handlePaste = async (event: ClipboardEvent) => {