@epistola.app/valtimo-plugin 0.7.0 → 0.8.0
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/fesm2022/epistola.app-valtimo-plugin.mjs +660 -458
- package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
- package/lib/components/check-job-status-configuration/check-job-status-configuration.component.d.ts +4 -1
- package/lib/components/download-document-configuration/download-document-configuration.component.d.ts +8 -1
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +17 -1
- package/lib/components/epistola-document/epistola-document.component.d.ts +65 -0
- package/lib/components/{epistola-download/epistola-download.formio.d.ts → epistola-document/epistola-document.formio.d.ts} +2 -2
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +13 -25
- package/lib/components/epistola-retry-form/epistola-retry-form.component.d.ts +3 -7
- package/lib/epistola.module.d.ts +4 -5
- package/lib/models/admin.d.ts +28 -0
- package/lib/models/config.d.ts +7 -11
- package/lib/services/epistola-admin.service.d.ts +14 -1
- package/lib/services/epistola-plugin.service.d.ts +46 -7
- package/lib/services/epistola-task-context.interceptor.d.ts +29 -0
- package/lib/services/epistola-task-context.matcher.d.ts +19 -0
- package/lib/services/epistola-task-context.service.d.ts +26 -0
- package/lib/services/index.d.ts +2 -0
- package/package.json +1 -1
- package/public_api.d.ts +2 -4
- package/sbom.json +1 -0
- package/lib/components/epistola-download/epistola-download.component.d.ts +0 -24
- package/lib/components/epistola-preview-button/epistola-preview-button.component.d.ts +0 -35
- package/lib/components/epistola-preview-button/epistola-preview-button.formio.d.ts +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, EventEmitter, Output, Input, Component, ChangeDetectionStrategy, inject, NgModule, ENVIRONMENT_INITIALIZER, Injector } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
|
-
import { HttpHeaders, HttpClientModule } from '@angular/common/http';
|
|
4
|
+
import { HttpHeaders, HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
|
5
5
|
import * as i2 from '@valtimo/shared';
|
|
6
6
|
import { ROLE_ADMIN } from '@valtimo/shared';
|
|
7
7
|
import { of, BehaviorSubject, combineLatest, take, Subject, debounceTime, takeUntil, merge } from 'rxjs';
|
|
@@ -16,12 +16,12 @@ import * as i2$2 from '@angular/forms';
|
|
|
16
16
|
import { FormsModule } from '@angular/forms';
|
|
17
17
|
import * as _jsonata from 'jsonata';
|
|
18
18
|
import * as i2$3 from '@valtimo/process-link';
|
|
19
|
-
import * as
|
|
19
|
+
import * as i2$4 from '@angular/platform-browser';
|
|
20
|
+
import * as i5 from '@formio/angular';
|
|
20
21
|
import { FormioModule } from '@formio/angular';
|
|
21
|
-
import * as
|
|
22
|
-
import * as i2$4 from '@angular/router';
|
|
22
|
+
import * as i2$5 from '@angular/router';
|
|
23
23
|
import { RouterModule, Router } from '@angular/router';
|
|
24
|
-
import * as i5 from 'carbon-components-angular/tabs';
|
|
24
|
+
import * as i5$1 from 'carbon-components-angular/tabs';
|
|
25
25
|
import { TabsModule } from 'carbon-components-angular/tabs';
|
|
26
26
|
import * as i6 from 'carbon-components-angular/tag';
|
|
27
27
|
import { TagModule } from 'carbon-components-angular/tag';
|
|
@@ -77,6 +77,23 @@ class EpistolaAdminService {
|
|
|
77
77
|
getPendingJobs() {
|
|
78
78
|
return this.http.get(`${this.apiEndpoint}/pending`);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Manually reconcile a stuck Epistola catch event by querying Epistola for the
|
|
82
|
+
* job's current status and re-running message correlation. Returns 200 with
|
|
83
|
+
* `correlated=true` on success, 409 with `correlated=false` when the job is
|
|
84
|
+
* still in flight, or surfaces a validation error when the execution can't be
|
|
85
|
+
* recovered (no subscription, missing variable, unknown tenant).
|
|
86
|
+
*/
|
|
87
|
+
reconcilePending(executionId) {
|
|
88
|
+
return this.http.post(`${this.apiEndpoint}/pending/${encodeURIComponent(executionId)}/reconcile`, null);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the latest BPMN race-safety validation violations across deployed
|
|
92
|
+
* process definitions. Empty list = healthy.
|
|
93
|
+
*/
|
|
94
|
+
getValidationViolations() {
|
|
95
|
+
return this.http.get(`${this.apiEndpoint}/validations`);
|
|
96
|
+
}
|
|
80
97
|
/**
|
|
81
98
|
* Export a single process link as a .process-link.json file.
|
|
82
99
|
*/
|
|
@@ -220,9 +237,11 @@ class EpistolaPluginService {
|
|
|
220
237
|
}
|
|
221
238
|
/**
|
|
222
239
|
* Get a dynamically generated Formio form for retrying a failed document generation.
|
|
240
|
+
*
|
|
241
|
+
* @param taskId Operaton user task id (required — backend authorizes via OperatonTask:VIEW)
|
|
223
242
|
*/
|
|
224
|
-
getRetryForm(processInstanceId, documentId, sourceActivityId) {
|
|
225
|
-
const params = { processInstanceId };
|
|
243
|
+
getRetryForm(taskId, processInstanceId, documentId, sourceActivityId) {
|
|
244
|
+
const params = { taskId, processInstanceId };
|
|
226
245
|
if (documentId) {
|
|
227
246
|
params['documentId'] = documentId;
|
|
228
247
|
}
|
|
@@ -245,24 +264,36 @@ class EpistolaPluginService {
|
|
|
245
264
|
return this.http.post(`${this.apiEndpoint}/validate-jsonata`, request);
|
|
246
265
|
}
|
|
247
266
|
/**
|
|
248
|
-
*
|
|
267
|
+
* Preview a document by dry-running the {@code generate-document} process
|
|
268
|
+
* link. Returns the rendered PDF as a {@link Blob}.
|
|
269
|
+
*
|
|
270
|
+
* <p>The {@code X-Skip-Interceptor: 422} header tells the global Valtimo
|
|
271
|
+
* error interceptor to skip its toast for validation failures so the
|
|
272
|
+
* caller can render an inline error message.
|
|
249
273
|
*/
|
|
250
|
-
|
|
251
|
-
return this.http.
|
|
252
|
-
|
|
274
|
+
previewToBlob(request) {
|
|
275
|
+
return this.http.post(`${this.apiEndpoint}/preview`, request, {
|
|
276
|
+
responseType: 'blob',
|
|
277
|
+
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
253
278
|
});
|
|
254
279
|
}
|
|
255
280
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
281
|
+
* Stream an already-generated Epistola PDF for the caller's task. The
|
|
282
|
+
* backend resolves the Epistola document id and tenant id from the named
|
|
283
|
+
* process variables on the task's process instance, so the wire never
|
|
284
|
+
* carries a raw PDF id.
|
|
258
285
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
286
|
+
downloadDocumentBlob(request) {
|
|
287
|
+
const params = new URLSearchParams({
|
|
288
|
+
taskId: request.taskId,
|
|
289
|
+
caseDocumentId: request.caseDocumentId,
|
|
290
|
+
documentVariable: request.documentVariable,
|
|
291
|
+
tenantIdVariable: request.tenantIdVariable,
|
|
292
|
+
filename: request.filename,
|
|
293
|
+
disposition: request.disposition,
|
|
294
|
+
});
|
|
295
|
+
return this.http.get(`${this.apiEndpoint}/documents/download?${params.toString()}`, {
|
|
296
|
+
responseType: 'blob',
|
|
266
297
|
});
|
|
267
298
|
}
|
|
268
299
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -272,6 +303,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
272
303
|
type: Injectable
|
|
273
304
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
274
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Holds the currently-open Operaton user task instance id so that custom
|
|
308
|
+
* Formio components rendered inside a Valtimo task form (preview, download,
|
|
309
|
+
* retry-form) can include it in backend requests for PBAC.
|
|
310
|
+
*
|
|
311
|
+
* Populated by {@link EpistolaTaskContextInterceptor}, which sniffs Valtimo's
|
|
312
|
+
* canonical "load process link for task" GET (`/api/v2/process-link/task/{taskId}`)
|
|
313
|
+
* — the request always fires when a task opens, before the form renders.
|
|
314
|
+
*
|
|
315
|
+
* <p><b>Why this exists:</b> Valtimo 13.21 does not expose the active task
|
|
316
|
+
* instance id through any service that custom Formio components can inject
|
|
317
|
+
* (`FormIoStateService` carries documentId and processInstanceId only;
|
|
318
|
+
* `TaskDetailContentComponent.taskInstanceId$` is private to that component
|
|
319
|
+
* and Formio elements live in their own injector tree). This service is a
|
|
320
|
+
* workaround until upstream exposes `taskInstanceId` via `FormIoStateService`.
|
|
321
|
+
*/
|
|
322
|
+
class EpistolaTaskContextService {
|
|
323
|
+
_taskInstanceId$ = new BehaviorSubject(null);
|
|
324
|
+
taskInstanceId$ = this._taskInstanceId$.asObservable();
|
|
325
|
+
get taskInstanceId() {
|
|
326
|
+
return this._taskInstanceId$.value;
|
|
327
|
+
}
|
|
328
|
+
setTaskInstanceId(id) {
|
|
329
|
+
this._taskInstanceId$.next(id);
|
|
330
|
+
}
|
|
331
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
332
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, providedIn: 'root' });
|
|
333
|
+
}
|
|
334
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, decorators: [{
|
|
335
|
+
type: Injectable,
|
|
336
|
+
args: [{ providedIn: 'root' }]
|
|
337
|
+
}] });
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Pure helpers for {@link EpistolaTaskContextInterceptor}, extracted so they
|
|
341
|
+
* can be unit-tested without pulling in {@code @angular/core} (which ts-jest
|
|
342
|
+
* cannot transform without {@code jest-preset-angular}).
|
|
343
|
+
*/
|
|
344
|
+
/**
|
|
345
|
+
* Pattern Valtimo uses for the canonical task-open call:
|
|
346
|
+
* {@code GET /api/v2/process-link/task/{taskInstanceId}}.
|
|
347
|
+
*
|
|
348
|
+
* Captures the {@code taskInstanceId} (UUID v4-style 36-character hyphenated
|
|
349
|
+
* hex string). Anchored at the end of the URL or at a query-string delimiter
|
|
350
|
+
* so we don't accidentally match a longer trailing segment.
|
|
351
|
+
*/
|
|
352
|
+
const TASK_PROCESS_LINK_PATTERN = /\/api\/v2\/process-link\/task\/([0-9a-fA-F-]{36})(?:\?|$)/;
|
|
353
|
+
/**
|
|
354
|
+
* Returns the captured {@code taskInstanceId} from a Valtimo task-open
|
|
355
|
+
* request URL, or {@code null} if the request does not match.
|
|
356
|
+
*/
|
|
357
|
+
function extractTaskInstanceIdFromUrl(method, url) {
|
|
358
|
+
if (method !== 'GET')
|
|
359
|
+
return null;
|
|
360
|
+
const match = TASK_PROCESS_LINK_PATTERN.exec(url);
|
|
361
|
+
return match ? match[1] : null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Sniffs Valtimo's task-open signal and pushes the active taskInstanceId into
|
|
366
|
+
* {@link EpistolaTaskContextService}. The signal is the canonical
|
|
367
|
+
* {@code GET /api/v2/process-link/task/{taskId}} call that
|
|
368
|
+
* {@code TaskDetailContentComponent.loadTaskDetails(...)} fires unconditionally
|
|
369
|
+
* before any task form is rendered (see @valtimo/task internals).
|
|
370
|
+
*
|
|
371
|
+
* <p>This interceptor does <b>not</b> modify the outgoing request. It only
|
|
372
|
+
* captures the taskId from the URL.
|
|
373
|
+
*
|
|
374
|
+
* <p>Workaround for Valtimo 13.21 not exposing taskInstanceId through any
|
|
375
|
+
* injectable service. Remove once upstream adds e.g.
|
|
376
|
+
* {@code FormIoStateService.setTaskInstanceId(...)}.
|
|
377
|
+
*
|
|
378
|
+
* <p>The actual URL-matching logic lives in
|
|
379
|
+
* {@link extractTaskInstanceIdFromUrl} so it can be unit-tested without an
|
|
380
|
+
* Angular harness.
|
|
381
|
+
*/
|
|
382
|
+
class EpistolaTaskContextInterceptor {
|
|
383
|
+
taskContext;
|
|
384
|
+
constructor(taskContext) {
|
|
385
|
+
this.taskContext = taskContext;
|
|
386
|
+
}
|
|
387
|
+
intercept(request, next) {
|
|
388
|
+
const taskId = extractTaskInstanceIdFromUrl(request.method, request.url);
|
|
389
|
+
if (taskId !== null && taskId !== this.taskContext.taskInstanceId) {
|
|
390
|
+
this.taskContext.setTaskInstanceId(taskId);
|
|
391
|
+
}
|
|
392
|
+
return next.handle(request);
|
|
393
|
+
}
|
|
394
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, deps: [{ token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
395
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor });
|
|
396
|
+
}
|
|
397
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, decorators: [{
|
|
398
|
+
type: Injectable
|
|
399
|
+
}], ctorParameters: () => [{ type: EpistolaTaskContextService }] });
|
|
400
|
+
|
|
275
401
|
class EpistolaConfigurationComponent {
|
|
276
402
|
save$;
|
|
277
403
|
disabled$;
|
|
@@ -1983,9 +2109,17 @@ class CheckJobStatusConfigurationComponent {
|
|
|
1983
2109
|
saveSubscription;
|
|
1984
2110
|
formValue$ = new BehaviorSubject(null);
|
|
1985
2111
|
valid$ = new BehaviorSubject(false);
|
|
2112
|
+
/** Resolved synchronously before the v-form renders — see download-document-configuration for the why. */
|
|
2113
|
+
resolvedPrefill = {};
|
|
2114
|
+
prefillResolved$ = new BehaviorSubject(false);
|
|
1986
2115
|
safeDisabled$;
|
|
1987
2116
|
ngOnInit() {
|
|
1988
2117
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2118
|
+
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2119
|
+
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2120
|
+
this.resolvedPrefill = prefill ?? {};
|
|
2121
|
+
this.prefillResolved$.next(true);
|
|
2122
|
+
});
|
|
1989
2123
|
this.openSaveSubscription();
|
|
1990
2124
|
}
|
|
1991
2125
|
ngOnDestroy() {
|
|
@@ -2013,11 +2147,11 @@ class CheckJobStatusConfigurationComponent {
|
|
|
2013
2147
|
});
|
|
2014
2148
|
}
|
|
2015
2149
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2016
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form
|
|
2150
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: CheckJobStatusConfigurationComponent, isStandalone: true, selector: "epistola-check-job-status-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.requestIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.statusVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.errorMessageVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
|
|
2017
2151
|
}
|
|
2018
2152
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: CheckJobStatusConfigurationComponent, decorators: [{
|
|
2019
2153
|
type: Component,
|
|
2020
|
-
args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form
|
|
2154
|
+
args: [{ selector: 'epistola-check-job-status-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"requestIdVariable\"\n [title]=\"'requestIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'requestIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.requestIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"statusVariable\"\n [title]=\"'statusVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'statusVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.statusVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"documentIdVariable\"\n [title]=\"'documentIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n\n <v-input\n name=\"errorMessageVariable\"\n [title]=\"'errorMessageVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'errorMessageVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.errorMessageVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"false\"\n >\n </v-input>\n</v-form>\n" }]
|
|
2021
2155
|
}], propDecorators: { save$: [{
|
|
2022
2156
|
type: Input
|
|
2023
2157
|
}], disabled$: [{
|
|
@@ -2042,9 +2176,21 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2042
2176
|
saveSubscription;
|
|
2043
2177
|
formValue$ = new BehaviorSubject(null);
|
|
2044
2178
|
valid$ = new BehaviorSubject(false);
|
|
2179
|
+
/**
|
|
2180
|
+
* Resolved prefill — populated synchronously before the v-form renders. Avoids the
|
|
2181
|
+
* v-input `[defaultValue]` async-binding race that otherwise drops one of the
|
|
2182
|
+
* fields when prefill arrives after mount.
|
|
2183
|
+
*/
|
|
2184
|
+
resolvedPrefill = {};
|
|
2185
|
+
prefillResolved$ = new BehaviorSubject(false);
|
|
2045
2186
|
safeDisabled$;
|
|
2046
2187
|
ngOnInit() {
|
|
2047
2188
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2189
|
+
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2190
|
+
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2191
|
+
this.resolvedPrefill = prefill ?? {};
|
|
2192
|
+
this.prefillResolved$.next(true);
|
|
2193
|
+
});
|
|
2048
2194
|
this.openSaveSubscription();
|
|
2049
2195
|
}
|
|
2050
2196
|
ngOnDestroy() {
|
|
@@ -2056,7 +2202,7 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2056
2202
|
this.handleValid(formValue);
|
|
2057
2203
|
}
|
|
2058
2204
|
handleValid(formValue) {
|
|
2059
|
-
const valid = !!(formValue?.
|
|
2205
|
+
const valid = !!(formValue?.documentVariable && formValue?.contentVariable);
|
|
2060
2206
|
this.valid$.next(valid);
|
|
2061
2207
|
this.valid.emit(valid);
|
|
2062
2208
|
}
|
|
@@ -2072,11 +2218,11 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2072
2218
|
});
|
|
2073
2219
|
}
|
|
2074
2220
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2075
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form
|
|
2221
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DownloadDocumentConfigurationComponent, isStandalone: true, selector: "epistola-download-document-configuration", inputs: { save$: "save$", disabled$: "disabled$", pluginId: "pluginId", prefillConfiguration$: "prefillConfiguration$" }, outputs: { valid: "valid", configuration: "configuration" }, ngImport: i0, template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: FormModule }, { kind: "component", type: i3.FormComponent, selector: "v-form", inputs: ["className"], outputs: ["valueChange"] }, { kind: "ngmodule", type: InputModule }, { kind: "component", type: i3.InputComponent, selector: "v-input", inputs: ["name", "type", "title", "titleTranslationKey", "defaultValue", "widthPx", "fullWidth", "margin", "smallMargin", "disabled", "step", "min", "maxLength", "tooltip", "required", "hideNumberSpinBox", "smallLabel", "rows", "clear$", "carbonTheme", "placeholder", "dataTestId", "trim", "presetsTitle", "presetOptions"], outputs: ["valueChange"] }] });
|
|
2076
2222
|
}
|
|
2077
2223
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
|
|
2078
2224
|
type: Component,
|
|
2079
|
-
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form
|
|
2225
|
+
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule], template: "<v-form (valueChange)=\"formValueChange($event)\" *ngIf=\"prefillResolved$ | async\">\n <v-input\n name=\"documentVariable\"\n [title]=\"'documentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'documentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.documentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n name=\"contentVariable\"\n [title]=\"'contentVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'contentVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.contentVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n</v-form>\n" }]
|
|
2080
2226
|
}], propDecorators: { save$: [{
|
|
2081
2227
|
type: Input
|
|
2082
2228
|
}], disabled$: [{
|
|
@@ -2091,35 +2237,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2091
2237
|
type: Output
|
|
2092
2238
|
}] } });
|
|
2093
2239
|
|
|
2094
|
-
|
|
2095
|
-
|
|
2240
|
+
/**
|
|
2241
|
+
* Unified Formio component for the after-generation Epistola PDF UX. Reads
|
|
2242
|
+
* the PDF id and tenant id from named process variables on the caller's
|
|
2243
|
+
* task via the {@code /documents/download} endpoint.
|
|
2244
|
+
*
|
|
2245
|
+
* <p>Three presentations, all backed by the same backend call:
|
|
2246
|
+
* <ul>
|
|
2247
|
+
* <li>{@code display="inline"} — render the PDF inline in a panel.</li>
|
|
2248
|
+
* <li>{@code display="button"} — show a click-to-save download button only.</li>
|
|
2249
|
+
* <li>{@code display="both"} (default) — inline panel with a download icon
|
|
2250
|
+
* in the header.</li>
|
|
2251
|
+
* </ul>
|
|
2252
|
+
*
|
|
2253
|
+
* <p>For the dry-run / what-would-be-generated UX use
|
|
2254
|
+
* {@code epistola-document-preview} instead.
|
|
2255
|
+
*/
|
|
2256
|
+
class EpistolaDocumentComponent {
|
|
2257
|
+
epistolaPluginService;
|
|
2258
|
+
sanitizer;
|
|
2259
|
+
formIoStateService;
|
|
2260
|
+
taskContext;
|
|
2261
|
+
cdr;
|
|
2096
2262
|
value;
|
|
2097
2263
|
valueChange = new EventEmitter();
|
|
2098
2264
|
disabled = false;
|
|
2265
|
+
label = 'Document';
|
|
2266
|
+
/** How to present the document. `both` (default) shows an inline panel with a download icon. */
|
|
2267
|
+
display = 'both';
|
|
2268
|
+
/**
|
|
2269
|
+
* Process-variable name holding the Epistola result. Default: `epistolaResult`.
|
|
2270
|
+
*
|
|
2271
|
+
* Type-tolerant on the backend: if the named variable holds a rich result object
|
|
2272
|
+
* (`Map<String, Object>` written by `generate-document` and updated by the result
|
|
2273
|
+
* collector), the backend digs out the `documentId` key. If it holds a plain
|
|
2274
|
+
* String (custom flow that wrote a bare id), the backend uses it as-is.
|
|
2275
|
+
*/
|
|
2276
|
+
documentVariable = 'epistolaResult';
|
|
2277
|
+
/** Process-variable name holding the Epistola tenant id. Default: `epistolaTenantId`. */
|
|
2278
|
+
tenantIdVariable = 'epistolaTenantId';
|
|
2279
|
+
/** Filename used for the download disposition. */
|
|
2099
2280
|
filename = 'document.pdf';
|
|
2100
|
-
|
|
2281
|
+
loading = false;
|
|
2101
2282
|
downloading = false;
|
|
2102
2283
|
error = null;
|
|
2103
|
-
|
|
2104
|
-
|
|
2284
|
+
previewUrl = null;
|
|
2285
|
+
currentBlobUrl = null;
|
|
2286
|
+
subscription;
|
|
2287
|
+
get designMode() {
|
|
2288
|
+
return !this.formIoStateService.documentId;
|
|
2105
2289
|
}
|
|
2106
|
-
constructor(
|
|
2107
|
-
this.
|
|
2290
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, taskContext, cdr) {
|
|
2291
|
+
this.epistolaPluginService = epistolaPluginService;
|
|
2292
|
+
this.sanitizer = sanitizer;
|
|
2293
|
+
this.formIoStateService = formIoStateService;
|
|
2294
|
+
this.taskContext = taskContext;
|
|
2295
|
+
this.cdr = cdr;
|
|
2296
|
+
}
|
|
2297
|
+
ngOnInit() {
|
|
2298
|
+
if (this.designMode) {
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (this.display !== 'button') {
|
|
2302
|
+
this.loadInline();
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
ngOnDestroy() {
|
|
2306
|
+
this.subscription?.unsubscribe();
|
|
2307
|
+
this.revokeBlobUrl();
|
|
2108
2308
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2309
|
+
refresh() {
|
|
2310
|
+
this.loadInline();
|
|
2111
2311
|
}
|
|
2112
2312
|
download() {
|
|
2113
|
-
if (
|
|
2313
|
+
if (this.designMode || this.downloading) {
|
|
2114
2314
|
return;
|
|
2115
2315
|
}
|
|
2316
|
+
const request = this.buildRequest('attachment');
|
|
2317
|
+
if (!request)
|
|
2318
|
+
return;
|
|
2116
2319
|
this.downloading = true;
|
|
2117
2320
|
this.error = null;
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
2121
|
-
`&filename=${encodeURIComponent(this.filename)}`;
|
|
2122
|
-
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
2321
|
+
this.cdr.markForCheck();
|
|
2322
|
+
this.epistolaPluginService.downloadDocumentBlob(request).subscribe({
|
|
2123
2323
|
next: (blob) => {
|
|
2124
2324
|
const objectUrl = URL.createObjectURL(blob);
|
|
2125
2325
|
const anchor = document.createElement('a');
|
|
@@ -2128,66 +2328,246 @@ class EpistolaDownloadComponent {
|
|
|
2128
2328
|
anchor.click();
|
|
2129
2329
|
URL.revokeObjectURL(objectUrl);
|
|
2130
2330
|
this.downloading = false;
|
|
2331
|
+
this.cdr.markForCheck();
|
|
2131
2332
|
},
|
|
2132
2333
|
error: (err) => {
|
|
2133
|
-
|
|
2134
|
-
this.error = 'Download mislukt. Probeer opnieuw.';
|
|
2334
|
+
this.error = err.status === 404 ? 'Document is nog niet gegenereerd.' : 'Download mislukt.';
|
|
2135
2335
|
this.downloading = false;
|
|
2336
|
+
this.cdr.markForCheck();
|
|
2136
2337
|
},
|
|
2137
2338
|
});
|
|
2138
2339
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2340
|
+
loadInline() {
|
|
2341
|
+
const request = this.buildRequest('inline');
|
|
2342
|
+
if (!request)
|
|
2343
|
+
return;
|
|
2344
|
+
this.loading = true;
|
|
2345
|
+
this.error = null;
|
|
2346
|
+
this.cdr.markForCheck();
|
|
2347
|
+
this.revokeBlobUrl();
|
|
2348
|
+
this.subscription?.unsubscribe();
|
|
2349
|
+
this.subscription = this.epistolaPluginService.downloadDocumentBlob(request).subscribe({
|
|
2350
|
+
next: (blob) => {
|
|
2351
|
+
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2352
|
+
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2353
|
+
this.loading = false;
|
|
2354
|
+
this.cdr.markForCheck();
|
|
2355
|
+
},
|
|
2356
|
+
error: (err) => {
|
|
2357
|
+
this.previewUrl = null;
|
|
2358
|
+
this.error =
|
|
2359
|
+
err.status === 404
|
|
2360
|
+
? 'Document is nog niet gegenereerd.'
|
|
2361
|
+
: 'Document kon niet geladen worden.';
|
|
2362
|
+
this.loading = false;
|
|
2363
|
+
this.cdr.markForCheck();
|
|
2364
|
+
},
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
buildRequest(disposition) {
|
|
2368
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2369
|
+
const caseDocumentId = this.formIoStateService.documentId;
|
|
2370
|
+
if (!taskId || !caseDocumentId) {
|
|
2371
|
+
this.error = 'Document is alleen beschikbaar binnen een taak.';
|
|
2372
|
+
this.cdr.markForCheck();
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
taskId,
|
|
2377
|
+
caseDocumentId,
|
|
2378
|
+
documentVariable: this.documentVariable,
|
|
2379
|
+
tenantIdVariable: this.tenantIdVariable,
|
|
2380
|
+
filename: this.filename,
|
|
2381
|
+
disposition,
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
revokeBlobUrl() {
|
|
2385
|
+
if (this.currentBlobUrl) {
|
|
2386
|
+
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2387
|
+
this.currentBlobUrl = null;
|
|
2388
|
+
this.previewUrl = null;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, deps: [{ token: EpistolaPluginService }, { token: i2$4.DomSanitizer }, { token: i3.FormIoStateService }, { token: EpistolaTaskContextService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2392
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentComponent, isStandalone: true, selector: "epistola-document-component", inputs: { value: "value", disabled: "disabled", label: "label", display: "display", documentVariable: "documentVariable", tenantIdVariable: "tenantIdVariable", filename: "filename" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
2393
|
+
<!-- Design-time placeholder -->
|
|
2394
|
+
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2395
|
+
<div class="doc-header">
|
|
2396
|
+
<span>{{ label || 'Document' }}</span>
|
|
2397
|
+
<span class="design-tag">design mode</span>
|
|
2398
|
+
</div>
|
|
2399
|
+
<div class="doc-body design-info">
|
|
2400
|
+
<div class="design-section">
|
|
2401
|
+
<div class="design-label">Display</div>
|
|
2402
|
+
<div class="design-value">{{ display }}</div>
|
|
2403
|
+
<div class="design-label">Document variable</div>
|
|
2404
|
+
<div class="design-value">{{ documentVariable }}</div>
|
|
2405
|
+
<div class="design-label">Tenant ID variable</div>
|
|
2406
|
+
<div class="design-value">{{ tenantIdVariable }}</div>
|
|
2407
|
+
</div>
|
|
2408
|
+
</div>
|
|
2409
|
+
</div>
|
|
2410
|
+
|
|
2411
|
+
<!-- Button-only display -->
|
|
2412
|
+
<div *ngIf="!designMode && display === 'button'">
|
|
2413
|
+
<button
|
|
2414
|
+
type="button"
|
|
2415
|
+
class="btn btn-outline-primary"
|
|
2416
|
+
[disabled]="disabled || downloading"
|
|
2417
|
+
(click)="download()"
|
|
2418
|
+
>
|
|
2419
|
+
<i class="mdi mdi-download mr-1"></i>
|
|
2420
|
+
{{ downloading ? 'Downloading...' : label || 'Download PDF' }}
|
|
2421
|
+
</button>
|
|
2422
|
+
<span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
|
|
2423
|
+
</div>
|
|
2424
|
+
|
|
2425
|
+
<!-- Inline / both: panel with optional download icon -->
|
|
2426
|
+
<div *ngIf="!designMode && display !== 'button'" class="epistola-doc-panel">
|
|
2427
|
+
<div class="doc-header">
|
|
2428
|
+
<span>{{ label || 'Document' }}</span>
|
|
2429
|
+
<div class="doc-controls">
|
|
2430
|
+
<button
|
|
2431
|
+
*ngIf="display === 'both'"
|
|
2432
|
+
type="button"
|
|
2433
|
+
class="doc-icon-btn"
|
|
2434
|
+
[disabled]="disabled || downloading"
|
|
2435
|
+
(click)="download()"
|
|
2436
|
+
[title]="downloading ? 'Downloading...' : 'Download'"
|
|
2437
|
+
>
|
|
2438
|
+
<i class="mdi mdi-download"></i>
|
|
2439
|
+
</button>
|
|
2440
|
+
<button
|
|
2441
|
+
type="button"
|
|
2442
|
+
class="doc-icon-btn"
|
|
2443
|
+
[disabled]="loading"
|
|
2444
|
+
(click)="refresh()"
|
|
2445
|
+
title="Refresh"
|
|
2446
|
+
>
|
|
2447
|
+
<i class="mdi mdi-refresh"></i>
|
|
2448
|
+
</button>
|
|
2449
|
+
</div>
|
|
2450
|
+
</div>
|
|
2451
|
+
<div class="doc-body">
|
|
2452
|
+
<div *ngIf="loading" class="doc-loading">Loading document...</div>
|
|
2453
|
+
<div *ngIf="error && !loading" class="doc-unavailable">
|
|
2454
|
+
<i class="mdi mdi-information-outline"></i>
|
|
2455
|
+
{{ error }}
|
|
2456
|
+
</div>
|
|
2457
|
+
<object
|
|
2458
|
+
*ngIf="previewUrl && !loading"
|
|
2459
|
+
[data]="previewUrl"
|
|
2460
|
+
type="application/pdf"
|
|
2461
|
+
class="doc-pdf"
|
|
2462
|
+
>
|
|
2463
|
+
PDF preview is not supported in this browser.
|
|
2464
|
+
</object>
|
|
2465
|
+
</div>
|
|
2466
|
+
</div>
|
|
2467
|
+
`, isInline: true, styles: [".epistola-doc-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.doc-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057}.doc-controls{display:flex;gap:.25rem}.doc-icon-btn{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .5rem;font-size:.9rem;cursor:pointer}.doc-icon-btn:hover:not(:disabled){background:#e9ecef}.doc-icon-btn:disabled{opacity:.5;cursor:not-allowed}.doc-body{display:flex;flex-direction:column;min-height:500px}.doc-loading,.doc-unavailable{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.doc-unavailable i{margin-right:.25rem}.doc-pdf{width:100%;flex:1;min-height:500px}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.5rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-tag{font-size:.7rem;font-weight:400;color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2152
2468
|
}
|
|
2153
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
2469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentComponent, decorators: [{
|
|
2154
2470
|
type: Component,
|
|
2155
|
-
args: [{
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
class="
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2471
|
+
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-document-component', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2472
|
+
<!-- Design-time placeholder -->
|
|
2473
|
+
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2474
|
+
<div class="doc-header">
|
|
2475
|
+
<span>{{ label || 'Document' }}</span>
|
|
2476
|
+
<span class="design-tag">design mode</span>
|
|
2477
|
+
</div>
|
|
2478
|
+
<div class="doc-body design-info">
|
|
2479
|
+
<div class="design-section">
|
|
2480
|
+
<div class="design-label">Display</div>
|
|
2481
|
+
<div class="design-value">{{ display }}</div>
|
|
2482
|
+
<div class="design-label">Document variable</div>
|
|
2483
|
+
<div class="design-value">{{ documentVariable }}</div>
|
|
2484
|
+
<div class="design-label">Tenant ID variable</div>
|
|
2485
|
+
<div class="design-value">{{ tenantIdVariable }}</div>
|
|
2486
|
+
</div>
|
|
2487
|
+
</div>
|
|
2488
|
+
</div>
|
|
2489
|
+
|
|
2490
|
+
<!-- Button-only display -->
|
|
2491
|
+
<div *ngIf="!designMode && display === 'button'">
|
|
2492
|
+
<button
|
|
2493
|
+
type="button"
|
|
2494
|
+
class="btn btn-outline-primary"
|
|
2495
|
+
[disabled]="disabled || downloading"
|
|
2496
|
+
(click)="download()"
|
|
2497
|
+
>
|
|
2498
|
+
<i class="mdi mdi-download mr-1"></i>
|
|
2499
|
+
{{ downloading ? 'Downloading...' : label || 'Download PDF' }}
|
|
2500
|
+
</button>
|
|
2501
|
+
<span *ngIf="error" class="text-danger ml-2">{{ error }}</span>
|
|
2502
|
+
</div>
|
|
2503
|
+
|
|
2504
|
+
<!-- Inline / both: panel with optional download icon -->
|
|
2505
|
+
<div *ngIf="!designMode && display !== 'button'" class="epistola-doc-panel">
|
|
2506
|
+
<div class="doc-header">
|
|
2507
|
+
<span>{{ label || 'Document' }}</span>
|
|
2508
|
+
<div class="doc-controls">
|
|
2509
|
+
<button
|
|
2510
|
+
*ngIf="display === 'both'"
|
|
2511
|
+
type="button"
|
|
2512
|
+
class="doc-icon-btn"
|
|
2513
|
+
[disabled]="disabled || downloading"
|
|
2514
|
+
(click)="download()"
|
|
2515
|
+
[title]="downloading ? 'Downloading...' : 'Download'"
|
|
2516
|
+
>
|
|
2517
|
+
<i class="mdi mdi-download"></i>
|
|
2518
|
+
</button>
|
|
2519
|
+
<button
|
|
2520
|
+
type="button"
|
|
2521
|
+
class="doc-icon-btn"
|
|
2522
|
+
[disabled]="loading"
|
|
2523
|
+
(click)="refresh()"
|
|
2524
|
+
title="Refresh"
|
|
2525
|
+
>
|
|
2526
|
+
<i class="mdi mdi-refresh"></i>
|
|
2527
|
+
</button>
|
|
2528
|
+
</div>
|
|
2529
|
+
</div>
|
|
2530
|
+
<div class="doc-body">
|
|
2531
|
+
<div *ngIf="loading" class="doc-loading">Loading document...</div>
|
|
2532
|
+
<div *ngIf="error && !loading" class="doc-unavailable">
|
|
2533
|
+
<i class="mdi mdi-information-outline"></i>
|
|
2534
|
+
{{ error }}
|
|
2535
|
+
</div>
|
|
2536
|
+
<object
|
|
2537
|
+
*ngIf="previewUrl && !loading"
|
|
2538
|
+
[data]="previewUrl"
|
|
2539
|
+
type="application/pdf"
|
|
2540
|
+
class="doc-pdf"
|
|
2541
|
+
>
|
|
2542
|
+
PDF preview is not supported in this browser.
|
|
2543
|
+
</object>
|
|
2544
|
+
</div>
|
|
2545
|
+
</div>
|
|
2546
|
+
`, styles: [".epistola-doc-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.doc-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057}.doc-controls{display:flex;gap:.25rem}.doc-icon-btn{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .5rem;font-size:.9rem;cursor:pointer}.doc-icon-btn:hover:not(:disabled){background:#e9ecef}.doc-icon-btn:disabled{opacity:.5;cursor:not-allowed}.doc-body{display:flex;flex-direction:column;min-height:500px}.doc-loading,.doc-unavailable{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.doc-unavailable i{margin-right:.25rem}.doc-pdf{width:100%;flex:1;min-height:500px}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.5rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-tag{font-size:.7rem;font-weight:400;color:#6c757d;font-style:italic}\n"] }]
|
|
2547
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: EpistolaTaskContextService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
2173
2548
|
type: Input
|
|
2174
2549
|
}], valueChange: [{
|
|
2175
2550
|
type: Output
|
|
2176
2551
|
}], disabled: [{
|
|
2177
2552
|
type: Input
|
|
2178
|
-
}], filename: [{
|
|
2179
|
-
type: Input
|
|
2180
2553
|
}], label: [{
|
|
2181
2554
|
type: Input
|
|
2555
|
+
}], display: [{
|
|
2556
|
+
type: Input
|
|
2557
|
+
}], documentVariable: [{
|
|
2558
|
+
type: Input
|
|
2559
|
+
}], tenantIdVariable: [{
|
|
2560
|
+
type: Input
|
|
2561
|
+
}], filename: [{
|
|
2562
|
+
type: Input
|
|
2182
2563
|
}] } });
|
|
2183
2564
|
|
|
2184
2565
|
class EpistolaRetryFormComponent {
|
|
2185
2566
|
epistolaPluginService;
|
|
2186
2567
|
formIoStateService;
|
|
2187
2568
|
cdr;
|
|
2188
|
-
http;
|
|
2189
2569
|
sanitizer;
|
|
2190
|
-
|
|
2570
|
+
taskContext;
|
|
2191
2571
|
value;
|
|
2192
2572
|
valueChange = new EventEmitter();
|
|
2193
2573
|
disabled = false;
|
|
@@ -2208,19 +2588,16 @@ class EpistolaRetryFormComponent {
|
|
|
2208
2588
|
currentBlobUrl = null;
|
|
2209
2589
|
resolvedSourceActivityId;
|
|
2210
2590
|
processDefinitionKey;
|
|
2211
|
-
apiEndpoint;
|
|
2212
2591
|
formOptions = {
|
|
2213
2592
|
noAlerts: true,
|
|
2214
2593
|
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
|
|
2215
2594
|
};
|
|
2216
|
-
constructor(epistolaPluginService, formIoStateService, cdr,
|
|
2595
|
+
constructor(epistolaPluginService, formIoStateService, cdr, sanitizer, taskContext) {
|
|
2217
2596
|
this.epistolaPluginService = epistolaPluginService;
|
|
2218
2597
|
this.formIoStateService = formIoStateService;
|
|
2219
2598
|
this.cdr = cdr;
|
|
2220
|
-
this.http = http;
|
|
2221
2599
|
this.sanitizer = sanitizer;
|
|
2222
|
-
this.
|
|
2223
|
-
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
2600
|
+
this.taskContext = taskContext;
|
|
2224
2601
|
// Debounce preview calls
|
|
2225
2602
|
this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
|
|
2226
2603
|
this.loadPreview(data);
|
|
@@ -2257,6 +2634,12 @@ class EpistolaRetryFormComponent {
|
|
|
2257
2634
|
const processInstanceId = this.formIoStateService.processInstanceId;
|
|
2258
2635
|
if (!documentId || !processInstanceId)
|
|
2259
2636
|
return;
|
|
2637
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2638
|
+
if (!taskId) {
|
|
2639
|
+
this.previewError = 'Preview is only available from within a user task.';
|
|
2640
|
+
this.cdr.markForCheck();
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2260
2643
|
this.previewLoading = true;
|
|
2261
2644
|
this.previewError = null;
|
|
2262
2645
|
this.cdr.markForCheck();
|
|
@@ -2265,13 +2648,14 @@ class EpistolaRetryFormComponent {
|
|
|
2265
2648
|
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2266
2649
|
this.currentBlobUrl = null;
|
|
2267
2650
|
}
|
|
2268
|
-
this.
|
|
2269
|
-
.
|
|
2651
|
+
this.epistolaPluginService
|
|
2652
|
+
.previewToBlob({
|
|
2653
|
+
taskId,
|
|
2270
2654
|
documentId,
|
|
2271
2655
|
processInstanceId,
|
|
2272
2656
|
sourceActivityId: this.sourceActivityId || null,
|
|
2273
2657
|
overrides: formData,
|
|
2274
|
-
}
|
|
2658
|
+
})
|
|
2275
2659
|
.subscribe({
|
|
2276
2660
|
next: (blob) => {
|
|
2277
2661
|
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
@@ -2313,8 +2697,15 @@ class EpistolaRetryFormComponent {
|
|
|
2313
2697
|
this.cdr.markForCheck();
|
|
2314
2698
|
return;
|
|
2315
2699
|
}
|
|
2700
|
+
const taskId = this.taskContext.taskInstanceId;
|
|
2701
|
+
if (!taskId) {
|
|
2702
|
+
this.error = 'Retry form is only available from within a user task.';
|
|
2703
|
+
this.loading = false;
|
|
2704
|
+
this.cdr.markForCheck();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2316
2707
|
this.loadSubscription = this.epistolaPluginService
|
|
2317
|
-
.getRetryForm(processInstanceId, documentId ?? undefined, this.sourceActivityId)
|
|
2708
|
+
.getRetryForm(taskId, processInstanceId, documentId ?? undefined, this.sourceActivityId)
|
|
2318
2709
|
.subscribe({
|
|
2319
2710
|
next: (form) => {
|
|
2320
2711
|
this.formDefinition = form;
|
|
@@ -2337,7 +2728,7 @@ class EpistolaRetryFormComponent {
|
|
|
2337
2728
|
},
|
|
2338
2729
|
});
|
|
2339
2730
|
}
|
|
2340
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token:
|
|
2731
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: i2$4.DomSanitizer }, { token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2341
2732
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaRetryFormComponent, isStandalone: true, selector: "epistola-retry-form-component", inputs: { value: "value", disabled: "disabled", label: "label", sourceActivityId: "sourceActivityId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2342
2733
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
2343
2734
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
@@ -2376,7 +2767,7 @@ class EpistolaRetryFormComponent {
|
|
|
2376
2767
|
</div>
|
|
2377
2768
|
</div>
|
|
2378
2769
|
</div>
|
|
2379
|
-
`, isInline: true, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type:
|
|
2770
|
+
`, isInline: true, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormioModule }, { kind: "component", type: i5.FormioComponent, selector: "formio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2380
2771
|
}
|
|
2381
2772
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, decorators: [{
|
|
2382
2773
|
type: Component,
|
|
@@ -2419,7 +2810,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2419
2810
|
</div>
|
|
2420
2811
|
</div>
|
|
2421
2812
|
`, styles: [".epistola-retry-loading{padding:1rem;color:#6c757d}.epistola-retry-error{padding:.5rem;color:#dc3545}.epistola-retry-container{display:flex;gap:1rem}.epistola-retry-form{flex:2;min-width:0}.epistola-retry-preview{flex:1;min-width:0;border:1px solid #dee2e6;border-radius:4px;padding:1rem;background:#f8f9fa;display:flex;flex-direction:column}.preview-expanded .epistola-retry-preview{flex:1}.preview-header{display:flex;justify-content:space-between;align-items:center;font-weight:700;margin-bottom:.5rem;color:#495057}.preview-toggle{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.2rem .5rem;font-size:.75rem;cursor:pointer}.preview-toggle:hover{background:#e9ecef}.preview-loading{color:#6c757d;font-style:italic}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-expanded .preview-pdf{min-height:80vh}.preview-error{color:#dc3545}.preview-empty{color:#6c757d;font-style:italic}\n"] }]
|
|
2422
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type:
|
|
2813
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: i2$4.DomSanitizer }, { type: EpistolaTaskContextService }], propDecorators: { value: [{
|
|
2423
2814
|
type: Input
|
|
2424
2815
|
}], valueChange: [{
|
|
2425
2816
|
type: Output
|
|
@@ -2431,160 +2822,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2431
2822
|
type: Input
|
|
2432
2823
|
}] } });
|
|
2433
2824
|
|
|
2434
|
-
class EpistolaPreviewButtonComponent {
|
|
2435
|
-
http;
|
|
2436
|
-
sanitizer;
|
|
2437
|
-
configService;
|
|
2438
|
-
value;
|
|
2439
|
-
valueChange = new EventEmitter();
|
|
2440
|
-
disabled = false;
|
|
2441
|
-
label = 'Preview PDF';
|
|
2442
|
-
modalOpen = false;
|
|
2443
|
-
loading = false;
|
|
2444
|
-
previewLoading = false;
|
|
2445
|
-
previewError = null;
|
|
2446
|
-
previewUrl = null;
|
|
2447
|
-
currentBlobUrl = null;
|
|
2448
|
-
apiEndpoint;
|
|
2449
|
-
get buttonLabel() {
|
|
2450
|
-
return this.label || 'Preview PDF';
|
|
2451
|
-
}
|
|
2452
|
-
constructor(http, sanitizer, configService) {
|
|
2453
|
-
this.http = http;
|
|
2454
|
-
this.sanitizer = sanitizer;
|
|
2455
|
-
this.configService = configService;
|
|
2456
|
-
this.apiEndpoint = `${this.configService.config.valtimoApi.endpointUri}v1/plugin/epistola`;
|
|
2457
|
-
}
|
|
2458
|
-
ngOnDestroy() {
|
|
2459
|
-
this.revokeBlobUrl();
|
|
2460
|
-
}
|
|
2461
|
-
hasRequiredData() {
|
|
2462
|
-
return !!(this.value?.documentId && this.value?.tenantId);
|
|
2463
|
-
}
|
|
2464
|
-
openPreview() {
|
|
2465
|
-
if (!this.hasRequiredData() || this.loading)
|
|
2466
|
-
return;
|
|
2467
|
-
this.modalOpen = true;
|
|
2468
|
-
this.previewLoading = true;
|
|
2469
|
-
this.previewError = null;
|
|
2470
|
-
this.revokeBlobUrl();
|
|
2471
|
-
const { documentId, tenantId } = this.value;
|
|
2472
|
-
const url = `${this.apiEndpoint}/documents/${encodeURIComponent(documentId)}/download` +
|
|
2473
|
-
`?tenantId=${encodeURIComponent(tenantId)}` +
|
|
2474
|
-
`&filename=preview.pdf`;
|
|
2475
|
-
this.http.get(url, { responseType: 'blob' }).subscribe({
|
|
2476
|
-
next: (blob) => {
|
|
2477
|
-
this.currentBlobUrl = URL.createObjectURL(blob);
|
|
2478
|
-
this.previewUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.currentBlobUrl);
|
|
2479
|
-
this.previewLoading = false;
|
|
2480
|
-
},
|
|
2481
|
-
error: () => {
|
|
2482
|
-
this.previewError = 'Could not load the document.';
|
|
2483
|
-
this.previewLoading = false;
|
|
2484
|
-
},
|
|
2485
|
-
});
|
|
2486
|
-
}
|
|
2487
|
-
closePreview() {
|
|
2488
|
-
this.modalOpen = false;
|
|
2489
|
-
this.revokeBlobUrl();
|
|
2490
|
-
this.previewUrl = null;
|
|
2491
|
-
this.previewError = null;
|
|
2492
|
-
}
|
|
2493
|
-
revokeBlobUrl() {
|
|
2494
|
-
if (this.currentBlobUrl) {
|
|
2495
|
-
URL.revokeObjectURL(this.currentBlobUrl);
|
|
2496
|
-
this.currentBlobUrl = null;
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, deps: [{ token: i1.HttpClient }, { token: i4.DomSanitizer }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2500
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaPreviewButtonComponent, isStandalone: true, selector: "epistola-preview-button-component", inputs: { value: "value", disabled: "disabled", label: "label" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
2501
|
-
<button
|
|
2502
|
-
type="button"
|
|
2503
|
-
class="btn btn-outline-secondary"
|
|
2504
|
-
[disabled]="disabled || loading || !hasRequiredData()"
|
|
2505
|
-
(click)="openPreview()"
|
|
2506
|
-
>
|
|
2507
|
-
<i class="mdi mdi-eye mr-1"></i>
|
|
2508
|
-
{{ loading ? 'Loading...' : buttonLabel }}
|
|
2509
|
-
</button>
|
|
2510
|
-
|
|
2511
|
-
<div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
|
|
2512
|
-
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
2513
|
-
<div class="preview-modal-header">
|
|
2514
|
-
<span>Document Preview</span>
|
|
2515
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2516
|
-
×
|
|
2517
|
-
</button>
|
|
2518
|
-
</div>
|
|
2519
|
-
<div class="preview-modal-body">
|
|
2520
|
-
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
2521
|
-
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
2522
|
-
<object
|
|
2523
|
-
*ngIf="previewUrl && !previewLoading"
|
|
2524
|
-
[data]="previewUrl"
|
|
2525
|
-
type="application/pdf"
|
|
2526
|
-
class="preview-pdf"
|
|
2527
|
-
>
|
|
2528
|
-
PDF preview is not supported in this browser.
|
|
2529
|
-
</object>
|
|
2530
|
-
</div>
|
|
2531
|
-
</div>
|
|
2532
|
-
</div>
|
|
2533
|
-
`, isInline: true, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
2534
|
-
}
|
|
2535
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPreviewButtonComponent, decorators: [{
|
|
2536
|
-
type: Component,
|
|
2537
|
-
args: [{ standalone: true, imports: [CommonModule], selector: 'epistola-preview-button-component', template: `
|
|
2538
|
-
<button
|
|
2539
|
-
type="button"
|
|
2540
|
-
class="btn btn-outline-secondary"
|
|
2541
|
-
[disabled]="disabled || loading || !hasRequiredData()"
|
|
2542
|
-
(click)="openPreview()"
|
|
2543
|
-
>
|
|
2544
|
-
<i class="mdi mdi-eye mr-1"></i>
|
|
2545
|
-
{{ loading ? 'Loading...' : buttonLabel }}
|
|
2546
|
-
</button>
|
|
2547
|
-
|
|
2548
|
-
<div *ngIf="modalOpen" class="preview-modal-overlay" (click)="closePreview()">
|
|
2549
|
-
<div class="preview-modal-content" (click)="$event.stopPropagation()">
|
|
2550
|
-
<div class="preview-modal-header">
|
|
2551
|
-
<span>Document Preview</span>
|
|
2552
|
-
<button type="button" class="preview-modal-close" (click)="closePreview()">
|
|
2553
|
-
×
|
|
2554
|
-
</button>
|
|
2555
|
-
</div>
|
|
2556
|
-
<div class="preview-modal-body">
|
|
2557
|
-
<div *ngIf="previewLoading" class="preview-loading">Generating preview...</div>
|
|
2558
|
-
<div *ngIf="previewError" class="preview-error">{{ previewError }}</div>
|
|
2559
|
-
<object
|
|
2560
|
-
*ngIf="previewUrl && !previewLoading"
|
|
2561
|
-
[data]="previewUrl"
|
|
2562
|
-
type="application/pdf"
|
|
2563
|
-
class="preview-pdf"
|
|
2564
|
-
>
|
|
2565
|
-
PDF preview is not supported in this browser.
|
|
2566
|
-
</object>
|
|
2567
|
-
</div>
|
|
2568
|
-
</div>
|
|
2569
|
-
</div>
|
|
2570
|
-
`, styles: [".preview-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000}.preview-modal-content{background:#fff;border-radius:8px;width:90vw;height:90vh;max-width:1200px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 8px 32px #0000004d}.preview-modal-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;font-size:1rem}.preview-modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:#6c757d;line-height:1;padding:0 .25rem}.preview-modal-close:hover{color:#333}.preview-modal-body{flex:1;overflow:hidden;display:flex;flex-direction:column}.preview-loading,.preview-error{padding:2rem;text-align:center}.preview-error{color:#dc3545}.preview-pdf{width:100%;flex:1}\n"] }]
|
|
2571
|
-
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i4.DomSanitizer }, { type: i2.ConfigService }], propDecorators: { value: [{
|
|
2572
|
-
type: Input
|
|
2573
|
-
}], valueChange: [{
|
|
2574
|
-
type: Output
|
|
2575
|
-
}], disabled: [{
|
|
2576
|
-
type: Input
|
|
2577
|
-
}], label: [{
|
|
2578
|
-
type: Input
|
|
2579
|
-
}] } });
|
|
2580
|
-
|
|
2581
2825
|
class EpistolaDocumentPreviewComponent {
|
|
2582
2826
|
epistolaPluginService;
|
|
2583
|
-
http;
|
|
2584
2827
|
sanitizer;
|
|
2585
|
-
configService;
|
|
2586
2828
|
formIoStateService;
|
|
2587
2829
|
cdr;
|
|
2830
|
+
taskContext;
|
|
2588
2831
|
value;
|
|
2589
2832
|
valueChange = new EventEmitter();
|
|
2590
2833
|
disabled = false;
|
|
@@ -2592,30 +2835,27 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2592
2835
|
processDefinitionKey;
|
|
2593
2836
|
sourceActivityId;
|
|
2594
2837
|
overrideMapping;
|
|
2595
|
-
sources = [];
|
|
2596
|
-
selectedIndex = 0;
|
|
2597
|
-
discovering = false;
|
|
2598
2838
|
loading = false;
|
|
2599
2839
|
error = null;
|
|
2600
2840
|
previewUrl = null;
|
|
2601
2841
|
designMode = false;
|
|
2602
2842
|
initialized = false;
|
|
2603
2843
|
currentBlobUrl = null;
|
|
2604
|
-
discoverSubscription;
|
|
2605
2844
|
previewSubscription;
|
|
2606
|
-
|
|
2607
|
-
/** Whether the component is in configured mode (explicit process link) vs auto-discover mode */
|
|
2608
|
-
get configuredMode() {
|
|
2609
|
-
return !!this.sourceActivityId;
|
|
2610
|
-
}
|
|
2611
|
-
constructor(epistolaPluginService, http, sanitizer, configService, formIoStateService, cdr) {
|
|
2845
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr, taskContext) {
|
|
2612
2846
|
this.epistolaPluginService = epistolaPluginService;
|
|
2613
|
-
this.http = http;
|
|
2614
2847
|
this.sanitizer = sanitizer;
|
|
2615
|
-
this.configService = configService;
|
|
2616
2848
|
this.formIoStateService = formIoStateService;
|
|
2617
2849
|
this.cdr = cdr;
|
|
2618
|
-
this.
|
|
2850
|
+
this.taskContext = taskContext;
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Resolve the active task id from {@link EpistolaTaskContextService}, populated
|
|
2854
|
+
* by {@code EpistolaTaskContextInterceptor} on the canonical Valtimo task-open
|
|
2855
|
+
* call. Returns null when used outside a task context (e.g. Formio builder).
|
|
2856
|
+
*/
|
|
2857
|
+
get currentTaskId() {
|
|
2858
|
+
return this.taskContext.taskInstanceId;
|
|
2619
2859
|
}
|
|
2620
2860
|
get overrideMappingScopes() {
|
|
2621
2861
|
return this.overrideMapping ? Object.keys(this.overrideMapping) : [];
|
|
@@ -2636,122 +2876,69 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2636
2876
|
this.cdr.markForCheck();
|
|
2637
2877
|
return;
|
|
2638
2878
|
}
|
|
2639
|
-
if (this.
|
|
2640
|
-
this.
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
this.discoverSources();
|
|
2879
|
+
if (!this.sourceActivityId) {
|
|
2880
|
+
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2881
|
+
this.cdr.markForCheck();
|
|
2882
|
+
return;
|
|
2644
2883
|
}
|
|
2884
|
+
this.loadPreview();
|
|
2645
2885
|
return;
|
|
2646
2886
|
}
|
|
2647
|
-
//
|
|
2648
|
-
if (
|
|
2649
|
-
this.
|
|
2887
|
+
// React to value changes (input overrides from the Formio wrapper).
|
|
2888
|
+
if (changes['value']) {
|
|
2889
|
+
this.loadPreview();
|
|
2650
2890
|
}
|
|
2651
2891
|
}
|
|
2652
2892
|
ngOnDestroy() {
|
|
2653
|
-
this.discoverSubscription?.unsubscribe();
|
|
2654
2893
|
this.previewSubscription?.unsubscribe();
|
|
2655
2894
|
this.revokeBlobUrl();
|
|
2656
2895
|
}
|
|
2657
|
-
onSourceChange(event) {
|
|
2658
|
-
this.selectedIndex = +event.target.value;
|
|
2659
|
-
this.loadDiscoveredPreview();
|
|
2660
|
-
}
|
|
2661
2896
|
refresh() {
|
|
2662
|
-
|
|
2663
|
-
this.loadConfiguredPreview();
|
|
2664
|
-
}
|
|
2665
|
-
else {
|
|
2666
|
-
this.loadDiscoveredPreview();
|
|
2667
|
-
}
|
|
2897
|
+
this.loadPreview();
|
|
2668
2898
|
}
|
|
2669
2899
|
/**
|
|
2670
|
-
*
|
|
2900
|
+
* Preview using the explicitly configured process link + input overrides.
|
|
2901
|
+
* Requires a runtime task context — the backend authorizes the request against
|
|
2902
|
+
* the task's process instance and case document, so all three ids must match.
|
|
2671
2903
|
*/
|
|
2672
|
-
|
|
2904
|
+
loadPreview() {
|
|
2673
2905
|
const documentId = this.formIoStateService.documentId;
|
|
2674
2906
|
if (!documentId) {
|
|
2675
2907
|
this.error = 'Could not determine document ID from context.';
|
|
2676
2908
|
this.cdr.markForCheck();
|
|
2677
2909
|
return;
|
|
2678
2910
|
}
|
|
2679
|
-
this.
|
|
2680
|
-
|
|
2681
|
-
this.cdr.markForCheck();
|
|
2682
|
-
this.revokeBlobUrl();
|
|
2683
|
-
this.previewSubscription?.unsubscribe();
|
|
2684
|
-
this.previewSubscription = this.http
|
|
2685
|
-
.post(`${this.apiEndpoint}/preview`, {
|
|
2686
|
-
documentId,
|
|
2687
|
-
processDefinitionKey: this.processDefinitionKey || null,
|
|
2688
|
-
processInstanceId: this.formIoStateService.processInstanceId || null,
|
|
2689
|
-
sourceActivityId: this.sourceActivityId,
|
|
2690
|
-
inputOverrides: this.value || null,
|
|
2691
|
-
overrides: null,
|
|
2692
|
-
}, {
|
|
2693
|
-
responseType: 'blob',
|
|
2694
|
-
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
2695
|
-
})
|
|
2696
|
-
.subscribe({
|
|
2697
|
-
next: (blob) => this.handlePreviewSuccess(blob),
|
|
2698
|
-
error: (err) => this.handlePreviewError(err),
|
|
2699
|
-
});
|
|
2700
|
-
}
|
|
2701
|
-
/**
|
|
2702
|
-
* Auto-discover mode: discover sources from running process instances.
|
|
2703
|
-
*/
|
|
2704
|
-
discoverSources() {
|
|
2705
|
-
const documentId = this.formIoStateService.documentId;
|
|
2706
|
-
if (!documentId) {
|
|
2707
|
-
this.error = 'Could not determine document ID from context.';
|
|
2911
|
+
if (!this.sourceActivityId) {
|
|
2912
|
+
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2708
2913
|
this.cdr.markForCheck();
|
|
2709
2914
|
return;
|
|
2710
2915
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
next: (sources) => {
|
|
2716
|
-
this.sources = sources;
|
|
2717
|
-
this.discovering = false;
|
|
2718
|
-
this.cdr.markForCheck();
|
|
2719
|
-
if (sources.length > 0) {
|
|
2720
|
-
this.selectedIndex = 0;
|
|
2721
|
-
this.loadDiscoveredPreview();
|
|
2722
|
-
}
|
|
2723
|
-
},
|
|
2724
|
-
error: (err) => {
|
|
2725
|
-
this.error = err.error?.error || 'Failed to discover preview sources';
|
|
2726
|
-
this.discovering = false;
|
|
2727
|
-
this.cdr.markForCheck();
|
|
2728
|
-
},
|
|
2729
|
-
});
|
|
2730
|
-
}
|
|
2731
|
-
/**
|
|
2732
|
-
* Auto-discover mode: load preview for the selected discovered source.
|
|
2733
|
-
*/
|
|
2734
|
-
loadDiscoveredPreview() {
|
|
2735
|
-
const source = this.sources[this.selectedIndex];
|
|
2736
|
-
if (!source)
|
|
2916
|
+
const processInstanceId = this.formIoStateService.processInstanceId;
|
|
2917
|
+
if (!processInstanceId) {
|
|
2918
|
+
this.error = 'Preview is only available from within a running process.';
|
|
2919
|
+
this.cdr.markForCheck();
|
|
2737
2920
|
return;
|
|
2738
|
-
|
|
2739
|
-
|
|
2921
|
+
}
|
|
2922
|
+
const taskId = this.currentTaskId;
|
|
2923
|
+
if (!taskId) {
|
|
2924
|
+
this.error = 'Preview is only available from within a user task.';
|
|
2925
|
+
this.cdr.markForCheck();
|
|
2740
2926
|
return;
|
|
2927
|
+
}
|
|
2741
2928
|
this.loading = true;
|
|
2742
2929
|
this.error = null;
|
|
2743
2930
|
this.cdr.markForCheck();
|
|
2744
2931
|
this.revokeBlobUrl();
|
|
2745
2932
|
this.previewSubscription?.unsubscribe();
|
|
2746
|
-
this.previewSubscription = this.
|
|
2747
|
-
.
|
|
2933
|
+
this.previewSubscription = this.epistolaPluginService
|
|
2934
|
+
.previewToBlob({
|
|
2935
|
+
taskId,
|
|
2748
2936
|
documentId,
|
|
2749
|
-
|
|
2750
|
-
|
|
2937
|
+
processDefinitionKey: this.processDefinitionKey || null,
|
|
2938
|
+
processInstanceId,
|
|
2939
|
+
sourceActivityId: this.sourceActivityId,
|
|
2940
|
+
inputOverrides: this.value || null,
|
|
2751
2941
|
overrides: null,
|
|
2752
|
-
}, {
|
|
2753
|
-
responseType: 'blob',
|
|
2754
|
-
headers: new HttpHeaders().set('X-Skip-Interceptor', '422'),
|
|
2755
2942
|
})
|
|
2756
2943
|
.subscribe({
|
|
2757
2944
|
next: (blob) => this.handlePreviewSuccess(blob),
|
|
@@ -2793,7 +2980,7 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2793
2980
|
this.previewUrl = null;
|
|
2794
2981
|
}
|
|
2795
2982
|
}
|
|
2796
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token:
|
|
2983
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaDocumentPreviewComponent, deps: [{ token: EpistolaPluginService }, { token: i2$4.DomSanitizer }, { token: i3.FormIoStateService }, { token: i0.ChangeDetectorRef }, { token: EpistolaTaskContextService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2797
2984
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaDocumentPreviewComponent, isStandalone: true, selector: "epistola-document-preview-component", inputs: { value: "value", disabled: "disabled", label: "label", processDefinitionKey: "processDefinitionKey", sourceActivityId: "sourceActivityId", overrideMapping: "overrideMapping" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2798
2985
|
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
2799
2986
|
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
@@ -2829,55 +3016,26 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2829
3016
|
<div class="preview-header">
|
|
2830
3017
|
<span>{{ label || 'Document Preview' }}</span>
|
|
2831
3018
|
<div class="preview-controls">
|
|
2832
|
-
<
|
|
2833
|
-
*ngIf="!sourceActivityId && sources.length > 1"
|
|
2834
|
-
class="preview-select"
|
|
2835
|
-
[value]="selectedIndex"
|
|
2836
|
-
(change)="onSourceChange($event)"
|
|
2837
|
-
>
|
|
2838
|
-
<option *ngFor="let source of sources; let i = index" [value]="i">
|
|
2839
|
-
{{ source.templateName }} ({{ source.activityId }})
|
|
2840
|
-
</option>
|
|
2841
|
-
</select>
|
|
2842
|
-
<button
|
|
2843
|
-
type="button"
|
|
2844
|
-
class="preview-refresh"
|
|
2845
|
-
[disabled]="loading || discovering"
|
|
2846
|
-
(click)="refresh()"
|
|
2847
|
-
>
|
|
3019
|
+
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
2848
3020
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
2849
3021
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
2850
3022
|
</button>
|
|
2851
3023
|
</div>
|
|
2852
3024
|
</div>
|
|
2853
3025
|
<div class="preview-body">
|
|
2854
|
-
<div *ngIf="
|
|
2855
|
-
<div *ngIf="
|
|
2856
|
-
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
3026
|
+
<div *ngIf="loading" class="preview-loading">Generating preview...</div>
|
|
3027
|
+
<div *ngIf="error && !loading" class="preview-unavailable">
|
|
2857
3028
|
<i class="mdi mdi-information-outline"></i>
|
|
2858
|
-
|
|
3029
|
+
{{ error }}
|
|
2859
3030
|
</div>
|
|
2860
3031
|
<object
|
|
2861
|
-
*ngIf="previewUrl && !loading
|
|
3032
|
+
*ngIf="previewUrl && !loading"
|
|
2862
3033
|
[data]="previewUrl"
|
|
2863
3034
|
type="application/pdf"
|
|
2864
3035
|
class="preview-pdf"
|
|
2865
3036
|
>
|
|
2866
3037
|
PDF preview is not supported in this browser.
|
|
2867
3038
|
</object>
|
|
2868
|
-
<div
|
|
2869
|
-
*ngIf="
|
|
2870
|
-
!previewUrl &&
|
|
2871
|
-
!loading &&
|
|
2872
|
-
!discovering &&
|
|
2873
|
-
!error &&
|
|
2874
|
-
!sourceActivityId &&
|
|
2875
|
-
sources.length === 0
|
|
2876
|
-
"
|
|
2877
|
-
class="preview-empty"
|
|
2878
|
-
>
|
|
2879
|
-
No previewable documents found
|
|
2880
|
-
</div>
|
|
2881
3039
|
</div>
|
|
2882
3040
|
</div>
|
|
2883
3041
|
`, isInline: true, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -2919,59 +3077,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2919
3077
|
<div class="preview-header">
|
|
2920
3078
|
<span>{{ label || 'Document Preview' }}</span>
|
|
2921
3079
|
<div class="preview-controls">
|
|
2922
|
-
<
|
|
2923
|
-
*ngIf="!sourceActivityId && sources.length > 1"
|
|
2924
|
-
class="preview-select"
|
|
2925
|
-
[value]="selectedIndex"
|
|
2926
|
-
(change)="onSourceChange($event)"
|
|
2927
|
-
>
|
|
2928
|
-
<option *ngFor="let source of sources; let i = index" [value]="i">
|
|
2929
|
-
{{ source.templateName }} ({{ source.activityId }})
|
|
2930
|
-
</option>
|
|
2931
|
-
</select>
|
|
2932
|
-
<button
|
|
2933
|
-
type="button"
|
|
2934
|
-
class="preview-refresh"
|
|
2935
|
-
[disabled]="loading || discovering"
|
|
2936
|
-
(click)="refresh()"
|
|
2937
|
-
>
|
|
3080
|
+
<button type="button" class="preview-refresh" [disabled]="loading" (click)="refresh()">
|
|
2938
3081
|
<i class="mdi mdi-refresh mr-1"></i>
|
|
2939
3082
|
{{ loading ? 'Generating...' : 'Refresh' }}
|
|
2940
3083
|
</button>
|
|
2941
3084
|
</div>
|
|
2942
3085
|
</div>
|
|
2943
3086
|
<div class="preview-body">
|
|
2944
|
-
<div *ngIf="
|
|
2945
|
-
<div *ngIf="
|
|
2946
|
-
<div *ngIf="error && !loading && !discovering" class="preview-unavailable">
|
|
3087
|
+
<div *ngIf="loading" class="preview-loading">Generating preview...</div>
|
|
3088
|
+
<div *ngIf="error && !loading" class="preview-unavailable">
|
|
2947
3089
|
<i class="mdi mdi-information-outline"></i>
|
|
2948
|
-
|
|
3090
|
+
{{ error }}
|
|
2949
3091
|
</div>
|
|
2950
3092
|
<object
|
|
2951
|
-
*ngIf="previewUrl && !loading
|
|
3093
|
+
*ngIf="previewUrl && !loading"
|
|
2952
3094
|
[data]="previewUrl"
|
|
2953
3095
|
type="application/pdf"
|
|
2954
3096
|
class="preview-pdf"
|
|
2955
3097
|
>
|
|
2956
3098
|
PDF preview is not supported in this browser.
|
|
2957
3099
|
</object>
|
|
2958
|
-
<div
|
|
2959
|
-
*ngIf="
|
|
2960
|
-
!previewUrl &&
|
|
2961
|
-
!loading &&
|
|
2962
|
-
!discovering &&
|
|
2963
|
-
!error &&
|
|
2964
|
-
!sourceActivityId &&
|
|
2965
|
-
sources.length === 0
|
|
2966
|
-
"
|
|
2967
|
-
class="preview-empty"
|
|
2968
|
-
>
|
|
2969
|
-
No previewable documents found
|
|
2970
|
-
</div>
|
|
2971
3100
|
</div>
|
|
2972
3101
|
</div>
|
|
2973
3102
|
`, styles: [".epistola-preview-panel{border:1px solid #dee2e6;border-radius:4px;background:#f8f9fa;display:flex;flex-direction:column}.preview-header{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #dee2e6;font-weight:700;color:#495057;flex-wrap:wrap;gap:.5rem}.preview-controls{display:flex;align-items:center;gap:.5rem}.preview-select{border:1px solid #ced4da;border-radius:4px;padding:.25rem .5rem;font-size:.8rem;background:#fff;max-width:300px}.preview-refresh{background:none;border:1px solid #6c757d;border-radius:4px;color:#6c757d;padding:.25rem .75rem;font-size:.8rem;cursor:pointer;display:flex;align-items:center;white-space:nowrap}.preview-refresh:hover:not(:disabled){background:#e9ecef}.preview-refresh:disabled{opacity:.5;cursor:not-allowed}.preview-body{display:flex;flex-direction:column;min-height:500px}.preview-loading{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable{padding:1.5rem;text-align:center;color:#6c757d;font-style:italic}.preview-unavailable i{margin-right:.25rem}.preview-pdf{width:100%;flex:1;min-height:500px}.preview-empty{padding:2rem;text-align:center;color:#6c757d;font-style:italic}.design-info{padding:1rem;min-height:auto}.design-section{margin-bottom:.75rem}.design-label{font-size:.7rem;text-transform:uppercase;color:#868e96;font-weight:600;letter-spacing:.05em}.design-value{font-family:monospace;font-size:.85rem;color:#212529;margin-bottom:.25rem}.design-mapping{margin-top:.25rem}.design-entry{font-family:monospace;font-size:.8rem;color:#495057;padding:.15rem 0}.design-scope{color:#0d6efd}.design-field{color:#198754}.design-entry i{font-size:.7rem;margin:0 .25rem;color:#adb5bd}.design-unconfigured{color:#6c757d;font-style:italic;font-size:.85rem}\n"] }]
|
|
2974
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type:
|
|
3103
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }, { type: EpistolaTaskContextService }], propDecorators: { value: [{
|
|
2975
3104
|
type: Input
|
|
2976
3105
|
}], valueChange: [{
|
|
2977
3106
|
type: Output
|
|
@@ -2994,8 +3123,12 @@ class EpistolaAdminPageComponent {
|
|
|
2994
3123
|
cards = [];
|
|
2995
3124
|
selectedCard = null;
|
|
2996
3125
|
activeTab = 'actions';
|
|
3126
|
+
overviewTab = 'configurations';
|
|
2997
3127
|
loading = false;
|
|
2998
3128
|
pluginVersion = null;
|
|
3129
|
+
validationViolations = [];
|
|
3130
|
+
reconcilingExecutionIds = new Set();
|
|
3131
|
+
reconcileFeedback = null;
|
|
2999
3132
|
connectionStatuses = [];
|
|
3000
3133
|
usageEntries = [];
|
|
3001
3134
|
pendingJobs = [];
|
|
@@ -3031,6 +3164,9 @@ class EpistolaAdminPageComponent {
|
|
|
3031
3164
|
this.activeTab = tab;
|
|
3032
3165
|
this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
|
|
3033
3166
|
}
|
|
3167
|
+
setOverviewTab(tab) {
|
|
3168
|
+
this.overviewTab = tab;
|
|
3169
|
+
}
|
|
3034
3170
|
refresh() {
|
|
3035
3171
|
this.selectedCard = null;
|
|
3036
3172
|
this.loadData();
|
|
@@ -3047,6 +3183,53 @@ class EpistolaAdminPageComponent {
|
|
|
3047
3183
|
},
|
|
3048
3184
|
});
|
|
3049
3185
|
}
|
|
3186
|
+
/**
|
|
3187
|
+
* Manually reconcile a single stuck catch event. Pulls the current Epistola job
|
|
3188
|
+
* status and re-runs message correlation if the job is in a terminal state.
|
|
3189
|
+
* Refreshes the Pending list on success so the row drops out of the table.
|
|
3190
|
+
*/
|
|
3191
|
+
reconcilePending(job) {
|
|
3192
|
+
if (this.reconcilingExecutionIds.has(job.executionId)) {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
this.reconcilingExecutionIds.add(job.executionId);
|
|
3196
|
+
this.reconcileFeedback = null;
|
|
3197
|
+
this.adminService.reconcilePending(job.executionId).subscribe({
|
|
3198
|
+
next: (result) => {
|
|
3199
|
+
this.reconcilingExecutionIds.delete(job.executionId);
|
|
3200
|
+
this.reconcileFeedback = {
|
|
3201
|
+
executionId: job.executionId,
|
|
3202
|
+
type: 'success',
|
|
3203
|
+
message: `OK (${result.epistolaStatus}, ${result.correlatedCount ?? 0} correlated)`,
|
|
3204
|
+
};
|
|
3205
|
+
this.loadData();
|
|
3206
|
+
},
|
|
3207
|
+
error: (err) => {
|
|
3208
|
+
this.reconcilingExecutionIds.delete(job.executionId);
|
|
3209
|
+
// 409 from the backend = job is still PENDING/IN_PROGRESS, surface as
|
|
3210
|
+
// informational rather than an error.
|
|
3211
|
+
if (err?.status === 409) {
|
|
3212
|
+
const status = err.error?.epistolaStatus ?? 'still pending';
|
|
3213
|
+
this.reconcileFeedback = {
|
|
3214
|
+
executionId: job.executionId,
|
|
3215
|
+
type: 'pending',
|
|
3216
|
+
message: `Epistola: ${status}. Try again in a moment.`,
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
else {
|
|
3220
|
+
const message = err?.error?.detail ?? err?.error?.message ?? err?.message ?? 'unknown error';
|
|
3221
|
+
this.reconcileFeedback = {
|
|
3222
|
+
executionId: job.executionId,
|
|
3223
|
+
type: 'error',
|
|
3224
|
+
message,
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
isReconciling(job) {
|
|
3231
|
+
return this.reconcilingExecutionIds.has(job.executionId);
|
|
3232
|
+
}
|
|
3050
3233
|
updateUrl(configurationId, tab) {
|
|
3051
3234
|
this.router.navigate([], {
|
|
3052
3235
|
relativeTo: this.route,
|
|
@@ -3098,6 +3281,16 @@ class EpistolaAdminPageComponent {
|
|
|
3098
3281
|
this.tryBuildCards();
|
|
3099
3282
|
},
|
|
3100
3283
|
});
|
|
3284
|
+
// Validation violations are independent of cards — load alongside but don't
|
|
3285
|
+
// gate the loading flag on them.
|
|
3286
|
+
this.adminService.getValidationViolations().subscribe({
|
|
3287
|
+
next: (violations) => {
|
|
3288
|
+
this.validationViolations = violations;
|
|
3289
|
+
},
|
|
3290
|
+
error: () => {
|
|
3291
|
+
this.validationViolations = [];
|
|
3292
|
+
},
|
|
3293
|
+
});
|
|
3101
3294
|
}
|
|
3102
3295
|
tryBuildCards() {
|
|
3103
3296
|
if (!this.connectionLoaded || !this.usageLoaded || !this.pendingLoaded) {
|
|
@@ -3138,13 +3331,13 @@ class EpistolaAdminPageComponent {
|
|
|
3138
3331
|
},
|
|
3139
3332
|
});
|
|
3140
3333
|
}
|
|
3141
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$
|
|
3142
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaAdminPageComponent, isStandalone: true, selector: "epistola-admin-page", ngImport: i0, template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i6.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }] });
|
|
3334
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, deps: [{ token: EpistolaAdminService }, { token: i2$5.ActivatedRoute }, { token: i2$5.Router }], target: i0.ɵɵFactoryTarget.Component });
|
|
3335
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: EpistolaAdminPageComponent, isStandalone: true, selector: "epistola-admin-page", ngImport: i0, template: "<div class=\"epistola-admin\">\n <!-- Overview: tabs (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$5.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PluginTranslatePipeModule }, { kind: "pipe", type: i2$1.PluginTranslatePipe, name: "pluginTranslate" }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i5$1.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i5$1.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i6.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }] });
|
|
3143
3336
|
}
|
|
3144
3337
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
3145
3338
|
type: Component,
|
|
3146
|
-
args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: card grid (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div *ngIf=\"loading\" class=\"text-muted\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"] }]
|
|
3147
|
-
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$
|
|
3339
|
+
args: [{ selector: 'epistola-admin-page', standalone: true, imports: [CommonModule, RouterModule, PluginTranslatePipeModule, TabsModule, TagModule], template: "<div class=\"epistola-admin\">\n <!-- Overview: tabs (no configuration selected) -->\n <ng-container *ngIf=\"!selectedCard\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div class=\"d-flex align-items-center\">\n <h5 class=\"mb-0\">{{ 'epistolaAdminOverview' | pluginTranslate: 'epistola' | async }}</h5>\n <span *ngIf=\"pluginVersion\" class=\"version-badge ms-2\">v{{ pluginVersion }}</span>\n </div>\n <button class=\"btn btn-outline-primary btn-sm\" (click)=\"refresh()\" [disabled]=\"loading\">\n {{ 'epistolaAdminRefresh' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <ng-template #configurationsHeading>\n {{ 'epistolaAdminConfigurations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ cards.length }}</cds-tag>\n </ng-template>\n\n <ng-template #validationsHeading>\n {{ 'epistolaAdminValidations' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" [type]=\"validationViolations.length > 0 ? 'red' : 'gray'\" class=\"ms-1\">\n {{ validationViolations.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"configurationsHeading\"\n [active]=\"overviewTab === 'configurations'\"\n (selected)=\"setOverviewTab('configurations')\"\n >\n <div *ngIf=\"loading\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length === 0\" class=\"text-muted mt-3\">\n {{ 'epistolaAdminNoConfigurations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!loading && cards.length > 0\" class=\"card-grid\">\n <div\n *ngFor=\"let card of cards\"\n class=\"config-card\"\n [class.config-card--ok]=\"card.reachable && card.problemCount === 0\"\n [class.config-card--warning]=\"card.reachable && card.problemCount > 0\"\n [class.config-card--error]=\"!card.reachable\"\n (click)=\"selectConfiguration(card)\"\n >\n <div class=\"config-card__header\">\n <span\n class=\"status-dot\"\n [class.status-dot--ok]=\"card.reachable\"\n [class.status-dot--error]=\"!card.reachable\"\n >\n </span>\n <h5 class=\"config-card__title\">{{ card.configurationTitle }}</h5>\n </div>\n\n <div class=\"config-card__body\">\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async\n }}</span>\n <code class=\"config-card__value\">{{ card.tenantId }}</code>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" [type]=\"card.reachable ? 'green' : 'red'\">\n {{\n card.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n </div>\n <div *ngIf=\"card.serverVersion\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.serverVersion }}</span>\n </div>\n <div class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async\n }}</span>\n <span class=\"config-card__value\">{{ card.usageCount }}</span>\n </div>\n <div *ngIf=\"card.pendingJobs.length > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">{{ card.pendingJobs.length }}</cds-tag>\n </div>\n <div *ngIf=\"card.problemCount > 0\" class=\"config-card__field\">\n <span class=\"config-card__label\">{{\n 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"red\">{{ card.problemCount }}</cds-tag>\n </div>\n </div>\n\n <div class=\"config-card__footer\">\n <span class=\"config-card__latency\">{{ card.latencyMs }} ms</span>\n </div>\n </div>\n </div>\n </cds-tab>\n\n <cds-tab\n [heading]=\"validationsHeading\"\n [active]=\"overviewTab === 'validations'\"\n (selected)=\"setOverviewTab('validations')\"\n >\n <div *ngIf=\"validationViolations.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoValidations' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"validationViolations.length > 0\" class=\"mt-3\">\n <p class=\"text-muted mb-3\">\n {{ 'epistolaAdminValidationWarningBody' | pluginTranslate: 'epistola' | async }}\n </p>\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminValidationCode' | pluginTranslate: 'epistola' | async }}</th>\n <th>\n {{ 'epistolaAdminValidationMessage' | pluginTranslate: 'epistola' | async }}\n </th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let v of validationViolations\">\n <td>\n {{ v.processDefinitionName || v.processDefinitionKey }}\n <div *ngIf=\"v.processDefinitionName\" class=\"text-muted small\">\n <code>{{ v.processDefinitionKey }}</code>\n </div>\n </td>\n <td>\n <code>{{ v.activityId }}</code>\n </td>\n <td>\n <cds-tag size=\"sm\" type=\"red\">{{ v.code }}</cds-tag>\n </td>\n <td>{{ v.message }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n\n <!-- Detail view: selected configuration -->\n <ng-container *ngIf=\"selectedCard\">\n <div class=\"detail-header mb-3\">\n <button class=\"btn btn-link btn-sm p-0\" (click)=\"backToOverview()\">\n ← {{ 'epistolaAdminBackToOverview' | pluginTranslate: 'epistola' | async }}\n </button>\n </div>\n\n <div class=\"detail-summary mb-4\">\n <h4>\n <span\n class=\"status-dot me-2\"\n [class.status-dot--ok]=\"selectedCard.reachable\"\n [class.status-dot--error]=\"!selectedCard.reachable\"\n >\n </span>\n {{ selectedCard.configurationTitle }}\n </h4>\n\n <table class=\"table table-sm detail-info-table\">\n <tbody>\n <tr>\n <th>{{ 'epistolaAdminTenantId' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <code>{{ selectedCard.tenantId }}</code>\n </td>\n </tr>\n <tr>\n <th>{{ 'epistolaAdminStatus' | pluginTranslate: 'epistola' | async }}</th>\n <td>\n <cds-tag size=\"sm\" [type]=\"selectedCard.reachable ? 'green' : 'red'\">\n {{\n selectedCard.reachable\n ? ('epistolaAdminConnected' | pluginTranslate: 'epistola' | async)\n : ('epistolaAdminUnreachable' | pluginTranslate: 'epistola' | async)\n }}\n </cds-tag>\n <span class=\"text-muted ms-2\">{{ selectedCard.latencyMs }} ms</span>\n </td>\n </tr>\n <tr *ngIf=\"selectedCard.serverVersion\">\n <th>{{ 'epistolaAdminServerVersion' | pluginTranslate: 'epistola' | async }}</th>\n <td>{{ selectedCard.serverVersion }}</td>\n </tr>\n <tr *ngIf=\"selectedCard.errorMessage\">\n <th>{{ 'epistolaAdminError' | pluginTranslate: 'epistola' | async }}</th>\n <td class=\"text-danger\">{{ selectedCard.errorMessage }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Tabs -->\n <ng-template #actionsHeading>\n {{ 'epistolaAdminPluginActions' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ selectedCard.usageEntries.length }}</cds-tag>\n </ng-template>\n\n <ng-template #pendingHeading>\n {{ 'epistolaAdminPendingJobs' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n size=\"sm\"\n [type]=\"selectedCard.pendingJobs.length > 0 ? 'blue' : 'gray'\"\n class=\"ms-1\"\n >\n {{ selectedCard.pendingJobs.length }}\n </cds-tag>\n </ng-template>\n\n <cds-tabs [cacheActive]=\"true\" type=\"contained\">\n <cds-tab\n [heading]=\"actionsHeading\"\n [active]=\"activeTab === 'actions'\"\n (selected)=\"setActiveTab('actions')\"\n >\n <div *ngIf=\"selectedCard.usageEntries.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoUsageForConfig' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.usageEntries.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCase' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminAction' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminProblems' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr\n *ngFor=\"let entry of selectedCard.usageEntries\"\n [class.table-warning]=\"entry.problems.length > 0\"\n >\n <td>{{ entry.caseDefinitionKey || '-' }}</td>\n <td>\n <a\n *ngIf=\"entry.caseDefinitionKey && entry.caseDefinitionVersionTag\"\n [routerLink]=\"[\n '/case-management',\n 'case',\n entry.caseDefinitionKey,\n 'version',\n entry.caseDefinitionVersionTag,\n 'processes',\n entry.processDefinitionKey,\n ]\"\n class=\"usage-link\"\n >\n {{ entry.processDefinitionName }}\n </a>\n <span *ngIf=\"!entry.caseDefinitionKey || !entry.caseDefinitionVersionTag\">\n {{ entry.processDefinitionName }}\n </span>\n </td>\n <td>{{ entry.activityName }}</td>\n <td>\n <code>{{ entry.actionKey }}</code>\n </td>\n <td>\n <cds-tag *ngIf=\"entry.problems.length === 0\" size=\"sm\" type=\"green\">OK</cds-tag>\n <cds-tag\n *ngFor=\"let problem of entry.problems\"\n size=\"sm\"\n type=\"red\"\n class=\"d-block mb-1\"\n >\n {{ problem }}\n </cds-tag>\n </td>\n <td>\n <button\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"exportProcessLink(entry)\"\n [title]=\"'epistolaAdminExport' | pluginTranslate: 'epistola' | async\"\n >\n ⤓\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n\n <cds-tab\n [heading]=\"pendingHeading\"\n [active]=\"activeTab === 'pending'\"\n (selected)=\"setActiveTab('pending')\"\n >\n <div *ngIf=\"selectedCard.pendingJobs.length === 0\" class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminNoPendingJobs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"selectedCard.pendingJobs.length > 0\" class=\"table table-striped mt-3\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminProcess' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminActivity' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminRequestId' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let job of selectedCard.pendingJobs\">\n <td>{{ job.processDefinitionName }}</td>\n <td>{{ job.activityName }}</td>\n <td>\n <code>{{ job.requestId }}</code>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"reconcilePending(job)\"\n [disabled]=\"isReconciling(job)\"\n [title]=\"'epistolaAdminReconcileTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isReconciling(job)\">\n {{ 'epistolaAdminReconcile' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isReconciling(job)\">\n {{ 'epistolaAdminReconciling' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"reconcileFeedback && reconcileFeedback.executionId === job.executionId\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"reconcileFeedback.type === 'success'\"\n [class.text-warning]=\"reconcileFeedback.type === 'pending'\"\n [class.text-danger]=\"reconcileFeedback.type === 'error'\"\n >\n {{ reconcileFeedback.message }}\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n </cds-tab>\n </cds-tabs>\n </ng-container>\n</div>\n", styles: [".epistola-admin{padding:1.5rem}.epistola-admin .version-badge{font-size:.75rem;font-weight:500;padding:.2em .6em;border-radius:4px;background-color:#e8e8e8;color:#525252}.epistola-admin .badge{font-size:.85em;padding:.35em .65em}.epistola-admin code{font-size:.9em;color:#525252}.epistola-admin table th{font-weight:600;white-space:nowrap}.epistola-admin .status-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:.5rem;flex-shrink:0;background-color:#adb5bd}.epistola-admin .status-dot--ok{background-color:#198754}.epistola-admin .status-dot--error{background-color:#dc3545}.epistola-admin .card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1rem}.epistola-admin .config-card{border:1px solid #dee2e6;border-radius:8px;padding:1.25rem;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease;background:#fff}.epistola-admin .config-card:hover{box-shadow:0 2px 8px #0000001a}.epistola-admin .config-card--ok{border-left:4px solid #198754}.epistola-admin .config-card--warning{border-left:4px solid #ffc107}.epistola-admin .config-card--error{border-left:4px solid #dc3545}.epistola-admin .config-card__header{display:flex;align-items:center;margin-bottom:1rem}.epistola-admin .config-card__title{margin:0;font-size:1.05rem;font-weight:600;color:#161616}.epistola-admin .config-card__body{display:flex;flex-direction:column;gap:.5rem}.epistola-admin .config-card__field{display:flex;justify-content:space-between;align-items:center}.epistola-admin .config-card__label{font-size:.875rem;color:#6c757d}.epistola-admin .config-card__value{font-size:.875rem;color:#161616}.epistola-admin .config-card__footer{margin-top:1rem;padding-top:.75rem;border-top:1px solid #f0f0f0;text-align:right}.epistola-admin .config-card__latency{font-size:.8rem;color:#adb5bd}.epistola-admin .usage-link{color:#0f62fe;text-decoration:none}.epistola-admin .usage-link:hover{text-decoration:underline}.epistola-admin .detail-info-table{max-width:500px}.epistola-admin .detail-info-table th{width:140px}.epistola-admin .detail-summary{padding:1rem 0;border-bottom:1px solid #dee2e6}\n"] }]
|
|
3340
|
+
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$5.ActivatedRoute }, { type: i2$5.Router }] });
|
|
3148
3341
|
|
|
3149
3342
|
function isRuntimeWindow(value) {
|
|
3150
3343
|
return typeof value === 'object' && value !== null;
|
|
@@ -3191,7 +3384,7 @@ const routes = [
|
|
|
3191
3384
|
];
|
|
3192
3385
|
class EpistolaAdminRoutingModule {
|
|
3193
3386
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3194
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$
|
|
3387
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [i2$5.RouterModule], exports: [RouterModule] });
|
|
3195
3388
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] });
|
|
3196
3389
|
}
|
|
3197
3390
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminRoutingModule, decorators: [{
|
|
@@ -3202,19 +3395,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3202
3395
|
}]
|
|
3203
3396
|
}] });
|
|
3204
3397
|
|
|
3205
|
-
const
|
|
3206
|
-
type: 'epistola-
|
|
3207
|
-
selector: 'epistola-
|
|
3208
|
-
title: 'Epistola
|
|
3398
|
+
const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
3399
|
+
type: 'epistola-document',
|
|
3400
|
+
selector: 'epistola-document-element',
|
|
3401
|
+
title: 'Epistola Document',
|
|
3209
3402
|
group: 'basic',
|
|
3210
|
-
icon: '
|
|
3403
|
+
icon: 'file-pdf-o',
|
|
3211
3404
|
emptyValue: null,
|
|
3212
|
-
fieldOptions: ['
|
|
3405
|
+
fieldOptions: ['label', 'display', 'documentVariable', 'tenantIdVariable', 'filename'],
|
|
3213
3406
|
};
|
|
3214
|
-
function
|
|
3215
|
-
if (
|
|
3216
|
-
|
|
3407
|
+
function registerEpistolaDocumentComponent(injector) {
|
|
3408
|
+
if (customElements.get(EPISTOLA_DOCUMENT_OPTIONS.selector)) {
|
|
3409
|
+
return;
|
|
3217
3410
|
}
|
|
3411
|
+
registerCustomFormioComponent(EPISTOLA_DOCUMENT_OPTIONS, EpistolaDocumentComponent, injector);
|
|
3218
3412
|
}
|
|
3219
3413
|
|
|
3220
3414
|
const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
@@ -3232,21 +3426,6 @@ function registerEpistolaRetryFormComponent(injector) {
|
|
|
3232
3426
|
}
|
|
3233
3427
|
}
|
|
3234
3428
|
|
|
3235
|
-
const EPISTOLA_PREVIEW_BUTTON_OPTIONS = {
|
|
3236
|
-
type: 'epistola-preview-button',
|
|
3237
|
-
selector: 'epistola-preview-button-element',
|
|
3238
|
-
title: 'Epistola Preview',
|
|
3239
|
-
group: 'basic',
|
|
3240
|
-
icon: 'eye',
|
|
3241
|
-
emptyValue: null,
|
|
3242
|
-
fieldOptions: ['label'],
|
|
3243
|
-
};
|
|
3244
|
-
function registerEpistolaPreviewButtonComponent(injector) {
|
|
3245
|
-
if (!customElements.get(EPISTOLA_PREVIEW_BUTTON_OPTIONS.selector)) {
|
|
3246
|
-
registerCustomFormioComponent(EPISTOLA_PREVIEW_BUTTON_OPTIONS, EpistolaPreviewButtonComponent, injector);
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
3429
|
const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
3251
3430
|
type: 'epistola-document-preview',
|
|
3252
3431
|
selector: 'epistola-document-preview-element',
|
|
@@ -3798,6 +3977,11 @@ class EpistolaPluginModule {
|
|
|
3798
3977
|
ngModule: EpistolaPluginModule,
|
|
3799
3978
|
providers: [
|
|
3800
3979
|
EpistolaMenuService,
|
|
3980
|
+
{
|
|
3981
|
+
provide: HTTP_INTERCEPTORS,
|
|
3982
|
+
useClass: EpistolaTaskContextInterceptor,
|
|
3983
|
+
multi: true,
|
|
3984
|
+
},
|
|
3801
3985
|
{
|
|
3802
3986
|
provide: ENVIRONMENT_INITIALIZER,
|
|
3803
3987
|
multi: true,
|
|
@@ -3805,9 +3989,8 @@ class EpistolaPluginModule {
|
|
|
3805
3989
|
if (!isEpistolaEnabled())
|
|
3806
3990
|
return;
|
|
3807
3991
|
const injector = inject(Injector);
|
|
3808
|
-
|
|
3992
|
+
registerEpistolaDocumentComponent(injector);
|
|
3809
3993
|
registerEpistolaRetryFormComponent(injector);
|
|
3810
|
-
registerEpistolaPreviewButtonComponent(injector);
|
|
3811
3994
|
registerEpistolaOverrideBuilderComponent(injector);
|
|
3812
3995
|
registerEpistolaProcessLinkSelectorComponent(injector);
|
|
3813
3996
|
registerEpistolaDocumentPreviewComponent(injector);
|
|
@@ -3830,17 +4013,15 @@ class EpistolaPluginModule {
|
|
|
3830
4013
|
GenerateDocumentConfigurationComponent,
|
|
3831
4014
|
CheckJobStatusConfigurationComponent,
|
|
3832
4015
|
DownloadDocumentConfigurationComponent,
|
|
3833
|
-
|
|
4016
|
+
EpistolaDocumentComponent,
|
|
3834
4017
|
EpistolaRetryFormComponent,
|
|
3835
|
-
EpistolaPreviewButtonComponent,
|
|
3836
4018
|
EpistolaDocumentPreviewComponent,
|
|
3837
4019
|
EpistolaAdminPageComponent], exports: [EpistolaConfigurationComponent,
|
|
3838
4020
|
GenerateDocumentConfigurationComponent,
|
|
3839
4021
|
CheckJobStatusConfigurationComponent,
|
|
3840
4022
|
DownloadDocumentConfigurationComponent,
|
|
3841
|
-
|
|
4023
|
+
EpistolaDocumentComponent,
|
|
3842
4024
|
EpistolaRetryFormComponent,
|
|
3843
|
-
EpistolaPreviewButtonComponent,
|
|
3844
4025
|
EpistolaDocumentPreviewComponent,
|
|
3845
4026
|
EpistolaAdminPageComponent] });
|
|
3846
4027
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaPluginModule, providers: [EpistolaPluginService, EpistolaAdminService], imports: [CommonModule,
|
|
@@ -3854,9 +4035,8 @@ class EpistolaPluginModule {
|
|
|
3854
4035
|
GenerateDocumentConfigurationComponent,
|
|
3855
4036
|
CheckJobStatusConfigurationComponent,
|
|
3856
4037
|
DownloadDocumentConfigurationComponent,
|
|
3857
|
-
|
|
4038
|
+
EpistolaDocumentComponent,
|
|
3858
4039
|
EpistolaRetryFormComponent,
|
|
3859
|
-
EpistolaPreviewButtonComponent,
|
|
3860
4040
|
EpistolaDocumentPreviewComponent,
|
|
3861
4041
|
EpistolaAdminPageComponent] });
|
|
3862
4042
|
}
|
|
@@ -3875,9 +4055,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3875
4055
|
GenerateDocumentConfigurationComponent,
|
|
3876
4056
|
CheckJobStatusConfigurationComponent,
|
|
3877
4057
|
DownloadDocumentConfigurationComponent,
|
|
3878
|
-
|
|
4058
|
+
EpistolaDocumentComponent,
|
|
3879
4059
|
EpistolaRetryFormComponent,
|
|
3880
|
-
EpistolaPreviewButtonComponent,
|
|
3881
4060
|
EpistolaDocumentPreviewComponent,
|
|
3882
4061
|
EpistolaAdminPageComponent,
|
|
3883
4062
|
],
|
|
@@ -3886,9 +4065,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3886
4065
|
GenerateDocumentConfigurationComponent,
|
|
3887
4066
|
CheckJobStatusConfigurationComponent,
|
|
3888
4067
|
DownloadDocumentConfigurationComponent,
|
|
3889
|
-
|
|
4068
|
+
EpistolaDocumentComponent,
|
|
3890
4069
|
EpistolaRetryFormComponent,
|
|
3891
|
-
EpistolaPreviewButtonComponent,
|
|
3892
4070
|
EpistolaDocumentPreviewComponent,
|
|
3893
4071
|
EpistolaAdminPageComponent,
|
|
3894
4072
|
],
|
|
@@ -3910,9 +4088,9 @@ const epistolaPluginSpecification = {
|
|
|
3910
4088
|
pluginLogoBase64: EPISTOLA_PLUGIN_LOGO_BASE64,
|
|
3911
4089
|
// Map action keys to their configuration components
|
|
3912
4090
|
functionConfigurationComponents: {
|
|
3913
|
-
'generate-document': GenerateDocumentConfigurationComponent,
|
|
3914
|
-
'check-job-status': CheckJobStatusConfigurationComponent,
|
|
3915
|
-
'download-document': DownloadDocumentConfigurationComponent,
|
|
4091
|
+
'epistola-generate-document': GenerateDocumentConfigurationComponent,
|
|
4092
|
+
'epistola-check-job-status': CheckJobStatusConfigurationComponent,
|
|
4093
|
+
'epistola-download-document': DownloadDocumentConfigurationComponent,
|
|
3916
4094
|
},
|
|
3917
4095
|
// Translations
|
|
3918
4096
|
pluginTranslations: {
|
|
@@ -3930,7 +4108,7 @@ const epistolaPluginSpecification = {
|
|
|
3930
4108
|
defaultEnvironmentIdTooltip: 'De standaard omgeving voor documentgeneratie (3-30 tekens, alleen kleine letters, cijfers en koppeltekens, bijv. "productie")',
|
|
3931
4109
|
templateSyncEnabled: 'Template synchronisatie',
|
|
3932
4110
|
templateSyncEnabledTooltip: 'Synchroniseer template definities automatisch van het classpath naar Epistola bij het opstarten',
|
|
3933
|
-
'generate-document': 'Genereer Document',
|
|
4111
|
+
'epistola-generate-document': 'Genereer Document',
|
|
3934
4112
|
catalogId: 'Catalogus',
|
|
3935
4113
|
catalogIdTooltip: 'Selecteer de catalogus waaruit een template gekozen wordt',
|
|
3936
4114
|
templateId: 'Template',
|
|
@@ -4007,7 +4185,7 @@ const epistolaPluginSpecification = {
|
|
|
4007
4185
|
sourceFieldPlaceholder: 'Bronveldnaam',
|
|
4008
4186
|
noTemplateFields: 'Geen template velden beschikbaar',
|
|
4009
4187
|
// Check job status action
|
|
4010
|
-
'check-job-status': 'Controleer Taakstatus',
|
|
4188
|
+
'epistola-check-job-status': 'Controleer Taakstatus',
|
|
4011
4189
|
requestIdVariable: 'Request ID Variabele',
|
|
4012
4190
|
requestIdVariableTooltip: 'Naam van de procesvariabele met het Epistola request ID',
|
|
4013
4191
|
statusVariable: 'Status Variabele',
|
|
@@ -4017,7 +4195,9 @@ const epistolaPluginSpecification = {
|
|
|
4017
4195
|
errorMessageVariable: 'Foutmelding Variabele',
|
|
4018
4196
|
errorMessageVariableTooltip: 'Naam van de procesvariabele waarin de foutmelding wordt opgeslagen (bij fout)',
|
|
4019
4197
|
// Download document action
|
|
4020
|
-
'download-document': 'Download Document',
|
|
4198
|
+
'epistola-download-document': 'Download Document',
|
|
4199
|
+
documentVariable: 'Document Variabele',
|
|
4200
|
+
documentVariableTooltip: 'Naam van de procesvariabele met het Epistola resultaat. Mag een String document ID zijn (legacy) of een rich-result object met een documentId-veld; de actie haalt het document ID eruit.',
|
|
4021
4201
|
contentVariable: 'Inhoud Variabele',
|
|
4022
4202
|
contentVariableTooltip: 'Naam van de procesvariabele waarin de documentinhoud (Base64) wordt opgeslagen',
|
|
4023
4203
|
// Admin page
|
|
@@ -4044,6 +4224,16 @@ const epistolaPluginSpecification = {
|
|
|
4044
4224
|
epistolaAdminNoPendingJobs: 'Geen wachtende taken voor deze verbinding.',
|
|
4045
4225
|
epistolaAdminConfiguration: 'Configuratie',
|
|
4046
4226
|
epistolaAdminRequestId: 'Request ID',
|
|
4227
|
+
epistolaAdminReconcile: 'Hersynchroniseer',
|
|
4228
|
+
epistolaAdminReconciling: 'Bezig...',
|
|
4229
|
+
epistolaAdminReconcileTooltip: 'Vraag de huidige status op bij Epistola en hervat het wachtende proces als het klaar is.',
|
|
4230
|
+
epistolaAdminConfigurations: 'Configuraties',
|
|
4231
|
+
epistolaAdminValidations: 'BPMN-validatie',
|
|
4232
|
+
epistolaAdminNoValidations: 'Geen race-onveilige procesdefinities gevonden. Alles ziet er goed uit.',
|
|
4233
|
+
epistolaAdminValidationWarningTitle: 'BPMN configuratie waarschuwing',
|
|
4234
|
+
epistolaAdminValidationWarningBody: 'In deze procesdefinities is de grens tussen de generate-document service task en de EpistolaDocumentGenerated catch event niet synchroon. Resultaten kunnen verloren gaan; gebruik in dat geval de Hersynchroniseer-knop in de Wachtende taken-tab.',
|
|
4235
|
+
epistolaAdminValidationCode: 'Code',
|
|
4236
|
+
epistolaAdminValidationMessage: 'Bericht',
|
|
4047
4237
|
},
|
|
4048
4238
|
en: {
|
|
4049
4239
|
title: 'Epistola Document Suite',
|
|
@@ -4059,7 +4249,7 @@ const epistolaPluginSpecification = {
|
|
|
4059
4249
|
defaultEnvironmentIdTooltip: 'The default environment for document generation (3-30 chars, lowercase letters, digits and hyphens only, e.g. "production")',
|
|
4060
4250
|
templateSyncEnabled: 'Template sync',
|
|
4061
4251
|
templateSyncEnabledTooltip: 'Automatically synchronize template definitions from classpath to Epistola on startup',
|
|
4062
|
-
'generate-document': 'Generate Document',
|
|
4252
|
+
'epistola-generate-document': 'Generate Document',
|
|
4063
4253
|
catalogId: 'Catalog',
|
|
4064
4254
|
catalogIdTooltip: 'Select the catalog to choose a template from',
|
|
4065
4255
|
templateId: 'Template',
|
|
@@ -4136,7 +4326,7 @@ const epistolaPluginSpecification = {
|
|
|
4136
4326
|
sourceFieldPlaceholder: 'Source field name',
|
|
4137
4327
|
noTemplateFields: 'No template fields available',
|
|
4138
4328
|
// Check job status action
|
|
4139
|
-
'check-job-status': 'Check Job Status',
|
|
4329
|
+
'epistola-check-job-status': 'Check Job Status',
|
|
4140
4330
|
requestIdVariable: 'Request ID Variable',
|
|
4141
4331
|
requestIdVariableTooltip: 'Name of the process variable containing the Epistola request ID',
|
|
4142
4332
|
statusVariable: 'Status Variable',
|
|
@@ -4146,7 +4336,9 @@ const epistolaPluginSpecification = {
|
|
|
4146
4336
|
errorMessageVariable: 'Error Message Variable',
|
|
4147
4337
|
errorMessageVariableTooltip: 'Name of the process variable to store the error message in (when failed)',
|
|
4148
4338
|
// Download document action
|
|
4149
|
-
'download-document': 'Download Document',
|
|
4339
|
+
'epistola-download-document': 'Download Document',
|
|
4340
|
+
documentVariable: 'Document Variable',
|
|
4341
|
+
documentVariableTooltip: 'Name of the process variable holding the Epistola result. May be a String document id (legacy) or a rich result object with a `documentId` key — the action extracts the document id from it.',
|
|
4150
4342
|
contentVariable: 'Content Variable',
|
|
4151
4343
|
contentVariableTooltip: 'Name of the process variable to store the document content (Base64) in',
|
|
4152
4344
|
// Admin page
|
|
@@ -4173,6 +4365,16 @@ const epistolaPluginSpecification = {
|
|
|
4173
4365
|
epistolaAdminNoPendingJobs: 'No pending jobs for this connection.',
|
|
4174
4366
|
epistolaAdminConfiguration: 'Configuration',
|
|
4175
4367
|
epistolaAdminRequestId: 'Request ID',
|
|
4368
|
+
epistolaAdminReconcile: 'Reconcile',
|
|
4369
|
+
epistolaAdminReconciling: 'Reconciling...',
|
|
4370
|
+
epistolaAdminReconcileTooltip: "Ask Epistola for this job's current status and resume the waiting process if it has finished.",
|
|
4371
|
+
epistolaAdminConfigurations: 'Configurations',
|
|
4372
|
+
epistolaAdminValidations: 'BPMN validation',
|
|
4373
|
+
epistolaAdminNoValidations: 'No race-unsafe process definitions detected. Everything looks good.',
|
|
4374
|
+
epistolaAdminValidationWarningTitle: 'BPMN configuration warning',
|
|
4375
|
+
epistolaAdminValidationWarningBody: 'These process definitions have a non-synchronous boundary between the generate-document service task and the EpistolaDocumentGenerated catch event. Results can be missed; use the Reconcile button on the Pending Jobs tab to recover.',
|
|
4376
|
+
epistolaAdminValidationCode: 'Code',
|
|
4377
|
+
epistolaAdminValidationMessage: 'Message',
|
|
4176
4378
|
},
|
|
4177
4379
|
},
|
|
4178
4380
|
};
|
|
@@ -4185,5 +4387,5 @@ const epistolaPluginSpecification = {
|
|
|
4185
4387
|
* Generated bundle index. Do not edit.
|
|
4186
4388
|
*/
|
|
4187
4389
|
|
|
4188
|
-
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent,
|
|
4390
|
+
export { CheckJobStatusConfigurationComponent, DownloadDocumentConfigurationComponent, EPISTOLA_DOCUMENT_OPTIONS, EPISTOLA_DOCUMENT_PREVIEW_OPTIONS, EPISTOLA_OVERRIDE_BUILDER_OPTIONS, EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EPISTOLA_RETRY_FORM_OPTIONS, EpistolaAdminPageComponent, EpistolaAdminRoutingModule, EpistolaAdminService, EpistolaConfigurationComponent, EpistolaDocumentComponent, EpistolaDocumentPreviewComponent, EpistolaMenuService, EpistolaOverrideBuilderComponent, EpistolaPluginModule, EpistolaPluginService, EpistolaProcessLinkSelectorComponent, EpistolaRetryFormComponent, EpistolaTaskContextInterceptor, EpistolaTaskContextService, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, epistolaPluginSpecification, errorResource, initialResource, isEpistolaEnabled, loadingResource, registerEpistolaDocumentComponent, registerEpistolaDocumentPreviewComponent, registerEpistolaOverrideBuilderComponent, registerEpistolaProcessLinkSelectorComponent, registerEpistolaRetryFormComponent, successResource };
|
|
4189
4391
|
//# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map
|