@rulebricks/cli 2.1.6 → 2.3.1
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 +75 -14
- package/cluster-setup/aws/README.md +123 -0
- package/cluster-setup/aws/check-aws-access.sh +242 -0
- package/cluster-setup/aws/parameters.json +13 -0
- package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
- package/cluster-setup/azure/README.md +141 -0
- package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
- package/cluster-setup/azure/parameters.json +30 -0
- package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
- package/cluster-setup/gcp/README.md +189 -0
- package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
- 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 -47
- 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 +174 -29
- package/dist/components/Wizard/WizardContext.js +896 -91
- package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
- 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 +959 -248
- 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 -7
- 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 +1937 -259
- package/dist/lib/helmValues.test.d.ts +1 -0
- package/dist/lib/helmValues.test.js +966 -0
- package/dist/lib/htpasswd.d.ts +1 -0
- package/dist/lib/htpasswd.js +15 -0
- package/dist/lib/kubernetes.d.ts +126 -13
- package/dist/lib/kubernetes.js +624 -134
- 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 +2152 -95
- package/dist/types/index.js +554 -286
- package/package.json +10 -4
- package/schema/values.schema.json +1934 -0
- 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,13 +1,19 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
|
-
import { Box, Text
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
4
|
import SelectInput from "ink-select-input";
|
|
5
5
|
import TextInput from "ink-text-input";
|
|
6
6
|
import { useWizard } from "../WizardContext.js";
|
|
7
|
-
import { BorderBox, useTheme } from "../../common/index.js";
|
|
7
|
+
import { BorderBox, useGatedInput, useTheme } from "../../common/index.js";
|
|
8
8
|
import { Spinner } from "../../common/Spinner.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { CLOUD_REGIONS, DEFAULT_EMAIL_SUBJECTS, } from "../../../types/index.js";
|
|
10
|
+
import { listRegions, listAzureManagedIdentities, getAzureTenantId, listAzurePrometheusTargets, listAwsPrometheusWorkspaces, } from "../../../lib/cloudCli.js";
|
|
11
|
+
import { findClusterSetupDefaultIndex } from "../../../lib/clusterSetupDefaults.js";
|
|
12
|
+
import { generateHtpasswdLine } from "../../../lib/htpasswd.js";
|
|
13
|
+
import { generateSecureSecret } from "../../../lib/validation.js";
|
|
14
|
+
// Sentinel value used in select lists to drop into manual text entry.
|
|
15
|
+
const MANUAL = "__manual__";
|
|
16
|
+
const REFRESH = "__refresh__";
|
|
11
17
|
const SSO_PROVIDERS = [
|
|
12
18
|
{ label: "Microsoft Azure AD", value: "azure" },
|
|
13
19
|
{ label: "Google Workspace", value: "google" },
|
|
@@ -16,18 +22,9 @@ const SSO_PROVIDERS = [
|
|
|
16
22
|
{ label: "Ory", value: "ory" },
|
|
17
23
|
{ label: "Other OIDC Provider", value: "other" },
|
|
18
24
|
];
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
];
|
|
25
|
+
// External logging forwards decision logs to a centralized logging platform.
|
|
26
|
+
// Cloud object storage (S3/Blob/GCS) for decision logs is configured in the
|
|
27
|
+
// dedicated Object Storage step, so it is intentionally not offered here.
|
|
31
28
|
const LOGGING_PLATFORM_SINKS = [
|
|
32
29
|
{ label: "Datadog", value: "datadog" },
|
|
33
30
|
{ label: "Splunk (HEC)", value: "splunk" },
|
|
@@ -44,54 +41,130 @@ const DATADOG_SITES = [
|
|
|
44
41
|
{ label: "EU1 (datadoghq.eu)", value: "datadoghq.eu" },
|
|
45
42
|
{ label: "AP1 (ap1.datadoghq.com)", value: "ap1.datadoghq.com" },
|
|
46
43
|
];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 },
|
|
44
|
+
const REMOTE_WRITE_DESTINATIONS = [
|
|
45
|
+
{ label: "AWS Managed Prometheus (AMP)", value: "aws-amp" },
|
|
46
|
+
{ label: "Azure Monitor managed Prometheus", value: "azure-monitor" },
|
|
47
|
+
{ label: "Grafana Cloud", value: "grafana-cloud" },
|
|
48
|
+
{ label: "Generic Prometheus remote_write", value: "generic" },
|
|
49
|
+
];
|
|
50
|
+
const AZURE_REMOTE_WRITE_AUTH = [
|
|
51
|
+
{ label: "Workload identity", value: "workload-identity" },
|
|
52
|
+
{ label: "Managed identity", value: "managed-identity" },
|
|
53
|
+
{ label: "OAuth client secret", value: "oauth" },
|
|
60
54
|
];
|
|
61
|
-
|
|
55
|
+
const GENERIC_REMOTE_WRITE_AUTH = [
|
|
56
|
+
{ label: "No additional auth", value: "none" },
|
|
57
|
+
{ label: "Basic auth from Kubernetes Secret", value: "basic" },
|
|
58
|
+
{ label: "Bearer token from Kubernetes Secret", value: "bearer" },
|
|
59
|
+
];
|
|
60
|
+
export function FeatureConfigStep({ onComplete, onBack, entryDirection, }) {
|
|
62
61
|
const { state, dispatch } = useWizard();
|
|
63
62
|
const { colors } = useTheme();
|
|
64
|
-
// Determine what needs to be configured
|
|
65
|
-
|
|
63
|
+
// Determine what needs to be configured. Metrics export (Prometheus
|
|
64
|
+
// remote_write) is opt-in via the Features step; in-cluster Prometheus is
|
|
65
|
+
// always installed and needs no configuration.
|
|
66
|
+
// Show the OpenAI key step whenever AI is enabled, even if a key is already
|
|
67
|
+
// in wizard state (e.g. pre-filled from the saved profile or an existing
|
|
68
|
+
// config on redeploy). The input is seeded with that value so the user can
|
|
69
|
+
// confirm or change it — previously a pre-filled key silently skipped this
|
|
70
|
+
// step entirely, which looked like the wizard never collected the key.
|
|
71
|
+
const needsAI = state.aiEnabled;
|
|
66
72
|
const needsSSO = state.ssoEnabled;
|
|
67
|
-
const needsMonitoring = state.
|
|
73
|
+
const needsMonitoring = !state.clickStackEnabled && state.metricsExportEnabled;
|
|
68
74
|
const needsLogging = state.loggingSink !== "console";
|
|
75
|
+
const needsTracing = !state.clickStackEnabled && state.tracingEnabled;
|
|
76
|
+
const needsAppLogs = !state.clickStackEnabled && state.appLogsEnabled;
|
|
77
|
+
const needsValkeyAdmin = state.valkeyAdminEnabled;
|
|
69
78
|
const needsCustomEmails = state.customEmailsEnabled;
|
|
70
|
-
// Configuration order:
|
|
79
|
+
// Configuration order:
|
|
80
|
+
// AI -> SSO -> Monitoring -> Logging -> Tracing -> AppLogs -> Valkey Admin -> Custom Emails
|
|
71
81
|
const getInitialStep = () => {
|
|
72
82
|
if (needsAI)
|
|
73
83
|
return "openai-key";
|
|
74
84
|
if (needsSSO)
|
|
75
85
|
return "sso-provider";
|
|
76
86
|
if (needsMonitoring)
|
|
77
|
-
return "monitoring-remote-write-
|
|
87
|
+
return "monitoring-remote-write-destination";
|
|
78
88
|
if (needsLogging)
|
|
79
|
-
return "logging-
|
|
89
|
+
return "logging-sink";
|
|
90
|
+
if (needsTracing)
|
|
91
|
+
return "tracing-destination";
|
|
92
|
+
if (needsAppLogs)
|
|
93
|
+
return "applogs-endpoint";
|
|
94
|
+
if (needsValkeyAdmin)
|
|
95
|
+
return "valkey-admin-username";
|
|
80
96
|
if (needsCustomEmails)
|
|
81
97
|
return "email-subject-invite";
|
|
82
98
|
return "done";
|
|
83
99
|
};
|
|
84
|
-
|
|
100
|
+
// Terminal sub-step of the last enabled section. Used when navigating *back*
|
|
101
|
+
// into this step so the user resumes at the end instead of the very start.
|
|
102
|
+
const tracingFinalStep = () => {
|
|
103
|
+
const destination = state.tracingDestination || "elastic";
|
|
104
|
+
if (destination === "otlp") {
|
|
105
|
+
return state.tracingOtlpAuthMode && state.tracingOtlpAuthMode !== "none"
|
|
106
|
+
? "tracing-otlp-cred"
|
|
107
|
+
: "tracing-otlp-auth";
|
|
108
|
+
}
|
|
109
|
+
if (destination === "azure-monitor")
|
|
110
|
+
return "tracing-azure-connection";
|
|
111
|
+
return "tracing-token";
|
|
112
|
+
};
|
|
113
|
+
const loggingFinalStep = () => {
|
|
114
|
+
switch (state.loggingSink) {
|
|
115
|
+
case "datadog":
|
|
116
|
+
return "logging-datadog-config";
|
|
117
|
+
case "splunk":
|
|
118
|
+
return "logging-splunk-config";
|
|
119
|
+
case "elasticsearch":
|
|
120
|
+
return "logging-elasticsearch-config";
|
|
121
|
+
case "loki":
|
|
122
|
+
return "logging-loki-config";
|
|
123
|
+
case "newrelic":
|
|
124
|
+
return "logging-newrelic-config";
|
|
125
|
+
case "axiom":
|
|
126
|
+
return "logging-axiom-config";
|
|
127
|
+
default:
|
|
128
|
+
return "logging-sink";
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const getFinalStep = () => {
|
|
132
|
+
if (needsCustomEmails)
|
|
133
|
+
return "email-template-change";
|
|
134
|
+
if (needsValkeyAdmin)
|
|
135
|
+
return "valkey-admin-allowed-ips";
|
|
136
|
+
if (needsAppLogs)
|
|
137
|
+
return "applogs-index";
|
|
138
|
+
if (needsTracing)
|
|
139
|
+
return tracingFinalStep();
|
|
140
|
+
if (needsLogging)
|
|
141
|
+
return loggingFinalStep();
|
|
142
|
+
if (needsMonitoring)
|
|
143
|
+
return "monitoring-remote-write-destination";
|
|
144
|
+
if (needsSSO)
|
|
145
|
+
return "sso-client-secret";
|
|
146
|
+
if (needsAI)
|
|
147
|
+
return "openai-key";
|
|
148
|
+
return "done";
|
|
149
|
+
};
|
|
150
|
+
const [subStep, setSubStep] = useState(entryDirection === "back" ? getFinalStep : getInitialStep);
|
|
85
151
|
const [openaiKey, setOpenaiKey] = useState(state.openaiApiKey || "");
|
|
86
152
|
const [ssoProvider, setSsoProvider] = useState(state.ssoProvider);
|
|
87
153
|
const [ssoUrl, setSsoUrl] = useState(state.ssoUrl || "");
|
|
88
154
|
const [ssoClientId, setSsoClientId] = useState(state.ssoClientId || "");
|
|
89
155
|
const [ssoClientSecret, setSsoClientSecret] = useState(state.ssoClientSecret || "");
|
|
90
156
|
const [remoteWriteUrl, setRemoteWriteUrl] = useState(state.prometheusRemoteWriteUrl || "");
|
|
157
|
+
const [remoteWriteDestination, setRemoteWriteDestination] = useState(state.prometheusRemoteWriteDestination);
|
|
158
|
+
const [remoteWriteAuthType, setRemoteWriteAuthType] = useState(state.prometheusRemoteWriteAuthType);
|
|
159
|
+
const [remoteWriteAwsRegion, setRemoteWriteAwsRegion] = useState(state.prometheusRemoteWriteAwsRegion || state.region || "us-east-1");
|
|
160
|
+
const [remoteWriteAzureCloud] = useState(state.prometheusRemoteWriteAzureCloud || "AzurePublic");
|
|
161
|
+
const [remoteWriteClientId, setRemoteWriteClientId] = useState(state.prometheusRemoteWriteClientId || "");
|
|
162
|
+
const [remoteWriteTenantId, setRemoteWriteTenantId] = useState(state.prometheusRemoteWriteTenantId || "");
|
|
163
|
+
const [remoteWriteSecretRef, setRemoteWriteSecretRef] = useState(state.prometheusRemoteWriteSecretRef || "");
|
|
164
|
+
const [remoteWriteUsernameSecretRef, setRemoteWriteUsernameSecretRef] = useState(state.prometheusRemoteWriteUsernameSecretRef || "");
|
|
165
|
+
const [remoteWritePasswordSecretRef, setRemoteWritePasswordSecretRef] = useState(state.prometheusRemoteWritePasswordSecretRef || "");
|
|
166
|
+
const [remoteWriteBearerSecretRef, setRemoteWriteBearerSecretRef] = useState(state.prometheusRemoteWriteBearerTokenSecretRef || "");
|
|
91
167
|
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
168
|
const [error, setError] = useState(null);
|
|
96
169
|
// Logging platform config
|
|
97
170
|
const [datadogApiKey, setDatadogApiKey] = useState("");
|
|
@@ -107,10 +180,46 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
107
180
|
const [newrelicAccountId, setNewrelicAccountId] = useState("");
|
|
108
181
|
const [axiomApiToken, setAxiomApiToken] = useState("");
|
|
109
182
|
const [axiomDataset, setAxiomDataset] = useState("rulebricks");
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const [
|
|
183
|
+
// Distributed tracing. The wizard supports three destinations (Elastic APM,
|
|
184
|
+
// generic OTLP/HTTP, Azure Monitor). For Elastic it collects endpoint + secret
|
|
185
|
+
// token (api-key auth remains available via config file).
|
|
186
|
+
const [tracingDestination, setTracingDestination] = useState(state.tracingDestination || "elastic");
|
|
187
|
+
const [tracingEndpoint, setTracingEndpoint] = useState(state.tracingElasticEndpoint || "");
|
|
188
|
+
const [tracingToken, setTracingToken] = useState(state.tracingElasticSecretToken || "");
|
|
189
|
+
const [tracingOtlpEndpoint, setTracingOtlpEndpoint] = useState(state.tracingOtlpEndpoint || "");
|
|
190
|
+
const [tracingOtlpAuthMode, setTracingOtlpAuthMode] = useState(state.tracingOtlpAuthMode === "bearer" ||
|
|
191
|
+
state.tracingOtlpAuthMode === "api-key"
|
|
192
|
+
? state.tracingOtlpAuthMode
|
|
193
|
+
: "none");
|
|
194
|
+
const [tracingOtlpToken, setTracingOtlpToken] = useState(state.tracingOtlpToken || "");
|
|
195
|
+
const [tracingAzureConnectionString, setTracingAzureConnectionString] = useState(state.tracingAzureConnectionString || "");
|
|
196
|
+
// Application log shipping (BYO Elasticsearch via Vector agent). Basic auth.
|
|
197
|
+
const [appLogsEndpoint, setAppLogsEndpoint] = useState(state.appLogsElasticEndpoint || "");
|
|
198
|
+
const [appLogsUser, setAppLogsUser] = useState(state.appLogsElasticUsername || "");
|
|
199
|
+
const [appLogsPass, setAppLogsPass] = useState(state.appLogsElasticPassword || "");
|
|
200
|
+
const [appLogsIndex, setAppLogsIndex] = useState(state.appLogsElasticIndex || "rulebricks-app-logs");
|
|
201
|
+
const [defaultValkeyAdminPassword] = useState(() => generateSecureSecret(16));
|
|
202
|
+
const [valkeyAdminUsername, setValkeyAdminUsername] = useState(() => {
|
|
203
|
+
const existingUser = state.valkeyAdminBasicAuthUsers[0];
|
|
204
|
+
return existingUser?.split(":")[0] || "admin";
|
|
205
|
+
});
|
|
206
|
+
const [valkeyAdminPassword, setValkeyAdminPassword] = useState("");
|
|
207
|
+
const [valkeyAdminAllowedIPs, setValkeyAdminAllowedIPs] = useState(state.valkeyAdminAllowedIPs.join(", "));
|
|
208
|
+
// Dynamic resource lists for remote-write identity selection.
|
|
209
|
+
const [rwRegions, setRwRegions] = useState([]);
|
|
210
|
+
const [rwIdentities, setRwIdentities] = useState([]);
|
|
211
|
+
const [rwTargets, setRwTargets] = useState([]);
|
|
212
|
+
// Only offer the managed-Prometheus option for the cluster's own cloud (no
|
|
213
|
+
// AWS Managed Prometheus on an Azure cluster, etc.); Grafana Cloud and generic
|
|
214
|
+
// remote_write stay available everywhere.
|
|
215
|
+
const remoteWriteDestinations = REMOTE_WRITE_DESTINATIONS.filter((d) => {
|
|
216
|
+
if (d.value === "aws-amp")
|
|
217
|
+
return state.provider === "aws";
|
|
218
|
+
if (d.value === "azure-monitor")
|
|
219
|
+
return state.provider === "azure";
|
|
220
|
+
return true;
|
|
221
|
+
});
|
|
222
|
+
const [rwTenantAutoDetected, setRwTenantAutoDetected] = useState(false);
|
|
114
223
|
// Custom email templates
|
|
115
224
|
const [emailSubjectInvite, setEmailSubjectInvite] = useState(state.emailSubjects?.invite || DEFAULT_EMAIL_SUBJECTS.invite);
|
|
116
225
|
const [emailSubjectConfirm, setEmailSubjectConfirm] = useState(state.emailSubjects?.confirmation || DEFAULT_EMAIL_SUBJECTS.confirmation);
|
|
@@ -126,11 +235,14 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
126
235
|
!needsSSO &&
|
|
127
236
|
!needsMonitoring &&
|
|
128
237
|
!needsLogging &&
|
|
238
|
+
!needsTracing &&
|
|
239
|
+
!needsAppLogs &&
|
|
240
|
+
!needsValkeyAdmin &&
|
|
129
241
|
!needsCustomEmails) {
|
|
130
242
|
onComplete();
|
|
131
243
|
}
|
|
132
244
|
}, []);
|
|
133
|
-
|
|
245
|
+
useGatedInput((input, key) => {
|
|
134
246
|
if (key.escape) {
|
|
135
247
|
setError(null);
|
|
136
248
|
handleBack();
|
|
@@ -159,7 +271,7 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
159
271
|
case "sso-client-secret":
|
|
160
272
|
setSubStep("sso-client-id");
|
|
161
273
|
break;
|
|
162
|
-
case "monitoring-remote-write-
|
|
274
|
+
case "monitoring-remote-write-destination":
|
|
163
275
|
if (needsSSO)
|
|
164
276
|
setSubStep("sso-client-secret");
|
|
165
277
|
else if (needsAI)
|
|
@@ -167,12 +279,70 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
167
279
|
else
|
|
168
280
|
onBack();
|
|
169
281
|
break;
|
|
282
|
+
case "monitoring-azure-target-loading":
|
|
283
|
+
case "monitoring-azure-target":
|
|
284
|
+
case "monitoring-aws-region-loading":
|
|
285
|
+
case "monitoring-aws-region":
|
|
286
|
+
// Azure target discovery and AWS region both branch directly off the
|
|
287
|
+
// destination choice.
|
|
288
|
+
setSubStep("monitoring-remote-write-destination");
|
|
289
|
+
break;
|
|
290
|
+
case "monitoring-aws-workspace-loading":
|
|
291
|
+
case "monitoring-aws-workspace":
|
|
292
|
+
setSubStep("monitoring-aws-region");
|
|
293
|
+
break;
|
|
170
294
|
case "monitoring-remote-write-url":
|
|
171
|
-
|
|
295
|
+
// The manual-URL fallback is reached from the discovery picker.
|
|
296
|
+
if (remoteWriteDestination === "azure-monitor") {
|
|
297
|
+
setSubStep("monitoring-azure-target");
|
|
298
|
+
}
|
|
299
|
+
else if (remoteWriteDestination === "aws-amp") {
|
|
300
|
+
setSubStep("monitoring-aws-workspace");
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
setSubStep("monitoring-remote-write-destination");
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
case "monitoring-remote-write-azure-auth":
|
|
307
|
+
setSubStep("monitoring-azure-target");
|
|
308
|
+
break;
|
|
309
|
+
case "monitoring-remote-write-generic-auth":
|
|
310
|
+
setSubStep("monitoring-remote-write-url");
|
|
311
|
+
break;
|
|
312
|
+
case "monitoring-azure-identity-loading":
|
|
313
|
+
case "monitoring-remote-write-client-id":
|
|
314
|
+
if (remoteWriteDestination === "azure-monitor") {
|
|
315
|
+
setSubStep("monitoring-remote-write-azure-auth");
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
setSubStep("monitoring-remote-write-url");
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
case "monitoring-remote-write-client-id-manual":
|
|
322
|
+
setSubStep("monitoring-remote-write-client-id");
|
|
323
|
+
break;
|
|
324
|
+
case "monitoring-remote-write-tenant-id":
|
|
325
|
+
setSubStep("monitoring-remote-write-client-id");
|
|
326
|
+
break;
|
|
327
|
+
case "monitoring-remote-write-secret-ref":
|
|
328
|
+
setSubStep(remoteWriteDestination === "azure-monitor"
|
|
329
|
+
? "monitoring-remote-write-tenant-id"
|
|
330
|
+
: "monitoring-remote-write-url");
|
|
331
|
+
break;
|
|
332
|
+
case "monitoring-remote-write-username-secret-ref":
|
|
333
|
+
setSubStep(remoteWriteDestination === "grafana-cloud"
|
|
334
|
+
? "monitoring-remote-write-url"
|
|
335
|
+
: "monitoring-remote-write-generic-auth");
|
|
336
|
+
break;
|
|
337
|
+
case "monitoring-remote-write-password-secret-ref":
|
|
338
|
+
setSubStep("monitoring-remote-write-username-secret-ref");
|
|
339
|
+
break;
|
|
340
|
+
case "monitoring-remote-write-bearer-secret-ref":
|
|
341
|
+
setSubStep("monitoring-remote-write-generic-auth");
|
|
172
342
|
break;
|
|
173
|
-
case "logging-
|
|
343
|
+
case "logging-sink":
|
|
174
344
|
if (needsMonitoring)
|
|
175
|
-
setSubStep("monitoring-remote-write-
|
|
345
|
+
setSubStep("monitoring-remote-write-destination");
|
|
176
346
|
else if (needsSSO)
|
|
177
347
|
setSubStep("sso-client-secret");
|
|
178
348
|
else if (needsAI)
|
|
@@ -180,17 +350,6 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
180
350
|
else
|
|
181
351
|
onBack();
|
|
182
352
|
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
353
|
// Logging platform config steps
|
|
195
354
|
case "logging-datadog-config":
|
|
196
355
|
case "logging-splunk-config":
|
|
@@ -200,12 +359,93 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
200
359
|
case "logging-axiom-config":
|
|
201
360
|
setSubStep("logging-sink");
|
|
202
361
|
break;
|
|
362
|
+
// Distributed tracing steps
|
|
363
|
+
case "tracing-destination":
|
|
364
|
+
if (needsLogging)
|
|
365
|
+
setSubStep("logging-sink");
|
|
366
|
+
else if (needsMonitoring)
|
|
367
|
+
setSubStep("monitoring-remote-write-destination");
|
|
368
|
+
else if (needsSSO)
|
|
369
|
+
setSubStep("sso-client-secret");
|
|
370
|
+
else if (needsAI)
|
|
371
|
+
setSubStep("openai-key");
|
|
372
|
+
else
|
|
373
|
+
onBack();
|
|
374
|
+
break;
|
|
375
|
+
case "tracing-endpoint":
|
|
376
|
+
case "tracing-otlp-endpoint":
|
|
377
|
+
case "tracing-azure-connection":
|
|
378
|
+
setSubStep("tracing-destination");
|
|
379
|
+
break;
|
|
380
|
+
case "tracing-token":
|
|
381
|
+
setSubStep("tracing-endpoint");
|
|
382
|
+
break;
|
|
383
|
+
case "tracing-otlp-auth":
|
|
384
|
+
setSubStep("tracing-otlp-endpoint");
|
|
385
|
+
break;
|
|
386
|
+
case "tracing-otlp-cred":
|
|
387
|
+
setSubStep("tracing-otlp-auth");
|
|
388
|
+
break;
|
|
389
|
+
// Application log shipping steps
|
|
390
|
+
case "applogs-endpoint":
|
|
391
|
+
if (needsTracing)
|
|
392
|
+
setSubStep(tracingFinalStep());
|
|
393
|
+
else if (needsLogging)
|
|
394
|
+
setSubStep(loggingFinalStep());
|
|
395
|
+
else if (needsMonitoring)
|
|
396
|
+
setSubStep("monitoring-remote-write-destination");
|
|
397
|
+
else if (needsSSO)
|
|
398
|
+
setSubStep("sso-client-secret");
|
|
399
|
+
else if (needsAI)
|
|
400
|
+
setSubStep("openai-key");
|
|
401
|
+
else
|
|
402
|
+
onBack();
|
|
403
|
+
break;
|
|
404
|
+
case "applogs-user":
|
|
405
|
+
setSubStep("applogs-endpoint");
|
|
406
|
+
break;
|
|
407
|
+
case "applogs-pass":
|
|
408
|
+
setSubStep("applogs-user");
|
|
409
|
+
break;
|
|
410
|
+
case "applogs-index":
|
|
411
|
+
setSubStep("applogs-pass");
|
|
412
|
+
break;
|
|
413
|
+
// Valkey Admin steps
|
|
414
|
+
case "valkey-admin-username":
|
|
415
|
+
if (needsAppLogs)
|
|
416
|
+
setSubStep("applogs-index");
|
|
417
|
+
else if (needsTracing)
|
|
418
|
+
setSubStep(tracingFinalStep());
|
|
419
|
+
else if (needsLogging)
|
|
420
|
+
setSubStep(loggingFinalStep());
|
|
421
|
+
else if (needsMonitoring)
|
|
422
|
+
setSubStep("monitoring-remote-write-destination");
|
|
423
|
+
else if (needsSSO)
|
|
424
|
+
setSubStep("sso-client-secret");
|
|
425
|
+
else if (needsAI)
|
|
426
|
+
setSubStep("openai-key");
|
|
427
|
+
else
|
|
428
|
+
onBack();
|
|
429
|
+
break;
|
|
430
|
+
case "valkey-admin-password":
|
|
431
|
+
setSubStep("valkey-admin-username");
|
|
432
|
+
break;
|
|
433
|
+
case "valkey-admin-allowed-ips":
|
|
434
|
+
setSubStep("valkey-admin-password");
|
|
435
|
+
break;
|
|
203
436
|
// Email template steps
|
|
204
437
|
case "email-subject-invite":
|
|
205
|
-
if (
|
|
206
|
-
setSubStep("
|
|
438
|
+
if (needsValkeyAdmin) {
|
|
439
|
+
setSubStep("valkey-admin-allowed-ips");
|
|
440
|
+
}
|
|
441
|
+
else if (needsAppLogs)
|
|
442
|
+
setSubStep("applogs-index");
|
|
443
|
+
else if (needsTracing)
|
|
444
|
+
setSubStep(tracingFinalStep());
|
|
445
|
+
else if (needsLogging)
|
|
446
|
+
setSubStep(loggingFinalStep());
|
|
207
447
|
else if (needsMonitoring)
|
|
208
|
-
setSubStep("monitoring-remote-write-
|
|
448
|
+
setSubStep("monitoring-remote-write-destination");
|
|
209
449
|
else if (needsSSO)
|
|
210
450
|
setSubStep("sso-client-secret");
|
|
211
451
|
else if (needsAI)
|
|
@@ -236,45 +476,68 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
236
476
|
break;
|
|
237
477
|
}
|
|
238
478
|
};
|
|
479
|
+
// Section sequencing helpers. Each "goTo<Section>OrAfter" advances to the next
|
|
480
|
+
// enabled section, so adding a section only required inserting it into this
|
|
481
|
+
// chain (Logging -> Tracing -> AppLogs -> Valkey Admin -> Custom Emails).
|
|
482
|
+
const goToCustomEmailsOrDone = () => {
|
|
483
|
+
if (needsCustomEmails)
|
|
484
|
+
setSubStep("email-subject-invite");
|
|
485
|
+
else
|
|
486
|
+
onComplete();
|
|
487
|
+
};
|
|
488
|
+
const goToValkeyAdminOrAfter = () => {
|
|
489
|
+
if (needsValkeyAdmin)
|
|
490
|
+
setSubStep("valkey-admin-username");
|
|
491
|
+
else
|
|
492
|
+
goToCustomEmailsOrDone();
|
|
493
|
+
};
|
|
494
|
+
const goToAppLogsOrAfter = () => {
|
|
495
|
+
if (needsAppLogs)
|
|
496
|
+
setSubStep("applogs-endpoint");
|
|
497
|
+
else
|
|
498
|
+
goToValkeyAdminOrAfter();
|
|
499
|
+
};
|
|
500
|
+
const goToTracingOrAfter = () => {
|
|
501
|
+
if (needsTracing)
|
|
502
|
+
setSubStep("tracing-destination");
|
|
503
|
+
else
|
|
504
|
+
goToAppLogsOrAfter();
|
|
505
|
+
};
|
|
506
|
+
const goToLoggingOrAfter = () => {
|
|
507
|
+
if (needsLogging)
|
|
508
|
+
setSubStep("logging-sink");
|
|
509
|
+
else
|
|
510
|
+
goToTracingOrAfter();
|
|
511
|
+
};
|
|
512
|
+
const goToMonitoringOrAfter = () => {
|
|
513
|
+
if (needsMonitoring)
|
|
514
|
+
setSubStep("monitoring-remote-write-destination");
|
|
515
|
+
else
|
|
516
|
+
goToLoggingOrAfter();
|
|
517
|
+
};
|
|
239
518
|
const advanceToNext = (from) => {
|
|
240
519
|
switch (from) {
|
|
241
520
|
case "openai-key":
|
|
242
521
|
if (needsSSO)
|
|
243
522
|
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
523
|
else
|
|
251
|
-
|
|
524
|
+
goToMonitoringOrAfter();
|
|
252
525
|
break;
|
|
253
526
|
case "sso-client-secret":
|
|
254
|
-
|
|
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();
|
|
527
|
+
goToMonitoringOrAfter();
|
|
262
528
|
break;
|
|
263
|
-
case "monitoring-remote-write-
|
|
529
|
+
case "monitoring-remote-write-destination":
|
|
264
530
|
case "monitoring-remote-write-url":
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
case "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
setSubStep("email-subject-invite");
|
|
276
|
-
else
|
|
277
|
-
onComplete();
|
|
531
|
+
case "monitoring-aws-region":
|
|
532
|
+
case "monitoring-remote-write-azure-auth":
|
|
533
|
+
case "monitoring-remote-write-generic-auth":
|
|
534
|
+
case "monitoring-remote-write-client-id":
|
|
535
|
+
case "monitoring-remote-write-tenant-id":
|
|
536
|
+
case "monitoring-remote-write-secret-ref":
|
|
537
|
+
case "monitoring-remote-write-username-secret-ref":
|
|
538
|
+
case "monitoring-remote-write-password-secret-ref":
|
|
539
|
+
case "monitoring-remote-write-bearer-secret-ref":
|
|
540
|
+
goToLoggingOrAfter();
|
|
278
541
|
break;
|
|
279
542
|
case "logging-datadog-config":
|
|
280
543
|
case "logging-splunk-config":
|
|
@@ -282,11 +545,19 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
282
545
|
case "logging-loki-config":
|
|
283
546
|
case "logging-newrelic-config":
|
|
284
547
|
case "logging-axiom-config":
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
548
|
+
// Decision-log platform config complete -> tracing/appLogs/emails.
|
|
549
|
+
goToTracingOrAfter();
|
|
550
|
+
break;
|
|
551
|
+
case "tracing-token":
|
|
552
|
+
case "tracing-otlp-cred":
|
|
553
|
+
case "tracing-azure-connection":
|
|
554
|
+
goToAppLogsOrAfter();
|
|
555
|
+
break;
|
|
556
|
+
case "applogs-index":
|
|
557
|
+
goToValkeyAdminOrAfter();
|
|
558
|
+
break;
|
|
559
|
+
case "valkey-admin-allowed-ips":
|
|
560
|
+
goToCustomEmailsOrDone();
|
|
290
561
|
break;
|
|
291
562
|
case "email-template-change":
|
|
292
563
|
// All email config complete
|
|
@@ -361,151 +632,339 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
361
632
|
advanceToNext("sso-client-secret");
|
|
362
633
|
};
|
|
363
634
|
// === Monitoring Configuration ===
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
635
|
+
const handleRemoteWriteDestinationSelect = (item) => {
|
|
636
|
+
const destination = item.value;
|
|
637
|
+
setRemoteWriteDestination(destination);
|
|
638
|
+
setError(null);
|
|
639
|
+
dispatch({
|
|
640
|
+
type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
|
|
641
|
+
config: {
|
|
642
|
+
prometheusMonitoringDestination: destination,
|
|
643
|
+
prometheusRemoteWriteDestination: destination,
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
// Auto-discover the remote_write URL where we can, so the user selects an
|
|
647
|
+
// existing target instead of hand-building a URL. Manual entry stays available.
|
|
648
|
+
if (destination === "azure-monitor") {
|
|
649
|
+
loadAzureTargets();
|
|
650
|
+
}
|
|
651
|
+
else if (destination === "aws-amp") {
|
|
652
|
+
loadAwsRegions();
|
|
367
653
|
}
|
|
368
654
|
else {
|
|
369
|
-
|
|
370
|
-
|
|
655
|
+
setSubStep("monitoring-remote-write-url");
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
// Azure Monitor: discover Data Collection Rules that ingest Prometheus metrics
|
|
659
|
+
// and pre-assemble each remote_write URL.
|
|
660
|
+
const loadAzureTargets = async () => {
|
|
661
|
+
setSubStep("monitoring-azure-target-loading");
|
|
662
|
+
try {
|
|
663
|
+
setRwTargets(await listAzurePrometheusTargets());
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
setRwTargets([]);
|
|
667
|
+
}
|
|
668
|
+
setSubStep("monitoring-azure-target");
|
|
669
|
+
};
|
|
670
|
+
const handleAzureTargetSelect = (item) => {
|
|
671
|
+
if (item.value === REFRESH) {
|
|
672
|
+
loadAzureTargets();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (item.value === MANUAL) {
|
|
676
|
+
setSubStep("monitoring-remote-write-url");
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
setRemoteWriteUrl(item.value);
|
|
680
|
+
dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: item.value });
|
|
681
|
+
setError(null);
|
|
682
|
+
setSubStep("monitoring-remote-write-azure-auth");
|
|
683
|
+
};
|
|
684
|
+
// AWS Managed Prometheus: discover workspaces in the chosen region and
|
|
685
|
+
// pre-assemble each remote_write URL.
|
|
686
|
+
const loadAwsWorkspaces = async (region) => {
|
|
687
|
+
setSubStep("monitoring-aws-workspace-loading");
|
|
688
|
+
try {
|
|
689
|
+
setRwTargets(await listAwsPrometheusWorkspaces(region));
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
setRwTargets([]);
|
|
371
693
|
}
|
|
694
|
+
setSubStep("monitoring-aws-workspace");
|
|
695
|
+
};
|
|
696
|
+
const handleAwsWorkspaceSelect = (item) => {
|
|
697
|
+
if (item.value === REFRESH) {
|
|
698
|
+
loadAwsWorkspaces(remoteWriteAwsRegion);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (item.value === MANUAL) {
|
|
702
|
+
setSubStep("monitoring-remote-write-url");
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
setRemoteWriteUrl(item.value);
|
|
706
|
+
dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: item.value });
|
|
707
|
+
setError(null);
|
|
708
|
+
saveAwsAmpConfig("");
|
|
372
709
|
};
|
|
373
710
|
const handleRemoteWriteUrlSubmit = () => {
|
|
374
|
-
if (remoteWriteUrl) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
711
|
+
if (!remoteWriteUrl) {
|
|
712
|
+
setError("Remote write URL is required. If you don't have a destination yet, go back and disable Metrics Export.");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
try {
|
|
716
|
+
new URL(remoteWriteUrl);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
setError("Invalid URL format");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Azure Monitor needs the full DCE metrics-ingestion path, not the bare DCE
|
|
723
|
+
// host. Catch it here so the user gets immediate feedback instead of a
|
|
724
|
+
// failure at save time.
|
|
725
|
+
if (remoteWriteDestination === "azure-monitor" &&
|
|
726
|
+
!(remoteWriteUrl.includes("/dataCollectionRules/") &&
|
|
727
|
+
remoteWriteUrl.includes("/streams/") &&
|
|
728
|
+
remoteWriteUrl.includes("/api/v1/write"))) {
|
|
729
|
+
setError("Azure Monitor needs the full ingestion URL, e.g.\n" +
|
|
730
|
+
"https://<dce>.<region>.metrics.ingest.monitor.azure.com/dataCollectionRules/<dcrImmutableId>/streams/Microsoft-PrometheusMetrics/api/v1/write?api-version=2023-04-24\n" +
|
|
731
|
+
"(the data collection endpoint host alone won't work).");
|
|
732
|
+
return;
|
|
382
733
|
}
|
|
383
734
|
setError(null);
|
|
384
735
|
dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: remoteWriteUrl });
|
|
385
|
-
|
|
736
|
+
if (remoteWriteDestination === "aws-amp") {
|
|
737
|
+
// Region was already chosen before this manual-URL fallback (it's reached
|
|
738
|
+
// from the workspace picker), so save directly.
|
|
739
|
+
saveAwsAmpConfig("");
|
|
740
|
+
}
|
|
741
|
+
else if (remoteWriteDestination === "azure-monitor") {
|
|
742
|
+
setSubStep("monitoring-remote-write-azure-auth");
|
|
743
|
+
}
|
|
744
|
+
else if (remoteWriteDestination === "grafana-cloud") {
|
|
745
|
+
setRemoteWriteAuthType("basic");
|
|
746
|
+
dispatch({
|
|
747
|
+
type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
|
|
748
|
+
config: { prometheusRemoteWriteAuthType: "basic" },
|
|
749
|
+
});
|
|
750
|
+
setSubStep("monitoring-remote-write-username-secret-ref");
|
|
751
|
+
}
|
|
752
|
+
else if (remoteWriteDestination === "generic") {
|
|
753
|
+
setSubStep("monitoring-remote-write-generic-auth");
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
setError("Select a remote_write destination first");
|
|
757
|
+
}
|
|
386
758
|
};
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
759
|
+
// AWS Managed Prometheus: pick region then IRSA role from CLI-backed lists.
|
|
760
|
+
const loadAwsRegions = async () => {
|
|
761
|
+
setSubStep("monitoring-aws-region-loading");
|
|
762
|
+
try {
|
|
763
|
+
const regions = await listRegions("aws");
|
|
764
|
+
setRwRegions(regions.length > 0 ? regions : CLOUD_REGIONS.aws);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
setRwRegions(CLOUD_REGIONS.aws);
|
|
768
|
+
}
|
|
769
|
+
setSubStep("monitoring-aws-region");
|
|
397
770
|
};
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
771
|
+
const handleAwsRegionSelect = (item) => {
|
|
772
|
+
setRemoteWriteAwsRegion(item.value);
|
|
773
|
+
// Discover AMP workspaces in this region; the role is the single Rulebricks
|
|
774
|
+
// role from the Storage step (reused at assembly), so there's no role prompt.
|
|
775
|
+
loadAwsWorkspaces(item.value);
|
|
401
776
|
};
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
777
|
+
const saveAwsAmpConfig = (roleArn) => {
|
|
778
|
+
dispatch({
|
|
779
|
+
type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
|
|
780
|
+
config: {
|
|
781
|
+
prometheusRemoteWriteDestination: "aws-amp",
|
|
782
|
+
prometheusMonitoringDestination: "aws-amp",
|
|
783
|
+
prometheusRemoteWriteAuthType: "none",
|
|
784
|
+
prometheusRemoteWriteAwsRegion: remoteWriteAwsRegion,
|
|
785
|
+
prometheusRemoteWriteAwsRoleArn: roleArn,
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
setError(null);
|
|
789
|
+
advanceToNext("monitoring-aws-region");
|
|
407
790
|
};
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
}
|
|
791
|
+
const saveRemoteWriteConfig = (authType, overrides = {}) => {
|
|
792
|
+
if (!remoteWriteDestination || !remoteWriteUrl) {
|
|
793
|
+
setError("Remote write destination and URL are required");
|
|
794
|
+
return;
|
|
439
795
|
}
|
|
796
|
+
dispatch({
|
|
797
|
+
type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
|
|
798
|
+
config: {
|
|
799
|
+
prometheusRemoteWriteDestination: remoteWriteDestination,
|
|
800
|
+
prometheusMonitoringDestination: remoteWriteDestination,
|
|
801
|
+
prometheusRemoteWriteAuthType: authType,
|
|
802
|
+
prometheusRemoteWriteAzureCloud: remoteWriteAzureCloud,
|
|
803
|
+
prometheusRemoteWriteClientId: overrides.clientId ?? remoteWriteClientId,
|
|
804
|
+
prometheusRemoteWriteTenantId: overrides.tenantId ?? remoteWriteTenantId,
|
|
805
|
+
prometheusRemoteWriteSecretRef: overrides.secretRef ?? remoteWriteSecretRef,
|
|
806
|
+
prometheusRemoteWriteUsernameSecretRef: overrides.usernameSecretRef ?? remoteWriteUsernameSecretRef,
|
|
807
|
+
prometheusRemoteWritePasswordSecretRef: overrides.passwordSecretRef ?? remoteWritePasswordSecretRef,
|
|
808
|
+
prometheusRemoteWriteBearerTokenSecretRef: overrides.bearerTokenSecretRef ?? remoteWriteBearerSecretRef,
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
setError(null);
|
|
812
|
+
advanceToNext("monitoring-remote-write-url");
|
|
440
813
|
};
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
814
|
+
const handleAzureRemoteWriteAuthSelect = (item) => {
|
|
815
|
+
const authType = item.value;
|
|
816
|
+
setRemoteWriteAuthType(authType);
|
|
817
|
+
dispatch({
|
|
818
|
+
type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
|
|
819
|
+
config: { prometheusRemoteWriteAuthType: authType },
|
|
820
|
+
});
|
|
821
|
+
// Workload/managed identity reuse the single Rulebricks identity chosen in
|
|
822
|
+
// the Storage step (filled in during config assembly), so there's no second
|
|
823
|
+
// identity to pick here. Only OAuth needs its own app-registration credentials.
|
|
824
|
+
if (authType === "workload-identity" || authType === "managed-identity") {
|
|
825
|
+
saveRemoteWriteConfig(authType);
|
|
446
826
|
return;
|
|
447
827
|
}
|
|
448
|
-
|
|
828
|
+
loadAzureIdentitiesForRemoteWrite();
|
|
829
|
+
};
|
|
830
|
+
// Azure Monitor: pick the managed/workload identity client ID from a list and
|
|
831
|
+
// auto-fill the tenant ID from the active Azure CLI session.
|
|
832
|
+
const loadAzureIdentitiesForRemoteWrite = async () => {
|
|
833
|
+
setSubStep("monitoring-azure-identity-loading");
|
|
449
834
|
try {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
835
|
+
const [identities, tenant] = await Promise.all([
|
|
836
|
+
listAzureManagedIdentities(),
|
|
837
|
+
remoteWriteTenantId
|
|
838
|
+
? Promise.resolve(null)
|
|
839
|
+
: getAzureTenantId(),
|
|
840
|
+
]);
|
|
841
|
+
setRwIdentities(identities);
|
|
842
|
+
if (tenant) {
|
|
843
|
+
setRemoteWriteTenantId(tenant);
|
|
844
|
+
setRwTenantAutoDetected(true);
|
|
456
845
|
}
|
|
457
846
|
}
|
|
458
847
|
catch {
|
|
459
|
-
|
|
848
|
+
setRwIdentities([]);
|
|
460
849
|
}
|
|
461
|
-
setSubStep("
|
|
850
|
+
setSubStep("monitoring-remote-write-client-id");
|
|
462
851
|
};
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
852
|
+
const proceedAfterClientId = () => {
|
|
853
|
+
if (remoteWriteAuthType === "managed-identity") {
|
|
854
|
+
saveRemoteWriteConfig("managed-identity", {
|
|
855
|
+
clientId: remoteWriteClientId,
|
|
856
|
+
});
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
setError(null);
|
|
860
|
+
setSubStep("monitoring-remote-write-tenant-id");
|
|
861
|
+
};
|
|
862
|
+
const handleRemoteWriteClientIdSelect = (item) => {
|
|
863
|
+
if (item.value === MANUAL) {
|
|
864
|
+
setSubStep("monitoring-remote-write-client-id-manual");
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
setRemoteWriteClientId(item.value);
|
|
868
|
+
proceedAfterClientId();
|
|
869
|
+
};
|
|
870
|
+
const handleGenericRemoteWriteAuthSelect = (item) => {
|
|
871
|
+
const authType = item.value;
|
|
872
|
+
setRemoteWriteAuthType(authType);
|
|
873
|
+
if (authType === "none") {
|
|
874
|
+
saveRemoteWriteConfig("none");
|
|
875
|
+
}
|
|
876
|
+
else if (authType === "basic") {
|
|
877
|
+
setSubStep("monitoring-remote-write-username-secret-ref");
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
setSubStep("monitoring-remote-write-bearer-secret-ref");
|
|
481
881
|
}
|
|
482
|
-
setSubStep("logging-bucket");
|
|
483
882
|
};
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
883
|
+
const handleRemoteWriteClientIdSubmit = () => {
|
|
884
|
+
if (!remoteWriteClientId) {
|
|
885
|
+
setError("Client ID is required");
|
|
487
886
|
return;
|
|
488
|
-
|
|
489
|
-
|
|
887
|
+
}
|
|
888
|
+
proceedAfterClientId();
|
|
889
|
+
};
|
|
890
|
+
const handleRemoteWriteTenantIdSubmit = () => {
|
|
891
|
+
if (!remoteWriteTenantId) {
|
|
892
|
+
setError("Tenant ID is required");
|
|
490
893
|
return;
|
|
491
|
-
setIsRefreshing(true);
|
|
492
|
-
try {
|
|
493
|
-
const buckets = await listBucketsInRegion(provider, loggingRegion);
|
|
494
|
-
setAvailableBuckets(buckets);
|
|
495
894
|
}
|
|
496
|
-
|
|
497
|
-
|
|
895
|
+
if (remoteWriteAuthType === "workload-identity") {
|
|
896
|
+
saveRemoteWriteConfig("workload-identity", {
|
|
897
|
+
clientId: remoteWriteClientId,
|
|
898
|
+
tenantId: remoteWriteTenantId,
|
|
899
|
+
});
|
|
900
|
+
return;
|
|
498
901
|
}
|
|
499
|
-
|
|
902
|
+
setError(null);
|
|
903
|
+
setSubStep("monitoring-remote-write-secret-ref");
|
|
500
904
|
};
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
905
|
+
const handleRemoteWriteSecretRefSubmit = () => {
|
|
906
|
+
if (!remoteWriteSecretRef.includes(":")) {
|
|
907
|
+
setError("Use secret-name:key format");
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
saveRemoteWriteConfig(remoteWriteAuthType || "oauth", {
|
|
911
|
+
clientId: remoteWriteClientId,
|
|
912
|
+
tenantId: remoteWriteTenantId,
|
|
913
|
+
secretRef: remoteWriteSecretRef,
|
|
507
914
|
});
|
|
508
|
-
|
|
915
|
+
};
|
|
916
|
+
const handleRemoteWriteUsernameSecretRefSubmit = () => {
|
|
917
|
+
if (!remoteWriteUsernameSecretRef.includes(":")) {
|
|
918
|
+
setError("Use secret-name:key format");
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
setError(null);
|
|
922
|
+
setSubStep("monitoring-remote-write-password-secret-ref");
|
|
923
|
+
};
|
|
924
|
+
const handleRemoteWritePasswordSecretRefSubmit = () => {
|
|
925
|
+
if (!remoteWritePasswordSecretRef.includes(":")) {
|
|
926
|
+
setError("Use secret-name:key format");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
saveRemoteWriteConfig("basic", {
|
|
930
|
+
usernameSecretRef: remoteWriteUsernameSecretRef,
|
|
931
|
+
passwordSecretRef: remoteWritePasswordSecretRef,
|
|
932
|
+
});
|
|
933
|
+
};
|
|
934
|
+
const handleRemoteWriteBearerSecretRefSubmit = () => {
|
|
935
|
+
if (!remoteWriteBearerSecretRef.includes(":")) {
|
|
936
|
+
setError("Use secret-name:key format");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
saveRemoteWriteConfig("bearer", {
|
|
940
|
+
bearerTokenSecretRef: remoteWriteBearerSecretRef,
|
|
941
|
+
});
|
|
942
|
+
};
|
|
943
|
+
// === Logging Configuration (external logging platforms) ===
|
|
944
|
+
const handleLoggingSinkSelect = (item) => {
|
|
945
|
+
const sink = item.value;
|
|
946
|
+
setLoggingSink(sink);
|
|
947
|
+
dispatch({ type: "SET_LOGGING_SINK", sink });
|
|
948
|
+
switch (sink) {
|
|
949
|
+
case "datadog":
|
|
950
|
+
setSubStep("logging-datadog-config");
|
|
951
|
+
break;
|
|
952
|
+
case "splunk":
|
|
953
|
+
setSubStep("logging-splunk-config");
|
|
954
|
+
break;
|
|
955
|
+
case "elasticsearch":
|
|
956
|
+
setSubStep("logging-elasticsearch-config");
|
|
957
|
+
break;
|
|
958
|
+
case "loki":
|
|
959
|
+
setSubStep("logging-loki-config");
|
|
960
|
+
break;
|
|
961
|
+
case "newrelic":
|
|
962
|
+
setSubStep("logging-newrelic-config");
|
|
963
|
+
break;
|
|
964
|
+
case "axiom":
|
|
965
|
+
setSubStep("logging-axiom-config");
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
509
968
|
};
|
|
510
969
|
// === Logging Platform Config Handlers ===
|
|
511
970
|
const handleDatadogConfigSubmit = () => {
|
|
@@ -517,8 +976,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
517
976
|
dispatch({
|
|
518
977
|
type: "SET_LOGGING_CONFIG",
|
|
519
978
|
config: {
|
|
520
|
-
|
|
521
|
-
|
|
979
|
+
loggingPlatformCredential: datadogApiKey, // platform API key
|
|
980
|
+
loggingPlatformDetail: datadogSite, // platform site
|
|
522
981
|
},
|
|
523
982
|
});
|
|
524
983
|
advanceToNext("logging-datadog-config");
|
|
@@ -543,8 +1002,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
543
1002
|
dispatch({
|
|
544
1003
|
type: "SET_LOGGING_CONFIG",
|
|
545
1004
|
config: {
|
|
546
|
-
|
|
547
|
-
|
|
1005
|
+
loggingPlatformCredential: splunkHecToken,
|
|
1006
|
+
loggingPlatformDetail: splunkUrl,
|
|
548
1007
|
},
|
|
549
1008
|
});
|
|
550
1009
|
advanceToNext("logging-splunk-config");
|
|
@@ -562,21 +1021,238 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
562
1021
|
return;
|
|
563
1022
|
}
|
|
564
1023
|
setError(null);
|
|
565
|
-
// Store as JSON in
|
|
1024
|
+
// Store the connection as JSON in the credential field for complex config.
|
|
566
1025
|
dispatch({
|
|
567
1026
|
type: "SET_LOGGING_CONFIG",
|
|
568
1027
|
config: {
|
|
569
|
-
|
|
1028
|
+
loggingPlatformCredential: JSON.stringify({
|
|
570
1029
|
url: elasticsearchUrl,
|
|
571
1030
|
user: elasticsearchUser,
|
|
572
1031
|
password: elasticsearchPass,
|
|
573
1032
|
index: elasticsearchIndex,
|
|
574
1033
|
}),
|
|
575
|
-
|
|
1034
|
+
loggingPlatformDetail: elasticsearchIndex,
|
|
576
1035
|
},
|
|
577
1036
|
});
|
|
578
1037
|
advanceToNext("logging-elasticsearch-config");
|
|
579
1038
|
};
|
|
1039
|
+
// === Distributed Tracing (Elastic APM) ===
|
|
1040
|
+
const handleTracingEndpointSubmit = () => {
|
|
1041
|
+
if (!tracingEndpoint) {
|
|
1042
|
+
setError("Elastic APM OTLP endpoint is required");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
new URL(tracingEndpoint);
|
|
1047
|
+
}
|
|
1048
|
+
catch {
|
|
1049
|
+
setError("Invalid URL format");
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
setError(null);
|
|
1053
|
+
dispatch({
|
|
1054
|
+
type: "SET_TRACING_CONFIG",
|
|
1055
|
+
config: {
|
|
1056
|
+
tracingElasticEndpoint: tracingEndpoint,
|
|
1057
|
+
tracingElasticAuthMode: "secret-token",
|
|
1058
|
+
},
|
|
1059
|
+
});
|
|
1060
|
+
setSubStep("tracing-token");
|
|
1061
|
+
};
|
|
1062
|
+
const handleTracingTokenSubmit = () => {
|
|
1063
|
+
if (!tracingToken) {
|
|
1064
|
+
setError("Elastic APM secret token is required");
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
setError(null);
|
|
1068
|
+
dispatch({
|
|
1069
|
+
type: "SET_TRACING_CONFIG",
|
|
1070
|
+
config: { tracingElasticSecretToken: tracingToken },
|
|
1071
|
+
});
|
|
1072
|
+
advanceToNext("tracing-token");
|
|
1073
|
+
};
|
|
1074
|
+
const handleTracingDestinationSelect = (item) => {
|
|
1075
|
+
const destination = item.value;
|
|
1076
|
+
setTracingDestination(destination);
|
|
1077
|
+
dispatch({
|
|
1078
|
+
type: "SET_TRACING_CONFIG",
|
|
1079
|
+
config: { tracingDestination: destination },
|
|
1080
|
+
});
|
|
1081
|
+
setError(null);
|
|
1082
|
+
if (destination === "elastic")
|
|
1083
|
+
setSubStep("tracing-endpoint");
|
|
1084
|
+
else if (destination === "otlp")
|
|
1085
|
+
setSubStep("tracing-otlp-endpoint");
|
|
1086
|
+
else
|
|
1087
|
+
setSubStep("tracing-azure-connection");
|
|
1088
|
+
};
|
|
1089
|
+
const handleTracingOtlpEndpointSubmit = () => {
|
|
1090
|
+
if (!tracingOtlpEndpoint) {
|
|
1091
|
+
setError("OTLP endpoint is required");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
new URL(tracingOtlpEndpoint);
|
|
1096
|
+
}
|
|
1097
|
+
catch {
|
|
1098
|
+
setError("Invalid URL format");
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
setError(null);
|
|
1102
|
+
dispatch({
|
|
1103
|
+
type: "SET_TRACING_CONFIG",
|
|
1104
|
+
config: { tracingOtlpEndpoint },
|
|
1105
|
+
});
|
|
1106
|
+
setSubStep("tracing-otlp-auth");
|
|
1107
|
+
};
|
|
1108
|
+
const handleTracingOtlpAuthSelect = (item) => {
|
|
1109
|
+
const mode = item.value;
|
|
1110
|
+
setTracingOtlpAuthMode(mode);
|
|
1111
|
+
dispatch({
|
|
1112
|
+
type: "SET_TRACING_CONFIG",
|
|
1113
|
+
config: { tracingOtlpAuthMode: mode },
|
|
1114
|
+
});
|
|
1115
|
+
setError(null);
|
|
1116
|
+
if (mode === "none") {
|
|
1117
|
+
// No credential needed; the section is complete.
|
|
1118
|
+
goToAppLogsOrAfter();
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
setSubStep("tracing-otlp-cred");
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
const handleTracingOtlpCredSubmit = () => {
|
|
1125
|
+
if (!tracingOtlpToken) {
|
|
1126
|
+
setError("A credential is required for the selected OTLP auth mode");
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
setError(null);
|
|
1130
|
+
dispatch({
|
|
1131
|
+
type: "SET_TRACING_CONFIG",
|
|
1132
|
+
config: { tracingOtlpToken },
|
|
1133
|
+
});
|
|
1134
|
+
advanceToNext("tracing-otlp-cred");
|
|
1135
|
+
};
|
|
1136
|
+
const handleTracingAzureConnectionSubmit = () => {
|
|
1137
|
+
if (!tracingAzureConnectionString) {
|
|
1138
|
+
setError("Azure Monitor connection string is required");
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
setError(null);
|
|
1142
|
+
dispatch({
|
|
1143
|
+
type: "SET_TRACING_CONFIG",
|
|
1144
|
+
config: { tracingAzureConnectionString },
|
|
1145
|
+
});
|
|
1146
|
+
advanceToNext("tracing-azure-connection");
|
|
1147
|
+
};
|
|
1148
|
+
// === Application Log Shipping (BYO Elasticsearch) ===
|
|
1149
|
+
const handleAppLogsEndpointSubmit = () => {
|
|
1150
|
+
if (!appLogsEndpoint) {
|
|
1151
|
+
setError("Elasticsearch endpoint is required");
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
try {
|
|
1155
|
+
new URL(appLogsEndpoint);
|
|
1156
|
+
}
|
|
1157
|
+
catch {
|
|
1158
|
+
setError("Invalid URL format");
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
setError(null);
|
|
1162
|
+
dispatch({
|
|
1163
|
+
type: "SET_APP_LOGS_CONFIG",
|
|
1164
|
+
config: {
|
|
1165
|
+
appLogsElasticEndpoint: appLogsEndpoint,
|
|
1166
|
+
appLogsElasticAuthMode: "basic",
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
setSubStep("applogs-user");
|
|
1170
|
+
};
|
|
1171
|
+
const handleAppLogsUserSubmit = () => {
|
|
1172
|
+
if (!appLogsUser) {
|
|
1173
|
+
setError("Elasticsearch username is required");
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
setError(null);
|
|
1177
|
+
dispatch({
|
|
1178
|
+
type: "SET_APP_LOGS_CONFIG",
|
|
1179
|
+
config: { appLogsElasticUsername: appLogsUser },
|
|
1180
|
+
});
|
|
1181
|
+
setSubStep("applogs-pass");
|
|
1182
|
+
};
|
|
1183
|
+
const handleAppLogsPassSubmit = () => {
|
|
1184
|
+
if (!appLogsPass) {
|
|
1185
|
+
setError("Elasticsearch password is required");
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
setError(null);
|
|
1189
|
+
dispatch({
|
|
1190
|
+
type: "SET_APP_LOGS_CONFIG",
|
|
1191
|
+
config: { appLogsElasticPassword: appLogsPass },
|
|
1192
|
+
});
|
|
1193
|
+
setSubStep("applogs-index");
|
|
1194
|
+
};
|
|
1195
|
+
const handleAppLogsIndexSubmit = () => {
|
|
1196
|
+
const index = appLogsIndex || "rulebricks-app-logs";
|
|
1197
|
+
setError(null);
|
|
1198
|
+
dispatch({
|
|
1199
|
+
type: "SET_APP_LOGS_CONFIG",
|
|
1200
|
+
config: { appLogsElasticIndex: index },
|
|
1201
|
+
});
|
|
1202
|
+
advanceToNext("applogs-index");
|
|
1203
|
+
};
|
|
1204
|
+
// === Valkey Admin Ingress ===
|
|
1205
|
+
const handleValkeyAdminUsernameSubmit = () => {
|
|
1206
|
+
const username = valkeyAdminUsername.trim();
|
|
1207
|
+
if (!username) {
|
|
1208
|
+
setError("Username is required");
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (username.includes(":")) {
|
|
1212
|
+
setError("Username cannot contain ':'");
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
setValkeyAdminUsername(username);
|
|
1216
|
+
setError(null);
|
|
1217
|
+
setSubStep("valkey-admin-password");
|
|
1218
|
+
};
|
|
1219
|
+
const handleValkeyAdminPasswordSubmit = () => {
|
|
1220
|
+
// Empty means "use a generated secure value", matching the Supabase flow.
|
|
1221
|
+
const effectivePassword = valkeyAdminPassword.trim() || defaultValkeyAdminPassword;
|
|
1222
|
+
if (effectivePassword.length < 8) {
|
|
1223
|
+
setError("Valkey Admin password must be at least 8 characters");
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
try {
|
|
1227
|
+
const htpasswdLine = generateHtpasswdLine(valkeyAdminUsername, effectivePassword);
|
|
1228
|
+
dispatch({
|
|
1229
|
+
type: "SET_EXTERNAL_SERVICES",
|
|
1230
|
+
config: {
|
|
1231
|
+
valkeyAdminExposure: "ingress",
|
|
1232
|
+
valkeyAdminHostname: "",
|
|
1233
|
+
valkeyAdminBasicAuthUsers: [htpasswdLine],
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
catch (err) {
|
|
1238
|
+
setError(err instanceof Error ? err.message : "Unable to hash password");
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
setError(null);
|
|
1242
|
+
setSubStep("valkey-admin-allowed-ips");
|
|
1243
|
+
};
|
|
1244
|
+
const handleValkeyAdminAllowedIPsSubmit = () => {
|
|
1245
|
+
const allowedIPs = valkeyAdminAllowedIPs
|
|
1246
|
+
.split(",")
|
|
1247
|
+
.map((value) => value.trim())
|
|
1248
|
+
.filter(Boolean);
|
|
1249
|
+
dispatch({
|
|
1250
|
+
type: "SET_EXTERNAL_SERVICES",
|
|
1251
|
+
config: { valkeyAdminAllowedIPs: allowedIPs },
|
|
1252
|
+
});
|
|
1253
|
+
setError(null);
|
|
1254
|
+
advanceToNext("valkey-admin-allowed-ips");
|
|
1255
|
+
};
|
|
580
1256
|
const handleLokiConfigSubmit = () => {
|
|
581
1257
|
if (!lokiUrl) {
|
|
582
1258
|
setError("Loki URL is required");
|
|
@@ -593,8 +1269,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
593
1269
|
dispatch({
|
|
594
1270
|
type: "SET_LOGGING_CONFIG",
|
|
595
1271
|
config: {
|
|
596
|
-
|
|
597
|
-
|
|
1272
|
+
loggingPlatformCredential: lokiUrl,
|
|
1273
|
+
loggingPlatformDetail: "",
|
|
598
1274
|
},
|
|
599
1275
|
});
|
|
600
1276
|
advanceToNext("logging-loki-config");
|
|
@@ -612,8 +1288,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
612
1288
|
dispatch({
|
|
613
1289
|
type: "SET_LOGGING_CONFIG",
|
|
614
1290
|
config: {
|
|
615
|
-
|
|
616
|
-
|
|
1291
|
+
loggingPlatformCredential: newrelicLicenseKey,
|
|
1292
|
+
loggingPlatformDetail: newrelicAccountId,
|
|
617
1293
|
},
|
|
618
1294
|
});
|
|
619
1295
|
advanceToNext("logging-newrelic-config");
|
|
@@ -631,8 +1307,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
631
1307
|
dispatch({
|
|
632
1308
|
type: "SET_LOGGING_CONFIG",
|
|
633
1309
|
config: {
|
|
634
|
-
|
|
635
|
-
|
|
1310
|
+
loggingPlatformCredential: axiomApiToken,
|
|
1311
|
+
loggingPlatformDetail: axiomDataset,
|
|
636
1312
|
},
|
|
637
1313
|
});
|
|
638
1314
|
advanceToNext("logging-axiom-config");
|
|
@@ -725,41 +1401,76 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
|
|
|
725
1401
|
});
|
|
726
1402
|
advanceToNext("email-template-change");
|
|
727
1403
|
};
|
|
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
1404
|
// If nothing to configure, don't render
|
|
743
1405
|
if (!needsAI &&
|
|
744
1406
|
!needsSSO &&
|
|
745
1407
|
!needsMonitoring &&
|
|
746
1408
|
!needsLogging &&
|
|
1409
|
+
!needsTracing &&
|
|
1410
|
+
!needsAppLogs &&
|
|
1411
|
+
!needsValkeyAdmin &&
|
|
747
1412
|
!needsCustomEmails) {
|
|
748
1413
|
return null;
|
|
749
1414
|
}
|
|
1415
|
+
// Shared list item renderer (matches the wizard's other select lists).
|
|
1416
|
+
const selectItem = ({ isSelected, label, }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] }));
|
|
750
1417
|
// Progress summary
|
|
751
1418
|
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
1419
|
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
1420
|
"e.g., https://login.microsoftonline.com/your-tenant-id", ssoProvider === "okta" && "e.g., https://your-org.okta.com", ssoProvider === "keycloak" &&
|
|
754
1421
|
"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-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
1422
|
+
"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-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" }), remoteWriteDestination === "azure-monitor" && (_jsx(Text, { color: "gray", dimColor: true, children: "Use the ingestion URL from your Azure Monitor workspace/Data Collection Rule." })), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteUrl, onChange: setRemoteWriteUrl, onSubmit: handleRemoteWriteUrlSubmit, placeholder: "https://metrics.example.com/api/v1/write" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-aws-region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading AWS regions..." }) })), subStep === "monitoring-aws-region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "AWS Managed Prometheus Region" }), _jsx(Text, { color: "gray", dimColor: true, children: "Region of your AMP workspace (defaults to your cluster region)." }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: rwRegions.map((r) => ({ label: r, value: r })), onSelect: handleAwsRegionSelect, limit: 8, initialIndex: Math.max(0, rwRegions.indexOf(remoteWriteAwsRegion)), indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-aws-workspace-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Discovering AMP workspaces..." }) })), subStep === "monitoring-aws-workspace" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Select AMP Workspace" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Choose the workspace your Rulebricks role can write to. cluster-setup creates ", `${state.clusterName || "<cluster>"}-amp`, " and grants aps:RemoteWrite on it."] }), rwTargets.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["None found in ", remoteWriteAwsRegion, ". Refresh after creating one, or enter a URL manually."] }) })), (() => {
|
|
1423
|
+
const recommendedPrefix = `${state.clusterName || ""}-amp`.toLowerCase();
|
|
1424
|
+
const isRec = (name) => recommendedPrefix !== "-amp" &&
|
|
1425
|
+
name.toLowerCase().startsWith(recommendedPrefix);
|
|
1426
|
+
const sorted = [...rwTargets].sort((a, b) => {
|
|
1427
|
+
return ((isRec(a.name) ? 0 : 1) - (isRec(b.name) ? 0 : 1) ||
|
|
1428
|
+
a.name.localeCompare(b.name));
|
|
1429
|
+
});
|
|
1430
|
+
return (_jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: [
|
|
1431
|
+
...sorted.map((t) => ({
|
|
1432
|
+
label: isRec(t.name) ? `${t.name} - recommended` : t.name,
|
|
1433
|
+
value: t.url,
|
|
1434
|
+
})),
|
|
1435
|
+
{ label: "↻ Refresh list", value: REFRESH },
|
|
1436
|
+
{ label: "Enter URL manually…", value: MANUAL },
|
|
1437
|
+
], onSelect: handleAwsWorkspaceSelect, limit: 8, indicatorComponent: () => null, itemComponent: selectItem }) }));
|
|
1438
|
+
})(), _jsx(ProgressSummary, {})] })), subStep === "monitoring-azure-target-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Discovering Azure Monitor data collection rules..." }) })), subStep === "monitoring-azure-target" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Select Azure Monitor target" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Choose the Data Collection Rule your Rulebricks identity can publish to. cluster-setup grants that on ", `${state.clusterName || "<cluster>"}-dcr`, "; the workspace's auto-created ", `${state.clusterName || "<cluster>"}-amw`, " rule usually lacks the publish role."] }), rwTargets.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: "None found. Refresh after creating a Prometheus DCR/DCE, or enter a URL manually." }) })), (() => {
|
|
1439
|
+
// Recommend the cluster-setup DCR (<cluster>-dcr) -- the one granted
|
|
1440
|
+
// Monitoring Metrics Publisher -- and list it first.
|
|
1441
|
+
const recommendedName = `${state.clusterName || ""}-dcr`.toLowerCase();
|
|
1442
|
+
const sorted = [...rwTargets].sort((a, b) => {
|
|
1443
|
+
const aRec = a.name.toLowerCase() === recommendedName ? 0 : 1;
|
|
1444
|
+
const bRec = b.name.toLowerCase() === recommendedName ? 0 : 1;
|
|
1445
|
+
return aRec - bRec || a.name.localeCompare(b.name);
|
|
1446
|
+
});
|
|
1447
|
+
return (_jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: [
|
|
1448
|
+
...sorted.map((t) => ({
|
|
1449
|
+
label: t.name.toLowerCase() === recommendedName
|
|
1450
|
+
? `${t.name} - recommended`
|
|
1451
|
+
: t.name,
|
|
1452
|
+
value: t.url,
|
|
1453
|
+
})),
|
|
1454
|
+
{ label: "↻ Refresh list", value: REFRESH },
|
|
1455
|
+
{ label: "Enter URL manually…", value: MANUAL },
|
|
1456
|
+
], onSelect: handleAzureTargetSelect, limit: 8, indicatorComponent: () => null, itemComponent: selectItem }) }));
|
|
1457
|
+
})(), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-destination" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Metrics Export Destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Select the backend to send Prometheus metrics to so required auth fields can be collected." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: remoteWriteDestinations, onSelect: handleRemoteWriteDestinationSelect, indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-azure-auth" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Monitor Authentication" }), _jsx(Text, { color: "gray", dimColor: true, children: "Azure Monitor managed Prometheus requires Azure AD authentication." }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Cloud: ", remoteWriteAzureCloud] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: AZURE_REMOTE_WRITE_AUTH, onSelect: handleAzureRemoteWriteAuthSelect, indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-generic-auth" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Generic Remote Write Authentication" }), _jsx(Text, { color: "gray", dimColor: true, children: "Choose the auth method required by the remote_write endpoint." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: GENERIC_REMOTE_WRITE_AUTH, onSelect: handleGenericRemoteWriteAuthSelect, indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-azure-identity-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading managed identities..." }) })), subStep === "monitoring-remote-write-client-id" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Identity" }), _jsx(Text, { color: "gray", dimColor: true, children: "Select the managed/workload identity for remote_write, or enter a client ID manually." }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: [
|
|
1458
|
+
...rwIdentities.map((i) => ({
|
|
1459
|
+
label: `${i.name} (${i.clientId})`,
|
|
1460
|
+
value: i.clientId,
|
|
1461
|
+
})),
|
|
1462
|
+
{ label: "Enter manually…", value: MANUAL },
|
|
1463
|
+
], onSelect: handleRemoteWriteClientIdSelect, limit: 8, initialIndex: Math.max(0, findClusterSetupDefaultIndex(rwIdentities.map((i) => i.name), "metrics-identity", { provider: "azure", clusterName: state.clusterName })), indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-client-id-manual" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Client ID" }), _jsx(Text, { color: "gray", dimColor: true, children: "Use the managed identity, workload identity, or app registration client ID." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteClientId, onChange: setRemoteWriteClientId, onSubmit: handleRemoteWriteClientIdSubmit, placeholder: "00000000-0000-0000-0000-000000000000" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-tenant-id" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Tenant ID" }), _jsx(Text, { color: "gray", dimColor: true, children: rwTenantAutoDetected
|
|
1464
|
+
? "Auto-detected from your Azure CLI session - edit if needed."
|
|
1465
|
+
: "Required for workload identity and OAuth client-secret auth." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteTenantId, onChange: setRemoteWriteTenantId, onSubmit: handleRemoteWriteTenantIdSubmit, placeholder: "00000000-0000-0000-0000-000000000000" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-secret-ref" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Client Secret Reference" }), _jsx(Text, { color: "gray", dimColor: true, children: "Existing Kubernetes Secret key in the format name:key." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteSecretRef, onChange: setRemoteWriteSecretRef, onSubmit: handleRemoteWriteSecretRefSubmit, placeholder: "azure-monitor-oauth:client-secret" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-username-secret-ref" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Basic Auth Username Reference" }), _jsx(Text, { color: "gray", dimColor: true, children: "Existing Kubernetes Secret key in the format name:key. For Grafana Cloud, this is the instance ID." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteUsernameSecretRef, onChange: setRemoteWriteUsernameSecretRef, onSubmit: handleRemoteWriteUsernameSecretRefSubmit, placeholder: "prometheus-remote-write:username" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-password-secret-ref" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Basic Auth Password Reference" }), _jsx(Text, { color: "gray", dimColor: true, children: "Existing Kubernetes Secret key in the format name:key. For Grafana Cloud, this is an API token." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWritePasswordSecretRef, onChange: setRemoteWritePasswordSecretRef, onSubmit: handleRemoteWritePasswordSecretRefSubmit, placeholder: "prometheus-remote-write:password" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-bearer-secret-ref" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Bearer Token Reference" }), _jsx(Text, { color: "gray", dimColor: true, children: "Existing Kubernetes Secret key in the format name:key." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteBearerSecretRef, onChange: setRemoteWriteBearerSecretRef, onSubmit: handleRemoteWriteBearerSecretRefSubmit, placeholder: "prometheus-remote-write:token" }) }), _jsx(ProgressSummary, {})] })), subStep === "logging-sink" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Additional Log Forwarding" }), _jsx(Text, { color: "gray", dimColor: true, children: "Optional: forward a copy of logs to a third-party logging platform. Decision logs are always archived to your object storage (configured in the Object Storage step); this is an additional destination." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: LOGGING_PLATFORM_SINKS, onSelect: handleLoggingSinkSelect, indicatorComponent: () => null, itemComponent: selectItem }) }), _jsx(ProgressSummary, {})] })), 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: selectItem })] }), _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
1466
|
? "→ 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" }) })] }));
|
|
1467
|
+
: "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] })] })] })] })), subStep === "tracing-destination" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Where the in-cluster OpenTelemetry Collector exports traces. Works on AWS and Azure." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
1468
|
+
{ label: "Elastic APM (Elastic Cloud / self-hosted)", value: "elastic" },
|
|
1469
|
+
{ label: "Generic OTLP/HTTP (Tempo, Honeycomb, Jaeger, ...)", value: "otlp" },
|
|
1470
|
+
{ label: "Azure Monitor / Application Insights", value: "azure-monitor" },
|
|
1471
|
+
], initialIndex: Math.max(0, ["elastic", "otlp", "azure-monitor"].indexOf(tracingDestination)), onSelect: handleTracingDestinationSelect }) })] })), subStep === "tracing-otlp-endpoint" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - OTLP/HTTP endpoint" }), _jsx(Text, { color: "gray", dimColor: true, children: "Full OTLP/HTTP traces endpoint of your backend (e.g. a Grafana Cloud OTLP gateway or Honeycomb)." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tracingOtlpEndpoint, onChange: setTracingOtlpEndpoint, onSubmit: handleTracingOtlpEndpointSubmit, placeholder: "https://otlp-gateway.example.com/otlp" }) })] })), subStep === "tracing-otlp-auth" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - OTLP authentication" }), _jsx(Text, { color: "gray", dimColor: true, children: "How the collector authenticates to the OTLP endpoint. (For a custom header name, configure tracing in your config file.)" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
1472
|
+
{ label: "None", value: "none" },
|
|
1473
|
+
{ label: "Bearer token (Authorization: Bearer)", value: "bearer" },
|
|
1474
|
+
{ label: "API key (Authorization: ApiKey)", value: "api-key" },
|
|
1475
|
+
], initialIndex: Math.max(0, ["none", "bearer", "api-key"].indexOf(tracingOtlpAuthMode)), onSelect: handleTracingOtlpAuthSelect }) })] })), subStep === "tracing-otlp-cred" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - OTLP credential" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Sent as Authorization:", " ", tracingOtlpAuthMode === "api-key" ? "ApiKey" : "Bearer", " <value>."] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tracingOtlpToken, onChange: setTracingOtlpToken, onSubmit: handleTracingOtlpCredSubmit, placeholder: "otlp-credential", mask: "*" }) })] })), subStep === "tracing-azure-connection" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - Azure Monitor connection string" }), _jsx(Text, { color: "gray", dimColor: true, children: "Application Insights connection string (carries the ingestion endpoint + instrumentation key)." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tracingAzureConnectionString, onChange: setTracingAzureConnectionString, onSubmit: handleTracingAzureConnectionSubmit, placeholder: "InstrumentationKey=...;IngestionEndpoint=https://...", mask: "*" }) })] })), subStep === "tracing-endpoint" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - Elastic APM endpoint" }), _jsx(Text, { color: "gray", dimColor: true, children: "OTLP endpoint of your (customer-managed) Elastic APM. The in-cluster OpenTelemetry Collector forwards traces here." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tracingEndpoint, onChange: setTracingEndpoint, onSubmit: handleTracingEndpointSubmit, placeholder: "https://<deployment>.apm.<region>.cloud.es.io:443" }) })] })), subStep === "tracing-token" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Distributed Tracing - Elastic APM secret token" }), _jsx(Text, { color: "gray", dimColor: true, children: "Sent as Authorization: Bearer <token> to Elastic APM. (For API key auth, configure tracing in your config file instead.)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: tracingToken, onChange: setTracingToken, onSubmit: handleTracingTokenSubmit, placeholder: "elastic-apm-secret-token", mask: "*" }) })] })), subStep === "applogs-endpoint" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Application Log Shipping - BYO Elasticsearch endpoint" }), _jsx(Text, { color: "gray", dimColor: true, children: "Optional BYO sink via Vector. For AWS/Azure native log collection, enable the provider's cluster logging agent instead. Decision logs stay in object storage." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: appLogsEndpoint, onChange: setAppLogsEndpoint, onSubmit: handleAppLogsEndpointSubmit, placeholder: "https://<host>.es.<region>.cloud.es.io:9243" }) })] })), subStep === "applogs-user" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Application Log Shipping - Elasticsearch username" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: appLogsUser, onChange: setAppLogsUser, onSubmit: handleAppLogsUserSubmit, placeholder: "elastic" }) })] })), subStep === "applogs-pass" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Application Log Shipping - Elasticsearch password" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: appLogsPass, onChange: setAppLogsPass, onSubmit: handleAppLogsPassSubmit, placeholder: "password", mask: "*" }) })] })), subStep === "applogs-index" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Application Log Shipping - index name" }), _jsx(Text, { color: "gray", dimColor: true, children: "Elasticsearch index (data stream) for app logs." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: appLogsIndex, onChange: setAppLogsIndex, onSubmit: handleAppLogsIndexSubmit, placeholder: "rulebricks-app-logs" }) })] })), subStep === "valkey-admin-username" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Valkey Admin BasicAuth Username" }), _jsxs(Text, { color: "gray", dimColor: true, children: ["This username protects https://valkey.", state.domain, "."] }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: valkeyAdminUsername, onChange: setValkeyAdminUsername, onSubmit: handleValkeyAdminUsernameSubmit, placeholder: "admin" }) })] })), subStep === "valkey-admin-password" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Valkey Admin BasicAuth Password" }), _jsx(Text, { color: "gray", dimColor: true, children: "Password for accessing the Valkey Admin console. Leave empty to generate a secure value. The CLI stores only an htpasswd bcrypt hash in generated Helm values." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: valkeyAdminPassword, onChange: setValkeyAdminPassword, onSubmit: handleValkeyAdminPasswordSubmit, placeholder: "Leave empty to generate a secure value", mask: "*" }) })] })), subStep === "valkey-admin-allowed-ips" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Valkey Admin Allowed IPs" }), _jsx(Text, { color: "gray", dimColor: true, children: "Optional comma-separated CIDR allowlist. Leave blank to allow any IP that can reach Traefik." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: valkeyAdminAllowedIPs, onChange: setValkeyAdminAllowedIPs, onSubmit: handleValkeyAdminAllowedIPsSubmit, placeholder: "203.0.113.0/24, 198.51.100.10/32" }) })] })), 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
1476
|
}
|