@epistola.app/valtimo-plugin 0.9.3 → 0.10.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 +539 -195
- package/fesm2022/epistola.app-valtimo-plugin.mjs.map +1 -1
- package/lib/components/download-document-configuration/download-document-config.util.d.ts +15 -0
- package/lib/components/download-document-configuration/download-document-configuration.component.d.ts +10 -1
- package/lib/components/epistola-admin-page/epistola-admin-page.component.d.ts +21 -4
- package/lib/components/epistola-document/epistola-document.component.d.ts +11 -7
- package/lib/components/epistola-document-preview/epistola-document-preview.component.d.ts +19 -7
- package/lib/components/epistola-document-preview/preview-utils.d.ts +22 -0
- package/lib/components/epistola-retry-form/epistola-retry-form.component.d.ts +9 -7
- package/lib/components/formio-builder-utils.d.ts +14 -0
- package/lib/components/process-link-selector/process-link-selector.util.d.ts +10 -0
- package/lib/models/admin.d.ts +38 -0
- package/lib/models/config.d.ts +16 -1
- package/lib/services/epistola-admin.service.d.ts +11 -4
- package/lib/services/epistola-plugin.service.d.ts +9 -9
- package/lib/services/index.d.ts +1 -2
- package/lib/services/prefilled-task-id.d.ts +52 -0
- package/package.json +1 -1
- package/sbom.json +1 -1
- package/lib/services/epistola-task-context.interceptor.d.ts +0 -29
- package/lib/services/epistola-task-context.matcher.d.ts +0 -19
- package/lib/services/epistola-task-context.service.d.ts +0 -26
|
@@ -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
|
|
4
|
+
import { HttpHeaders, 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';
|
|
@@ -17,11 +17,11 @@ import { FormsModule } from '@angular/forms';
|
|
|
17
17
|
import * as _jsonata from 'jsonata';
|
|
18
18
|
import * as i2$3 from '@valtimo/process-link';
|
|
19
19
|
import * as i2$4 from '@angular/platform-browser';
|
|
20
|
-
import * as
|
|
20
|
+
import * as i4 from '@formio/angular';
|
|
21
21
|
import { FormioModule } from '@formio/angular';
|
|
22
22
|
import * as i2$5 from '@angular/router';
|
|
23
23
|
import { RouterModule, Router } from '@angular/router';
|
|
24
|
-
import * as i5
|
|
24
|
+
import * as i5 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';
|
|
@@ -88,10 +88,11 @@ class EpistolaAdminService {
|
|
|
88
88
|
return this.http.post(`${this.apiEndpoint}/pending/${encodeURIComponent(executionId)}/reconcile`, null);
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Get the latest BPMN race-safety validation
|
|
92
|
-
*
|
|
91
|
+
* Get the latest BPMN race-safety validation report across deployed process
|
|
92
|
+
* definitions: the violations (empty = healthy) plus when it was last checked
|
|
93
|
+
* and how often it refreshes.
|
|
93
94
|
*/
|
|
94
|
-
|
|
95
|
+
getValidationReport() {
|
|
95
96
|
return this.http.get(`${this.apiEndpoint}/validations`);
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
@@ -126,6 +127,19 @@ class EpistolaAdminService {
|
|
|
126
127
|
responseType: 'blob',
|
|
127
128
|
});
|
|
128
129
|
}
|
|
130
|
+
// ---- TEMPORARY (removed in 1.0.0): task-id carrier detection + repair ----
|
|
131
|
+
/** Forms whose Epistola components are missing the task-id carrier. */
|
|
132
|
+
getFormCarrierIssues() {
|
|
133
|
+
return this.http.get(`${this.apiEndpoint}/forms/carrier-issues`);
|
|
134
|
+
}
|
|
135
|
+
/** Inject the task-id carrier into a single form's Epistola components. */
|
|
136
|
+
repairFormCarrier(formId) {
|
|
137
|
+
return this.http.post(`${this.apiEndpoint}/forms/${encodeURIComponent(formId)}/repair-carrier`, null);
|
|
138
|
+
}
|
|
139
|
+
/** Repair every flagged form. */
|
|
140
|
+
repairAllFormCarriers() {
|
|
141
|
+
return this.http.post(`${this.apiEndpoint}/forms/repair-carrier`, null);
|
|
142
|
+
}
|
|
129
143
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService, deps: [{ token: i1.HttpClient }, { token: i2.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
130
144
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminService });
|
|
131
145
|
}
|
|
@@ -262,13 +276,13 @@ class EpistolaPluginService {
|
|
|
262
276
|
/**
|
|
263
277
|
* Get a dynamically generated Formio form for retrying a failed document generation.
|
|
264
278
|
*
|
|
279
|
+
* The backend derives the process instance and case document from the authorized task,
|
|
280
|
+
* so only the task id (and optionally the source activity) is sent.
|
|
281
|
+
*
|
|
265
282
|
* @param taskId Operaton user task id (required — backend authorizes via OperatonTask:VIEW)
|
|
266
283
|
*/
|
|
267
|
-
getRetryForm(taskId,
|
|
268
|
-
const params = { taskId
|
|
269
|
-
if (documentId) {
|
|
270
|
-
params['documentId'] = documentId;
|
|
271
|
-
}
|
|
284
|
+
getRetryForm(taskId, sourceActivityId) {
|
|
285
|
+
const params = { taskId };
|
|
272
286
|
if (sourceActivityId) {
|
|
273
287
|
params['sourceActivityId'] = sourceActivityId;
|
|
274
288
|
}
|
|
@@ -328,99 +342,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
328
342
|
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.ConfigService }] });
|
|
329
343
|
|
|
330
344
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
345
|
+
* Helpers for reading the active user task's id out of a Valtimo task form that was
|
|
346
|
+
* prefilled server-side by the {@code epistola:} value resolver (see the backend
|
|
347
|
+
* {@code EpistolaTaskValueResolverFactory}).
|
|
334
348
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
349
|
+
* <p>Background: the Epistola Formio components need the id of the user task whose form
|
|
350
|
+
* they're rendered in, to authorize their backend requests ({@code OperatonTask:VIEW}).
|
|
351
|
+
* Valtimo exposes no service that carries the task id to a custom Formio component at
|
|
352
|
+
* runtime, and earlier URL-sniffing only worked in the direct task-open flow (the
|
|
353
|
+
* task-list / case-detail flow bulk-fetches process links and never fires the per-task
|
|
354
|
+
* call).
|
|
338
355
|
*
|
|
339
|
-
* <p
|
|
340
|
-
*
|
|
341
|
-
* (
|
|
342
|
-
*
|
|
343
|
-
* and Formio elements live in their own injector tree). This service is a
|
|
344
|
-
* workaround until upstream exposes `taskInstanceId` via `FormIoStateService`.
|
|
345
|
-
*/
|
|
346
|
-
class EpistolaTaskContextService {
|
|
347
|
-
_taskInstanceId$ = new BehaviorSubject(null);
|
|
348
|
-
taskInstanceId$ = this._taskInstanceId$.asObservable();
|
|
349
|
-
get taskInstanceId() {
|
|
350
|
-
return this._taskInstanceId$.value;
|
|
351
|
-
}
|
|
352
|
-
setTaskInstanceId(id) {
|
|
353
|
-
this._taskInstanceId$.next(id);
|
|
354
|
-
}
|
|
355
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
356
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, providedIn: 'root' });
|
|
357
|
-
}
|
|
358
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextService, decorators: [{
|
|
359
|
-
type: Injectable,
|
|
360
|
-
args: [{ providedIn: 'root' }]
|
|
361
|
-
}] });
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Pure helpers for {@link EpistolaTaskContextInterceptor}, extracted so they
|
|
365
|
-
* can be unit-tested without pulling in {@code @angular/core} (which ts-jest
|
|
366
|
-
* cannot transform without {@code jest-preset-angular}).
|
|
356
|
+
* <p>Form prefill, however, runs server-side in every flow. A form field with
|
|
357
|
+
* {@code properties.sourceKey = "epistola:taskId"} is filled with the task id at prefill
|
|
358
|
+
* time (by the backend {@code EpistolaTaskValueResolverFactory}); this helper reads it back
|
|
359
|
+
* from the Formio root — robustly, regardless of how the task was opened.
|
|
367
360
|
*/
|
|
361
|
+
/** The value-resolver source key that yields the current task id at prefill time. */
|
|
362
|
+
const PREFILLED_TASK_ID_SOURCE_KEY = 'epistola:taskId';
|
|
363
|
+
/** Conventional key of the hidden carrier field that holds the prefilled task id. */
|
|
364
|
+
const PREFILLED_TASK_ID_DATA_KEY = 'epistolaTaskId';
|
|
368
365
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
366
|
+
* Hidden Formio child component that carries the prefilled task id. It is embedded as a
|
|
367
|
+
* nested component inside each Epistola task component's schema, so dropping that component
|
|
368
|
+
* brings the carrier with it — the form author never adds a separate field. Valtimo's
|
|
369
|
+
* server-side prefill fills its {@code defaultValue} from the {@code epistola:taskId}
|
|
370
|
+
* value resolver; {@link readPrefilledTaskId} reads it back from the form definition.
|
|
371
371
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* so we don't accidentally match a longer trailing segment.
|
|
372
|
+
* {@code persistent: false} keeps the value out of the submission, so the task id never
|
|
373
|
+
* lands in the case document / process variables.
|
|
375
374
|
*/
|
|
376
|
-
const
|
|
375
|
+
const PREFILLED_TASK_ID_CARRIER = {
|
|
376
|
+
type: 'hidden',
|
|
377
|
+
key: PREFILLED_TASK_ID_DATA_KEY,
|
|
378
|
+
input: true,
|
|
379
|
+
persistent: false,
|
|
380
|
+
label: 'Epistola Task Id',
|
|
381
|
+
properties: { sourceKey: PREFILLED_TASK_ID_SOURCE_KEY },
|
|
382
|
+
};
|
|
377
383
|
/**
|
|
378
|
-
*
|
|
379
|
-
*
|
|
384
|
+
* Reads the prefilled task id from a Formio webform/wizard root, or null when absent.
|
|
385
|
+
*
|
|
386
|
+
* Looks in two places, in order:
|
|
387
|
+
* 1. The (prefilled) form definition — any component whose {@code properties.sourceKey}
|
|
388
|
+
* is {@code epistola:taskId} carries the task id in its {@code defaultValue}. This works
|
|
389
|
+
* even when the carrier is a hidden field that Formio doesn't surface into submission data.
|
|
390
|
+
* 2. The submission data under {@link PREFILLED_TASK_ID_DATA_KEY}, for a rendered sibling
|
|
391
|
+
* hidden field whose value Formio copied into {@code root.data}.
|
|
380
392
|
*/
|
|
381
|
-
function
|
|
382
|
-
if (
|
|
393
|
+
function readPrefilledTaskId(root) {
|
|
394
|
+
if (!root) {
|
|
383
395
|
return null;
|
|
384
|
-
|
|
385
|
-
|
|
396
|
+
}
|
|
397
|
+
const fromForm = findSourceKeyDefaultValue(root.form, PREFILLED_TASK_ID_SOURCE_KEY);
|
|
398
|
+
if (typeof fromForm === 'string' && fromForm.length > 0) {
|
|
399
|
+
return fromForm;
|
|
400
|
+
}
|
|
401
|
+
const fromData = root.data?.[PREFILLED_TASK_ID_DATA_KEY];
|
|
402
|
+
if (typeof fromData === 'string' && fromData.length > 0) {
|
|
403
|
+
return fromData;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
386
406
|
}
|
|
387
|
-
|
|
388
407
|
/**
|
|
389
|
-
*
|
|
390
|
-
* {@
|
|
391
|
-
* {@code
|
|
392
|
-
* {@code TaskDetailContentComponent.loadTaskDetails(...)} fires unconditionally
|
|
393
|
-
* before any task form is rendered (see @valtimo/task internals).
|
|
394
|
-
*
|
|
395
|
-
* <p>This interceptor does <b>not</b> modify the outgoing request. It only
|
|
396
|
-
* captures the taskId from the URL.
|
|
397
|
-
*
|
|
398
|
-
* <p>Workaround for Valtimo 13.21 not exposing taskInstanceId through any
|
|
399
|
-
* injectable service. Remove once upstream adds e.g.
|
|
400
|
-
* {@code FormIoStateService.setTaskInstanceId(...)}.
|
|
401
|
-
*
|
|
402
|
-
* <p>The actual URL-matching logic lives in
|
|
403
|
-
* {@link extractTaskInstanceIdFromUrl} so it can be unit-tested without an
|
|
404
|
-
* Angular harness.
|
|
408
|
+
* Deep-walks a form definition node looking for a component whose
|
|
409
|
+
* {@code properties.sourceKey} equals {@code sourceKey}, and returns its
|
|
410
|
+
* {@code defaultValue} (the prefilled value). Returns null when not found.
|
|
405
411
|
*/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
function findSourceKeyDefaultValue(node, sourceKey) {
|
|
413
|
+
if (Array.isArray(node)) {
|
|
414
|
+
for (const item of node) {
|
|
415
|
+
const found = findSourceKeyDefaultValue(item, sourceKey);
|
|
416
|
+
if (found != null) {
|
|
417
|
+
return found;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
if (node && typeof node === 'object') {
|
|
423
|
+
if (node.properties?.sourceKey === sourceKey && typeof node.defaultValue === 'string') {
|
|
424
|
+
return node.defaultValue;
|
|
425
|
+
}
|
|
426
|
+
for (const key of Object.keys(node)) {
|
|
427
|
+
const found = findSourceKeyDefaultValue(node[key], sourceKey);
|
|
428
|
+
if (found != null) {
|
|
429
|
+
return found;
|
|
430
|
+
}
|
|
415
431
|
}
|
|
416
|
-
return next.handle(request);
|
|
417
432
|
}
|
|
418
|
-
|
|
419
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor });
|
|
433
|
+
return null;
|
|
420
434
|
}
|
|
421
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaTaskContextInterceptor, decorators: [{
|
|
422
|
-
type: Injectable
|
|
423
|
-
}], ctorParameters: () => [{ type: EpistolaTaskContextService }] });
|
|
424
435
|
|
|
425
436
|
class EpistolaConfigurationComponent {
|
|
426
437
|
save$;
|
|
@@ -1634,6 +1645,37 @@ function expandDotNotation(flat) {
|
|
|
1634
1645
|
}
|
|
1635
1646
|
return result;
|
|
1636
1647
|
}
|
|
1648
|
+
/**
|
|
1649
|
+
* A preview is "override-driven" when it has a non-empty override mapping: its
|
|
1650
|
+
* input data comes from the form via the mapping, so it must wait for that data
|
|
1651
|
+
* before it can render. Previews without a mapping load straight from the base
|
|
1652
|
+
* doc/case data.
|
|
1653
|
+
*/
|
|
1654
|
+
function isOverrideDriven(mapping) {
|
|
1655
|
+
return !!mapping && Object.keys(mapping).length > 0;
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Whether the computed input overrides carry any usable data yet.
|
|
1659
|
+
*/
|
|
1660
|
+
function hasUsableOverrides(overrides) {
|
|
1661
|
+
return !!overrides && Object.keys(overrides).length > 0;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Decide whether a preview request should fire given the configured override
|
|
1665
|
+
* mapping and the currently computed overrides.
|
|
1666
|
+
*
|
|
1667
|
+
* - Override-driven previews only load once the mapped form data is present;
|
|
1668
|
+
* before that they show a "complete the form" placeholder and fire nothing
|
|
1669
|
+
* (avoids a doomed request that Epistola rejects with a 400 for missing
|
|
1670
|
+
* required fields).
|
|
1671
|
+
* - Previews without a mapping always load (base data is the whole input).
|
|
1672
|
+
*/
|
|
1673
|
+
function shouldLoadPreview(mapping, overrides) {
|
|
1674
|
+
if (isOverrideDriven(mapping)) {
|
|
1675
|
+
return hasUsableOverrides(overrides);
|
|
1676
|
+
}
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1637
1679
|
/**
|
|
1638
1680
|
* Given an override mapping (scope -> { inputPath -> "form:<componentKey>" })
|
|
1639
1681
|
* and form data, produce the inputOverrides object for the backend.
|
|
@@ -2225,6 +2267,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2225
2267
|
type: Output
|
|
2226
2268
|
}] } });
|
|
2227
2269
|
|
|
2270
|
+
const DEFAULT_STORAGE_TARGET = 'TEMPORARY_RESOURCE';
|
|
2271
|
+
/** Normalize a (possibly undefined) storageTarget to a concrete target, applying the default. */
|
|
2272
|
+
function resolveStorageTarget(target) {
|
|
2273
|
+
return target === 'PROCESS_VARIABLE' ? 'PROCESS_VARIABLE' : DEFAULT_STORAGE_TARGET;
|
|
2274
|
+
}
|
|
2275
|
+
/**
|
|
2276
|
+
* A config is valid when the input variable and the output variable that matches the chosen
|
|
2277
|
+
* storage target are both set.
|
|
2278
|
+
*/
|
|
2279
|
+
function isDownloadDocumentConfigValid(config) {
|
|
2280
|
+
if (!config?.documentVariable) {
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
return resolveStorageTarget(config.storageTarget) === 'PROCESS_VARIABLE'
|
|
2284
|
+
? !!config.contentVariable
|
|
2285
|
+
: !!config.resourceIdVariable;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2228
2288
|
class DownloadDocumentConfigurationComponent {
|
|
2229
2289
|
save$;
|
|
2230
2290
|
disabled$;
|
|
@@ -2243,11 +2303,24 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2243
2303
|
resolvedPrefill = {};
|
|
2244
2304
|
prefillResolved$ = new BehaviorSubject(false);
|
|
2245
2305
|
safeDisabled$;
|
|
2306
|
+
/**
|
|
2307
|
+
* Static option set for the storage target. Values match the backend
|
|
2308
|
+
* {@code DocumentStorageTarget} enum constants; labels are explained further via the
|
|
2309
|
+
* translated field title/tooltip.
|
|
2310
|
+
*/
|
|
2311
|
+
storageTargetOptions = [
|
|
2312
|
+
{ id: 'TEMPORARY_RESOURCE', text: 'Temporary resource storage' },
|
|
2313
|
+
{ id: 'PROCESS_VARIABLE', text: 'Process variable (inline bytes)' },
|
|
2314
|
+
];
|
|
2315
|
+
defaultStorageTarget = DEFAULT_STORAGE_TARGET;
|
|
2316
|
+
/** Drives which output-variable field is shown (resource id vs inline content). */
|
|
2317
|
+
selectedTarget$ = new BehaviorSubject(this.defaultStorageTarget);
|
|
2246
2318
|
ngOnInit() {
|
|
2247
2319
|
this.safeDisabled$ = this.disabled$.pipe(startWith(true), delay(0));
|
|
2248
2320
|
const prefill$ = this.prefillConfiguration$ ?? of({});
|
|
2249
2321
|
prefill$.pipe(take(1)).subscribe((prefill) => {
|
|
2250
2322
|
this.resolvedPrefill = prefill ?? {};
|
|
2323
|
+
this.selectedTarget$.next(resolveStorageTarget(this.resolvedPrefill.storageTarget));
|
|
2251
2324
|
this.prefillResolved$.next(true);
|
|
2252
2325
|
});
|
|
2253
2326
|
this.openSaveSubscription();
|
|
@@ -2257,11 +2330,14 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2257
2330
|
}
|
|
2258
2331
|
formValueChange(formOutput) {
|
|
2259
2332
|
const formValue = formOutput;
|
|
2333
|
+
if (formValue?.storageTarget) {
|
|
2334
|
+
this.selectedTarget$.next(formValue.storageTarget);
|
|
2335
|
+
}
|
|
2260
2336
|
this.formValue$.next(formValue);
|
|
2261
2337
|
this.handleValid(formValue);
|
|
2262
2338
|
}
|
|
2263
2339
|
handleValid(formValue) {
|
|
2264
|
-
const valid =
|
|
2340
|
+
const valid = isDownloadDocumentConfigValid(formValue);
|
|
2265
2341
|
this.valid$.next(valid);
|
|
2266
2342
|
this.valid.emit(valid);
|
|
2267
2343
|
}
|
|
@@ -2277,11 +2353,11 @@ class DownloadDocumentConfigurationComponent {
|
|
|
2277
2353
|
});
|
|
2278
2354
|
}
|
|
2279
2355
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2280
|
-
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"] }] });
|
|
2356
|
+
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-select\n name=\"storageTarget\"\n [title]=\"'storageTarget' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'storageTargetTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"storageTargetOptions\"\n [defaultSelectionId]=\"resolvedPrefill?.storageTarget || defaultStorageTarget\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'TEMPORARY_RESOURCE'\"\n name=\"resourceIdVariable\"\n [title]=\"'resourceIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resourceIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.resourceIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'PROCESS_VARIABLE'\"\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"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }] });
|
|
2281
2357
|
}
|
|
2282
2358
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DownloadDocumentConfigurationComponent, decorators: [{
|
|
2283
2359
|
type: Component,
|
|
2284
|
-
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" }]
|
|
2360
|
+
args: [{ selector: 'epistola-download-document-configuration', standalone: true, imports: [CommonModule, PluginTranslatePipeModule, FormModule, InputModule, SelectModule], 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-select\n name=\"storageTarget\"\n [title]=\"'storageTarget' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'storageTargetTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [items]=\"storageTargetOptions\"\n [defaultSelectionId]=\"resolvedPrefill?.storageTarget || defaultStorageTarget\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-select>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'TEMPORARY_RESOURCE'\"\n name=\"resourceIdVariable\"\n [title]=\"'resourceIdVariable' | pluginTranslate: pluginId | async\"\n [tooltip]=\"'resourceIdVariableTooltip' | pluginTranslate: pluginId | async\"\n [margin]=\"true\"\n [defaultValue]=\"resolvedPrefill?.resourceIdVariable\"\n [disabled]=\"safeDisabled$ | async\"\n [required]=\"true\"\n >\n </v-input>\n\n <v-input\n *ngIf=\"(selectedTarget$ | async) === 'PROCESS_VARIABLE'\"\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" }]
|
|
2285
2361
|
}], propDecorators: { save$: [{
|
|
2286
2362
|
type: Input
|
|
2287
2363
|
}], disabled$: [{
|
|
@@ -2316,7 +2392,6 @@ class EpistolaDocumentComponent {
|
|
|
2316
2392
|
epistolaPluginService;
|
|
2317
2393
|
sanitizer;
|
|
2318
2394
|
formIoStateService;
|
|
2319
|
-
taskContext;
|
|
2320
2395
|
cdr;
|
|
2321
2396
|
value;
|
|
2322
2397
|
valueChange = new EventEmitter();
|
|
@@ -2337,6 +2412,11 @@ class EpistolaDocumentComponent {
|
|
|
2337
2412
|
tenantIdVariable = 'epistolaTenantId';
|
|
2338
2413
|
/** Filename used for the download disposition. */
|
|
2339
2414
|
filename = 'document.pdf';
|
|
2415
|
+
/**
|
|
2416
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
2417
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
2418
|
+
*/
|
|
2419
|
+
taskInstanceId;
|
|
2340
2420
|
loading = false;
|
|
2341
2421
|
downloading = false;
|
|
2342
2422
|
error = null;
|
|
@@ -2346,18 +2426,21 @@ class EpistolaDocumentComponent {
|
|
|
2346
2426
|
get designMode() {
|
|
2347
2427
|
return !this.formIoStateService.documentId;
|
|
2348
2428
|
}
|
|
2349
|
-
constructor(epistolaPluginService, sanitizer, formIoStateService,
|
|
2429
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr) {
|
|
2350
2430
|
this.epistolaPluginService = epistolaPluginService;
|
|
2351
2431
|
this.sanitizer = sanitizer;
|
|
2352
2432
|
this.formIoStateService = formIoStateService;
|
|
2353
|
-
this.taskContext = taskContext;
|
|
2354
2433
|
this.cdr = cdr;
|
|
2355
2434
|
}
|
|
2356
|
-
|
|
2357
|
-
if (this.designMode) {
|
|
2435
|
+
ngOnChanges(changes) {
|
|
2436
|
+
if (this.designMode || this.display === 'button') {
|
|
2358
2437
|
return;
|
|
2359
2438
|
}
|
|
2360
|
-
|
|
2439
|
+
// The Formio wrapper sets taskInstanceId around attach, so it can arrive after the
|
|
2440
|
+
// first change — (re)load the inline document once it's available instead of leaving
|
|
2441
|
+
// the "Document is alleen beschikbaar binnen een taak" message until a manual refresh.
|
|
2442
|
+
// (For display="button" the download() click reads the task id on demand.)
|
|
2443
|
+
if (changes['taskInstanceId'] && this.taskInstanceId) {
|
|
2361
2444
|
this.loadInline();
|
|
2362
2445
|
}
|
|
2363
2446
|
}
|
|
@@ -2424,7 +2507,7 @@ class EpistolaDocumentComponent {
|
|
|
2424
2507
|
});
|
|
2425
2508
|
}
|
|
2426
2509
|
buildRequest(disposition) {
|
|
2427
|
-
const taskId = this.
|
|
2510
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2428
2511
|
const caseDocumentId = this.formIoStateService.documentId;
|
|
2429
2512
|
if (!taskId || !caseDocumentId) {
|
|
2430
2513
|
this.error = 'Document is alleen beschikbaar binnen een taak.';
|
|
@@ -2447,8 +2530,8 @@ class EpistolaDocumentComponent {
|
|
|
2447
2530
|
this.previewUrl = null;
|
|
2448
2531
|
}
|
|
2449
2532
|
}
|
|
2450
|
-
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:
|
|
2451
|
-
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: `
|
|
2533
|
+
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: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2534
|
+
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", taskInstanceId: "taskInstanceId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2452
2535
|
<!-- Design-time placeholder -->
|
|
2453
2536
|
<div *ngIf="designMode" class="epistola-doc-panel">
|
|
2454
2537
|
<div class="doc-header">
|
|
@@ -2603,7 +2686,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2603
2686
|
</div>
|
|
2604
2687
|
</div>
|
|
2605
2688
|
`, 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"] }]
|
|
2606
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type:
|
|
2689
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
2607
2690
|
type: Input
|
|
2608
2691
|
}], valueChange: [{
|
|
2609
2692
|
type: Output
|
|
@@ -2619,19 +2702,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2619
2702
|
type: Input
|
|
2620
2703
|
}], filename: [{
|
|
2621
2704
|
type: Input
|
|
2705
|
+
}], taskInstanceId: [{
|
|
2706
|
+
type: Input
|
|
2622
2707
|
}] } });
|
|
2623
2708
|
|
|
2624
2709
|
class EpistolaRetryFormComponent {
|
|
2625
2710
|
epistolaPluginService;
|
|
2626
|
-
formIoStateService;
|
|
2627
2711
|
cdr;
|
|
2628
2712
|
sanitizer;
|
|
2629
|
-
taskContext;
|
|
2630
2713
|
value;
|
|
2631
2714
|
valueChange = new EventEmitter();
|
|
2632
2715
|
disabled = false;
|
|
2633
2716
|
label = 'Document Data';
|
|
2634
2717
|
sourceActivityId;
|
|
2718
|
+
/**
|
|
2719
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
2720
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
2721
|
+
*/
|
|
2722
|
+
taskInstanceId;
|
|
2635
2723
|
formDefinition;
|
|
2636
2724
|
submission;
|
|
2637
2725
|
loading = true;
|
|
@@ -2646,17 +2734,14 @@ class EpistolaRetryFormComponent {
|
|
|
2646
2734
|
previewSubject = new Subject();
|
|
2647
2735
|
currentBlobUrl = null;
|
|
2648
2736
|
resolvedSourceActivityId;
|
|
2649
|
-
processDefinitionKey;
|
|
2650
2737
|
formOptions = {
|
|
2651
2738
|
noAlerts: true,
|
|
2652
2739
|
buttonSettings: { showCancel: false, showSubmit: false, showPrevious: false, showNext: false },
|
|
2653
2740
|
};
|
|
2654
|
-
constructor(epistolaPluginService,
|
|
2741
|
+
constructor(epistolaPluginService, cdr, sanitizer) {
|
|
2655
2742
|
this.epistolaPluginService = epistolaPluginService;
|
|
2656
|
-
this.formIoStateService = formIoStateService;
|
|
2657
2743
|
this.cdr = cdr;
|
|
2658
2744
|
this.sanitizer = sanitizer;
|
|
2659
|
-
this.taskContext = taskContext;
|
|
2660
2745
|
// Debounce preview calls
|
|
2661
2746
|
this.previewSubscription = this.previewSubject.pipe(debounceTime$1(1500)).subscribe((data) => {
|
|
2662
2747
|
this.loadPreview(data);
|
|
@@ -2666,6 +2751,13 @@ class EpistolaRetryFormComponent {
|
|
|
2666
2751
|
if (!this.loaded) {
|
|
2667
2752
|
this.loaded = true;
|
|
2668
2753
|
this.loadForm();
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
// The Formio wrapper sets taskInstanceId after attach, so it can land after the
|
|
2757
|
+
// first render — if the form failed to load for lack of a task id, retry once it
|
|
2758
|
+
// arrives instead of leaving the "only available from within a user task" message.
|
|
2759
|
+
if (changes['taskInstanceId'] && this.taskInstanceId && !this.formDefinition) {
|
|
2760
|
+
this.loadForm();
|
|
2669
2761
|
}
|
|
2670
2762
|
}
|
|
2671
2763
|
ngOnDestroy() {
|
|
@@ -2689,11 +2781,8 @@ class EpistolaRetryFormComponent {
|
|
|
2689
2781
|
}
|
|
2690
2782
|
}
|
|
2691
2783
|
loadPreview(formData) {
|
|
2692
|
-
|
|
2693
|
-
const
|
|
2694
|
-
if (!documentId || !processInstanceId)
|
|
2695
|
-
return;
|
|
2696
|
-
const taskId = this.taskContext.taskInstanceId;
|
|
2784
|
+
// The backend derives the process instance and case document from the task.
|
|
2785
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2697
2786
|
if (!taskId) {
|
|
2698
2787
|
this.previewError = 'Preview is only available from within a user task.';
|
|
2699
2788
|
this.cdr.markForCheck();
|
|
@@ -2710,8 +2799,6 @@ class EpistolaRetryFormComponent {
|
|
|
2710
2799
|
this.epistolaPluginService
|
|
2711
2800
|
.previewToBlob({
|
|
2712
2801
|
taskId,
|
|
2713
|
-
documentId,
|
|
2714
|
-
processInstanceId,
|
|
2715
2802
|
sourceActivityId: this.sourceActivityId || null,
|
|
2716
2803
|
overrides: formData,
|
|
2717
2804
|
})
|
|
@@ -2748,15 +2835,8 @@ class EpistolaRetryFormComponent {
|
|
|
2748
2835
|
});
|
|
2749
2836
|
}
|
|
2750
2837
|
loadForm() {
|
|
2751
|
-
|
|
2752
|
-
const
|
|
2753
|
-
if (!processInstanceId) {
|
|
2754
|
-
this.error = 'Could not determine process instance ID.';
|
|
2755
|
-
this.loading = false;
|
|
2756
|
-
this.cdr.markForCheck();
|
|
2757
|
-
return;
|
|
2758
|
-
}
|
|
2759
|
-
const taskId = this.taskContext.taskInstanceId;
|
|
2838
|
+
// The backend derives the process instance and case document from the task.
|
|
2839
|
+
const taskId = this.taskInstanceId ?? null;
|
|
2760
2840
|
if (!taskId) {
|
|
2761
2841
|
this.error = 'Retry form is only available from within a user task.';
|
|
2762
2842
|
this.loading = false;
|
|
@@ -2764,7 +2844,7 @@ class EpistolaRetryFormComponent {
|
|
|
2764
2844
|
return;
|
|
2765
2845
|
}
|
|
2766
2846
|
this.loadSubscription = this.epistolaPluginService
|
|
2767
|
-
.getRetryForm(taskId,
|
|
2847
|
+
.getRetryForm(taskId, this.sourceActivityId)
|
|
2768
2848
|
.subscribe({
|
|
2769
2849
|
next: (form) => {
|
|
2770
2850
|
this.formDefinition = form;
|
|
@@ -2787,8 +2867,8 @@ class EpistolaRetryFormComponent {
|
|
|
2787
2867
|
},
|
|
2788
2868
|
});
|
|
2789
2869
|
}
|
|
2790
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token:
|
|
2791
|
-
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: `
|
|
2870
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, deps: [{ token: EpistolaPluginService }, { token: i0.ChangeDetectorRef }, { token: i2$4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
2871
|
+
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", taskInstanceId: "taskInstanceId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
2792
2872
|
<div *ngIf="loading" class="epistola-retry-loading">Loading form...</div>
|
|
2793
2873
|
<div *ngIf="error" class="epistola-retry-error">{{ error }}</div>
|
|
2794
2874
|
<div
|
|
@@ -2826,7 +2906,7 @@ class EpistolaRetryFormComponent {
|
|
|
2826
2906
|
</div>
|
|
2827
2907
|
</div>
|
|
2828
2908
|
</div>
|
|
2829
|
-
`, 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:
|
|
2909
|
+
`, 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: i4.FormioComponent, selector: "formio" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2830
2910
|
}
|
|
2831
2911
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaRetryFormComponent, decorators: [{
|
|
2832
2912
|
type: Component,
|
|
@@ -2869,7 +2949,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2869
2949
|
</div>
|
|
2870
2950
|
</div>
|
|
2871
2951
|
`, 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"] }]
|
|
2872
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type:
|
|
2952
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i0.ChangeDetectorRef }, { type: i2$4.DomSanitizer }], propDecorators: { value: [{
|
|
2873
2953
|
type: Input
|
|
2874
2954
|
}], valueChange: [{
|
|
2875
2955
|
type: Output
|
|
@@ -2879,6 +2959,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
2879
2959
|
type: Input
|
|
2880
2960
|
}], sourceActivityId: [{
|
|
2881
2961
|
type: Input
|
|
2962
|
+
}], taskInstanceId: [{
|
|
2963
|
+
type: Input
|
|
2882
2964
|
}] } });
|
|
2883
2965
|
|
|
2884
2966
|
class EpistolaDocumentPreviewComponent {
|
|
@@ -2886,7 +2968,6 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2886
2968
|
sanitizer;
|
|
2887
2969
|
formIoStateService;
|
|
2888
2970
|
cdr;
|
|
2889
|
-
taskContext;
|
|
2890
2971
|
value;
|
|
2891
2972
|
valueChange = new EventEmitter();
|
|
2892
2973
|
disabled = false;
|
|
@@ -2894,6 +2975,11 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2894
2975
|
processDefinitionKey;
|
|
2895
2976
|
sourceActivityId;
|
|
2896
2977
|
overrideMapping;
|
|
2978
|
+
/**
|
|
2979
|
+
* Task id forwarded by the Formio wrapper from the server-prefilled form
|
|
2980
|
+
* ({@code epistola:taskId} value resolver), populated in every Valtimo task-open flow.
|
|
2981
|
+
*/
|
|
2982
|
+
taskInstanceId;
|
|
2897
2983
|
loading = false;
|
|
2898
2984
|
error = null;
|
|
2899
2985
|
previewUrl = null;
|
|
@@ -2901,20 +2987,19 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2901
2987
|
initialized = false;
|
|
2902
2988
|
currentBlobUrl = null;
|
|
2903
2989
|
previewSubscription;
|
|
2904
|
-
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr
|
|
2990
|
+
constructor(epistolaPluginService, sanitizer, formIoStateService, cdr) {
|
|
2905
2991
|
this.epistolaPluginService = epistolaPluginService;
|
|
2906
2992
|
this.sanitizer = sanitizer;
|
|
2907
2993
|
this.formIoStateService = formIoStateService;
|
|
2908
2994
|
this.cdr = cdr;
|
|
2909
|
-
this.taskContext = taskContext;
|
|
2910
2995
|
}
|
|
2911
2996
|
/**
|
|
2912
|
-
*
|
|
2913
|
-
*
|
|
2914
|
-
*
|
|
2997
|
+
* The active task id, forwarded by the Formio wrapper from the server-prefilled
|
|
2998
|
+
* form ({@code epistola:taskId} value resolver). Null outside a task context
|
|
2999
|
+
* (e.g. Formio builder), in which case the component fails closed.
|
|
2915
3000
|
*/
|
|
2916
3001
|
get currentTaskId() {
|
|
2917
|
-
return this.
|
|
3002
|
+
return this.taskInstanceId ?? null;
|
|
2918
3003
|
}
|
|
2919
3004
|
get overrideMappingScopes() {
|
|
2920
3005
|
return this.overrideMapping ? Object.keys(this.overrideMapping) : [];
|
|
@@ -2940,12 +3025,17 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2940
3025
|
this.cdr.markForCheck();
|
|
2941
3026
|
return;
|
|
2942
3027
|
}
|
|
2943
|
-
this.
|
|
3028
|
+
this.triggerPreview();
|
|
2944
3029
|
return;
|
|
2945
3030
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3031
|
+
if (this.designMode)
|
|
3032
|
+
return;
|
|
3033
|
+
// React to input-override changes, and to the task id arriving late: the Formio
|
|
3034
|
+
// wrapper sets taskInstanceId after attach, so it can land after the first render —
|
|
3035
|
+
// re-run the preview once it does, instead of leaving the "only available from
|
|
3036
|
+
// within a user task" message until a manual refresh.
|
|
3037
|
+
if (changes['value'] || changes['taskInstanceId']) {
|
|
3038
|
+
this.triggerPreview();
|
|
2949
3039
|
}
|
|
2950
3040
|
}
|
|
2951
3041
|
ngOnDestroy() {
|
|
@@ -2953,31 +3043,42 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2953
3043
|
this.revokeBlobUrl();
|
|
2954
3044
|
}
|
|
2955
3045
|
refresh() {
|
|
3046
|
+
this.triggerPreview();
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Load the preview only when there is enough data for it. Override-driven
|
|
3050
|
+
* previews (those with an override mapping) wait until the mapped form data
|
|
3051
|
+
* has been computed; until then they show a placeholder rather than firing a
|
|
3052
|
+
* request that Epistola would reject with a 400 for missing required fields.
|
|
3053
|
+
*/
|
|
3054
|
+
triggerPreview() {
|
|
3055
|
+
if (!shouldLoadPreview(this.overrideMapping, this.value)) {
|
|
3056
|
+
this.showWaitingForInput();
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
2956
3059
|
this.loadPreview();
|
|
2957
3060
|
}
|
|
3061
|
+
showWaitingForInput() {
|
|
3062
|
+
this.revokeBlobUrl();
|
|
3063
|
+
this.previewSubscription?.unsubscribe();
|
|
3064
|
+
this.previewUrl = null;
|
|
3065
|
+
this.loading = false;
|
|
3066
|
+
this.error = 'Complete the form to generate a preview.';
|
|
3067
|
+
this.cdr.markForCheck();
|
|
3068
|
+
}
|
|
2958
3069
|
/**
|
|
2959
3070
|
* Preview using the explicitly configured process link + input overrides.
|
|
2960
3071
|
* Requires a runtime task context — the backend authorizes the request against
|
|
2961
3072
|
* the task's process instance and case document, so all three ids must match.
|
|
2962
3073
|
*/
|
|
2963
3074
|
loadPreview() {
|
|
2964
|
-
const documentId = this.formIoStateService.documentId;
|
|
2965
|
-
if (!documentId) {
|
|
2966
|
-
this.error = 'Could not determine document ID from context.';
|
|
2967
|
-
this.cdr.markForCheck();
|
|
2968
|
-
return;
|
|
2969
|
-
}
|
|
2970
3075
|
if (!this.sourceActivityId) {
|
|
2971
3076
|
this.error = 'Preview is not configured: set the source activity on the form component.';
|
|
2972
3077
|
this.cdr.markForCheck();
|
|
2973
3078
|
return;
|
|
2974
3079
|
}
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
this.error = 'Preview is only available from within a running process.';
|
|
2978
|
-
this.cdr.markForCheck();
|
|
2979
|
-
return;
|
|
2980
|
-
}
|
|
3080
|
+
// The backend derives the process instance and case document from the task, so the
|
|
3081
|
+
// task id is the only runtime context the request carries.
|
|
2981
3082
|
const taskId = this.currentTaskId;
|
|
2982
3083
|
if (!taskId) {
|
|
2983
3084
|
this.error = 'Preview is only available from within a user task.';
|
|
@@ -2992,9 +3093,6 @@ class EpistolaDocumentPreviewComponent {
|
|
|
2992
3093
|
this.previewSubscription = this.epistolaPluginService
|
|
2993
3094
|
.previewToBlob({
|
|
2994
3095
|
taskId,
|
|
2995
|
-
documentId,
|
|
2996
|
-
processDefinitionKey: this.processDefinitionKey || null,
|
|
2997
|
-
processInstanceId,
|
|
2998
3096
|
sourceActivityId: this.sourceActivityId,
|
|
2999
3097
|
inputOverrides: this.value || null,
|
|
3000
3098
|
overrides: null,
|
|
@@ -3039,8 +3137,8 @@ class EpistolaDocumentPreviewComponent {
|
|
|
3039
3137
|
this.previewUrl = null;
|
|
3040
3138
|
}
|
|
3041
3139
|
}
|
|
3042
|
-
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 }
|
|
3043
|
-
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: `
|
|
3140
|
+
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 }], target: i0.ɵɵFactoryTarget.Component });
|
|
3141
|
+
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", taskInstanceId: "taskInstanceId" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
3044
3142
|
<!-- Design-time view: show configuration summary when no runtime context -->
|
|
3045
3143
|
<div *ngIf="designMode" class="epistola-preview-panel">
|
|
3046
3144
|
<div class="preview-header">
|
|
@@ -3159,7 +3257,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3159
3257
|
</div>
|
|
3160
3258
|
</div>
|
|
3161
3259
|
`, 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"] }]
|
|
3162
|
-
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }
|
|
3260
|
+
}], ctorParameters: () => [{ type: EpistolaPluginService }, { type: i2$4.DomSanitizer }, { type: i3.FormIoStateService }, { type: i0.ChangeDetectorRef }], propDecorators: { value: [{
|
|
3163
3261
|
type: Input
|
|
3164
3262
|
}], valueChange: [{
|
|
3165
3263
|
type: Output
|
|
@@ -3173,6 +3271,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
3173
3271
|
type: Input
|
|
3174
3272
|
}], overrideMapping: [{
|
|
3175
3273
|
type: Input
|
|
3274
|
+
}], taskInstanceId: [{
|
|
3275
|
+
type: Input
|
|
3176
3276
|
}] } });
|
|
3177
3277
|
|
|
3178
3278
|
class EpistolaAdminPageComponent {
|
|
@@ -3182,18 +3282,25 @@ class EpistolaAdminPageComponent {
|
|
|
3182
3282
|
cards = [];
|
|
3183
3283
|
selectedCard = null;
|
|
3184
3284
|
activeTab = 'actions';
|
|
3285
|
+
// NOTE: the 'forms' tab is TEMPORARY (remove in 1.0.0) — see the carrier-repair block below.
|
|
3185
3286
|
overviewTab = 'configurations';
|
|
3186
3287
|
loading = false;
|
|
3187
3288
|
pluginVersion = null;
|
|
3188
3289
|
changelog = null;
|
|
3189
3290
|
changelogLoading = false;
|
|
3190
|
-
|
|
3291
|
+
validationReport = null;
|
|
3191
3292
|
reconcilingExecutionIds = new Set();
|
|
3192
3293
|
reconcileFeedback = null;
|
|
3193
3294
|
catalogs = [];
|
|
3194
3295
|
catalogsLoading = false;
|
|
3195
3296
|
redeployingSlugs = new Set();
|
|
3196
3297
|
catalogFeedback = null;
|
|
3298
|
+
// TEMPORARY (removed in 1.0.0): task-id carrier detection + repair.
|
|
3299
|
+
formIssues = null;
|
|
3300
|
+
formIssuesLoading = false;
|
|
3301
|
+
repairingFormIds = new Set();
|
|
3302
|
+
repairingAll = false;
|
|
3303
|
+
formFeedback = null;
|
|
3197
3304
|
connectionStatuses = [];
|
|
3198
3305
|
usageEntries = [];
|
|
3199
3306
|
pendingJobs = [];
|
|
@@ -3206,6 +3313,14 @@ class EpistolaAdminPageComponent {
|
|
|
3206
3313
|
this.route = route;
|
|
3207
3314
|
this.router = router;
|
|
3208
3315
|
}
|
|
3316
|
+
/** Violations from the latest report (empty when healthy or not yet loaded). */
|
|
3317
|
+
get validationViolations() {
|
|
3318
|
+
return this.validationReport?.violations ?? [];
|
|
3319
|
+
}
|
|
3320
|
+
/** Scan cadence in whole minutes, for the "refreshes every N min" note. */
|
|
3321
|
+
get refreshIntervalMinutes() {
|
|
3322
|
+
return Math.round((this.validationReport?.refreshIntervalMs ?? 600000) / 60000);
|
|
3323
|
+
}
|
|
3209
3324
|
ngOnInit() {
|
|
3210
3325
|
this.deepLinkConfigId = this.route.snapshot.queryParamMap.get('configurationId');
|
|
3211
3326
|
const tab = this.route.snapshot.queryParamMap.get('tab');
|
|
@@ -3232,11 +3347,79 @@ class EpistolaAdminPageComponent {
|
|
|
3232
3347
|
this.activeTab = tab;
|
|
3233
3348
|
this.updateUrl(this.selectedCard?.configurationId ?? null, tab);
|
|
3234
3349
|
}
|
|
3350
|
+
// 'forms' is TEMPORARY (remove in 1.0.0).
|
|
3235
3351
|
setOverviewTab(tab) {
|
|
3236
3352
|
this.overviewTab = tab;
|
|
3237
3353
|
if (tab === 'changelog' && this.changelog === null && !this.changelogLoading) {
|
|
3238
3354
|
this.loadChangelog();
|
|
3239
3355
|
}
|
|
3356
|
+
if (tab === 'forms' && this.formIssues === null && !this.formIssuesLoading) {
|
|
3357
|
+
this.loadFormIssues();
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
// ---- TEMPORARY (removed in 1.0.0): task-id carrier detection + repair ----
|
|
3361
|
+
loadFormIssues() {
|
|
3362
|
+
this.formIssuesLoading = true;
|
|
3363
|
+
this.formFeedback = null;
|
|
3364
|
+
this.adminService.getFormCarrierIssues().subscribe({
|
|
3365
|
+
next: (issues) => {
|
|
3366
|
+
this.formIssues = issues;
|
|
3367
|
+
this.formIssuesLoading = false;
|
|
3368
|
+
},
|
|
3369
|
+
error: () => {
|
|
3370
|
+
this.formIssues = [];
|
|
3371
|
+
this.formIssuesLoading = false;
|
|
3372
|
+
},
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
isRepairingForm(issue) {
|
|
3376
|
+
return this.repairingFormIds.has(issue.formId);
|
|
3377
|
+
}
|
|
3378
|
+
repairForm(issue) {
|
|
3379
|
+
if (this.repairingFormIds.has(issue.formId)) {
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
this.repairingFormIds.add(issue.formId);
|
|
3383
|
+
this.formFeedback = null;
|
|
3384
|
+
this.adminService.repairFormCarrier(issue.formId).subscribe({
|
|
3385
|
+
next: (result) => {
|
|
3386
|
+
this.repairingFormIds.delete(issue.formId);
|
|
3387
|
+
this.formFeedback = {
|
|
3388
|
+
formId: issue.formId,
|
|
3389
|
+
type: 'success',
|
|
3390
|
+
message: `OK — ${result.componentsPatched} component(s) patched`,
|
|
3391
|
+
};
|
|
3392
|
+
this.loadFormIssues();
|
|
3393
|
+
},
|
|
3394
|
+
error: (err) => {
|
|
3395
|
+
this.repairingFormIds.delete(issue.formId);
|
|
3396
|
+
const message = err?.error?.errorMessage ?? err?.error?.message ?? err?.message ?? 'unknown error';
|
|
3397
|
+
this.formFeedback = { formId: issue.formId, type: 'error', message };
|
|
3398
|
+
},
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
repairAllForms() {
|
|
3402
|
+
if (this.repairingAll) {
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
this.repairingAll = true;
|
|
3406
|
+
this.formFeedback = null;
|
|
3407
|
+
this.adminService.repairAllFormCarriers().subscribe({
|
|
3408
|
+
next: (summary) => {
|
|
3409
|
+
this.repairingAll = false;
|
|
3410
|
+
this.formFeedback = {
|
|
3411
|
+
formId: 'all',
|
|
3412
|
+
type: summary.failed > 0 ? 'error' : 'success',
|
|
3413
|
+
message: `Repaired ${summary.formsRepaired} form(s), ${summary.componentsPatched} component(s)${summary.failed > 0 ? `, ${summary.failed} failed` : ''}`,
|
|
3414
|
+
};
|
|
3415
|
+
this.loadFormIssues();
|
|
3416
|
+
},
|
|
3417
|
+
error: (err) => {
|
|
3418
|
+
this.repairingAll = false;
|
|
3419
|
+
const message = err?.error?.message ?? err?.message ?? 'unknown error';
|
|
3420
|
+
this.formFeedback = { formId: 'all', type: 'error', message };
|
|
3421
|
+
},
|
|
3422
|
+
});
|
|
3240
3423
|
}
|
|
3241
3424
|
loadChangelog() {
|
|
3242
3425
|
this.changelogLoading = true;
|
|
@@ -3425,14 +3608,14 @@ class EpistolaAdminPageComponent {
|
|
|
3425
3608
|
this.tryBuildCards();
|
|
3426
3609
|
},
|
|
3427
3610
|
});
|
|
3428
|
-
// Validation
|
|
3429
|
-
// gate the loading flag on
|
|
3430
|
-
this.adminService.
|
|
3431
|
-
next: (
|
|
3432
|
-
this.
|
|
3611
|
+
// Validation report is independent of cards — load alongside but don't
|
|
3612
|
+
// gate the loading flag on it.
|
|
3613
|
+
this.adminService.getValidationReport().subscribe({
|
|
3614
|
+
next: (report) => {
|
|
3615
|
+
this.validationReport = report;
|
|
3433
3616
|
},
|
|
3434
3617
|
error: () => {
|
|
3435
|
-
this.
|
|
3618
|
+
this.validationReport = null;
|
|
3436
3619
|
},
|
|
3437
3620
|
});
|
|
3438
3621
|
}
|
|
@@ -3477,11 +3660,11 @@ class EpistolaAdminPageComponent {
|
|
|
3477
3660
|
});
|
|
3478
3661
|
}
|
|
3479
3662
|
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 });
|
|
3480
|
-
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 <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\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\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\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"] }] });
|
|
3663
|
+
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 <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <ng-template #formsHeading>\n {{ 'epistolaAdminForms' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n *ngIf=\"formIssues\"\n size=\"sm\"\n [type]=\"formIssues.length > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formIssues.length }}</cds-tag\n >\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 class=\"text-muted small mt-3\">\n <div>\n {{ 'epistolaAdminValidationLastChecked' | pluginTranslate: 'epistola' | async }}:\n <ng-container *ngIf=\"validationReport?.lastCheckedAt; else notYetRun\">\n {{ validationReport?.lastCheckedAt | date: 'medium' }}\n </ng-container>\n <ng-template #notYetRun>\n {{ 'epistolaAdminValidationNotYetRun' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n \u00B7 {{ 'epistolaAdminValidationAutoRefresh' | pluginTranslate: 'epistola' | async }}\n {{ refreshIntervalMinutes }} min.\n </div>\n <div>\n {{ 'epistolaAdminValidationLatestVersionNote' | pluginTranslate: 'epistola' | async }}\n </div>\n </div>\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\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <cds-tab\n [heading]=\"formsHeading\"\n [active]=\"overviewTab === 'forms'\"\n (selected)=\"setOverviewTab('forms')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminFormsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"formIssuesLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!formIssuesLoading && formIssues && formIssues.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoFormIssues' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!formIssuesLoading && formIssues && formIssues.length > 0\">\n <div class=\"mb-2\">\n <button\n class=\"btn btn-sm btn-primary\"\n (click)=\"repairAllForms()\"\n [disabled]=\"repairingAll\"\n >\n {{\n (repairingAll ? 'epistolaAdminRepairing' : 'epistolaAdminRepairAll')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <span\n *ngIf=\"formFeedback && formFeedback.formId === 'all'\"\n class=\"small ms-2\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >{{ formFeedback.message }}</span\n >\n </div>\n\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminFormMissing' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let issue of formIssues\">\n <td>\n <code>{{ issue.name }}</code>\n <cds-tag\n *ngIf=\"issue.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{\n 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async\n }}</cds-tag\n >\n </td>\n <td>{{ issue.missingComponents }}</td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"repairForm(issue)\"\n [disabled]=\"isRepairingForm(issue)\"\n [title]=\"'epistolaAdminRepairTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{\n (isRepairingForm(issue) ? 'epistolaAdminRepairing' : 'epistolaAdminRepair')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <div\n *ngIf=\"formFeedback && formFeedback.formId === issue.formId\"\n class=\"small mt-1\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >\n {{ formFeedback.message }}\n </div>\n </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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\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: "pipe", type: i1$1.DatePipe, name: "date" }, { 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.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"] }] });
|
|
3481
3664
|
}
|
|
3482
3665
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: EpistolaAdminPageComponent, decorators: [{
|
|
3483
3666
|
type: Component,
|
|
3484
|
-
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 <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\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\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\n"] }]
|
|
3667
|
+
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 <ng-template #changelogHeading>\n {{ 'epistolaAdminChangelog' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <ng-template #formsHeading>\n {{ 'epistolaAdminForms' | pluginTranslate: 'epistola' | async }}\n <cds-tag\n *ngIf=\"formIssues\"\n size=\"sm\"\n [type]=\"formIssues.length > 0 ? 'red' : 'gray'\"\n class=\"ms-1\"\n >{{ formIssues.length }}</cds-tag\n >\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 class=\"text-muted small mt-3\">\n <div>\n {{ 'epistolaAdminValidationLastChecked' | pluginTranslate: 'epistola' | async }}:\n <ng-container *ngIf=\"validationReport?.lastCheckedAt; else notYetRun\">\n {{ validationReport?.lastCheckedAt | date: 'medium' }}\n </ng-container>\n <ng-template #notYetRun>\n {{ 'epistolaAdminValidationNotYetRun' | pluginTranslate: 'epistola' | async }}\n </ng-template>\n \u00B7 {{ 'epistolaAdminValidationAutoRefresh' | pluginTranslate: 'epistola' | async }}\n {{ refreshIntervalMinutes }} min.\n </div>\n <div>\n {{ 'epistolaAdminValidationLatestVersionNote' | pluginTranslate: 'epistola' | async }}\n </div>\n </div>\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\n <cds-tab\n [heading]=\"changelogHeading\"\n [active]=\"overviewTab === 'changelog'\"\n (selected)=\"setOverviewTab('changelog')\"\n >\n <div class=\"d-flex align-items-center mt-3 mb-3\">\n <span class=\"text-muted me-2\">{{\n 'epistolaAdminRunningVersion' | pluginTranslate: 'epistola' | async\n }}</span>\n <cds-tag size=\"sm\" type=\"blue\">v{{ pluginVersion || '\u2014' }}</cds-tag>\n </div>\n\n <div *ngIf=\"changelogLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoChangelog' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!changelogLoading && changelog && changelog.length > 0\"\n class=\"epistola-changelog\"\n >\n <section *ngFor=\"let release of changelog\" class=\"changelog-release\">\n <div class=\"changelog-release__header\">\n <cds-tag size=\"sm\" type=\"purple\">{{ release.version }}</cds-tag>\n <span *ngIf=\"release.date\" class=\"text-muted ms-2\">{{ release.date }}</span>\n </div>\n <div *ngFor=\"let section of release.sections\" class=\"changelog-section\">\n <h6 class=\"changelog-section__title\">{{ section.title }}</h6>\n <ul class=\"changelog-section__items\">\n <li *ngFor=\"let item of section.items\">{{ item }}</li>\n </ul>\n </div>\n </section>\n </div>\n </cds-tab>\n\n <!-- TEMPORARY (removed in 1.0.0): forms missing the task-id carrier -->\n <cds-tab\n [heading]=\"formsHeading\"\n [active]=\"overviewTab === 'forms'\"\n (selected)=\"setOverviewTab('forms')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminFormsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"formIssuesLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div\n *ngIf=\"!formIssuesLoading && formIssues && formIssues.length === 0\"\n class=\"text-muted mb-3\"\n >\n {{ 'epistolaAdminNoFormIssues' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!formIssuesLoading && formIssues && formIssues.length > 0\">\n <div class=\"mb-2\">\n <button\n class=\"btn btn-sm btn-primary\"\n (click)=\"repairAllForms()\"\n [disabled]=\"repairingAll\"\n >\n {{\n (repairingAll ? 'epistolaAdminRepairing' : 'epistolaAdminRepairAll')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <span\n *ngIf=\"formFeedback && formFeedback.formId === 'all'\"\n class=\"small ms-2\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >{{ formFeedback.message }}</span\n >\n </div>\n\n <table class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminFormName' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminFormMissing' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let issue of formIssues\">\n <td>\n <code>{{ issue.name }}</code>\n <cds-tag\n *ngIf=\"issue.readOnly\"\n size=\"sm\"\n type=\"warm-gray\"\n class=\"ms-1\"\n [title]=\"'epistolaAdminFormReadOnlyHint' | pluginTranslate: 'epistola' | async\"\n >{{\n 'epistolaAdminFormReadOnly' | pluginTranslate: 'epistola' | async\n }}</cds-tag\n >\n </td>\n <td>{{ issue.missingComponents }}</td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"repairForm(issue)\"\n [disabled]=\"isRepairingForm(issue)\"\n [title]=\"'epistolaAdminRepairTooltip' | pluginTranslate: 'epistola' | async\"\n >\n {{\n (isRepairingForm(issue) ? 'epistolaAdminRepairing' : 'epistolaAdminRepair')\n | pluginTranslate: 'epistola'\n | async\n }}\n </button>\n <div\n *ngIf=\"formFeedback && formFeedback.formId === issue.formId\"\n class=\"small mt-1\"\n [class.text-success]=\"formFeedback.type === 'success'\"\n [class.text-danger]=\"formFeedback.type === 'error'\"\n >\n {{ formFeedback.message }}\n </div>\n </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 <ng-template #catalogsHeading>\n {{ 'epistolaAdminCatalogs' | pluginTranslate: 'epistola' | async }}\n <cds-tag size=\"sm\" type=\"gray\" class=\"ms-1\">{{ catalogs.length }}</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\n <cds-tab\n [heading]=\"catalogsHeading\"\n [active]=\"activeTab === 'catalogs'\"\n (selected)=\"setActiveTab('catalogs')\"\n >\n <p class=\"text-muted mt-3 mb-3\">\n {{ 'epistolaAdminCatalogsIntro' | pluginTranslate: 'epistola' | async }}\n </p>\n\n <div *ngIf=\"catalogsLoading\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminLoading' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <div *ngIf=\"!catalogsLoading && catalogs.length === 0\" class=\"text-muted mb-3\">\n {{ 'epistolaAdminNoCatalogs' | pluginTranslate: 'epistola' | async }}\n </div>\n\n <table *ngIf=\"!catalogsLoading && catalogs.length > 0\" class=\"table table-striped\">\n <thead>\n <tr>\n <th>{{ 'epistolaAdminCatalogSlug' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogVersion' | pluginTranslate: 'epistola' | async }}</th>\n <th>{{ 'epistolaAdminCatalogStatus' | pluginTranslate: 'epistola' | async }}</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let catalog of catalogs\">\n <td>\n <code>{{ catalog.slug }}</code>\n </td>\n <td>{{ catalog.version }}</td>\n <td>\n <cds-tag *ngIf=\"catalog.status === 'IN_EPISTOLA'\" size=\"sm\" type=\"green\">\n {{ 'epistolaAdminCatalogInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'NOT_IN_EPISTOLA'\" size=\"sm\" type=\"red\">\n {{ 'epistolaAdminCatalogNotInEpistola' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n <cds-tag *ngIf=\"catalog.status === 'UNKNOWN'\" size=\"sm\" type=\"gray\">\n {{ 'epistolaAdminCatalogStatusUnknown' | pluginTranslate: 'epistola' | async }}\n </cds-tag>\n </td>\n <td class=\"text-end\">\n <button\n class=\"btn btn-sm btn-outline-primary\"\n (click)=\"redeployCatalog(catalog)\"\n [disabled]=\"isRedeploying(catalog)\"\n [title]=\"'epistolaAdminRedeployTooltip' | pluginTranslate: 'epistola' | async\"\n >\n <span *ngIf=\"!isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploy' | pluginTranslate: 'epistola' | async }}\n </span>\n <span *ngIf=\"isRedeploying(catalog)\">\n {{ 'epistolaAdminRedeploying' | pluginTranslate: 'epistola' | async }}\n </span>\n </button>\n <div\n *ngIf=\"catalogFeedback && catalogFeedback.slug === catalog.slug\"\n class=\"reconcile-feedback small mt-1\"\n [class.text-success]=\"catalogFeedback.type === 'success'\"\n [class.text-danger]=\"catalogFeedback.type === 'error'\"\n >\n {{ catalogFeedback.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}.epistola-admin .epistola-changelog{max-height:60vh;overflow:auto;padding:.5rem 1rem;border:1px solid #dee2e6;border-radius:4px}.epistola-admin .epistola-changelog .changelog-release{padding:.75rem 0}.epistola-admin .epistola-changelog .changelog-release+.changelog-release{border-top:1px solid #eee}.epistola-admin .epistola-changelog .changelog-release__header{display:flex;align-items:center;margin-bottom:.5rem}.epistola-admin .epistola-changelog .changelog-section{margin:.25rem 0 .75rem}.epistola-admin .epistola-changelog .changelog-section__title{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.02em;color:#6f6f6f;margin-bottom:.25rem}.epistola-admin .epistola-changelog .changelog-section__items{margin:0;padding-left:1.25rem;font-size:.8125rem;line-height:1.5}.epistola-admin .epistola-changelog .changelog-section__items li{margin-bottom:.25rem;word-break:break-word}\n"] }]
|
|
3485
3668
|
}], ctorParameters: () => [{ type: EpistolaAdminService }, { type: i2$5.ActivatedRoute }, { type: i2$5.Router }] });
|
|
3486
3669
|
|
|
3487
3670
|
function isRuntimeWindow(value) {
|
|
@@ -3548,12 +3731,59 @@ const EPISTOLA_DOCUMENT_OPTIONS = {
|
|
|
3548
3731
|
icon: 'file-pdf-o',
|
|
3549
3732
|
emptyValue: null,
|
|
3550
3733
|
fieldOptions: ['label', 'display', 'documentVariable', 'tenantIdVariable', 'filename'],
|
|
3734
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
3735
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
3736
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
3551
3737
|
};
|
|
3552
3738
|
function registerEpistolaDocumentComponent(injector) {
|
|
3553
3739
|
if (customElements.get(EPISTOLA_DOCUMENT_OPTIONS.selector)) {
|
|
3554
3740
|
return;
|
|
3555
3741
|
}
|
|
3556
3742
|
registerCustomFormioComponent(EPISTOLA_DOCUMENT_OPTIONS, EpistolaDocumentComponent, injector);
|
|
3743
|
+
// Extend the base class to forward the server-prefilled task id (epistola: value
|
|
3744
|
+
// resolver) to the Angular element, so the download authorizes against the exact task in
|
|
3745
|
+
// every Valtimo task-open flow.
|
|
3746
|
+
const Formio = window.Formio;
|
|
3747
|
+
const BaseComponent = Formio?.Components?.components?.[EPISTOLA_DOCUMENT_OPTIONS.type];
|
|
3748
|
+
if (!BaseComponent) {
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
class EpistolaDocumentWithTaskContext extends BaseComponent {
|
|
3752
|
+
attach(element) {
|
|
3753
|
+
const result = super.attach(element);
|
|
3754
|
+
if (this._customAngularElement) {
|
|
3755
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
3756
|
+
if (prefilledTaskId) {
|
|
3757
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
return result;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
Formio.Components.setComponent(EPISTOLA_DOCUMENT_OPTIONS.type, EpistolaDocumentWithTaskContext);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
/**
|
|
3767
|
+
* Hides a registered custom Formio component from the builder's component palette,
|
|
3768
|
+
* while keeping it fully usable inside other components' `editForm`s and at runtime.
|
|
3769
|
+
*
|
|
3770
|
+
* Formio's `WebformBuilder` only adds a component to the palette when
|
|
3771
|
+
* `component.builderInfo && component.builderInfo.schema` is truthy. Overriding the
|
|
3772
|
+
* registered class's static `builderInfo` getter to `false` therefore removes it from
|
|
3773
|
+
* the palette. Runtime instantiation and editForm usage don't consult `builderInfo`,
|
|
3774
|
+
* so they are unaffected.
|
|
3775
|
+
*
|
|
3776
|
+
* Call this AFTER the component is registered (and after any `setComponent` re-registration),
|
|
3777
|
+
* so it targets the final class in `Formio.Components.components[type]`.
|
|
3778
|
+
*/
|
|
3779
|
+
function hideFormioComponentFromBuilder(type) {
|
|
3780
|
+
const registered = window.Formio?.Components?.components?.[type];
|
|
3781
|
+
if (registered) {
|
|
3782
|
+
Object.defineProperty(registered, 'builderInfo', {
|
|
3783
|
+
get: () => false,
|
|
3784
|
+
configurable: true,
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3557
3787
|
}
|
|
3558
3788
|
|
|
3559
3789
|
const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
@@ -3564,11 +3794,39 @@ const EPISTOLA_RETRY_FORM_OPTIONS = {
|
|
|
3564
3794
|
icon: 'refresh',
|
|
3565
3795
|
emptyValue: null,
|
|
3566
3796
|
fieldOptions: ['sourceActivityId', 'label'], // sourceActivityId is optional (set via BPMN input parameter)
|
|
3797
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
3798
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
3799
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
3567
3800
|
};
|
|
3568
3801
|
function registerEpistolaRetryFormComponent(injector) {
|
|
3569
|
-
if (
|
|
3570
|
-
|
|
3802
|
+
if (customElements.get(EPISTOLA_RETRY_FORM_OPTIONS.selector)) {
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
registerCustomFormioComponent(EPISTOLA_RETRY_FORM_OPTIONS, EpistolaRetryFormComponent, injector);
|
|
3806
|
+
// Extend the base class to forward the server-prefilled task id (epistola: value
|
|
3807
|
+
// resolver) to the Angular element, so the retry form authorizes against the exact task in
|
|
3808
|
+
// every Valtimo task-open flow.
|
|
3809
|
+
const Formio = window.Formio;
|
|
3810
|
+
const BaseComponent = Formio?.Components?.components?.[EPISTOLA_RETRY_FORM_OPTIONS.type];
|
|
3811
|
+
if (!BaseComponent) {
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
class EpistolaRetryFormWithTaskContext extends BaseComponent {
|
|
3815
|
+
attach(element) {
|
|
3816
|
+
const result = super.attach(element);
|
|
3817
|
+
if (this._customAngularElement) {
|
|
3818
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
3819
|
+
if (prefilledTaskId) {
|
|
3820
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
return result;
|
|
3824
|
+
}
|
|
3571
3825
|
}
|
|
3826
|
+
Formio.Components.setComponent(EPISTOLA_RETRY_FORM_OPTIONS.type, EpistolaRetryFormWithTaskContext);
|
|
3827
|
+
// Part of the plugin's auto-deployed retry form, not a drop-anywhere component — hide it
|
|
3828
|
+
// from the builder palette. It still renders wherever it's already present in a form.
|
|
3829
|
+
hideFormioComponentFromBuilder(EPISTOLA_RETRY_FORM_OPTIONS.type);
|
|
3572
3830
|
}
|
|
3573
3831
|
|
|
3574
3832
|
const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
@@ -3579,6 +3837,9 @@ const EPISTOLA_DOCUMENT_PREVIEW_OPTIONS = {
|
|
|
3579
3837
|
icon: 'file-pdf-o',
|
|
3580
3838
|
emptyValue: null,
|
|
3581
3839
|
fieldOptions: ['label', 'processDefinitionKey', 'sourceActivityId', 'overrideMapping'],
|
|
3840
|
+
// Embed the hidden task-id carrier so dropping this component is enough — no separate
|
|
3841
|
+
// field for the author to add. Valtimo prefills it server-side via the epistola: resolver.
|
|
3842
|
+
schema: { components: [PREFILLED_TASK_ID_CARRIER] },
|
|
3582
3843
|
editForm: () => ({
|
|
3583
3844
|
components: [
|
|
3584
3845
|
{
|
|
@@ -3614,7 +3875,15 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3614
3875
|
class PreviewWithOverrides extends BasePreviewComponent {
|
|
3615
3876
|
_debounceTimer = null;
|
|
3616
3877
|
_changeListenerAttached = false;
|
|
3878
|
+
_changeHandler = null;
|
|
3879
|
+
_destroyed = false;
|
|
3617
3880
|
attach(element) {
|
|
3881
|
+
// Formio detaches and re-attaches components on every redraw — not only at
|
|
3882
|
+
// teardown — so a re-attach means the component is alive again. Clear the
|
|
3883
|
+
// destroyed flag here; it only stays set when detach() is the final call
|
|
3884
|
+
// (genuine teardown, e.g. task completion), which is what suppresses the
|
|
3885
|
+
// post-submit preview.
|
|
3886
|
+
this._destroyed = false;
|
|
3618
3887
|
// Bidirectional sync between processLinkSelection object and separate properties.
|
|
3619
3888
|
// The editForm uses processLinkSelection (single field), while the component
|
|
3620
3889
|
// config and Angular inputs use processDefinitionKey + sourceActivityId.
|
|
@@ -3634,32 +3903,61 @@ function registerEpistolaDocumentPreviewComponent(injector) {
|
|
|
3634
3903
|
this._customAngularElement['processDefinitionKey'] =
|
|
3635
3904
|
this.component.processDefinitionKey || '';
|
|
3636
3905
|
this._customAngularElement['sourceActivityId'] = this.component.sourceActivityId || '';
|
|
3906
|
+
// Forward the server-prefilled task id (epistola: value resolver) so the
|
|
3907
|
+
// component authorizes against the exact task in every Valtimo task-open flow.
|
|
3908
|
+
const prefilledTaskId = readPrefilledTaskId(this.root);
|
|
3909
|
+
if (prefilledTaskId) {
|
|
3910
|
+
this._customAngularElement['taskInstanceId'] = prefilledTaskId;
|
|
3911
|
+
}
|
|
3637
3912
|
}
|
|
3638
3913
|
// Listen to form changes and compute input overrides from the mapping
|
|
3639
3914
|
if (this.root && this.component?.overrideMapping && !this._changeListenerAttached) {
|
|
3640
3915
|
this._changeListenerAttached = true;
|
|
3641
|
-
this.
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
//
|
|
3645
|
-
this._computeAndSetOverrides();
|
|
3916
|
+
this._changeHandler = () => this._computeAndSetOverrides();
|
|
3917
|
+
this.root.on('change', this._changeHandler);
|
|
3918
|
+
// Compute the initial overrides immediately (no debounce) so a pre-filled
|
|
3919
|
+
// form paints its preview without the 1.5s delay.
|
|
3920
|
+
this._computeAndSetOverrides(true);
|
|
3646
3921
|
}
|
|
3647
3922
|
return result;
|
|
3648
3923
|
}
|
|
3649
|
-
|
|
3924
|
+
// Tear down the change listener and any pending debounce so a preview is never
|
|
3925
|
+
// fired after the form is unmounted (e.g. on task completion). Without this the
|
|
3926
|
+
// 1.5s debounce can outlive submit and POST /preview with reset/incomplete data,
|
|
3927
|
+
// which Epistola rejects with a 400.
|
|
3928
|
+
detach() {
|
|
3929
|
+
this._destroyed = true;
|
|
3930
|
+
if (this._debounceTimer) {
|
|
3931
|
+
clearTimeout(this._debounceTimer);
|
|
3932
|
+
this._debounceTimer = null;
|
|
3933
|
+
}
|
|
3934
|
+
if (this._changeHandler && this.root?.off) {
|
|
3935
|
+
this.root.off('change', this._changeHandler);
|
|
3936
|
+
this._changeHandler = null;
|
|
3937
|
+
}
|
|
3938
|
+
this._changeListenerAttached = false;
|
|
3939
|
+
return super.detach();
|
|
3940
|
+
}
|
|
3941
|
+
_computeAndSetOverrides(immediate = false) {
|
|
3650
3942
|
if (this._debounceTimer) {
|
|
3651
3943
|
clearTimeout(this._debounceTimer);
|
|
3652
3944
|
}
|
|
3653
3945
|
this._debounceTimer = setTimeout(() => {
|
|
3946
|
+
// Skip if the form is being/has been submitted or the component is gone —
|
|
3947
|
+
// those previews would run with incomplete/reset data and 400 from Epistola.
|
|
3948
|
+
if (this._destroyed || this.root?.submitting || this.root?.submitted) {
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3654
3951
|
const mapping = this.component?.overrideMapping;
|
|
3655
3952
|
const formData = this.root?.data;
|
|
3656
|
-
if (mapping
|
|
3657
|
-
|
|
3658
|
-
if (Object.keys(overrides).length > 0) {
|
|
3659
|
-
this.setValue(overrides);
|
|
3660
|
-
}
|
|
3953
|
+
if (!mapping || !formData) {
|
|
3954
|
+
return;
|
|
3661
3955
|
}
|
|
3662
|
-
|
|
3956
|
+
const overrides = computeInputOverrides(mapping, formData);
|
|
3957
|
+
// Push null when there's nothing usable yet so the component reverts to
|
|
3958
|
+
// its "complete the form" placeholder instead of keeping a stale preview.
|
|
3959
|
+
this.setValue(Object.keys(overrides).length > 0 ? overrides : null);
|
|
3960
|
+
}, immediate ? 0 : 1500);
|
|
3663
3961
|
}
|
|
3664
3962
|
}
|
|
3665
3963
|
// Re-register with the extended class
|
|
@@ -3986,6 +4284,20 @@ function registerEpistolaOverrideBuilderComponent(injector) {
|
|
|
3986
4284
|
}
|
|
3987
4285
|
// Re-register with the extended class
|
|
3988
4286
|
Formio.Components.setComponent(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type, OverrideBuilderWithFields);
|
|
4287
|
+
// Internal editForm widget — not a standalone form field. Hide it from the builder palette.
|
|
4288
|
+
hideFormioComponentFromBuilder(EPISTOLA_OVERRIDE_BUILDER_OPTIONS.type);
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
/**
|
|
4292
|
+
* The plugin action definition key the backend serializes for generate-document
|
|
4293
|
+
* process links. It carries the `epistola-` prefix — see `EPISTOLA_ACTION_KEYS`
|
|
4294
|
+
* in `EpistolaAdminService` on the backend. The selector must match this exact
|
|
4295
|
+
* value; the un-prefixed `generate-document` never appears in the API response.
|
|
4296
|
+
*/
|
|
4297
|
+
const GENERATE_DOCUMENT_ACTION_KEY = 'epistola-generate-document';
|
|
4298
|
+
/** Keeps only the generate-document process links from a usage overview. */
|
|
4299
|
+
function filterGenerateDocumentEntries(entries) {
|
|
4300
|
+
return entries.filter((e) => e.actionKey === GENERATE_DOCUMENT_ACTION_KEY);
|
|
3989
4301
|
}
|
|
3990
4302
|
|
|
3991
4303
|
class EpistolaProcessLinkSelectorComponent {
|
|
@@ -4038,7 +4350,7 @@ class EpistolaProcessLinkSelectorComponent {
|
|
|
4038
4350
|
this.cdr.markForCheck();
|
|
4039
4351
|
this.loadSubscription = this.adminService.getPluginUsage().subscribe({
|
|
4040
4352
|
next: (entries) => {
|
|
4041
|
-
this.filteredEntries = entries
|
|
4353
|
+
this.filteredEntries = filterGenerateDocumentEntries(entries);
|
|
4042
4354
|
this.loading = false;
|
|
4043
4355
|
// Restore selection from value
|
|
4044
4356
|
if (this.value) {
|
|
@@ -4113,6 +4425,8 @@ const EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS = {
|
|
|
4113
4425
|
function registerEpistolaProcessLinkSelectorComponent(injector) {
|
|
4114
4426
|
if (!customElements.get(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.selector)) {
|
|
4115
4427
|
registerCustomFormioComponent(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS, EpistolaProcessLinkSelectorComponent, injector);
|
|
4428
|
+
// Internal editForm widget — not a standalone form field. Hide it from the builder palette.
|
|
4429
|
+
hideFormioComponentFromBuilder(EPISTOLA_PROCESS_LINK_SELECTOR_OPTIONS.type);
|
|
4116
4430
|
}
|
|
4117
4431
|
}
|
|
4118
4432
|
|
|
@@ -4150,11 +4464,6 @@ class EpistolaPluginModule {
|
|
|
4150
4464
|
EpistolaPluginService,
|
|
4151
4465
|
EpistolaAdminService,
|
|
4152
4466
|
EpistolaMenuService,
|
|
4153
|
-
{
|
|
4154
|
-
provide: HTTP_INTERCEPTORS,
|
|
4155
|
-
useClass: EpistolaTaskContextInterceptor,
|
|
4156
|
-
multi: true,
|
|
4157
|
-
},
|
|
4158
4467
|
{
|
|
4159
4468
|
provide: ENVIRONMENT_INITIALIZER,
|
|
4160
4469
|
multi: true,
|
|
@@ -4221,11 +4530,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
4221
4530
|
EpistolaPluginService,
|
|
4222
4531
|
EpistolaAdminService,
|
|
4223
4532
|
EpistolaMenuService,
|
|
4224
|
-
{
|
|
4225
|
-
provide: HTTP_INTERCEPTORS,
|
|
4226
|
-
useClass: EpistolaTaskContextInterceptor,
|
|
4227
|
-
multi: true,
|
|
4228
|
-
},
|
|
4229
4533
|
{
|
|
4230
4534
|
provide: ENVIRONMENT_INITIALIZER,
|
|
4231
4535
|
multi: true,
|
|
@@ -4370,8 +4674,12 @@ const epistolaPluginSpecification = {
|
|
|
4370
4674
|
'epistola-download-document': 'Download Document',
|
|
4371
4675
|
documentVariable: 'Document Variabele',
|
|
4372
4676
|
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.',
|
|
4677
|
+
storageTarget: 'Opslagdoel',
|
|
4678
|
+
storageTargetTooltip: 'Waar het gedownloade PDF wordt opgeslagen. "Tijdelijke resource-opslag" (aanbevolen) zet alleen een resource-id in de variabele — geschikt om door te geven aan documenten-api:store-temp-document. "Procesvariabele" zet de ruwe bytes inline in de variabele (alleen voor kleine, niet-gevoelige documenten; deze worden in de taakrespons meegestuurd).',
|
|
4679
|
+
resourceIdVariable: 'Resource-ID Variabele',
|
|
4680
|
+
resourceIdVariableTooltip: 'Naam van de procesvariabele waarin het tijdelijke resource-id wordt opgeslagen (geef deze door aan documenten-api:store-temp-document).',
|
|
4373
4681
|
contentVariable: 'Inhoud Variabele',
|
|
4374
|
-
contentVariableTooltip: 'Naam van de procesvariabele waarin de
|
|
4682
|
+
contentVariableTooltip: 'Naam van de procesvariabele waarin de ruwe PDF-bytes inline worden opgeslagen (alleen voor kleine, niet-gevoelige documenten).',
|
|
4375
4683
|
// Admin page
|
|
4376
4684
|
epistolaAdminOverview: 'Overzicht',
|
|
4377
4685
|
epistolaAdminRefresh: 'Vernieuwen',
|
|
@@ -4406,6 +4714,10 @@ const epistolaPluginSpecification = {
|
|
|
4406
4714
|
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.',
|
|
4407
4715
|
epistolaAdminValidationCode: 'Code',
|
|
4408
4716
|
epistolaAdminValidationMessage: 'Bericht',
|
|
4717
|
+
epistolaAdminValidationLastChecked: 'Laatst gecontroleerd',
|
|
4718
|
+
epistolaAdminValidationNotYetRun: 'nog niet uitgevoerd',
|
|
4719
|
+
epistolaAdminValidationAutoRefresh: 'automatisch opnieuw gecontroleerd, ongeveer elke',
|
|
4720
|
+
epistolaAdminValidationLatestVersionNote: 'Alleen de meest recente versie van elke procesdefinitie wordt gecontroleerd; oudere versies met nog lopende instanties kunnen problemen hebben die hier niet worden getoond.',
|
|
4409
4721
|
epistolaAdminCatalogs: 'Catalogi',
|
|
4410
4722
|
epistolaAdminCatalogsIntro: 'Catalogi op het classpath van de applicatie. Deze worden bij het opstarten automatisch uitgerold; gebruik Opnieuw uitrollen om een catalogus handmatig (geforceerd) naar deze Epistola-omgeving te sturen, bijvoorbeeld als de automatische uitrol is mislukt.',
|
|
4411
4723
|
epistolaAdminNoCatalogs: 'Geen catalogi gevonden op het classpath.',
|
|
@@ -4421,6 +4733,18 @@ const epistolaPluginSpecification = {
|
|
|
4421
4733
|
epistolaAdminChangelog: 'Changelog',
|
|
4422
4734
|
epistolaAdminRunningVersion: 'Actieve plugin-versie:',
|
|
4423
4735
|
epistolaAdminNoChangelog: 'Geen changelog beschikbaar in deze build.',
|
|
4736
|
+
// TEMPORARY (remove in 1.0.0): task-id carrier repair (admin "Forms" tab)
|
|
4737
|
+
epistolaAdminForms: 'Formulieren',
|
|
4738
|
+
epistolaAdminFormsIntro: 'Formulieren met een Epistola-component dat het verborgen task-id veld mist. Zonder dat veld werkt het voorbeeld/downloaden/opnieuw genereren niet in elke taak-openflow. Herstel voegt het veld toe.',
|
|
4739
|
+
epistolaAdminFormName: 'Formulier',
|
|
4740
|
+
epistolaAdminFormMissing: 'Ontbrekende componenten',
|
|
4741
|
+
epistolaAdminFormReadOnly: 'Alleen-lezen',
|
|
4742
|
+
epistolaAdminFormReadOnlyHint: 'Dit formulier komt van het classpath en wordt bij de volgende herstart teruggezet naar de bron. Voeg het veld toe aan de bron (component opnieuw plaatsen) voor een blijvende oplossing.',
|
|
4743
|
+
epistolaAdminRepair: 'Herstellen',
|
|
4744
|
+
epistolaAdminRepairAll: 'Alles herstellen',
|
|
4745
|
+
epistolaAdminRepairing: 'Bezig...',
|
|
4746
|
+
epistolaAdminRepairTooltip: 'Voeg het verborgen task-id veld toe aan alle Epistola-componenten in dit formulier.',
|
|
4747
|
+
epistolaAdminNoFormIssues: 'Geen formulieren met een ontbrekend task-id veld gevonden.',
|
|
4424
4748
|
},
|
|
4425
4749
|
en: {
|
|
4426
4750
|
title: 'Epistola Document Suite',
|
|
@@ -4526,8 +4850,12 @@ const epistolaPluginSpecification = {
|
|
|
4526
4850
|
'epistola-download-document': 'Download Document',
|
|
4527
4851
|
documentVariable: 'Document Variable',
|
|
4528
4852
|
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.',
|
|
4853
|
+
storageTarget: 'Storage Target',
|
|
4854
|
+
storageTargetTooltip: 'Where the downloaded PDF is stored. "Temporary resource storage" (recommended) writes only a resource id to the variable — ready to hand to documenten-api:store-temp-document. "Process variable" writes the raw bytes inline (small, non-sensitive documents only; they are included in the task response).',
|
|
4855
|
+
resourceIdVariable: 'Resource ID Variable',
|
|
4856
|
+
resourceIdVariableTooltip: 'Name of the process variable to store the temporary resource id in (hand this to documenten-api:store-temp-document).',
|
|
4529
4857
|
contentVariable: 'Content Variable',
|
|
4530
|
-
contentVariableTooltip: 'Name of the process variable to store the
|
|
4858
|
+
contentVariableTooltip: 'Name of the process variable to store the raw PDF bytes inline in (small, non-sensitive documents only).',
|
|
4531
4859
|
// Admin page
|
|
4532
4860
|
epistolaAdminOverview: 'Overview',
|
|
4533
4861
|
epistolaAdminRefresh: 'Refresh',
|
|
@@ -4562,6 +4890,10 @@ const epistolaPluginSpecification = {
|
|
|
4562
4890
|
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.',
|
|
4563
4891
|
epistolaAdminValidationCode: 'Code',
|
|
4564
4892
|
epistolaAdminValidationMessage: 'Message',
|
|
4893
|
+
epistolaAdminValidationLastChecked: 'Last checked',
|
|
4894
|
+
epistolaAdminValidationNotYetRun: 'not yet run',
|
|
4895
|
+
epistolaAdminValidationAutoRefresh: 'automatically re-checked roughly every',
|
|
4896
|
+
epistolaAdminValidationLatestVersionNote: "Only the latest deployed version of each process definition is checked; older versions with running instances may have problems that aren't shown here.",
|
|
4565
4897
|
epistolaAdminCatalogs: 'Catalogs',
|
|
4566
4898
|
epistolaAdminCatalogsIntro: 'Catalogs on the application classpath. These are deployed automatically on startup; use Redeploy to manually (force) push one to this Epistola installation — for example when the automatic startup deploy failed.',
|
|
4567
4899
|
epistolaAdminNoCatalogs: 'No catalogs found on the classpath.',
|
|
@@ -4577,6 +4909,18 @@ const epistolaPluginSpecification = {
|
|
|
4577
4909
|
epistolaAdminChangelog: 'Changelog',
|
|
4578
4910
|
epistolaAdminRunningVersion: 'Running plugin version:',
|
|
4579
4911
|
epistolaAdminNoChangelog: 'No changelog available in this build.',
|
|
4912
|
+
// TEMPORARY (remove in 1.0.0): task-id carrier repair (admin "Forms" tab)
|
|
4913
|
+
epistolaAdminForms: 'Forms',
|
|
4914
|
+
epistolaAdminFormsIntro: 'Forms with an Epistola component that is missing the hidden task-id field. Without it, preview/download/retry do not work in every task-open flow. Repair adds the field.',
|
|
4915
|
+
epistolaAdminFormName: 'Form',
|
|
4916
|
+
epistolaAdminFormMissing: 'Missing components',
|
|
4917
|
+
epistolaAdminFormReadOnly: 'Read-only',
|
|
4918
|
+
epistolaAdminFormReadOnlyHint: 'This form is deployed from the classpath and is reconciled to its source on the next restart. Add the field to the source (re-drop the component) for a permanent fix.',
|
|
4919
|
+
epistolaAdminRepair: 'Repair',
|
|
4920
|
+
epistolaAdminRepairAll: 'Repair all',
|
|
4921
|
+
epistolaAdminRepairing: 'Working...',
|
|
4922
|
+
epistolaAdminRepairTooltip: 'Add the hidden task-id field to all Epistola components in this form.',
|
|
4923
|
+
epistolaAdminNoFormIssues: 'No forms with a missing task-id field found.',
|
|
4580
4924
|
},
|
|
4581
4925
|
},
|
|
4582
4926
|
};
|
|
@@ -4589,5 +4933,5 @@ const epistolaPluginSpecification = {
|
|
|
4589
4933
|
* Generated bundle index. Do not edit.
|
|
4590
4934
|
*/
|
|
4591
4935
|
|
|
4592
|
-
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,
|
|
4936
|
+
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, GenerateDocumentConfigurationComponent, JsonataEditorComponent, MappingBuilderComponent, PREFILLED_TASK_ID_CARRIER, PREFILLED_TASK_ID_DATA_KEY, PREFILLED_TASK_ID_SOURCE_KEY, epistolaPluginSpecification, errorResource, initialResource, isEpistolaEnabled, loadingResource, readPrefilledTaskId, registerEpistolaDocumentComponent, registerEpistolaDocumentPreviewComponent, registerEpistolaOverrideBuilderComponent, registerEpistolaProcessLinkSelectorComponent, registerEpistolaRetryFormComponent, successResource };
|
|
4593
4937
|
//# sourceMappingURL=epistola.app-valtimo-plugin.mjs.map
|