diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index 458082b37cf2..349aa82618b4 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -278,6 +278,7 @@ class CaseRead(CaseBase): tags: Optional[List[TagRead]] = [] ticket: Optional[TicketRead] = None triage_at: Optional[datetime] = None + updated_at: Optional[datetime] = None workflow_instances: Optional[List[WorkflowInstanceRead]] = [] diff --git a/src/dispatch/static/dispatch/components.d.ts b/src/dispatch/static/dispatch/components.d.ts index 499299563ebe..d4e8a7a4bb8a 100644 --- a/src/dispatch/static/dispatch/components.d.ts +++ b/src/dispatch/static/dispatch/components.d.ts @@ -21,8 +21,11 @@ declare module '@vue/runtime-core' { DateTimePickerMenu: typeof import('./src/components/DateTimePickerMenu.vue')['default'] DateWindowInput: typeof import('./src/components/DateWindowInput.vue')['default'] DefaultLayout: typeof import('./src/components/layouts/DefaultLayout.vue')['default'] + DMenu: typeof import('./src/components/DMenu.vue')['default'] + DTooltip: typeof import('./src/components/DTooltip.vue')['default'] InfoWidget: typeof import('./src/components/InfoWidget.vue')['default'] Loading: typeof import('./src/components/Loading.vue')['default'] + LockButton: typeof import('./src/components/LockButton.vue')['default'] MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default'] NotificationSnackbarsWrapper: typeof import('./src/components/NotificationSnackbarsWrapper.vue')['default'] PageHeader: typeof import('./src/components/PageHeader.vue')['default'] @@ -30,12 +33,16 @@ declare module '@vue/runtime-core' { ParticipantSelect: typeof import('./src/components/ParticipantSelect.vue')['default'] ProjectAutoComplete: typeof import('./src/components/ProjectAutoComplete.vue')['default'] Refresh: typeof import('./src/components/Refresh.vue')['default'] + RichEditor: typeof import('./src/components/RichEditor.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SavingState: typeof import('./src/components/SavingState.vue')['default'] + SearchPopover: typeof import('./src/components/SearchPopover.vue')['default'] SettingsBreadcrumbs: typeof import('./src/components/SettingsBreadcrumbs.vue')['default'] ShepherdStep: typeof import('./src/components/ShepherdStep.vue')['default'] ShpherdStep: typeof import('./src/components/ShpherdStep.vue')['default'] StatWidget: typeof import('./src/components/StatWidget.vue')['default'] + SubjectLastUpdated: typeof import('./src/components/SubjectLastUpdated.vue')['default'] VAlert: typeof import('vuetify/lib')['VAlert'] VApp: typeof import('vuetify/lib')['VApp'] VAppBar: typeof import('vuetify/lib')['VAppBar'] diff --git a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue index 5f96ec50bd2e..2e81cf7cddf1 100644 --- a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue +++ b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue @@ -8,6 +8,7 @@ import CaseSeveritySearchPopover from "@/case/severity/CaseSeveritySearchPopover import CaseTypeSearchPopover from "@/case/type/CaseTypeSearchPopover.vue" import ParticipantSearchPopover from "@/participant/ParticipantSearchPopover.vue" import ProjectSearchPopover from "@/project/ProjectSearchPopover.vue" +import { useSavingState } from "@/composables/useSavingState" // Define the props const props = defineProps({ @@ -25,7 +26,7 @@ const props = defineProps({ // Define the emits const emit = defineEmits(["update:modelValue", "update:open"]) const drawerVisible = ref(props.open) -// Create a local state for modelValue +const { setSaving } = useSavingState() const modelValue = ref({ ...props.modelValue }) watch( @@ -52,7 +53,9 @@ const handleResolutionUpdate = (newResolution) => { const saveCaseDetails = async () => { try { + setSaving(true) await CaseApi.update(modelValue.value.id, modelValue.value) + setSaving(false) } catch (e) { console.error("Failed to save case details", e) } diff --git a/src/dispatch/static/dispatch/src/case/CaseResolutionSearchPopover.vue b/src/dispatch/static/dispatch/src/case/CaseResolutionSearchPopover.vue index b10fb0d2fa65..d826a1ac7c8d 100644 --- a/src/dispatch/static/dispatch/src/case/CaseResolutionSearchPopover.vue +++ b/src/dispatch/static/dispatch/src/case/CaseResolutionSearchPopover.vue @@ -4,12 +4,13 @@ import type { Ref } from "vue" import CaseApi from "@/case/api" import SearchPopover from "@/components/SearchPopover.vue" +import { useSavingState } from "@/composables/useSavingState" import { useStore } from "vuex" defineProps<{ caseResolution: string }>() const store = useStore() - +const { setSaving } = useSavingState() const caseResolutions: Ref = ref([ "False Positive", "User Acknowledged", @@ -22,7 +23,9 @@ const selectCaseResolution = async (caseResolutionName: string) => { const caseDetails = store.state.case_management.selected caseDetails.resolution_reason = caseResolutionName + setSaving(true) await CaseApi.update(caseDetails.id, caseDetails) + setSaving(false) } diff --git a/src/dispatch/static/dispatch/src/case/Page.vue b/src/dispatch/static/dispatch/src/case/Page.vue index 88d00a69a206..63d338d480e4 100644 --- a/src/dispatch/static/dispatch/src/case/Page.vue +++ b/src/dispatch/static/dispatch/src/case/Page.vue @@ -4,6 +4,7 @@ :case-name="caseDetails.name" :case-visibility="caseDetails.visibility" :case-status="caseDetails.status" + :case-updated-at="caseDetails.updated_at" :is-drawer-open="isDrawerOpen" @toggle-drawer="toggleDrawer" /> @@ -39,6 +40,7 @@ import PageHeader from "@/case//PageHeader.vue" import CaseTabs from "@/case/CaseTabs.vue" import RichEditor from "@/components/RichEditor.vue" import CaseStatusSelectGroup from "@/case/CaseStatusSelectGroup.vue" +import { useSavingState } from "@/composables/useSavingState" const route = useRoute() const store = useStore() @@ -72,6 +74,7 @@ const caseDefaults = { tags: [], ticket: null, triage_at: null, + updated_at: null, visibility: "", conversation: null, workflow_instances: null, @@ -80,6 +83,7 @@ const caseDefaults = { const caseDetails = ref(caseDefaults) const loading = ref(false) const isDrawerOpen = ref(true) +const { setSaving } = useSavingState() const toggleDrawer = () => { isDrawerOpen.value = !isDrawerOpen.value @@ -115,7 +119,9 @@ const handleDescriptionUpdate = (newDescription) => { const saveCaseDetails = async () => { try { + setSaving(true) await CaseApi.update(caseDetails.value.id, caseDetails.value) + setSaving(false) } catch (e) { console.error("Failed to save case details", e) } diff --git a/src/dispatch/static/dispatch/src/case/PageHeader.vue b/src/dispatch/static/dispatch/src/case/PageHeader.vue index 5e7e52905ffc..965102f2ec42 100644 --- a/src/dispatch/static/dispatch/src/case/PageHeader.vue +++ b/src/dispatch/static/dispatch/src/case/PageHeader.vue @@ -5,6 +5,8 @@ + +