@rulebricks/cli 1.9.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/README.md +62 -0
- package/dist/commands/clone.d.ts +6 -0
- package/dist/commands/clone.js +60 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.js +409 -0
- package/dist/commands/destroy.d.ts +8 -0
- package/dist/commands/destroy.js +298 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +201 -0
- package/dist/commands/logs.d.ts +9 -0
- package/dist/commands/logs.js +222 -0
- package/dist/commands/open.d.ts +7 -0
- package/dist/commands/open.js +139 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +125 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +239 -0
- package/dist/components/DNSWaitScreen.d.ts +9 -0
- package/dist/components/DNSWaitScreen.js +73 -0
- package/dist/components/Wizard/WizardContext.d.ts +176 -0
- package/dist/components/Wizard/WizardContext.js +346 -0
- package/dist/components/Wizard/index.d.ts +2 -0
- package/dist/components/Wizard/index.js +2 -0
- package/dist/components/Wizard/steps/CloudProviderStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CloudProviderStep.js +210 -0
- package/dist/components/Wizard/steps/CredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/CredentialsStep.js +22 -0
- package/dist/components/Wizard/steps/DatabaseStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DatabaseStep.js +80 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +5 -0
- package/dist/components/Wizard/steps/DeploymentModeStep.js +26 -0
- package/dist/components/Wizard/steps/DomainStep.d.ts +6 -0
- package/dist/components/Wizard/steps/DomainStep.js +126 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeatureConfigStep.js +765 -0
- package/dist/components/Wizard/steps/FeaturesStep.d.ts +6 -0
- package/dist/components/Wizard/steps/FeaturesStep.js +119 -0
- package/dist/components/Wizard/steps/ReviewStep.d.ts +6 -0
- package/dist/components/Wizard/steps/ReviewStep.js +56 -0
- package/dist/components/Wizard/steps/SMTPStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SMTPStep.js +191 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.d.ts +6 -0
- package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +76 -0
- package/dist/components/Wizard/steps/TierStep.d.ts +6 -0
- package/dist/components/Wizard/steps/TierStep.js +29 -0
- package/dist/components/Wizard/steps/VersionStep.d.ts +6 -0
- package/dist/components/Wizard/steps/VersionStep.js +113 -0
- package/dist/components/Wizard/steps/index.d.ts +12 -0
- package/dist/components/Wizard/steps/index.js +12 -0
- package/dist/components/common/AppShell.d.ts +31 -0
- package/dist/components/common/AppShell.js +31 -0
- package/dist/components/common/Box.d.ts +20 -0
- package/dist/components/common/Box.js +20 -0
- package/dist/components/common/Logo.d.ts +7 -0
- package/dist/components/common/Logo.js +22 -0
- package/dist/components/common/Spinner.d.ts +12 -0
- package/dist/components/common/Spinner.js +28 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/index.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +202 -0
- package/dist/lib/cloudCli.d.ts +156 -0
- package/dist/lib/cloudCli.js +691 -0
- package/dist/lib/config.d.ts +91 -0
- package/dist/lib/config.js +278 -0
- package/dist/lib/dns.d.ts +41 -0
- package/dist/lib/dns.js +235 -0
- package/dist/lib/dockerHub.d.ts +57 -0
- package/dist/lib/dockerHub.js +128 -0
- package/dist/lib/helm.d.ts +53 -0
- package/dist/lib/helm.js +209 -0
- package/dist/lib/helmValues.d.ts +17 -0
- package/dist/lib/helmValues.js +693 -0
- package/dist/lib/kubernetes.d.ts +161 -0
- package/dist/lib/kubernetes.js +755 -0
- package/dist/lib/terraform.d.ts +44 -0
- package/dist/lib/terraform.js +230 -0
- package/dist/lib/theme.d.ts +81 -0
- package/dist/lib/theme.js +115 -0
- package/dist/lib/validation.d.ts +47 -0
- package/dist/lib/validation.js +164 -0
- package/dist/lib/versions.d.ts +69 -0
- package/dist/lib/versions.js +139 -0
- package/dist/types/index.d.ts +718 -0
- package/dist/types/index.js +556 -0
- package/email-templates/email_change.html +325 -0
- package/email-templates/invite.html +383 -0
- package/email-templates/password_change.html +414 -0
- package/email-templates/verify.html +396 -0
- package/package.json +78 -0
- package/terraform/aws/main.tf +327 -0
- package/terraform/azure/main.tf +326 -0
- package/terraform/gcp/main.tf +369 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import SelectInput from "ink-select-input";
|
|
5
|
+
import TextInput from "ink-text-input";
|
|
6
|
+
import { useWizard } from "../WizardContext.js";
|
|
7
|
+
import { BorderBox, useTheme } from "../../common/index.js";
|
|
8
|
+
import { Spinner } from "../../common/Spinner.js";
|
|
9
|
+
import { LOGGING_SINK_INFO, CLOUD_REGIONS, DEFAULT_EMAIL_SUBJECTS, } from "../../../types/index.js";
|
|
10
|
+
import { listBucketsInRegion, listRegions } from "../../../lib/cloudCli.js";
|
|
11
|
+
const SSO_PROVIDERS = [
|
|
12
|
+
{ label: "Microsoft Azure AD", value: "azure" },
|
|
13
|
+
{ label: "Google Workspace", value: "google" },
|
|
14
|
+
{ label: "Okta", value: "okta" },
|
|
15
|
+
{ label: "Keycloak", value: "keycloak" },
|
|
16
|
+
{ label: "Ory", value: "ory" },
|
|
17
|
+
{ label: "Other OIDC Provider", value: "other" },
|
|
18
|
+
];
|
|
19
|
+
const LOGGING_CATEGORIES = [
|
|
20
|
+
{ label: "Cloud Storage (S3, Azure Blob, GCS)", value: "cloud-storage" },
|
|
21
|
+
{
|
|
22
|
+
label: "Logging Platform (Datadog, Splunk, etc.)",
|
|
23
|
+
value: "logging-platform",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
const CLOUD_STORAGE_SINKS = [
|
|
27
|
+
{ label: "AWS S3", value: "s3" },
|
|
28
|
+
{ label: "Azure Blob Storage", value: "azure-blob" },
|
|
29
|
+
{ label: "Google Cloud Storage", value: "gcs" },
|
|
30
|
+
];
|
|
31
|
+
const LOGGING_PLATFORM_SINKS = [
|
|
32
|
+
{ label: "Datadog", value: "datadog" },
|
|
33
|
+
{ label: "Splunk (HEC)", value: "splunk" },
|
|
34
|
+
{ label: "Elasticsearch", value: "elasticsearch" },
|
|
35
|
+
{ label: "Grafana Loki", value: "loki" },
|
|
36
|
+
{ label: "New Relic", value: "newrelic" },
|
|
37
|
+
{ label: "Axiom", value: "axiom" },
|
|
38
|
+
];
|
|
39
|
+
// Datadog sites
|
|
40
|
+
const DATADOG_SITES = [
|
|
41
|
+
{ label: "US1 (datadoghq.com)", value: "datadoghq.com" },
|
|
42
|
+
{ label: "US3 (us3.datadoghq.com)", value: "us3.datadoghq.com" },
|
|
43
|
+
{ label: "US5 (us5.datadoghq.com)", value: "us5.datadoghq.com" },
|
|
44
|
+
{ label: "EU1 (datadoghq.eu)", value: "datadoghq.eu" },
|
|
45
|
+
{ label: "AP1 (ap1.datadoghq.com)", value: "ap1.datadoghq.com" },
|
|
46
|
+
];
|
|
47
|
+
function BucketSelector({ loggingSink, loggingRegion, availableBuckets, isRefreshing, onSelect, onRefresh, colors, }) {
|
|
48
|
+
useInput((input) => {
|
|
49
|
+
if (input.toLowerCase() === "r") {
|
|
50
|
+
onRefresh();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const bucketItems = availableBuckets.map((b) => ({ label: b, value: b }));
|
|
54
|
+
const hasBuckets = availableBuckets.length > 0;
|
|
55
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [loggingSink === "s3" && "Select S3 Bucket", loggingSink === "azure-blob" && "Select Azure Storage Account", loggingSink === "gcs" && "Select GCS Bucket"] }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Select an existing bucket in ", loggingRegion] }), isRefreshing ? (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Refreshing bucket list..." }) })) : hasBuckets ? (_jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: bucketItems, onSelect: onSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["No buckets found in ", loggingRegion, "."] }), _jsx(Text, { color: "gray", dimColor: true, children: "Create a bucket in your cloud console, then press R to refresh." })] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Sink: ", LOGGING_SINK_INFO[loggingSink]?.name] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Region: ", loggingRegion] })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "R to refresh list \u2022 \u2191/\u2193 to navigate \u2022 Enter to select" }) })] }));
|
|
56
|
+
}
|
|
57
|
+
const YES_NO_OPTIONS = [
|
|
58
|
+
{ label: "No, just collect metrics locally", value: false },
|
|
59
|
+
{ label: "Yes, send metrics to external system", value: true },
|
|
60
|
+
];
|
|
61
|
+
export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
62
|
+
const { state, dispatch } = useWizard();
|
|
63
|
+
const { colors } = useTheme();
|
|
64
|
+
// Determine what needs to be configured
|
|
65
|
+
const needsAI = state.aiEnabled && !state.openaiApiKey;
|
|
66
|
+
const needsSSO = state.ssoEnabled;
|
|
67
|
+
const needsMonitoring = state.monitoringEnabled;
|
|
68
|
+
const needsLogging = state.loggingSink !== "console";
|
|
69
|
+
const needsCustomEmails = state.customEmailsEnabled;
|
|
70
|
+
// Configuration order: AI -> SSO -> Monitoring -> Logging -> Custom Emails
|
|
71
|
+
const getInitialStep = () => {
|
|
72
|
+
if (needsAI)
|
|
73
|
+
return "openai-key";
|
|
74
|
+
if (needsSSO)
|
|
75
|
+
return "sso-provider";
|
|
76
|
+
if (needsMonitoring)
|
|
77
|
+
return "monitoring-remote-write-ask";
|
|
78
|
+
if (needsLogging)
|
|
79
|
+
return "logging-category";
|
|
80
|
+
if (needsCustomEmails)
|
|
81
|
+
return "email-subject-invite";
|
|
82
|
+
return "done";
|
|
83
|
+
};
|
|
84
|
+
const [subStep, setSubStep] = useState(getInitialStep);
|
|
85
|
+
const [openaiKey, setOpenaiKey] = useState(state.openaiApiKey || "");
|
|
86
|
+
const [ssoProvider, setSsoProvider] = useState(state.ssoProvider);
|
|
87
|
+
const [ssoUrl, setSsoUrl] = useState(state.ssoUrl || "");
|
|
88
|
+
const [ssoClientId, setSsoClientId] = useState(state.ssoClientId || "");
|
|
89
|
+
const [ssoClientSecret, setSsoClientSecret] = useState(state.ssoClientSecret || "");
|
|
90
|
+
const [remoteWriteUrl, setRemoteWriteUrl] = useState(state.prometheusRemoteWriteUrl || "");
|
|
91
|
+
const [loggingSink, setLoggingSink] = useState(state.loggingSink);
|
|
92
|
+
const [loggingBucket, setLoggingBucket] = useState(state.loggingBucket || "");
|
|
93
|
+
const [loggingRegion, setLoggingRegion] = useState(state.loggingRegion || "");
|
|
94
|
+
const [loggingCategory, setLoggingCategory] = useState(null);
|
|
95
|
+
const [error, setError] = useState(null);
|
|
96
|
+
// Logging platform config
|
|
97
|
+
const [datadogApiKey, setDatadogApiKey] = useState("");
|
|
98
|
+
const [datadogSite, setDatadogSite] = useState("datadoghq.com");
|
|
99
|
+
const [splunkHecToken, setSplunkHecToken] = useState("");
|
|
100
|
+
const [splunkUrl, setSplunkUrl] = useState("");
|
|
101
|
+
const [elasticsearchUrl, setElasticsearchUrl] = useState("");
|
|
102
|
+
const [elasticsearchUser, setElasticsearchUser] = useState("");
|
|
103
|
+
const [elasticsearchPass, setElasticsearchPass] = useState("");
|
|
104
|
+
const [elasticsearchIndex, setElasticsearchIndex] = useState("rulebricks-logs");
|
|
105
|
+
const [lokiUrl, setLokiUrl] = useState("");
|
|
106
|
+
const [newrelicLicenseKey, setNewrelicLicenseKey] = useState("");
|
|
107
|
+
const [newrelicAccountId, setNewrelicAccountId] = useState("");
|
|
108
|
+
const [axiomApiToken, setAxiomApiToken] = useState("");
|
|
109
|
+
const [axiomDataset, setAxiomDataset] = useState("rulebricks");
|
|
110
|
+
// Dynamic bucket/region lists
|
|
111
|
+
const [availableBuckets, setAvailableBuckets] = useState([]);
|
|
112
|
+
const [availableRegions, setAvailableRegions] = useState([]);
|
|
113
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
114
|
+
// Custom email templates
|
|
115
|
+
const [emailSubjectInvite, setEmailSubjectInvite] = useState(state.emailSubjects?.invite || DEFAULT_EMAIL_SUBJECTS.invite);
|
|
116
|
+
const [emailSubjectConfirm, setEmailSubjectConfirm] = useState(state.emailSubjects?.confirmation || DEFAULT_EMAIL_SUBJECTS.confirmation);
|
|
117
|
+
const [emailSubjectRecovery, setEmailSubjectRecovery] = useState(state.emailSubjects?.recovery || DEFAULT_EMAIL_SUBJECTS.recovery);
|
|
118
|
+
const [emailSubjectChange, setEmailSubjectChange] = useState(state.emailSubjects?.emailChange || DEFAULT_EMAIL_SUBJECTS.emailChange);
|
|
119
|
+
const [emailTemplateInvite, setEmailTemplateInvite] = useState(state.emailTemplates?.invite || "");
|
|
120
|
+
const [emailTemplateConfirm, setEmailTemplateConfirm] = useState(state.emailTemplates?.confirmation || "");
|
|
121
|
+
const [emailTemplateRecovery, setEmailTemplateRecovery] = useState(state.emailTemplates?.recovery || "");
|
|
122
|
+
const [emailTemplateChange, setEmailTemplateChange] = useState(state.emailTemplates?.emailChange || "");
|
|
123
|
+
// If nothing needs configuration, complete immediately
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!needsAI &&
|
|
126
|
+
!needsSSO &&
|
|
127
|
+
!needsMonitoring &&
|
|
128
|
+
!needsLogging &&
|
|
129
|
+
!needsCustomEmails) {
|
|
130
|
+
onComplete();
|
|
131
|
+
}
|
|
132
|
+
}, []);
|
|
133
|
+
useInput((input, key) => {
|
|
134
|
+
if (key.escape) {
|
|
135
|
+
setError(null);
|
|
136
|
+
handleBack();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const handleBack = () => {
|
|
140
|
+
switch (subStep) {
|
|
141
|
+
case "openai-key":
|
|
142
|
+
onBack();
|
|
143
|
+
break;
|
|
144
|
+
case "sso-provider":
|
|
145
|
+
if (needsAI)
|
|
146
|
+
setSubStep("openai-key");
|
|
147
|
+
else
|
|
148
|
+
onBack();
|
|
149
|
+
break;
|
|
150
|
+
case "sso-url":
|
|
151
|
+
setSubStep("sso-provider");
|
|
152
|
+
break;
|
|
153
|
+
case "sso-client-id":
|
|
154
|
+
if (ssoProvider === "google")
|
|
155
|
+
setSubStep("sso-provider");
|
|
156
|
+
else
|
|
157
|
+
setSubStep("sso-url");
|
|
158
|
+
break;
|
|
159
|
+
case "sso-client-secret":
|
|
160
|
+
setSubStep("sso-client-id");
|
|
161
|
+
break;
|
|
162
|
+
case "monitoring-remote-write-ask":
|
|
163
|
+
if (needsSSO)
|
|
164
|
+
setSubStep("sso-client-secret");
|
|
165
|
+
else if (needsAI)
|
|
166
|
+
setSubStep("openai-key");
|
|
167
|
+
else
|
|
168
|
+
onBack();
|
|
169
|
+
break;
|
|
170
|
+
case "monitoring-remote-write-url":
|
|
171
|
+
setSubStep("monitoring-remote-write-ask");
|
|
172
|
+
break;
|
|
173
|
+
case "logging-category":
|
|
174
|
+
if (needsMonitoring)
|
|
175
|
+
setSubStep("monitoring-remote-write-ask");
|
|
176
|
+
else if (needsSSO)
|
|
177
|
+
setSubStep("sso-client-secret");
|
|
178
|
+
else if (needsAI)
|
|
179
|
+
setSubStep("openai-key");
|
|
180
|
+
else
|
|
181
|
+
onBack();
|
|
182
|
+
break;
|
|
183
|
+
case "logging-sink":
|
|
184
|
+
setSubStep("logging-category");
|
|
185
|
+
break;
|
|
186
|
+
case "logging-region":
|
|
187
|
+
case "logging-region-loading":
|
|
188
|
+
setSubStep("logging-sink");
|
|
189
|
+
break;
|
|
190
|
+
case "logging-bucket":
|
|
191
|
+
case "logging-bucket-loading":
|
|
192
|
+
setSubStep("logging-region");
|
|
193
|
+
break;
|
|
194
|
+
// Logging platform config steps
|
|
195
|
+
case "logging-datadog-config":
|
|
196
|
+
case "logging-splunk-config":
|
|
197
|
+
case "logging-elasticsearch-config":
|
|
198
|
+
case "logging-loki-config":
|
|
199
|
+
case "logging-newrelic-config":
|
|
200
|
+
case "logging-axiom-config":
|
|
201
|
+
setSubStep("logging-sink");
|
|
202
|
+
break;
|
|
203
|
+
// Email template steps
|
|
204
|
+
case "email-subject-invite":
|
|
205
|
+
if (needsLogging)
|
|
206
|
+
setSubStep("logging-category");
|
|
207
|
+
else if (needsMonitoring)
|
|
208
|
+
setSubStep("monitoring-remote-write-ask");
|
|
209
|
+
else if (needsSSO)
|
|
210
|
+
setSubStep("sso-client-secret");
|
|
211
|
+
else if (needsAI)
|
|
212
|
+
setSubStep("openai-key");
|
|
213
|
+
else
|
|
214
|
+
onBack();
|
|
215
|
+
break;
|
|
216
|
+
case "email-subject-confirm":
|
|
217
|
+
setSubStep("email-subject-invite");
|
|
218
|
+
break;
|
|
219
|
+
case "email-subject-recovery":
|
|
220
|
+
setSubStep("email-subject-confirm");
|
|
221
|
+
break;
|
|
222
|
+
case "email-subject-change":
|
|
223
|
+
setSubStep("email-subject-recovery");
|
|
224
|
+
break;
|
|
225
|
+
case "email-template-invite":
|
|
226
|
+
setSubStep("email-subject-change");
|
|
227
|
+
break;
|
|
228
|
+
case "email-template-confirm":
|
|
229
|
+
setSubStep("email-template-invite");
|
|
230
|
+
break;
|
|
231
|
+
case "email-template-recovery":
|
|
232
|
+
setSubStep("email-template-confirm");
|
|
233
|
+
break;
|
|
234
|
+
case "email-template-change":
|
|
235
|
+
setSubStep("email-template-recovery");
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const advanceToNext = (from) => {
|
|
240
|
+
switch (from) {
|
|
241
|
+
case "openai-key":
|
|
242
|
+
if (needsSSO)
|
|
243
|
+
setSubStep("sso-provider");
|
|
244
|
+
else if (needsMonitoring)
|
|
245
|
+
setSubStep("monitoring-remote-write-ask");
|
|
246
|
+
else if (needsLogging)
|
|
247
|
+
setSubStep("logging-category");
|
|
248
|
+
else if (needsCustomEmails)
|
|
249
|
+
setSubStep("email-subject-invite");
|
|
250
|
+
else
|
|
251
|
+
onComplete();
|
|
252
|
+
break;
|
|
253
|
+
case "sso-client-secret":
|
|
254
|
+
if (needsMonitoring)
|
|
255
|
+
setSubStep("monitoring-remote-write-ask");
|
|
256
|
+
else if (needsLogging)
|
|
257
|
+
setSubStep("logging-category");
|
|
258
|
+
else if (needsCustomEmails)
|
|
259
|
+
setSubStep("email-subject-invite");
|
|
260
|
+
else
|
|
261
|
+
onComplete();
|
|
262
|
+
break;
|
|
263
|
+
case "monitoring-remote-write-ask":
|
|
264
|
+
case "monitoring-remote-write-url":
|
|
265
|
+
if (needsLogging)
|
|
266
|
+
setSubStep("logging-category");
|
|
267
|
+
else if (needsCustomEmails)
|
|
268
|
+
setSubStep("email-subject-invite");
|
|
269
|
+
else
|
|
270
|
+
onComplete();
|
|
271
|
+
break;
|
|
272
|
+
case "logging-bucket":
|
|
273
|
+
// Cloud storage config complete, check for custom emails
|
|
274
|
+
if (needsCustomEmails)
|
|
275
|
+
setSubStep("email-subject-invite");
|
|
276
|
+
else
|
|
277
|
+
onComplete();
|
|
278
|
+
break;
|
|
279
|
+
case "logging-datadog-config":
|
|
280
|
+
case "logging-splunk-config":
|
|
281
|
+
case "logging-elasticsearch-config":
|
|
282
|
+
case "logging-loki-config":
|
|
283
|
+
case "logging-newrelic-config":
|
|
284
|
+
case "logging-axiom-config":
|
|
285
|
+
// Platform config complete, check for custom emails
|
|
286
|
+
if (needsCustomEmails)
|
|
287
|
+
setSubStep("email-subject-invite");
|
|
288
|
+
else
|
|
289
|
+
onComplete();
|
|
290
|
+
break;
|
|
291
|
+
case "email-template-change":
|
|
292
|
+
// All email config complete
|
|
293
|
+
onComplete();
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
// === AI Configuration ===
|
|
298
|
+
const handleOpenAIKeySubmit = () => {
|
|
299
|
+
if (!openaiKey) {
|
|
300
|
+
setError("OpenAI API key is required for AI features");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (!openaiKey.startsWith("sk-")) {
|
|
304
|
+
setError('OpenAI API key should start with "sk-"');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
setError(null);
|
|
308
|
+
dispatch({ type: "SET_OPENAI_KEY", key: openaiKey });
|
|
309
|
+
advanceToNext("openai-key");
|
|
310
|
+
};
|
|
311
|
+
// === SSO Configuration ===
|
|
312
|
+
const handleSsoProviderSelect = (item) => {
|
|
313
|
+
const provider = item.value;
|
|
314
|
+
setSsoProvider(provider);
|
|
315
|
+
dispatch({ type: "SET_SSO_CONFIG", config: { ssoProvider: provider } });
|
|
316
|
+
if (provider === "google") {
|
|
317
|
+
setSubStep("sso-client-id");
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
setSubStep("sso-url");
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const handleSsoUrlSubmit = () => {
|
|
324
|
+
if (!ssoUrl) {
|
|
325
|
+
setError("SSO provider URL is required");
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
new URL(ssoUrl);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
setError("Invalid URL format");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
setError(null);
|
|
336
|
+
setSubStep("sso-client-id");
|
|
337
|
+
};
|
|
338
|
+
const handleSsoClientIdSubmit = () => {
|
|
339
|
+
if (!ssoClientId) {
|
|
340
|
+
setError("Client ID is required");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
setError(null);
|
|
344
|
+
setSubStep("sso-client-secret");
|
|
345
|
+
};
|
|
346
|
+
const handleSsoClientSecretSubmit = () => {
|
|
347
|
+
if (!ssoClientSecret) {
|
|
348
|
+
setError("Client secret is required");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
setError(null);
|
|
352
|
+
dispatch({
|
|
353
|
+
type: "SET_SSO_CONFIG",
|
|
354
|
+
config: {
|
|
355
|
+
ssoProvider,
|
|
356
|
+
ssoUrl,
|
|
357
|
+
ssoClientId,
|
|
358
|
+
ssoClientSecret,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
advanceToNext("sso-client-secret");
|
|
362
|
+
};
|
|
363
|
+
// === Monitoring Configuration ===
|
|
364
|
+
const handleRemoteWriteAsk = (item) => {
|
|
365
|
+
if (item.value) {
|
|
366
|
+
setSubStep("monitoring-remote-write-url");
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: "" });
|
|
370
|
+
advanceToNext("monitoring-remote-write-ask");
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const handleRemoteWriteUrlSubmit = () => {
|
|
374
|
+
if (remoteWriteUrl) {
|
|
375
|
+
try {
|
|
376
|
+
new URL(remoteWriteUrl);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
setError("Invalid URL format");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
setError(null);
|
|
384
|
+
dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: remoteWriteUrl });
|
|
385
|
+
advanceToNext("monitoring-remote-write-url");
|
|
386
|
+
};
|
|
387
|
+
// === Logging Configuration ===
|
|
388
|
+
// Map logging sink to cloud provider
|
|
389
|
+
const sinkToProvider = (sink) => {
|
|
390
|
+
if (sink === "s3")
|
|
391
|
+
return "aws";
|
|
392
|
+
if (sink === "azure-blob")
|
|
393
|
+
return "azure";
|
|
394
|
+
if (sink === "gcs")
|
|
395
|
+
return "gcp";
|
|
396
|
+
return null;
|
|
397
|
+
};
|
|
398
|
+
// Is sink a cloud storage type?
|
|
399
|
+
const isCloudStorageSink = (sink) => {
|
|
400
|
+
return sink === "s3" || sink === "azure-blob" || sink === "gcs";
|
|
401
|
+
};
|
|
402
|
+
// Step 1: Select logging category
|
|
403
|
+
const handleLoggingCategorySelect = (item) => {
|
|
404
|
+
const category = item.value;
|
|
405
|
+
setLoggingCategory(category);
|
|
406
|
+
setSubStep("logging-sink");
|
|
407
|
+
};
|
|
408
|
+
// Step 2: Select logging sink based on category
|
|
409
|
+
const handleLoggingSinkSelect = async (item) => {
|
|
410
|
+
const sink = item.value;
|
|
411
|
+
setLoggingSink(sink);
|
|
412
|
+
dispatch({ type: "SET_LOGGING_SINK", sink });
|
|
413
|
+
if (isCloudStorageSink(sink)) {
|
|
414
|
+
// Cloud storage: go to region selection
|
|
415
|
+
loadRegionsForLogging(sink);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// Logging platform: go to platform-specific config
|
|
419
|
+
switch (sink) {
|
|
420
|
+
case "datadog":
|
|
421
|
+
setSubStep("logging-datadog-config");
|
|
422
|
+
break;
|
|
423
|
+
case "splunk":
|
|
424
|
+
setSubStep("logging-splunk-config");
|
|
425
|
+
break;
|
|
426
|
+
case "elasticsearch":
|
|
427
|
+
setSubStep("logging-elasticsearch-config");
|
|
428
|
+
break;
|
|
429
|
+
case "loki":
|
|
430
|
+
setSubStep("logging-loki-config");
|
|
431
|
+
break;
|
|
432
|
+
case "newrelic":
|
|
433
|
+
setSubStep("logging-newrelic-config");
|
|
434
|
+
break;
|
|
435
|
+
case "axiom":
|
|
436
|
+
setSubStep("logging-axiom-config");
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
// Load regions for cloud storage
|
|
442
|
+
const loadRegionsForLogging = async (sink) => {
|
|
443
|
+
const provider = sinkToProvider(sink);
|
|
444
|
+
if (!provider) {
|
|
445
|
+
setSubStep("logging-region");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
setSubStep("logging-region-loading");
|
|
449
|
+
try {
|
|
450
|
+
const regions = await listRegions(provider);
|
|
451
|
+
if (regions.length > 0) {
|
|
452
|
+
setAvailableRegions(regions);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
setAvailableRegions(CLOUD_REGIONS[provider]);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
setAvailableRegions(CLOUD_REGIONS[provider]);
|
|
460
|
+
}
|
|
461
|
+
setSubStep("logging-region");
|
|
462
|
+
};
|
|
463
|
+
// After region is selected, load buckets in that region
|
|
464
|
+
const handleLoggingRegionSelect = async (item) => {
|
|
465
|
+
setLoggingRegion(item.value);
|
|
466
|
+
dispatch({
|
|
467
|
+
type: "SET_LOGGING_CONFIG",
|
|
468
|
+
config: { loggingRegion: item.value },
|
|
469
|
+
});
|
|
470
|
+
// Now load buckets in this region
|
|
471
|
+
const provider = sinkToProvider(loggingSink);
|
|
472
|
+
if (provider) {
|
|
473
|
+
setSubStep("logging-bucket-loading");
|
|
474
|
+
try {
|
|
475
|
+
const buckets = await listBucketsInRegion(provider, item.value);
|
|
476
|
+
setAvailableBuckets(buckets);
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
setAvailableBuckets([]);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
setSubStep("logging-bucket");
|
|
483
|
+
};
|
|
484
|
+
// Refresh bucket list
|
|
485
|
+
const refreshBuckets = async () => {
|
|
486
|
+
if (isRefreshing)
|
|
487
|
+
return;
|
|
488
|
+
const provider = sinkToProvider(loggingSink);
|
|
489
|
+
if (!provider || !loggingRegion)
|
|
490
|
+
return;
|
|
491
|
+
setIsRefreshing(true);
|
|
492
|
+
try {
|
|
493
|
+
const buckets = await listBucketsInRegion(provider, loggingRegion);
|
|
494
|
+
setAvailableBuckets(buckets);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
// Keep existing list on error
|
|
498
|
+
}
|
|
499
|
+
setIsRefreshing(false);
|
|
500
|
+
};
|
|
501
|
+
// Select bucket (no create option)
|
|
502
|
+
const handleLoggingBucketSelect = (item) => {
|
|
503
|
+
setLoggingBucket(item.value);
|
|
504
|
+
dispatch({
|
|
505
|
+
type: "SET_LOGGING_CONFIG",
|
|
506
|
+
config: { loggingBucket: item.value },
|
|
507
|
+
});
|
|
508
|
+
advanceToNext("logging-bucket");
|
|
509
|
+
};
|
|
510
|
+
// === Logging Platform Config Handlers ===
|
|
511
|
+
const handleDatadogConfigSubmit = () => {
|
|
512
|
+
if (!datadogApiKey) {
|
|
513
|
+
setError("Datadog API key is required");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
setError(null);
|
|
517
|
+
dispatch({
|
|
518
|
+
type: "SET_LOGGING_CONFIG",
|
|
519
|
+
config: {
|
|
520
|
+
loggingBucket: datadogApiKey, // Repurpose bucket field for API key
|
|
521
|
+
loggingRegion: datadogSite, // Repurpose region field for site
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
advanceToNext("logging-datadog-config");
|
|
525
|
+
};
|
|
526
|
+
const handleSplunkConfigSubmit = () => {
|
|
527
|
+
if (!splunkUrl) {
|
|
528
|
+
setError("Splunk HEC URL is required");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!splunkHecToken) {
|
|
532
|
+
setError("Splunk HEC token is required");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
new URL(splunkUrl);
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
setError("Invalid URL format");
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
setError(null);
|
|
543
|
+
dispatch({
|
|
544
|
+
type: "SET_LOGGING_CONFIG",
|
|
545
|
+
config: {
|
|
546
|
+
loggingBucket: splunkHecToken,
|
|
547
|
+
loggingRegion: splunkUrl,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
advanceToNext("logging-splunk-config");
|
|
551
|
+
};
|
|
552
|
+
const handleElasticsearchConfigSubmit = () => {
|
|
553
|
+
if (!elasticsearchUrl) {
|
|
554
|
+
setError("Elasticsearch URL is required");
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
new URL(elasticsearchUrl);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
setError("Invalid URL format");
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
setError(null);
|
|
565
|
+
// Store as JSON in bucket field for complex config
|
|
566
|
+
dispatch({
|
|
567
|
+
type: "SET_LOGGING_CONFIG",
|
|
568
|
+
config: {
|
|
569
|
+
loggingBucket: JSON.stringify({
|
|
570
|
+
url: elasticsearchUrl,
|
|
571
|
+
user: elasticsearchUser,
|
|
572
|
+
password: elasticsearchPass,
|
|
573
|
+
index: elasticsearchIndex,
|
|
574
|
+
}),
|
|
575
|
+
loggingRegion: elasticsearchIndex,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
advanceToNext("logging-elasticsearch-config");
|
|
579
|
+
};
|
|
580
|
+
const handleLokiConfigSubmit = () => {
|
|
581
|
+
if (!lokiUrl) {
|
|
582
|
+
setError("Loki URL is required");
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
new URL(lokiUrl);
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
setError("Invalid URL format");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
setError(null);
|
|
593
|
+
dispatch({
|
|
594
|
+
type: "SET_LOGGING_CONFIG",
|
|
595
|
+
config: {
|
|
596
|
+
loggingBucket: lokiUrl,
|
|
597
|
+
loggingRegion: "",
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
advanceToNext("logging-loki-config");
|
|
601
|
+
};
|
|
602
|
+
const handleNewrelicConfigSubmit = () => {
|
|
603
|
+
if (!newrelicLicenseKey) {
|
|
604
|
+
setError("New Relic License Key is required");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (!newrelicAccountId) {
|
|
608
|
+
setError("New Relic Account ID is required");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
setError(null);
|
|
612
|
+
dispatch({
|
|
613
|
+
type: "SET_LOGGING_CONFIG",
|
|
614
|
+
config: {
|
|
615
|
+
loggingBucket: newrelicLicenseKey,
|
|
616
|
+
loggingRegion: newrelicAccountId,
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
advanceToNext("logging-newrelic-config");
|
|
620
|
+
};
|
|
621
|
+
const handleAxiomConfigSubmit = () => {
|
|
622
|
+
if (!axiomApiToken) {
|
|
623
|
+
setError("Axiom API token is required");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!axiomDataset) {
|
|
627
|
+
setError("Axiom dataset is required");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
setError(null);
|
|
631
|
+
dispatch({
|
|
632
|
+
type: "SET_LOGGING_CONFIG",
|
|
633
|
+
config: {
|
|
634
|
+
loggingBucket: axiomApiToken,
|
|
635
|
+
loggingRegion: axiomDataset,
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
advanceToNext("logging-axiom-config");
|
|
639
|
+
};
|
|
640
|
+
// === Custom Email Configuration Handlers ===
|
|
641
|
+
const handleEmailSubjectInviteSubmit = () => {
|
|
642
|
+
dispatch({
|
|
643
|
+
type: "SET_EMAIL_SUBJECTS",
|
|
644
|
+
subjects: { invite: emailSubjectInvite },
|
|
645
|
+
});
|
|
646
|
+
setSubStep("email-subject-confirm");
|
|
647
|
+
};
|
|
648
|
+
const handleEmailSubjectConfirmSubmit = () => {
|
|
649
|
+
dispatch({
|
|
650
|
+
type: "SET_EMAIL_SUBJECTS",
|
|
651
|
+
subjects: { confirmation: emailSubjectConfirm },
|
|
652
|
+
});
|
|
653
|
+
setSubStep("email-subject-recovery");
|
|
654
|
+
};
|
|
655
|
+
const handleEmailSubjectRecoverySubmit = () => {
|
|
656
|
+
dispatch({
|
|
657
|
+
type: "SET_EMAIL_SUBJECTS",
|
|
658
|
+
subjects: { recovery: emailSubjectRecovery },
|
|
659
|
+
});
|
|
660
|
+
setSubStep("email-subject-change");
|
|
661
|
+
};
|
|
662
|
+
const handleEmailSubjectChangeSubmit = () => {
|
|
663
|
+
dispatch({
|
|
664
|
+
type: "SET_EMAIL_SUBJECTS",
|
|
665
|
+
subjects: { emailChange: emailSubjectChange },
|
|
666
|
+
});
|
|
667
|
+
setSubStep("email-template-invite");
|
|
668
|
+
};
|
|
669
|
+
const validateUrl = (url) => {
|
|
670
|
+
if (!url)
|
|
671
|
+
return false;
|
|
672
|
+
try {
|
|
673
|
+
new URL(url);
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const handleEmailTemplateInviteSubmit = () => {
|
|
681
|
+
if (!validateUrl(emailTemplateInvite)) {
|
|
682
|
+
setError("Please enter a valid URL for the invite template");
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
setError(null);
|
|
686
|
+
dispatch({
|
|
687
|
+
type: "SET_EMAIL_TEMPLATES",
|
|
688
|
+
templates: { invite: emailTemplateInvite },
|
|
689
|
+
});
|
|
690
|
+
setSubStep("email-template-confirm");
|
|
691
|
+
};
|
|
692
|
+
const handleEmailTemplateConfirmSubmit = () => {
|
|
693
|
+
if (!validateUrl(emailTemplateConfirm)) {
|
|
694
|
+
setError("Please enter a valid URL for the confirmation template");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
setError(null);
|
|
698
|
+
dispatch({
|
|
699
|
+
type: "SET_EMAIL_TEMPLATES",
|
|
700
|
+
templates: { confirmation: emailTemplateConfirm },
|
|
701
|
+
});
|
|
702
|
+
setSubStep("email-template-recovery");
|
|
703
|
+
};
|
|
704
|
+
const handleEmailTemplateRecoverySubmit = () => {
|
|
705
|
+
if (!validateUrl(emailTemplateRecovery)) {
|
|
706
|
+
setError("Please enter a valid URL for the recovery template");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
setError(null);
|
|
710
|
+
dispatch({
|
|
711
|
+
type: "SET_EMAIL_TEMPLATES",
|
|
712
|
+
templates: { recovery: emailTemplateRecovery },
|
|
713
|
+
});
|
|
714
|
+
setSubStep("email-template-change");
|
|
715
|
+
};
|
|
716
|
+
const handleEmailTemplateChangeSubmit = () => {
|
|
717
|
+
if (!validateUrl(emailTemplateChange)) {
|
|
718
|
+
setError("Please enter a valid URL for the email change template");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
setError(null);
|
|
722
|
+
dispatch({
|
|
723
|
+
type: "SET_EMAIL_TEMPLATES",
|
|
724
|
+
templates: { emailChange: emailTemplateChange },
|
|
725
|
+
});
|
|
726
|
+
advanceToNext("email-template-change");
|
|
727
|
+
};
|
|
728
|
+
// Build bucket items for selection (no create option - just existing buckets)
|
|
729
|
+
const getBucketItems = () => {
|
|
730
|
+
return availableBuckets.map((b) => ({ label: b, value: b }));
|
|
731
|
+
};
|
|
732
|
+
// Get regions based on logging sink (use dynamic regions if available)
|
|
733
|
+
const getLoggingRegions = () => {
|
|
734
|
+
if (availableRegions.length > 0) {
|
|
735
|
+
return availableRegions.map((r) => ({ label: r, value: r }));
|
|
736
|
+
}
|
|
737
|
+
const provider = sinkToProvider(loggingSink);
|
|
738
|
+
if (!provider)
|
|
739
|
+
return [];
|
|
740
|
+
return CLOUD_REGIONS[provider].map((r) => ({ label: r, value: r }));
|
|
741
|
+
};
|
|
742
|
+
// If nothing to configure, don't render
|
|
743
|
+
if (!needsAI &&
|
|
744
|
+
!needsSSO &&
|
|
745
|
+
!needsMonitoring &&
|
|
746
|
+
!needsLogging &&
|
|
747
|
+
!needsCustomEmails) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
// Progress summary
|
|
751
|
+
const ProgressSummary = () => (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.openaiApiKey && (_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsx(Text, { color: "gray", children: " OpenAI API key configured" })] })), state.ssoProvider && (_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" SSO: ", state.ssoProvider] })] })), state.prometheusRemoteWriteUrl && (_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsx(Text, { color: "gray", children: " Metrics remote write configured" })] }))] }));
|
|
752
|
+
return (_jsxs(BorderBox, { title: "Feature Configuration", children: [subStep === "openai-key" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "OpenAI API Key" }), _jsx(Text, { color: "gray", dimColor: true, children: "Required for AI-powered rule generation features" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: openaiKey, onChange: setOpenaiKey, onSubmit: handleOpenAIKeySubmit, placeholder: "sk-...", mask: "*" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Get your API key at: https://platform.openai.com/api-keys" }) })] })), subStep === "sso-provider" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "SSO Provider" }), _jsx(Text, { color: "gray", dimColor: true, children: "Select your identity provider" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: SSO_PROVIDERS, onSelect: handleSsoProviderSelect, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === "sso-url" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [ssoProvider?.toUpperCase(), " Provider URL"] }), _jsxs(Text, { color: "gray", dimColor: true, children: [ssoProvider === "azure" &&
|
|
753
|
+
"e.g., https://login.microsoftonline.com/your-tenant-id", ssoProvider === "okta" && "e.g., https://your-org.okta.com", ssoProvider === "keycloak" &&
|
|
754
|
+
"e.g., https://keycloak.example.com/realms/your-realm", ssoProvider === "ory" &&
|
|
755
|
+
"e.g., https://your-project.projects.oryapis.com", ssoProvider === "other" && "The base URL of your OIDC provider"] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: ssoUrl, onChange: setSsoUrl, onSubmit: handleSsoUrlSubmit, placeholder: "https://..." }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Provider: ", ssoProvider] })] })] })), subStep === "sso-client-id" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "OAuth Client ID" }), _jsx(Text, { color: "gray", dimColor: true, children: "The client/application ID from your identity provider" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: ssoClientId, onChange: setSsoClientId, onSubmit: handleSsoClientIdSubmit, placeholder: "your-client-id" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Provider: ", ssoProvider] })] }), ssoUrl && (_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" URL: ", ssoUrl] })] }))] })] })), subStep === "sso-client-secret" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "OAuth Client Secret" }), _jsx(Text, { color: "gray", dimColor: true, children: "The client secret from your identity provider" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: ssoClientSecret, onChange: setSsoClientSecret, onSubmit: handleSsoClientSecretSubmit, placeholder: "your-client-secret", mask: "*" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Provider: ", ssoProvider] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsx(Text, { color: "gray", children: " Client ID configured" })] })] })] })), subStep === "monitoring-remote-write-ask" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Prometheus Remote Write" }), _jsx(Text, { color: "gray", dimColor: true, children: "Do you want to send metrics to an external monitoring system?" }), _jsx(Text, { color: "gray", dimColor: true, children: "(e.g., Datadog, Grafana Cloud, Chronosphere)" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: YES_NO_OPTIONS, onSelect: handleRemoteWriteAsk, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-url" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Remote Write URL" }), _jsx(Text, { color: "gray", dimColor: true, children: "Prometheus remote_write endpoint URL" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteUrl, onChange: setRemoteWriteUrl, onSubmit: handleRemoteWriteUrlSubmit, placeholder: "https://metrics.example.com/api/v1/write" }) }), _jsx(ProgressSummary, {})] })), subStep === "logging-category" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "External Logging Destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Choose how you want to store decision logs (Console logging is always included)" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: LOGGING_CATEGORIES, onSelect: handleLoggingCategorySelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsx(ProgressSummary, {})] })), subStep === "logging-sink" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: loggingCategory === "cloud-storage"
|
|
756
|
+
? "Select Cloud Storage"
|
|
757
|
+
: "Select Logging Platform" }), _jsx(Text, { color: "gray", dimColor: true, children: loggingCategory === "cloud-storage"
|
|
758
|
+
? "Store logs in cloud object storage"
|
|
759
|
+
: "Send logs to a centralized logging platform" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: loggingCategory === "cloud-storage"
|
|
760
|
+
? CLOUD_STORAGE_SINKS
|
|
761
|
+
: LOGGING_PLATFORM_SINKS, onSelect: handleLoggingSinkSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.infrastructureMode === "existing" &&
|
|
762
|
+
loggingCategory === "cloud-storage" && (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: _jsx(Text, { color: "yellow", children: "Note: Cloud storage requires IRSA/Workload Identity in your cluster." }) })), _jsx(ProgressSummary, {})] })), subStep === "logging-region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "logging-region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [loggingSink === "s3" && "Select AWS Region", loggingSink === "azure-blob" && "Select Azure Region", loggingSink === "gcs" && "Select GCP Region"] }), _jsx(Text, { color: "gray", dimColor: true, children: "Select the region where logs will be stored" }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: getLoggingRegions(), onSelect: handleLoggingRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Sink: ", LOGGING_SINK_INFO[loggingSink]?.name] })] })] })), subStep === "logging-bucket-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Loading buckets in ${loggingRegion}...` }) })), subStep === "logging-bucket" && (_jsx(BucketSelector, { loggingSink: loggingSink, loggingRegion: loggingRegion, availableBuckets: availableBuckets, isRefreshing: isRefreshing, onSelect: handleLoggingBucketSelect, onRefresh: refreshBuckets, colors: colors })), subStep === "logging-datadog-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Datadog Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Datadog Logs integration" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "API Key:" }), _jsx(Box, { children: _jsx(TextInput, { value: datadogApiKey, onChange: setDatadogApiKey, placeholder: "your-api-key", mask: "*" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Datadog Site:" }), _jsx(SelectInput, { items: DATADOG_SITES, initialIndex: DATADOG_SITES.findIndex((s) => s.value === datadogSite), onSelect: (item) => setDatadogSite(item.value), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press Enter after selecting site to continue" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: datadogApiKey ? colors.accent : colors.muted, bold: !!datadogApiKey, children: datadogApiKey
|
|
763
|
+
? "→ Press Enter to continue"
|
|
764
|
+
: "Enter API key to continue" }) }), datadogApiKey && (_jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: "", onChange: () => { }, onSubmit: handleDatadogConfigSubmit, placeholder: "" }) }))] })), subStep === "logging-splunk-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Splunk HEC Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Splunk HTTP Event Collector" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "HEC URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: splunkUrl, onChange: setSplunkUrl, placeholder: "https://splunk.example.com:8088" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "HEC Token:" }), _jsx(Box, { children: _jsx(TextInput, { value: splunkHecToken, onChange: setSplunkHecToken, onSubmit: handleSplunkConfigSubmit, placeholder: "your-hec-token", mask: "*" }) })] })] })), subStep === "logging-elasticsearch-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Elasticsearch Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Elasticsearch logging destination" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Elasticsearch URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: elasticsearchUrl, onChange: setElasticsearchUrl, placeholder: "https://elasticsearch.example.com:9200" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Username (optional):" }), _jsx(Box, { children: _jsx(TextInput, { value: elasticsearchUser, onChange: setElasticsearchUser, placeholder: "elastic" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Password (optional):" }), _jsx(Box, { children: _jsx(TextInput, { value: elasticsearchPass, onChange: setElasticsearchPass, placeholder: "", mask: "*" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Index name:" }), _jsx(Box, { children: _jsx(TextInput, { value: elasticsearchIndex, onChange: setElasticsearchIndex, onSubmit: handleElasticsearchConfigSubmit, placeholder: "rulebricks-logs" }) })] })] })), subStep === "logging-loki-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Grafana Loki Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Loki logging destination" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Loki URL:" }), _jsx(Text, { color: "gray", dimColor: true, children: "Include /loki/api/v1/push endpoint" }), _jsx(Box, { children: _jsx(TextInput, { value: lokiUrl, onChange: setLokiUrl, onSubmit: handleLokiConfigSubmit, placeholder: "https://loki.example.com/loki/api/v1/push" }) })] })] })), subStep === "logging-newrelic-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "New Relic Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure New Relic Logs integration" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "License Key:" }), _jsx(Box, { children: _jsx(TextInput, { value: newrelicLicenseKey, onChange: setNewrelicLicenseKey, placeholder: "your-license-key", mask: "*" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Account ID:" }), _jsx(Box, { children: _jsx(TextInput, { value: newrelicAccountId, onChange: setNewrelicAccountId, onSubmit: handleNewrelicConfigSubmit, placeholder: "1234567" }) })] })] })), subStep === "logging-axiom-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Axiom Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Axiom logging destination" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "API Token:" }), _jsx(Box, { children: _jsx(TextInput, { value: axiomApiToken, onChange: setAxiomApiToken, placeholder: "xaat-...", mask: "*" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Dataset:" }), _jsx(Box, { children: _jsx(TextInput, { value: axiomDataset, onChange: setAxiomDataset, onSubmit: handleAxiomConfigSubmit, placeholder: "rulebricks" }) })] })] })), subStep === "email-subject-invite" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates" }), _jsx(Text, { color: "gray", dimColor: true, children: "Customize Supabase auth email subjects and templates." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "First, let's customize the subject lines. Press Enter to use defaults." }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Invite Email Subject:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default: \"", DEFAULT_EMAIL_SUBJECTS.invite, "\""] }), _jsx(Box, { children: _jsx(TextInput, { value: emailSubjectInvite, onChange: setEmailSubjectInvite, onSubmit: handleEmailSubjectInviteSubmit, placeholder: DEFAULT_EMAIL_SUBJECTS.invite }) })] })] })), subStep === "email-subject-confirm" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Subject Lines" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Confirmation Email Subject:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default: \"", DEFAULT_EMAIL_SUBJECTS.confirmation, "\""] }), _jsx(Box, { children: _jsx(TextInput, { value: emailSubjectConfirm, onChange: setEmailSubjectConfirm, onSubmit: handleEmailSubjectConfirmSubmit, placeholder: DEFAULT_EMAIL_SUBJECTS.confirmation }) })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailSubjectInvite] })] })] })), subStep === "email-subject-recovery" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Subject Lines" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Password Recovery Email Subject:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default: \"", DEFAULT_EMAIL_SUBJECTS.recovery, "\""] }), _jsx(Box, { children: _jsx(TextInput, { value: emailSubjectRecovery, onChange: setEmailSubjectRecovery, onSubmit: handleEmailSubjectRecoverySubmit, placeholder: DEFAULT_EMAIL_SUBJECTS.recovery }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailSubjectInvite] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Confirmation: ", emailSubjectConfirm] })] })] })] })), subStep === "email-subject-change" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Subject Lines" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Email Change Subject:" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Default: \"", DEFAULT_EMAIL_SUBJECTS.emailChange, "\""] }), _jsx(Box, { children: _jsx(TextInput, { value: emailSubjectChange, onChange: setEmailSubjectChange, onSubmit: handleEmailSubjectChangeSubmit, placeholder: DEFAULT_EMAIL_SUBJECTS.emailChange }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailSubjectInvite] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Confirmation: ", emailSubjectConfirm] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Recovery: ", emailSubjectRecovery] })] })] })] })), subStep === "email-template-invite" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Template URLs" }), _jsx(Text, { color: "gray", dimColor: true, children: "Provide URLs to your custom HTML email templates." }), _jsx(Text, { color: "gray", dimColor: true, children: "Templates must be publicly accessible (S3, GCS, or any HTTPS URL)." }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Invite Template URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: emailTemplateInvite, onChange: setEmailTemplateInvite, onSubmit: handleEmailTemplateInviteSubmit, placeholder: "https://bucket.s3.amazonaws.com/templates/invite.html" }) })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsx(Text, { color: "gray", children: " All subject lines configured" })] })] })), subStep === "email-template-confirm" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Template URLs" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Confirmation Template URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: emailTemplateConfirm, onChange: setEmailTemplateConfirm, onSubmit: handleEmailTemplateConfirmSubmit, placeholder: "https://bucket.s3.amazonaws.com/templates/verify.html" }) })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailTemplateInvite] })] })] })), subStep === "email-template-recovery" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Template URLs" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Recovery Template URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: emailTemplateRecovery, onChange: setEmailTemplateRecovery, onSubmit: handleEmailTemplateRecoverySubmit, placeholder: "https://bucket.s3.amazonaws.com/templates/password_change.html" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailTemplateInvite] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Confirmation: ", emailTemplateConfirm] })] })] })] })), subStep === "email-template-change" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Custom Email Templates - Template URLs" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Email Change Template URL:" }), _jsx(Box, { children: _jsx(TextInput, { value: emailTemplateChange, onChange: setEmailTemplateChange, onSubmit: handleEmailTemplateChangeSubmit, placeholder: "https://bucket.s3.amazonaws.com/templates/email_change.html" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Invite: ", emailTemplateInvite] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Confirmation: ", emailTemplateConfirm] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Recovery: ", emailTemplateRecovery] })] })] })] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", error] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Esc to go back \u2022 Enter to continue" }) })] }));
|
|
765
|
+
}
|