@contractspec/bundle.library 3.9.10 → 3.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/.turbo/turbo-build.log +54 -40
- package/CHANGELOG.md +35 -0
- package/README.md +1 -0
- package/dist/components/integrations/atoms/IntegrationCredentialFieldList.d.ts +6 -0
- package/dist/components/integrations/atoms/IntegrationCredentialFieldList.js +2 -0
- package/dist/components/integrations/atoms/IntegrationCredentialModeTabs.d.ts +7 -0
- package/dist/components/integrations/atoms/IntegrationCredentialModeTabs.js +2 -0
- package/dist/components/integrations/atoms/IntegrationEnvAliasPreview.d.ts +7 -0
- package/dist/components/integrations/atoms/IntegrationEnvAliasPreview.js +2 -0
- package/dist/components/integrations/blocks/IntegrationCredentialSetupBlock.d.ts +11 -0
- package/dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js +2 -0
- package/dist/components/integrations/byok-env-ui-kit.test.d.ts +1 -0
- package/dist/components/integrations/helpers/credentialSetupAliases.d.ts +3 -0
- package/dist/components/integrations/helpers/credentialSetupAliases.js +2 -0
- package/dist/components/integrations/helpers/credentialSetupModel.d.ts +58 -0
- package/dist/components/integrations/helpers/credentialSetupModel.js +2 -0
- package/dist/components/integrations/index.d.ts +5 -0
- package/dist/components/integrations/index.js +2 -2
- package/dist/components/integrations/organisms/IntegrationSettings.d.ts +3 -1
- package/dist/components/integrations/organisms/IntegrationSettings.js +2 -2
- package/dist/components/integrations/organisms/IntegrationSettingsSecretReference.d.ts +8 -0
- package/dist/components/integrations/organisms/IntegrationSettingsSecretReference.js +2 -0
- package/dist/index.js +302 -302
- package/dist/node/components/integrations/atoms/IntegrationCredentialFieldList.js +1 -0
- package/dist/node/components/integrations/atoms/IntegrationCredentialModeTabs.js +1 -0
- package/dist/node/components/integrations/atoms/IntegrationEnvAliasPreview.js +1 -0
- package/dist/node/components/integrations/blocks/IntegrationCredentialSetupBlock.js +1 -0
- package/dist/node/components/integrations/helpers/credentialSetupAliases.js +1 -0
- package/dist/node/components/integrations/helpers/credentialSetupModel.js +1 -0
- package/dist/node/components/integrations/index.js +2 -2
- package/dist/node/components/integrations/organisms/IntegrationSettings.js +2 -2
- package/dist/node/components/integrations/organisms/IntegrationSettingsSecretReference.js +1 -0
- package/dist/node/index.js +302 -302
- package/package.json +107 -23
- package/src/components/integrations/atoms/IntegrationCredentialFieldList.tsx +51 -0
- package/src/components/integrations/atoms/IntegrationCredentialModeTabs.tsx +44 -0
- package/src/components/integrations/atoms/IntegrationEnvAliasPreview.tsx +56 -0
- package/src/components/integrations/blocks/IntegrationCredentialSetupBlock.tsx +95 -0
- package/src/components/integrations/byok-env-ui-kit.test.tsx +194 -0
- package/src/components/integrations/helpers/credentialSetupAliases.ts +137 -0
- package/src/components/integrations/helpers/credentialSetupModel.ts +218 -0
- package/src/components/integrations/index.ts +5 -0
- package/src/components/integrations/organisms/IntegrationSettings.tsx +91 -97
- package/src/components/integrations/organisms/IntegrationSettingsSecretReference.tsx +84 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationCredentialAlias,
|
|
3
|
+
IntegrationCredentialModeManifest,
|
|
4
|
+
} from '@contractspec/lib.contracts-integrations';
|
|
5
|
+
import type {
|
|
6
|
+
EnvironmentConfig,
|
|
7
|
+
EnvVariableAlias,
|
|
8
|
+
} from '@contractspec/lib.contracts-spec/workspace-config/environment';
|
|
9
|
+
import type {
|
|
10
|
+
BuildIntegrationCredentialSetupModelInput,
|
|
11
|
+
CredentialSetupAliasRow,
|
|
12
|
+
CredentialSetupWarning,
|
|
13
|
+
} from './credentialSetupModel';
|
|
14
|
+
|
|
15
|
+
export function buildAliases(
|
|
16
|
+
manifest: IntegrationCredentialModeManifest | undefined,
|
|
17
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
18
|
+
secretKeys: Set<string>,
|
|
19
|
+
warnings: CredentialSetupWarning[]
|
|
20
|
+
): CredentialSetupAliasRow[] {
|
|
21
|
+
return [
|
|
22
|
+
...fromManifestAliases(manifest?.envAliases ?? [], secretKeys, warnings),
|
|
23
|
+
...fromEnvironmentAliases(input.environment, input, secretKeys, warnings),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fromManifestAliases(
|
|
28
|
+
aliases: IntegrationCredentialAlias[],
|
|
29
|
+
secretKeys: Set<string>,
|
|
30
|
+
warnings: CredentialSetupWarning[]
|
|
31
|
+
) {
|
|
32
|
+
return aliases.flatMap((alias): CredentialSetupAliasRow[] => {
|
|
33
|
+
if (
|
|
34
|
+
secretKeys.has(alias.logicalKey) &&
|
|
35
|
+
isPublicAlias(alias.envVar, alias.public)
|
|
36
|
+
) {
|
|
37
|
+
warnings.push({
|
|
38
|
+
level: 'error',
|
|
39
|
+
fieldKey: alias.logicalKey,
|
|
40
|
+
message: `Unsafe public alias was omitted for secret field ${alias.logicalKey}.`,
|
|
41
|
+
});
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
logicalKey: alias.logicalKey,
|
|
47
|
+
envName: alias.envVar,
|
|
48
|
+
targetId: alias.targetId,
|
|
49
|
+
public: isPublicAlias(alias.envVar, alias.public),
|
|
50
|
+
warning: alias.public ? 'Public client alias' : undefined,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function fromEnvironmentAliases(
|
|
57
|
+
environment: EnvironmentConfig | undefined,
|
|
58
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
59
|
+
secretKeys: Set<string>,
|
|
60
|
+
warnings: CredentialSetupWarning[]
|
|
61
|
+
) {
|
|
62
|
+
if (!environment) return [];
|
|
63
|
+
return Object.values(environment?.variables ?? {}).flatMap((definition) =>
|
|
64
|
+
(definition.aliases ?? [])
|
|
65
|
+
.filter((alias) => aliasMatchesInput(alias, environment, input))
|
|
66
|
+
.flatMap((alias): CredentialSetupAliasRow[] => {
|
|
67
|
+
const publicAlias =
|
|
68
|
+
isPublicAlias(alias.name) || isPublicExposure(alias.exposure);
|
|
69
|
+
if (secretKeys.has(definition.key) && publicAlias) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
level: 'error',
|
|
72
|
+
fieldKey: definition.key,
|
|
73
|
+
message: `Unsafe public alias was omitted for secret field ${definition.key}.`,
|
|
74
|
+
});
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
logicalKey: definition.key,
|
|
80
|
+
envName: alias.name,
|
|
81
|
+
targetId: alias.targetId,
|
|
82
|
+
targetLabel: targetLabel(environment, alias.targetId),
|
|
83
|
+
profile: alias.profile,
|
|
84
|
+
framework: alias.framework,
|
|
85
|
+
public: publicAlias,
|
|
86
|
+
warning: publicAlias ? 'Public client alias' : undefined,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function aliasMatchesInput(
|
|
94
|
+
alias: EnvVariableAlias,
|
|
95
|
+
environment: EnvironmentConfig,
|
|
96
|
+
input: BuildIntegrationCredentialSetupModelInput
|
|
97
|
+
) {
|
|
98
|
+
if (
|
|
99
|
+
input.targetIds?.length &&
|
|
100
|
+
alias.targetId &&
|
|
101
|
+
!input.targetIds.includes(alias.targetId)
|
|
102
|
+
)
|
|
103
|
+
return false;
|
|
104
|
+
if (input.profile && alias.profile && alias.profile !== input.profile)
|
|
105
|
+
return false;
|
|
106
|
+
if (
|
|
107
|
+
!input.targetIds?.length &&
|
|
108
|
+
(alias.targetId || alias.profile || alias.framework)
|
|
109
|
+
)
|
|
110
|
+
return false;
|
|
111
|
+
if (alias.framework && alias.targetId) {
|
|
112
|
+
const framework = environment.targets?.[alias.targetId]?.framework;
|
|
113
|
+
if (framework && framework !== alias.framework) return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function targetLabel(
|
|
119
|
+
environment: EnvironmentConfig,
|
|
120
|
+
targetId: string | undefined
|
|
121
|
+
) {
|
|
122
|
+
if (!targetId) return undefined;
|
|
123
|
+
const target = environment.targets?.[targetId];
|
|
124
|
+
return target?.packageName ?? target?.appId ?? target?.id ?? targetId;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isPublicExposure(exposure: string | undefined) {
|
|
128
|
+
return exposure === 'public-client' || exposure === 'native-client';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isPublicAlias(name: string, explicit?: boolean) {
|
|
132
|
+
return (
|
|
133
|
+
explicit === true ||
|
|
134
|
+
name.startsWith('NEXT_PUBLIC_') ||
|
|
135
|
+
name.startsWith('EXPO_PUBLIC_')
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationCredentialFieldRef,
|
|
3
|
+
IntegrationCredentialManifest,
|
|
4
|
+
IntegrationCredentialModeManifest,
|
|
5
|
+
IntegrationOwnershipMode,
|
|
6
|
+
IntegrationSpec,
|
|
7
|
+
} from '@contractspec/lib.contracts-integrations';
|
|
8
|
+
import { getIntegrationCredentialManifest } from '@contractspec/lib.contracts-integrations';
|
|
9
|
+
import type { EnvironmentConfig } from '@contractspec/lib.contracts-spec/workspace-config/environment';
|
|
10
|
+
import { buildAliases } from './credentialSetupAliases';
|
|
11
|
+
|
|
12
|
+
export type CredentialSetupFieldKind = 'config' | 'secret';
|
|
13
|
+
export type CredentialSetupFieldStatus = 'configured' | 'missing' | 'optional';
|
|
14
|
+
export type CredentialSetupWarningLevel = 'info' | 'warning' | 'error';
|
|
15
|
+
|
|
16
|
+
export interface CredentialSetupFieldRow {
|
|
17
|
+
kind: CredentialSetupFieldKind;
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
required: boolean;
|
|
22
|
+
status: CredentialSetupFieldStatus;
|
|
23
|
+
secretRef?: string;
|
|
24
|
+
envVar?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CredentialSetupAliasRow {
|
|
28
|
+
logicalKey: string;
|
|
29
|
+
envName: string;
|
|
30
|
+
targetId?: string;
|
|
31
|
+
targetLabel?: string;
|
|
32
|
+
profile?: string;
|
|
33
|
+
framework?: string;
|
|
34
|
+
public: boolean;
|
|
35
|
+
warning?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CredentialSetupWarning {
|
|
39
|
+
level: CredentialSetupWarningLevel;
|
|
40
|
+
message: string;
|
|
41
|
+
fieldKey?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CredentialSetupModeOption {
|
|
45
|
+
mode: IntegrationOwnershipMode;
|
|
46
|
+
label: string;
|
|
47
|
+
available: boolean;
|
|
48
|
+
fieldCount: number;
|
|
49
|
+
missingRequiredCount: number;
|
|
50
|
+
docsUrl?: string;
|
|
51
|
+
setupSteps?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IntegrationCredentialSetupModel {
|
|
55
|
+
selectedMode: IntegrationOwnershipMode;
|
|
56
|
+
modes: CredentialSetupModeOption[];
|
|
57
|
+
fields: CredentialSetupFieldRow[];
|
|
58
|
+
aliases: CredentialSetupAliasRow[];
|
|
59
|
+
warnings: CredentialSetupWarning[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface BuildIntegrationCredentialSetupModelInput {
|
|
63
|
+
integration?: IntegrationSpec;
|
|
64
|
+
credentialManifest?: IntegrationCredentialManifest;
|
|
65
|
+
supportedModes?: IntegrationOwnershipMode[];
|
|
66
|
+
selectedMode?: IntegrationOwnershipMode;
|
|
67
|
+
environment?: EnvironmentConfig;
|
|
68
|
+
configValues?: Record<string, unknown>;
|
|
69
|
+
secretRefs?: Record<string, string | undefined>;
|
|
70
|
+
targetIds?: string[];
|
|
71
|
+
profile?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildIntegrationCredentialSetupModel(
|
|
75
|
+
input: BuildIntegrationCredentialSetupModelInput
|
|
76
|
+
): IntegrationCredentialSetupModel {
|
|
77
|
+
const manifest = input.credentialManifest ?? manifestFromIntegration(input);
|
|
78
|
+
const supportedModes = resolveSupportedModes(input, manifest);
|
|
79
|
+
const selectedMode = input.selectedMode ?? supportedModes[0] ?? 'managed';
|
|
80
|
+
const modeManifest = manifest.modes?.[selectedMode];
|
|
81
|
+
const secretKeys = new Set(
|
|
82
|
+
(modeManifest?.secretFields ?? []).map((f) => f.key)
|
|
83
|
+
);
|
|
84
|
+
const warnings: CredentialSetupWarning[] = [];
|
|
85
|
+
const fields = buildFields(modeManifest, input, warnings);
|
|
86
|
+
const aliases = buildAliases(modeManifest, input, secretKeys, warnings);
|
|
87
|
+
const modes = supportedModes.map((mode) =>
|
|
88
|
+
buildModeOption(mode, manifest.modes?.[mode], input)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return { selectedMode, modes, fields, aliases, warnings };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function manifestFromIntegration(
|
|
95
|
+
input: BuildIntegrationCredentialSetupModelInput
|
|
96
|
+
) {
|
|
97
|
+
return input.integration
|
|
98
|
+
? getIntegrationCredentialManifest(input.integration)
|
|
99
|
+
: {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveSupportedModes(
|
|
103
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
104
|
+
manifest: IntegrationCredentialManifest
|
|
105
|
+
): IntegrationOwnershipMode[] {
|
|
106
|
+
return (
|
|
107
|
+
input.supportedModes ??
|
|
108
|
+
input.integration?.supportedModes ??
|
|
109
|
+
(Object.keys(manifest.modes ?? {}) as IntegrationOwnershipMode[])
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildModeOption(
|
|
114
|
+
mode: IntegrationOwnershipMode,
|
|
115
|
+
manifest: IntegrationCredentialModeManifest | undefined,
|
|
116
|
+
input: BuildIntegrationCredentialSetupModelInput
|
|
117
|
+
): CredentialSetupModeOption {
|
|
118
|
+
const fields = [
|
|
119
|
+
...(manifest?.configFields ?? []),
|
|
120
|
+
...(manifest?.secretFields ?? []),
|
|
121
|
+
];
|
|
122
|
+
const missingRequiredCount = fields.filter(
|
|
123
|
+
(field) => field.required && !hasConfiguredField(field, input)
|
|
124
|
+
).length;
|
|
125
|
+
return {
|
|
126
|
+
mode,
|
|
127
|
+
label: mode === 'byok' ? 'BYOK' : 'Managed',
|
|
128
|
+
available: Boolean(manifest),
|
|
129
|
+
fieldCount: fields.length,
|
|
130
|
+
missingRequiredCount,
|
|
131
|
+
docsUrl: manifest?.docsUrl,
|
|
132
|
+
setupSteps: manifest?.setupSteps,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildFields(
|
|
137
|
+
manifest: IntegrationCredentialModeManifest | undefined,
|
|
138
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
139
|
+
warnings: CredentialSetupWarning[]
|
|
140
|
+
) {
|
|
141
|
+
const configRows = (manifest?.configFields ?? []).map((field) =>
|
|
142
|
+
buildFieldRow('config', field, input, warnings)
|
|
143
|
+
);
|
|
144
|
+
const secretRows = (manifest?.secretFields ?? []).map((field) =>
|
|
145
|
+
buildFieldRow('secret', field, input, warnings)
|
|
146
|
+
);
|
|
147
|
+
return [...configRows, ...secretRows];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildFieldRow(
|
|
151
|
+
kind: CredentialSetupFieldKind,
|
|
152
|
+
field: IntegrationCredentialFieldRef,
|
|
153
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
154
|
+
warnings: CredentialSetupWarning[]
|
|
155
|
+
): CredentialSetupFieldRow {
|
|
156
|
+
const configured = hasConfiguredField(field, input);
|
|
157
|
+
const secretRef =
|
|
158
|
+
kind === 'secret' ? safeSecretRef(field, input, warnings) : undefined;
|
|
159
|
+
if (kind === 'secret' && field.required === true && !configured) {
|
|
160
|
+
warnings.push({
|
|
161
|
+
level: 'warning',
|
|
162
|
+
fieldKey: field.key,
|
|
163
|
+
message: `${toLabel(field.key)} is required for BYOK secret reference setup.`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
kind,
|
|
168
|
+
key: field.key,
|
|
169
|
+
label: toLabel(field.key),
|
|
170
|
+
description: field.description,
|
|
171
|
+
required: field.required === true,
|
|
172
|
+
status: configured ? 'configured' : field.required ? 'missing' : 'optional',
|
|
173
|
+
secretRef,
|
|
174
|
+
envVar: field.envVar,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hasConfiguredField(
|
|
179
|
+
field: IntegrationCredentialFieldRef,
|
|
180
|
+
input: BuildIntegrationCredentialSetupModelInput
|
|
181
|
+
) {
|
|
182
|
+
const configValue = input.configValues?.[field.key];
|
|
183
|
+
const secretRef =
|
|
184
|
+
input.secretRefs?.[field.key] ?? input.secretRefs?.[field.envVar ?? ''];
|
|
185
|
+
return hasPresence(configValue) || hasPresence(secretRef);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function safeSecretRef(
|
|
189
|
+
field: IntegrationCredentialFieldRef,
|
|
190
|
+
input: BuildIntegrationCredentialSetupModelInput,
|
|
191
|
+
warnings: CredentialSetupWarning[]
|
|
192
|
+
) {
|
|
193
|
+
const value =
|
|
194
|
+
input.secretRefs?.[field.key] ?? input.secretRefs?.[field.envVar ?? ''];
|
|
195
|
+
if (!value) return undefined;
|
|
196
|
+
if (
|
|
197
|
+
/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ||
|
|
198
|
+
/^[A-Z][A-Z0-9_]*$/.test(value)
|
|
199
|
+
) {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
warnings.push({
|
|
203
|
+
level: 'warning',
|
|
204
|
+
fieldKey: field.key,
|
|
205
|
+
message: `${toLabel(field.key)} has a value, but it is hidden because it is not a secret reference.`,
|
|
206
|
+
});
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function hasPresence(value: unknown) {
|
|
211
|
+
return value !== undefined && value !== null && value !== '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function toLabel(key: string) {
|
|
215
|
+
return key
|
|
216
|
+
.replace(/[_-]+/g, ' ')
|
|
217
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
218
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export * from './atoms/IntegrationCredentialFieldList';
|
|
2
|
+
export * from './atoms/IntegrationCredentialModeTabs';
|
|
3
|
+
export * from './atoms/IntegrationEnvAliasPreview';
|
|
4
|
+
export * from './blocks/IntegrationCredentialSetupBlock';
|
|
5
|
+
export * from './helpers/credentialSetupModel';
|
|
1
6
|
export * from './molecules/IntegrationCard';
|
|
2
7
|
export * from './organisms/IntegrationMarketplace';
|
|
3
8
|
export * from './organisms/IntegrationSettings';
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Button,
|
|
4
|
+
HStack,
|
|
5
|
+
Input,
|
|
6
|
+
Muted,
|
|
7
|
+
Small,
|
|
8
|
+
Text,
|
|
9
|
+
Textarea,
|
|
10
|
+
VStack,
|
|
11
|
+
} from '@contractspec/lib.design-system';
|
|
2
12
|
import { Checkbox } from '@contractspec/lib.ui-kit-web/ui/checkbox';
|
|
3
13
|
import { Label } from '@contractspec/lib.ui-kit-web/ui/label';
|
|
4
14
|
import {
|
|
@@ -10,6 +20,9 @@ import {
|
|
|
10
20
|
} from '@contractspec/lib.ui-kit-web/ui/select';
|
|
11
21
|
import { Key, ShieldCheck, TestTube2 } from 'lucide-react';
|
|
12
22
|
import * as React from 'react';
|
|
23
|
+
import { IntegrationCredentialSetupBlock } from '../blocks/IntegrationCredentialSetupBlock';
|
|
24
|
+
import type { BuildIntegrationCredentialSetupModelInput } from '../helpers/credentialSetupModel';
|
|
25
|
+
import { IntegrationSettingsSecretReference } from './IntegrationSettingsSecretReference';
|
|
13
26
|
|
|
14
27
|
export interface IntegrationSettingsForm {
|
|
15
28
|
apiKey: string;
|
|
@@ -27,6 +40,10 @@ export interface IntegrationSettingsProps {
|
|
|
27
40
|
onSave?: (values: IntegrationSettingsForm) => Promise<void> | void;
|
|
28
41
|
isSaving?: boolean;
|
|
29
42
|
isTesting?: boolean;
|
|
43
|
+
credentialSetup?: Omit<
|
|
44
|
+
BuildIntegrationCredentialSetupModelInput,
|
|
45
|
+
'selectedMode'
|
|
46
|
+
>;
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
export function IntegrationSettings({
|
|
@@ -36,6 +53,7 @@ export function IntegrationSettings({
|
|
|
36
53
|
onSave,
|
|
37
54
|
isSaving,
|
|
38
55
|
isTesting,
|
|
56
|
+
credentialSetup,
|
|
39
57
|
}: IntegrationSettingsProps) {
|
|
40
58
|
const [values, setValues] = React.useState<IntegrationSettingsForm>({
|
|
41
59
|
apiKey: initialValues?.apiKey ?? '',
|
|
@@ -49,31 +67,31 @@ export function IntegrationSettings({
|
|
|
49
67
|
const handleChange = (
|
|
50
68
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
51
69
|
) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
setValues((prev) => ({
|
|
55
|
-
...prev,
|
|
56
|
-
[name]: value,
|
|
57
|
-
}));
|
|
70
|
+
const { name, value } = event.target;
|
|
71
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
58
72
|
};
|
|
59
73
|
|
|
60
74
|
return (
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
<VStack
|
|
76
|
+
align="stretch"
|
|
77
|
+
gap="md"
|
|
78
|
+
className="rounded-2xl border border-border bg-card p-4"
|
|
79
|
+
>
|
|
80
|
+
<HStack wrap="wrap" justify="between" gap="md">
|
|
81
|
+
<VStack gap="sm" align="start">
|
|
82
|
+
<Small className="uppercase tracking-wide">{`${provider} credentials`}</Small>
|
|
83
|
+
<Muted>
|
|
68
84
|
Store encrypted keys with BYOK and run safe connection tests.
|
|
69
|
-
</
|
|
70
|
-
</
|
|
85
|
+
</Muted>
|
|
86
|
+
</VStack>
|
|
71
87
|
<ShieldCheck className="h-5 w-5 text-muted-foreground" />
|
|
72
|
-
</
|
|
88
|
+
</HStack>
|
|
73
89
|
|
|
74
|
-
<
|
|
75
|
-
<
|
|
76
|
-
<Label htmlFor="ownershipMode">
|
|
90
|
+
<Box className="grid gap-4 md:grid-cols-2">
|
|
91
|
+
<VStack gap="sm" align="stretch">
|
|
92
|
+
<Label htmlFor="ownershipMode">
|
|
93
|
+
<Text>Ownership</Text>
|
|
94
|
+
</Label>
|
|
77
95
|
<Select
|
|
78
96
|
value={values.ownershipMode ?? 'managed'}
|
|
79
97
|
onValueChange={(next) =>
|
|
@@ -87,21 +105,34 @@ export function IntegrationSettings({
|
|
|
87
105
|
<SelectValue />
|
|
88
106
|
</SelectTrigger>
|
|
89
107
|
<SelectContent>
|
|
90
|
-
<SelectItem value="managed">
|
|
108
|
+
<SelectItem value="managed">
|
|
109
|
+
<Text>Managed (store encrypted)</Text>
|
|
110
|
+
</SelectItem>
|
|
91
111
|
<SelectItem value="byok">
|
|
92
|
-
BYOK (store secret reference)
|
|
112
|
+
<Text>BYOK (store secret reference)</Text>
|
|
93
113
|
</SelectItem>
|
|
94
114
|
</SelectContent>
|
|
95
115
|
</Select>
|
|
96
|
-
</
|
|
97
|
-
</
|
|
116
|
+
</VStack>
|
|
117
|
+
</Box>
|
|
118
|
+
|
|
119
|
+
{credentialSetup ? (
|
|
120
|
+
<IntegrationCredentialSetupBlock
|
|
121
|
+
{...credentialSetup}
|
|
122
|
+
selectedMode={values.ownershipMode}
|
|
123
|
+
title={`${provider} setup`}
|
|
124
|
+
onModeChange={(mode) =>
|
|
125
|
+
setValues((prev) => ({ ...prev, ownershipMode: mode }))
|
|
126
|
+
}
|
|
127
|
+
/>
|
|
128
|
+
) : null}
|
|
98
129
|
|
|
99
|
-
<
|
|
100
|
-
<
|
|
130
|
+
<Box className="grid gap-4 md:grid-cols-2">
|
|
131
|
+
<VStack gap="sm" align="stretch">
|
|
101
132
|
<Label htmlFor="apiKey" className="font-semibold">
|
|
102
|
-
API key
|
|
133
|
+
<Text>API key</Text>
|
|
103
134
|
</Label>
|
|
104
|
-
<
|
|
135
|
+
<Box className="relative">
|
|
105
136
|
<Key className="pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
106
137
|
<Input
|
|
107
138
|
type="text"
|
|
@@ -112,11 +143,11 @@ export function IntegrationSettings({
|
|
|
112
143
|
onChange={handleChange}
|
|
113
144
|
required
|
|
114
145
|
/>
|
|
115
|
-
</
|
|
116
|
-
</
|
|
117
|
-
<
|
|
146
|
+
</Box>
|
|
147
|
+
</VStack>
|
|
148
|
+
<VStack gap="sm" align="stretch">
|
|
118
149
|
<Label htmlFor="secret" className="font-semibold">
|
|
119
|
-
Secret
|
|
150
|
+
<Text>Secret</Text>
|
|
120
151
|
</Label>
|
|
121
152
|
<Input
|
|
122
153
|
type="password"
|
|
@@ -126,71 +157,34 @@ export function IntegrationSettings({
|
|
|
126
157
|
value={values.secret}
|
|
127
158
|
onChange={handleChange}
|
|
128
159
|
/>
|
|
129
|
-
</
|
|
130
|
-
</
|
|
160
|
+
</VStack>
|
|
161
|
+
</Box>
|
|
131
162
|
|
|
132
163
|
{values.ownershipMode === 'byok' ? (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
onValueChange={(next) =>
|
|
141
|
-
setValues((prev) => ({
|
|
142
|
-
...prev,
|
|
143
|
-
secretProvider:
|
|
144
|
-
next as IntegrationSettingsForm['secretProvider'],
|
|
145
|
-
}))
|
|
146
|
-
}
|
|
147
|
-
>
|
|
148
|
-
<SelectTrigger id="secretProvider" className="w-full">
|
|
149
|
-
<SelectValue />
|
|
150
|
-
</SelectTrigger>
|
|
151
|
-
<SelectContent>
|
|
152
|
-
<SelectItem value="env">Environment</SelectItem>
|
|
153
|
-
<SelectItem value="gcp">GCP Secret Manager</SelectItem>
|
|
154
|
-
</SelectContent>
|
|
155
|
-
</Select>
|
|
156
|
-
</div>
|
|
157
|
-
<div className="space-y-2">
|
|
158
|
-
<Label htmlFor="secretRef">Secret reference</Label>
|
|
159
|
-
<Input
|
|
160
|
-
id="secretRef"
|
|
161
|
-
name="secretRef"
|
|
162
|
-
placeholder={
|
|
163
|
-
values.secretProvider === 'gcp'
|
|
164
|
-
? 'gcp://projects/.../secrets/...'
|
|
165
|
-
: 'env://MY_TOKEN_ENV_VAR'
|
|
166
|
-
}
|
|
167
|
-
value={values.secretRef ?? ''}
|
|
168
|
-
onChange={handleChange}
|
|
169
|
-
/>
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
164
|
+
<IntegrationSettingsSecretReference
|
|
165
|
+
values={values}
|
|
166
|
+
onChange={handleChange}
|
|
167
|
+
onSecretProviderChange={(secretProvider) =>
|
|
168
|
+
setValues((prev) => ({ ...prev, secretProvider }))
|
|
169
|
+
}
|
|
170
|
+
/>
|
|
173
171
|
) : (
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Secrets are encrypted server-side for this tenant.
|
|
186
|
-
</p>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
172
|
+
<HStack
|
|
173
|
+
align="center"
|
|
174
|
+
gap="md"
|
|
175
|
+
className="rounded-xl border border-emerald-500/20 bg-emerald-500/5 p-4 text-sm"
|
|
176
|
+
>
|
|
177
|
+
<Checkbox checked onCheckedChange={() => {}} aria-label="Managed" />
|
|
178
|
+
<VStack gap="sm" align="start">
|
|
179
|
+
<Small>Managed encryption enabled</Small>
|
|
180
|
+
<Muted>Secrets are encrypted server-side for this tenant.</Muted>
|
|
181
|
+
</VStack>
|
|
182
|
+
</HStack>
|
|
189
183
|
)}
|
|
190
184
|
|
|
191
|
-
<
|
|
185
|
+
<VStack gap="sm" align="stretch">
|
|
192
186
|
<Label htmlFor="config" className="font-semibold">
|
|
193
|
-
Configuration (JSON)
|
|
187
|
+
<Text>Configuration (JSON)</Text>
|
|
194
188
|
</Label>
|
|
195
189
|
<Textarea
|
|
196
190
|
id="config"
|
|
@@ -199,25 +193,25 @@ export function IntegrationSettings({
|
|
|
199
193
|
value={values.config}
|
|
200
194
|
onChange={handleChange}
|
|
201
195
|
/>
|
|
202
|
-
</
|
|
196
|
+
</VStack>
|
|
203
197
|
|
|
204
|
-
<
|
|
198
|
+
<HStack wrap="wrap" gap="md" align="center">
|
|
205
199
|
<Button
|
|
206
200
|
variant="ghost"
|
|
207
201
|
onPress={() => onTestConnection?.(values)}
|
|
208
202
|
disabled={isTesting}
|
|
209
203
|
>
|
|
210
204
|
<TestTube2 className="h-4 w-4" />
|
|
211
|
-
Test connection
|
|
205
|
+
<Text>Test connection</Text>
|
|
212
206
|
</Button>
|
|
213
207
|
<Button
|
|
214
208
|
variant="default"
|
|
215
209
|
onPress={() => onSave?.(values)}
|
|
216
210
|
disabled={isSaving}
|
|
217
211
|
>
|
|
218
|
-
Save settings
|
|
212
|
+
<Text>Save settings</Text>
|
|
219
213
|
</Button>
|
|
220
|
-
</
|
|
221
|
-
</
|
|
214
|
+
</HStack>
|
|
215
|
+
</VStack>
|
|
222
216
|
);
|
|
223
217
|
}
|