11<template lang="pug">
2- .column.no-wrap.fit ( style ='height: 100%; overflow: hidden; ' )
2+ .column.no-wrap.fit ( style ='height: 100%;' )
33 div( style ='position: sticky; top: 0; z-index: 1; flex: 0 0 auto;' )
44 q-toolbar( :class ='[$q.dark.isActive ? "bg-dark" : "bg-white"]' )
55 q-toolbar-title Historique des cycles de vie
5353 @click.stop ="openLifecycleDetails(lifecycle)"
5454 )
5555 q-tooltip.text-body2 ( anchor ='top middle' self ="bottom middle" ) Voir le détail de l'événement
56+ q-slide-transition
57+ q-card.q-mt-sm ( v-if ="selectedLifecycleId === lifecycle?._id" flat bordered )
58+ q-toolbar( bordered dense style ="height: 28px; line-height: 28px;" )
59+ q-toolbar-title Détails événement cycle de vie
60+ q-space
61+ q-btn( icon ="mdi-close" flat round dense @click ="closeLifecycleDetails" aria-label ="Fermer" )
62+ q-bar.bg-transparent ( dense style ="height: 28px; line-height: 28px;" )
63+ q-chip(
64+ v-if ="lifecycleDetailsMeta.lifecycleLabel"
65+ dense
66+ size ="sm"
67+ :color ="lifecycleDetailsMeta.lifecycleColor"
68+ :text-color ="lifecycleDetailsMeta.lifecycleTextColor"
69+ :icon ="lifecycleDetailsMeta.lifecycleIcon"
70+ )
71+ span {{ lifecycleDetailsMeta.lifecycleLabel }}
72+ q-space
73+ .text-caption.text-grey-7
74+ span( v-if ="lifecycleDetailsMeta.actor" ) Par: {{ lifecycleDetailsMeta.actor }}
75+ span( v-if ="lifecycleDetailsMeta.actor && lifecycleDetailsMeta.date" ) -
76+ span( v-if ="lifecycleDetailsMeta.date" ) Le: {{ lifecycleDetailsMeta.date }}
77+ q-separator
78+ .q-pa-md.row.justify-center ( v-if ="lifecycleDetailsLoading" )
79+ q-spinner-dots( color ="primary" size ="32px" )
80+ q-banner.bg-warning.text-black ( v-else-if ="!lifecycleDetailsJson" )
81+ | Aucun détail exploitable trouvé pour cet événement.
82+ .audit-diff-editor ( v-else )
83+ LazyMonacoEditor(
84+ style ="height: 45vh; width: 100%"
85+ lang ="json"
86+ :model-value ="lifecycleDetailsJson"
87+ :options ="monacoOptions"
88+ )
5689 q-timeline-entry(
5790 :key ="`${group.key}-end-dot`"
5891 color ="grey-5"
5992 class ="lifecycle-end-entry"
6093 )
6194 q-banner.text-negative.text-center ( v-if ="empty" dense icon ="mdi-flag-off" class ="q-my-md" )
6295 | Fin de la liste ({{ lifecycles.length }} événement{{ lifecycles.length > 1 ? 's' : '' }}).
63-
64- q-dialog( v-model ="lifecycleDetailsDialogOpen" maximized )
65- q-card.audit-diff-card
66- q-toolbar.bg-orange-8.text-white ( dense style ="height: 28px; line-height: 28px;" )
67- q-toolbar-title Détails événement cycle de vie
68- q-space
69- q-btn( icon ="mdi-close" flat round dense v-close-popup aria-label ="Fermer" )
70- q-bar.bg-transparent ( dense style ="height: 28px; line-height: 28px;" )
71- q-chip(
72- v-if ="lifecycleDetailsMeta.lifecycleLabel"
73- dense
74- size ="sm"
75- :color ="lifecycleDetailsMeta.lifecycleColor"
76- :text-color ="lifecycleDetailsMeta.lifecycleTextColor"
77- :icon ="lifecycleDetailsMeta.lifecycleIcon"
78- )
79- span {{ lifecycleDetailsMeta.lifecycleLabel }}
80- q-space
81- .text-caption.text-grey-7
82- span( v-if ="lifecycleDetailsMeta.actor" ) Par: {{ lifecycleDetailsMeta.actor }}
83- span( v-if ="lifecycleDetailsMeta.actor && lifecycleDetailsMeta.date" ) -
84- span( v-if ="lifecycleDetailsMeta.date" ) Le: {{ lifecycleDetailsMeta.date }}
85- q-separator
86- .q-pa-md.row.justify-center ( v-if ="lifecycleDetailsLoading" )
87- q-spinner-dots( color ="primary" size ="40px" )
88- q-banner.bg-warning.text-black ( v-if ="!lifecycleDetailsLoading && !lifecycleDetailsJson" )
89- | Aucun détail exploitable trouvé pour cet événement.
90- .audit-diff-editor ( v-else-if ="!lifecycleDetailsLoading" )
91- LazyMonacoEditor(
92- style ="height: 100%; width: 100%"
93- lang ="json"
94- :model-value ="lifecycleDetailsJson"
95- :options ="monacoOptions"
96- )
9796</template >
9897
9998<script lang="ts">
@@ -109,6 +108,8 @@ export default defineNuxtComponent({
109108 { label: ' Mois' , value: ' MM/YYYY' },
110109 { label: ' Année' , value: ' YYYY' },
111110 ],
111+ editorJsonCache: new WeakMap <object , string >(),
112+ maxLifecycleEditorChars: 30000 ,
112113 }
113114 },
114115 async setup() {
@@ -143,16 +144,9 @@ export default defineNuxtComponent({
143144 const lifecycles = ref <any >([])
144145
145146 const lifecycleDetails = ref <any >(null )
146- const lifecycleDetailsDialogOpen = ref ( false )
147+ const selectedLifecycleId = ref < string | null >( null )
147148 const lifecycleDetailsLoading = ref (false )
148- const lifecycleDetailsJson = computed (() => {
149- if (! lifecycleDetails .value ) return ' '
150- try {
151- return JSON .stringify (lifecycleDetails .value , null , 2 )
152- } catch {
153- return ' '
154- }
155- })
149+ const lifecycleDetailsJson = ref (' ' )
156150 const lifecycleDetailsMeta = reactive ({
157151 actor: ' ' ,
158152 date: ' ' ,
@@ -280,7 +274,7 @@ export default defineNuxtComponent({
280274 getLifecycleTimelineIconStyle ,
281275 monacoOptions ,
282276 lifecycleDetails ,
283- lifecycleDetailsDialogOpen ,
277+ selectedLifecycleId ,
284278 lifecycleDetailsLoading ,
285279 lifecycleDetailsJson ,
286280 lifecycleDetailsMeta ,
@@ -317,13 +311,18 @@ export default defineNuxtComponent({
317311 done (true )
318312 }
319313 },
320- openLifecycleDetails(lifecycle : any ) {
314+ async openLifecycleDetails(lifecycle : any ) {
315+ if (this .selectedLifecycleId === lifecycle ?._id ) {
316+ this .closeLifecycleDetails ()
317+ return
318+ }
321319 this .lifecycleDetails = lifecycle
322- this .lifecycleDetailsDialogOpen = true
320+ this .selectedLifecycleId = lifecycle ?. _id || null
323321 this .lifecycleDetailsLoading = true
324322
325323 try {
326324 const lifecycleInfos = lifecycle ?.lifecycle ? this .getLifecycleInfos (lifecycle .lifecycle ) : null
325+ this .lifecycleDetailsJson = this .stringifyForEditor (this .buildLifecycleDetailsPayload (lifecycle ))
327326
328327 this .lifecycleDetailsMeta .actor = this .getLifecycleActor (lifecycle )
329328 this .lifecycleDetailsMeta .date = this .formatLifecycleDate (lifecycle )
@@ -348,6 +347,94 @@ export default defineNuxtComponent({
348347 this .lifecycleDetailsLoading = false
349348 }
350349 },
350+ closeLifecycleDetails() {
351+ this .selectedLifecycleId = null
352+ this .lifecycleDetails = null
353+ this .lifecycleDetailsJson = ' '
354+ },
355+ buildLifecycleDetailsPayload(lifecycle : any ) {
356+ return lifecycle
357+ },
358+ stringifyForEditor(payload : unknown ): string {
359+ if (payload && typeof payload === ' object' ) {
360+ const cached = this .editorJsonCache .get (payload as object )
361+ if (cached ) {
362+ return cached
363+ }
364+ }
365+
366+ let value = ' '
367+ try {
368+ const seen = new WeakSet <object >()
369+ const maxDepth = 4
370+ const maxArrayItems = 50
371+ const maxObjectKeys = 60
372+ const maxStringLength = 1200
373+
374+ const sanitize = (input : unknown , depth : number ): unknown => {
375+ if (input === null || input === undefined ) return input
376+
377+ const inputType = typeof input
378+ if (inputType === ' string' ) {
379+ const text = input as string
380+ return text .length > maxStringLength ? ` ${text .slice (0 , maxStringLength )}...[truncated:${text .length - maxStringLength }] ` : text
381+ }
382+ if (inputType === ' number' || inputType === ' boolean' ) return input
383+ if (inputType !== ' object' ) return String (input )
384+
385+ const obj = input as Record <string , unknown >
386+ if (seen .has (obj )) {
387+ return ' [Circular]'
388+ }
389+ if (depth >= maxDepth ) {
390+ return ' [MaxDepthReached]'
391+ }
392+ seen .add (obj )
393+
394+ if (Array .isArray (obj )) {
395+ const items = obj .slice (0 , maxArrayItems ).map ((item ) => sanitize (item , depth + 1 ))
396+ if (obj .length > maxArrayItems ) {
397+ items .push (` [ArrayTruncated:${obj .length - maxArrayItems }] ` )
398+ }
399+ return items
400+ }
401+
402+ const entries = Object .entries (obj )
403+ const limitedEntries = entries .slice (0 , maxObjectKeys )
404+ const out: Record <string , unknown > = {}
405+
406+ limitedEntries .forEach (([key , val ]) => {
407+ out [key ] = sanitize (val , depth + 1 )
408+ })
409+
410+ if (entries .length > maxObjectKeys ) {
411+ out .__truncatedKeys = entries .length - maxObjectKeys
412+ }
413+
414+ return out
415+ }
416+
417+ value = JSON .stringify (sanitize (payload ?? null , 0 ), null , 2 ) ?? ' null'
418+ } catch {
419+ value = String (payload )
420+ }
421+
422+ if (value .length > this .maxLifecycleEditorChars ) {
423+ this .$q .notify ({
424+ message: ` Contenu tronque a ${this .maxLifecycleEditorChars } caracteres pour eviter un depassement memoire ` ,
425+ color: ' warning' ,
426+ icon: ' mdi-alert' ,
427+ position: ' top-right' ,
428+ })
429+ value = ` ${value .slice (0 , this .maxLifecycleEditorChars )}\n\n /* ... contenu tronque ... */ `
430+ }
431+
432+ if (payload && typeof payload === ' object' ) {
433+ this .editorJsonCache .set (payload as object , value )
434+ }
435+
436+ return value
437+ },
351438 },
352439})
353440 </script >
0 commit comments