@juice-js/audit 0.0.0-stage → 1.0.0-alpha.1

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.
package/README.md CHANGED
@@ -1,3 +1,75 @@
1
- # Temporary Holding Version
1
+ # @juice-js/audit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Juice Audit pages for an Angular app built on `@juice-js/layout`:
4
+ - **Overview:** totals, failures, users, apps and changed tables over a time window.
5
+ - **Access records:** the audited requests, messages and jobs, with each one's request, response, server and metadata.
6
+ - **Data changes:** each changed row's values before and after, and its history.
7
+ - **Trace:** everything audited under one trace id.
8
+
9
+ The pages read the audit API that `Juice.Audit.Api` maps with `MapJuiceAudit()`.
10
+
11
+ ## Host (ASP.NET Core)
12
+
13
+ ```csharp
14
+ builder.Services.ConfigureAuditDefault(builder.Configuration); // or ConfigureAuditGrpcHost() on the audit server
15
+ // …
16
+ app.UseAuthentication();
17
+ app.UseAuthorization();
18
+ app.MapJuiceAudit("/api/audit"); // any authenticated user (the default policy)
19
+ app.MapJuiceAudit("/api/audit", "AuditReaders"); // or a policy of yours
20
+ ```
21
+
22
+ The records are read through `AuditDbContext`. A tenant sees only its own records, and the root tenant sees all of them. Map the API where the audit database is: in the app itself with `ConfigureAuditDefault()`, or on the gRPC audit server that its apps send to.
23
+
24
+ ## App
25
+
26
+ ```ts
27
+ // app module or app config
28
+ providers: [
29
+ provideHttpClient(withInterceptorsFromDi()),
30
+ provideJuiceAudit({ apiBase: '', basePath: '/audit' }),
31
+ ],
32
+
33
+ // routes: mount the pages under basePath, behind your own sign-in guard
34
+ { path: 'audit', children: AUDIT_ROUTES.map(r => ({ ...r, canActivate: [AuthGuard] })) },
35
+
36
+ // menu (juice-layout)
37
+ MenuModule.forRoot([...yourMenus, ...auditMenus('/audit', 200)]),
38
+ ```
39
+
40
+ ```scss
41
+ // styles.scss (the app's global styles, once): the pages' layout, in the Juice Console's teal theme, light and dark (.dark).
42
+ @use '@juice-js/audit';
43
+
44
+ // Optional: colours from your own Angular Material (M2) themes instead.
45
+ :root { @include audit.colors($light-theme); }
46
+ .dark { @include audit.colors($dark-theme); } // under your dark-mode selector
47
+ ```
48
+
49
+ `colors($theme, $tones: ())` sets the `--ja-*` custom properties the pages use:
50
+ - **Text and surfaces:** the theme's background and foreground colours.
51
+ - **Links and selection:** the theme's primary palette.
52
+ - **Failures:** the theme's warn palette.
53
+ - **Tones:** succeeded, warning, modified and neutral default to Material's green, deep orange, blue and greys. Pass `$tones: (ok: …, warn: …, bad: …, info: …, none: …)` to change them.
54
+
55
+ The default colours use zero-specificity `:where()` selectors, so your `colors()` replaces them wherever it sits in the file. The pages don't style the page itself, so give `body` your own colours.
56
+
57
+ - `apiBase`: the API origin; empty when the API serves the app.
58
+ - `apiPath`: where `MapJuiceAudit()` mapped the API; `/api/audit` by default.
59
+ - `basePath`: where the routes are mounted; every link between the pages goes under it.
60
+ - `traceLink`: optional. Turns each trace id into a link to your tracing UI, as a route (`id => ['/observability/traces', id]`) or a URL string.
61
+ - The router needs `bindToComponentInputs: true`, because the pages take their route and query parameters as inputs.
62
+ - The host's bearer-token interceptor (`@juice-js/auth`) must cover `apiBase`.
63
+
64
+ ## With @juice-js/observability
65
+
66
+ The two packages can share one app.
67
+ - **No collisions:** classes start with `ja-` here and `jc-` there, and menu ids start with `audit-`.
68
+ - **Mounting:** mount each package's routes at its own path.
69
+ - **Theme:** pass your theme to both `colors()` mixins.
70
+ - **Traces:** set `traceLink` to the observability trace page, so an audit record opens its spans and logs:
71
+
72
+ ```ts
73
+ provideJuiceObservability({ basePath: '/observability' }),
74
+ provideJuiceAudit({ basePath: '/audit', traceLink: id => ['/observability/traces', id] }),
75
+ ```
@@ -0,0 +1,291 @@
1
+ import { AuditApiService, AuditContextService, AuditLinkPipe } from "./juice-js-audit.mjs";
2
+ import { a as DataChangeTable, c as TraceRef, d as PrettyJsonPipe, l as DurationPipe, m as apiResource, o as KeyValueTable, p as problemText, r as AuditContextBar, s as StatusBadge } from "./juice-js-audit-ui-DnfhiB6p.mjs";
3
+ import * as i0 from "@angular/core";
4
+ import { ChangeDetectionStrategy, Component, computed, inject, input } from "@angular/core";
5
+ import { RouterLink } from "@angular/router";
6
+ import { of } from "rxjs";
7
+ import { DatePipe } from "@angular/common";
8
+ import * as i1 from "@angular/material/progress-bar";
9
+ import { MatProgressBarModule } from "@angular/material/progress-bar";
10
+ var AccessLogPage = class AccessLogPage {
11
+ api = inject(AuditApiService);
12
+ ctx = inject(AuditContextService);
13
+ id = input.required(...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
14
+ log = apiResource({
15
+ params: () => this.id(),
16
+ stream: ({ params }) => this.api.accessLog(params)
17
+ });
18
+ trace = apiResource({
19
+ params: () => {
20
+ const l = this.log.value();
21
+ return l?.traceId && l.dataChanges > 0 ? l.traceId : void 0;
22
+ },
23
+ stream: ({ params }) => params ? this.api.trace(params) : of(void 0)
24
+ });
25
+ error = computed(() => problemText(this.log.error()), ...ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []);
26
+ static ɵfac = i0.ɵɵngDeclareFactory({
27
+ minVersion: "12.0.0",
28
+ version: "22.2.4",
29
+ ngImport: i0,
30
+ type: AccessLogPage,
31
+ deps: [],
32
+ target: i0.ɵɵFactoryTarget.Component
33
+ });
34
+ static ɵcmp = i0.ɵɵngDeclareComponent({
35
+ minVersion: "17.0.0",
36
+ version: "22.2.4",
37
+ type: AccessLogPage,
38
+ isStandalone: true,
39
+ selector: "ng-component",
40
+ inputs: { id: {
41
+ classPropertyName: "id",
42
+ publicName: "id",
43
+ isSignal: true,
44
+ isRequired: true,
45
+ transformFunction: null
46
+ } },
47
+ ngImport: i0,
48
+ template: `
49
+ <ja-context-bar [showRange]="false">
50
+ <span class="ja-crumb"><a [routerLink]="['access-logs'] | jaLink" [queryParams]="ctx.keep()">Access records</a> /</span>
51
+ <h1>{{ log.value()?.action ?? 'Access record' }}</h1>
52
+ </ja-context-bar>
53
+ @if (log.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
54
+ @if (log.error()) { <p class="ja-error">{{ error() }}</p> }
55
+ @if (log.value(); as l) {
56
+ <div class="ja-summary">
57
+ <ja-status [status]="l.response?.status" />
58
+ <span>{{ l.dateTime | date: 'MMM d, y, HH:mm:ss.SSS' }}</span>
59
+ <span>{{ l.response?.elapsedMs | jaDuration }}</span>
60
+ <span>by <strong>{{ l.user || 'no user' }}</strong></span>
61
+ @if (l.tenantId) { <span class="ja-muted">tenant {{ l.tenantId }}</span> }
62
+ @if (l.isRestricted) { <span class="ja-badge ja-tone-warn">restricted</span> }
63
+ <span class="ja-muted">trace <ja-trace-ref [traceId]="l.traceId" [short]="false" /></span>
64
+ </div>
65
+ <div class="ja-grid-2">
66
+ <section class="ja-card">
67
+ <h2>Request</h2>
68
+ @if (l.request; as r) {
69
+ <p class="ja-mono">{{ r.method }} {{ r.scheme ? r.scheme + '://' : '' }}{{ r.host }}{{ r.path }}{{ r.query }}</p>
70
+ <ja-kv [values]="{ 'Remote IP': r.remoteIp, 'Access zone': r.zone }" empty="" />
71
+ @if (r.headers) { <h3>Headers</h3><pre class="ja-pre">{{ r.headers }}</pre> }
72
+ @if (r.data) { <h3>Body</h3><pre class="ja-pre">{{ r.data | jaPretty }}</pre> }
73
+ } @else { <p class="ja-muted">Not recorded.</p> }
74
+ </section>
75
+ <section class="ja-card">
76
+ <h2>Response</h2>
77
+ @if (l.response; as r) {
78
+ @if (r.message) { <p>{{ r.message }}</p> }
79
+ @if (r.error) { <h3>Error</h3><pre class="ja-pre ja-error">{{ r.error }}</pre> }
80
+ @if (r.headers) { <h3>Headers</h3><pre class="ja-pre">{{ r.headers }}</pre> }
81
+ @if (r.data) { <h3>Body</h3><pre class="ja-pre">{{ r.data | jaPretty }}</pre> }
82
+ @if (!r.message && !r.error && !r.headers && !r.data) { <p class="ja-muted">Status and duration only.</p> }
83
+ } @else { <p class="ja-muted">Not recorded.</p> }
84
+ </section>
85
+ <section class="ja-card">
86
+ <h2>Server</h2>
87
+ <ja-kv [values]="{ App: l.server?.app, Version: l.server?.appVersion, Machine: l.server?.machine, OS: l.server?.os }" empty="Not recorded." />
88
+ </section>
89
+ <section class="ja-card">
90
+ <h2>Metadata</h2>
91
+ <ja-kv [values]="l.metadata" />
92
+ </section>
93
+ </div>
94
+ <section class="ja-card">
95
+ <h2>Data changed ({{ l.dataChanges }})</h2>
96
+ @if (l.dataChanges === 0) { <p class="ja-muted">No data changes under this trace.</p> }
97
+ @else {
98
+ @if (trace.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
99
+ @if (trace.value(); as t) {
100
+ <ja-data-change-table [rows]="t.dataChanges" [compact]="true" />
101
+ @if (t.truncated) { <p class="ja-muted">The trace has more records than shown; <a [routerLink]="['traces', l.traceId] | jaLink">open the trace</a>.</p> }
102
+ }
103
+ }
104
+ </section>
105
+ }`,
106
+ isInline: true,
107
+ dependencies: [
108
+ {
109
+ kind: "directive",
110
+ type: RouterLink,
111
+ selector: "[routerLink]",
112
+ inputs: [
113
+ "target",
114
+ "queryParams",
115
+ "fragment",
116
+ "queryParamsHandling",
117
+ "state",
118
+ "info",
119
+ "relativeTo",
120
+ "preserveFragment",
121
+ "skipLocationChange",
122
+ "replaceUrl",
123
+ "browserUrl",
124
+ "routerLink"
125
+ ]
126
+ },
127
+ {
128
+ kind: "ngmodule",
129
+ type: MatProgressBarModule
130
+ },
131
+ {
132
+ kind: "component",
133
+ type: i1.MatProgressBar,
134
+ selector: "mat-progress-bar",
135
+ inputs: [
136
+ "color",
137
+ "value",
138
+ "bufferValue",
139
+ "mode"
140
+ ],
141
+ outputs: ["animationEnd"],
142
+ exportAs: ["matProgressBar"]
143
+ },
144
+ {
145
+ kind: "component",
146
+ type: AuditContextBar,
147
+ selector: "ja-context-bar",
148
+ inputs: ["showRange"]
149
+ },
150
+ {
151
+ kind: "component",
152
+ type: DataChangeTable,
153
+ selector: "ja-data-change-table",
154
+ inputs: ["rows", "compact"]
155
+ },
156
+ {
157
+ kind: "component",
158
+ type: KeyValueTable,
159
+ selector: "ja-kv",
160
+ inputs: ["values", "empty"]
161
+ },
162
+ {
163
+ kind: "component",
164
+ type: StatusBadge,
165
+ selector: "ja-status",
166
+ inputs: ["status"]
167
+ },
168
+ {
169
+ kind: "component",
170
+ type: TraceRef,
171
+ selector: "ja-trace-ref",
172
+ inputs: ["traceId", "short"]
173
+ },
174
+ {
175
+ kind: "pipe",
176
+ type: DatePipe,
177
+ name: "date"
178
+ },
179
+ {
180
+ kind: "pipe",
181
+ type: AuditLinkPipe,
182
+ name: "jaLink"
183
+ },
184
+ {
185
+ kind: "pipe",
186
+ type: DurationPipe,
187
+ name: "jaDuration"
188
+ },
189
+ {
190
+ kind: "pipe",
191
+ type: PrettyJsonPipe,
192
+ name: "jaPretty"
193
+ }
194
+ ],
195
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
196
+ });
197
+ };
198
+ i0.ɵɵngDeclareClassMetadata({
199
+ minVersion: "12.0.0",
200
+ version: "22.2.4",
201
+ ngImport: i0,
202
+ type: AccessLogPage,
203
+ decorators: [{
204
+ type: Component,
205
+ args: [{
206
+ changeDetection: ChangeDetectionStrategy.OnPush,
207
+ imports: [
208
+ RouterLink,
209
+ DatePipe,
210
+ MatProgressBarModule,
211
+ AuditLinkPipe,
212
+ DurationPipe,
213
+ PrettyJsonPipe,
214
+ AuditContextBar,
215
+ DataChangeTable,
216
+ KeyValueTable,
217
+ StatusBadge,
218
+ TraceRef
219
+ ],
220
+ template: `
221
+ <ja-context-bar [showRange]="false">
222
+ <span class="ja-crumb"><a [routerLink]="['access-logs'] | jaLink" [queryParams]="ctx.keep()">Access records</a> /</span>
223
+ <h1>{{ log.value()?.action ?? 'Access record' }}</h1>
224
+ </ja-context-bar>
225
+ @if (log.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
226
+ @if (log.error()) { <p class="ja-error">{{ error() }}</p> }
227
+ @if (log.value(); as l) {
228
+ <div class="ja-summary">
229
+ <ja-status [status]="l.response?.status" />
230
+ <span>{{ l.dateTime | date: 'MMM d, y, HH:mm:ss.SSS' }}</span>
231
+ <span>{{ l.response?.elapsedMs | jaDuration }}</span>
232
+ <span>by <strong>{{ l.user || 'no user' }}</strong></span>
233
+ @if (l.tenantId) { <span class="ja-muted">tenant {{ l.tenantId }}</span> }
234
+ @if (l.isRestricted) { <span class="ja-badge ja-tone-warn">restricted</span> }
235
+ <span class="ja-muted">trace <ja-trace-ref [traceId]="l.traceId" [short]="false" /></span>
236
+ </div>
237
+ <div class="ja-grid-2">
238
+ <section class="ja-card">
239
+ <h2>Request</h2>
240
+ @if (l.request; as r) {
241
+ <p class="ja-mono">{{ r.method }} {{ r.scheme ? r.scheme + '://' : '' }}{{ r.host }}{{ r.path }}{{ r.query }}</p>
242
+ <ja-kv [values]="{ 'Remote IP': r.remoteIp, 'Access zone': r.zone }" empty="" />
243
+ @if (r.headers) { <h3>Headers</h3><pre class="ja-pre">{{ r.headers }}</pre> }
244
+ @if (r.data) { <h3>Body</h3><pre class="ja-pre">{{ r.data | jaPretty }}</pre> }
245
+ } @else { <p class="ja-muted">Not recorded.</p> }
246
+ </section>
247
+ <section class="ja-card">
248
+ <h2>Response</h2>
249
+ @if (l.response; as r) {
250
+ @if (r.message) { <p>{{ r.message }}</p> }
251
+ @if (r.error) { <h3>Error</h3><pre class="ja-pre ja-error">{{ r.error }}</pre> }
252
+ @if (r.headers) { <h3>Headers</h3><pre class="ja-pre">{{ r.headers }}</pre> }
253
+ @if (r.data) { <h3>Body</h3><pre class="ja-pre">{{ r.data | jaPretty }}</pre> }
254
+ @if (!r.message && !r.error && !r.headers && !r.data) { <p class="ja-muted">Status and duration only.</p> }
255
+ } @else { <p class="ja-muted">Not recorded.</p> }
256
+ </section>
257
+ <section class="ja-card">
258
+ <h2>Server</h2>
259
+ <ja-kv [values]="{ App: l.server?.app, Version: l.server?.appVersion, Machine: l.server?.machine, OS: l.server?.os }" empty="Not recorded." />
260
+ </section>
261
+ <section class="ja-card">
262
+ <h2>Metadata</h2>
263
+ <ja-kv [values]="l.metadata" />
264
+ </section>
265
+ </div>
266
+ <section class="ja-card">
267
+ <h2>Data changed ({{ l.dataChanges }})</h2>
268
+ @if (l.dataChanges === 0) { <p class="ja-muted">No data changes under this trace.</p> }
269
+ @else {
270
+ @if (trace.isLoading()) { <mat-progress-bar mode="indeterminate" /> }
271
+ @if (trace.value(); as t) {
272
+ <ja-data-change-table [rows]="t.dataChanges" [compact]="true" />
273
+ @if (t.truncated) { <p class="ja-muted">The trace has more records than shown; <a [routerLink]="['traces', l.traceId] | jaLink">open the trace</a>.</p> }
274
+ }
275
+ }
276
+ </section>
277
+ }`
278
+ }]
279
+ }],
280
+ propDecorators: { id: [{
281
+ type: i0.Input,
282
+ args: [{
283
+ isSignal: true,
284
+ alias: "id",
285
+ required: true
286
+ }]
287
+ }] }
288
+ });
289
+ export { AccessLogPage };
290
+
291
+ //# sourceMappingURL=juice-js-audit-access-log.page-CrH7ib4K.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"juice-js-audit-access-log.page-CrH7ib4K.mjs","names":[],"sources":["../../../../projects/juice-js/audit/src/lib/pages/access-log.page.ts"],"sourcesContent":["import { DatePipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { apiResource } from '../shared/resource';\nimport { MatProgressBarModule } from '@angular/material/progress-bar';\nimport { RouterLink } from '@angular/router';\nimport { of } from 'rxjs';\nimport { AuditApiService } from '../core/api.service';\nimport { AuditContextService } from '../core/context.service';\nimport { AuditLinkPipe } from '../core/options';\nimport { DurationPipe, PrettyJsonPipe, problemText } from '../shared/format';\nimport { AuditContextBar, DataChangeTable, KeyValueTable, StatusBadge, TraceRef } from '../shared/ui';\n\n/** One access record: the request, its outcome, where it ran, its metadata and the data it changed. */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, DatePipe, MatProgressBarModule, AuditLinkPipe, DurationPipe, PrettyJsonPipe, AuditContextBar,\n DataChangeTable, KeyValueTable, StatusBadge, TraceRef],\n template: `\n <ja-context-bar [showRange]=\"false\">\n <span class=\"ja-crumb\"><a [routerLink]=\"['access-logs'] | jaLink\" [queryParams]=\"ctx.keep()\">Access records</a> /</span>\n <h1>{{ log.value()?.action ?? 'Access record' }}</h1>\n </ja-context-bar>\n @if (log.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (log.error()) { <p class=\"ja-error\">{{ error() }}</p> }\n @if (log.value(); as l) {\n <div class=\"ja-summary\">\n <ja-status [status]=\"l.response?.status\" />\n <span>{{ l.dateTime | date: 'MMM d, y, HH:mm:ss.SSS' }}</span>\n <span>{{ l.response?.elapsedMs | jaDuration }}</span>\n <span>by <strong>{{ l.user || 'no user' }}</strong></span>\n @if (l.tenantId) { <span class=\"ja-muted\">tenant {{ l.tenantId }}</span> }\n @if (l.isRestricted) { <span class=\"ja-badge ja-tone-warn\">restricted</span> }\n <span class=\"ja-muted\">trace <ja-trace-ref [traceId]=\"l.traceId\" [short]=\"false\" /></span>\n </div>\n <div class=\"ja-grid-2\">\n <section class=\"ja-card\">\n <h2>Request</h2>\n @if (l.request; as r) {\n <p class=\"ja-mono\">{{ r.method }} {{ r.scheme ? r.scheme + '://' : '' }}{{ r.host }}{{ r.path }}{{ r.query }}</p>\n <ja-kv [values]=\"{ 'Remote IP': r.remoteIp, 'Access zone': r.zone }\" empty=\"\" />\n @if (r.headers) { <h3>Headers</h3><pre class=\"ja-pre\">{{ r.headers }}</pre> }\n @if (r.data) { <h3>Body</h3><pre class=\"ja-pre\">{{ r.data | jaPretty }}</pre> }\n } @else { <p class=\"ja-muted\">Not recorded.</p> }\n </section>\n <section class=\"ja-card\">\n <h2>Response</h2>\n @if (l.response; as r) {\n @if (r.message) { <p>{{ r.message }}</p> }\n @if (r.error) { <h3>Error</h3><pre class=\"ja-pre ja-error\">{{ r.error }}</pre> }\n @if (r.headers) { <h3>Headers</h3><pre class=\"ja-pre\">{{ r.headers }}</pre> }\n @if (r.data) { <h3>Body</h3><pre class=\"ja-pre\">{{ r.data | jaPretty }}</pre> }\n @if (!r.message && !r.error && !r.headers && !r.data) { <p class=\"ja-muted\">Status and duration only.</p> }\n } @else { <p class=\"ja-muted\">Not recorded.</p> }\n </section>\n <section class=\"ja-card\">\n <h2>Server</h2>\n <ja-kv [values]=\"{ App: l.server?.app, Version: l.server?.appVersion, Machine: l.server?.machine, OS: l.server?.os }\" empty=\"Not recorded.\" />\n </section>\n <section class=\"ja-card\">\n <h2>Metadata</h2>\n <ja-kv [values]=\"l.metadata\" />\n </section>\n </div>\n <section class=\"ja-card\">\n <h2>Data changed ({{ l.dataChanges }})</h2>\n @if (l.dataChanges === 0) { <p class=\"ja-muted\">No data changes under this trace.</p> }\n @else {\n @if (trace.isLoading()) { <mat-progress-bar mode=\"indeterminate\" /> }\n @if (trace.value(); as t) {\n <ja-data-change-table [rows]=\"t.dataChanges\" [compact]=\"true\" />\n @if (t.truncated) { <p class=\"ja-muted\">The trace has more records than shown; <a [routerLink]=\"['traces', l.traceId] | jaLink\">open the trace</a>.</p> }\n }\n }\n </section>\n }`,\n})\nexport class AccessLogPage {\n private readonly api = inject(AuditApiService);\n protected readonly ctx = inject(AuditContextService);\n readonly id = input.required<string>();\n\n protected readonly log = apiResource({ params: () => this.id(), stream: ({ params }) => this.api.accessLog(params) });\n protected readonly trace = apiResource({\n params: () => { const l = this.log.value(); return l?.traceId && l.dataChanges > 0 ? l.traceId : undefined; },\n stream: ({ params }) => params ? this.api.trace(params) : of(undefined),\n });\n protected readonly error = computed(() => problemText(this.log.error()));\n}\n"],"mappings":";;;;;;;;;AA4EA,IAAa,gBAAb,MAAa,cAAa;CACP,MAAM,OAAO,eAAe;CAC1B,MAAM,OAAO,mBAAmB;CAC1C,KAAK,MAAM,SAAkB,GAAA,YAAA,CAAA,EAAA,WAAA,KAAA,CAAA,+BAAA,CAAA,CAAA;CAEnB,MAAM,YAAY;EAAE,cAAc,KAAK,GAAE;EAAI,SAAS,EAAE,aAAa,KAAK,IAAI,UAAU,MAAM;CAAC,CAAE;CACjG,QAAQ,YAAY;EACrC,cAAa;GAAG,MAAM,IAAI,KAAK,IAAI,MAAK;GAAI,OAAO,GAAG,WAAW,EAAE,cAAc,IAAI,EAAE,UAAU;EAAW;EAC5G,SAAS,EAAE,aAAa,SAAS,KAAK,IAAI,MAAM,MAAM,IAAI,GAAG,MAAS;CACvE,CAAA;CACkB,QAAQ,eAAe,YAAY,KAAK,IAAI,MAAK,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAV7D,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAa,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAb,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAa,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,IAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UA3Dd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDN,UAAA;EAAA,cAAA;GAAA;IAAA,MAAA;IAAA,MA3DM;IAAU,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;KAAA;IAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAY;GAAoB;GAAA;IAAA,MAAA;IAAA,MAAA,GAAA;IAAA,UAAA;IAAA,QAAA;KAAA;KAAA;KAAA;KAAA;IAAA;IAAA,SAAA,CAAA,cAAA;IAAA,UAAA,CAAA,gBAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAA+C;IAAe,UAAA;IAAA,QAAA,CAAA,WAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAChH;IAAe,UAAA;IAAA,QAAA,CAAA,QAAA,SAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAa,UAAA;IAAA,QAAA,CAAA,UAAA,OAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAW,UAAA;IAAA,QAAA,CAAA,QAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAQ,UAAA;IAAA,QAAA,CAAA,WAAA,OAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MADjC;IAAQ,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAwB;IAAa,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAY,MAAA;GAAA;GAAA;IAAA,MAAA;IAAA,MAAE;IAAc,MAAA;GAAA;EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AA6DtF,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAa,YAAA,CAAA;EA/DzB,MAAA;EAAU,MAAA,CAAA;GACT,iBAAiB,wBAAwB;GACzC,SAAS;IAAC;IAAY;IAAU;IAAsB;IAAe;IAAc;IAAgB;IACjG;IAAiB;IAAe;IAAa;GAAQ;GACvD,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0DX,CAAA"}