@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 +74 -2
- package/fesm2022/juice-js-audit-access-log.page-CrH7ib4K.mjs +291 -0
- package/fesm2022/juice-js-audit-access-log.page-CrH7ib4K.mjs.map +1 -0
- package/fesm2022/juice-js-audit-access-logs.page-qVBQLq2K.mjs +588 -0
- package/fesm2022/juice-js-audit-access-logs.page-qVBQLq2K.mjs.map +1 -0
- package/fesm2022/juice-js-audit-data-change.page-CuI9ccJb.mjs +269 -0
- package/fesm2022/juice-js-audit-data-change.page-CuI9ccJb.mjs.map +1 -0
- package/fesm2022/juice-js-audit-data-changes.page-C-k_Shce.mjs +435 -0
- package/fesm2022/juice-js-audit-data-changes.page-C-k_Shce.mjs.map +1 -0
- package/fesm2022/juice-js-audit-overview.page-DnP722u2.mjs +282 -0
- package/fesm2022/juice-js-audit-overview.page-DnP722u2.mjs.map +1 -0
- package/fesm2022/juice-js-audit-paging-D5-1J1rG.mjs +61 -0
- package/fesm2022/juice-js-audit-paging-D5-1J1rG.mjs.map +1 -0
- package/fesm2022/juice-js-audit-trace.page-Df6Sy2x8.mjs +229 -0
- package/fesm2022/juice-js-audit-trace.page-Df6Sy2x8.mjs.map +1 -0
- package/fesm2022/juice-js-audit-ui-DnfhiB6p.mjs +1123 -0
- package/fesm2022/juice-js-audit-ui-DnfhiB6p.mjs.map +1 -0
- package/fesm2022/juice-js-audit.mjs +313 -0
- package/fesm2022/juice-js-audit.mjs.map +1 -0
- package/package.json +45 -4
- package/styles/_audit.scss +139 -0
- package/types/juice-js-audit.d.ts +216 -0
package/README.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @juice-js/audit
|
|
2
2
|
|
|
3
|
-
|
|
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"}
|