@rulebricks/cli 2.1.7 → 2.3.2
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/README.md +51 -16
- package/cluster-setup/aws/README.md +96 -47
- package/cluster-setup/aws/check-aws-access.sh +216 -52
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +103 -55
- package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +51 -34
- package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
- package/dist/commands/backup.d.ts +5 -0
- package/dist/commands/backup.js +104 -0
- package/dist/commands/deploy.d.ts +3 -1
- package/dist/commands/deploy.js +226 -326
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.js +73 -123
- package/dist/commands/init.d.ts +5 -1
- package/dist/commands/init.js +78 -54
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +74 -0
- package/dist/commands/open.d.ts +1 -1
- package/dist/commands/open.js +4 -12
- package/dist/commands/redeploy.d.ts +6 -0
- package/dist/commands/redeploy.js +310 -0
- package/dist/commands/restore.d.ts +5 -0
- package/dist/commands/restore.js +338 -0
- package/dist/commands/status.js +62 -49
- package/dist/commands/upgrade.js +74 -51
- package/dist/components/DNSWaitScreen.d.ts +5 -1
- package/dist/components/DNSWaitScreen.js +47 -41
- package/dist/components/Wizard/WizardContext.d.ts +157 -36
- package/dist/components/Wizard/WizardContext.js +872 -160
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
- package/dist/components/Wizard/steps/DomainStep.js +5 -24
- package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
- package/dist/components/Wizard/steps/FeatureConfigStep.js +739 -425
- package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
- package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
- package/dist/components/Wizard/steps/ReviewStep.js +56 -12
- package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
- package/dist/components/Wizard/steps/StorageStep.js +592 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
- package/dist/components/Wizard/steps/VersionStep.js +45 -23
- package/dist/components/Wizard/steps/index.d.ts +3 -3
- package/dist/components/Wizard/steps/index.js +3 -3
- package/dist/components/common/CommandApproval.d.ts +12 -0
- package/dist/components/common/CommandApproval.js +91 -0
- package/dist/components/common/DeploymentPicker.d.ts +14 -0
- package/dist/components/common/DeploymentPicker.js +16 -0
- package/dist/components/common/index.d.ts +2 -0
- package/dist/components/common/index.js +2 -0
- package/dist/index.js +94 -62
- package/dist/lib/cloudCli.d.ts +134 -63
- package/dist/lib/cloudCli.js +512 -220
- package/dist/lib/clusterSetupDefaults.d.ts +30 -0
- package/dist/lib/clusterSetupDefaults.js +64 -0
- package/dist/lib/commandApproval.d.ts +26 -0
- package/dist/lib/commandApproval.js +114 -0
- package/dist/lib/config.d.ts +12 -10
- package/dist/lib/config.js +91 -33
- package/dist/lib/configFixtures.d.ts +5 -0
- package/dist/lib/configFixtures.js +513 -0
- package/dist/lib/deploymentHealth.d.ts +32 -0
- package/dist/lib/deploymentHealth.js +157 -0
- package/dist/lib/dns.d.ts +1 -1
- package/dist/lib/dns.js +19 -1
- package/dist/lib/dns.test.d.ts +1 -0
- package/dist/lib/dns.test.js +27 -0
- package/dist/lib/dockerHub.d.ts +12 -1
- package/dist/lib/dockerHub.js +18 -8
- package/dist/lib/helm.d.ts +4 -0
- package/dist/lib/helm.js +16 -0
- package/dist/lib/helmValues.d.ts +25 -0
- package/dist/lib/helmValues.js +1841 -289
- package/dist/lib/helmValues.test.d.ts +1 -0
- package/dist/lib/helmValues.test.js +1012 -0
- package/dist/lib/htpasswd.d.ts +1 -0
- package/dist/lib/htpasswd.js +15 -0
- package/dist/lib/kubernetes.d.ts +124 -17
- package/dist/lib/kubernetes.js +576 -145
- package/dist/lib/secrets.d.ts +23 -0
- package/dist/lib/secrets.js +158 -0
- package/dist/lib/validateValues.d.ts +31 -0
- package/dist/lib/validateValues.js +253 -0
- package/dist/lib/versions.d.ts +82 -11
- package/dist/lib/versions.js +131 -31
- package/dist/lib/versions.test.d.ts +1 -0
- package/dist/lib/versions.test.js +81 -0
- package/dist/lib/wizardSteps.d.ts +14 -0
- package/dist/lib/wizardSteps.js +23 -0
- package/dist/lib/workloadIdentity.d.ts +26 -0
- package/dist/lib/workloadIdentity.js +323 -0
- package/dist/lib/workloadIdentity.test.d.ts +1 -0
- package/dist/lib/workloadIdentity.test.js +57 -0
- package/dist/types/index.d.ts +1860 -164
- package/dist/types/index.js +518 -295
- package/package.json +9 -4
- package/schema/values.schema.json +1934 -0
- package/cluster-setup/aws/cluster.yaml +0 -33
- package/cluster-setup/azure/main.bicep +0 -282
- package/cluster-setup/azure/main.parameters.json +0 -21
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
- package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
- package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
- package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
- package/dist/components/Wizard/steps/TierStep.js +0 -29
- package/dist/lib/terraform.d.ts +0 -66
- package/dist/lib/terraform.js +0 -754
- package/terraform/aws/main.tf +0 -355
- package/terraform/azure/main.tf +0 -371
- package/terraform/gcp/main.tf +0 -407
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useReducer } from "react";
|
|
3
|
-
import { DEFAULT_EMAIL_SUBJECTS, } from "../../types/index.js";
|
|
3
|
+
import { DEFAULT_EMAIL_SUBJECTS, validateRemoteWriteConfig, } from "../../types/index.js";
|
|
4
|
+
import { generateSecureSecret } from "../../lib/validation.js";
|
|
4
5
|
/**
|
|
5
6
|
* Creates the initial wizard state, optionally pre-populated from a user profile.
|
|
6
7
|
* Profile values are used as defaults that the user can still modify.
|
|
@@ -10,7 +11,6 @@ function getInitialState(profile) {
|
|
|
10
11
|
step: 0,
|
|
11
12
|
name: "",
|
|
12
13
|
// Infrastructure - pre-populate from profile
|
|
13
|
-
infrastructureMode: profile?.infrastructureMode ?? null,
|
|
14
14
|
provider: profile?.provider ?? null,
|
|
15
15
|
region: profile?.region ?? "",
|
|
16
16
|
clusterName: profile?.clusterName ?? "",
|
|
@@ -19,11 +19,13 @@ function getInitialState(profile) {
|
|
|
19
19
|
// Domain & Email - pre-populate from profile
|
|
20
20
|
domain: "", // Domain is intentionally left empty - user should enter unique domain per deployment
|
|
21
21
|
adminEmail: profile?.adminEmail ?? "",
|
|
22
|
-
|
|
22
|
+
// The TLS email is no longer asked in the wizard; it defaults to the admin
|
|
23
|
+
// email in toConfig. Only an existing config (redeploy) can carry a custom
|
|
24
|
+
// value, so it is intentionally not pre-populated from the profile.
|
|
25
|
+
tlsEmail: "",
|
|
23
26
|
// DNS Configuration - pre-populate from profile
|
|
24
27
|
dnsProvider: profile?.dnsProvider ?? "other",
|
|
25
28
|
dnsAutoManage: false,
|
|
26
|
-
existingExternalDns: false,
|
|
27
29
|
// SMTP - pre-populate from profile
|
|
28
30
|
smtpHost: profile?.smtpHost ?? "",
|
|
29
31
|
smtpPort: profile?.smtpPort ?? 587,
|
|
@@ -38,12 +40,34 @@ function getInitialState(profile) {
|
|
|
38
40
|
supabaseServiceKey: "",
|
|
39
41
|
supabaseAccessToken: "",
|
|
40
42
|
supabaseProjectRef: "",
|
|
41
|
-
supabaseJwtSecret:
|
|
43
|
+
supabaseJwtSecret: generateSecureSecret(64),
|
|
42
44
|
supabaseDbPassword: "",
|
|
43
45
|
supabaseDashboardUser: "supabase",
|
|
44
46
|
supabaseDashboardPass: "",
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
+
// Cluster capabilities (populated by the cluster scan)
|
|
48
|
+
nodeArchitecture: null,
|
|
49
|
+
arm64TolerationRequired: false,
|
|
50
|
+
storageClass: "",
|
|
51
|
+
storageProvisioner: "",
|
|
52
|
+
schedulableNodeCount: 0,
|
|
53
|
+
totalCpuCores: 0,
|
|
54
|
+
totalMemoryGi: 0,
|
|
55
|
+
eligibleCpuCores: 0,
|
|
56
|
+
eligibleMemoryGi: 0,
|
|
57
|
+
totalPersistentStorageGi: 0,
|
|
58
|
+
// Shared object storage
|
|
59
|
+
storageProvider: profile?.storage?.provider ?? null,
|
|
60
|
+
storageBucket: profile?.storage?.bucket ?? "",
|
|
61
|
+
storageRegion: profile?.storage?.region ?? "",
|
|
62
|
+
storageCloudAuthMode: profile?.storage?.cloudAuthMode ?? "workload-identity",
|
|
63
|
+
storageAwsIamRoleArn: profile?.storage?.awsIamRoleArn ?? "",
|
|
64
|
+
storageAzureBlobContainer: profile?.storage?.azureBlobContainer ?? "rulebricks",
|
|
65
|
+
storageAzureBlobClientId: profile?.storage?.azureBlobClientId ?? "",
|
|
66
|
+
storageAzureBlobTenantId: profile?.storage?.azureBlobTenantId ?? "",
|
|
67
|
+
storageAzureBlobConnectionStringSecretRef: profile?.storage?.azureBlobConnectionStringSecretRef
|
|
68
|
+
? `${profile.storage.azureBlobConnectionStringSecretRef.name}:${profile.storage.azureBlobConnectionStringSecretRef.key}`
|
|
69
|
+
: "",
|
|
70
|
+
storageGcpServiceAccountEmail: profile?.storage?.gcpServiceAccountEmail ?? "",
|
|
47
71
|
// Features - AI - pre-populate from profile
|
|
48
72
|
aiEnabled: !!profile?.openaiApiKey,
|
|
49
73
|
openaiApiKey: profile?.openaiApiKey ?? "",
|
|
@@ -53,8 +77,12 @@ function getInitialState(profile) {
|
|
|
53
77
|
ssoUrl: profile?.ssoUrl ?? "",
|
|
54
78
|
ssoClientId: profile?.ssoClientId ?? "",
|
|
55
79
|
ssoClientSecret: profile?.ssoClientSecret ?? "",
|
|
56
|
-
// Features - Monitoring
|
|
57
|
-
|
|
80
|
+
// Features - Monitoring (metrics export is opt-in; in-cluster Prometheus
|
|
81
|
+
// is always installed)
|
|
82
|
+
clickStackEnabled: true,
|
|
83
|
+
clickStackTelemetryRetentionDays: 7,
|
|
84
|
+
clickHouseStorageSize: "100Gi",
|
|
85
|
+
metricsExportEnabled: false,
|
|
58
86
|
prometheusMonitoringDestination: null,
|
|
59
87
|
prometheusRemoteWriteUrl: "",
|
|
60
88
|
prometheusRemoteWriteDestination: null,
|
|
@@ -70,15 +98,28 @@ function getInitialState(profile) {
|
|
|
70
98
|
prometheusRemoteWriteBearerTokenSecretRef: "",
|
|
71
99
|
// Features - Logging
|
|
72
100
|
loggingSink: "console", // Default to console only
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
loggingPlatformCredential: "",
|
|
102
|
+
loggingPlatformDetail: "",
|
|
103
|
+
// Features - Distributed Tracing
|
|
104
|
+
tracingEnabled: false,
|
|
105
|
+
tracingDestination: "elastic",
|
|
106
|
+
tracingElasticEndpoint: "",
|
|
107
|
+
tracingElasticAuthMode: "secret-token",
|
|
108
|
+
tracingElasticSecretToken: "",
|
|
109
|
+
tracingElasticApiKey: "",
|
|
110
|
+
tracingOtlpEndpoint: "",
|
|
111
|
+
tracingOtlpAuthMode: "none",
|
|
112
|
+
tracingOtlpHeaderName: "Authorization",
|
|
113
|
+
tracingOtlpToken: "",
|
|
114
|
+
tracingAzureConnectionString: "",
|
|
115
|
+
// Features - Application log shipping
|
|
116
|
+
appLogsEnabled: false,
|
|
117
|
+
appLogsElasticEndpoint: "",
|
|
118
|
+
appLogsElasticIndex: "rulebricks-app-logs",
|
|
119
|
+
appLogsElasticAuthMode: "basic",
|
|
120
|
+
appLogsElasticUsername: "",
|
|
121
|
+
appLogsElasticPassword: "",
|
|
122
|
+
appLogsElasticApiKey: "",
|
|
82
123
|
// Features - Custom Email Templates
|
|
83
124
|
customEmailsEnabled: false,
|
|
84
125
|
emailSubjects: { ...DEFAULT_EMAIL_SUBJECTS },
|
|
@@ -88,32 +129,579 @@ function getInitialState(profile) {
|
|
|
88
129
|
recovery: "",
|
|
89
130
|
emailChange: "",
|
|
90
131
|
},
|
|
132
|
+
// Database backups
|
|
133
|
+
backupEnabled: false,
|
|
134
|
+
backupSchedule: "0 2 * * *",
|
|
135
|
+
backupRetentionDays: 7,
|
|
136
|
+
// External services - Redis (default to in-cluster)
|
|
137
|
+
redisMode: "embedded",
|
|
138
|
+
redisHost: "",
|
|
139
|
+
redisPort: 6379,
|
|
140
|
+
redisPassword: "",
|
|
141
|
+
redisExistingSecret: "",
|
|
142
|
+
redisExistingSecretKey: "redis-password",
|
|
143
|
+
redisTls: false,
|
|
144
|
+
redisHttpApiEnabled: false,
|
|
145
|
+
redisHttpApiUrl: "",
|
|
146
|
+
redisHttpApiToken: "",
|
|
147
|
+
valkeyAdminEnabled: false,
|
|
148
|
+
valkeyAdminExposure: "internal",
|
|
149
|
+
valkeyAdminHostname: "",
|
|
150
|
+
valkeyAdminBasicAuthUsers: [],
|
|
151
|
+
valkeyAdminAllowedIPs: [],
|
|
152
|
+
redisExporterEnabled: false,
|
|
153
|
+
kafkaExporterEnabled: false,
|
|
154
|
+
// External services - Kafka (default to in-cluster)
|
|
155
|
+
kafkaMode: "embedded",
|
|
156
|
+
kafkaPreset: null,
|
|
157
|
+
kafkaBrokers: "",
|
|
158
|
+
kafkaTopic: "logs",
|
|
159
|
+
kafkaTopicPrefix: "com.rulebricks.",
|
|
160
|
+
kafkaProvisionTopics: true,
|
|
161
|
+
kafkaSsl: false,
|
|
162
|
+
kafkaSaslMechanism: "",
|
|
163
|
+
kafkaSaslRegion: "",
|
|
164
|
+
kafkaSaslUsername: "",
|
|
165
|
+
kafkaSaslPassword: "",
|
|
166
|
+
kafkaSaslExistingSecret: "",
|
|
167
|
+
kafkaIdentityAwsRoleArn: "",
|
|
168
|
+
kafkaIdentityGcpServiceAccountEmail: "",
|
|
169
|
+
kafkaIdentityAzureClientId: "",
|
|
170
|
+
// External services - Postgres
|
|
171
|
+
postgresMode: "embedded",
|
|
172
|
+
postgresHost: "",
|
|
173
|
+
postgresPort: 5432,
|
|
174
|
+
postgresDatabase: "postgres",
|
|
175
|
+
postgresMasterUsername: "postgres",
|
|
176
|
+
postgresMasterPassword: "",
|
|
91
177
|
// Credentials - pre-populate from profile
|
|
92
178
|
licenseKey: profile?.licenseKey ?? "",
|
|
93
179
|
// Version
|
|
94
|
-
|
|
95
|
-
hpsVersion: "",
|
|
180
|
+
version: "",
|
|
96
181
|
chartVersion: "",
|
|
97
182
|
};
|
|
98
183
|
}
|
|
99
184
|
// Default initial state (for backwards compatibility)
|
|
100
185
|
const initialState = getInitialState();
|
|
186
|
+
/**
|
|
187
|
+
* Derives the Supabase project ref from a managed-Supabase project URL
|
|
188
|
+
* (https://<ref>.supabase.co), so the wizard never has to ask for it.
|
|
189
|
+
* Returns undefined for custom domains, where the ref can't be inferred.
|
|
190
|
+
*/
|
|
191
|
+
function deriveSupabaseProjectRef(url) {
|
|
192
|
+
const match = url
|
|
193
|
+
.trim()
|
|
194
|
+
.match(/^https?:\/\/([a-z0-9-]+)\.supabase\.(?:co|com|in)(?:[/:]|$)/i);
|
|
195
|
+
return match ? match[1].toLowerCase() : undefined;
|
|
196
|
+
}
|
|
101
197
|
function parseSecretKeyRef(value) {
|
|
102
198
|
const [name, key] = value.split(":").map((part) => part.trim());
|
|
103
199
|
if (!name || !key)
|
|
104
200
|
return undefined;
|
|
105
201
|
return { name, key };
|
|
106
202
|
}
|
|
203
|
+
function formatSecretKeyRef(ref) {
|
|
204
|
+
return ref ? `${ref.name}:${ref.key}` : "";
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Builds the externalServices config from wizard state. Returns undefined when
|
|
208
|
+
* both Redis and Kafka use the in-cluster defaults so configs stay clean.
|
|
209
|
+
*/
|
|
210
|
+
function buildExternalServices(state) {
|
|
211
|
+
const redisExternal = state.redisMode === "external";
|
|
212
|
+
const kafkaExternal = state.kafkaMode === "external";
|
|
213
|
+
const postgresExternal = state.postgresMode === "external";
|
|
214
|
+
if (!redisExternal && !kafkaExternal && !postgresExternal) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
redis: {
|
|
219
|
+
mode: state.redisMode,
|
|
220
|
+
external: redisExternal
|
|
221
|
+
? {
|
|
222
|
+
host: state.redisHost.trim() || undefined,
|
|
223
|
+
port: state.redisPort || undefined,
|
|
224
|
+
password: state.redisPassword || undefined,
|
|
225
|
+
existingSecret: state.redisExistingSecret.trim() || undefined,
|
|
226
|
+
existingSecretKey: state.redisExistingSecret.trim() && state.redisExistingSecretKey
|
|
227
|
+
? state.redisExistingSecretKey
|
|
228
|
+
: undefined,
|
|
229
|
+
tls: state.redisTls,
|
|
230
|
+
httpApi: state.redisHttpApiEnabled
|
|
231
|
+
? {
|
|
232
|
+
enabled: true,
|
|
233
|
+
url: state.redisHttpApiUrl.trim() || undefined,
|
|
234
|
+
token: state.redisHttpApiToken || undefined,
|
|
235
|
+
}
|
|
236
|
+
: undefined,
|
|
237
|
+
}
|
|
238
|
+
: undefined,
|
|
239
|
+
},
|
|
240
|
+
kafka: {
|
|
241
|
+
mode: state.kafkaMode,
|
|
242
|
+
external: kafkaExternal
|
|
243
|
+
? {
|
|
244
|
+
preset: state.kafkaPreset ?? "custom",
|
|
245
|
+
brokers: state.kafkaBrokers.trim() || undefined,
|
|
246
|
+
topic: state.kafkaTopic.trim() || undefined,
|
|
247
|
+
// Always emit the prefix (incl. "") so the choice round-trips and the
|
|
248
|
+
// chart doesn't silently fall back to its default.
|
|
249
|
+
topicPrefix: state.kafkaTopicPrefix,
|
|
250
|
+
provisionTopics: state.kafkaProvisionTopics,
|
|
251
|
+
ssl: state.kafkaSsl,
|
|
252
|
+
sasl: state.kafkaSaslMechanism
|
|
253
|
+
? {
|
|
254
|
+
mechanism: state.kafkaSaslMechanism,
|
|
255
|
+
region: state.kafkaSaslRegion.trim() || undefined,
|
|
256
|
+
username: state.kafkaSaslUsername || undefined,
|
|
257
|
+
password: state.kafkaSaslPassword || undefined,
|
|
258
|
+
existingSecret: state.kafkaSaslExistingSecret.trim() || undefined,
|
|
259
|
+
}
|
|
260
|
+
: undefined,
|
|
261
|
+
identity: buildKafkaIdentity(state),
|
|
262
|
+
}
|
|
263
|
+
: undefined,
|
|
264
|
+
},
|
|
265
|
+
postgres: {
|
|
266
|
+
mode: state.postgresMode,
|
|
267
|
+
external: postgresExternal
|
|
268
|
+
? {
|
|
269
|
+
provider: state.provider === "aws" || state.provider === "azure"
|
|
270
|
+
? state.provider
|
|
271
|
+
: undefined,
|
|
272
|
+
host: state.postgresHost.trim() || undefined,
|
|
273
|
+
port: state.postgresPort || undefined,
|
|
274
|
+
database: state.postgresDatabase.trim() || undefined,
|
|
275
|
+
// One-time creds for the chart's bootstrap hook. The shared
|
|
276
|
+
// service-role password is sourced separately from the Supabase DB
|
|
277
|
+
// password (secret.db) so it matches what the services present.
|
|
278
|
+
bootstrap: {
|
|
279
|
+
enabled: true,
|
|
280
|
+
masterUsername: state.postgresMasterUsername.trim() || undefined,
|
|
281
|
+
masterPassword: state.postgresMasterPassword || undefined,
|
|
282
|
+
appRole: "postgres",
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
: undefined,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Collects the cloud workload identity for token-based Kafka auth. Returns
|
|
291
|
+
* undefined when no identity is provided (e.g. Azure Event Hubs PLAIN).
|
|
292
|
+
*/
|
|
293
|
+
function buildKafkaIdentity(state) {
|
|
294
|
+
const awsRoleArn = state.kafkaIdentityAwsRoleArn.trim();
|
|
295
|
+
const gcpServiceAccountEmail = state.kafkaIdentityGcpServiceAccountEmail.trim();
|
|
296
|
+
const azureClientId = state.kafkaIdentityAzureClientId.trim();
|
|
297
|
+
if (!awsRoleArn && !gcpServiceAccountEmail && !azureClientId) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
awsRoleArn: awsRoleArn || undefined,
|
|
302
|
+
gcpServiceAccountEmail: gcpServiceAccountEmail || undefined,
|
|
303
|
+
azureClientId: azureClientId || undefined,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Builds the Prometheus remote_write config from wizard state. Metrics reuse the
|
|
308
|
+
* single Rulebricks identity chosen in the Storage step, so Azure/AWS principals
|
|
309
|
+
* fall back to the storage identity when not set explicitly.
|
|
310
|
+
*/
|
|
311
|
+
function buildRemoteWriteFromState(state) {
|
|
312
|
+
if (state.prometheusMonitoringDestination === "local-grafana" ||
|
|
313
|
+
!state.prometheusRemoteWriteDestination ||
|
|
314
|
+
!state.prometheusRemoteWriteUrl) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
destination: state.prometheusRemoteWriteDestination,
|
|
319
|
+
url: state.prometheusRemoteWriteUrl,
|
|
320
|
+
authType: state.prometheusRemoteWriteAuthType ||
|
|
321
|
+
(state.prometheusRemoteWriteDestination === "azure-monitor"
|
|
322
|
+
? "managed-identity"
|
|
323
|
+
: undefined),
|
|
324
|
+
awsRegion: state.prometheusRemoteWriteDestination === "aws-amp"
|
|
325
|
+
? state.prometheusRemoteWriteAwsRegion || state.region || undefined
|
|
326
|
+
: undefined,
|
|
327
|
+
awsRoleArn: state.prometheusRemoteWriteDestination === "aws-amp"
|
|
328
|
+
? state.prometheusRemoteWriteAwsRoleArn ||
|
|
329
|
+
state.storageAwsIamRoleArn ||
|
|
330
|
+
undefined
|
|
331
|
+
: undefined,
|
|
332
|
+
azureCloud: state.prometheusRemoteWriteAzureCloud,
|
|
333
|
+
clientId: state.prometheusRemoteWriteDestination === "azure-monitor"
|
|
334
|
+
? state.prometheusRemoteWriteClientId ||
|
|
335
|
+
state.storageAzureBlobClientId ||
|
|
336
|
+
undefined
|
|
337
|
+
: state.prometheusRemoteWriteClientId || undefined,
|
|
338
|
+
tenantId: state.prometheusRemoteWriteDestination === "azure-monitor"
|
|
339
|
+
? state.prometheusRemoteWriteTenantId ||
|
|
340
|
+
state.storageAzureBlobTenantId ||
|
|
341
|
+
undefined
|
|
342
|
+
: state.prometheusRemoteWriteTenantId || undefined,
|
|
343
|
+
clientSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteSecretRef),
|
|
344
|
+
usernameSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteUsernameSecretRef),
|
|
345
|
+
passwordSecretRef: parseSecretKeyRef(state.prometheusRemoteWritePasswordSecretRef),
|
|
346
|
+
bearerTokenSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteBearerTokenSecretRef),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Returns a list of human-readable reasons the wizard state can't be saved as a
|
|
351
|
+
* valid DeploymentConfig. Empty array means it's good to save. Shared by toConfig
|
|
352
|
+
* (which returns null when non-empty) and the review screen (which shows them),
|
|
353
|
+
* so users get a specific reason instead of a generic "invalid configuration".
|
|
354
|
+
*/
|
|
355
|
+
export function collectConfigIssues(state) {
|
|
356
|
+
const issues = [];
|
|
357
|
+
if (!state.name)
|
|
358
|
+
issues.push("Deployment name is required.");
|
|
359
|
+
if (!state.domain)
|
|
360
|
+
issues.push("Domain is required.");
|
|
361
|
+
if (!state.adminEmail)
|
|
362
|
+
issues.push("Admin email is required.");
|
|
363
|
+
if (!state.licenseKey)
|
|
364
|
+
issues.push("License key is required.");
|
|
365
|
+
if (!state.smtpHost || !state.smtpUser || !state.smtpPass || !state.smtpFrom) {
|
|
366
|
+
issues.push("SMTP host, user, password, and from address are required.");
|
|
367
|
+
}
|
|
368
|
+
if (state.databaseType === "supabase-cloud") {
|
|
369
|
+
if (!state.supabaseUrl ||
|
|
370
|
+
!state.supabaseAnonKey ||
|
|
371
|
+
!state.supabaseServiceKey ||
|
|
372
|
+
!state.supabaseAccessToken) {
|
|
373
|
+
issues.push("Managed Supabase requires a project URL, anon key, service key, and access token.");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (state.databaseType === "self-hosted") {
|
|
377
|
+
if (!state.supabaseJwtSecret) {
|
|
378
|
+
issues.push("Self-hosted Supabase requires a JWT secret.");
|
|
379
|
+
}
|
|
380
|
+
if (!state.supabaseDbPassword) {
|
|
381
|
+
issues.push("Self-hosted Supabase requires a database password.");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (state.aiEnabled && !state.openaiApiKey) {
|
|
385
|
+
issues.push("AI is enabled but the OpenAI API key is missing.");
|
|
386
|
+
}
|
|
387
|
+
if (state.ssoEnabled &&
|
|
388
|
+
(!state.ssoProvider || !state.ssoClientId || !state.ssoClientSecret)) {
|
|
389
|
+
issues.push("SSO is enabled but the provider, client ID, or client secret is missing.");
|
|
390
|
+
}
|
|
391
|
+
if (state.loggingSink !== "console" &&
|
|
392
|
+
state.loggingSink !== "pending" &&
|
|
393
|
+
!state.loggingPlatformCredential) {
|
|
394
|
+
issues.push("The selected logging platform is missing its credentials/endpoint.");
|
|
395
|
+
}
|
|
396
|
+
if (!state.clickStackEnabled && state.tracingEnabled) {
|
|
397
|
+
if (state.tracingDestination === "elastic") {
|
|
398
|
+
if (!state.tracingElasticEndpoint) {
|
|
399
|
+
issues.push("Distributed tracing is enabled but the Elastic APM endpoint is missing.");
|
|
400
|
+
}
|
|
401
|
+
if (state.tracingElasticAuthMode === "secret-token" &&
|
|
402
|
+
!state.tracingElasticSecretToken) {
|
|
403
|
+
issues.push("Distributed tracing (secret-token auth) is missing the Elastic APM secret token.");
|
|
404
|
+
}
|
|
405
|
+
if (state.tracingElasticAuthMode === "api-key" &&
|
|
406
|
+
!state.tracingElasticApiKey) {
|
|
407
|
+
issues.push("Distributed tracing (API key auth) is missing the Elastic APM API key.");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (state.tracingDestination === "otlp") {
|
|
411
|
+
if (!state.tracingOtlpEndpoint) {
|
|
412
|
+
issues.push("Distributed tracing is enabled but the OTLP endpoint is missing.");
|
|
413
|
+
}
|
|
414
|
+
if (state.tracingOtlpAuthMode !== "none" && !state.tracingOtlpToken) {
|
|
415
|
+
issues.push("Distributed tracing (OTLP) is missing its authentication credential.");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (state.tracingDestination === "azure-monitor") {
|
|
419
|
+
if (!state.tracingAzureConnectionString) {
|
|
420
|
+
issues.push("Distributed tracing is enabled but the Azure Monitor connection string is missing.");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!state.clickStackEnabled && state.appLogsEnabled) {
|
|
425
|
+
if (!state.appLogsElasticEndpoint) {
|
|
426
|
+
issues.push("Application log shipping is enabled but the Elasticsearch endpoint is missing.");
|
|
427
|
+
}
|
|
428
|
+
if (state.appLogsElasticAuthMode === "basic" &&
|
|
429
|
+
(!state.appLogsElasticUsername || !state.appLogsElasticPassword)) {
|
|
430
|
+
issues.push("Application log shipping (basic auth) is missing the Elasticsearch username or password.");
|
|
431
|
+
}
|
|
432
|
+
if (state.appLogsElasticAuthMode === "api-key" &&
|
|
433
|
+
!state.appLogsElasticApiKey) {
|
|
434
|
+
issues.push("Application log shipping (API key auth) is missing the Elasticsearch API key.");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!state.storageProvider || !state.storageBucket || !state.storageRegion) {
|
|
438
|
+
issues.push("Object storage provider, bucket/account, and region are required.");
|
|
439
|
+
}
|
|
440
|
+
if (state.storageProvider === "s3" && !state.storageAwsIamRoleArn) {
|
|
441
|
+
issues.push("S3 storage requires an IAM role (IRSA).");
|
|
442
|
+
}
|
|
443
|
+
if (state.storageProvider === "azure-blob") {
|
|
444
|
+
if (!state.storageAzureBlobContainer) {
|
|
445
|
+
issues.push("Azure Blob storage requires a container name.");
|
|
446
|
+
}
|
|
447
|
+
if (state.storageCloudAuthMode === "workload-identity" &&
|
|
448
|
+
(!state.storageAzureBlobClientId || !state.storageAzureBlobTenantId)) {
|
|
449
|
+
issues.push("Azure Blob workload identity requires a client ID and tenant ID.");
|
|
450
|
+
}
|
|
451
|
+
if (state.storageCloudAuthMode === "secret" &&
|
|
452
|
+
!parseSecretKeyRef(state.storageAzureBlobConnectionStringSecretRef)) {
|
|
453
|
+
issues.push("Azure Blob connection-string auth requires a secret reference (name:key).");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (state.storageProvider === "gcs" && !state.storageGcpServiceAccountEmail) {
|
|
457
|
+
issues.push("GCS storage requires a Google service account email.");
|
|
458
|
+
}
|
|
459
|
+
if (state.redisMode === "external" && !state.redisHost.trim()) {
|
|
460
|
+
issues.push("External Redis requires a host.");
|
|
461
|
+
}
|
|
462
|
+
if (state.kafkaMode === "external" && !state.kafkaBrokers.trim()) {
|
|
463
|
+
issues.push("External Kafka requires brokers.");
|
|
464
|
+
}
|
|
465
|
+
if (state.postgresMode === "external") {
|
|
466
|
+
if (!state.postgresHost.trim()) {
|
|
467
|
+
issues.push("External Postgres requires a host.");
|
|
468
|
+
}
|
|
469
|
+
if (!state.postgresMasterPassword) {
|
|
470
|
+
issues.push("External Postgres requires master credentials to initialize the database.");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (!state.clickStackEnabled && state.metricsExportEnabled) {
|
|
474
|
+
const remoteWrite = buildRemoteWriteFromState(state);
|
|
475
|
+
if (!remoteWrite) {
|
|
476
|
+
issues.push("Metrics export is enabled but the Prometheus remote_write destination is not configured.");
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
for (const message of validateRemoteWriteConfig(remoteWrite)) {
|
|
480
|
+
issues.push(message);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (state.valkeyAdminEnabled &&
|
|
485
|
+
state.valkeyAdminExposure === "ingress" &&
|
|
486
|
+
state.valkeyAdminBasicAuthUsers.length === 0) {
|
|
487
|
+
issues.push("Valkey Admin ingress exposure requires at least one htpasswd BasicAuth user.");
|
|
488
|
+
}
|
|
489
|
+
return issues;
|
|
490
|
+
}
|
|
491
|
+
export function configToWizardState(config, profile) {
|
|
492
|
+
const base = getInitialState(profile);
|
|
493
|
+
const remoteWrite = config.features.monitoring.remoteWrite;
|
|
494
|
+
const storage = config.storage;
|
|
495
|
+
const customEmails = config.features.customEmails;
|
|
496
|
+
const externalRedis = config.externalServices?.redis;
|
|
497
|
+
const externalKafka = config.externalServices?.kafka;
|
|
498
|
+
const externalPostgres = config.externalServices?.postgres;
|
|
499
|
+
return {
|
|
500
|
+
...base,
|
|
501
|
+
name: config.name,
|
|
502
|
+
provider: config.infrastructure.provider ?? base.provider,
|
|
503
|
+
region: config.infrastructure.region ?? base.region,
|
|
504
|
+
clusterName: config.infrastructure.clusterName ?? base.clusterName,
|
|
505
|
+
gcpProjectId: config.infrastructure.gcpProjectId ?? "",
|
|
506
|
+
azureResourceGroup: config.infrastructure.azureResourceGroup ?? "",
|
|
507
|
+
domain: config.domain,
|
|
508
|
+
adminEmail: config.adminEmail,
|
|
509
|
+
tlsEmail: config.tlsEmail,
|
|
510
|
+
dnsProvider: config.dns.provider,
|
|
511
|
+
dnsAutoManage: config.dns.autoManage,
|
|
512
|
+
smtpHost: config.smtp.host,
|
|
513
|
+
smtpPort: config.smtp.port,
|
|
514
|
+
smtpUser: config.smtp.user,
|
|
515
|
+
smtpPass: config.smtp.pass,
|
|
516
|
+
smtpFrom: config.smtp.from,
|
|
517
|
+
smtpFromName: config.smtp.fromName,
|
|
518
|
+
databaseType: config.database.type,
|
|
519
|
+
supabaseUrl: config.database.supabaseUrl ?? "",
|
|
520
|
+
supabaseAnonKey: config.database.supabaseAnonKey ?? "",
|
|
521
|
+
supabaseServiceKey: config.database.supabaseServiceKey ?? "",
|
|
522
|
+
supabaseAccessToken: config.database.supabaseAccessToken ?? "",
|
|
523
|
+
supabaseProjectRef: config.database.supabaseProjectRef ?? "",
|
|
524
|
+
supabaseJwtSecret: config.database.type === "self-hosted"
|
|
525
|
+
? config.database.supabaseJwtSecret || base.supabaseJwtSecret
|
|
526
|
+
: config.database.supabaseJwtSecret ?? "",
|
|
527
|
+
supabaseDbPassword: config.database.supabaseDbPassword ?? "",
|
|
528
|
+
supabaseDashboardUser: config.database.supabaseDashboardUser ?? base.supabaseDashboardUser,
|
|
529
|
+
supabaseDashboardPass: config.database.supabaseDashboardPass ?? "",
|
|
530
|
+
nodeArchitecture: config.infrastructure.nodeArchitecture ?? null,
|
|
531
|
+
arm64TolerationRequired: config.infrastructure.arm64TolerationRequired ?? false,
|
|
532
|
+
storageClass: config.infrastructure.storageClass ?? "",
|
|
533
|
+
storageProvisioner: config.infrastructure.storageProvisioner ?? "",
|
|
534
|
+
schedulableNodeCount: config.infrastructure.schedulableNodeCount ?? 0,
|
|
535
|
+
totalCpuCores: config.infrastructure.totalCpuCores ?? 0,
|
|
536
|
+
totalMemoryGi: config.infrastructure.totalMemoryGi ?? 0,
|
|
537
|
+
eligibleCpuCores: config.infrastructure.eligibleCpuCores ?? 0,
|
|
538
|
+
eligibleMemoryGi: config.infrastructure.eligibleMemoryGi ?? 0,
|
|
539
|
+
totalPersistentStorageGi: config.infrastructure.totalPersistentStorageGi ?? 0,
|
|
540
|
+
storageProvider: storage?.provider ?? base.storageProvider,
|
|
541
|
+
storageBucket: storage?.bucket ?? "",
|
|
542
|
+
storageRegion: storage?.region ?? "",
|
|
543
|
+
storageCloudAuthMode: storage?.cloudAuthMode ?? base.storageCloudAuthMode,
|
|
544
|
+
storageAwsIamRoleArn: storage?.awsIamRoleArn ?? "",
|
|
545
|
+
storageAzureBlobContainer: storage?.azureBlobContainer ?? base.storageAzureBlobContainer,
|
|
546
|
+
storageAzureBlobClientId: storage?.azureBlobClientId ?? "",
|
|
547
|
+
storageAzureBlobTenantId: storage?.azureBlobTenantId ?? "",
|
|
548
|
+
storageAzureBlobConnectionStringSecretRef: formatSecretKeyRef(storage?.azureBlobConnectionStringSecretRef),
|
|
549
|
+
storageGcpServiceAccountEmail: storage?.gcpServiceAccountEmail ?? "",
|
|
550
|
+
aiEnabled: config.features.ai.enabled,
|
|
551
|
+
openaiApiKey: config.features.ai.openaiApiKey ?? "",
|
|
552
|
+
ssoEnabled: config.features.sso.enabled,
|
|
553
|
+
ssoProvider: config.features.sso.provider ?? null,
|
|
554
|
+
ssoUrl: config.features.sso.url ?? "",
|
|
555
|
+
ssoClientId: config.features.sso.clientId ?? "",
|
|
556
|
+
ssoClientSecret: config.features.sso.clientSecret ?? "",
|
|
557
|
+
clickStackEnabled: config.features.observability?.clickstack?.enabled ?? true,
|
|
558
|
+
clickStackTelemetryRetentionDays: config.features.observability?.clickstack?.telemetryRetentionDays ??
|
|
559
|
+
base.clickStackTelemetryRetentionDays,
|
|
560
|
+
clickHouseStorageSize: config.features.observability?.clickstack?.clickHouseStorageSize ??
|
|
561
|
+
base.clickHouseStorageSize,
|
|
562
|
+
// The toggle reflects whether remote_write is actually configured, so a
|
|
563
|
+
// redeploy resumes with the metrics-export sub-flow only when in use.
|
|
564
|
+
metricsExportEnabled: !!(remoteWrite || config.features.monitoring.remoteWriteUrl),
|
|
565
|
+
prometheusMonitoringDestination: config.features.monitoring.destination ?? null,
|
|
566
|
+
prometheusRemoteWriteUrl: remoteWrite?.url ?? config.features.monitoring.remoteWriteUrl ?? "",
|
|
567
|
+
prometheusRemoteWriteDestination: remoteWrite?.destination ?? null,
|
|
568
|
+
prometheusRemoteWriteAuthType: remoteWrite?.authType ?? null,
|
|
569
|
+
prometheusRemoteWriteAwsRegion: remoteWrite?.awsRegion ?? "",
|
|
570
|
+
prometheusRemoteWriteAwsRoleArn: remoteWrite?.awsRoleArn ?? "",
|
|
571
|
+
prometheusRemoteWriteAzureCloud: remoteWrite?.azureCloud ?? base.prometheusRemoteWriteAzureCloud,
|
|
572
|
+
prometheusRemoteWriteClientId: remoteWrite?.clientId ?? "",
|
|
573
|
+
prometheusRemoteWriteTenantId: remoteWrite?.tenantId ?? "",
|
|
574
|
+
prometheusRemoteWriteSecretRef: formatSecretKeyRef(remoteWrite?.clientSecretRef),
|
|
575
|
+
prometheusRemoteWriteUsernameSecretRef: formatSecretKeyRef(remoteWrite?.usernameSecretRef),
|
|
576
|
+
prometheusRemoteWritePasswordSecretRef: formatSecretKeyRef(remoteWrite?.passwordSecretRef),
|
|
577
|
+
prometheusRemoteWriteBearerTokenSecretRef: formatSecretKeyRef(remoteWrite?.bearerTokenSecretRef),
|
|
578
|
+
loggingSink: config.features.logging.sink,
|
|
579
|
+
loggingPlatformCredential: config.features.logging.bucket ?? "",
|
|
580
|
+
loggingPlatformDetail: config.features.logging.region ?? "",
|
|
581
|
+
// Distributed tracing (Elastic APM / generic OTLP / Azure Monitor)
|
|
582
|
+
tracingEnabled: config.features.tracing?.enabled ?? false,
|
|
583
|
+
tracingDestination: config.features.tracing?.destination ?? "elastic",
|
|
584
|
+
tracingElasticEndpoint: config.features.tracing?.elastic?.endpoint ?? "",
|
|
585
|
+
tracingElasticAuthMode: config.features.tracing?.elastic?.authMode === "api-key"
|
|
586
|
+
? "api-key"
|
|
587
|
+
: "secret-token",
|
|
588
|
+
tracingElasticSecretToken: config.features.tracing?.elastic?.secretToken ?? "",
|
|
589
|
+
tracingElasticApiKey: config.features.tracing?.elastic?.apiKey ?? "",
|
|
590
|
+
tracingOtlpEndpoint: config.features.tracing?.otlp?.endpoint ?? "",
|
|
591
|
+
tracingOtlpAuthMode: config.features.tracing?.otlp?.authMode ?? "none",
|
|
592
|
+
tracingOtlpHeaderName: config.features.tracing?.otlp?.headerName ?? "Authorization",
|
|
593
|
+
tracingOtlpToken: config.features.tracing?.otlp?.token ??
|
|
594
|
+
config.features.tracing?.otlp?.apiKey ??
|
|
595
|
+
config.features.tracing?.otlp?.headerValue ??
|
|
596
|
+
"",
|
|
597
|
+
tracingAzureConnectionString: config.features.tracing?.azureMonitor?.connectionString ?? "",
|
|
598
|
+
// Application log shipping (Elasticsearch via Vector agent)
|
|
599
|
+
appLogsEnabled: config.features.logging.appLogs?.enabled ?? false,
|
|
600
|
+
appLogsElasticEndpoint: config.features.logging.appLogs?.elasticsearch?.endpoint ?? "",
|
|
601
|
+
appLogsElasticIndex: config.features.logging.appLogs?.elasticsearch?.index ??
|
|
602
|
+
base.appLogsElasticIndex,
|
|
603
|
+
appLogsElasticAuthMode: config.features.logging.appLogs?.elasticsearch?.authMode === "api-key"
|
|
604
|
+
? "api-key"
|
|
605
|
+
: "basic",
|
|
606
|
+
appLogsElasticUsername: config.features.logging.appLogs?.elasticsearch?.username ?? "",
|
|
607
|
+
appLogsElasticPassword: config.features.logging.appLogs?.elasticsearch?.password ?? "",
|
|
608
|
+
appLogsElasticApiKey: config.features.logging.appLogs?.elasticsearch?.apiKey ?? "",
|
|
609
|
+
customEmailsEnabled: customEmails?.enabled ?? false,
|
|
610
|
+
emailSubjects: customEmails?.subjects ?? base.emailSubjects,
|
|
611
|
+
emailTemplates: customEmails?.templates ?? base.emailTemplates,
|
|
612
|
+
backupEnabled: config.backup?.enabled ?? false,
|
|
613
|
+
backupSchedule: config.backup?.schedule ?? base.backupSchedule,
|
|
614
|
+
backupRetentionDays: config.backup?.retentionDays ?? base.backupRetentionDays,
|
|
615
|
+
// External services - Redis
|
|
616
|
+
redisMode: externalRedis?.mode ?? base.redisMode,
|
|
617
|
+
redisHost: externalRedis?.external?.host ?? base.redisHost,
|
|
618
|
+
redisPort: externalRedis?.external?.port ?? base.redisPort,
|
|
619
|
+
redisPassword: externalRedis?.external?.password ?? base.redisPassword,
|
|
620
|
+
redisExistingSecret: externalRedis?.external?.existingSecret ?? base.redisExistingSecret,
|
|
621
|
+
redisExistingSecretKey: externalRedis?.external?.existingSecretKey ?? base.redisExistingSecretKey,
|
|
622
|
+
redisTls: externalRedis?.external?.tls ?? base.redisTls,
|
|
623
|
+
redisHttpApiEnabled: externalRedis?.external?.httpApi?.enabled ?? base.redisHttpApiEnabled,
|
|
624
|
+
redisHttpApiUrl: externalRedis?.external?.httpApi?.url ?? base.redisHttpApiUrl,
|
|
625
|
+
redisHttpApiToken: externalRedis?.external?.httpApi?.token ?? base.redisHttpApiToken,
|
|
626
|
+
valkeyAdminEnabled: config.features.cache?.valkeyAdmin?.enabled ?? base.valkeyAdminEnabled,
|
|
627
|
+
valkeyAdminExposure: config.features.cache?.valkeyAdmin?.exposure ?? base.valkeyAdminExposure,
|
|
628
|
+
valkeyAdminHostname: config.features.cache?.valkeyAdmin?.hostname ?? base.valkeyAdminHostname,
|
|
629
|
+
valkeyAdminBasicAuthUsers: config.features.cache?.valkeyAdmin?.basicAuthUsers ??
|
|
630
|
+
base.valkeyAdminBasicAuthUsers,
|
|
631
|
+
valkeyAdminAllowedIPs: config.features.cache?.valkeyAdmin?.allowedIPs ??
|
|
632
|
+
base.valkeyAdminAllowedIPs,
|
|
633
|
+
redisExporterEnabled: config.features.cache?.redisExporter?.enabled ??
|
|
634
|
+
base.redisExporterEnabled,
|
|
635
|
+
kafkaExporterEnabled: config.features.cache?.kafkaExporter?.enabled ??
|
|
636
|
+
base.kafkaExporterEnabled,
|
|
637
|
+
// External services - Kafka
|
|
638
|
+
kafkaMode: externalKafka?.mode ?? base.kafkaMode,
|
|
639
|
+
kafkaPreset: externalKafka?.external?.preset ?? base.kafkaPreset,
|
|
640
|
+
kafkaBrokers: externalKafka?.external?.brokers ?? base.kafkaBrokers,
|
|
641
|
+
kafkaTopic: externalKafka?.external?.topic ?? base.kafkaTopic,
|
|
642
|
+
kafkaTopicPrefix: externalKafka?.external?.topicPrefix ?? base.kafkaTopicPrefix,
|
|
643
|
+
kafkaProvisionTopics: externalKafka?.external?.provisionTopics ?? base.kafkaProvisionTopics,
|
|
644
|
+
kafkaSsl: externalKafka?.external?.ssl ?? base.kafkaSsl,
|
|
645
|
+
kafkaSaslMechanism: externalKafka?.external?.sasl?.mechanism ?? base.kafkaSaslMechanism,
|
|
646
|
+
kafkaSaslRegion: externalKafka?.external?.sasl?.region ?? base.kafkaSaslRegion,
|
|
647
|
+
kafkaSaslUsername: externalKafka?.external?.sasl?.username ?? base.kafkaSaslUsername,
|
|
648
|
+
kafkaSaslPassword: externalKafka?.external?.sasl?.password ?? base.kafkaSaslPassword,
|
|
649
|
+
kafkaSaslExistingSecret: externalKafka?.external?.sasl?.existingSecret ??
|
|
650
|
+
base.kafkaSaslExistingSecret,
|
|
651
|
+
kafkaIdentityAwsRoleArn: externalKafka?.external?.identity?.awsRoleArn ??
|
|
652
|
+
base.kafkaIdentityAwsRoleArn,
|
|
653
|
+
kafkaIdentityGcpServiceAccountEmail: externalKafka?.external?.identity?.gcpServiceAccountEmail ??
|
|
654
|
+
base.kafkaIdentityGcpServiceAccountEmail,
|
|
655
|
+
kafkaIdentityAzureClientId: externalKafka?.external?.identity?.azureClientId ??
|
|
656
|
+
base.kafkaIdentityAzureClientId,
|
|
657
|
+
// External services - Postgres
|
|
658
|
+
postgresMode: externalPostgres?.mode ?? base.postgresMode,
|
|
659
|
+
postgresHost: externalPostgres?.external?.host ?? base.postgresHost,
|
|
660
|
+
postgresPort: externalPostgres?.external?.port ?? base.postgresPort,
|
|
661
|
+
postgresDatabase: externalPostgres?.external?.database ?? base.postgresDatabase,
|
|
662
|
+
postgresMasterUsername: externalPostgres?.external?.bootstrap?.masterUsername ??
|
|
663
|
+
base.postgresMasterUsername,
|
|
664
|
+
postgresMasterPassword: externalPostgres?.external?.bootstrap?.masterPassword ??
|
|
665
|
+
base.postgresMasterPassword,
|
|
666
|
+
licenseKey: config.licenseKey,
|
|
667
|
+
version: config.version,
|
|
668
|
+
chartVersion: config.chartVersion ?? "",
|
|
669
|
+
};
|
|
670
|
+
}
|
|
107
671
|
function wizardReducer(state, action) {
|
|
108
672
|
switch (action.type) {
|
|
109
673
|
case "SET_STEP":
|
|
110
674
|
return { ...state, step: action.step };
|
|
111
675
|
case "SET_NAME":
|
|
112
676
|
return { ...state, name: action.name };
|
|
113
|
-
case "SET_INFRA_MODE":
|
|
114
|
-
return { ...state, infrastructureMode: action.mode };
|
|
115
677
|
case "SET_PROVIDER":
|
|
116
|
-
|
|
678
|
+
if (action.provider === state.provider) {
|
|
679
|
+
return {
|
|
680
|
+
...state,
|
|
681
|
+
provider: action.provider,
|
|
682
|
+
region: "",
|
|
683
|
+
clusterName: "",
|
|
684
|
+
gcpProjectId: "",
|
|
685
|
+
azureResourceGroup: "",
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
...state,
|
|
690
|
+
provider: action.provider,
|
|
691
|
+
region: "",
|
|
692
|
+
clusterName: "",
|
|
693
|
+
gcpProjectId: "",
|
|
694
|
+
azureResourceGroup: "",
|
|
695
|
+
storageProvider: null,
|
|
696
|
+
storageBucket: "",
|
|
697
|
+
storageRegion: "",
|
|
698
|
+
storageAwsIamRoleArn: "",
|
|
699
|
+
storageAzureBlobContainer: "",
|
|
700
|
+
storageAzureBlobClientId: "",
|
|
701
|
+
storageAzureBlobTenantId: "",
|
|
702
|
+
storageAzureBlobConnectionStringSecretRef: "",
|
|
703
|
+
storageGcpServiceAccountEmail: "",
|
|
704
|
+
};
|
|
117
705
|
case "SET_REGION":
|
|
118
706
|
return { ...state, region: action.region };
|
|
119
707
|
case "SET_CLUSTER_NAME":
|
|
@@ -126,8 +714,6 @@ function wizardReducer(state, action) {
|
|
|
126
714
|
return { ...state, domain: action.domain };
|
|
127
715
|
case "SET_ADMIN_EMAIL":
|
|
128
716
|
return { ...state, adminEmail: action.email };
|
|
129
|
-
case "SET_TLS_EMAIL":
|
|
130
|
-
return { ...state, tlsEmail: action.email };
|
|
131
717
|
case "SET_DNS_PROVIDER":
|
|
132
718
|
// Reset auto-manage if switching to unsupported provider
|
|
133
719
|
return {
|
|
@@ -137,8 +723,6 @@ function wizardReducer(state, action) {
|
|
|
137
723
|
};
|
|
138
724
|
case "SET_DNS_AUTO_MANAGE":
|
|
139
725
|
return { ...state, dnsAutoManage: action.autoManage };
|
|
140
|
-
case "SET_EXISTING_EXTERNAL_DNS":
|
|
141
|
-
return { ...state, existingExternalDns: action.exists };
|
|
142
726
|
case "SET_SMTP":
|
|
143
727
|
return { ...state, ...action.config };
|
|
144
728
|
case "SET_DATABASE_TYPE":
|
|
@@ -147,8 +731,20 @@ function wizardReducer(state, action) {
|
|
|
147
731
|
return { ...state, ...action.config };
|
|
148
732
|
case "SET_SUPABASE_SELF_HOSTED":
|
|
149
733
|
return { ...state, ...action.config };
|
|
150
|
-
case "
|
|
151
|
-
return {
|
|
734
|
+
case "SET_CLUSTER_CAPABILITIES":
|
|
735
|
+
return {
|
|
736
|
+
...state,
|
|
737
|
+
nodeArchitecture: action.nodeArchitecture,
|
|
738
|
+
arm64TolerationRequired: action.arm64TolerationRequired,
|
|
739
|
+
storageClass: action.storageClass ?? state.storageClass,
|
|
740
|
+
storageProvisioner: action.storageProvisioner ?? state.storageProvisioner,
|
|
741
|
+
schedulableNodeCount: action.schedulableNodeCount ?? state.schedulableNodeCount,
|
|
742
|
+
totalCpuCores: action.totalCpuCores ?? state.totalCpuCores,
|
|
743
|
+
totalMemoryGi: action.totalMemoryGi ?? state.totalMemoryGi,
|
|
744
|
+
eligibleCpuCores: action.eligibleCpuCores ?? state.eligibleCpuCores,
|
|
745
|
+
eligibleMemoryGi: action.eligibleMemoryGi ?? state.eligibleMemoryGi,
|
|
746
|
+
totalPersistentStorageGi: action.totalPersistentStorageGi ?? state.totalPersistentStorageGi,
|
|
747
|
+
};
|
|
152
748
|
case "SET_AI_ENABLED":
|
|
153
749
|
return { ...state, aiEnabled: action.enabled };
|
|
154
750
|
case "SET_OPENAI_KEY":
|
|
@@ -157,22 +753,68 @@ function wizardReducer(state, action) {
|
|
|
157
753
|
return { ...state, ssoEnabled: action.enabled };
|
|
158
754
|
case "SET_SSO_CONFIG":
|
|
159
755
|
return { ...state, ...action.config };
|
|
160
|
-
case "
|
|
161
|
-
return {
|
|
756
|
+
case "SET_METRICS_EXPORT":
|
|
757
|
+
return {
|
|
758
|
+
...state,
|
|
759
|
+
clickStackEnabled: action.enabled ? false : state.clickStackEnabled,
|
|
760
|
+
metricsExportEnabled: action.enabled,
|
|
761
|
+
};
|
|
162
762
|
case "SET_PROMETHEUS_REMOTE_WRITE":
|
|
163
763
|
return { ...state, prometheusRemoteWriteUrl: action.url };
|
|
164
764
|
case "SET_PROMETHEUS_REMOTE_WRITE_CONFIG":
|
|
165
765
|
return { ...state, ...action.config };
|
|
166
766
|
case "SET_LOGGING_SINK":
|
|
167
|
-
// Reset
|
|
767
|
+
// Reset the platform credential/detail if switching back to console.
|
|
168
768
|
return {
|
|
169
769
|
...state,
|
|
170
770
|
loggingSink: action.sink,
|
|
171
|
-
|
|
172
|
-
|
|
771
|
+
loggingPlatformCredential: action.sink === "console" ? "" : state.loggingPlatformCredential,
|
|
772
|
+
loggingPlatformDetail: action.sink === "console" ? "" : state.loggingPlatformDetail,
|
|
173
773
|
};
|
|
774
|
+
case "SET_STORAGE_CONFIG":
|
|
775
|
+
return { ...state, ...action.config };
|
|
174
776
|
case "SET_LOGGING_CONFIG":
|
|
175
777
|
return { ...state, ...action.config };
|
|
778
|
+
case "SET_CLICKSTACK_ENABLED":
|
|
779
|
+
return {
|
|
780
|
+
...state,
|
|
781
|
+
clickStackEnabled: action.enabled,
|
|
782
|
+
metricsExportEnabled: action.enabled ? false : state.metricsExportEnabled,
|
|
783
|
+
tracingEnabled: action.enabled ? false : state.tracingEnabled,
|
|
784
|
+
appLogsEnabled: action.enabled ? false : state.appLogsEnabled,
|
|
785
|
+
};
|
|
786
|
+
case "SET_CLICKSTACK_CONFIG":
|
|
787
|
+
return { ...state, ...action.config };
|
|
788
|
+
case "SET_TRACING_ENABLED":
|
|
789
|
+
return {
|
|
790
|
+
...state,
|
|
791
|
+
clickStackEnabled: action.enabled ? false : state.clickStackEnabled,
|
|
792
|
+
tracingEnabled: action.enabled,
|
|
793
|
+
};
|
|
794
|
+
case "SET_TRACING_CONFIG":
|
|
795
|
+
return { ...state, ...action.config };
|
|
796
|
+
case "SET_APP_LOGS_ENABLED":
|
|
797
|
+
return {
|
|
798
|
+
...state,
|
|
799
|
+
clickStackEnabled: action.enabled ? false : state.clickStackEnabled,
|
|
800
|
+
appLogsEnabled: action.enabled,
|
|
801
|
+
};
|
|
802
|
+
case "SET_APP_LOGS_CONFIG":
|
|
803
|
+
return { ...state, ...action.config };
|
|
804
|
+
case "SET_BACKUP_ENABLED":
|
|
805
|
+
return { ...state, backupEnabled: action.enabled };
|
|
806
|
+
case "SET_BACKUP_SCHEDULE":
|
|
807
|
+
return { ...state, backupSchedule: action.schedule };
|
|
808
|
+
case "SET_BACKUP_RETENTION_DAYS":
|
|
809
|
+
return { ...state, backupRetentionDays: action.retentionDays };
|
|
810
|
+
case "SET_EXTERNAL_SERVICES":
|
|
811
|
+
return {
|
|
812
|
+
...state,
|
|
813
|
+
...action.config,
|
|
814
|
+
...(action.config.postgresMode === "external"
|
|
815
|
+
? { backupEnabled: false }
|
|
816
|
+
: {}),
|
|
817
|
+
};
|
|
176
818
|
case "SET_CUSTOM_EMAILS_ENABLED":
|
|
177
819
|
return { ...state, customEmailsEnabled: action.enabled };
|
|
178
820
|
case "SET_EMAIL_SUBJECTS":
|
|
@@ -187,11 +829,10 @@ function wizardReducer(state, action) {
|
|
|
187
829
|
};
|
|
188
830
|
case "SET_LICENSE_KEY":
|
|
189
831
|
return { ...state, licenseKey: action.key };
|
|
190
|
-
case "
|
|
832
|
+
case "SET_VERSION":
|
|
191
833
|
return {
|
|
192
834
|
...state,
|
|
193
|
-
|
|
194
|
-
hpsVersion: action.hpsVersion,
|
|
835
|
+
version: action.version,
|
|
195
836
|
};
|
|
196
837
|
case "SET_CHART_VERSION":
|
|
197
838
|
return { ...state, chartVersion: action.version };
|
|
@@ -204,101 +845,58 @@ function wizardReducer(state, action) {
|
|
|
204
845
|
}
|
|
205
846
|
}
|
|
206
847
|
const WizardContext = createContext(null);
|
|
207
|
-
export function WizardProvider({ children, initialName, profile, }) {
|
|
848
|
+
export function WizardProvider({ children, initialName, initialState, profile, }) {
|
|
208
849
|
// Initialize state with profile values for pre-population
|
|
209
850
|
const [state, dispatch] = useReducer(wizardReducer, {
|
|
210
851
|
...getInitialState(profile),
|
|
211
|
-
|
|
852
|
+
...initialState,
|
|
853
|
+
name: initialState?.name || initialName || "",
|
|
212
854
|
});
|
|
213
855
|
const toConfig = (options = {}) => {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
!state.tlsEmail ||
|
|
219
|
-
!state.licenseKey) {
|
|
856
|
+
// All field/credential gates (including the remote_write checks) live in one
|
|
857
|
+
// place so the review screen can show the specific reason a config is
|
|
858
|
+
// invalid instead of a generic message.
|
|
859
|
+
if (collectConfigIssues(state).length > 0) {
|
|
220
860
|
return null;
|
|
221
861
|
}
|
|
222
|
-
//
|
|
223
|
-
if (!state.
|
|
224
|
-
!state.smtpUser ||
|
|
225
|
-
!state.smtpPass ||
|
|
226
|
-
!state.smtpFrom) {
|
|
862
|
+
// collectConfigIssues guarantees these, but narrow them for the type checker.
|
|
863
|
+
if (!state.storageProvider) {
|
|
227
864
|
return null;
|
|
228
865
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
!state.supabaseAnonKey ||
|
|
233
|
-
!state.supabaseServiceKey) {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
else if (state.databaseType === "self-hosted") {
|
|
238
|
-
if (!state.supabaseDbPassword) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Validate logging sink config
|
|
243
|
-
if (state.loggingSink !== "console" && !state.loggingBucket) {
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
if (state.loggingSink === "azure-blob" &&
|
|
247
|
-
(!state.loggingAzureBlobContainer ||
|
|
248
|
-
(state.loggingCloudAuthMode === "workload-identity" &&
|
|
249
|
-
(!state.loggingAzureBlobClientId || !state.loggingAzureBlobTenantId)) ||
|
|
250
|
-
(state.loggingCloudAuthMode === "secret" &&
|
|
251
|
-
!parseSecretKeyRef(state.loggingAzureBlobConnectionStringSecretRef)))) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
if (state.loggingSink === "s3" && !state.loggingAwsIamRoleArn) {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
if (state.loggingSink === "gcs" && !state.loggingGcpServiceAccountEmail) {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
const remoteWrite = state.monitoringEnabled &&
|
|
261
|
-
state.prometheusMonitoringDestination !== "local-grafana" &&
|
|
262
|
-
state.prometheusRemoteWriteDestination &&
|
|
263
|
-
state.prometheusRemoteWriteUrl
|
|
264
|
-
? {
|
|
265
|
-
destination: state.prometheusRemoteWriteDestination,
|
|
266
|
-
url: state.prometheusRemoteWriteUrl,
|
|
267
|
-
authType: state.prometheusRemoteWriteAuthType || undefined,
|
|
268
|
-
awsRegion: state.prometheusRemoteWriteDestination === "aws-amp"
|
|
269
|
-
? state.prometheusRemoteWriteAwsRegion ||
|
|
270
|
-
state.region ||
|
|
271
|
-
undefined
|
|
272
|
-
: undefined,
|
|
273
|
-
awsRoleArn: state.prometheusRemoteWriteDestination === "aws-amp"
|
|
274
|
-
? state.prometheusRemoteWriteAwsRoleArn || undefined
|
|
275
|
-
: undefined,
|
|
276
|
-
azureCloud: state.prometheusRemoteWriteAzureCloud,
|
|
277
|
-
clientId: state.prometheusRemoteWriteClientId || undefined,
|
|
278
|
-
tenantId: state.prometheusRemoteWriteTenantId || undefined,
|
|
279
|
-
clientSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteSecretRef),
|
|
280
|
-
usernameSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteUsernameSecretRef),
|
|
281
|
-
passwordSecretRef: parseSecretKeyRef(state.prometheusRemoteWritePasswordSecretRef),
|
|
282
|
-
bearerTokenSecretRef: parseSecretKeyRef(state.prometheusRemoteWriteBearerTokenSecretRef),
|
|
283
|
-
}
|
|
866
|
+
const externalServices = buildExternalServices(state);
|
|
867
|
+
const remoteWrite = !state.clickStackEnabled && state.metricsExportEnabled
|
|
868
|
+
? buildRemoteWriteFromState(state)
|
|
284
869
|
: undefined;
|
|
285
870
|
return {
|
|
286
871
|
name: state.name,
|
|
287
872
|
infrastructure: {
|
|
288
|
-
mode:
|
|
873
|
+
mode: "existing",
|
|
289
874
|
provider: state.provider || undefined,
|
|
290
875
|
region: state.region || undefined,
|
|
291
876
|
clusterName: state.clusterName || undefined,
|
|
292
877
|
gcpProjectId: state.gcpProjectId || undefined,
|
|
293
878
|
azureResourceGroup: state.azureResourceGroup || undefined,
|
|
879
|
+
nodeArchitecture: options.nodeArchitecture || state.nodeArchitecture || undefined,
|
|
880
|
+
arm64TolerationRequired: options.arm64TolerationRequired ?? state.arm64TolerationRequired,
|
|
881
|
+
storageClass: options.storageClass || state.storageClass || undefined,
|
|
882
|
+
storageProvisioner: options.storageProvisioner || state.storageProvisioner || undefined,
|
|
883
|
+
schedulableNodeCount: options.schedulableNodeCount || state.schedulableNodeCount || undefined,
|
|
884
|
+
totalCpuCores: options.totalCpuCores || state.totalCpuCores || undefined,
|
|
885
|
+
totalMemoryGi: options.totalMemoryGi || state.totalMemoryGi || undefined,
|
|
886
|
+
eligibleCpuCores: options.eligibleCpuCores || state.eligibleCpuCores || undefined,
|
|
887
|
+
eligibleMemoryGi: options.eligibleMemoryGi || state.eligibleMemoryGi || undefined,
|
|
888
|
+
totalPersistentStorageGi: options.totalPersistentStorageGi ||
|
|
889
|
+
state.totalPersistentStorageGi ||
|
|
890
|
+
undefined,
|
|
294
891
|
},
|
|
295
892
|
domain: state.domain,
|
|
296
893
|
adminEmail: state.adminEmail,
|
|
297
|
-
|
|
894
|
+
// Not asked in the wizard; defaults to the admin email. A custom value
|
|
895
|
+
// survives redeploys via configToWizardState / config.yaml edits.
|
|
896
|
+
tlsEmail: state.tlsEmail || state.adminEmail,
|
|
298
897
|
dns: {
|
|
299
898
|
provider: state.dnsProvider,
|
|
300
899
|
autoManage: state.dnsAutoManage,
|
|
301
|
-
existingExternalDns: state.existingExternalDns || undefined,
|
|
302
900
|
},
|
|
303
901
|
smtp: {
|
|
304
902
|
host: state.smtpHost,
|
|
@@ -314,13 +912,52 @@ export function WizardProvider({ children, initialName, profile, }) {
|
|
|
314
912
|
supabaseAnonKey: state.supabaseAnonKey || undefined,
|
|
315
913
|
supabaseServiceKey: state.supabaseServiceKey || undefined,
|
|
316
914
|
supabaseAccessToken: state.supabaseAccessToken || undefined,
|
|
317
|
-
|
|
915
|
+
// Derived from the project URL when not set explicitly (config.yaml
|
|
916
|
+
// edits and redeploy merges still win).
|
|
917
|
+
supabaseProjectRef: state.supabaseProjectRef ||
|
|
918
|
+
(state.databaseType === "supabase-cloud" && state.supabaseUrl
|
|
919
|
+
? deriveSupabaseProjectRef(state.supabaseUrl)
|
|
920
|
+
: undefined) ||
|
|
921
|
+
undefined,
|
|
318
922
|
supabaseJwtSecret: state.supabaseJwtSecret || undefined,
|
|
319
923
|
supabaseDbPassword: state.supabaseDbPassword || undefined,
|
|
320
924
|
supabaseDashboardUser: state.supabaseDashboardUser || undefined,
|
|
321
925
|
supabaseDashboardPass: state.supabaseDashboardPass || undefined,
|
|
322
926
|
},
|
|
323
|
-
|
|
927
|
+
storage: {
|
|
928
|
+
provider: state.storageProvider,
|
|
929
|
+
cloudAuthMode: state.storageCloudAuthMode,
|
|
930
|
+
bucket: state.storageBucket,
|
|
931
|
+
region: state.storageRegion,
|
|
932
|
+
awsIamRoleArn: state.storageProvider === "s3"
|
|
933
|
+
? state.storageAwsIamRoleArn || undefined
|
|
934
|
+
: undefined,
|
|
935
|
+
azureBlobClientId: state.storageProvider === "azure-blob"
|
|
936
|
+
? state.storageAzureBlobClientId || undefined
|
|
937
|
+
: undefined,
|
|
938
|
+
azureBlobTenantId: state.storageProvider === "azure-blob"
|
|
939
|
+
? state.storageAzureBlobTenantId || undefined
|
|
940
|
+
: undefined,
|
|
941
|
+
azureBlobConnectionStringSecretRef: state.storageProvider === "azure-blob"
|
|
942
|
+
? parseSecretKeyRef(state.storageAzureBlobConnectionStringSecretRef)
|
|
943
|
+
: undefined,
|
|
944
|
+
azureBlobContainer: state.storageProvider === "azure-blob"
|
|
945
|
+
? state.storageAzureBlobContainer || undefined
|
|
946
|
+
: undefined,
|
|
947
|
+
gcpServiceAccountEmail: state.storageProvider === "gcs"
|
|
948
|
+
? state.storageGcpServiceAccountEmail || undefined
|
|
949
|
+
: undefined,
|
|
950
|
+
paths: {
|
|
951
|
+
decisionLogs: "decision-logs",
|
|
952
|
+
dbBackups: "db-backups",
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
backup: {
|
|
956
|
+
enabled: state.databaseType === "self-hosted" ? state.backupEnabled : false,
|
|
957
|
+
schedule: state.backupSchedule || "0 2 * * *",
|
|
958
|
+
retentionDays: state.backupRetentionDays || 7,
|
|
959
|
+
},
|
|
960
|
+
externalServices,
|
|
324
961
|
features: {
|
|
325
962
|
ai: {
|
|
326
963
|
enabled: state.aiEnabled,
|
|
@@ -334,39 +971,129 @@ export function WizardProvider({ children, initialName, profile, }) {
|
|
|
334
971
|
clientSecret: state.ssoClientSecret || undefined,
|
|
335
972
|
},
|
|
336
973
|
monitoring: {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
974
|
+
// In-cluster Prometheus is always installed.
|
|
975
|
+
enabled: true,
|
|
976
|
+
destination: !state.clickStackEnabled && state.metricsExportEnabled
|
|
977
|
+
? state.prometheusMonitoringDestination ||
|
|
978
|
+
remoteWrite?.destination ||
|
|
979
|
+
undefined
|
|
980
|
+
: // "local-grafana" is a config-file-only option (in-cluster
|
|
981
|
+
// Grafana, no remote write) and must survive redeploys.
|
|
982
|
+
state.prometheusMonitoringDestination === "local-grafana"
|
|
983
|
+
? "local-grafana"
|
|
984
|
+
: undefined,
|
|
985
|
+
remoteWriteUrl: !state.clickStackEnabled && state.metricsExportEnabled
|
|
986
|
+
? state.prometheusRemoteWriteUrl || undefined
|
|
987
|
+
: undefined,
|
|
342
988
|
remoteWrite,
|
|
343
989
|
},
|
|
990
|
+
observability: {
|
|
991
|
+
clickstack: {
|
|
992
|
+
enabled: state.clickStackEnabled,
|
|
993
|
+
telemetryRetentionDays: state.clickStackTelemetryRetentionDays,
|
|
994
|
+
clickHouseStorageSize: state.clickHouseStorageSize,
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
// Distributed tracing (self-hosted only). Omitted when disabled. The
|
|
998
|
+
// destination selects which backend sub-block is emitted.
|
|
999
|
+
tracing: !state.clickStackEnabled && state.tracingEnabled
|
|
1000
|
+
? state.tracingDestination === "otlp"
|
|
1001
|
+
? {
|
|
1002
|
+
enabled: true,
|
|
1003
|
+
destination: "otlp",
|
|
1004
|
+
otlp: {
|
|
1005
|
+
endpoint: state.tracingOtlpEndpoint || undefined,
|
|
1006
|
+
authMode: state.tracingOtlpAuthMode,
|
|
1007
|
+
headerName: state.tracingOtlpAuthMode === "header"
|
|
1008
|
+
? state.tracingOtlpHeaderName || undefined
|
|
1009
|
+
: undefined,
|
|
1010
|
+
token: state.tracingOtlpAuthMode === "bearer"
|
|
1011
|
+
? state.tracingOtlpToken || undefined
|
|
1012
|
+
: undefined,
|
|
1013
|
+
apiKey: state.tracingOtlpAuthMode === "api-key"
|
|
1014
|
+
? state.tracingOtlpToken || undefined
|
|
1015
|
+
: undefined,
|
|
1016
|
+
headerValue: state.tracingOtlpAuthMode === "header"
|
|
1017
|
+
? state.tracingOtlpToken || undefined
|
|
1018
|
+
: undefined,
|
|
1019
|
+
},
|
|
1020
|
+
}
|
|
1021
|
+
: state.tracingDestination === "azure-monitor"
|
|
1022
|
+
? {
|
|
1023
|
+
enabled: true,
|
|
1024
|
+
destination: "azure-monitor",
|
|
1025
|
+
azureMonitor: {
|
|
1026
|
+
connectionString: state.tracingAzureConnectionString || undefined,
|
|
1027
|
+
},
|
|
1028
|
+
}
|
|
1029
|
+
: {
|
|
1030
|
+
enabled: true,
|
|
1031
|
+
destination: "elastic",
|
|
1032
|
+
elastic: {
|
|
1033
|
+
endpoint: state.tracingElasticEndpoint || undefined,
|
|
1034
|
+
authMode: state.tracingElasticAuthMode,
|
|
1035
|
+
secretToken: state.tracingElasticAuthMode === "secret-token"
|
|
1036
|
+
? state.tracingElasticSecretToken || undefined
|
|
1037
|
+
: undefined,
|
|
1038
|
+
apiKey: state.tracingElasticAuthMode === "api-key"
|
|
1039
|
+
? state.tracingElasticApiKey || undefined
|
|
1040
|
+
: undefined,
|
|
1041
|
+
},
|
|
1042
|
+
}
|
|
1043
|
+
: undefined,
|
|
1044
|
+
cache: state.valkeyAdminEnabled ||
|
|
1045
|
+
state.redisExporterEnabled ||
|
|
1046
|
+
state.kafkaExporterEnabled
|
|
1047
|
+
? {
|
|
1048
|
+
valkeyAdmin: state.valkeyAdminEnabled
|
|
1049
|
+
? {
|
|
1050
|
+
enabled: true,
|
|
1051
|
+
exposure: state.valkeyAdminExposure,
|
|
1052
|
+
hostname: state.valkeyAdminHostname || undefined,
|
|
1053
|
+
basicAuthUsers: state.valkeyAdminBasicAuthUsers.length > 0
|
|
1054
|
+
? state.valkeyAdminBasicAuthUsers
|
|
1055
|
+
: undefined,
|
|
1056
|
+
allowedIPs: state.valkeyAdminAllowedIPs.length > 0
|
|
1057
|
+
? state.valkeyAdminAllowedIPs
|
|
1058
|
+
: undefined,
|
|
1059
|
+
}
|
|
1060
|
+
: undefined,
|
|
1061
|
+
redisExporter: state.redisExporterEnabled
|
|
1062
|
+
? { enabled: true }
|
|
1063
|
+
: undefined,
|
|
1064
|
+
kafkaExporter: state.kafkaExporterEnabled
|
|
1065
|
+
? { enabled: true }
|
|
1066
|
+
: undefined,
|
|
1067
|
+
}
|
|
1068
|
+
: undefined,
|
|
344
1069
|
logging: {
|
|
1070
|
+
// External logging is now a platform-only sink (Datadog, Splunk,
|
|
1071
|
+
// etc.). The persisted bucket/region keys carry the platform
|
|
1072
|
+
// credential and endpoint/detail (not an object-storage bucket).
|
|
1073
|
+
// Cloud object storage for decision logs is configured separately
|
|
1074
|
+
// under `storage` above.
|
|
345
1075
|
sink: state.loggingSink,
|
|
346
|
-
bucket: state.
|
|
347
|
-
region: state.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
: undefined,
|
|
368
|
-
gcpServiceAccountEmail: state.loggingSink === "gcs"
|
|
369
|
-
? state.loggingGcpServiceAccountEmail || undefined
|
|
1076
|
+
bucket: state.loggingPlatformCredential || undefined,
|
|
1077
|
+
region: state.loggingPlatformDetail || undefined,
|
|
1078
|
+
// Application/container log shipping to Elasticsearch (Vector agent).
|
|
1079
|
+
appLogs: !state.clickStackEnabled && state.appLogsEnabled
|
|
1080
|
+
? {
|
|
1081
|
+
enabled: true,
|
|
1082
|
+
elasticsearch: {
|
|
1083
|
+
endpoint: state.appLogsElasticEndpoint || undefined,
|
|
1084
|
+
index: state.appLogsElasticIndex || undefined,
|
|
1085
|
+
authMode: state.appLogsElasticAuthMode,
|
|
1086
|
+
username: state.appLogsElasticAuthMode === "basic"
|
|
1087
|
+
? state.appLogsElasticUsername || undefined
|
|
1088
|
+
: undefined,
|
|
1089
|
+
password: state.appLogsElasticAuthMode === "basic"
|
|
1090
|
+
? state.appLogsElasticPassword || undefined
|
|
1091
|
+
: undefined,
|
|
1092
|
+
apiKey: state.appLogsElasticAuthMode === "api-key"
|
|
1093
|
+
? state.appLogsElasticApiKey || undefined
|
|
1094
|
+
: undefined,
|
|
1095
|
+
},
|
|
1096
|
+
}
|
|
370
1097
|
: undefined,
|
|
371
1098
|
},
|
|
372
1099
|
customEmails: state.customEmailsEnabled
|
|
@@ -383,51 +1110,36 @@ export function WizardProvider({ children, initialName, profile, }) {
|
|
|
383
1110
|
: undefined,
|
|
384
1111
|
},
|
|
385
1112
|
licenseKey: state.licenseKey,
|
|
386
|
-
|
|
387
|
-
hpsVersion: state.hpsVersion || undefined,
|
|
1113
|
+
version: state.version,
|
|
388
1114
|
chartVersion: state.chartVersion || undefined,
|
|
389
1115
|
};
|
|
390
1116
|
};
|
|
391
1117
|
const skipToStep = (stepId) => {
|
|
392
|
-
// For conditional step skipping
|
|
393
1118
|
const stepIndex = [
|
|
394
|
-
"mode",
|
|
395
1119
|
"cloud",
|
|
396
1120
|
"domain",
|
|
397
1121
|
"smtp",
|
|
398
1122
|
"database",
|
|
399
1123
|
"database-creds",
|
|
400
|
-
"
|
|
1124
|
+
"external-services",
|
|
1125
|
+
"storage",
|
|
1126
|
+
"observability",
|
|
401
1127
|
"features",
|
|
402
1128
|
"feature-config",
|
|
403
|
-
"
|
|
1129
|
+
"version",
|
|
404
1130
|
"review",
|
|
405
1131
|
].indexOf(stepId);
|
|
406
1132
|
if (stepIndex >= 0) {
|
|
407
1133
|
dispatch({ type: "SET_STEP", step: stepIndex });
|
|
408
1134
|
}
|
|
409
1135
|
};
|
|
410
|
-
/**
|
|
411
|
-
* Suggests a domain based on the profile's domain suffix and a deployment name.
|
|
412
|
-
* e.g., if profile has domainSuffix ".example.com" and name is "staging",
|
|
413
|
-
* suggests "staging.example.com"
|
|
414
|
-
*/
|
|
415
|
-
const suggestDomain = (name) => {
|
|
416
|
-
if (!profile?.domainSuffix || !name)
|
|
417
|
-
return "";
|
|
418
|
-
// Remove leading dot if present and combine with name
|
|
419
|
-
const suffix = profile.domainSuffix.startsWith(".")
|
|
420
|
-
? profile.domainSuffix.slice(1)
|
|
421
|
-
: profile.domainSuffix;
|
|
422
|
-
return `${name}.${suffix}`;
|
|
423
|
-
};
|
|
424
1136
|
return (_jsx(WizardContext.Provider, { value: {
|
|
425
1137
|
state,
|
|
426
1138
|
dispatch,
|
|
427
1139
|
toConfig,
|
|
1140
|
+
configIssues: () => collectConfigIssues(state),
|
|
428
1141
|
skipToStep,
|
|
429
1142
|
profile: profile ?? null,
|
|
430
|
-
suggestDomain,
|
|
431
1143
|
}, children: children }));
|
|
432
1144
|
}
|
|
433
1145
|
export function useWizard() {
|