@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-audit-ui-DnfhiB6p.mjs","names":[],"sources":["../../../../projects/juice-js/audit/src/lib/shared/resource.ts","../../../../projects/juice-js/audit/src/lib/shared/format.ts","../../../../projects/juice-js/audit/src/lib/shared/ui.ts"],"sourcesContent":["import { ResourceRef, Signal, computed, isSignal } from '@angular/core';\nimport { RxResourceOptions, rxResource } from '@angular/core/rxjs-interop';\n\n/** A resource whose `value()` is `undefined` while its request failed, instead of throwing. */\nexport type ApiResource<T> = Omit<ResourceRef<T | undefined>, 'value'> & { readonly value: Signal<T | undefined> };\n\n/**\n * An `rxResource` for an API read. Angular's `value()` throws while the resource is in error, so a failed request (a\n * trace not found, a 500, the API unreachable) would break the page's rendering, and with it every overlay such as a\n * select's options. Here `value()` reads `undefined` then, and `error()` says why.\n */\nexport function apiResource<T, P>(options: RxResourceOptions<T, P>): ApiResource<T> {\n const ref = rxResource<T, P>(options);\n const value = computed(() => (ref.hasValue() ? ref.value() : undefined));\n return new Proxy(ref, {\n get(target, prop) {\n if (prop === 'value') return value;\n const member = Reflect.get(target, prop, target);\n return typeof member === 'function' && !isSignal(member) ? member.bind(target) : member;\n },\n }) as unknown as ApiResource<T>;\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { JsonValue } from '../core/models';\n\n/** 0.8 ms, 120 ms, 1.42 s, 2 m 05 s. */\nexport function formatDuration(ms: number | null | undefined): string {\n if (ms === null || ms === undefined || Number.isNaN(ms)) return '—';\n if (ms < 1000) return `${Math.round(ms)} ms`;\n if (ms < 60_000) return `${(ms / 1000).toFixed(2)} s`;\n const s = Math.round(ms / 1000);\n return `${Math.floor(s / 60)} m ${String(s % 60).padStart(2, '0')} s`;\n}\n\n/** A JSON value as one line: strings bare, everything else as JSON. */\nexport function formatValue(value: JsonValue | undefined): string {\n if (value === undefined) return '';\n if (value === null) return 'null';\n return typeof value === 'string' ? value : JSON.stringify(value);\n}\n\n/** The keys of a changed row, `Id=42, Tenant=a`, from the JSON object the API returns. */\nexport function formatKeys(keys: JsonValue): string {\n if (keys && typeof keys === 'object' && !Array.isArray(keys)) {\n return Object.entries(keys).map(([k, v]) => `${k}=${formatValue(v)}`).join(', ');\n }\n return formatValue(keys);\n}\n\n/** Text that may be JSON, indented when it is. */\nexport function prettyJson(text: string | null | undefined): string {\n if (!text) return '';\n try { return JSON.stringify(JSON.parse(text), null, 2); } catch { return text; }\n}\n\n/** A status's badge: 2xx and 3xx ok, 4xx a warning, 5xx an error. */\nexport function statusClass(status: number | null | undefined): string {\n if (!status) return 'ja-tone-none';\n return status >= 500 ? 'ja-tone-bad' : status >= 400 ? 'ja-tone-warn' : 'ja-tone-ok';\n}\n\n/** Inserted, Modified, Deleted (and anything else an app reports). */\nexport function actionClass(action: string): string {\n const a = action.toLowerCase();\n return a.startsWith('insert') || a === 'added' ? 'ja-tone-ok'\n : a.startsWith('delete') ? 'ja-tone-bad'\n : a.startsWith('modif') || a.startsWith('update') ? 'ja-tone-info' : 'ja-tone-none';\n}\n\n@Pipe({ name: 'jaDuration' })\nexport class DurationPipe implements PipeTransform {\n transform(ms: number | null | undefined) { return formatDuration(ms); }\n}\n\n@Pipe({ name: 'jaKeys' })\nexport class KeysPipe implements PipeTransform {\n transform(keys: JsonValue) { return formatKeys(keys); }\n}\n\n@Pipe({ name: 'jaPretty' })\nexport class PrettyJsonPipe implements PipeTransform {\n transform(text: string | null | undefined) { return prettyJson(text); }\n}\n\n/** Shows a request's failure as a short line instead of a broken page. */\nexport function problemText(error: unknown): string {\n const e = error as { status?: number; error?: { title?: string; detail?: string } } | undefined;\n if (!e) return '';\n if (e.status === 0) return 'The audit API cannot be reached.';\n if (e.status === 401 || e.status === 403) return 'You are not allowed to read the audit records.';\n return [e.error?.title, e.error?.detail].filter(Boolean).join(': ') || `Request failed (${e.status}).`;\n}\n","import { DatePipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatSelectModule } from '@angular/material/select';\nimport { Router, RouterLink } from '@angular/router';\nimport { AUDIT_RANGES, AuditContextService } from '../core/context.service';\nimport { AccessLogSummary, DataChangeSummary, JsonValue } from '../core/models';\nimport { AuditLinkPipe, AuditLinks } from '../core/options';\nimport { DurationPipe, KeysPipe, actionClass, formatValue, statusClass } from './format';\n\n/**\n * The bar on top of every page: its title, the time window, and a lookup that opens everything audited under a trace\n * id.\n */\n@Component({\n selector: 'ja-context-bar',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [FormsModule, MatSelectModule, MatIconModule],\n template: `\n <div class=\"ja-context\">\n <ng-content />\n <span class=\"ja-spacer\"></span>\n @if (showRange()) {\n <mat-form-field subscriptSizing=\"dynamic\" class=\"ja-field-sm\">\n <mat-label>Time range</mat-label>\n <mat-select [value]=\"ctx.range()\" (valueChange)=\"ctx.set({ range: $event })\">\n @for (r of ranges; track r) { <mat-option [value]=\"r\">Last {{ r }}</mat-option> }\n @if (ctx.range() === 'custom') { <mat-option value=\"custom\" disabled>Custom window</mat-option> }\n </mat-select>\n </mat-form-field>\n }\n <form class=\"ja-lookup\" (ngSubmit)=\"lookup()\">\n <mat-icon>search</mat-icon>\n <input name=\"trace\" [(ngModel)]=\"trace\" placeholder=\"Trace ID\" aria-label=\"Open the audit records of a trace\">\n </form>\n </div>`,\n})\nexport class AuditContextBar {\n protected readonly ctx = inject(AuditContextService);\n private readonly router = inject(Router);\n private readonly links = inject(AuditLinks);\n /** A page that is not about a window (one record, one trace) hides the range. */\n readonly showRange = input(true);\n protected readonly ranges = Object.keys(AUDIT_RANGES);\n protected trace = '';\n\n lookup() {\n const id = this.trace.trim();\n if (id) void this.router.navigate(this.links.to('traces', id), { queryParams: this.ctx.keep() });\n }\n}\n\n/** An HTTP status (or the outcome of a message or job) as a badge. */\n@Component({\n selector: 'ja-status',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<span class=\"ja-badge\" [class]=\"tone()\">{{ status() || '—' }}</span>`,\n})\nexport class StatusBadge {\n readonly status = input<number | null | undefined>();\n protected readonly tone = computed(() => statusClass(this.status()));\n}\n\n/** Inserted, Modified or Deleted as a badge. */\n@Component({\n selector: 'ja-action',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `<span class=\"ja-badge\" [class]=\"tone()\">{{ action() }}</span>`,\n})\nexport class ActionBadge {\n readonly action = input.required<string>();\n protected readonly tone = computed(() => actionClass(this.action()));\n}\n\n/** A trace id: everything audited under it, and the host's tracing UI when it gave a link (provideJuiceAudit). */\n@Component({\n selector: 'ja-trace-ref',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, AuditLinkPipe],\n template: `\n @if (traceId(); as id) {\n <a class=\"ja-mono\" [routerLink]=\"['traces', id] | jaLink\" [queryParams]=\"ctx.keep()\" [attr.title]=\"'Everything audited under trace ' + id\">{{ short() ? id.slice(0, 12) + '…' : id }}</a>\n @if (external(); as x) {\n @if ('href' in x) { <a class=\"ja-external\" [href]=\"x.href\" target=\"_blank\" rel=\"noopener\" title=\"Open the trace\">spans ↗</a> }\n @else { <a class=\"ja-external\" [routerLink]=\"x.route\" title=\"Open the trace\">spans ↗</a> }\n }\n } @else { <span class=\"ja-muted\">—</span> }`,\n})\nexport class TraceRef {\n protected readonly ctx = inject(AuditContextService);\n private readonly links = inject(AuditLinks);\n readonly traceId = input<string | null | undefined>();\n readonly short = input(true);\n protected readonly external = computed(() => this.links.externalTrace(this.traceId()));\n}\n\n/** Access records as a table, newest first; each opens its detail. */\n@Component({\n selector: 'ja-access-log-table',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, DatePipe, AuditLinkPipe, DurationPipe, StatusBadge, TraceRef],\n template: `\n <table class=\"ja-table\">\n <thead><tr><th>Time</th><th>User</th><th>Action</th><th>Status</th><th>Duration</th>@if (!compact()) { <th>App</th><th>Trace</th> }</tr></thead>\n <tbody>\n @for (r of rows(); track r.id) {\n <tr>\n <td class=\"ja-nowrap\">{{ r.dateTime | date: 'MMM d, HH:mm:ss' }}</td>\n <td>{{ r.user || '—' }}@if (r.tenantId) { <div class=\"ja-muted ja-small\">{{ r.tenantId }}</div> }</td>\n <td><a [routerLink]=\"['access-logs', r.id] | jaLink\" [queryParams]=\"ctx.keep()\">{{ r.action }}</a>\n @if (r.method || r.path) { <div class=\"ja-muted ja-small ja-mono\">{{ r.method }} {{ r.path }}</div> }\n @if (r.isRestricted) { <span class=\"ja-badge ja-tone-warn\">restricted</span> }</td>\n <td><ja-status [status]=\"r.status\" /></td>\n <td class=\"ja-nowrap\">@if (longest() > 0 && r.elapsedMs !== null) { <span class=\"ja-bar\" [style.width.px]=\"60 * r.elapsedMs / longest()\"></span> }{{ r.elapsedMs | jaDuration }}</td>\n @if (!compact()) {\n <td class=\"ja-muted\">{{ r.app || '—' }}</td>\n <td class=\"ja-nowrap\"><ja-trace-ref [traceId]=\"r.traceId\" /></td>\n }\n </tr>\n }\n </tbody>\n </table>`,\n})\nexport class AccessLogTable {\n protected readonly ctx = inject(AuditContextService);\n readonly rows = input.required<AccessLogSummary[]>();\n readonly compact = input(false);\n protected readonly longest = computed(() => Math.max(0, ...this.rows().map(r => r.elapsedMs ?? 0)));\n}\n\n/** Data changes as a table, newest first; each opens its before and after. */\n@Component({\n selector: 'ja-data-change-table',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [RouterLink, DatePipe, AuditLinkPipe, KeysPipe, ActionBadge, TraceRef],\n template: `\n <table class=\"ja-table\">\n <thead><tr><th>Time</th><th>User</th><th>Change</th><th>Table</th><th>Keys</th>@if (!compact()) { <th>Trace</th> }</tr></thead>\n <tbody>\n @for (c of rows(); track c.id) {\n <tr>\n <td class=\"ja-nowrap\"><a [routerLink]=\"['data-changes', c.id] | jaLink\" [queryParams]=\"ctx.keep()\">{{ c.dateTime | date: 'MMM d, HH:mm:ss' }}</a></td>\n <td>{{ c.user || '—' }}@if (c.tenantId) { <div class=\"ja-muted ja-small\">{{ c.tenantId }}</div> }</td>\n <td><ja-action [action]=\"c.action\" /></td>\n <td class=\"ja-mono\">{{ c.schema ? c.schema + '.' : '' }}{{ c.table }}@if (c.database) { <div class=\"ja-muted ja-small\">{{ c.database }}</div> }</td>\n <td class=\"ja-mono ja-small\">{{ c.keys | jaKeys }}</td>\n @if (!compact()) { <td class=\"ja-nowrap\"><ja-trace-ref [traceId]=\"c.traceId\" /></td> }\n </tr>\n }\n </tbody>\n </table>`,\n})\nexport class DataChangeTable {\n protected readonly ctx = inject(AuditContextService);\n readonly rows = input.required<DataChangeSummary[]>();\n readonly compact = input(false);\n}\n\ninterface DiffRow { field: string; before: string; after: string; changed: boolean; hasBefore: boolean; hasAfter: boolean; }\n\n/**\n * A changed row's values side by side: before (OriginalValues) and after (CurrentValues), changed fields first. An\n * insert has no before and a delete no after.\n */\n@Component({\n selector: 'ja-change-diff',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (rows().length === 0) { <p class=\"ja-muted\">No values were recorded.</p> }\n @else {\n <table class=\"ja-table ja-diff\">\n <thead><tr><th>Field</th><th>Before</th><th>After</th></tr></thead>\n <tbody>\n @for (r of rows(); track r.field) {\n <tr [class.ja-changed]=\"r.changed\">\n <th class=\"ja-mono\">{{ r.field }}</th>\n <td class=\"ja-mono\" [class.ja-old]=\"r.changed && r.hasBefore\">@if (r.hasBefore) { {{ r.before }} } @else { <span class=\"ja-muted\">—</span> }</td>\n <td class=\"ja-mono\" [class.ja-new]=\"r.changed && r.hasAfter\">@if (r.hasAfter) { {{ r.after }} } @else { <span class=\"ja-muted\">—</span> }</td>\n </tr>\n }\n </tbody>\n </table>\n @if (unchanged() > 0) { <p class=\"ja-muted ja-small\">{{ unchanged() }} field{{ unchanged() === 1 ? '' : 's' }} unchanged, shown after the changed ones.</p> }\n }`,\n})\nexport class ChangeDiff {\n readonly changes = input.required<JsonValue>();\n\n protected readonly rows = computed<DiffRow[]>(() => {\n const changes = asObject(this.changes());\n const before = asObject(changes['OriginalValues'] ?? changes['originalValues'] ?? null);\n const after = asObject(changes['CurrentValues'] ?? changes['currentValues'] ?? null);\n const fields = [...new Set([...Object.keys(before), ...Object.keys(after)])];\n const rows = fields.map(field => {\n const hasBefore = field in before;\n const hasAfter = field in after;\n const b = formatValue(before[field]);\n const a = formatValue(after[field]);\n return { field, before: b, after: a, hasBefore, hasAfter, changed: hasBefore !== hasAfter || b !== a };\n });\n return [...rows.filter(r => r.changed), ...rows.filter(r => !r.changed)];\n });\n protected readonly unchanged = computed(() => this.rows().filter(r => !r.changed).length);\n}\n\nfunction asObject(value: JsonValue | undefined): Record<string, JsonValue> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value : {};\n}\n\n/** Name/value pairs, sorted, empty ones left out. */\n@Component({\n selector: 'ja-kv',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n @if (entries().length === 0) { <p class=\"ja-muted\">{{ empty() }}</p> }\n @else {\n <table class=\"ja-kv\"><tbody>\n @for (e of entries(); track e[0]) { <tr><th>{{ e[0] }}</th><td>{{ e[1] }}</td></tr> }\n </tbody></table>\n }`,\n})\nexport class KeyValueTable {\n readonly values = input<Record<string, string | number | boolean | null | undefined>>({});\n readonly empty = input('None');\n protected readonly entries = computed(() =>\n Object.entries(this.values())\n .filter((e): e is [string, string | number | boolean] => e[1] !== null && e[1] !== undefined && e[1] !== '')\n .sort((a, b) => a[0].localeCompare(b[0])));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { Injectable, InjectionToken, Pipe, computed, inject, makeEnvironmentProviders } from "@angular/core";
|
|
3
|
+
import { HttpClient, HttpParams } from "@angular/common/http";
|
|
4
|
+
import { toSignal } from "@angular/core/rxjs-interop";
|
|
5
|
+
import { NavigationEnd, Router } from "@angular/router";
|
|
6
|
+
import { filter, map, startWith } from "rxjs";
|
|
7
|
+
const AUDIT_OPTIONS = new InjectionToken("AUDIT_OPTIONS", {
|
|
8
|
+
providedIn: "root",
|
|
9
|
+
factory: () => ({
|
|
10
|
+
apiBase: "",
|
|
11
|
+
apiPath: "/api/audit",
|
|
12
|
+
basePath: "/",
|
|
13
|
+
traceLink: null
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
function provideJuiceAudit(options = {}) {
|
|
17
|
+
return makeEnvironmentProviders([{
|
|
18
|
+
provide: AUDIT_OPTIONS,
|
|
19
|
+
useValue: {
|
|
20
|
+
apiBase: options.apiBase ?? "",
|
|
21
|
+
apiPath: options.apiPath ?? "/api/audit",
|
|
22
|
+
basePath: options.basePath ?? "/",
|
|
23
|
+
traceLink: options.traceLink ?? null
|
|
24
|
+
}
|
|
25
|
+
}]);
|
|
26
|
+
}
|
|
27
|
+
function normalizeBasePath(basePath) {
|
|
28
|
+
const trimmed = basePath.trim().replace(/^\/+|\/+$/g, "");
|
|
29
|
+
return trimmed ? `/${trimmed}` : "/";
|
|
30
|
+
}
|
|
31
|
+
var AuditLinks = class AuditLinks {
|
|
32
|
+
options = inject(AUDIT_OPTIONS);
|
|
33
|
+
base = normalizeBasePath(this.options.basePath);
|
|
34
|
+
to(...segments) {
|
|
35
|
+
return [this.base, ...segments];
|
|
36
|
+
}
|
|
37
|
+
externalTrace(traceId) {
|
|
38
|
+
if (!traceId || !this.options.traceLink) return null;
|
|
39
|
+
const link = this.options.traceLink(traceId);
|
|
40
|
+
if (!link) return null;
|
|
41
|
+
return typeof link === "string" ? { href: link } : { route: link };
|
|
42
|
+
}
|
|
43
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
44
|
+
minVersion: "12.0.0",
|
|
45
|
+
version: "22.2.4",
|
|
46
|
+
ngImport: i0,
|
|
47
|
+
type: AuditLinks,
|
|
48
|
+
deps: [],
|
|
49
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
50
|
+
});
|
|
51
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
52
|
+
minVersion: "12.0.0",
|
|
53
|
+
version: "22.2.4",
|
|
54
|
+
ngImport: i0,
|
|
55
|
+
type: AuditLinks,
|
|
56
|
+
providedIn: "root"
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
60
|
+
minVersion: "12.0.0",
|
|
61
|
+
version: "22.2.4",
|
|
62
|
+
ngImport: i0,
|
|
63
|
+
type: AuditLinks,
|
|
64
|
+
decorators: [{
|
|
65
|
+
type: Injectable,
|
|
66
|
+
args: [{ providedIn: "root" }]
|
|
67
|
+
}]
|
|
68
|
+
});
|
|
69
|
+
var AuditLinkPipe = class AuditLinkPipe {
|
|
70
|
+
links = inject(AuditLinks);
|
|
71
|
+
transform(segments) {
|
|
72
|
+
return this.links.to(...segments);
|
|
73
|
+
}
|
|
74
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
75
|
+
minVersion: "12.0.0",
|
|
76
|
+
version: "22.2.4",
|
|
77
|
+
ngImport: i0,
|
|
78
|
+
type: AuditLinkPipe,
|
|
79
|
+
deps: [],
|
|
80
|
+
target: i0.ɵɵFactoryTarget.Pipe
|
|
81
|
+
});
|
|
82
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({
|
|
83
|
+
minVersion: "14.0.0",
|
|
84
|
+
version: "22.2.4",
|
|
85
|
+
ngImport: i0,
|
|
86
|
+
type: AuditLinkPipe,
|
|
87
|
+
isStandalone: true,
|
|
88
|
+
name: "jaLink"
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
92
|
+
minVersion: "12.0.0",
|
|
93
|
+
version: "22.2.4",
|
|
94
|
+
ngImport: i0,
|
|
95
|
+
type: AuditLinkPipe,
|
|
96
|
+
decorators: [{
|
|
97
|
+
type: Pipe,
|
|
98
|
+
args: [{ name: "jaLink" }]
|
|
99
|
+
}]
|
|
100
|
+
});
|
|
101
|
+
function accessLogSummary(d) {
|
|
102
|
+
return {
|
|
103
|
+
id: d.id,
|
|
104
|
+
dateTime: d.dateTime,
|
|
105
|
+
user: d.user,
|
|
106
|
+
tenantId: d.tenantId,
|
|
107
|
+
action: d.action,
|
|
108
|
+
method: d.request?.method ?? null,
|
|
109
|
+
path: d.request?.path ?? null,
|
|
110
|
+
status: d.response?.status ?? null,
|
|
111
|
+
elapsedMs: d.response?.elapsedMs ?? null,
|
|
112
|
+
app: d.server?.app ?? null,
|
|
113
|
+
traceId: d.traceId,
|
|
114
|
+
isRestricted: d.isRestricted
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
var AuditApiService = class AuditApiService {
|
|
118
|
+
http = inject(HttpClient);
|
|
119
|
+
base = (() => {
|
|
120
|
+
const o = inject(AUDIT_OPTIONS);
|
|
121
|
+
return `${o.apiBase.replace(/\/+$/, "")}/${o.apiPath.replace(/^\/+|\/+$/g, "")}`;
|
|
122
|
+
})();
|
|
123
|
+
get(path, query = {}) {
|
|
124
|
+
let params = new HttpParams();
|
|
125
|
+
for (const [key, value] of Object.entries(query)) {
|
|
126
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
127
|
+
params = params.set(key, String(value));
|
|
128
|
+
}
|
|
129
|
+
return this.http.get(`${this.base}${path}`, { params });
|
|
130
|
+
}
|
|
131
|
+
accessLogs(query) {
|
|
132
|
+
return this.get("/access-logs", query);
|
|
133
|
+
}
|
|
134
|
+
accessLog(id) {
|
|
135
|
+
return this.get(`/access-logs/${id}`);
|
|
136
|
+
}
|
|
137
|
+
dataChanges(query) {
|
|
138
|
+
return this.get("/data-changes", query);
|
|
139
|
+
}
|
|
140
|
+
dataChange(id) {
|
|
141
|
+
return this.get(`/data-changes/${id}`);
|
|
142
|
+
}
|
|
143
|
+
trace(traceId) {
|
|
144
|
+
return this.get(`/traces/${encodeURIComponent(traceId)}`);
|
|
145
|
+
}
|
|
146
|
+
overview(query) {
|
|
147
|
+
return this.get("/overview", query);
|
|
148
|
+
}
|
|
149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
150
|
+
minVersion: "12.0.0",
|
|
151
|
+
version: "22.2.4",
|
|
152
|
+
ngImport: i0,
|
|
153
|
+
type: AuditApiService,
|
|
154
|
+
deps: [],
|
|
155
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
156
|
+
});
|
|
157
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
158
|
+
minVersion: "12.0.0",
|
|
159
|
+
version: "22.2.4",
|
|
160
|
+
ngImport: i0,
|
|
161
|
+
type: AuditApiService,
|
|
162
|
+
providedIn: "root"
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
166
|
+
minVersion: "12.0.0",
|
|
167
|
+
version: "22.2.4",
|
|
168
|
+
ngImport: i0,
|
|
169
|
+
type: AuditApiService,
|
|
170
|
+
decorators: [{
|
|
171
|
+
type: Injectable,
|
|
172
|
+
args: [{ providedIn: "root" }]
|
|
173
|
+
}]
|
|
174
|
+
});
|
|
175
|
+
const AUDIT_RANGES = {
|
|
176
|
+
"1h": 36e5,
|
|
177
|
+
"6h": 216e5,
|
|
178
|
+
"24h": 864e5,
|
|
179
|
+
"7d": 6048e5,
|
|
180
|
+
"30d": 2592e6
|
|
181
|
+
};
|
|
182
|
+
var AuditContextService = class AuditContextService {
|
|
183
|
+
router = inject(Router);
|
|
184
|
+
params = toSignal(this.router.events.pipe(filter((e) => e instanceof NavigationEnd), startWith(null), map(() => this.leaf().snapshot.queryParams)), { initialValue: {} });
|
|
185
|
+
range = computed(() => {
|
|
186
|
+
const p = this.params();
|
|
187
|
+
return p["from"] && p["to"] ? "custom" : p["range"] ?? "24h";
|
|
188
|
+
}, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
|
|
189
|
+
absolute = computed(() => {
|
|
190
|
+
const p = this.params();
|
|
191
|
+
return p["from"] && p["to"] ? {
|
|
192
|
+
from: p["from"],
|
|
193
|
+
to: p["to"]
|
|
194
|
+
} : null;
|
|
195
|
+
}, ...ngDevMode ? [{ debugName: "absolute" }] : /* istanbul ignore next */ []);
|
|
196
|
+
window() {
|
|
197
|
+
const absolute = this.absolute();
|
|
198
|
+
if (absolute) return absolute;
|
|
199
|
+
const to = Date.now();
|
|
200
|
+
return {
|
|
201
|
+
from: new Date(to - (AUDIT_RANGES[this.range()] ?? AUDIT_RANGES["24h"])).toISOString(),
|
|
202
|
+
to: new Date(to).toISOString()
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
keep(extra = {}) {
|
|
206
|
+
const p = this.params();
|
|
207
|
+
const context = {};
|
|
208
|
+
for (const key of [
|
|
209
|
+
"range",
|
|
210
|
+
"from",
|
|
211
|
+
"to"
|
|
212
|
+
]) if (p[key]) context[key] = p[key];
|
|
213
|
+
return {
|
|
214
|
+
...context,
|
|
215
|
+
...extra
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
set(change) {
|
|
219
|
+
const queryParams = { ...change };
|
|
220
|
+
if (change.range) Object.assign(queryParams, {
|
|
221
|
+
from: null,
|
|
222
|
+
to: null
|
|
223
|
+
});
|
|
224
|
+
this.router.navigate([], {
|
|
225
|
+
relativeTo: this.leaf(),
|
|
226
|
+
queryParams,
|
|
227
|
+
queryParamsHandling: "merge"
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
filter(change) {
|
|
231
|
+
this.router.navigate([], {
|
|
232
|
+
relativeTo: this.leaf(),
|
|
233
|
+
queryParams: change,
|
|
234
|
+
queryParamsHandling: "merge",
|
|
235
|
+
replaceUrl: true
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
leaf() {
|
|
239
|
+
let route = this.router.routerState.root;
|
|
240
|
+
while (route.firstChild) route = route.firstChild;
|
|
241
|
+
return route;
|
|
242
|
+
}
|
|
243
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
244
|
+
minVersion: "12.0.0",
|
|
245
|
+
version: "22.2.4",
|
|
246
|
+
ngImport: i0,
|
|
247
|
+
type: AuditContextService,
|
|
248
|
+
deps: [],
|
|
249
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
250
|
+
});
|
|
251
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
252
|
+
minVersion: "12.0.0",
|
|
253
|
+
version: "22.2.4",
|
|
254
|
+
ngImport: i0,
|
|
255
|
+
type: AuditContextService,
|
|
256
|
+
providedIn: "root"
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
260
|
+
minVersion: "12.0.0",
|
|
261
|
+
version: "22.2.4",
|
|
262
|
+
ngImport: i0,
|
|
263
|
+
type: AuditContextService,
|
|
264
|
+
decorators: [{
|
|
265
|
+
type: Injectable,
|
|
266
|
+
args: [{ providedIn: "root" }]
|
|
267
|
+
}]
|
|
268
|
+
});
|
|
269
|
+
function page(path, title, load) {
|
|
270
|
+
return {
|
|
271
|
+
path,
|
|
272
|
+
title: `${title} · Juice Audit`,
|
|
273
|
+
loadComponent: load
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const AUDIT_ROUTES = [
|
|
277
|
+
page("", "Overview", () => import("./juice-js-audit-overview.page-DnP722u2.mjs").then((m) => m.OverviewPage)),
|
|
278
|
+
page("access-logs", "Access records", () => import("./juice-js-audit-access-logs.page-qVBQLq2K.mjs").then((m) => m.AccessLogsPage)),
|
|
279
|
+
page("access-logs/:id", "Access record", () => import("./juice-js-audit-access-log.page-CrH7ib4K.mjs").then((m) => m.AccessLogPage)),
|
|
280
|
+
page("data-changes", "Data changes", () => import("./juice-js-audit-data-changes.page-C-k_Shce.mjs").then((m) => m.DataChangesPage)),
|
|
281
|
+
page("data-changes/:id", "Data change", () => import("./juice-js-audit-data-change.page-CuI9ccJb.mjs").then((m) => m.DataChangePage)),
|
|
282
|
+
page("traces/:traceId", "Trace", () => import("./juice-js-audit-trace.page-Df6Sy2x8.mjs").then((m) => m.TracePage))
|
|
283
|
+
];
|
|
284
|
+
function auditMenus(basePath = "/", order = 1) {
|
|
285
|
+
const base = normalizeBasePath(basePath);
|
|
286
|
+
const link = (path) => base === "/" ? `/${path}` : path ? `${base}/${path}` : base;
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
id: "audit-overview",
|
|
290
|
+
label: "Audit",
|
|
291
|
+
icon: "policy",
|
|
292
|
+
routerLink: link("")
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
id: "audit-access-logs",
|
|
296
|
+
label: "Access records",
|
|
297
|
+
icon: "manage_search",
|
|
298
|
+
routerLink: link("access-logs")
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "audit-data-changes",
|
|
302
|
+
label: "Data changes",
|
|
303
|
+
icon: "difference",
|
|
304
|
+
routerLink: link("data-changes")
|
|
305
|
+
}
|
|
306
|
+
].map((menu, index) => ({
|
|
307
|
+
...menu,
|
|
308
|
+
order: order + index
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
export { AUDIT_OPTIONS, AUDIT_RANGES, AUDIT_ROUTES, AuditApiService, AuditContextService, AuditLinkPipe, AuditLinks, accessLogSummary, auditMenus, normalizeBasePath, provideJuiceAudit };
|
|
312
|
+
|
|
313
|
+
//# sourceMappingURL=juice-js-audit.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juice-js-audit.mjs","names":[],"sources":["../../../../projects/juice-js/audit/src/lib/core/options.ts","../../../../projects/juice-js/audit/src/lib/core/models.ts","../../../../projects/juice-js/audit/src/lib/core/api.service.ts","../../../../projects/juice-js/audit/src/lib/core/context.service.ts","../../../../projects/juice-js/audit/src/lib/audit.routes.ts"],"sourcesContent":["import { EnvironmentProviders, Injectable, InjectionToken, Pipe, PipeTransform, inject, makeEnvironmentProviders } from '@angular/core';\n\n/** How the host app mounts Juice Audit. */\nexport interface AuditOptions {\n /** The origin of the API that maps MapJuiceAudit(); empty when it serves the app (same origin). */\n apiBase: string;\n /** Where MapJuiceAudit() mapped the API on that origin (its `prefix`). */\n apiPath: string;\n /** Where the host mounts {@link AUDIT_ROUTES}: `/` for the whole app, or a path such as `/audit`. */\n basePath: string;\n /**\n * A link to a trace in a tracing UI, shown beside the trace id of each record: for the Juice Observability pages\n * mounted at `/observability`, `id => ['/observability/traces', id]`. A string is an external URL. Without it, no link.\n */\n traceLink?: ((traceId: string) => unknown[] | string | null) | null;\n}\n\nexport const AUDIT_OPTIONS = new InjectionToken<AuditOptions>('AUDIT_OPTIONS', {\n providedIn: 'root',\n factory: () => ({ apiBase: '', apiPath: '/api/audit', basePath: '/', traceLink: null }),\n});\n\n/** Configures the pages: where the API is, where the host mounted the routes, and where a trace opens. */\nexport function provideJuiceAudit(options: Partial<AuditOptions> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: AUDIT_OPTIONS,\n useValue: {\n apiBase: options.apiBase ?? '',\n apiPath: options.apiPath ?? '/api/audit',\n basePath: options.basePath ?? '/',\n traceLink: options.traceLink ?? null,\n } satisfies AuditOptions,\n },\n ]);\n}\n\n/** `/audit/` → `/audit`; `` and `/` → `/`. */\nexport function normalizeBasePath(basePath: string): string {\n const trimmed = basePath.trim().replace(/^\\/+|\\/+$/g, '');\n return trimmed ? `/${trimmed}` : '/';\n}\n\n/** Router links to the pages, under the host's mount path. */\n@Injectable({ providedIn: 'root' })\nexport class AuditLinks {\n private readonly options = inject(AUDIT_OPTIONS);\n readonly base = normalizeBasePath(this.options.basePath);\n\n /** `to('access-logs', id)` → `['/audit', 'access-logs', id]`. */\n to(...segments: unknown[]): unknown[] { return [this.base, ...segments]; }\n\n /** The host's link to a trace in its tracing UI, if it gave one. */\n externalTrace(traceId: string | null | undefined): { route: unknown[] } | { href: string } | null {\n if (!traceId || !this.options.traceLink) return null;\n const link = this.options.traceLink(traceId);\n if (!link) return null;\n return typeof link === 'string' ? { href: link } : { route: link };\n }\n}\n\n/** `['access-logs', id] | jaLink`: the same as {@link AuditLinks.to} in a template. */\n@Pipe({ name: 'jaLink' })\nexport class AuditLinkPipe implements PipeTransform {\n private readonly links = inject(AuditLinks);\n transform(segments: unknown[]): unknown[] { return this.links.to(...segments); }\n}\n","/** The audit API's shapes (Juice.Audit.Api, MapJuiceAudit()). Times are ISO 8601. */\n\nexport interface AuditPage<T> { items: T[]; nextCursor?: string | null; }\n\n/** One audited HTTP request, message, mediator request or notification. */\nexport interface AccessLogSummary {\n id: string;\n dateTime: string;\n user: string | null;\n tenantId: string | null;\n action: string;\n /** The HTTP method, or MESSAGE, REQUEST or NOTIFICATION for work outside a request. */\n method: string | null;\n path: string | null;\n status: number | null;\n elapsedMs: number | null;\n app: string | null;\n traceId: string | null;\n isRestricted: boolean;\n}\n\nexport interface AccessLogDetail {\n id: string;\n dateTime: string;\n user: string | null;\n tenantId: string | null;\n action: string;\n traceId: string | null;\n isRestricted: boolean;\n metadata: Record<string, string | null>;\n request: {\n method: string | null; scheme: string | null; host: string | null; path: string | null; query: string | null;\n headers: string | null; data: string | null; remoteIp: string | null; zone: string | null;\n } | null;\n response: {\n status: number | null; elapsedMs: number | null; message: string | null; error: string | null;\n headers: string | null; data: string | null;\n } | null;\n server: { app: string | null; appVersion: string | null; machine: string | null; os: string | null } | null;\n /** Data changes recorded under the same trace id. */\n dataChanges: number;\n}\n\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\n\n/** A row change: inserted, modified or deleted, with its keys. */\nexport interface DataChangeSummary {\n id: string;\n dateTime: string;\n user: string | null;\n tenantId: string | null;\n action: string;\n database: string | null;\n schema: string | null;\n table: string;\n keys: JsonValue;\n traceId: string | null;\n}\n\nexport interface DataChangeDetail extends DataChangeSummary {\n /** `{ OriginalValues: {…}, CurrentValues: {…} }`, as the audited app reported them. */\n changes: JsonValue;\n}\n\nexport interface AuditTrace {\n traceId: string;\n accessLogs: AccessLogDetail[];\n dataChanges: DataChangeDetail[];\n /** More records share the trace than the API returns. */\n truncated: boolean;\n}\n\nexport interface AuditCount { name: string; count: number; errors: number; }\n\nexport interface AuditOverview {\n from: string;\n to: string;\n requests: number;\n clientErrors: number;\n serverErrors: number;\n restricted: number;\n dataChanges: number;\n statuses: AuditCount[];\n apps: AuditCount[];\n users: AuditCount[];\n tables: AuditCount[];\n changeActions: AuditCount[];\n recentErrors: AccessLogSummary[];\n slowest: AccessLogSummary[];\n}\n\n/** An access record's list row, from its detail (the trace endpoint returns details). */\nexport function accessLogSummary(d: AccessLogDetail): AccessLogSummary {\n return {\n id: d.id, dateTime: d.dateTime, user: d.user, tenantId: d.tenantId, action: d.action,\n method: d.request?.method ?? null, path: d.request?.path ?? null, status: d.response?.status ?? null,\n elapsedMs: d.response?.elapsedMs ?? null, app: d.server?.app ?? null, traceId: d.traceId, isRestricted: d.isRestricted,\n };\n}\n","import { HttpClient, HttpParams } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { AUDIT_OPTIONS } from './options';\nimport {\n AccessLogDetail, AccessLogSummary, AuditOverview, AuditPage, AuditTrace, DataChangeDetail, DataChangeSummary,\n} from './models';\n\nexport type AuditQuery = Record<string, string | number | boolean | null | undefined>;\n\n/**\n * The audit API that Juice.Audit.Api maps with MapJuiceAudit(). It is scoped to the caller's tenant on the server, and\n * the host's bearer-token interceptor authenticates the calls.\n */\n@Injectable({ providedIn: 'root' })\nexport class AuditApiService {\n private readonly http = inject(HttpClient);\n private readonly base = (() => {\n const o = inject(AUDIT_OPTIONS);\n return `${o.apiBase.replace(/\\/+$/, '')}/${o.apiPath.replace(/^\\/+|\\/+$/g, '')}`;\n })();\n\n private get<T>(path: string, query: AuditQuery = {}): Observable<T> {\n let params = new HttpParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === null || value === undefined || value === '') continue;\n params = params.set(key, String(value));\n }\n return this.http.get<T>(`${this.base}${path}`, { params });\n }\n\n /** Access records, newest first; pass the previous page's `nextCursor` as `cursor` for the next. */\n accessLogs(query: AuditQuery) { return this.get<AuditPage<AccessLogSummary>>('/access-logs', query); }\n accessLog(id: string) { return this.get<AccessLogDetail>(`/access-logs/${id}`); }\n /** Data changes, newest first, paged like {@link accessLogs}. */\n dataChanges(query: AuditQuery) { return this.get<AuditPage<DataChangeSummary>>('/data-changes', query); }\n dataChange(id: string) { return this.get<DataChangeDetail>(`/data-changes/${id}`); }\n trace(traceId: string) { return this.get<AuditTrace>(`/traces/${encodeURIComponent(traceId)}`); }\n overview(query: AuditQuery) { return this.get<AuditOverview>('/overview', query); }\n}\n","import { Injectable, computed, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router';\nimport { filter, map, startWith } from 'rxjs';\n\nexport const AUDIT_RANGES: Record<string, number> = {\n '1h': 3_600_000, '6h': 6 * 3_600_000, '24h': 24 * 3_600_000, '7d': 7 * 24 * 3_600_000, '30d': 30 * 24 * 3_600_000,\n};\n\n/**\n * The time window that follows the user between the audit pages. It lives in the URL (`range`, or `from` and `to`), so\n * every page is a deep link and the back button restores it.\n */\n@Injectable({ providedIn: 'root' })\nexport class AuditContextService {\n private readonly router = inject(Router);\n\n // Created on first use, after the first navigation may already have ended: start from the current URL.\n private readonly params = toSignal(this.router.events.pipe(\n filter(e => e instanceof NavigationEnd),\n startWith(null),\n map(() => this.leaf().snapshot.queryParams),\n ), { initialValue: {} as Params });\n\n readonly range = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? 'custom' : ((p['range'] as string | undefined) ?? '24h');\n });\n readonly absolute = computed(() => {\n const p = this.params();\n return p['from'] && p['to'] ? { from: p['from'] as string, to: p['to'] as string } : null;\n });\n\n /** The window as ISO strings, evaluated now for a relative range. */\n window(): { from: string; to: string } {\n const absolute = this.absolute();\n if (absolute) return absolute;\n const to = Date.now();\n return { from: new Date(to - (AUDIT_RANGES[this.range()] ?? AUDIT_RANGES['24h'])).toISOString(), to: new Date(to).toISOString() };\n }\n\n /** Query parameters for a link: the window plus the target page's own. */\n keep(extra: Params = {}): Params {\n const p = this.params();\n const context: Params = {};\n for (const key of ['range', 'from', 'to']) if (p[key]) context[key] = p[key];\n return { ...context, ...extra };\n }\n\n /** Changes the window on the current page, keeping its other parameters. */\n set(change: { range?: string | null; from?: string | null; to?: string | null }) {\n const queryParams: Params = { ...change };\n if (change.range) Object.assign(queryParams, { from: null, to: null });\n void this.router.navigate([], { relativeTo: this.leaf(), queryParams, queryParamsHandling: 'merge' });\n }\n\n /** Sets the current page's own query parameters (its filters), keeping the window. */\n filter(change: Params) {\n void this.router.navigate([], { relativeTo: this.leaf(), queryParams: change, queryParamsHandling: 'merge', replaceUrl: true });\n }\n\n private leaf(): ActivatedRoute {\n let route = this.router.routerState.root;\n while (route.firstChild) route = route.firstChild;\n return route;\n }\n}\n","import { Routes } from '@angular/router';\nimport { Menus } from '@juice-js/core';\nimport { normalizeBasePath } from './core/options';\n\nfunction page(path: string, title: string, load: Routes[number]['loadComponent']): Routes[number] {\n return { path, title: `${title} · Juice Audit`, loadComponent: load };\n}\n\n/**\n * The audit pages. Detail pages are deep links and carry the time window in their query parameters. The host mounts\n * them at its `basePath` (see {@link provideJuiceAudit}) and adds its own guards.\n */\nexport const AUDIT_ROUTES: Routes = [\n page('', 'Overview', () => import('./pages/overview.page').then(m => m.OverviewPage)),\n page('access-logs', 'Access records', () => import('./pages/access-logs.page').then(m => m.AccessLogsPage)),\n page('access-logs/:id', 'Access record', () => import('./pages/access-log.page').then(m => m.AccessLogPage)),\n page('data-changes', 'Data changes', () => import('./pages/data-changes.page').then(m => m.DataChangesPage)),\n page('data-changes/:id', 'Data change', () => import('./pages/data-change.page').then(m => m.DataChangePage)),\n page('traces/:traceId', 'Trace', () => import('./pages/trace.page').then(m => m.TracePage)),\n];\n\n/**\n * The side menu entries (juice-layout), linking under `basePath`. Their ids start with `audit-`, so they sit beside\n * another package's menus (such as @juice-js/observability's) in one MenuModule.\n */\nexport function auditMenus(basePath = '/', order = 1): Menus {\n const base = normalizeBasePath(basePath);\n const link = (path: string) => (base === '/' ? `/${path}` : path ? `${base}/${path}` : base);\n return [\n { id: 'audit-overview', label: 'Audit', icon: 'policy', routerLink: link('') },\n { id: 'audit-access-logs', label: 'Access records', icon: 'manage_search', routerLink: link('access-logs') },\n { id: 'audit-data-changes', label: 'Data changes', icon: 'difference', routerLink: link('data-changes') },\n ].map((menu, index) => ({ ...menu, order: order + index }));\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juice-js/audit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "Juice Audit pages (overview, access records, data changes, trace) for an Angular app on juice-layout",
|
|
5
|
+
"author": "jack@creatorflow.io",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/creatorflow-io/Juice.Audit/issues"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/creatorflow-io/Juice.Audit#readme",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"provenance": true
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/creatorflow-io/Juice.Audit.git",
|
|
18
|
+
"directory": "console/projects/juice-js/audit"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@angular/cdk": "^22.0.0",
|
|
22
|
+
"@angular/common": "^22.0.0",
|
|
23
|
+
"@angular/core": "^22.0.0",
|
|
24
|
+
"@angular/forms": "^22.0.0",
|
|
25
|
+
"@angular/material": "^22.0.0",
|
|
26
|
+
"@angular/router": "^22.0.0",
|
|
27
|
+
"@juice-js/core": "^2.0.1",
|
|
28
|
+
"rxjs": "^7.8.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "^2.3.0"
|
|
32
|
+
},
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"sass": "./styles/_audit.scss",
|
|
37
|
+
"types": "./types/juice-js-audit.d.ts",
|
|
38
|
+
"default": "./fesm2022/juice-js-audit.mjs"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": {
|
|
41
|
+
"default": "./package.json"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"module": "fesm2022/juice-js-audit.mjs",
|
|
45
|
+
"typings": "types/juice-js-audit.d.ts",
|
|
46
|
+
"type": "module"
|
|
47
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Juice Audit page styles. `@use` the module once, in the app's global styles, and the pages are styled:
|
|
2
|
+
//
|
|
3
|
+
// @use '@juice-js/audit'; // layout, and a default light and dark (.dark) theme
|
|
4
|
+
// :root { @include audit.colors($light-theme); } // optional: colours from the app's own theme
|
|
5
|
+
// .dark { @include audit.colors($dark-theme); } // again under the app's own dark-mode selector
|
|
6
|
+
//
|
|
7
|
+
// colors() writes the --ja-* custom properties the pages read, from an Angular Material (M2) theme: text, background,
|
|
8
|
+
// card and divider from its background and foreground palettes; links and selection from primary; failures from warn.
|
|
9
|
+
// A theme has no slot for "succeeded", "warning" or "modified", so pass those in $tones to change them.
|
|
10
|
+
//
|
|
11
|
+
// Without colors(), the pages use the Juice Console's teal, amber and red theme. It sits under zero-specificity
|
|
12
|
+
// :where() selectors, so the app's colors() wins whatever the order. Every class and property starts with ja- /
|
|
13
|
+
// --ja-, so the pages sit beside other packages' (such as @juice-js/observability's jc-) in one app.
|
|
14
|
+
@use '@angular/material' as mat;
|
|
15
|
+
@use 'sass:color';
|
|
16
|
+
@use 'sass:map';
|
|
17
|
+
@use 'sass:meta';
|
|
18
|
+
|
|
19
|
+
@function _hue($palette, $light, $dark, $is-dark) {
|
|
20
|
+
@return mat.m2-get-color-from-palette($palette, if(sass($is-dark): $dark; else: $light));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// The --ja-* colour properties from an Angular Material (M2) theme. Include it inside a selector: :root for the
|
|
24
|
+
/// default theme, and the app's dark-mode selector (juice-layout: .dark) for the dark one.
|
|
25
|
+
/// @param $theme the app's theme, from mat.m2-define-light-theme or mat.m2-define-dark-theme
|
|
26
|
+
/// @param $tones optional overrides: (ok, warn, bad, info, none), each a colour; backgrounds are tinted from them
|
|
27
|
+
@mixin colors($theme, $tones: ()) {
|
|
28
|
+
$config: mat.m2-get-color-config($theme);
|
|
29
|
+
$is-dark: map.get($config, is-dark);
|
|
30
|
+
$background: map.get($config, background);
|
|
31
|
+
$foreground: map.get($config, foreground);
|
|
32
|
+
$primary: map.get($config, primary);
|
|
33
|
+
$warn: map.get($config, warn);
|
|
34
|
+
$surface: map.get($background, card);
|
|
35
|
+
$tint: if(sass($is-dark): 22%; else: 14%);
|
|
36
|
+
|
|
37
|
+
$defaults: (
|
|
38
|
+
ok: _hue(mat.m2-define-palette(mat.$m2-green-palette), 800, 300, $is-dark),
|
|
39
|
+
warn: if(sass($is-dark): _hue(mat.m2-define-palette(mat.$m2-amber-palette), 300, 300, true); else: _hue(mat.m2-define-palette(mat.$m2-deep-orange-palette), 900, 900, false)),
|
|
40
|
+
bad: _hue($warn, 700, 300, $is-dark),
|
|
41
|
+
info: _hue(mat.m2-define-palette(mat.$m2-blue-palette), 700, 200, $is-dark),
|
|
42
|
+
none: map.get($foreground, secondary-text),
|
|
43
|
+
);
|
|
44
|
+
$tones: map.merge($defaults, $tones);
|
|
45
|
+
|
|
46
|
+
--ja-bg: #{map.get($background, background)};
|
|
47
|
+
--ja-surface: #{$surface};
|
|
48
|
+
--ja-border: #{map.get($foreground, divider)};
|
|
49
|
+
--ja-text: #{map.get($foreground, text)};
|
|
50
|
+
--ja-muted: #{map.get($foreground, secondary-text)};
|
|
51
|
+
--ja-accent: #{_hue($primary, 700, 200, $is-dark)};
|
|
52
|
+
--ja-select: #{color.mix(_hue($primary, 500, 300, $is-dark), $surface, $tint)};
|
|
53
|
+
@each $name, $value in $tones {
|
|
54
|
+
@if meta.type-of($value) == color and color.alpha($value) < 1 {
|
|
55
|
+
// Foreground palette entries are translucent: flatten them on the surface so the tint below is solid.
|
|
56
|
+
$value: color.mix(color.change($value, $alpha: 1), $surface, color.alpha($value) * 100%);
|
|
57
|
+
}
|
|
58
|
+
--ja-#{$name}: #{$value};
|
|
59
|
+
--ja-#{$name}-bg: #{color.mix($value, $surface, $tint)};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@mixin _layout() {
|
|
64
|
+
.ja-muted { color: var(--ja-muted); }
|
|
65
|
+
.ja-small { font-size: 12px; }
|
|
66
|
+
.ja-mono { font-family: 'JetBrains Mono', Consolas, monospace; font-size: 13px; }
|
|
67
|
+
.ja-nowrap { white-space: nowrap; }
|
|
68
|
+
.ja-error, .ja-bad { color: var(--ja-bad); }
|
|
69
|
+
.ja-warn { color: var(--ja-warn); }
|
|
70
|
+
.ja-spacer { flex: 1; }
|
|
71
|
+
.ja-notice { padding: 8px 12px; border-left: 3px solid var(--ja-warn); background: var(--ja-warn-bg); font-size: 13px; }
|
|
72
|
+
.ja-more { display: flex; align-items: center; gap: 12px; padding: 8px 0; font-size: 13px; }
|
|
73
|
+
|
|
74
|
+
.ja-context { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 4px 0 12px; border-bottom: 1px solid var(--ja-border); margin-bottom: 12px; }
|
|
75
|
+
.ja-h-id { font-size: 15px; font-weight: 400; word-break: break-all; }
|
|
76
|
+
.ja-crumb { color: var(--ja-muted); }
|
|
77
|
+
.ja-field-xs { width: 130px; }
|
|
78
|
+
.ja-field-sm { width: 170px; }
|
|
79
|
+
.ja-lookup { display: flex; align-items: center; gap: 4px; border: 1px solid var(--ja-border); border-radius: 6px; padding: 2px 8px; background: var(--ja-surface);
|
|
80
|
+
input { border: 0; outline: 0; background: transparent; color: inherit; width: 260px; font: inherit; padding: 6px 0; }
|
|
81
|
+
mat-icon { color: var(--ja-muted); font-size: 18px; height: 18px; width: 18px; } }
|
|
82
|
+
.ja-filter-form { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 12px; }
|
|
83
|
+
|
|
84
|
+
.ja-card { display: block; background: var(--ja-surface); border: 1px solid var(--ja-border); border-radius: 8px; padding: 12px 16px; margin-bottom: 12px; }
|
|
85
|
+
.ja-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
86
|
+
.ja-grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 12px; }
|
|
87
|
+
.ja-grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }
|
|
88
|
+
.ja-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 12px; }
|
|
89
|
+
a.ja-tile { display: flex; flex-direction: column; gap: 4px; color: inherit; text-decoration: none; strong { font-size: 24px; } }
|
|
90
|
+
a.ja-tile:hover { border-color: var(--ja-accent); text-decoration: none; }
|
|
91
|
+
.ja-summary { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; margin-bottom: 12px; }
|
|
92
|
+
|
|
93
|
+
.ja-badge { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; white-space: nowrap; }
|
|
94
|
+
@each $tone in (ok, warn, bad, info, none) {
|
|
95
|
+
.ja-tone-#{$tone} { background: var(--ja-#{$tone}-bg); color: var(--ja-#{$tone}); }
|
|
96
|
+
.ja-fill-#{$tone} { background: var(--ja-#{$tone}); }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ja-table { width: 100%; border-collapse: collapse; background: var(--ja-surface); font-size: 13px; margin-bottom: 8px;
|
|
100
|
+
th { text-align: left; font-weight: 500; color: var(--ja-muted); padding: 6px 8px; border-bottom: 1px solid var(--ja-border); white-space: nowrap; }
|
|
101
|
+
td { padding: 6px 8px; border-bottom: 1px solid var(--ja-border); vertical-align: top; } }
|
|
102
|
+
.ja-bar { display: inline-block; height: 6px; border-radius: 3px; background: var(--ja-accent); opacity: .5; margin-right: 6px; vertical-align: middle; }
|
|
103
|
+
.ja-external { margin-left: 6px; font-size: 12px; }
|
|
104
|
+
|
|
105
|
+
.ja-diff { th { width: 28%; word-break: break-all; color: var(--ja-text); font-weight: 400; }
|
|
106
|
+
td { width: 36%; word-break: break-word; white-space: pre-wrap; }
|
|
107
|
+
tr:not(.ja-changed) { color: var(--ja-muted); th { color: var(--ja-muted); } }
|
|
108
|
+
.ja-old { background: var(--ja-bad-bg); }
|
|
109
|
+
.ja-new { background: var(--ja-ok-bg); } }
|
|
110
|
+
|
|
111
|
+
.ja-kv { width: 100%; border-collapse: collapse; font-size: 13px;
|
|
112
|
+
th { text-align: left; color: var(--ja-muted); font-weight: 400; padding: 3px 8px 3px 0; vertical-align: top; width: 35%; word-break: break-all; }
|
|
113
|
+
td { padding: 3px 0; word-break: break-word; font-family: 'JetBrains Mono', Consolas, monospace; font-size: 12px; } }
|
|
114
|
+
.ja-pre { margin: 0 0 8px; white-space: pre-wrap; word-break: break-word; font-family: 'JetBrains Mono', Consolas, monospace; font-size: 12px; max-height: 20em; overflow: auto;
|
|
115
|
+
background: var(--ja-bg); border: 1px solid var(--ja-border); border-radius: 4px; padding: 6px 8px; }
|
|
116
|
+
|
|
117
|
+
.ja-count { display: grid; grid-template-columns: minmax(0, 40%) 1fr max-content; gap: 8px; align-items: center; padding: 3px 0; font-size: 13px;
|
|
118
|
+
> a, > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } }
|
|
119
|
+
.ja-count-bar { height: 8px; background: var(--ja-bg); border-radius: 4px; overflow: hidden;
|
|
120
|
+
i { display: block; height: 100%; border-radius: 4px; opacity: .75; } }
|
|
121
|
+
|
|
122
|
+
@media (max-width: 700px) {
|
|
123
|
+
.ja-grid-2, .ja-grid-3 { grid-template-columns: 1fr; }
|
|
124
|
+
.ja-lookup input { width: 160px; }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Emitted once, on @use: the layout, and the default colours ───
|
|
129
|
+
|
|
130
|
+
@include _layout();
|
|
131
|
+
|
|
132
|
+
// The Juice Console's own theme: teal, amber and red.
|
|
133
|
+
$_accent: mat.m2-define-palette(mat.$m2-amber-palette);
|
|
134
|
+
$_warn: mat.m2-define-palette(mat.$m2-red-palette);
|
|
135
|
+
|
|
136
|
+
:where(:root) { @include colors(mat.m2-define-light-theme((color: (
|
|
137
|
+
primary: mat.m2-define-palette(mat.$m2-teal-palette, 700), accent: $_accent, warn: $_warn)))); }
|
|
138
|
+
:where(.dark) { @include colors(mat.m2-define-dark-theme((color: (
|
|
139
|
+
primary: mat.m2-define-palette(mat.$m2-teal-palette, 300), accent: $_accent, warn: $_warn)))); }
|