@revenexx/studio-integrations 0.5.0 → 0.6.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/CHANGELOG.md +201 -0
- package/README.md +17 -4
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +15 -14
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +91 -19
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -21
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +4 -8
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -2
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +54 -25
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +12 -12
- package/dist/runtime/components/Integrations/Layout.vue +21 -2
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +10 -11
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +2 -5
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +160 -67
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1075 -303
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +42 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +100 -34
- package/dist/runtime/components/Integrations/QueryBuilder.vue +8 -1
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +3 -1
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +17 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +2 -1
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +109 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +7 -10
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +21 -37
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +23 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +22 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +170 -48
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +22 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +4 -20
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +11 -2
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +189 -46
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +4 -20
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +7 -2
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -2
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +30 -17
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +47 -7
- package/dist/runtime/composables/useCredentialTypes.js +4 -4
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -4
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsNodes.js +4 -4
- package/dist/runtime/composables/useIntegrationsRuns.js +11 -6
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +10 -7
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsTemplates.js +12 -8
- package/dist/runtime/composables/useIntegrationsTriggers.js +6 -6
- package/dist/runtime/composables/useIntegrationsWorkflows.js +8 -4
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationTemplates.d.ts +39 -0
- package/dist/runtime/data/integrationTemplates.js +15 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +27 -3
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -5
- package/dist/runtime/pages/integrations/audit.vue +91 -6
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +6 -4
- package/dist/runtime/pages/integrations/credentials.vue +119 -21
- package/dist/runtime/pages/integrations/index.vue +49 -44
- package/dist/runtime/pages/integrations/runs.vue +1 -1
- package/dist/runtime/pages/integrations/schedules.vue +51 -7
- package/dist/runtime/pages/integrations/secrets.vue +29 -7
- package/dist/runtime/pages/integrations/templates/[slug].vue +52 -30
- package/dist/runtime/pages/integrations/templates/index.vue +57 -86
- package/dist/runtime/pages/integrations/workflows/[id].vue +243 -25
- package/dist/runtime/pages/integrations/workflows/index.vue +15 -2
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +29 -0
- package/dist/runtime/utils/localize.js +4 -0
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +141 -0
- package/dist/runtime/utils/schedule.js +207 -0
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +53 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/package.json +8 -2
|
@@ -11,8 +11,19 @@ export type LocalizedString = string | Record<string, string>;
|
|
|
11
11
|
export type WorkflowVariableType = 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
12
12
|
export interface WorkflowVariable {
|
|
13
13
|
type: WorkflowVariableType;
|
|
14
|
+
/** The value the workflow runs with — the studio labels it "Value". */
|
|
14
15
|
default?: unknown;
|
|
15
16
|
description?: LocalizedString;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated No effect anywhere: the engine resolves `default` regardless,
|
|
19
|
+
* and nothing validates it. Not editable in the studio any more, but kept in
|
|
20
|
+
* the type because existing blobs still carry the field and must round-trip.
|
|
21
|
+
*
|
|
22
|
+
* That round-trip does not rest on the dialog being careful: the save path
|
|
23
|
+
* spreads the stored `variables` object verbatim (`useIntegrationsWorkflows.ts`,
|
|
24
|
+
* no per-field mapping), so nothing in the studio is in a position to drop the
|
|
25
|
+
* field. Change that spread and this guarantee is what you are changing.
|
|
26
|
+
*/
|
|
16
27
|
required?: boolean;
|
|
17
28
|
}
|
|
18
29
|
export type TriggerType = 'manual' | 'schedule' | 'webhook' | 'event';
|
|
@@ -27,10 +38,35 @@ export interface WorkflowTrigger {
|
|
|
27
38
|
timezone?: string;
|
|
28
39
|
path?: string;
|
|
29
40
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
41
|
+
/** @deprecated superseded by authProfile/authConfig — was never enforced */
|
|
30
42
|
secretRef?: string;
|
|
43
|
+
/** Ingress verification profile for the public endpoint. */
|
|
44
|
+
authProfile?: 'none' | 'token' | 'basic' | 'hmac-generic' | 'stripe' | 'standard-webhooks';
|
|
45
|
+
authConfig?: {
|
|
46
|
+
header?: string;
|
|
47
|
+
prefix?: string;
|
|
48
|
+
username?: string;
|
|
49
|
+
algorithm?: string;
|
|
50
|
+
encoding?: string;
|
|
51
|
+
mode?: string;
|
|
52
|
+
};
|
|
53
|
+
public?: boolean;
|
|
54
|
+
/** Server-owned: the provisioned ingress endpoint. */
|
|
55
|
+
inbound?: {
|
|
56
|
+
endpoint_id?: string;
|
|
57
|
+
url?: string;
|
|
58
|
+
};
|
|
31
59
|
event_type?: string;
|
|
60
|
+
payloadFields?: DeclaredPayloadField[];
|
|
32
61
|
};
|
|
33
62
|
}
|
|
63
|
+
/** A payload field the workflow author declares on a manual trigger. */
|
|
64
|
+
export interface DeclaredPayloadField {
|
|
65
|
+
/** Dot-path below `trigger.payload` (e.g. `orderId`, `import.file`). */
|
|
66
|
+
path: string;
|
|
67
|
+
label?: string;
|
|
68
|
+
kind?: 'text' | 'file';
|
|
69
|
+
}
|
|
34
70
|
export type RetryStrategy = 'linear' | 'exponential';
|
|
35
71
|
export interface WorkflowNodeRetry {
|
|
36
72
|
maxAttempts: number;
|
|
@@ -151,6 +187,17 @@ export type WorkflowExecutionMode = 'async_only' | 'sync_only' | 'caller_decides
|
|
|
151
187
|
* bundle is `ready`; the API rejects runs with 409 in any other state.
|
|
152
188
|
*/
|
|
153
189
|
export type WorkflowBuildStatus = 'pending' | 'building' | 'ready' | 'failed';
|
|
190
|
+
/**
|
|
191
|
+
* A non-blocking manifest cross-validation finding returned on the create /
|
|
192
|
+
* update / show responses (never on the *list* response — that path skips the
|
|
193
|
+
* per-node DB resolution). `path` is keyed into the blob (`blob.nodes.N.config.KEY`),
|
|
194
|
+
* matching the `errors` keys of the 422 an active save is rejected with. See
|
|
195
|
+
* `docs/workflow-validation.md` (PO-186 port; behavior: specs/workflow-validation.md).
|
|
196
|
+
*/
|
|
197
|
+
export interface WorkflowWarning {
|
|
198
|
+
path: string;
|
|
199
|
+
message: string;
|
|
200
|
+
}
|
|
154
201
|
export interface Workflow {
|
|
155
202
|
id: number;
|
|
156
203
|
name: string;
|
|
@@ -164,6 +211,13 @@ export interface Workflow {
|
|
|
164
211
|
buildStatus: WorkflowBuildStatus | null;
|
|
165
212
|
/** Human-readable build failure reason, present when `buildStatus === 'failed'`. */
|
|
166
213
|
buildError: string | null;
|
|
214
|
+
/**
|
|
215
|
+
* Non-blocking manifest validation findings for the persisted blob. Present on
|
|
216
|
+
* show/create/update, **absent (undefined) on the list response** — so `undefined`
|
|
217
|
+
* means "unknown", not "valid". Callers must fail open: only gate activation when
|
|
218
|
+
* this is a non-empty array. See [[WorkflowWarning]].
|
|
219
|
+
*/
|
|
220
|
+
warnings?: WorkflowWarning[];
|
|
167
221
|
created_at: string;
|
|
168
222
|
updated_at: string;
|
|
169
223
|
}
|
|
@@ -219,8 +273,7 @@ export interface ConfigField {
|
|
|
219
273
|
* (e.g. 'revenexx:business-central'). An array lets a node accept more than
|
|
220
274
|
* one credential type; the picker merges the tenant's credentials across all. */
|
|
221
275
|
credentialType?: string | string[];
|
|
222
|
-
/** keys of other config fields this field's options/schema depend on.
|
|
223
|
-
* driving `select` fields also form the node's canvas subtitle. */
|
|
276
|
+
/** keys of other config fields this field's options/schema depend on. */
|
|
224
277
|
dependsOn?: string[];
|
|
225
278
|
validation?: {
|
|
226
279
|
pattern?: string;
|
|
@@ -266,6 +319,8 @@ export interface NodeManifest {
|
|
|
266
319
|
category?: string;
|
|
267
320
|
}>;
|
|
268
321
|
input: NodeManifestInput;
|
|
322
|
+
/** Empty marks a terminal node (PO-201): the run ends there, so the canvas
|
|
323
|
+
* draws no output handle and no edge may leave it. */
|
|
269
324
|
outputs: NodeManifestOutput[];
|
|
270
325
|
config?: ConfigField[];
|
|
271
326
|
/** Source package (editor-side enrichment from GET /nodes; not manifest data). */
|
|
@@ -379,7 +434,8 @@ export interface ScheduleEntry {
|
|
|
379
434
|
workflowId: number;
|
|
380
435
|
workflowName: string;
|
|
381
436
|
triggerId: string;
|
|
382
|
-
|
|
437
|
+
/** `null` when the trigger carries no expression at all — never invented. */
|
|
438
|
+
cron: string | null;
|
|
383
439
|
timezone: string;
|
|
384
440
|
active: boolean;
|
|
385
441
|
nextRunAt: string | null;
|
|
@@ -409,7 +465,12 @@ export interface WorkflowTemplate {
|
|
|
409
465
|
name: string;
|
|
410
466
|
label: string | null;
|
|
411
467
|
} | null;
|
|
412
|
-
|
|
468
|
+
/**
|
|
469
|
+
* `''` when the registry published none. The studio does not pick a category
|
|
470
|
+
* on a publisher's behalf: a chip reading "Sync" over a template nobody filed
|
|
471
|
+
* under `sync` is a name invented by the reader's side of the wire.
|
|
472
|
+
*/
|
|
473
|
+
category: TemplateCategory | '';
|
|
413
474
|
industries: Array<'mechanical-engineering' | 'chemicals' | 'wholesale' | 'manufacturing' | 'retail' | 'any'>;
|
|
414
475
|
/** Vendor slugs this template wires together (used to compute prerequisites). */
|
|
415
476
|
vendors: string[];
|
|
@@ -421,8 +482,6 @@ export interface WorkflowTemplate {
|
|
|
421
482
|
outline: string[];
|
|
422
483
|
/** Number of node instances in the canonical blob. */
|
|
423
484
|
nodeCount: number;
|
|
424
|
-
/** Number of installs across the platform (used for "popular" ranking). */
|
|
425
|
-
installs: number;
|
|
426
485
|
/** Author identity. */
|
|
427
486
|
publisher: {
|
|
428
487
|
name: string;
|
|
@@ -15,3 +15,50 @@ export declare class IntegrationsApiError extends Error {
|
|
|
15
15
|
body: unknown;
|
|
16
16
|
constructor(status: number, body: unknown, message: string);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Manifest validation issues parsed out of a 422 rejection body.
|
|
20
|
+
*
|
|
21
|
+
* Saving a workflow with `active: true` re-runs the DB-aware manifest
|
|
22
|
+
* cross-validation as a hard block: on any issue the backend responds `422`
|
|
23
|
+
* with Laravel-style `errors` keyed by the blob path
|
|
24
|
+
* (`{ "blob.nodes.0.config.credentials": ["Required config field ..."] }`).
|
|
25
|
+
* This flattens that map into `{ path, message }[]` — the same shape as the
|
|
26
|
+
* non-blocking `warnings` on a show/update response.
|
|
27
|
+
*
|
|
28
|
+
* Keys are filtered to the blob keyspace only: `blob` itself and any
|
|
29
|
+
* `blob.*` child, but **not** the sibling `blob_definition_version` (a
|
|
30
|
+
* `startsWith('blob')` would wrongly swallow it). A non-blob-keyed 422 (e.g.
|
|
31
|
+
* a duplicate-name validation error) yields `[]`, so callers can tell an
|
|
32
|
+
* activation-blocked 422 apart from an unrelated one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function apiValidationIssues(err: unknown): {
|
|
35
|
+
path: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}[];
|
|
38
|
+
/**
|
|
39
|
+
* Field-keyed validation messages from a rejection, as `{ field: message }`.
|
|
40
|
+
*
|
|
41
|
+
* The counterpart to `apiValidationIssues` above, for the ordinary case that has
|
|
42
|
+
* nothing to do with the workflow blob: a form field the service refused, named the
|
|
43
|
+
* way the form names it. Laravel answers a `422` with `errors` keyed by field and a
|
|
44
|
+
* list of messages per key; the first message is the one a person reads, so that is
|
|
45
|
+
* the one kept.
|
|
46
|
+
*
|
|
47
|
+
* A form uses this to put the refusal where the operator is looking — "The name has
|
|
48
|
+
* already been taken." under the Name field, rather than as a sentence somewhere
|
|
49
|
+
* else about a field they then have to find (PO-275). Keys that address the blob are
|
|
50
|
+
* left out: those belong to `apiValidationIssues`, which resolves them to nodes.
|
|
51
|
+
*/
|
|
52
|
+
export declare function apiFieldErrors(err: unknown): Record<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* The reason a rejection gives, without the count of everything else it found.
|
|
55
|
+
*
|
|
56
|
+
* The service leads with one message and appends how many more there were — "The name
|
|
57
|
+
* has already been taken. (and 2 more errors)". That count is honest arithmetic and
|
|
58
|
+
* misleading copy: it counts by validation rule, so a missing field the service checks
|
|
59
|
+
* twice (`config`, then `config.authKey`) is two of them, while the operator can see
|
|
60
|
+
* one — and it counts things no form draws. A surface that shows every field-keyed
|
|
61
|
+
* message at its field does not need the number at all; what it needs is the reason
|
|
62
|
+
* (PO-275, reported from use).
|
|
63
|
+
*/
|
|
64
|
+
export declare function refusalReason(err: unknown, fallback: string): string;
|
|
@@ -6,3 +6,35 @@ export class IntegrationsApiError extends Error {
|
|
|
6
6
|
this.name = "IntegrationsApiError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
export function apiValidationIssues(err) {
|
|
10
|
+
if (!(err instanceof IntegrationsApiError) || err.status !== 422) return [];
|
|
11
|
+
const errors = err.body?.errors;
|
|
12
|
+
if (!errors || typeof errors !== "object") return [];
|
|
13
|
+
const issues = [];
|
|
14
|
+
for (const [key, val] of Object.entries(errors)) {
|
|
15
|
+
if (key !== "blob" && !key.startsWith("blob.")) continue;
|
|
16
|
+
const messages = Array.isArray(val) ? val : [val];
|
|
17
|
+
for (const m of messages) issues.push({ path: key, message: String(m) });
|
|
18
|
+
}
|
|
19
|
+
return issues;
|
|
20
|
+
}
|
|
21
|
+
export function apiFieldErrors(err) {
|
|
22
|
+
if (!(err instanceof IntegrationsApiError) || err.status !== 422) return {};
|
|
23
|
+
const errors = err.body?.errors;
|
|
24
|
+
if (!errors || typeof errors !== "object") return {};
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [key, val] of Object.entries(errors)) {
|
|
27
|
+
if (key === "blob" || key.startsWith("blob.")) continue;
|
|
28
|
+
const first = Array.isArray(val) ? val[0] : val;
|
|
29
|
+
if (first !== void 0 && first !== null) out[key] = String(first);
|
|
30
|
+
}
|
|
31
|
+
for (const key of Object.keys(out)) {
|
|
32
|
+
if (Object.keys(out).some((other) => other.startsWith(`${key}.`))) delete out[key];
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
export function refusalReason(err, fallback) {
|
|
37
|
+
const raw = err?.message?.trim();
|
|
38
|
+
if (!raw) return fallback;
|
|
39
|
+
return raw.replace(/\s*\(and \d+ more errors?\)\s*$/, "").trim() || fallback;
|
|
40
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the module expects a **cockpit** host to provide, and how it asks.
|
|
3
|
+
*
|
|
4
|
+
* The three managed credential types (`revenexx:sftp`, `revenexx:smtp`,
|
|
5
|
+
* `revenexx:api`) offer a "Use revenexx …" alternative to typing access data by
|
|
6
|
+
* hand: the operator picks an SFTP account, a mail credential or a fresh API key
|
|
7
|
+
* that already exists elsewhere in the cockpit, and the studio copies its access
|
|
8
|
+
* data into the credential. That is cockpit-coupled by design.
|
|
9
|
+
*
|
|
10
|
+
* The coupling is five composables the cockpit auto-imports. They are not part of
|
|
11
|
+
* this module and there is no import to write for them — in the cockpit they are
|
|
12
|
+
* simply in scope, and in any other host they are not there at all. Referencing
|
|
13
|
+
* one where it does not exist throws a `ReferenceError`, and thrown from a setup
|
|
14
|
+
* it takes the whole surrounding form down: before PO-222 that left both credential
|
|
15
|
+
* dialogs with no fields at all for those three types, so a credential of one could
|
|
16
|
+
* neither be created nor edited outside the cockpit — including in this
|
|
17
|
+
* repository's own documented dev loop, which serves exactly those three.
|
|
18
|
+
*
|
|
19
|
+
* **`typeof` on a bare identifier is the only safe way to ask.** It is the one
|
|
20
|
+
* expression that does not throw on a name that was never declared. It is
|
|
21
|
+
* deliberately not `globalThis.useConsoleTenants`: an auto-import is injected at
|
|
22
|
+
* build time as a module binding, not hung off the global object, so a
|
|
23
|
+
* `globalThis` lookup answers "absent" **in the cockpit too** — which would
|
|
24
|
+
* silently switch the feature off in the one host it exists for. That is the
|
|
25
|
+
* failure this file is most at risk of, so it is written down rather than left to
|
|
26
|
+
* be rediscovered.
|
|
27
|
+
*
|
|
28
|
+
* **The ambient declarations below must stay `declare const`, never `declare
|
|
29
|
+
* function`.** They are the same failure arriving by a second door: unimport
|
|
30
|
+
* decides what to inject by scanning the source for identifiers that are not
|
|
31
|
+
* already declared in it, and one of its exclusion patterns is
|
|
32
|
+
* `/\bfunction\s*([\w$]+)\s*\(/` — so a `declare function useConsoleSftp()` reads
|
|
33
|
+
* as "this file already provides it" and the injection is skipped. In the cockpit
|
|
34
|
+
* the name is then genuinely absent, `typeof` answers `'undefined'`, and the panel
|
|
35
|
+
* disables itself in the one host it works in. `declare const useConsoleSftp:
|
|
36
|
+
* (() => …) | undefined` is not excluded, because the pattern for a variable
|
|
37
|
+
* captures up to `=`, `;` or the line end and the captured text (`useConsoleSftp:
|
|
38
|
+
* (() => …) | undefined`) is not the bare identifier.
|
|
39
|
+
*
|
|
40
|
+
* That is measured, not read off the regex: unimport's own `injectImports`, run
|
|
41
|
+
* over this file, injects all five — and over the same file with the declarations
|
|
42
|
+
* rewritten as `declare function`, injects nothing. Repeat it that way if the
|
|
43
|
+
* spelling ever has to change. Nothing stands guard over it automatically, because
|
|
44
|
+
* a test would mean this repository taking a dependency on a build tool it does
|
|
45
|
+
* not otherwise use, to assert that tool's behaviour rather than its own.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* The shapes this module uses.
|
|
49
|
+
*
|
|
50
|
+
* Narrowed to the members it reads — but narrowed against the cockpit's own
|
|
51
|
+
* source (`app/composables/*.ts`, `app/types/consoleSftp.ts`), not guessed. A
|
|
52
|
+
* declaration that omits a member the code below reads is not a typing nit: it is
|
|
53
|
+
* this file claiming a contract the caller then violates, and nothing in CI
|
|
54
|
+
* typechecks these files today, so the claim has to be right by construction.
|
|
55
|
+
*
|
|
56
|
+
* Two members exist here only because the code reads them off values the cockpit
|
|
57
|
+
* may hand back as `null`: `getCredential` and `createCredential` both resolve to
|
|
58
|
+
* `MailCredential | null`, and the callers have to say what they do about it.
|
|
59
|
+
*/
|
|
60
|
+
export interface CockpitSources {
|
|
61
|
+
tenants: {
|
|
62
|
+
/** `ConsoleTenant[]` — the picker renders `name` and keys by `slug`. */
|
|
63
|
+
tenants: {
|
|
64
|
+
value: {
|
|
65
|
+
slug: string;
|
|
66
|
+
name: string;
|
|
67
|
+
}[];
|
|
68
|
+
};
|
|
69
|
+
onlyTenantSlug: {
|
|
70
|
+
value: string | null;
|
|
71
|
+
};
|
|
72
|
+
load: () => Promise<unknown>;
|
|
73
|
+
};
|
|
74
|
+
market: {
|
|
75
|
+
markets: {
|
|
76
|
+
value: {
|
|
77
|
+
code: string;
|
|
78
|
+
name: string;
|
|
79
|
+
}[];
|
|
80
|
+
};
|
|
81
|
+
activeCode: {
|
|
82
|
+
value: string;
|
|
83
|
+
};
|
|
84
|
+
GLOBAL_MARKET_CODE: string;
|
|
85
|
+
fetchMarkets: () => Promise<unknown>;
|
|
86
|
+
};
|
|
87
|
+
sftp: {
|
|
88
|
+
/**
|
|
89
|
+
* `ConsoleSftpAccount[]`. Provisioning is asynchronous, so a row carries both
|
|
90
|
+
* the flag and the state machine — `create()` hands back a `pending` row and
|
|
91
|
+
* the caller polls `refresh()` until one of them says active.
|
|
92
|
+
*/
|
|
93
|
+
accounts: {
|
|
94
|
+
value: CockpitSftpAccount[];
|
|
95
|
+
};
|
|
96
|
+
load: (tenant: string | null) => Promise<unknown>;
|
|
97
|
+
/** `null` while the account is still provisioning (the Console answers 404). */
|
|
98
|
+
revealPassword: (tenant: string, id: number) => Promise<{
|
|
99
|
+
username: string;
|
|
100
|
+
password: string;
|
|
101
|
+
server?: string;
|
|
102
|
+
} | null>;
|
|
103
|
+
create: (tenant: string, name?: string) => Promise<CockpitSftpAccount>;
|
|
104
|
+
refresh: (tenant: string, id: number) => Promise<CockpitSftpAccount>;
|
|
105
|
+
};
|
|
106
|
+
mail: {
|
|
107
|
+
listCredentials: () => Promise<CockpitMailCredential[]>;
|
|
108
|
+
getCredential: (uuid: string) => Promise<CockpitMailCredential | null>;
|
|
109
|
+
createCredential: (input: {
|
|
110
|
+
name: string;
|
|
111
|
+
type: 'SMTP' | 'API';
|
|
112
|
+
}) => Promise<CockpitMailCredential | null>;
|
|
113
|
+
};
|
|
114
|
+
keys: {
|
|
115
|
+
create: (name: string, scopes: string[]) => Promise<{
|
|
116
|
+
secret: string;
|
|
117
|
+
}>;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** The members of the cockpit's `ConsoleSftpAccount` this module reads. */
|
|
121
|
+
export interface CockpitSftpAccount {
|
|
122
|
+
id: number;
|
|
123
|
+
/** The gateway login. `null` until the account finished provisioning. */
|
|
124
|
+
login_username: string | null;
|
|
125
|
+
status: string;
|
|
126
|
+
is_active: boolean;
|
|
127
|
+
error_message: string | null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The members of the cockpit's `MailCredential` this module reads.
|
|
131
|
+
*
|
|
132
|
+
* The SMTP pair is optional on the wire: the mail engine returns it on `show` and
|
|
133
|
+
* on create, but the type does not promise it, which is why every read of it here
|
|
134
|
+
* falls back to an empty string rather than assuming.
|
|
135
|
+
*/
|
|
136
|
+
export interface CockpitMailCredential {
|
|
137
|
+
uuid: string;
|
|
138
|
+
name: string;
|
|
139
|
+
type?: string;
|
|
140
|
+
smtp_username?: string;
|
|
141
|
+
smtp_password?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The cockpit's sources, or `null` in a host that does not have them.
|
|
145
|
+
*
|
|
146
|
+
* All five or none: a host with three of them is not a cockpit, and half a picker
|
|
147
|
+
* is worse than none — the operator would meet a control that lists nothing and
|
|
148
|
+
* says nothing about why. `null` is what the caller turns into "the managed
|
|
149
|
+
* sources are unavailable here", with the manual fields left in place.
|
|
150
|
+
*
|
|
151
|
+
* Must be called from a component's setup, like the composables it calls.
|
|
152
|
+
*/
|
|
153
|
+
export declare function cockpitSources(): CockpitSources | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function cockpitSources() {
|
|
2
|
+
if (typeof useConsoleTenants !== "function") return null;
|
|
3
|
+
if (typeof useActiveMarket !== "function") return null;
|
|
4
|
+
if (typeof useConsoleSftp !== "function") return null;
|
|
5
|
+
if (typeof useMailAdmin !== "function") return null;
|
|
6
|
+
if (typeof useConsoleApiKeys !== "function") return null;
|
|
7
|
+
return {
|
|
8
|
+
tenants: useConsoleTenants(),
|
|
9
|
+
market: useActiveMarket(),
|
|
10
|
+
sftp: useConsoleSftp(),
|
|
11
|
+
mail: useMailAdmin(),
|
|
12
|
+
keys: useConsoleApiKeys()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* The load guard of every composable in the studio, in one place: who waits, who
|
|
4
|
+
* sends a request of its own, and the rule that keeps **one** load in flight.
|
|
5
|
+
*
|
|
6
|
+
* `whenIdle` next door only answers "has the load finished". What a caller does
|
|
7
|
+
* *after* waiting is the part every hand-written guard got wrong the same way, in
|
|
8
|
+
* both directions:
|
|
9
|
+
*
|
|
10
|
+
* - Returning the state **as it stands** handed a cold caller an empty list, which
|
|
11
|
+
* it then fanned out over and cached as an answer (PO-219, PO-230).
|
|
12
|
+
* - Waiting and then always sending its own request lost the other half: two forced
|
|
13
|
+
* callers woken by the same load each sent one, so `loading` went down with a
|
|
14
|
+
* request still out. The flag stopped meaning "a load is out" — and `runs.vue`'s
|
|
15
|
+
* poll reads exactly that flag to decide whether to shed a tick (found in review
|
|
16
|
+
* on PO-230, reproduced with three discards in quick succession).
|
|
17
|
+
*
|
|
18
|
+
* So the gate counts loads rather than trusting a boolean. A caller that waited is
|
|
19
|
+
* satisfied by the load it waited for **unless** it asked for a fresh read, and a
|
|
20
|
+
* forced caller is satisfied only by a load that *began after it asked* — which is
|
|
21
|
+
* what "Try again", a replay and a discard all mean. Everything else waits again,
|
|
22
|
+
* so at most one request is ever out and at most one extra one is ever sent, no
|
|
23
|
+
* matter how many callers arrive during the same load.
|
|
24
|
+
*
|
|
25
|
+
* Two properties worth relying on:
|
|
26
|
+
*
|
|
27
|
+
* - **`loading` is true exactly while a load is out.** The gate is its only writer;
|
|
28
|
+
* a caller that sets it itself breaks the same invariant again.
|
|
29
|
+
* - **A forced caller waits for at most two loads** — the one in flight, then the
|
|
30
|
+
* one that answers it — rather than chaining behind every later arrival.
|
|
31
|
+
*
|
|
32
|
+
* The one thing it cannot do is decide what a waiting caller *returns*: that is the
|
|
33
|
+
* composable's own state, and its shape differs (a list, nothing, a page appended).
|
|
34
|
+
* `claim()` says whether to load; the caller says what to answer with.
|
|
35
|
+
*/
|
|
36
|
+
export interface LoadGateState {
|
|
37
|
+
/** True exactly while a load is out. Written by the gate alone. */
|
|
38
|
+
loading: Ref<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Set by the composable once a load has actually read something. Left out where
|
|
41
|
+
* nothing is cached — the audit feed is paged by cursor, so every caller either
|
|
42
|
+
* opens it or asks for the next page.
|
|
43
|
+
*/
|
|
44
|
+
loaded?: Ref<boolean>;
|
|
45
|
+
/** Loads begun. */
|
|
46
|
+
started: Ref<number>;
|
|
47
|
+
/** Loads finished. Together with `started` this is what tells a forced caller
|
|
48
|
+
* whether the answer it waited for was read after it asked. */
|
|
49
|
+
finished: Ref<number>;
|
|
50
|
+
}
|
|
51
|
+
export interface LoadGate {
|
|
52
|
+
/**
|
|
53
|
+
* Resolves to `true` when the caller must run the load itself — in which case
|
|
54
|
+
* `loading` is already up and `release()` is owed in a `finally`. `false` means
|
|
55
|
+
* the state now holds an answer the caller can return.
|
|
56
|
+
*/
|
|
57
|
+
claim: (force?: boolean) => Promise<boolean>;
|
|
58
|
+
/** The load is done: the flag goes down and the load is counted. */
|
|
59
|
+
release: () => void;
|
|
60
|
+
}
|
|
61
|
+
export declare function createLoadGate(state: LoadGateState): LoadGate;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { whenIdle } from "./whenIdle.js";
|
|
2
|
+
export function createLoadGate(state) {
|
|
3
|
+
const { loading, loaded, started, finished } = state;
|
|
4
|
+
return {
|
|
5
|
+
async claim(force = false) {
|
|
6
|
+
const askedAt = started.value;
|
|
7
|
+
while (loading.value) {
|
|
8
|
+
await whenIdle(loading);
|
|
9
|
+
if (!force) return false;
|
|
10
|
+
if (finished.value > askedAt) return false;
|
|
11
|
+
}
|
|
12
|
+
if (loaded?.value && !force) return false;
|
|
13
|
+
started.value++;
|
|
14
|
+
loading.value = true;
|
|
15
|
+
return true;
|
|
16
|
+
},
|
|
17
|
+
release() {
|
|
18
|
+
loading.value = false;
|
|
19
|
+
finished.value++;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -7,5 +7,34 @@
|
|
|
7
7
|
* Priority: 1) "en", 2) first key found. English is the cockpit default —
|
|
8
8
|
* there is no working language switcher yet; browser-locale preference
|
|
9
9
|
* returns together with the switcher.
|
|
10
|
+
*
|
|
11
|
+
* **This is the studio's only resolver, and that is the point.** The same three
|
|
12
|
+
* lines used to sit in seventeen components under six names (`labelText`, `loc`,
|
|
13
|
+
* `locText`, `localLabel`, `title`, `descriptionText`), all agreeing with this
|
|
14
|
+
* function by coincidence. A language switcher wired into this one place would
|
|
15
|
+
* have translated a fraction of the surface with nothing going red — so PO-229
|
|
16
|
+
* collapsed them here before building one. Anything that renders a name, a
|
|
17
|
+
* label, a description or a placeholder from the API calls this; nothing
|
|
18
|
+
* re-derives the rule.
|
|
19
|
+
*
|
|
20
|
+
* Two of the seventeen were named for their call site rather than for the rule
|
|
21
|
+
* (`WorkflowTriggerView`'s `title`, `WorkflowVariablesDialog`'s
|
|
22
|
+
* `descriptionText`), so no grep for the rule's shape found them. Both resolve
|
|
23
|
+
* text a person typed and went to `resolveDisplayName` below, not here.
|
|
10
24
|
*/
|
|
11
25
|
export declare function resolveLocalized(value: unknown): string;
|
|
26
|
+
/**
|
|
27
|
+
* The same resolution, for a value that may be **text a person wrote** — a
|
|
28
|
+
* node's own name, a group frame's label.
|
|
29
|
+
*
|
|
30
|
+
* `resolveLocalized` normalises: it trims, and it reads a leading `{` as JSON.
|
|
31
|
+
* That is right for a manifest string and wrong for a user's own, and it has
|
|
32
|
+
* already cost once (PO-204): run through the trimming resolver, the node-name
|
|
33
|
+
* field ate the space the user had just typed, so "Fetch open orders" came back
|
|
34
|
+
* as "Fetchopenorders". A name like `{"a":"b"}` would render as `b`.
|
|
35
|
+
*
|
|
36
|
+
* So a string is passed through untouched and only the localised-object form is
|
|
37
|
+
* resolved. Use this wherever the value can be something a person typed; use
|
|
38
|
+
* `resolveLocalized` for anything that can only have come from a manifest.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveDisplayName(value: string | Record<string, string> | undefined | null): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A proxy rather than a hand-written object, so the whole library stays reachable:
|
|
3
|
+
* `toast.dismiss`, `toast.promise`, `toast.custom` and whatever it gains next are
|
|
4
|
+
* forwarded and guarded on access. Listing the methods by hand would have turned the
|
|
5
|
+
* first call to an unlisted one into "undefined is not a function" — a second defect
|
|
6
|
+
* of the same shape as the one this file exists for.
|
|
7
|
+
*/
|
|
8
|
+
export declare const toast: ((message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number) & {
|
|
9
|
+
success: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
10
|
+
info: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
11
|
+
warning: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
12
|
+
error: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
13
|
+
custom: (component: import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
14
|
+
message: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
15
|
+
promise: <ToastData>(promise: Promise<ToastData> | (() => Promise<ToastData>), data?: ({
|
|
16
|
+
action?: (import("vue-sonner").Action | import("vue").Component) | undefined;
|
|
17
|
+
icon?: import("vue").Component | undefined;
|
|
18
|
+
class?: string | undefined;
|
|
19
|
+
id?: number | string | undefined;
|
|
20
|
+
position?: ("top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center") | undefined;
|
|
21
|
+
cancel?: (import("vue-sonner").Action | import("vue").Component) | undefined;
|
|
22
|
+
toasterId?: string | undefined;
|
|
23
|
+
component?: import("vue").Component | undefined;
|
|
24
|
+
componentProps?: any;
|
|
25
|
+
richColors?: boolean | undefined;
|
|
26
|
+
invert?: boolean | undefined;
|
|
27
|
+
closeButton?: boolean | undefined;
|
|
28
|
+
dismissible?: boolean | undefined;
|
|
29
|
+
duration?: number | undefined;
|
|
30
|
+
important?: boolean | undefined;
|
|
31
|
+
onDismiss?: ((toast: import("vue-sonner").ToastT) => void) | undefined;
|
|
32
|
+
onAutoClose?: ((toast: import("vue-sonner").ToastT) => void) | undefined;
|
|
33
|
+
cancelButtonStyle?: import("vue").CSSProperties | undefined;
|
|
34
|
+
actionButtonStyle?: import("vue").CSSProperties | undefined;
|
|
35
|
+
style?: import("vue").CSSProperties | undefined;
|
|
36
|
+
unstyled?: boolean | undefined;
|
|
37
|
+
classes?: import("vue-sonner").ToastClasses | undefined;
|
|
38
|
+
descriptionClass?: string | undefined;
|
|
39
|
+
closeButtonPosition?: ("top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
|
|
40
|
+
testId?: string | undefined;
|
|
41
|
+
} & {
|
|
42
|
+
loading?: string | import("vue").Component;
|
|
43
|
+
success?: (string | import("vue").Component | ((data: ToastData) => import("vue").Component | string | Promise<import("vue").Component | string>)) | (import("vue-sonner").PromiseIExtendedResult | ((data: ToastData) => import("vue-sonner").PromiseIExtendedResult | Promise<import("vue-sonner").PromiseIExtendedResult>)) | undefined;
|
|
44
|
+
error?: (string | import("vue").Component | ((data: any) => import("vue").Component | string | Promise<import("vue").Component | string>)) | (import("vue-sonner").PromiseIExtendedResult | ((data: any) => import("vue-sonner").PromiseIExtendedResult | Promise<import("vue-sonner").PromiseIExtendedResult>));
|
|
45
|
+
description?: string | import("vue").Component | ((data: any) => import("vue").Component | string | Promise<import("vue").Component | string>);
|
|
46
|
+
finally?: () => void | Promise<void>;
|
|
47
|
+
}) | undefined) => (string & {
|
|
48
|
+
unwrap: () => Promise<ToastData>;
|
|
49
|
+
}) | (number & {
|
|
50
|
+
unwrap: () => Promise<ToastData>;
|
|
51
|
+
}) | {
|
|
52
|
+
unwrap: () => Promise<ToastData>;
|
|
53
|
+
} | undefined;
|
|
54
|
+
dismiss: (id?: number | string) => string | number | undefined;
|
|
55
|
+
loading: (message: string | (() => string | import("vue").Component) | import("vue").Component, data?: import("vue-sonner").ExternalToast) => string | number;
|
|
56
|
+
} & {
|
|
57
|
+
getHistory: () => (import("vue-sonner").ToastT<import("vue").Component> | import("vue-sonner").ToastToDismiss)[];
|
|
58
|
+
getToasts: () => (import("vue-sonner").ToastT<import("vue").Component> | import("vue-sonner").ToastToDismiss)[];
|
|
59
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { toast as sonner } from "vue-sonner";
|
|
2
|
+
function guarded(kind, fn) {
|
|
3
|
+
return (...args) => {
|
|
4
|
+
try {
|
|
5
|
+
return fn(...args);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
console.warn(`[studio-integrations] notification dropped (${kind}): ${String(args[0])}`, e);
|
|
8
|
+
return void 0;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export const toast = new Proxy(sonner, {
|
|
13
|
+
apply(target, thisArg, args) {
|
|
14
|
+
return guarded("message", (...a) => Reflect.apply(target, thisArg, a))(...args);
|
|
15
|
+
},
|
|
16
|
+
get(target, prop) {
|
|
17
|
+
const value = Reflect.get(target, prop);
|
|
18
|
+
if (typeof value !== "function") return value;
|
|
19
|
+
return guarded(String(prop), (...args) => value.apply(target, args));
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which credential types offer the "Use revenexx …" source alternative, and
|
|
3
|
+
* under which kind.
|
|
4
|
+
*
|
|
5
|
+
* This lives apart from the component that renders it because its *parents* need
|
|
6
|
+
* it too: the detail dialog mounts that component only for the types it actually
|
|
7
|
+
* serves, since for every other type it renders nothing anyway.
|
|
8
|
+
*
|
|
9
|
+
* It was once load-bearing for a different reason — the component resolved
|
|
10
|
+
* cockpit-only composables at setup time, so mounting it outside the cockpit
|
|
11
|
+
* threw and took the surrounding form down. That is fixed (PO-222): the component
|
|
12
|
+
* asks whether those sources exist. So this guard is now tidiness rather than
|
|
13
|
+
* protection, which is also why the create dialog never having one stopped
|
|
14
|
+
* mattering.
|
|
15
|
+
*/
|
|
16
|
+
export type RevenexxSourceKind = 'sftp' | 'smtp' | 'api';
|
|
17
|
+
export declare const REVENEXX_SOURCE_KIND_BY_SLUG: Record<string, RevenexxSourceKind>;
|
|
18
|
+
/**
|
|
19
|
+
* Whether this credential type has a managed revenexx counterpart to copy from.
|
|
20
|
+
*
|
|
21
|
+
* Own properties only: `in` would answer yes for everything on the prototype
|
|
22
|
+
* chain, so a type slugged `toString` or `constructor` would pass the guard and
|
|
23
|
+
* then resolve to a function instead of a kind — and this guard is what decides
|
|
24
|
+
* whether the cockpit-coupled component mounts at all.
|
|
25
|
+
*/
|
|
26
|
+
export declare function hasRevenexxSource(slug: string | null | undefined): boolean;
|