@firestitch/report 18.0.7 → 18.0.9
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/app/reports/data/report.data.d.ts +1 -0
- package/app/reports/dialogs/global-settings/components/ai-knowledge/ai-knowledge.component.d.ts +8 -0
- package/app/reports/dialogs/global-settings/components/history/history.component.d.ts +8 -0
- package/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +50 -0
- package/app/reports/dialogs/global-settings/global-settings.component.d.ts +6 -16
- package/app/reports/interfaces/report.interface.d.ts +29 -0
- package/esm2022/app/reports/data/report.data.mjs +10 -1
- package/esm2022/app/reports/dialogs/global-settings/components/ai-knowledge/ai-knowledge.component.mjs +31 -0
- package/esm2022/app/reports/dialogs/global-settings/components/history/history.component.mjs +32 -0
- package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +195 -0
- package/esm2022/app/reports/dialogs/global-settings/global-settings.component.mjs +36 -94
- package/esm2022/app/reports/interfaces/report.interface.mjs +1 -1
- package/fesm2022/{firestitch-report-echarts-CTAtb1Nq.mjs → firestitch-report-echarts-BjULVOed.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-CTAtb1Nq.mjs.map → firestitch-report-echarts-BjULVOed.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-LsQ2-fxU.mjs → firestitch-report-firestitch-report-DVb3fNHr.mjs} +277 -99
- package/fesm2022/firestitch-report-firestitch-report-DVb3fNHr.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-LsQ2-fxU.mjs.map +0 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, computed, inject, signal } from '@angular/core';
|
|
2
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
3
|
+
import { MatButton } from '@angular/material/button';
|
|
4
|
+
import { MatExpansionModule } from '@angular/material/expansion';
|
|
5
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
6
|
+
import { StreamEventType } from '@firestitch/api';
|
|
7
|
+
import { FsChipModule } from '@firestitch/chip';
|
|
8
|
+
import { FsDateModule } from '@firestitch/date';
|
|
9
|
+
import { FsMessage, FsMessageModule } from '@firestitch/message';
|
|
10
|
+
import { FsProcess } from '@firestitch/process';
|
|
11
|
+
import { FsPrompt } from '@firestitch/prompt';
|
|
12
|
+
import { FsSkeletonModule } from '@firestitch/skeleton';
|
|
13
|
+
import { filter, map, switchMap, take } from 'rxjs/operators';
|
|
14
|
+
import { ReportData } from '../../../../data/report.data';
|
|
15
|
+
import * as i0 from "@angular/core";
|
|
16
|
+
import * as i1 from "@angular/material/expansion";
|
|
17
|
+
import * as i2 from "@angular/material/tooltip";
|
|
18
|
+
import * as i3 from "@firestitch/chip";
|
|
19
|
+
import * as i4 from "@firestitch/date";
|
|
20
|
+
import * as i5 from "@firestitch/message";
|
|
21
|
+
import * as i6 from "@firestitch/skeleton";
|
|
22
|
+
// The Semantic Database tab: the model scoreboard the report AI is built from.
|
|
23
|
+
// Shows the last build's status, runs (or stops) a build — streamed into the
|
|
24
|
+
// FsProcess dock so it survives the dialog closing — and drills into every
|
|
25
|
+
// entity to reveal its shape, inlined enum values, computed fields and the
|
|
26
|
+
// AI-classified code functions cached from its model class. Each entity's
|
|
27
|
+
// functions load lazily on first expand, because the full set across all
|
|
28
|
+
// entities is far too big to ship with the scoreboard.
|
|
29
|
+
export class SemanticDatabaseComponent {
|
|
30
|
+
// The build-in-flight states of the ff_processes row (queued/running/stopping).
|
|
31
|
+
static ACTIVE_STATES = ['Q', 'R', 'X'];
|
|
32
|
+
static STATE_LABELS = {
|
|
33
|
+
Q: 'Queued',
|
|
34
|
+
R: 'Running',
|
|
35
|
+
X: 'Stopping',
|
|
36
|
+
C: 'Completed',
|
|
37
|
+
K: 'Stopped',
|
|
38
|
+
F: 'Failed',
|
|
39
|
+
};
|
|
40
|
+
// Scoreboard completion palette: built = green, everything else (needs
|
|
41
|
+
// building) = grey. No error red — a missing stage is unbuilt content, not a
|
|
42
|
+
// failure.
|
|
43
|
+
static DONE_COLOR = '#2e7d32';
|
|
44
|
+
static PENDING_COLOR = '#9e9e9e';
|
|
45
|
+
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
46
|
+
static KIND_COLORS = {
|
|
47
|
+
'calculation': '#1565c0',
|
|
48
|
+
'business-rule': '#6a1b9a',
|
|
49
|
+
'query': '#00695c',
|
|
50
|
+
'mutation': '#e65100',
|
|
51
|
+
'accessor': '#546e7a',
|
|
52
|
+
'other': '#757575',
|
|
53
|
+
};
|
|
54
|
+
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
55
|
+
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
56
|
+
// The scoreboard's per-entity stage chips, left to right.
|
|
57
|
+
stageKeys = ['introspected', 'enumsInlined', 'computed', 'functions'];
|
|
58
|
+
stageLabels = {
|
|
59
|
+
introspected: 'Introspected',
|
|
60
|
+
enumsInlined: 'Enum cases',
|
|
61
|
+
computed: 'Computed',
|
|
62
|
+
functions: 'Functions',
|
|
63
|
+
};
|
|
64
|
+
// null until loaded — gates the tab behind the skeleton.
|
|
65
|
+
status = signal(null);
|
|
66
|
+
// True while a build started from this tab is streaming into the dock.
|
|
67
|
+
building = signal(false);
|
|
68
|
+
buildActive = computed(() => this.building()
|
|
69
|
+
|| SemanticDatabaseComponent.ACTIVE_STATES.includes(this.status()?.build?.state));
|
|
70
|
+
buildStateLabel = computed(() => SemanticDatabaseComponent.STATE_LABELS[this.status()?.build?.state] ?? '');
|
|
71
|
+
// Per-entity function caches, loaded on first expand. undefined = not
|
|
72
|
+
// requested yet, null = request in flight (the skeleton window), object = loaded.
|
|
73
|
+
_entityFunctions = signal({});
|
|
74
|
+
_reportData = inject(ReportData);
|
|
75
|
+
_process = inject(FsProcess);
|
|
76
|
+
_prompt = inject(FsPrompt);
|
|
77
|
+
_message = inject(FsMessage);
|
|
78
|
+
_destroyRef = inject(DestroyRef);
|
|
79
|
+
constructor() {
|
|
80
|
+
this._init();
|
|
81
|
+
}
|
|
82
|
+
// Run the semantic-database build. The live feed streams into the FsProcess
|
|
83
|
+
// dock (bottom-right), whose subscription survives this dialog closing — so
|
|
84
|
+
// dismissing the dialog never interrupts a running build.
|
|
85
|
+
build() {
|
|
86
|
+
this.building.set(true);
|
|
87
|
+
const target$ = this._reportData.semanticBuild()
|
|
88
|
+
.pipe(filter((event) => event?.type === StreamEventType.Data
|
|
89
|
+
&& event.data?.kind === 'log'
|
|
90
|
+
&& typeof event.data.line === 'string'), map((event) => event.data.line));
|
|
91
|
+
this._process
|
|
92
|
+
.run('Semantic database build', target$)
|
|
93
|
+
.completed$
|
|
94
|
+
.pipe(take(1), takeUntilDestroyed(this._destroyRef))
|
|
95
|
+
.subscribe(() => {
|
|
96
|
+
this.building.set(false);
|
|
97
|
+
// The build rewrote every entity's function cache — drop the loaded
|
|
98
|
+
// copies so the next expand refetches fresh data.
|
|
99
|
+
this._entityFunctions.set({});
|
|
100
|
+
this._refreshStatus();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
stopBuild() {
|
|
104
|
+
this._prompt.confirm({
|
|
105
|
+
title: 'Stop build',
|
|
106
|
+
template: 'Stop the running build? The current semantic database stays in place until a build finishes.',
|
|
107
|
+
commitLabel: 'Stop',
|
|
108
|
+
})
|
|
109
|
+
.pipe(switchMap(() => this._reportData.semanticBuildStop()), takeUntilDestroyed(this._destroyRef))
|
|
110
|
+
.subscribe(() => {
|
|
111
|
+
this.building.set(false);
|
|
112
|
+
this._message.success('Stopping the build…');
|
|
113
|
+
this._refreshStatus();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
stageColor(stage) {
|
|
117
|
+
return stage === 'done'
|
|
118
|
+
? SemanticDatabaseComponent.DONE_COLOR
|
|
119
|
+
: SemanticDatabaseComponent.PENDING_COLOR;
|
|
120
|
+
}
|
|
121
|
+
stageTooltip(stage) {
|
|
122
|
+
return stage === 'done' ? 'Built' : 'Needs building';
|
|
123
|
+
}
|
|
124
|
+
kindColor(kind) {
|
|
125
|
+
return SemanticDatabaseComponent.KIND_COLORS[kind] ?? SemanticDatabaseComponent.KIND_COLORS.other;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The loaded function cache for an entity: undefined until the panel is first
|
|
129
|
+
* expanded, null while the request is in flight (the skeleton window), then
|
|
130
|
+
* the loaded cache.
|
|
131
|
+
*/
|
|
132
|
+
entityFunctions(key) {
|
|
133
|
+
return this._entityFunctions()[key];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* First expand of an entity: fetch its cached functions through the per-entity
|
|
137
|
+
* endpoint. Subsequent expands reuse the loaded copy (a finished build clears
|
|
138
|
+
* the copies so a re-expand refetches fresh data).
|
|
139
|
+
*/
|
|
140
|
+
entityOpened(entity) {
|
|
141
|
+
if (this._entityFunctions()[entity.key] !== undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this._entityFunctions.update((loaded) => ({ ...loaded, [entity.key]: null }));
|
|
145
|
+
this._reportData.semanticEntityFunctions(entity.key)
|
|
146
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
147
|
+
.subscribe((entityFunctions) => {
|
|
148
|
+
this._entityFunctions.update((loaded) => ({
|
|
149
|
+
...loaded,
|
|
150
|
+
[entity.key]: {
|
|
151
|
+
...entityFunctions,
|
|
152
|
+
functions: this._sortFunctions(entityFunctions),
|
|
153
|
+
},
|
|
154
|
+
}));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
_init() {
|
|
158
|
+
this._refreshStatus();
|
|
159
|
+
}
|
|
160
|
+
_refreshStatus() {
|
|
161
|
+
this._reportData.semanticBuildStatus()
|
|
162
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
163
|
+
.subscribe((status) => this.status.set(status));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Order once at load time (kind priority, then method name) so the template
|
|
167
|
+
* renders the list as-is.
|
|
168
|
+
*/
|
|
169
|
+
_sortFunctions(entityFunctions) {
|
|
170
|
+
return [...entityFunctions.functions]
|
|
171
|
+
.sort((a, b) => {
|
|
172
|
+
const kindDiff = this._kindRank(a.kind) - this._kindRank(b.kind);
|
|
173
|
+
return kindDiff !== 0 ? kindDiff : a.method.localeCompare(b.method);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
_kindRank(kind) {
|
|
177
|
+
const rank = SemanticDatabaseComponent.KIND_ORDER.indexOf(kind);
|
|
178
|
+
return rank === -1 ? SemanticDatabaseComponent.KIND_ORDER.length : rank;
|
|
179
|
+
}
|
|
180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
181
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SemanticDatabaseComponent, isStandalone: true, selector: "fs-ai-report-global-settings-semantic-database", ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <div class=\"build-actions\">\n @if (buildActive()) {\n <button\n type=\"button\"\n mat-button\n color=\"warn\"\n (click)=\"stopBuild()\">\n Stop Build\n </button>\n } @else {\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n (click)=\"build()\">\n {{ buildStatus.detail.generatedAt ? 'Rebuild' : 'Build' }}\n </button>\n }\n </div>\n\n <!-- The model scoreboard: every entity the AI knows about, its\n built-vs-needs-building stages, and a drill-down into its shape, enum\n values, computed fields and AI-classified code functions. -->\n @if (buildStatus.detail.entities?.length) {\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel (opened)=\"entityOpened(entity)\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body (and its functions request) only exists\n once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div><strong>Table:</strong> {{ entity.table }}</div>\n <div><strong>Columns:</strong> {{ entity.columns }}</div>\n <div><strong>Relationships:</strong> {{ entity.relationships }}</div>\n @if (entity.enums.length) {\n <div class=\"enum-list\">\n <strong>Enum values:</strong>\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"enum-row\">\n <span class=\"enum-column\">{{ enumColumn.column }}</span>\n <span class=\"enum-values\">{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n }\n @if (entity.computedFields.length) {\n <div><strong>Computed fields:</strong> {{ entity.computedFields.join(', ') }}</div>\n }\n\n <div class=\"functions-section\">\n <h3 class=\"functions-heading\">\n Code functions\n @if (entity.functionCount) {\n <span class=\"functions-count\">({{ entity.functionCount }})</span>\n }\n </h3>\n\n <div class=\"functions-body\" *fsSkeleton=\"entityFunctions(entity.key)\">\n @if (entityFunctions(entity.key); as fns) {\n @if (fns.functions.length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ fns.class }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of fns.functions; track fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".tab-body{padding-top:16px}.hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.build-status{display:flex;flex-direction:column;gap:8px}.build-status .stat{display:flex;gap:8px}.build-status .stat .label{min-width:110px;color:#0009}.build-actions{margin:12px 0}.entities{display:block}.entities .stage-chips{display:flex;flex-wrap:wrap;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:5px}.entities .entity-detail .enum-list{display:flex;flex-direction:column;gap:3px}.entities .entity-detail .enum-list .enum-row{display:flex;gap:10px;padding-left:10px}.entities .entity-detail .enum-list .enum-row .enum-column{min-width:140px;font-weight:600}.entities .entity-detail .enum-list .enum-row .enum-values{color:#0009}.entities .entity-detail .functions-section{margin-top:10px}.entities .entity-detail .functions-section .functions-heading{margin:0 0 5px;font-size:15px}.entities .entity-detail .functions-section .functions-heading .functions-count{font-weight:400;color:#0009}.entities .entity-detail .functions-section .functions-generated{margin-bottom:10px}.entities .entity-detail .functions-section .functions-list{display:flex;flex-direction:column}.entities .entity-detail .functions-section .functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.entities .entity-detail .functions-section .functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.entities .entity-detail .functions-section .functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.entities .entity-detail .functions-section .functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.entities .entity-detail .functions-section .functions-list .function-row .function-summary{color:#000000bf}.entities .entity-detail .functions-section .functions-list .function-row .function-produces{padding-left:10px}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i1.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i3.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i5.FsMessageWarningComponent, selector: "fs-message-warning" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i6.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
182
|
+
}
|
|
183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, decorators: [{
|
|
184
|
+
type: Component,
|
|
185
|
+
args: [{ selector: 'fs-ai-report-global-settings-semantic-database', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
186
|
+
MatButton,
|
|
187
|
+
MatExpansionModule,
|
|
188
|
+
MatTooltipModule,
|
|
189
|
+
FsChipModule,
|
|
190
|
+
FsDateModule,
|
|
191
|
+
FsMessageModule,
|
|
192
|
+
FsSkeletonModule,
|
|
193
|
+
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>\n {{ buildStatus.detail.entityCount }} entities \u00B7\n {{ buildStatus.detail.functionCount }} code functions\n </span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <div class=\"build-actions\">\n @if (buildActive()) {\n <button\n type=\"button\"\n mat-button\n color=\"warn\"\n (click)=\"stopBuild()\">\n Stop Build\n </button>\n } @else {\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n (click)=\"build()\">\n {{ buildStatus.detail.generatedAt ? 'Rebuild' : 'Build' }}\n </button>\n }\n </div>\n\n <!-- The model scoreboard: every entity the AI knows about, its\n built-vs-needs-building stages, and a drill-down into its shape, enum\n values, computed fields and AI-classified code functions. -->\n @if (buildStatus.detail.entities?.length) {\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel (opened)=\"entityOpened(entity)\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body (and its functions request) only exists\n once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div><strong>Table:</strong> {{ entity.table }}</div>\n <div><strong>Columns:</strong> {{ entity.columns }}</div>\n <div><strong>Relationships:</strong> {{ entity.relationships }}</div>\n @if (entity.enums.length) {\n <div class=\"enum-list\">\n <strong>Enum values:</strong>\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"enum-row\">\n <span class=\"enum-column\">{{ enumColumn.column }}</span>\n <span class=\"enum-values\">{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n }\n @if (entity.computedFields.length) {\n <div><strong>Computed fields:</strong> {{ entity.computedFields.join(', ') }}</div>\n }\n\n <div class=\"functions-section\">\n <h3 class=\"functions-heading\">\n Code functions\n @if (entity.functionCount) {\n <span class=\"functions-count\">({{ entity.functionCount }})</span>\n }\n </h3>\n\n <div class=\"functions-body\" *fsSkeleton=\"entityFunctions(entity.key)\">\n @if (entityFunctions(entity.key); as fns) {\n @if (fns.functions.length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ fns.class }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of fns.functions; track fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".tab-body{padding-top:16px}.hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.build-status{display:flex;flex-direction:column;gap:8px}.build-status .stat{display:flex;gap:8px}.build-status .stat .label{min-width:110px;color:#0009}.build-actions{margin:12px 0}.entities{display:block}.entities .stage-chips{display:flex;flex-wrap:wrap;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:5px}.entities .entity-detail .enum-list{display:flex;flex-direction:column;gap:3px}.entities .entity-detail .enum-list .enum-row{display:flex;gap:10px;padding-left:10px}.entities .entity-detail .enum-list .enum-row .enum-column{min-width:140px;font-weight:600}.entities .entity-detail .enum-list .enum-row .enum-values{color:#0009}.entities .entity-detail .functions-section{margin-top:10px}.entities .entity-detail .functions-section .functions-heading{margin:0 0 5px;font-size:15px}.entities .entity-detail .functions-section .functions-heading .functions-count{font-weight:400;color:#0009}.entities .entity-detail .functions-section .functions-generated{margin-bottom:10px}.entities .entity-detail .functions-section .functions-list{display:flex;flex-direction:column}.entities .entity-detail .functions-section .functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.entities .entity-detail .functions-section .functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.entities .entity-detail .functions-section .functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.entities .entity-detail .functions-section .functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.entities .entity-detail .functions-section .functions-list .function-row .function-summary{color:#000000bf}.entities .entity-detail .functions-section .functions-list .function-row .function-produces{padding-left:10px}\n"] }]
|
|
194
|
+
}], ctorParameters: () => [] });
|
|
195
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VtYW50aWMtZGF0YWJhc2UuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2RpYWxvZ3MvZ2xvYmFsLXNldHRpbmdzL2NvbXBvbmVudHMvc2VtYW50aWMtZGF0YWJhc2Uvc2VtYW50aWMtZGF0YWJhc2UuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2FwcC9yZXBvcnRzL2RpYWxvZ3MvZ2xvYmFsLXNldHRpbmdzL2NvbXBvbmVudHMvc2VtYW50aWMtZGF0YWJhc2Uvc2VtYW50aWMtZGF0YWJhc2UuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLHVCQUF1QixFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDekcsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFaEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRTdELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxTQUFTLEVBQUUsZUFBZSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDakUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM5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LE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRWpFLE9BQU8sUUFBUSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDdEUsQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDO0lBRU8sU0FBUyxDQUFDLElBQVk7UUFDNUIsTUFBTSxJQUFJLEdBQUcseUJBQXlCLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVoRSxPQUFPLElBQUksS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMseUJBQXlCLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQzFFLENBQUM7d0dBbk1VLHlCQUF5Qjs0RkFBekIseUJBQXlCLDBHQ2pEdEMsa3BPQXdLQSxpOUREaElJLFNBQVMsZ0xBQ1Qsa0JBQWtCLHEwQkFDbEIsZ0JBQWdCLDRUQUNoQixZQUFZLHFYQUNaLFlBQVksNElBQ1osZUFBZSx5SEFDZixnQkFBZ0I7OzRGQUdQLHlCQUF5QjtrQkFoQnJDLFNBQVM7K0JBQ0UsZ0RBQWdELG1CQUd6Qyx1QkFBdUIsQ0FBQyxNQUFNLGNBQ25DLElBQUksV0FDUDt3QkFDUCxTQUFTO3dCQUNULGtCQUFrQjt3QkFDbEIsZ0JBQWdCO3dCQUNoQixZQUFZO3dCQUNaLFlBQVk7d0JBQ1osZUFBZTt3QkFDZixnQkFBZ0I7cUJBQ2pCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENvbXBvbmVudCwgRGVzdHJveVJlZiwgY29tcHV0ZWQsIGluamVjdCwgc2lnbmFsIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyB0YWtlVW50aWxEZXN0cm95ZWQgfSBmcm9tICdAYW5ndWxhci9jb3JlL3J4anMtaW50ZXJvcCc7XG5cbmltcG9ydCB7IE1hdEJ1dHRvbiB9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsL2J1dHRvbic7XG5pbXBvcnQgeyBNYXRFeHBhbnNpb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9tYXRlcmlhbC9leHBhbnNpb24nO1xuaW1wb3J0IHsgTWF0VG9vbHRpcE1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsL3Rvb2x0aXAnO1xuXG5pbXBvcnQgeyBTdHJlYW1FdmVudFR5cGUgfSBmcm9tICdAZmlyZXN0aXRjaC9hcGknO1xuaW1wb3J0IHsgRnNDaGlwTW9kdWxlIH0gZnJvbSAnQGZpcmVzdGl0Y2gvY2hpcCc7XG5pbXBvcnQgeyBGc0RhdGVNb2R1bGUgfSBmcm9tICdAZmlyZXN0aXRjaC9kYXRlJztcbmltcG9ydCB7IEZzTWVzc2FnZSwgRnNNZXNzYWdlTW9kdWxlIH0gZnJvbSAnQGZpcmVzdGl0Y2gvbWVzc2FnZSc7XG5pbXBvcnQgeyBGc1Byb2Nlc3MgfSBmcm9tICdAZmlyZXN0aXRjaC9wcm9jZXNzJztcbmltcG9ydCB7IEZzUHJvbXB0IH0gZnJvbSAnQGZpcmVzdGl0Y2gvcHJvbXB0JztcbmltcG9ydCB7IEZzU2tlbGV0b25Nb2R1bGUgfSBmcm9tICdAZmlyZXN0aXRjaC9za2VsZXRvbic7XG5cbmltcG9ydCB7IGZpbHRlciwgbWFwLCBzd2l0Y2hNYXAsIHRha2UgfSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5cbmltcG9ydCB7IFJlcG9ydERhdGEgfSBmcm9tICcuLi8uLi8uLi8uLi9kYXRhL3JlcG9ydC5kYXRhJztcbmltcG9ydCB7XG4gIFNlbWFudGljQnVpbGRFbnRpdHksXG4gIFNlbWFudGljQnVpbGRTdGFnZSxcbiAgU2VtYW50aWNCdWlsZFN0YXR1cyxcbiAgU2VtYW50aWNFbnRpdHlGdW5jdGlvbnMsXG59IGZyb20gJy4uLy4uLy4uLy4uL2ludGVyZmFjZXMvcmVwb3J0LmludGVyZmFjZSc7XG5cblxuLy8gVGhlIFNlbWFudGljIERhdGFiYXNlIHRhYjogdGhlIG1vZGVsIHNjb3JlYm9hcmQgdGhlIHJlcG9ydCBBSSBpcyBidWlsdCBmcm9tLlxuLy8gU2hvd3MgdGhlIGxhc3QgYnVpbGQncyBzdGF0dXMsIHJ1bnMgKG9yIHN0b3BzKSBhIGJ1aWxkIOKAlCBzdHJlYW1lZCBpbnRvIHRoZVxuLy8gRnNQcm9jZXNzIGRvY2sgc28gaXQgc3Vydml2ZXMgdGhlIGRpYWxvZyBjbG9zaW5nIOKAlCBhbmQgZHJpbGxzIGludG8gZXZlcnlcbi8vIGVudGl0eSB0byByZXZlYWwgaXRzIHNoYXBlLCBpbmxpbmVkIGVudW0gdmFsdWVzLCBjb21wdXRlZCBmaWVsZHMgYW5kIHRoZVxuLy8gQUktY2xhc3NpZmllZCBjb2RlIGZ1bmN0aW9ucyBjYWNoZWQgZnJvbSBpdHMgbW9kZWwgY2xhc3MuIEVhY2ggZW50aXR5J3Ncbi8vIGZ1bmN0aW9ucyBsb2FkIGxhemlseSBvbiBmaXJzdCBleHBhbmQsIGJlY2F1c2UgdGhlIGZ1bGwgc2V0IGFjcm9zcyBhbGxcbi8vIGVudGl0aWVzIGlzIGZhciB0b28gYmlnIHRvIHNoaXAgd2l0aCB0aGUgc2NvcmVib2FyZC5cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ2ZzLWFpLXJlcG9ydC1nbG9iYWwtc2V0dGluZ3Mtc2VtYW50aWMtZGF0YWJhc2UnLFxuICB0ZW1wbGF0ZVVybDogJy4vc2VtYW50aWMtZGF0YWJhc2UuY29tcG9uZW50Lmh0bWwnLFxuICBzdHlsZVVybHM6IFsnLi9zZW1hbnRpYy1kYXRhYmFzZS5jb21wb25lbnQuc2NzcyddLFxuICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIE1hdEJ1dHRvbixcbiAgICBNYXRFeHBhbnNpb25Nb2R1bGUsXG4gICAgTWF0VG9vbHRpcE1vZHVsZSxcbiAgICBGc0NoaXBNb2R1bGUsXG4gICAgRnNEYXRlTW9kdWxlLFxuICAgIEZzTWVzc2FnZU1vZHVsZSxcbiAgICBGc1NrZWxldG9uTW9kdWxlLFxuICBdLFxufSlcbmV4cG9ydCBjbGFzcyBTZW1hbnRpY0RhdGFiYXNlQ29tcG9uZW50IHtcblxuICAvLyBUaGUgYnVpbGQtaW4tZmxpZ2h0IHN0YXRlcyBvZiB0aGUgZmZfcHJvY2Vzc2VzIHJvdyAocXVldWVkL3J1bm5pbmcvc3RvcHBpbmcpLlxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBBQ1RJVkVfU1RBVEVTID0gWydRJywgJ1InLCAnWCddO1xuXG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IFNUQVRFX0xBQkVMUzogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHtcbiAgICBROiAnUXVldWVkJyxcbiAgICBSOiAnUnVubmluZycsXG4gICAgWDogJ1N0b3BwaW5nJyxcbiAgICBDOiAnQ29tcGxldGVkJyxcbiAgICBLOiAnU3RvcHBlZCcsXG4gICAgRjogJ0ZhaWxlZCcsXG4gIH07XG5cbiAgLy8gU2NvcmVib2FyZCBjb21wbGV0aW9uIHBhbGV0dGU6IGJ1aWx0ID0gZ3JlZW4sIGV2ZXJ5dGhpbmcgZWxzZSAobmVlZHNcbiAgLy8gYnVpbGRpbmcpID0gZ3JleS4gTm8gZXJyb3IgcmVkIOKAlCBhIG1pc3Npbmcgc3RhZ2UgaXMgdW5idWlsdCBjb250ZW50LCBub3QgYVxuICAvLyBmYWlsdXJlLlxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBET05FX0NPTE9SID0gJyMyZTdkMzInO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBQRU5ESU5HX0NPTE9SID0gJyM5ZTllOWUnO1xuXG4gIC8vIE9uZSBjb2xvciBwZXIgQUkgY2xhc3NpZmljYXRpb24ga2luZCwgc28gYSBmdW5jdGlvbidzIG5hdHVyZSByZWFkcyBhdCBhIGdsYW5jZS5cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgS0lORF9DT0xPUlM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgJ2NhbGN1bGF0aW9uJzogJyMxNTY1YzAnLFxuICAgICdidXNpbmVzcy1ydWxlJzogJyM2YTFiOWEnLFxuICAgICdxdWVyeSc6ICcjMDA2OTVjJyxcbiAgICAnbXV0YXRpb24nOiAnI2U2NTEwMCcsXG4gICAgJ2FjY2Vzc29yJzogJyM1NDZlN2EnLFxuICAgICdvdGhlcic6ICcjNzU3NTc1JyxcbiAgfTtcblxuICAvLyBJbnRlcmVzdGluZyBsb2dpYyBmaXJzdDogZGVyaXZlZCB2YWx1ZXMgYW5kIHJ1bGVzLCB0aGVuIGRhdGEgYWNjZXNzLCB0aGVuIHRoZSByZXN0LlxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBLSU5EX09SREVSID0gWydjYWxjdWxhdGlvbicsICdidXNpbmVzcy1ydWxlJywgJ3F1ZXJ5JywgJ211dGF0aW9uJywgJ2FjY2Vzc29yJywgJ290aGVyJ107XG5cbiAgLy8gVGhlIHNjb3JlYm9hcmQncyBwZXItZW50aXR5IHN0YWdlIGNoaXBzLCBsZWZ0IHRvIHJpZ2h0LlxuICBwdWJsaWMgcmVhZG9ubHkgc3RhZ2VLZXlzID0gWydpbnRyb3NwZWN0ZWQnLCAnZW51bXNJbmxpbmVkJywgJ2NvbXB1dGVkJywgJ2Z1bmN0aW9ucyddO1xuXG4gIHB1YmxpYyByZWFkb25seSBzdGFnZUxhYmVsczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHtcbiAgICBpbnRyb3NwZWN0ZWQ6ICdJbnRyb3NwZWN0ZWQnLFxuICAgIGVudW1zSW5saW5lZDogJ0VudW0gY2FzZXMnLFxuICAgIGNvbXB1dGVkOiAnQ29tcHV0ZWQnLFxuICAgIGZ1bmN0aW9uczogJ0Z1bmN0aW9ucycsXG4gIH07XG5cbiAgLy8gbnVsbCB1bnRpbCBsb2FkZWQg4oCUIGdhdGVzIHRoZSB0YWIgYmVoaW5kIHRoZSBza2VsZXRvbi5cbiAgcHVibGljIHJlYWRvbmx5IHN0YXR1cyA9IHNpZ25hbDxTZW1hbnRpY0J1aWxkU3RhdHVzIHwgbnVsbD4obnVsbCk7XG5cbiAgLy8gVHJ1ZSB3aGlsZSBhIGJ1aWxkIHN0YXJ0ZWQgZnJvbSB0aGlzIHRhYiBpcyBzdHJlYW1pbmcgaW50byB0aGUgZG9jay5cbiAgcHVibGljIHJlYWRvbmx5IGJ1aWxkaW5nID0gc2lnbmFsKGZhbHNlKTtcblxuICBwdWJsaWMgcmVhZG9ubHkgYnVpbGRBY3RpdmUgPSBjb21wdXRlZDxib29sZWFuPigoKSA9PlxuICAgIHRoaXMuYnVpbGRpbmcoKVxuICAgICAgfHwgU2VtYW50aWNEYXRhYmFzZUNvbXBvbmVudC5BQ1RJVkVfU1RBVEVTLmluY2x1ZGVzKHRoaXMuc3RhdHVzKCk/LmJ1aWxkPy5zdGF0ZSksXG4gICk7XG5cbiAgcHVibGljIHJlYWRvbmx5IGJ1aWxkU3RhdGVMYWJlbCA9IGNvbXB1dGVkPHN0cmluZz4oKCkgPT5cbiAgICBTZW1hbnRpY0RhdGFiYXNlQ29tcG9uZW50LlNUQVRFX0xBQkVMU1t0aGlzLnN0YXR1cygpPy5idWlsZD8uc3RhdGVdID8/ICcnLFxuICApO1xuXG4gIC8vIFBlci1lbnRpdHkgZnVuY3Rpb24gY2FjaGVzLCBsb2FkZWQgb24gZmlyc3QgZXhwYW5kLiB1bmRlZmluZWQgPSBub3RcbiAgLy8gcmVxdWVzdGVkIHlldCwgbnVsbCA9IHJlcXVlc3QgaW4gZmxpZ2h0ICh0aGUgc2tlbGV0b24gd2luZG93KSwgb2JqZWN0ID0gbG9hZGVkLlxuICBwcml2YXRlIHJlYWRvbmx5IF9lbnRpdHlGdW5jdGlvbnMgPSBzaWduYWw8UmVjb3JkPHN0cmluZywgU2VtYW50aWNFbnRpdHlGdW5jdGlvbnMgfCBudWxsPj4oe30pO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgX3JlcG9ydERhdGEgPSBpbmplY3QoUmVwb3J0RGF0YSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgX3Byb2Nlc3MgPSBpbmplY3QoRnNQcm9jZXNzKTtcbiAgcHJpdmF0ZSByZWFkb25seSBfcHJvbXB0ID0gaW5qZWN0KEZzUHJvbXB0KTtcbiAgcHJpdmF0ZSByZWFkb25seSBfbWVzc2FnZSA9IGluamVjdChGc01lc3NhZ2UpO1xuICBwcml2YXRlIHJlYWRvbmx5IF9kZXN0cm95UmVmID0gaW5qZWN0KERlc3Ryb3lSZWYpO1xuXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuX2luaXQoKTtcbiAgfVxuXG4gIC8vIFJ1biB0aGUgc2VtYW50aWMtZGF0YWJhc2UgYnVpbGQuIFRoZSBsaXZlIGZlZWQgc3RyZWFtcyBpbnRvIHRoZSBGc1Byb2Nlc3NcbiAgLy8gZG9jayAoYm90dG9tLXJpZ2h0KSwgd2hvc2Ugc3Vic2NyaXB0aW9uIHN1cnZpdmVzIHRoaXMgZGlhbG9nIGNsb3Npbmcg4oCUIHNvXG4gIC8vIGRpc21pc3NpbmcgdGhlIGRpYWxvZyBuZXZlciBpbnRlcnJ1cHRzIGEgcnVubmluZyBidWlsZC5cbiAgcHVibGljIGJ1aWxkKCk6IHZvaWQge1xuICAgIHRoaXMuYnVpbGRpbmcuc2V0KHRydWUpO1xuXG4gICAgY29uc3QgdGFyZ2V0JCA9IHRoaXMuX3JlcG9ydERhdGEuc2VtYW50aWNCdWlsZCgpXG4gICAgICAucGlwZShcbiAgICAgICAgZmlsdGVyKChldmVudCkgPT5cbiAgICAgICAgICBldmVudD8udHlwZSA9PT0gU3RyZWFtRXZlbnRUeXBlLkRhdGFcbiAgICAgICAgICAmJiBldmVudC5kYXRhPy5raW5kID09PSAnbG9nJ1xuICAgICAgICAgICYmIHR5cGVvZiBldmVudC5kYXRhLmxpbmUgPT09ICdzdHJpbmcnKSxcbiAgICAgICAgbWFwKChldmVudCkgPT4gZXZlbnQuZGF0YS5saW5lIGFzIHN0cmluZyksXG4gICAgICApO1xuXG4gICAgdGhpcy5fcHJvY2Vzc1xuICAgICAgLnJ1bignU2VtYW50aWMgZGF0YWJhc2UgYnVpbGQnLCB0YXJnZXQkKVxuICAgICAgLmNvbXBsZXRlZCRcbiAgICAgIC5waXBlKFxuICAgICAgICB0YWtlKDEpLFxuICAgICAgICB0YWtlVW50aWxEZXN0cm95ZWQodGhpcy5fZGVzdHJveVJlZiksXG4gICAgICApXG4gICAgICAuc3Vic2NyaWJlKCgpID0+IHtcbiAgICAgICAgdGhpcy5idWlsZGluZy5zZXQoZmFsc2UpO1xuICAgICAgICAvLyBUaGUgYnVpbGQgcmV3cm90ZSBldmVyeSBlbnRpdHkncyBmdW5jdGlvbiBjYWNoZSDigJQgZHJvcCB0aGUgbG9hZGVkXG4gICAgICAgIC8vIGNvcGllcyBzbyB0aGUgbmV4dCBleHBhbmQgcmVmZXRjaGVzIGZyZXNoIGRhdGEuXG4gICAgICAgIHRoaXMuX2VudGl0eUZ1bmN0aW9ucy5zZXQoe30pO1xuICAgICAgICB0aGlzLl9yZWZyZXNoU3RhdHVzKCk7XG4gICAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBzdG9wQnVpbGQoKTogdm9pZCB7XG4gICAgdGhpcy5fcHJvbXB0LmNvbmZpcm0oe1xuICAgICAgdGl0bGU6ICdTdG9wIGJ1aWxkJyxcbiAgICAgIHRlbXBsYXRlOiAnU3RvcCB0aGUgcnVubmluZyBidWlsZD8gVGhlIGN1cnJlbnQgc2VtYW50aWMgZGF0YWJhc2Ugc3RheXMgaW4gcGxhY2UgdW50aWwgYSBidWlsZCBmaW5pc2hlcy4nLFxuICAgICAgY29tbWl0TGFiZWw6ICdTdG9wJyxcbiAgICB9KVxuICAgICAgLnBpcGUoXG4gICAgICAgIHN3aXRjaE1hcCgoKSA9PiB0aGlzLl9yZXBvcnREYXRhLnNlbWFudGljQnVpbGRTdG9wKCkpLFxuICAgICAgICB0YWtlVW50aWxEZXN0cm95ZWQodGhpcy5fZGVzdHJveVJlZiksXG4gICAgICApXG4gICAgICAuc3Vic2NyaWJlKCgpID0+IHtcbiAgICAgICAgdGhpcy5idWlsZGluZy5zZXQoZmFsc2UpO1xuICAgICAgICB0aGlzLl9tZXNzYWdlLnN1Y2Nlc3MoJ1N0b3BwaW5nIHRoZSBidWlsZOKApicpO1xuICAgICAgICB0aGlzLl9yZWZyZXNoU3RhdHVzKCk7XG4gICAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBzdGFnZUNvbG9yKHN0YWdlOiBTZW1hbnRpY0J1aWxkU3RhZ2UpOiBzdHJpbmcge1xuICAgIHJldHVybiBzdGFnZSA9PT0gJ2RvbmUnXG4gICAgICA/IFNlbWFudGljRGF0YWJhc2VDb21wb25lbnQuRE9ORV9DT0xPUlxuICAgICAgOiBTZW1hbnRpY0RhdGFiYXNlQ29tcG9uZW50LlBFTkRJTkdfQ09MT1I7XG4gIH1cblxuICBwdWJsaWMgc3RhZ2VUb29sdGlwKHN0YWdlOiBTZW1hbnRpY0J1aWxkU3RhZ2UpOiBzdHJpbmcge1xuICAgIHJldHVybiBzdGFnZSA9PT0gJ2RvbmUnID8gJ0J1aWx0JyA6ICdOZWVkcyBidWlsZGluZyc7XG4gIH1cblxuICBwdWJsaWMga2luZENvbG9yKGtpbmQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIFNlbWFudGljRGF0YWJhc2VDb21wb25lbnQuS0lORF9DT0xPUlNba2luZF0gPz8gU2VtYW50aWNEYXRhYmFzZUNvbXBvbmVudC5LSU5EX0NPTE9SUy5vdGhlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgbG9hZGVkIGZ1bmN0aW9uIGNhY2hlIGZvciBhbiBlbnRpdHk6IHVuZGVmaW5lZCB1bnRpbCB0aGUgcGFuZWwgaXMgZmlyc3RcbiAgICogZXhwYW5kZWQsIG51bGwgd2hpbGUgdGhlIHJlcXVlc3QgaXMgaW4gZmxpZ2h0ICh0aGUgc2tlbGV0b24gd2luZG93KSwgdGhlblxuICAgKiB0aGUgbG9hZGVkIGNhY2hlLlxuICAgKi9cbiAgcHVibGljIGVudGl0eUZ1bmN0aW9ucyhrZXk6IHN0cmluZyk6IFNlbWFudGljRW50aXR5RnVuY3Rpb25zIHwgbnVsbCB8IHVuZGVmaW5lZCB7XG4gICAgcmV0dXJuIHRoaXMuX2VudGl0eUZ1bmN0aW9ucygpW2tleV07XG4gIH1cblxuICAvKipcbiAgICogRmlyc3QgZXhwYW5kIG9mIGFuIGVudGl0eTogZmV0Y2ggaXRzIGNhY2hlZCBmdW5jdGlvbnMgdGhyb3VnaCB0aGUgcGVyLWVudGl0eVxuICAgKiBlbmRwb2ludC4gU3Vic2VxdWVudCBleHBhbmRzIHJldXNlIHRoZSBsb2FkZWQgY29weSAoYSBmaW5pc2hlZCBidWlsZCBjbGVhcnNcbiAgICogdGhlIGNvcGllcyBzbyBhIHJlLWV4cGFuZCByZWZldGNoZXMgZnJlc2ggZGF0YSkuXG4gICAqL1xuICBwdWJsaWMgZW50aXR5T3BlbmVkKGVudGl0eTogU2VtYW50aWNCdWlsZEVudGl0eSk6IHZvaWQge1xuICAgIGlmICh0aGlzLl9lbnRpdHlGdW5jdGlvbnMoKVtlbnRpdHkua2V5XSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdGhpcy5fZW50aXR5RnVuY3Rpb25zLnVwZGF0ZSgobG9hZGVkKSA9PiAoeyAuLi5sb2FkZWQsIFtlbnRpdHkua2V5XTogbnVsbCB9KSk7XG5cbiAgICB0aGlzLl9yZXBvcnREYXRhLnNlbWFudGljRW50aXR5RnVuY3Rpb25zKGVudGl0eS5rZXkpXG4gICAgICAucGlwZSh0YWtlVW50aWxEZXN0cm95ZWQodGhpcy5fZGVzdHJveVJlZikpXG4gICAgICAuc3Vic2NyaWJlKChlbnRpdHlGdW5jdGlvbnM6IFNlbWFudGljRW50aXR5RnVuY3Rpb25zKSA9PiB7XG4gICAgICAgIHRoaXMuX2VudGl0eUZ1bmN0aW9ucy51cGRhdGUoKGxvYWRlZCkgPT4gKHtcbiAgICAgICAgICAuLi5sb2FkZWQsXG4gICAgICAgICAgW2VudGl0eS5rZXldOiB7XG4gICAgICAgICAgICAuLi5lbnRpdHlGdW5jdGlvbnMsXG4gICAgICAgICAgICBmdW5jdGlvbnM6IHRoaXMuX3NvcnRGdW5jdGlvbnMoZW50aXR5RnVuY3Rpb25zKSxcbiAgICAgICAgICB9LFxuICAgICAgICB9KSk7XG4gICAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgX2luaXQoKTogdm9pZCB7XG4gICAgdGhpcy5fcmVmcmVzaFN0YXR1cygpO1xuICB9XG5cbiAgcHJpdmF0ZSBfcmVmcmVzaFN0YXR1cygpOiB2b2lkIHtcbiAgICB0aGlzLl9yZXBvcnREYXRhLnNlbWFudGljQnVpbGRTdGF0dXMoKVxuICAgICAgLnBpcGUodGFrZVVudGlsRGVzdHJveWVkKHRoaXMuX2Rlc3Ryb3lSZWYpKVxuICAgICAgLnN1YnNjcmliZSgoc3RhdHVzOiBTZW1hbnRpY0J1aWxkU3RhdHVzKSA9PiB0aGlzLnN0YXR1cy5zZXQoc3RhdHVzKSk7XG4gIH1cblxuICAvKipcbiAgICogT3JkZXIgb25jZSBhdCBsb2FkIHRpbWUgKGtpbmQgcHJpb3JpdHksIHRoZW4gbWV0aG9kIG5hbWUpIHNvIHRoZSB0ZW1wbGF0ZVxuICAgKiByZW5kZXJzIHRoZSBsaXN0IGFzLWlzLlxuICAgKi9cbiAgcHJpdmF0ZSBfc29ydEZ1bmN0aW9ucyhlbnRpdHlGdW5jdGlvbnM6IFNlbWFudGljRW50aXR5RnVuY3Rpb25zKTogU2VtYW50aWNFbnRpdHlGdW5jdGlvbnNbJ2Z1bmN0aW9ucyddIHtcbiAgICByZXR1cm4gWy4uLmVudGl0eUZ1bmN0aW9ucy5mdW5jdGlvbnNdXG4gICAgICAuc29ydCgoYSwgYikgPT4ge1xuICAgICAgICBjb25zdCBraW5kRGlmZiA9IHRoaXMuX2tpbmRSYW5rKGEua2luZCkgLSB0aGlzLl9raW5kUmFuayhiLmtpbmQpO1xuXG4gICAgICAgIHJldHVybiBraW5kRGlmZiAhPT0gMCA/IGtpbmREaWZmIDogYS5tZXRob2QubG9jYWxlQ29tcGFyZShiLm1ldGhvZCk7XG4gICAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgX2tpbmRSYW5rKGtpbmQ6IHN0cmluZyk6IG51bWJlciB7XG4gICAgY29uc3QgcmFuayA9IFNlbWFudGljRGF0YWJhc2VDb21wb25lbnQuS0lORF9PUkRFUi5pbmRleE9mKGtpbmQpO1xuXG4gICAgcmV0dXJuIHJhbmsgPT09IC0xID8gU2VtYW50aWNEYXRhYmFzZUNvbXBvbmVudC5LSU5EX09SREVSLmxlbmd0aCA6IHJhbms7XG4gIH1cbn1cbiIsIjxkaXYgY2xhc3M9XCJmcy1jb2x1bW4gdGFiLWJvZHlcIj5cbiAgPHAgY2xhc3M9XCJoaW50XCI+XG4gICAgVGhlIHNlbWFudGljIGRhdGFiYXNlIGlzIHdoYXQgdGhlIHJlcG9ydCBBSSB1c2VzIHRvIHVuZGVyc3RhbmQgeW91ciBkYXRhIOKAlFxuICAgIHRhYmxlcywgcmVsYXRpb25zaGlwcyBhbmQgdGhlIGNhbGN1bGF0aW9ucyBpbiBjb2RlLiBSZWJ1aWxkIGl0IGFmdGVyXG4gICAgc2lnbmlmaWNhbnQgYXBwbGljYXRpb24gdXBkYXRlcy4gVGhlIGJ1aWxkIHJ1bnMgaW4gdGhlIGJhY2tncm91bmQ7IHByb2dyZXNzXG4gICAgYXBwZWFycyBpbiB0aGUgZG9jayBhdCB0aGUgYm90dG9tIHJpZ2h0IGFuZCB0aGlzIGRpYWxvZyBjYW4gYmUgY2xvc2VkIHdoaWxlXG4gICAgaXQgcnVucy5cbiAgPC9wPlxuXG4gIDxkaXYgKmZzU2tlbGV0b249XCJzdGF0dXMoKVwiPlxuICAgIEBpZiAoc3RhdHVzKCk7IGFzIGJ1aWxkU3RhdHVzKSB7XG4gICAgICA8ZGl2IGNsYXNzPVwiYnVpbGQtc3RhdHVzXCI+XG4gICAgICAgIEBpZiAoYnVpbGRTdGF0dXMuZGV0YWlsLmdlbmVyYXRlZEF0KSB7XG4gICAgICAgICAgPGRpdiBjbGFzcz1cInN0YXRcIj5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibGFiZWxcIj5MYXN0IGJ1aWx0PC9zcGFuPlxuICAgICAgICAgICAgPGZzLWRhdGVcbiAgICAgICAgICAgICAgICBbZGF0ZV09XCJidWlsZFN0YXR1cy5kZXRhaWwuZ2VuZXJhdGVkQXRcIlxuICAgICAgICAgICAgICAgIGZvcm1hdD1cImRhdGUtdGltZVwiPlxuICAgICAgICAgICAgPC9mcy1kYXRlPlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJzdGF0XCI+XG4gICAgICAgICAgICA8c3BhbiBjbGFzcz1cImxhYmVsXCI+Q292ZXJhZ2U8L3NwYW4+XG4gICAgICAgICAgICA8c3Bhbj5cbiAgICAgICAgICAgICAge3sgYnVpbGRTdGF0dXMuZGV0YWlsLmVudGl0eUNvdW50IH19IGVudGl0aWVzIMK3XG4gICAgICAgICAgICAgIHt7IGJ1aWxkU3RhdHVzLmRldGFpbC5mdW5jdGlvbkNvdW50IH19IGNvZGUgZnVuY3Rpb25zXG4gICAgICAgICAgICA8L3NwYW4+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIH0gQGVsc2Uge1xuICAgICAgICAgIDxmcy1tZXNzYWdlLXdhcm5pbmc+XG4gICAgICAgICAgICBUaGUgc2VtYW50aWMgZGF0YWJhc2UgaGFzIG5vdCBiZWVuIGJ1aWx0IHlldCDigJQgdGhlIHJlcG9ydCBBSSBjYW4ndFxuICAgICAgICAgICAgYW5zd2VyIGRhdGEgcXVlc3Rpb25zIHVudGlsIGl0IGlzLiBSdW4gdGhlIGZpcnN0IGJ1aWxkLlxuICAgICAgICAgIDwvZnMtbWVzc2FnZS13YXJuaW5nPlxuICAgICAgICB9XG4gICAgICAgIEBpZiAoYnVpbGRTdGF0dXMuYnVpbGQpIHtcbiAgICAgICAgICA8ZGl2IGNsYXNzPVwic3RhdFwiPlxuICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJsYWJlbFwiPkxhc3QgYnVpbGQgcnVuPC9zcGFuPlxuICAgICAgICAgICAgPHNwYW4+XG4gICAgICAgICAgICAgIHt7IGJ1aWxkU3RhdGVMYWJlbCgpIH19XG4gICAgICAgICAgICAgIEBpZiAoYnVpbGRTdGF0dXMuYnVpbGQubWVzc2FnZSkge1xuICAgICAgICAgICAgICAgIOKAlCB7eyBidWlsZFN0YXR1cy5idWlsZC5tZXNzYWdlIH19XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIDwvc3Bhbj5cbiAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgfVxuICAgICAgPC9kaXY+XG5cbiAgICAgIDxkaXYgY2xhc3M9XCJidWlsZC1hY3Rpb25zXCI+XG4gICAgICAgIEBpZiAoYnVpbGRBY3RpdmUoKSkge1xuICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgICAgdHlwZT1cImJ1dHRvblwiXG4gICAgICAgICAgICAgIG1hdC1idXR0b25cbiAgICAgICAgICAgICAgY29sb3I9XCJ3YXJuXCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cInN0b3BCdWlsZCgpXCI+XG4gICAgICAgICAgICBTdG9wIEJ1aWxkXG4gICAgICAgICAgPC9idXR0b24+XG4gICAgICAgIH0gQGVsc2Uge1xuICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgICAgdHlwZT1cImJ1dHRvblwiXG4gICAgICAgICAgICAgIG1hdC1idXR0b25cbiAgICAgICAgICAgICAgY29sb3I9XCJwcmltYXJ5XCJcbiAgICAgICAgICAgICAgKGNsaWNrKT1cImJ1aWxkKClcIj5cbiAgICAgICAgICAgIHt7IGJ1aWxkU3RhdHVzLmRldGFpbC5nZW5lcmF0ZWRBdCA/ICdSZWJ1aWxkJyA6ICdCdWlsZCcgfX1cbiAgICAgICAgICA8L2J1dHRvbj5cbiAgICAgICAgfVxuICAgICAgPC9kaXY+XG5cbiAgICAgIDwhLS0gVGhlIG1vZGVsIHNjb3JlYm9hcmQ6IGV2ZXJ5IGVudGl0eSB0aGUgQUkga25vd3MgYWJvdXQsIGl0c1xuICAgICAgICAgICBidWlsdC12cy1uZWVkcy1idWlsZGluZyBzdGFnZXMsIGFuZCBhIGRyaWxsLWRvd24gaW50byBpdHMgc2hhcGUsIGVudW1cbiAgICAgICAgICAgdmFsdWVzLCBjb21wdXRlZCBmaWVsZHMgYW5kIEFJLWNsYXNzaWZpZWQgY29kZSBmdW5jdGlvbnMuIC0tPlxuICAgICAgQGlmIChidWlsZFN0YXR1cy5kZXRhaWwuZW50aXRpZXM/Lmxlbmd0aCkge1xuICAgICAgICA8bWF0LWFjY29yZGlvbiBjbGFzcz1cImVudGl0aWVzXCI+XG4gICAgICAgICAgQGZvciAoZW50aXR5IG9mIGJ1aWxkU3RhdHVzLmRldGFpbC5lbnRpdGllczsgdHJhY2sgZW50aXR5LmtleSkge1xuICAgICAgICAgICAgPG1hdC1leHBhbnNpb24tcGFuZWwgKG9wZW5lZCk9XCJlbnRpdHlPcGVuZWQoZW50aXR5KVwiPlxuICAgICAgICAgICAgICA8bWF0LWV4cGFuc2lvbi1wYW5lbC1oZWFkZXI+XG4gICAgICAgICAgICAgICAgPG1hdC1wYW5lbC10aXRsZT57eyBlbnRpdHkua2V5IH19PC9tYXQtcGFuZWwtdGl0bGU+XG4gICAgICAgICAgICAgICAgPG1hdC1wYW5lbC1kZXNjcmlwdGlvbj5cbiAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJzdGFnZS1jaGlwc1wiPlxuICAgICAgICAgICAgICAgICAgICBAZm9yIChzdGFnZSBvZiBzdGFnZUtleXM7IHRyYWNrIHN0YWdlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgPGZzLWNoaXBcbiAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT1cInRpbnlcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICBbYmFja2dyb3VuZENvbG9yXT1cInN0YWdlQ29sb3IoZW50aXR5LnN0YWdlc1tzdGFnZV0pXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9XCIjZmZmZmZmXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgW21hdFRvb2x0aXBdPVwic3RhZ2VUb29sdGlwKGVudGl0eS5zdGFnZXNbc3RhZ2VdKVwiPlxuICAgICAgICAgICAgICAgICAgICAgICAge3sgc3RhZ2VMYWJlbHNbc3RhZ2VdIH19XG4gICAgICAgICAgICAgICAgICAgICAgPC9mcy1jaGlwPlxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICA8L21hdC1wYW5lbC1kZXNjcmlwdGlvbj5cbiAgICAgICAgICAgICAgPC9tYXQtZXhwYW5zaW9uLXBhbmVsLWhlYWRlcj5cblxuICAgICAgICAgICAgICA8IS0tIERlZmVycmVkOiB0aGUgYm9keSAoYW5kIGl0cyBmdW5jdGlvbnMgcmVxdWVzdCkgb25seSBleGlzdHNcbiAgICAgICAgICAgICAgICAgICBvbmNlIGV4cGFuZGVkLiAtLT5cbiAgICAgICAgICAgICAgPG5nLXRlbXBsYXRlIG1hdEV4cGFuc2lvblBhbmVsQ29udGVudD5cbiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZW50aXR5LWRldGFpbFwiPlxuICAgICAgICAgICAgICAgICAgPGRpdj48c3Ryb25nPlRhYmxlOjwvc3Ryb25nPiB7eyBlbnRpdHkudGFibGUgfX08L2Rpdj5cbiAgICAgICAgICAgICAgICAgIDxkaXY+PHN0cm9uZz5Db2x1bW5zOjwvc3Ryb25nPiB7eyBlbnRpdHkuY29sdW1ucyB9fTwvZGl2PlxuICAgICAgICAgICAgICAgICAgPGRpdj48c3Ryb25nPlJlbGF0aW9uc2hpcHM6PC9zdHJvbmc+IHt7IGVudGl0eS5yZWxhdGlvbnNoaXBzIH19PC9kaXY+XG4gICAgICAgICAgICAgICAgICBAaWYgKGVudGl0eS5lbnVtcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImVudW0tbGlzdFwiPlxuICAgICAgICAgICAgICAgICAgICAgIDxzdHJvbmc+RW51bSB2YWx1ZXM6PC9zdHJvbmc+XG4gICAgICAgICAgICAgICAgICAgICAgQGZvciAoZW51bUNvbHVtbiBvZiBlbnRpdHkuZW51bXM7IHRyYWNrIGVudW1Db2x1bW4uY29sdW1uKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZW51bS1yb3dcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9XCJlbnVtLWNvbHVtblwiPnt7IGVudW1Db2x1bW4uY29sdW1uIH19PC9zcGFuPlxuICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cImVudW0tdmFsdWVzXCI+e3sgZW51bUNvbHVtbi52YWx1ZXMuam9pbignLCAnKSB9fTwvc3Bhbj5cbiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICBAaWYgKGVudGl0eS5jb21wdXRlZEZpZWxkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgPGRpdj48c3Ryb25nPkNvbXB1dGVkIGZpZWxkczo8L3N0cm9uZz4ge3sgZW50aXR5LmNvbXB1dGVkRmllbGRzLmpvaW4oJywgJykgfX08L2Rpdj5cbiAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZ1bmN0aW9ucy1zZWN0aW9uXCI+XG4gICAgICAgICAgICAgICAgICAgIDxoMyBjbGFzcz1cImZ1bmN0aW9ucy1oZWFkaW5nXCI+XG4gICAgICAgICAgICAgICAgICAgICAgQ29kZSBmdW5jdGlvbnNcbiAgICAgICAgICAgICAgICAgICAgICBAaWYgKGVudGl0eS5mdW5jdGlvbkNvdW50KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cImZ1bmN0aW9ucy1jb3VudFwiPih7eyBlbnRpdHkuZnVuY3Rpb25Db3VudCB9fSk8L3NwYW4+XG4gICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICA8L2gzPlxuXG4gICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmdW5jdGlvbnMtYm9keVwiICpmc1NrZWxldG9uPVwiZW50aXR5RnVuY3Rpb25zKGVudGl0eS5rZXkpXCI+XG4gICAgICAgICAgICAgICAgICAgICAgQGlmIChlbnRpdHlGdW5jdGlvbnMoZW50aXR5LmtleSk7IGFzIGZucykge1xuICAgICAgICAgICAgICAgICAgICAgICAgQGlmIChmbnMuZnVuY3Rpb25zLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICBAaWYgKGZucy5nZW5lcmF0ZWRBdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJoaW50IGZ1bmN0aW9ucy1nZW5lcmF0ZWRcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERpc2NvdmVyZWQgZnJvbSB7eyBmbnMuY2xhc3MgfX0gJm1pZGRvdDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxmcy1kYXRlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgW2RhdGVdPVwiZm5zLmdlbmVyYXRlZEF0XCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQ9XCJkYXRlLXRpbWVcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZnMtZGF0ZT5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZnVuY3Rpb25zLWxpc3RcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBAZm9yIChmbiBvZiBmbnMuZnVuY3Rpb25zOyB0cmFjayBmbi5tZXRob2QpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmdW5jdGlvbi1yb3dcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZ1bmN0aW9uLWhlYWRcIj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZnMtY2hpcFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplPVwidGlueVwiXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtiYWNrZ3JvdW5kQ29sb3JdPVwia2luZENvbG9yKGZuLmtpbmQpXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3I9XCIjZmZmZmZmXCI+XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7eyBmbi5raW5kIH19XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9mcy1jaGlwPlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxjb2RlIGNsYXNzPVwiZnVuY3Rpb24tbWV0aG9kXCI+e3sgZm4ubWV0aG9kIH19PC9jb2RlPlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZ1bmN0aW9uLXN1bW1hcnlcIj57eyBmbi5zdW1tYXJ5IH19PC9kaXY+XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEBpZiAoZm4ucHJvZHVjZXMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZnVuY3Rpb24tcHJvZHVjZXMgaGludFwiPlByb2R1Y2VzOiB7eyBmbi5wcm9kdWNlcyB9fTwvZGl2PlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICAgICAgICAgICAgfSBAZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJoaW50XCI+XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgTm8gZnVuY3Rpb25zIGNhY2hlZCBmb3IgdGhpcyBlbnRpdHkgeWV0IOKAlCBydW4gYSBidWlsZCB0byBkaXNjb3ZlciBpdHMgY29kZS5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgICAgICAgPC9tYXQtZXhwYW5zaW9uLXBhbmVsPlxuICAgICAgICAgIH1cbiAgICAgICAgPC9tYXQtYWNjb3JkaW9uPlxuICAgICAgfVxuICAgIH1cbiAgPC9kaXY+XG48L2Rpdj5cbiJdfQ==
|