@revenexx/studio-integrations 3.2.0 → 3.3.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.
@@ -282,7 +282,10 @@ export interface NodeManifestOutput {
282
282
  }
283
283
  export type ConfigFieldType = 'string' | 'number' | 'boolean' | 'select' | 'multiselect' | 'object' | 'array' | 'expression' | 'secret-ref' | 'credentials-ref'
284
284
  /** Names one of the workflow's declared state namespaces (PO-374). */
285
- | 'state-ref' | 'dynamic-schema';
285
+ | 'state-ref'
286
+ /** A route of one of the tenant's VPN connections, or the public internet
287
+ * (PO-831). Stored as `VpnRouteValue | null`. */
288
+ | 'vpn-route' | 'dynamic-schema';
286
289
  export interface ConfigFieldOption {
287
290
  value: string | number | boolean;
288
291
  label: LocalizedString;
@@ -335,6 +338,11 @@ export interface ConfigField {
335
338
  * the stored value is the namespace NAME.
336
339
  */
337
340
  stateRole?: WorkflowStateRole;
341
+ /**
342
+ * Only for `type: 'vpn-route'` (PO-831): which kind of route the picker offers.
343
+ * `http` where the node declares nothing, as the SDK defaults it.
344
+ */
345
+ routeKind?: VpnRouteKind;
338
346
  /** keys of other config fields this field's options/schema depend on. */
339
347
  dependsOn?: string[];
340
348
  /** When this setting applies at all (PO-410): another setting's key, an
@@ -471,6 +479,8 @@ export interface CredentialTypeField {
471
479
  type: 'string' | 'number' | 'boolean' | 'select' | 'secret';
472
480
  label: LocalizedString;
473
481
  required?: boolean;
482
+ /** What the system uses when the operator changes nothing; a new credential's form starts from it (IN-38). */
483
+ default?: string | number | boolean;
474
484
  options?: Array<{
475
485
  label: LocalizedString;
476
486
  value: string | number | boolean;
@@ -503,6 +513,27 @@ export interface CredentialType {
503
513
  images?: CredentialTypeImage[];
504
514
  auth_kind: AuthKind;
505
515
  fields: CredentialTypeField[];
516
+ /**
517
+ * Which of the type's fields a VPN route stands in for (PO-829, the SDK's
518
+ * `ICredentialDescription.network`). Absent: the type cannot be routed through a
519
+ * VPN, and the credential forms draw no Network section for it.
520
+ */
521
+ network?: CredentialNetworkDeclaration | null;
522
+ }
523
+ /** What a credential type declares a VPN route replaces. */
524
+ export type CredentialNetworkDeclaration = {
525
+ kind: 'tcp';
526
+ hostField: string;
527
+ portField: string;
528
+ } | {
529
+ kind: 'http';
530
+ urlField: string;
531
+ };
532
+ /** How a stored credential reaches its system, when not over the public internet. */
533
+ export interface CredentialNetwork {
534
+ kind: 'vpn';
535
+ connection: string;
536
+ route: string;
506
537
  }
507
538
  export type CredentialStatus = 'active' | 'needs_reauth';
508
539
  /** A tenant's credential instance (secret fields never returned). */
@@ -517,9 +548,35 @@ export interface Credential {
517
548
  last_test_at?: string | null;
518
549
  last_test_ok?: boolean | null;
519
550
  last_test_message?: string | null;
551
+ /** The VPN route this credential is reached through; `null` or absent = public internet. */
552
+ network?: CredentialNetwork | null;
520
553
  created_at: string;
521
554
  updated_at: string;
522
555
  }
556
+ export type VpnRouteKind = 'http' | 'tcp';
557
+ /** Console's derived status of a connection; only `active` carries traffic. */
558
+ export type VpnConnectionStatus = 'provisioning' | 'failed' | 'active' | 'down' | 'standby' | 'disabled' | 'unknown';
559
+ export interface VpnRoute {
560
+ name: string;
561
+ kind: VpnRouteKind;
562
+ /** The target inside the tenant's network. */
563
+ host: string;
564
+ port: number;
565
+ proto?: string | null;
566
+ /** Where the gateway serves the route (http: a URL, tcp: `ip:port`). */
567
+ endpoint?: string | null;
568
+ }
569
+ export interface VpnConnection {
570
+ name: string;
571
+ display_name?: string | null;
572
+ status: VpnConnectionStatus | string;
573
+ routes: VpnRoute[];
574
+ }
575
+ /** What a `vpn-route` setting stores, and what a credential's `network` names. */
576
+ export interface VpnRouteValue {
577
+ connection: string;
578
+ route: string;
579
+ }
523
580
  export type ConnectionStatus = 'connected' | 'misconfigured' | 'unverified' | 'failing';
524
581
  export interface ConnectionTemplate {
525
582
  /** kebab-case slug (e.g. 'shopify'). */
@@ -9,8 +9,11 @@ import type { ConfigField } from '../types/integrations.js';
9
9
  * applied never gets to apply it. An absent key is the absence the node's
10
10
  * default answers to.
11
11
  *
12
- * Used in the two places a fresh set of values is made: a node dropped on the
13
- * canvas (WorkflowEditor), and a row added to a list setting (NodeConfigField).
12
+ * Used wherever a fresh set of values is made: a node dropped on the canvas
13
+ * (WorkflowEditor), a row added to a list setting (NodeConfigField), and a
14
+ * credential's form — a new one in both create dialogs, and a stored one's
15
+ * missing fields in the detail view (IN-38), which also compares an edit against
16
+ * it. A change here changes all of them.
14
17
  * Flat on purpose — it reads `default` at the level it is given and does not
15
18
  * descend into `properties` or `items`, so what a newly inserted node carries
16
19
  * is exactly what it carried before this became shared.
@@ -0,0 +1,69 @@
1
+ import type { CredentialNetwork, CredentialNetworkDeclaration, CredentialType, VpnConnection, VpnRoute, VpnRouteKind, VpnRouteValue } from '../types/integrations.js';
2
+ /**
3
+ * What the studio makes of a VPN route reference — a credential's `network`, or the
4
+ * value of a `vpn-route` setting — against the tenant's connections
5
+ * (specs/credential-network.md). Pure, so the surfaces that draw it agree.
6
+ */
7
+ /** The name a connection is shown under: its display name, else its own. */
8
+ export declare function connectionLabel(c: Pick<VpnConnection, 'name' | 'display_name'>): string;
9
+ /** The routes of a connection a picker filtered to `kind` offers. */
10
+ export declare function routesOfKind(c: VpnConnection | undefined, kind: VpnRouteKind): VpnRoute[];
11
+ /** Whether a stored value names a route at all, as opposed to the public internet. */
12
+ export declare function isVpnRouteValue(v: unknown): v is VpnRouteValue;
13
+ export type VpnReference =
14
+ /** The route exists; `active` says whether its connection carries traffic now. */
15
+ {
16
+ state: 'ok';
17
+ connection: VpnConnection;
18
+ route: VpnRoute;
19
+ active: boolean;
20
+ }
21
+ /** The connection or the route is gone — a broken reference. */
22
+ | {
23
+ state: 'missing';
24
+ connection?: VpnConnection;
25
+ };
26
+ /**
27
+ * Resolve a reference against the tenant's connections. A connection that is still
28
+ * there but no longer has the route counts as missing, as the backend refuses it
29
+ * with `VPN_ROUTE_MISSING` either way.
30
+ */
31
+ export declare function resolveVpnReference(connections: readonly VpnConnection[], ref: VpnRouteValue): VpnReference;
32
+ /** The keys of a credential type's fields a VPN route stands in for. */
33
+ export declare function replacedFieldKeys(decl: CredentialNetworkDeclaration | null | undefined): string[];
34
+ /**
35
+ * What a replaced field reads while a route stands in for it: `host:port via
36
+ * <connection>` for a tcp route, the gateway's endpoint for an http one — the
37
+ * address the request is actually sent to.
38
+ */
39
+ export declare function routeTargetText(route: VpnRoute, connection: VpnConnection): string;
40
+ /**
41
+ * The read-only text of every field a chosen route replaces, keyed by field. Empty
42
+ * while the credential is reached over the public internet, or while the route
43
+ * cannot be resolved — then the fields keep their stored values on screen.
44
+ */
45
+ export declare function replacedFieldTexts(type: Pick<CredentialType, 'network'> | null | undefined, value: VpnRouteValue | null, connections: readonly VpnConnection[]): Record<string, string>;
46
+ /** Whether the gateway reaches the target over TLS it does not verify. */
47
+ export declare function isHttpsTarget(route: VpnRoute | undefined): boolean;
48
+ /** The credential's `network` as the service takes it, from what the form holds. */
49
+ export declare function credentialNetworkPayload(value: VpnRouteValue | null): CredentialNetwork | null;
50
+ /** The picker's value from what a credential stores. */
51
+ export declare function fromCredentialNetwork(n: CredentialNetwork | null | undefined): VpnRouteValue | null;
52
+ /** Whether two values name the same route (both public counts as the same). */
53
+ export declare function sameVpnRoute(a: VpnRouteValue | null, b: VpnRouteValue | null): boolean;
54
+ /** A connection's status in the reader's words; an unforeseen one reads as unknown. */
55
+ export declare function connectionStatusLabel(status: string): string;
56
+ /** Badge classes for a connection status: green only for one that carries traffic. */
57
+ export declare function connectionStatusTone(status: string): string;
58
+ /**
59
+ * The VPN badge a credential carries where it is picked or listed, or `null` for one
60
+ * reached over the public internet. `connections` loaded or not decides whether a
61
+ * reference nothing resolves is called broken: before the list arrives it is only
62
+ * named.
63
+ */
64
+ export declare function credentialVpnBadge(network: CredentialNetwork | null | undefined, connections: readonly VpnConnection[], loaded: boolean): {
65
+ text: string;
66
+ broken: boolean;
67
+ active: boolean;
68
+ tone: string;
69
+ } | null;
@@ -0,0 +1,79 @@
1
+ import { useIntegrationsT } from "../composables/useIntegrationsT.js";
2
+ export function connectionLabel(c) {
3
+ return c.display_name?.trim() || c.name;
4
+ }
5
+ export function routesOfKind(c, kind) {
6
+ return (c?.routes ?? []).filter((r) => r.kind === kind);
7
+ }
8
+ export function isVpnRouteValue(v) {
9
+ return !!v && typeof v === "object" && typeof v.connection === "string" && v.connection !== "" && typeof v.route === "string" && v.route !== "";
10
+ }
11
+ export function resolveVpnReference(connections, ref) {
12
+ const connection = connections.find((c) => c.name === ref.connection);
13
+ const route = connection?.routes.find((r) => r.name === ref.route);
14
+ if (!connection || !route) return { state: "missing", connection };
15
+ return { state: "ok", connection, route, active: connection.status === "active" };
16
+ }
17
+ export function replacedFieldKeys(decl) {
18
+ if (!decl) return [];
19
+ return decl.kind === "tcp" ? [decl.hostField, decl.portField] : [decl.urlField];
20
+ }
21
+ export function routeTargetText(route, connection) {
22
+ const t = useIntegrationsT();
23
+ const target = route.kind === "http" && route.endpoint ? route.endpoint : `${route.host}:${route.port}`;
24
+ return t("integration.vpn.target", { target, connection: connectionLabel(connection) });
25
+ }
26
+ export function replacedFieldTexts(type, value, connections) {
27
+ const keys = replacedFieldKeys(type?.network);
28
+ if (!keys.length || !value) return {};
29
+ const ref = resolveVpnReference(connections, value);
30
+ if (ref.state !== "ok") return {};
31
+ const text = routeTargetText(ref.route, ref.connection);
32
+ return Object.fromEntries(keys.map((k) => [k, text]));
33
+ }
34
+ export function isHttpsTarget(route) {
35
+ return (route?.proto ?? "").toLowerCase() === "https";
36
+ }
37
+ export function credentialNetworkPayload(value) {
38
+ return value ? { kind: "vpn", connection: value.connection, route: value.route } : null;
39
+ }
40
+ export function fromCredentialNetwork(n) {
41
+ return n && n.kind === "vpn" && n.connection && n.route ? { connection: n.connection, route: n.route } : null;
42
+ }
43
+ export function sameVpnRoute(a, b) {
44
+ if (!a || !b) return !a && !b;
45
+ return a.connection === b.connection && a.route === b.route;
46
+ }
47
+ const STATUS_KEYS = ["provisioning", "failed", "active", "down", "standby", "disabled", "unknown"];
48
+ export function connectionStatusLabel(status) {
49
+ const key = STATUS_KEYS.includes(status) ? status : "unknown";
50
+ return useIntegrationsT()(`integration.vpn.status.${key}`);
51
+ }
52
+ export function connectionStatusTone(status) {
53
+ if (status === "active") return "bg-emerald-500/10 border-emerald-500/20 text-emerald-700";
54
+ if (status === "failed" || status === "down") return "bg-rose-500/10 border-rose-500/20 text-rose-700";
55
+ if (status === "provisioning" || status === "standby") return "bg-amber-500/10 border-amber-500/20 text-amber-700";
56
+ return "bg-slate-500/10 border-slate-500/20 text-slate-600";
57
+ }
58
+ export function credentialVpnBadge(network, connections, loaded) {
59
+ const value = fromCredentialNetwork(network);
60
+ if (!value) return null;
61
+ const t = useIntegrationsT();
62
+ if (!loaded) {
63
+ return { text: t("integration.vpn.badge.named", { connection: value.connection }), broken: false, active: true, tone: connectionStatusTone("unknown") };
64
+ }
65
+ const ref = resolveVpnReference(connections, value);
66
+ if (ref.state === "missing") {
67
+ return { text: t("integration.vpn.badge.missing"), broken: true, active: false, tone: connectionStatusTone("failed") };
68
+ }
69
+ const name = connectionLabel(ref.connection);
70
+ if (ref.active) {
71
+ return { text: t("integration.vpn.badge.named", { connection: name }), broken: false, active: true, tone: connectionStatusTone("active") };
72
+ }
73
+ return {
74
+ text: t("integration.vpn.badge.withState", { connection: name, status: connectionStatusLabel(String(ref.connection.status)) }),
75
+ broken: false,
76
+ active: false,
77
+ tone: connectionStatusTone(String(ref.connection.status))
78
+ };
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {