@masterteam/audit-logs 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +1,87 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Component, Injectable, computed, viewChild, linkedSignal } from '@angular/core';
2
+ import { inject, ChangeDetectionStrategy, Component, Injectable, computed, input, output, signal, effect, viewChild, linkedSignal } from '@angular/core';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
3
4
  import { Router, RouterOutlet } from '@angular/router';
4
5
  import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
5
6
  import { Breadcrumb } from '@masterteam/components/breadcrumb';
6
7
  import { Page } from '@masterteam/components/page';
8
+ import * as i2 from '@angular/common';
7
9
  import { CommonModule } from '@angular/common';
10
+ import { Button } from '@masterteam/components/button';
11
+ import { Chip } from '@masterteam/components/chip';
12
+ import { StatisticCard } from '@masterteam/components/statistic-card';
8
13
  import { Table } from '@masterteam/components/table';
9
- import { SkeletonModule } from 'primeng/skeleton';
10
- import { Avatar } from '@masterteam/components/avatar';
11
- import { Action, Selector, State, Store, select } from '@ngxs/store';
12
14
  import { HttpClient, HttpContext } from '@angular/common/http';
13
- import { CrudStateBase, handleApiRequest, REQUEST_CONTEXT } from '@masterteam/components';
15
+ import { CrudStateBase, REQUEST_CONTEXT, handleApiRequest } from '@masterteam/components';
16
+ import { Action, Selector, State, Store, select } from '@ngxs/store';
17
+ import { Card } from '@masterteam/components/card';
18
+ import { Drawer } from '@masterteam/components/drawer';
19
+ import { EntityStatus, EntityUser } from '@masterteam/components/entities';
20
+ import { ModalService } from '@masterteam/components/modal';
21
+ import * as i1 from 'primeng/skeleton';
22
+ import { SkeletonModule } from 'primeng/skeleton';
14
23
 
15
24
  class AuditLogs {
16
25
  router = inject(Router);
17
26
  translocoService = inject(TranslocoService);
18
- breadcrumbItems = [
19
- {
20
- label: '',
21
- icon: 'general.home-line',
22
- routerLink: '/control-panel/workspaces',
23
- },
24
- {
25
- label: this.translocoService.translate('product-settings.product-settings'),
26
- routerLink: '/control-panel/product-settings',
27
- },
28
- { label: this.translocoService.translate('audit-logs.audit-logs') },
29
- ];
27
+ activeLang = toSignal(this.translocoService.langChanges$, {
28
+ initialValue: this.translocoService.getActiveLang(),
29
+ });
30
+ get breadcrumbItems() {
31
+ this.activeLang();
32
+ return [
33
+ {
34
+ label: '',
35
+ icon: 'general.home-line',
36
+ routerLink: '/control-panel/workspaces',
37
+ },
38
+ {
39
+ label: this.translocoService.translate('product-settings.product-settings'),
40
+ routerLink: '/control-panel/product-settings',
41
+ },
42
+ { label: this.translocoService.translate('audit-logs.audit-logs') },
43
+ ];
44
+ }
30
45
  goBack() {
31
46
  this.router.navigate(['control-panel/product-settings']);
32
47
  }
33
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogs, deps: [], target: i0.ɵɵFactoryTarget.Component });
34
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.3", type: AuditLogs, isStandalone: true, selector: "mt-audit-logs", ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <mt-page\r\n [title]=\"t('audit-logs.audit-logs')\"\r\n [avatarIcon]=\"'time.clock-fast-forward'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-lime-50)',\r\n '--p-avatar-color': 'var(--p-lime-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <h3 class=\"font-bold text-xl\">{{ t(\"audit-logs.audit-logs\") }}</h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'flex justify-start mx-1 mb-4'\"\r\n ></mt-breadcrumb>\r\n <router-outlet />\r\n </mt-page>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }] });
48
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogs, deps: [], target: i0.ɵɵFactoryTarget.Component });
49
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.8", type: AuditLogs, isStandalone: true, selector: "mt-audit-logs", ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <mt-page\r\n [title]=\"t('audit-logs.audit-logs')\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarIcon]=\"'time.clock-fast-forward'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-lime-50)',\r\n '--p-avatar-color': 'var(--p-lime-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex min-h-0 flex-col gap-4\">\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'mx-1 flex justify-start'\"\r\n />\r\n\r\n <router-outlet />\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35
50
  }
36
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogs, decorators: [{
51
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogs, decorators: [{
37
52
  type: Component,
38
- args: [{ selector: 'mt-audit-logs', imports: [Page, RouterOutlet, TranslocoDirective, Breadcrumb], template: "<ng-container *transloco=\"let t\">\r\n <mt-page\r\n [title]=\"t('audit-logs.audit-logs')\"\r\n [avatarIcon]=\"'time.clock-fast-forward'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-lime-50)',\r\n '--p-avatar-color': 'var(--p-lime-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <h3 class=\"font-bold text-xl\">{{ t(\"audit-logs.audit-logs\") }}</h3>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'flex justify-start mx-1 mb-4'\"\r\n ></mt-breadcrumb>\r\n <router-outlet />\r\n </mt-page>\r\n</ng-container>\r\n" }]
53
+ args: [{ selector: 'mt-audit-logs', imports: [Page, RouterOutlet, TranslocoDirective, Breadcrumb], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <mt-page\r\n [title]=\"t('audit-logs.audit-logs')\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarIcon]=\"'time.clock-fast-forward'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-lime-50)',\r\n '--p-avatar-color': 'var(--p-lime-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <div class=\"flex min-h-0 flex-col gap-4\">\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems\"\r\n [styleClass]=\"'mx-1 flex justify-start'\"\r\n />\r\n\r\n <router-outlet />\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"] }]
39
54
  }] });
40
55
 
41
56
  var AuditLogsActionKey;
42
57
  (function (AuditLogsActionKey) {
43
58
  AuditLogsActionKey["GetAuditLogs"] = "getAuditLogs";
59
+ AuditLogsActionKey["GetAuditLogDetail"] = "getAuditLogDetail";
44
60
  })(AuditLogsActionKey || (AuditLogsActionKey = {}));
45
61
 
46
62
  class GetAuditLogs {
47
63
  page;
48
64
  pageSize;
49
65
  filters;
50
- static type = '[AuditLogs] Get Audit Logs';
51
- constructor(page, pageSize, filters) {
66
+ static type = '[AuditLogs] Get Audit Trail Search';
67
+ constructor(page = 1, pageSize = 20, filters = {}) {
52
68
  this.page = page;
53
69
  this.pageSize = pageSize;
54
70
  this.filters = filters;
55
71
  }
56
72
  }
73
+ class GetAuditLogDetail {
74
+ eventId;
75
+ language;
76
+ static type = '[AuditLogs] Get Audit Trail Detail';
77
+ constructor(eventId, language) {
78
+ this.eventId = eventId;
79
+ this.language = language;
80
+ }
81
+ }
82
+ class ClearAuditLogDetail {
83
+ static type = '[AuditLogs] Clear Audit Trail Detail';
84
+ }
57
85
 
58
86
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
59
87
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -61,67 +89,135 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
61
89
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
62
90
  return c > 3 && r && Object.defineProperty(target, key, r), r;
63
91
  };
92
+ const DEFAULT_SEARCH_RESPONSE = {
93
+ totalCount: 0,
94
+ page: 1,
95
+ pageSize: 20,
96
+ items: [],
97
+ };
64
98
  let AuditLogsState = class AuditLogsState extends CrudStateBase {
65
99
  http = inject(HttpClient);
66
- // ============================================================================
67
- // Selectors - Individual data selectors for fine-grained reactivity
68
- // ============================================================================
100
+ context = new HttpContext().set(REQUEST_CONTEXT, {
101
+ useBaseUrl: false,
102
+ });
103
+ baseUrl = 'control-panel/audit-trail';
69
104
  static getAuditLogs(state) {
70
105
  return state.auditLogs;
71
106
  }
72
- static getPage(state) {
73
- return state.page;
107
+ static getFilters(state) {
108
+ return state.filters;
109
+ }
110
+ static getSelectedEventId(state) {
111
+ return state.selectedEventId;
74
112
  }
75
- static getPageSize(state) {
76
- return state.pageSize;
113
+ static getSelectedEventDetail(state) {
114
+ return state.selectedEventDetail;
77
115
  }
78
- // ============================================================================
79
- // Loading/Error Slice Selectors - REQUIRED for optimal performance
80
- // ============================================================================
81
116
  static getLoadingActive(state) {
82
117
  return state.loadingActive;
83
118
  }
84
119
  static getErrors(state) {
85
120
  return state.errors;
86
121
  }
87
- // ============================================================================
88
- // Actions
89
- // ============================================================================
90
122
  getAuditLogs(ctx, { page, pageSize, filters }) {
91
- const params = {
92
- page: page.toString(),
93
- pageSize: pageSize.toString(),
123
+ const query = this.buildQueryParams({
94
124
  ...filters,
95
- };
96
- const req$ = this.http.get(`app/audittrail`, {
97
- params: params,
125
+ page: String(page),
126
+ pageSize: String(pageSize),
98
127
  });
99
128
  return handleApiRequest({
100
129
  ctx,
101
130
  key: AuditLogsActionKey.GetAuditLogs,
102
- request$: req$,
131
+ request$: this.http.get(`${this.baseUrl}/search`, {
132
+ params: query,
133
+ context: this.context,
134
+ }),
103
135
  onSuccess: (response) => ({
104
- auditLogs: response.data ?? [],
105
- page,
106
- pageSize,
136
+ auditLogs: response.data
137
+ ? {
138
+ ...DEFAULT_SEARCH_RESPONSE,
139
+ ...response.data,
140
+ items: response.data.items ?? [],
141
+ }
142
+ : {
143
+ ...DEFAULT_SEARCH_RESPONSE,
144
+ page,
145
+ pageSize,
146
+ },
147
+ filters: this.sanitizeFilters(filters),
148
+ }),
149
+ onError: () => ({
150
+ filters: this.sanitizeFilters(filters),
107
151
  }),
108
152
  });
109
153
  }
110
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
111
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsState });
154
+ getAuditLogDetail(ctx, { eventId, language }) {
155
+ ctx.patchState({
156
+ selectedEventId: eventId,
157
+ selectedEventDetail: null,
158
+ });
159
+ return handleApiRequest({
160
+ ctx,
161
+ key: AuditLogsActionKey.GetAuditLogDetail,
162
+ request$: this.http.get(`${this.baseUrl}/events/${encodeURIComponent(eventId)}`, {
163
+ params: this.buildQueryParams(language ? { language } : undefined),
164
+ context: this.context,
165
+ }),
166
+ onSuccess: (response, state) => state.selectedEventId === eventId
167
+ ? {
168
+ selectedEventDetail: response.data ?? null,
169
+ }
170
+ : {},
171
+ onError: (_error, state) => state.selectedEventId === eventId
172
+ ? {
173
+ selectedEventDetail: null,
174
+ }
175
+ : {},
176
+ });
177
+ }
178
+ clearAuditLogDetail(ctx) {
179
+ ctx.patchState({
180
+ selectedEventId: null,
181
+ selectedEventDetail: null,
182
+ });
183
+ }
184
+ buildQueryParams(query) {
185
+ return Object.entries(query ?? {}).reduce((params, [key, value]) => {
186
+ if (value === null || value === undefined || value.trim() === '') {
187
+ return params;
188
+ }
189
+ params[key] = value;
190
+ return params;
191
+ }, {});
192
+ }
193
+ sanitizeFilters(filters) {
194
+ const { language: _language, ...rest } = filters;
195
+ return rest;
196
+ }
197
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
198
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsState });
112
199
  };
113
200
  __decorate([
114
201
  Action(GetAuditLogs)
115
202
  ], AuditLogsState.prototype, "getAuditLogs", null);
203
+ __decorate([
204
+ Action(GetAuditLogDetail)
205
+ ], AuditLogsState.prototype, "getAuditLogDetail", null);
206
+ __decorate([
207
+ Action(ClearAuditLogDetail)
208
+ ], AuditLogsState.prototype, "clearAuditLogDetail", null);
116
209
  __decorate([
117
210
  Selector()
118
211
  ], AuditLogsState, "getAuditLogs", null);
119
212
  __decorate([
120
213
  Selector()
121
- ], AuditLogsState, "getPage", null);
214
+ ], AuditLogsState, "getFilters", null);
122
215
  __decorate([
123
216
  Selector()
124
- ], AuditLogsState, "getPageSize", null);
217
+ ], AuditLogsState, "getSelectedEventId", null);
218
+ __decorate([
219
+ Selector()
220
+ ], AuditLogsState, "getSelectedEventDetail", null);
125
221
  __decorate([
126
222
  Selector()
127
223
  ], AuditLogsState, "getLoadingActive", null);
@@ -132,53 +228,125 @@ AuditLogsState = __decorate([
132
228
  State({
133
229
  name: 'auditLogs',
134
230
  defaults: {
135
- auditLogs: { items: [] },
136
- page: 1,
137
- pageSize: 10,
231
+ auditLogs: DEFAULT_SEARCH_RESPONSE,
232
+ filters: {},
233
+ selectedEventId: null,
234
+ selectedEventDetail: null,
138
235
  loadingActive: [],
139
236
  errors: {},
140
237
  },
141
238
  })
142
239
  ], AuditLogsState);
143
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsState, decorators: [{
240
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsState, decorators: [{
144
241
  type: Injectable
145
- }], propDecorators: { getAuditLogs: [] } });
242
+ }], propDecorators: { getAuditLogs: [], getAuditLogDetail: [], clearAuditLogDetail: [] } });
146
243
 
147
244
  class AuditLogsFacade {
148
245
  store = inject(Store);
149
- // ============================================================================
150
- // Data Selectors - Memoized by NGXS (fine-grained reactivity)
151
- // ============================================================================
152
246
  auditLogs = select(AuditLogsState.getAuditLogs);
153
- page = select(AuditLogsState.getPage);
154
- pageSize = select(AuditLogsState.getPageSize);
155
- // ============================================================================
156
- // Loading/Error Slices - Memoized by NGXS
157
- // ============================================================================
247
+ filters = select(AuditLogsState.getFilters);
248
+ selectedEventId = select(AuditLogsState.getSelectedEventId);
249
+ selectedEventDetail = select(AuditLogsState.getSelectedEventDetail);
158
250
  loadingActive = select(AuditLogsState.getLoadingActive);
159
251
  errors = select(AuditLogsState.getErrors);
160
- // ============================================================================
161
- // Loading Signals - Computed from slice (minimal reactivity)
162
- // ============================================================================
163
- isLoadingAuditLogs = computed(() => this.loadingActive().includes(AuditLogsActionKey.GetAuditLogs), ...(ngDevMode ? [{ debugName: "isLoadingAuditLogs" }] : []));
164
- // ============================================================================
165
- // Error Signals - Computed from slice (minimal reactivity)
166
- // ============================================================================
167
- getAuditLogsError = computed(() => this.errors()[AuditLogsActionKey.GetAuditLogs] ?? null, ...(ngDevMode ? [{ debugName: "getAuditLogsError" }] : []));
168
- // ============================================================================
169
- // Action Dispatchers
170
- // ============================================================================
171
- getAuditLogs(page = 1, pageSize = 10, filters) {
252
+ isLoadingAuditLogs = computed(() => this.loadingActive().includes(AuditLogsActionKey.GetAuditLogs), ...(ngDevMode ? [{ debugName: "isLoadingAuditLogs" }] : /* istanbul ignore next */ []));
253
+ isLoadingAuditLogDetail = computed(() => this.loadingActive().includes(AuditLogsActionKey.GetAuditLogDetail), ...(ngDevMode ? [{ debugName: "isLoadingAuditLogDetail" }] : /* istanbul ignore next */ []));
254
+ getAuditLogsError = computed(() => this.errors()[AuditLogsActionKey.GetAuditLogs] ?? null, ...(ngDevMode ? [{ debugName: "getAuditLogsError" }] : /* istanbul ignore next */ []));
255
+ getAuditLogDetailError = computed(() => this.errors()[AuditLogsActionKey.GetAuditLogDetail] ?? null, ...(ngDevMode ? [{ debugName: "getAuditLogDetailError" }] : /* istanbul ignore next */ []));
256
+ activeFilterCount = computed(() => Object.values(this.filters()).filter((value) => value !== null && value !== undefined && value !== '').length, ...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
257
+ getAuditLogs(page = 1, pageSize = 20, filters = {}) {
172
258
  return this.store.dispatch(new GetAuditLogs(page, pageSize, filters));
173
259
  }
174
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
175
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsFacade, providedIn: 'root' });
260
+ getAuditLogDetail(eventId, language) {
261
+ return this.store.dispatch(new GetAuditLogDetail(eventId, language));
262
+ }
263
+ clearAuditLogDetail() {
264
+ return this.store.dispatch(new ClearAuditLogDetail());
265
+ }
266
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
267
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsFacade, providedIn: 'root' });
176
268
  }
177
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsFacade, decorators: [{
269
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsFacade, decorators: [{
178
270
  type: Injectable,
179
271
  args: [{ providedIn: 'root' }]
180
272
  }] });
181
273
 
274
+ const FALLBACK_STATUS_COLOR = '#475569';
275
+ const STATUS_COLOR_MAPS = {
276
+ category: {
277
+ Runtime: '#047857',
278
+ ControlPanel: '#0369a1',
279
+ Discussion: '#7c3aed',
280
+ Process: '#0f766e',
281
+ Escalation: '#c2410c',
282
+ PhaseGate: '#0e7490',
283
+ Governance: '#b45309',
284
+ System: '#475569',
285
+ },
286
+ operation: {
287
+ Create: '#047857',
288
+ Update: '#0369a1',
289
+ Delete: '#be123c',
290
+ Close: '#475569',
291
+ Move: '#b45309',
292
+ Progress: '#0e7490',
293
+ Configure: '#4338ca',
294
+ Comment: '#7c3aed',
295
+ BulkUpdate: '#1d4ed8',
296
+ Change: '#0369a1',
297
+ Unknown: '#475569',
298
+ },
299
+ importance: {
300
+ Low: '#475569',
301
+ Medium: '#0369a1',
302
+ High: '#b45309',
303
+ Critical: '#be123c',
304
+ },
305
+ surface: {
306
+ Runtime: '#047857',
307
+ ControlPanel: '#4338ca',
308
+ Background: '#b45309',
309
+ System: '#475569',
310
+ },
311
+ };
312
+ const STATUS_LABELS = {
313
+ category: 'Category',
314
+ operation: 'Operation',
315
+ importance: 'Importance',
316
+ surface: 'Surface',
317
+ };
318
+ function getAuditStatusMap(kind) {
319
+ return Object.fromEntries(Object.entries(STATUS_COLOR_MAPS[kind]).map(([key, color]) => [
320
+ key,
321
+ { color },
322
+ ]));
323
+ }
324
+ function getAuditStatusColor(kind, code) {
325
+ if (!code) {
326
+ return FALLBACK_STATUS_COLOR;
327
+ }
328
+ return STATUS_COLOR_MAPS[kind][code] ?? FALLBACK_STATUS_COLOR;
329
+ }
330
+ function getAuditStatusEntity(kind, item) {
331
+ const display = item?.label || item?.code;
332
+ const code = item?.code || display;
333
+ if (!display || !code) {
334
+ return null;
335
+ }
336
+ return {
337
+ name: STATUS_LABELS[kind],
338
+ viewType: 'Status',
339
+ value: {
340
+ key: code,
341
+ display,
342
+ color: getAuditStatusColor(kind, code),
343
+ },
344
+ configuration: {
345
+ hideName: true,
346
+ },
347
+ };
348
+ }
349
+
182
350
  /**
183
351
  * Recursively normalizes Date objects to YYYY-MM-DDTHH:mm:ss format.
184
352
  * Prevents UTC timezone leaks by converting dates to local datetime strings.
@@ -210,85 +378,422 @@ function normalizeDates(value) {
210
378
  return value;
211
379
  }
212
380
 
381
+ class AuditLogDetailDrawer {
382
+ modal = inject(ModalService);
383
+ visible = input(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
384
+ detail = input(null, ...(ngDevMode ? [{ debugName: "detail" }] : /* istanbul ignore next */ []));
385
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
386
+ error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
387
+ visibleChange = output();
388
+ expandedEvidence = signal({}, ...(ngDevMode ? [{ debugName: "expandedEvidence" }] : /* istanbul ignore next */ []));
389
+ drawerTitle = computed(() => this.detail()?.summary ?? '', ...(ngDevMode ? [{ debugName: "drawerTitle" }] : /* istanbul ignore next */ []));
390
+ constructor() {
391
+ effect(() => {
392
+ this.detail();
393
+ this.expandedEvidence.set({});
394
+ });
395
+ }
396
+ onDrawerVisibleChange(visible) {
397
+ this.visibleChange.emit(visible);
398
+ }
399
+ getStatusEntity(kind, item) {
400
+ return getAuditStatusEntity(kind, item);
401
+ }
402
+ isEmptyValue(value) {
403
+ return value === null || value === undefined || value === '';
404
+ }
405
+ formatValue(value, valueKind) {
406
+ if (this.isEmptyValue(value)) {
407
+ return '';
408
+ }
409
+ if (this.isJsonValue(valueKind, value)) {
410
+ return this.formatJson(value);
411
+ }
412
+ if (valueKind === 'Boolean') {
413
+ if (value === 'true') {
414
+ return 'True';
415
+ }
416
+ if (value === 'false') {
417
+ return 'False';
418
+ }
419
+ }
420
+ return value ?? '';
421
+ }
422
+ isJsonValue(valueKind, value) {
423
+ if (valueKind === 'Json') {
424
+ return true;
425
+ }
426
+ const trimmed = value?.trim();
427
+ return !!trimmed && (trimmed.startsWith('{') || trimmed.startsWith('['));
428
+ }
429
+ formatJson(value) {
430
+ if (!value) {
431
+ return '';
432
+ }
433
+ try {
434
+ return JSON.stringify(JSON.parse(value), null, 2);
435
+ }
436
+ catch {
437
+ return value;
438
+ }
439
+ }
440
+ getFieldLabel(change) {
441
+ return change.fieldLabel || this.humanizeKey(change.fieldKey);
442
+ }
443
+ getChangeSource(change) {
444
+ return change.changeSource ? this.humanizeKey(change.changeSource) : null;
445
+ }
446
+ getSubjectLabel(subject) {
447
+ return (subject.label ||
448
+ `${this.humanizeKey(subject.subjectType)} ${subject.subjectId}`.trim());
449
+ }
450
+ getActorUserEntity(value) {
451
+ if (!value) {
452
+ return null;
453
+ }
454
+ return {
455
+ viewType: 'User',
456
+ value,
457
+ };
458
+ }
459
+ isEvidenceExpanded(index) {
460
+ return !!this.expandedEvidence()[String(index)];
461
+ }
462
+ toggleEvidence(index) {
463
+ const key = String(index);
464
+ this.expandedEvidence.update((state) => ({
465
+ ...state,
466
+ [key]: !state[key],
467
+ }));
468
+ }
469
+ humanizeKey(value) {
470
+ if (!value) {
471
+ return '';
472
+ }
473
+ return value
474
+ .replace(/[_-]+/g, ' ')
475
+ .replace(/\s+/g, ' ')
476
+ .trim()
477
+ .replace(/\b\w/g, (char) => char.toUpperCase());
478
+ }
479
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
480
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: AuditLogDetailDrawer, isStandalone: true, selector: "mt-audit-log-detail-drawer", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, detail: { classPropertyName: "detail", publicName: "detail", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visibleChange: "visibleChange" }, host: { classAttribute: "block h-full" }, ngImport: i0, template: "<mt-drawer\r\n [visible]=\"visible()\"\r\n [title]=\"drawerTitle()\"\r\n [loadingHeader]=\"loading()\"\r\n styleClass=\"mt-audit-log-drawer !absolute !w-[90%] xl:!w-[68rem]\"\r\n appendTo=\"page-content\"\r\n position=\"right\"\r\n (visibleChange)=\"onDrawerVisibleChange($event)\"\r\n *transloco=\"let t; prefix: 'audit-logs'\"\r\n>\r\n <ng-container content>\r\n @if (loading()) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <div class=\"flex flex-col gap-5\">\r\n <div class=\"flex flex-wrap gap-3\">\r\n @for (_ of [1, 2, 3, 4]; track $index) {\r\n <p-skeleton width=\"7rem\" height=\"2rem\" borderRadius=\"0.5rem\" />\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-5 xl:grid-cols-[minmax(0,1.35fr)_minmax(18rem,0.85fr)]\"\r\n >\r\n <div class=\"flex flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.change-groups')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"12rem\" class=\"rounded-lg\" />\r\n <p-skeleton height=\"14rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.evidence')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"9rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.request-context')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"3rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.subjects')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"8rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n } @else if (error(); as errorMessage) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <mt-card class=\"min-h-[28rem]\">\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center p-6 text-center\"\r\n >\r\n <p class=\"max-w-2xl text-sm font-medium text-rose-700\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n </mt-card>\r\n </div>\r\n } @else if (detail(); as event) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <div class=\"flex flex-col gap-5\">\r\n <div\r\n class=\"grid gap-5 xl:grid-cols-[minmax(0,1.35fr)_minmax(18rem,0.85fr)]\"\r\n >\r\n <div class=\"flex min-w-0 flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.change-groups')\">\r\n @if (event.changeGroups.length > 0) {\r\n @let hasMultipleChangeGroups = event.changeGroups.length > 1;\r\n <div class=\"flex flex-col gap-5\">\r\n @for (group of event.changeGroups; track group.key) {\r\n @if (hasMultipleChangeGroups) {\r\n <div\r\n class=\"overflow-hidden rounded-xl border border-surface-200\"\r\n >\r\n <div class=\"border-b border-surface-200 px-4 py-3\">\r\n <h5 class=\"font-semibold text-surface-900\">\r\n {{ group.label || humanizeKey(group.key) }}\r\n </h5>\r\n </div>\r\n\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n change of group.changes;\r\n track change.fieldKey + \"-\" + $index\r\n ) {\r\n <div class=\"flex flex-col gap-3 px-4 py-4\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-2\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getFieldLabel(change) }}\r\n </span>\r\n\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n @if (\r\n getChangeSource(change);\r\n as changeSource\r\n ) {\r\n <span>\r\n {{ t(\"drawer.change-source\") }}:\r\n {{ changeSource }}\r\n </span>\r\n }\r\n\r\n <span>\r\n {{ t(\"drawer.value-kind\") }}:\r\n {{ change.valueKind }}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]\"\r\n >\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.from\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.oldValue)) {\r\n <span class=\"text-sm text-surface-400\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.oldValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex items-center justify-center text-xl text-surface-300\"\r\n >\r\n -&gt;\r\n </div>\r\n\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-0 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.to\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.newValue)) {\r\n <span class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.newValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-4\">\r\n @if (group.label || group.key) {\r\n <h5 class=\"font-semibold text-surface-900\">\r\n {{ group.label || humanizeKey(group.key) }}\r\n </h5>\r\n }\r\n\r\n <div class=\"flex flex-col gap-5\">\r\n @for (\r\n change of group.changes;\r\n track change.fieldKey + \"-\" + $index\r\n ) {\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-2\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getFieldLabel(change) }}\r\n </span>\r\n\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n @if (\r\n getChangeSource(change);\r\n as changeSource\r\n ) {\r\n <span>\r\n {{ t(\"drawer.change-source\") }}:\r\n {{ changeSource }}\r\n </span>\r\n }\r\n\r\n <span>\r\n {{ t(\"drawer.value-kind\") }}:\r\n {{ change.valueKind }}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]\"\r\n >\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.from\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.oldValue)) {\r\n <span class=\"text-sm text-surface-400\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.oldValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex items-center justify-center text-xl text-surface-300\"\r\n >\r\n -&gt;\r\n </div>\r\n\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-0 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.to\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.newValue)) {\r\n <span class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.newValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-changes\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n <div class=\"flex min-w-0 flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.request-context')\">\r\n <dl class=\"grid gap-4\">\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"drawer.actor\") }}\r\n </dt>\r\n <dd class=\"text-sm text-surface-800\">\r\n @if (\r\n getActorUserEntity(event.requestContext.actorUser);\r\n as actorUserEntity\r\n ) {\r\n <mt-entity-user [data]=\"actorUserEntity\" />\r\n } @else {\r\n <span class=\"break-all\">\r\n {{ event.requestContext.actorUserId || \"system\" }}\r\n </span>\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"drawer.occurred-at\") }}\r\n </dt>\r\n <dd class=\"text-sm text-surface-800\">\r\n {{ event.requestContext.occurredAtUtc | date: \"medium\" }}\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"category\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"category\", event.category);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"operation\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"operation\", event.operation);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"importance\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"importance\", event.importance);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"surface\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"surface\", event.surface);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n </dl>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.subjects')\">\r\n @if (event.subjects.length > 0) {\r\n <div\r\n class=\"overflow-hidden rounded-xl border border-surface-200\"\r\n >\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n subject of event.subjects;\r\n track subject.subjectType + \"-\" + subject.subjectId\r\n ) {\r\n <div class=\"flex flex-col gap-3 px-4 py-4\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-3\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getSubjectLabel(subject) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ humanizeKey(subject.subjectType) }}\r\n </span>\r\n </div>\r\n\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1 text-xs font-medium text-surface-700\"\r\n >\r\n {{ humanizeKey(subject.relation) }}\r\n </span>\r\n </div>\r\n\r\n <div\r\n class=\"flex flex-wrap gap-2 text-xs text-surface-500\"\r\n >\r\n <span class=\"rounded-full bg-surface-100 px-3 py-1\">\r\n {{ t(\"drawer.scope\") }}: {{ subject.subjectId }}\r\n </span>\r\n @if (\r\n subject.levelId !== null &&\r\n subject.levelId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.level-id\") }}:\r\n {{ subject.levelId }}\r\n </span>\r\n }\r\n @if (\r\n subject.levelDataId !== null &&\r\n subject.levelDataId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.level-data-id\") }}:\r\n {{ subject.levelDataId }}\r\n </span>\r\n }\r\n @if (\r\n subject.moduleDataId !== null &&\r\n subject.moduleDataId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.module-data-id\") }}:\r\n {{ subject.moduleDataId }}\r\n </span>\r\n }\r\n @if (subject.moduleKey) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.module-key\") }}:\r\n {{ subject.moduleKey }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-subjects\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <mt-card [title]=\"t('drawer.evidence')\">\r\n <p class=\"mb-4 text-sm text-surface-500\">\r\n {{ t(\"drawer.advanced-description\") }}\r\n </p>\r\n\r\n @if (event.evidence.length > 0) {\r\n <div class=\"overflow-hidden rounded-xl border border-surface-200\">\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n evidence of event.evidence;\r\n track evidence.type + \"-\" + $index\r\n ) {\r\n <section>\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-3 px-4 py-3\"\r\n >\r\n <span class=\"font-medium text-surface-900\">\r\n {{ evidence.type || t(\"drawer.technical-evidence\") }}\r\n </span>\r\n\r\n <mt-button\r\n [label]=\"t('drawer.view-json')\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n size=\"small\"\r\n (onClick)=\"toggleEvidence($index)\"\r\n />\r\n </div>\r\n\r\n @if (isEvidenceExpanded($index)) {\r\n <pre class=\"audit-json-panel\">{{\r\n formatJson(evidence.contentJson)\r\n }}</pre>\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-evidence\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n </div>\r\n } @else {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <mt-card class=\"min-h-[28rem]\">\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center p-6 text-center\"\r\n >\r\n <p class=\"max-w-2xl text-sm text-surface-500\">\r\n {{ t(\"drawer.no-event-selected\") }}\r\n </p>\r\n </div>\r\n </mt-card>\r\n </div>\r\n }\r\n </ng-container>\r\n</mt-drawer>\r\n", styles: [":host{display:block}.audit-json-panel,.audit-json-value{margin:0;overflow-x:auto;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.audit-json-panel{border-top:1px solid var(--p-surface-100);background:color-mix(in srgb,var(--p-surface-50) 80%,white);padding:1rem;font-size:.8rem;line-height:1.55}.audit-json-value{font-size:.78rem;line-height:1.55;color:var(--p-surface-700)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Drawer, selector: "mt-drawer", inputs: ["visible", "position", "fullScreen", "closeOnEscape", "blockScroll", "dismissible", "title", "subtitle", "loadingHeader", "styleClass", "transitionOptions", "appendTo", "modal"], outputs: ["visibleChange", "onShow", "onHide"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EntityStatus, selector: "mt-entity-status", inputs: ["data", "name", "value"] }, { kind: "component", type: EntityUser, selector: "mt-entity-user", inputs: ["data"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
481
+ }
482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogDetailDrawer, decorators: [{
483
+ type: Component,
484
+ args: [{ selector: 'mt-audit-log-detail-drawer', imports: [
485
+ CommonModule,
486
+ TranslocoDirective,
487
+ Drawer,
488
+ Button,
489
+ Card,
490
+ EntityStatus,
491
+ EntityUser,
492
+ SkeletonModule,
493
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
494
+ class: 'block h-full',
495
+ }, template: "<mt-drawer\r\n [visible]=\"visible()\"\r\n [title]=\"drawerTitle()\"\r\n [loadingHeader]=\"loading()\"\r\n styleClass=\"mt-audit-log-drawer !absolute !w-[90%] xl:!w-[68rem]\"\r\n appendTo=\"page-content\"\r\n position=\"right\"\r\n (visibleChange)=\"onDrawerVisibleChange($event)\"\r\n *transloco=\"let t; prefix: 'audit-logs'\"\r\n>\r\n <ng-container content>\r\n @if (loading()) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <div class=\"flex flex-col gap-5\">\r\n <div class=\"flex flex-wrap gap-3\">\r\n @for (_ of [1, 2, 3, 4]; track $index) {\r\n <p-skeleton width=\"7rem\" height=\"2rem\" borderRadius=\"0.5rem\" />\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-5 xl:grid-cols-[minmax(0,1.35fr)_minmax(18rem,0.85fr)]\"\r\n >\r\n <div class=\"flex flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.change-groups')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"12rem\" class=\"rounded-lg\" />\r\n <p-skeleton height=\"14rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.evidence')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"9rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.request-context')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"1rem\" borderRadius=\"0.5rem\" />\r\n <p-skeleton height=\"3rem\" borderRadius=\"0.75rem\" />\r\n </div>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.subjects')\">\r\n <div class=\"flex flex-col gap-4\">\r\n <p-skeleton height=\"8rem\" class=\"rounded-lg\" />\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n } @else if (error(); as errorMessage) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <mt-card class=\"min-h-[28rem]\">\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center p-6 text-center\"\r\n >\r\n <p class=\"max-w-2xl text-sm font-medium text-rose-700\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n </mt-card>\r\n </div>\r\n } @else if (detail(); as event) {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <div class=\"flex flex-col gap-5\">\r\n <div\r\n class=\"grid gap-5 xl:grid-cols-[minmax(0,1.35fr)_minmax(18rem,0.85fr)]\"\r\n >\r\n <div class=\"flex min-w-0 flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.change-groups')\">\r\n @if (event.changeGroups.length > 0) {\r\n @let hasMultipleChangeGroups = event.changeGroups.length > 1;\r\n <div class=\"flex flex-col gap-5\">\r\n @for (group of event.changeGroups; track group.key) {\r\n @if (hasMultipleChangeGroups) {\r\n <div\r\n class=\"overflow-hidden rounded-xl border border-surface-200\"\r\n >\r\n <div class=\"border-b border-surface-200 px-4 py-3\">\r\n <h5 class=\"font-semibold text-surface-900\">\r\n {{ group.label || humanizeKey(group.key) }}\r\n </h5>\r\n </div>\r\n\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n change of group.changes;\r\n track change.fieldKey + \"-\" + $index\r\n ) {\r\n <div class=\"flex flex-col gap-3 px-4 py-4\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-2\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getFieldLabel(change) }}\r\n </span>\r\n\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n @if (\r\n getChangeSource(change);\r\n as changeSource\r\n ) {\r\n <span>\r\n {{ t(\"drawer.change-source\") }}:\r\n {{ changeSource }}\r\n </span>\r\n }\r\n\r\n <span>\r\n {{ t(\"drawer.value-kind\") }}:\r\n {{ change.valueKind }}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]\"\r\n >\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.from\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.oldValue)) {\r\n <span class=\"text-sm text-surface-400\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.oldValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex items-center justify-center text-xl text-surface-300\"\r\n >\r\n -&gt;\r\n </div>\r\n\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-0 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.to\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.newValue)) {\r\n <span class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.newValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"flex flex-col gap-4\">\r\n @if (group.label || group.key) {\r\n <h5 class=\"font-semibold text-surface-900\">\r\n {{ group.label || humanizeKey(group.key) }}\r\n </h5>\r\n }\r\n\r\n <div class=\"flex flex-col gap-5\">\r\n @for (\r\n change of group.changes;\r\n track change.fieldKey + \"-\" + $index\r\n ) {\r\n <div class=\"flex flex-col gap-3\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-2\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getFieldLabel(change) }}\r\n </span>\r\n\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n @if (\r\n getChangeSource(change);\r\n as changeSource\r\n ) {\r\n <span>\r\n {{ t(\"drawer.change-source\") }}:\r\n {{ changeSource }}\r\n </span>\r\n }\r\n\r\n <span>\r\n {{ t(\"drawer.value-kind\") }}:\r\n {{ change.valueKind }}\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n class=\"grid gap-3 lg:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]\"\r\n >\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.from\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.oldValue)) {\r\n <span class=\"text-sm text-surface-400\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.oldValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.oldValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"flex items-center justify-center text-xl text-surface-300\"\r\n >\r\n -&gt;\r\n </div>\r\n\r\n <div\r\n class=\"rounded-xl border border-surface-200 bg-surface-0 p-4\"\r\n >\r\n <p\r\n class=\"mb-2 text-xs font-semibold uppercase tracking-[0.18em] text-surface-500\"\r\n >\r\n {{ t(\"drawer.to\") }}\r\n </p>\r\n\r\n @if (isEmptyValue(change.newValue)) {\r\n <span class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.empty-value\") }}\r\n </span>\r\n } @else if (\r\n isJsonValue(\r\n change.valueKind,\r\n change.newValue\r\n )\r\n ) {\r\n <pre class=\"audit-json-value\">{{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}</pre>\r\n } @else {\r\n <span class=\"text-sm text-surface-700\">\r\n {{\r\n formatValue(\r\n change.newValue,\r\n change.valueKind\r\n )\r\n }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-changes\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n\r\n <div class=\"flex min-w-0 flex-col gap-5\">\r\n <mt-card [title]=\"t('drawer.request-context')\">\r\n <dl class=\"grid gap-4\">\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"drawer.actor\") }}\r\n </dt>\r\n <dd class=\"text-sm text-surface-800\">\r\n @if (\r\n getActorUserEntity(event.requestContext.actorUser);\r\n as actorUserEntity\r\n ) {\r\n <mt-entity-user [data]=\"actorUserEntity\" />\r\n } @else {\r\n <span class=\"break-all\">\r\n {{ event.requestContext.actorUserId || \"system\" }}\r\n </span>\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"drawer.occurred-at\") }}\r\n </dt>\r\n <dd class=\"text-sm text-surface-800\">\r\n {{ event.requestContext.occurredAtUtc | date: \"medium\" }}\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"category\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"category\", event.category);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"operation\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"operation\", event.operation);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"importance\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"importance\", event.importance);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n\r\n <div class=\"grid gap-1\">\r\n <dt\r\n class=\"text-xs font-semibold uppercase text-surface-500\"\r\n >\r\n {{ t(\"surface\") }}\r\n </dt>\r\n <dd>\r\n @if (\r\n getStatusEntity(\"surface\", event.surface);\r\n as status\r\n ) {\r\n <mt-entity-status [data]=\"status\" />\r\n }\r\n </dd>\r\n </div>\r\n </dl>\r\n </mt-card>\r\n\r\n <mt-card [title]=\"t('drawer.subjects')\">\r\n @if (event.subjects.length > 0) {\r\n <div\r\n class=\"overflow-hidden rounded-xl border border-surface-200\"\r\n >\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n subject of event.subjects;\r\n track subject.subjectType + \"-\" + subject.subjectId\r\n ) {\r\n <div class=\"flex flex-col gap-3 px-4 py-4\">\r\n <div\r\n class=\"flex flex-wrap items-start justify-between gap-3\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"font-medium text-surface-900\">\r\n {{ getSubjectLabel(subject) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ humanizeKey(subject.subjectType) }}\r\n </span>\r\n </div>\r\n\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1 text-xs font-medium text-surface-700\"\r\n >\r\n {{ humanizeKey(subject.relation) }}\r\n </span>\r\n </div>\r\n\r\n <div\r\n class=\"flex flex-wrap gap-2 text-xs text-surface-500\"\r\n >\r\n <span class=\"rounded-full bg-surface-100 px-3 py-1\">\r\n {{ t(\"drawer.scope\") }}: {{ subject.subjectId }}\r\n </span>\r\n @if (\r\n subject.levelId !== null &&\r\n subject.levelId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.level-id\") }}:\r\n {{ subject.levelId }}\r\n </span>\r\n }\r\n @if (\r\n subject.levelDataId !== null &&\r\n subject.levelDataId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.level-data-id\") }}:\r\n {{ subject.levelDataId }}\r\n </span>\r\n }\r\n @if (\r\n subject.moduleDataId !== null &&\r\n subject.moduleDataId !== undefined\r\n ) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.module-data-id\") }}:\r\n {{ subject.moduleDataId }}\r\n </span>\r\n }\r\n @if (subject.moduleKey) {\r\n <span\r\n class=\"rounded-full bg-surface-100 px-3 py-1\"\r\n >\r\n {{ t(\"drawer.module-key\") }}:\r\n {{ subject.moduleKey }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-subjects\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <mt-card [title]=\"t('drawer.evidence')\">\r\n <p class=\"mb-4 text-sm text-surface-500\">\r\n {{ t(\"drawer.advanced-description\") }}\r\n </p>\r\n\r\n @if (event.evidence.length > 0) {\r\n <div class=\"overflow-hidden rounded-xl border border-surface-200\">\r\n <div class=\"divide-y divide-surface-100\">\r\n @for (\r\n evidence of event.evidence;\r\n track evidence.type + \"-\" + $index\r\n ) {\r\n <section>\r\n <div\r\n class=\"flex flex-wrap items-center justify-between gap-3 px-4 py-3\"\r\n >\r\n <span class=\"font-medium text-surface-900\">\r\n {{ evidence.type || t(\"drawer.technical-evidence\") }}\r\n </span>\r\n\r\n <mt-button\r\n [label]=\"t('drawer.view-json')\"\r\n severity=\"secondary\"\r\n variant=\"text\"\r\n size=\"small\"\r\n (onClick)=\"toggleEvidence($index)\"\r\n />\r\n </div>\r\n\r\n @if (isEvidenceExpanded($index)) {\r\n <pre class=\"audit-json-panel\">{{\r\n formatJson(evidence.contentJson)\r\n }}</pre>\r\n }\r\n </section>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-surface-500\">\r\n {{ t(\"drawer.no-evidence\") }}\r\n </div>\r\n }\r\n </mt-card>\r\n </div>\r\n </div>\r\n } @else {\r\n <div [class]=\"modal.contentClass + ' h-full overflow-y-auto p-5'\">\r\n <mt-card class=\"min-h-[28rem]\">\r\n <div\r\n class=\"flex min-h-[28rem] items-center justify-center p-6 text-center\"\r\n >\r\n <p class=\"max-w-2xl text-sm text-surface-500\">\r\n {{ t(\"drawer.no-event-selected\") }}\r\n </p>\r\n </div>\r\n </mt-card>\r\n </div>\r\n }\r\n </ng-container>\r\n</mt-drawer>\r\n", styles: [":host{display:block}.audit-json-panel,.audit-json-value{margin:0;overflow-x:auto;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.audit-json-panel{border-top:1px solid var(--p-surface-100);background:color-mix(in srgb,var(--p-surface-50) 80%,white);padding:1rem;font-size:.8rem;line-height:1.55}.audit-json-value{font-size:.78rem;line-height:1.55;color:var(--p-surface-700)}\n"] }]
496
+ }], ctorParameters: () => [], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], detail: [{ type: i0.Input, args: [{ isSignal: true, alias: "detail", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], visibleChange: [{ type: i0.Output, args: ["visibleChange"] }] } });
497
+
498
+ const CATEGORY_OPTIONS = [
499
+ 'Runtime',
500
+ 'ControlPanel',
501
+ 'Discussion',
502
+ 'Process',
503
+ 'Escalation',
504
+ 'PhaseGate',
505
+ 'Governance',
506
+ 'System',
507
+ ].map((value) => ({
508
+ label: value,
509
+ value,
510
+ }));
511
+ const OPERATION_OPTIONS = [
512
+ 'Create',
513
+ 'Update',
514
+ 'Delete',
515
+ 'Close',
516
+ 'Move',
517
+ 'Progress',
518
+ 'Configure',
519
+ 'Comment',
520
+ 'BulkUpdate',
521
+ 'Change',
522
+ 'Unknown',
523
+ ].map((value) => ({
524
+ label: value,
525
+ value,
526
+ }));
527
+ const IMPORTANCE_OPTIONS = [
528
+ 'Low',
529
+ 'Medium',
530
+ 'High',
531
+ 'Critical',
532
+ ].map((value) => ({
533
+ label: value,
534
+ value,
535
+ }));
213
536
  class AuditLogsList {
214
- userCol = viewChild.required('userCol');
537
+ summaryCol = viewChild.required('summaryCol');
538
+ actorCol = viewChild.required('actorCol');
215
539
  facade = inject(AuditLogsFacade);
216
540
  translocoService = inject(TranslocoService);
217
- context = new HttpContext().set(REQUEST_CONTEXT, {
218
- useBaseUrl: true,
541
+ activeLang = toSignal(this.translocoService.langChanges$, {
542
+ initialValue: this.translocoService.getActiveLang(),
219
543
  });
220
- auditLogs = computed(() => this.facade.auditLogs()?.items, ...(ngDevMode ? [{ debugName: "auditLogs" }] : []));
221
- fullDataAuditLogs = computed(() => this.facade.auditLogs(), ...(ngDevMode ? [{ debugName: "fullDataAuditLogs" }] : []));
222
- tableColumns = linkedSignal(() => [
223
- {
224
- key: 'name',
225
- label: this.translocoService.translate('audit-logs.title'),
226
- },
227
- {
228
- key: 'module',
229
- label: this.translocoService.translate('audit-logs.module'),
230
- },
231
- {
232
- key: 'originalUserId.userName',
233
- label: this.translocoService.translate('audit-logs.user'),
234
- type: 'custom',
235
- customCellTpl: this.userCol(),
236
- filterConfig: {
544
+ auditLogs = this.facade.auditLogs;
545
+ appliedFilters = this.facade.filters;
546
+ loading = this.facade.isLoadingAuditLogs;
547
+ error = this.facade.getAuditLogsError;
548
+ detail = this.facade.selectedEventDetail;
549
+ detailError = this.facade.getAuditLogDetailError;
550
+ loadingDetail = this.facade.isLoadingAuditLogDetail;
551
+ drawerVisible = signal(false, ...(ngDevMode ? [{ debugName: "drawerVisible" }] : /* istanbul ignore next */ []));
552
+ tableFilters = signal({}, ...(ngDevMode ? [{ debugName: "tableFilters" }] : /* istanbul ignore next */ []));
553
+ summaryCards = computed(() => {
554
+ this.activeLang();
555
+ const response = this.auditLogs();
556
+ const items = response.items ?? [];
557
+ const elevatedCount = items.filter((item) => ['High', 'Critical'].includes(item.importance?.code ?? '')).length;
558
+ const uniqueActors = new Set(items
559
+ .map((item) => item.actorUserId?.trim())
560
+ .filter((value) => !!value)).size;
561
+ return [
562
+ {
563
+ title: String(response.totalCount ?? 0),
564
+ subTitle: this.translate('total-events'),
565
+ icon: 'file.file-05',
566
+ color: 'blue',
567
+ },
568
+ {
569
+ title: String(items.length),
570
+ subTitle: this.translate('visible-results'),
571
+ icon: 'general.building-02',
572
+ color: 'emerald',
573
+ },
574
+ {
575
+ title: String(elevatedCount),
576
+ subTitle: this.translate('high-impact'),
577
+ icon: 'alert.bell-02',
578
+ color: 'amber',
579
+ },
580
+ {
581
+ title: String(uniqueActors),
582
+ subTitle: this.translate('unique-actors'),
583
+ icon: 'custom.user-pp',
584
+ color: 'violet',
585
+ },
586
+ ];
587
+ }, ...(ngDevMode ? [{ debugName: "summaryCards" }] : /* istanbul ignore next */ []));
588
+ activeFilterBadges = computed(() => {
589
+ this.activeLang();
590
+ const filters = this.appliedFilters();
591
+ const badges = [];
592
+ this.pushFilterBadge(badges, this.translate('search'), filters.search);
593
+ this.pushFilterBadge(badges, this.translate('category'), filters.category);
594
+ this.pushFilterBadge(badges, this.translate('operation'), filters.operation);
595
+ this.pushFilterBadge(badges, this.translate('importance'), filters.importance);
596
+ this.pushFilterBadge(badges, this.translate('surface'), filters.surface);
597
+ this.pushFilterBadge(badges, this.translate('actor'), filters.actorUserId);
598
+ if (filters.fromUtc) {
599
+ badges.push(`${this.translate('from')}: ${this.formatFilterDate(filters.fromUtc)}`);
600
+ }
601
+ if (filters.toUtc) {
602
+ badges.push(`${this.translate('to')}: ${this.formatFilterDate(filters.toUtc)}`);
603
+ }
604
+ return badges;
605
+ }, ...(ngDevMode ? [{ debugName: "activeFilterBadges" }] : /* istanbul ignore next */ []));
606
+ tableColumns = linkedSignal(() => {
607
+ this.activeLang();
608
+ return [
609
+ {
610
+ key: 'summary',
611
+ label: this.translate('summary'),
612
+ type: 'custom',
613
+ customCellTpl: this.summaryCol(),
614
+ width: '27rem',
615
+ },
616
+ {
617
+ key: 'category.code',
618
+ label: this.translate('category'),
619
+ type: 'status',
620
+ statusMap: getAuditStatusMap('category'),
621
+ filterConfig: {
622
+ type: 'select',
623
+ label: this.translate('category'),
624
+ options: CATEGORY_OPTIONS,
625
+ },
626
+ width: '10rem',
627
+ },
628
+ {
629
+ key: 'operation.code',
630
+ label: this.translate('operation'),
631
+ type: 'status',
632
+ statusMap: getAuditStatusMap('operation'),
633
+ filterConfig: {
634
+ type: 'select',
635
+ label: this.translate('operation'),
636
+ options: OPERATION_OPTIONS,
637
+ },
638
+ width: '10rem',
639
+ },
640
+ {
641
+ key: 'importance.code',
642
+ label: this.translate('importance'),
643
+ type: 'status',
644
+ statusMap: getAuditStatusMap('importance'),
645
+ filterConfig: {
646
+ type: 'select',
647
+ label: this.translate('importance'),
648
+ options: IMPORTANCE_OPTIONS,
649
+ },
650
+ width: '10rem',
651
+ },
652
+ {
653
+ key: 'actorUser',
654
+ label: this.translate('actor'),
237
655
  type: 'user',
238
- label: this.translocoService.translate('audit-logs.user'),
239
- context: this.context,
240
- apiUrl: 'Identity/users',
656
+ filterConfig: {
657
+ type: 'user',
658
+ label: this.translate('actor'),
659
+ },
660
+ width: '14rem',
241
661
  },
242
- },
243
- {
244
- key: 'description',
245
- label: this.translocoService.translate('audit-logs.activity'),
246
- },
247
- {
248
- key: 'userIpAddress',
249
- label: this.translocoService.translate('audit-logs.ip-address'),
250
- },
251
- {
252
- key: 'date.displayValue',
253
- label: this.translocoService.translate('audit-logs.date'),
254
- filterConfig: {
662
+ {
663
+ key: 'occurredAtUtc',
664
+ label: this.translate('occurred-at'),
255
665
  type: 'date',
256
- label: this.translocoService.translate('audit-logs.start-date'),
666
+ filterConfig: {
667
+ type: 'date',
668
+ label: this.translate('occurred-at'),
669
+ },
670
+ width: '12rem',
257
671
  },
258
- },
259
- ], ...(ngDevMode ? [{ debugName: "tableColumns" }] : []));
260
- loading = this.facade.isLoadingAuditLogs;
672
+ ];
673
+ }, ...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
261
674
  onLazyLoad(event) {
262
- // Transform filters to API format
263
- const apiFilters = {};
264
- if (event.filters) {
265
- // Handle date filter transformation
266
- if (event.filters['date.displayValue']) {
267
- const dateFilter = event.filters['date.displayValue'];
268
- if (dateFilter.from) {
269
- apiFilters['startDate'] = normalizeDates(dateFilter.from);
270
- }
271
- if (dateFilter.to) {
272
- apiFilters['endDate'] = normalizeDates(dateFilter.to);
273
- }
274
- }
275
- if (event.filters['generalSearch']) {
276
- apiFilters['searchTerm'] = event.filters['generalSearch'];
277
- }
278
- if (event.filters['originalUserId.userName']) {
279
- const userFilter = event.filters['originalUserId.userName'];
280
- apiFilters['userId'] = userFilter?.userId;
281
- }
675
+ const filters = event.filters ?? {};
676
+ const apiFilters = {
677
+ language: this.currentLanguage(),
678
+ };
679
+ const generalSearch = this.readFilterValue(filters['generalSearch']);
680
+ if (generalSearch) {
681
+ apiFilters.search = generalSearch;
682
+ }
683
+ const category = this.readFilterValue(filters['category.code']);
684
+ if (category) {
685
+ apiFilters.category = category;
686
+ }
687
+ const operation = this.readFilterValue(filters['operation.code']);
688
+ if (operation) {
689
+ apiFilters.operation = operation;
690
+ }
691
+ const importance = this.readFilterValue(filters['importance.code']);
692
+ if (importance) {
693
+ apiFilters.importance = importance;
694
+ }
695
+ const surface = this.readFilterValue(filters['surface.code']);
696
+ if (surface) {
697
+ apiFilters.surface = surface;
698
+ }
699
+ const actorUserId = this.readFilterValue(filters['actorUserId']);
700
+ if (actorUserId) {
701
+ apiFilters.actorUserId = actorUserId;
702
+ }
703
+ const occurredAtRange = filters['occurredAtUtc'];
704
+ if (occurredAtRange?.from) {
705
+ apiFilters.fromUtc = this.normalizeDateBoundary(occurredAtRange.from, 'start');
706
+ }
707
+ if (occurredAtRange?.to) {
708
+ apiFilters.toUtc = this.normalizeDateBoundary(occurredAtRange.to, 'end');
709
+ }
710
+ this.facade.getAuditLogs(event.currentPage ?? 1, event.pageSize ?? this.auditLogs().pageSize ?? 20, apiFilters);
711
+ }
712
+ openEventDetail(row) {
713
+ this.drawerVisible.set(true);
714
+ this.facade.getAuditLogDetail(row.eventId, this.currentLanguage());
715
+ }
716
+ onDrawerVisibleChange(visible) {
717
+ this.drawerVisible.set(visible);
718
+ if (!visible) {
719
+ this.facade.clearAuditLogDetail();
720
+ }
721
+ }
722
+ retry() {
723
+ const response = this.auditLogs();
724
+ this.facade.getAuditLogs(response.page || 1, response.pageSize || 20, {
725
+ ...this.appliedFilters(),
726
+ language: this.currentLanguage(),
727
+ });
728
+ }
729
+ formatSubjectType(row) {
730
+ return row.primarySubjectType || this.translate('subject-type-unknown');
731
+ }
732
+ currentLanguage() {
733
+ return this.activeLang() === 'ar' ? 'ar' : 'en';
734
+ }
735
+ translate(key) {
736
+ this.activeLang();
737
+ return this.translocoService.translate(`audit-logs.${key}`);
738
+ }
739
+ pushFilterBadge(badges, label, value) {
740
+ if (!value) {
741
+ return;
742
+ }
743
+ badges.push(`${label}: ${value}`);
744
+ }
745
+ formatFilterDate(value) {
746
+ const date = new Date(value);
747
+ if (Number.isNaN(date.getTime())) {
748
+ return value;
749
+ }
750
+ return new Intl.DateTimeFormat(this.currentLanguage() === 'ar' ? 'ar' : undefined, {
751
+ dateStyle: 'medium',
752
+ }).format(date);
753
+ }
754
+ readFilterValue(value) {
755
+ if (typeof value === 'string' && value.trim().length > 0) {
756
+ return value.trim();
757
+ }
758
+ if (typeof value === 'number') {
759
+ return String(value);
760
+ }
761
+ if (value &&
762
+ typeof value === 'object' &&
763
+ 'value' in value &&
764
+ typeof value.value === 'string' &&
765
+ value.value.trim().length > 0) {
766
+ return value.value.trim();
767
+ }
768
+ return undefined;
769
+ }
770
+ normalizeDateBoundary(value, boundary) {
771
+ const normalizedDate = new Date(value);
772
+ if (boundary === 'start') {
773
+ normalizedDate.setHours(0, 0, 0, 0);
774
+ }
775
+ else {
776
+ normalizedDate.setHours(23, 59, 59, 999);
282
777
  }
283
- this.facade.getAuditLogs(event.currentPage, event.pageSize, apiFilters);
778
+ return normalizeDates(normalizedDate);
284
779
  }
285
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
286
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.3", type: AuditLogsList, isStandalone: true, selector: "mt-audit-logs-list", viewQueries: [{ propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <mt-table\r\n [data]=\"auditLogs()\"\r\n [columns]=\"tableColumns()\"\r\n [loading]=\"loading()\"\r\n [lazy]=\"true\"\r\n [showFilters]=\"true\"\r\n [exportable]=\"true\"\r\n [generalSearch]=\"true\"\r\n [lazyTotalRecords]=\"fullDataAuditLogs().totalCount\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n </mt-table>\r\n\r\n <ng-template #userCol let-row>\r\n <div class=\"flex items-center gap-2\">\r\n <mt-avatar [icon]=\"'custom.user-pp'\"> </mt-avatar>\r\n {{ row.originalUserId?.displayName }}\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "exportable", "exportFilename", "actionShape", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: AuditLogsList, isStandalone: true, selector: "mt-audit-logs-list", host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "summaryCol", first: true, predicate: ["summaryCol"], descendants: true, isSignal: true }, { propertyName: "actorCol", first: true, predicate: ["actorCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'audit-logs'\">\r\n <div class=\"flex flex-col gap-4\">\r\n @if (activeFilterBadges().length > 0) {\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (badge of activeFilterBadges(); track badge) {\r\n <mt-chip\r\n [label]=\"badge\"\r\n styleClass=\"border border-surface-200 !bg-surface-0 !text-surface-700 text-xs\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\r\n @for (card of summaryCards(); track card.subTitle) {\r\n <mt-statistic-card [data]=\"card\" cardClass=\"shadow-sm\" />\r\n }\r\n </div>\r\n\r\n @if (error(); as errorMessage) {\r\n <section\r\n class=\"flex flex-col gap-3 rounded-xl border border-rose-200 bg-rose-50 px-5 py-4 lg:flex-row lg:items-center lg:justify-between\"\r\n >\r\n <div class=\"space-y-1\">\r\n <p class=\"text-sm font-semibold text-rose-700\">\r\n {{ t(\"load-error-title\") }}\r\n </p>\r\n <p class=\"text-sm text-rose-600\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n severity=\"danger\"\r\n variant=\"outlined\"\r\n icon=\"general.refresh-cw-05\"\r\n [label]=\"t('retry')\"\r\n (onClick)=\"retry()\"\r\n />\r\n </section>\r\n }\r\n\r\n <mt-table\r\n [(filters)]=\"tableFilters\"\r\n [data]=\"auditLogs().items\"\r\n [columns]=\"tableColumns()\"\r\n [loading]=\"loading()\"\r\n [lazy]=\"true\"\r\n [showFilters]=\"true\"\r\n [generalSearch]=\"true\"\r\n [clickableRows]=\"true\"\r\n [lazyTotalRecords]=\"auditLogs().totalCount\"\r\n dataKey=\"eventId\"\r\n storageKey=\"audit-logs-list-table\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n (rowClick)=\"openEventDetail($event)\"\r\n />\r\n </div>\r\n\r\n <ng-template #summaryCol let-row>\r\n <div class=\"flex flex-col gap-2 py-1\">\r\n <p class=\"line-clamp-2 text-sm font-semibold text-surface-900\">\r\n {{ row.summary }}\r\n </p>\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n <span>{{ t(\"event-id\") }}: {{ row.eventId }}</span>\r\n @if (row.primarySubjectType) {\r\n <span>{{ formatSubjectType(row) }}</span>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #actorCol let-row>\r\n <div class=\"flex flex-col gap-1 py-1\">\r\n <span class=\"break-all text-sm font-medium text-surface-800\">\r\n {{ row.actorUserId || t(\"actor-unknown\") }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ row.moduleKey || row.levelDataId || row.moduleDataId || \"audit\" }}\r\n </span>\r\n </div>\r\n </ng-template>\r\n\r\n <mt-audit-log-detail-drawer\r\n [visible]=\"drawerVisible()\"\r\n [detail]=\"detail()\"\r\n [loading]=\"loadingDetail()\"\r\n [error]=\"detailError()\"\r\n (visibleChange)=\"onDrawerVisibleChange($event)\"\r\n />\r\n</ng-container>\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "exportFilename", "actionShape", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: StatisticCard, selector: "mt-statistic-card", inputs: ["data", "cardClass"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: AuditLogDetailDrawer, selector: "mt-audit-log-detail-drawer", inputs: ["visible", "detail", "loading", "error"], outputs: ["visibleChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
287
782
  }
288
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: AuditLogsList, decorators: [{
783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: AuditLogsList, decorators: [{
289
784
  type: Component,
290
- args: [{ selector: 'mt-audit-logs-list', standalone: true, imports: [CommonModule, SkeletonModule, Table, Avatar, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\r\n <mt-table\r\n [data]=\"auditLogs()\"\r\n [columns]=\"tableColumns()\"\r\n [loading]=\"loading()\"\r\n [lazy]=\"true\"\r\n [showFilters]=\"true\"\r\n [exportable]=\"true\"\r\n [generalSearch]=\"true\"\r\n [lazyTotalRecords]=\"fullDataAuditLogs().totalCount\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n </mt-table>\r\n\r\n <ng-template #userCol let-row>\r\n <div class=\"flex items-center gap-2\">\r\n <mt-avatar [icon]=\"'custom.user-pp'\"> </mt-avatar>\r\n {{ row.originalUserId?.displayName }}\r\n </div>\r\n </ng-template>\r\n</ng-container>\r\n" }]
291
- }], propDecorators: { userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }] } });
785
+ args: [{ selector: 'mt-audit-logs-list', imports: [
786
+ CommonModule,
787
+ Table,
788
+ Chip,
789
+ Button,
790
+ StatisticCard,
791
+ TranslocoDirective,
792
+ AuditLogDetailDrawer,
793
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
794
+ class: 'block h-full',
795
+ }, template: "<ng-container *transloco=\"let t; prefix: 'audit-logs'\">\r\n <div class=\"flex flex-col gap-4\">\r\n @if (activeFilterBadges().length > 0) {\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (badge of activeFilterBadges(); track badge) {\r\n <mt-chip\r\n [label]=\"badge\"\r\n styleClass=\"border border-surface-200 !bg-surface-0 !text-surface-700 text-xs\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"grid gap-3 md:grid-cols-2 xl:grid-cols-4\">\r\n @for (card of summaryCards(); track card.subTitle) {\r\n <mt-statistic-card [data]=\"card\" cardClass=\"shadow-sm\" />\r\n }\r\n </div>\r\n\r\n @if (error(); as errorMessage) {\r\n <section\r\n class=\"flex flex-col gap-3 rounded-xl border border-rose-200 bg-rose-50 px-5 py-4 lg:flex-row lg:items-center lg:justify-between\"\r\n >\r\n <div class=\"space-y-1\">\r\n <p class=\"text-sm font-semibold text-rose-700\">\r\n {{ t(\"load-error-title\") }}\r\n </p>\r\n <p class=\"text-sm text-rose-600\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n\r\n <mt-button\r\n severity=\"danger\"\r\n variant=\"outlined\"\r\n icon=\"general.refresh-cw-05\"\r\n [label]=\"t('retry')\"\r\n (onClick)=\"retry()\"\r\n />\r\n </section>\r\n }\r\n\r\n <mt-table\r\n [(filters)]=\"tableFilters\"\r\n [data]=\"auditLogs().items\"\r\n [columns]=\"tableColumns()\"\r\n [loading]=\"loading()\"\r\n [lazy]=\"true\"\r\n [showFilters]=\"true\"\r\n [generalSearch]=\"true\"\r\n [clickableRows]=\"true\"\r\n [lazyTotalRecords]=\"auditLogs().totalCount\"\r\n dataKey=\"eventId\"\r\n storageKey=\"audit-logs-list-table\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n (rowClick)=\"openEventDetail($event)\"\r\n />\r\n </div>\r\n\r\n <ng-template #summaryCol let-row>\r\n <div class=\"flex flex-col gap-2 py-1\">\r\n <p class=\"line-clamp-2 text-sm font-semibold text-surface-900\">\r\n {{ row.summary }}\r\n </p>\r\n <div\r\n class=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-surface-500\"\r\n >\r\n <span>{{ t(\"event-id\") }}: {{ row.eventId }}</span>\r\n @if (row.primarySubjectType) {\r\n <span>{{ formatSubjectType(row) }}</span>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #actorCol let-row>\r\n <div class=\"flex flex-col gap-1 py-1\">\r\n <span class=\"break-all text-sm font-medium text-surface-800\">\r\n {{ row.actorUserId || t(\"actor-unknown\") }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ row.moduleKey || row.levelDataId || row.moduleDataId || \"audit\" }}\r\n </span>\r\n </div>\r\n </ng-template>\r\n\r\n <mt-audit-log-detail-drawer\r\n [visible]=\"drawerVisible()\"\r\n [detail]=\"detail()\"\r\n [loading]=\"loadingDetail()\"\r\n [error]=\"detailError()\"\r\n (visibleChange)=\"onDrawerVisibleChange($event)\"\r\n />\r\n</ng-container>\r\n", styles: [":host{display:block}\n"] }]
796
+ }], propDecorators: { summaryCol: [{ type: i0.ViewChild, args: ['summaryCol', { isSignal: true }] }], actorCol: [{ type: i0.ViewChild, args: ['actorCol', { isSignal: true }] }] } });
292
797
 
293
798
  // store/index.ts
294
799
 
@@ -296,5 +801,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
296
801
  * Generated bundle index. Do not edit.
297
802
  */
298
803
 
299
- export { AuditLogs, AuditLogsActionKey, AuditLogsFacade, AuditLogsList, AuditLogsState, GetAuditLogs };
804
+ export { AuditLogs, AuditLogsActionKey, AuditLogsFacade, AuditLogsList, AuditLogsState, ClearAuditLogDetail, GetAuditLogDetail, GetAuditLogs };
300
805
  //# sourceMappingURL=masterteam-audit-logs.mjs.map