@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.
Files changed (114) hide show
  1. package/README.md +75 -14
  2. package/cluster-setup/aws/README.md +123 -0
  3. package/cluster-setup/aws/check-aws-access.sh +242 -0
  4. package/cluster-setup/aws/parameters.json +13 -0
  5. package/cluster-setup/aws/rulebricks-cluster.cfn.yaml +355 -0
  6. package/cluster-setup/azure/README.md +141 -0
  7. package/cluster-setup/azure/check-aks-prereqs.sh +276 -0
  8. package/cluster-setup/azure/parameters.json +30 -0
  9. package/cluster-setup/azure/rulebricks-cluster.bicep +546 -0
  10. package/cluster-setup/gcp/README.md +189 -0
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +260 -0
  12. package/dist/commands/backup.d.ts +5 -0
  13. package/dist/commands/backup.js +104 -0
  14. package/dist/commands/deploy.d.ts +3 -1
  15. package/dist/commands/deploy.js +226 -326
  16. package/dist/commands/destroy.d.ts +1 -1
  17. package/dist/commands/destroy.js +73 -123
  18. package/dist/commands/init.d.ts +5 -1
  19. package/dist/commands/init.js +78 -47
  20. package/dist/commands/list.d.ts +1 -0
  21. package/dist/commands/list.js +74 -0
  22. package/dist/commands/open.d.ts +1 -1
  23. package/dist/commands/open.js +4 -12
  24. package/dist/commands/redeploy.d.ts +6 -0
  25. package/dist/commands/redeploy.js +310 -0
  26. package/dist/commands/restore.d.ts +5 -0
  27. package/dist/commands/restore.js +338 -0
  28. package/dist/commands/status.js +62 -49
  29. package/dist/commands/upgrade.js +74 -51
  30. package/dist/components/DNSWaitScreen.d.ts +5 -1
  31. package/dist/components/DNSWaitScreen.js +47 -41
  32. package/dist/components/Wizard/WizardContext.d.ts +174 -29
  33. package/dist/components/Wizard/WizardContext.js +896 -91
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -102
  35. package/dist/components/Wizard/steps/DomainStep.js +5 -24
  36. package/dist/components/Wizard/steps/ExternalServicesStep.d.ts +6 -0
  37. package/dist/components/Wizard/steps/ExternalServicesStep.js +645 -0
  38. package/dist/components/Wizard/steps/FeatureConfigStep.d.ts +2 -1
  39. package/dist/components/Wizard/steps/FeatureConfigStep.js +959 -248
  40. package/dist/components/Wizard/steps/FeaturesStep.js +31 -35
  41. package/dist/components/Wizard/steps/ObservabilityStep.d.ts +6 -0
  42. package/dist/components/Wizard/steps/ObservabilityStep.js +137 -0
  43. package/dist/components/Wizard/steps/ReviewStep.d.ts +2 -1
  44. package/dist/components/Wizard/steps/ReviewStep.js +56 -7
  45. package/dist/components/Wizard/steps/StorageStep.d.ts +9 -0
  46. package/dist/components/Wizard/steps/StorageStep.js +592 -0
  47. package/dist/components/Wizard/steps/SupabaseCredentialsStep.js +20 -21
  48. package/dist/components/Wizard/steps/VersionStep.js +45 -23
  49. package/dist/components/Wizard/steps/index.d.ts +3 -3
  50. package/dist/components/Wizard/steps/index.js +3 -3
  51. package/dist/components/common/CommandApproval.d.ts +12 -0
  52. package/dist/components/common/CommandApproval.js +91 -0
  53. package/dist/components/common/DeploymentPicker.d.ts +14 -0
  54. package/dist/components/common/DeploymentPicker.js +16 -0
  55. package/dist/components/common/index.d.ts +2 -0
  56. package/dist/components/common/index.js +2 -0
  57. package/dist/index.js +94 -62
  58. package/dist/lib/cloudCli.d.ts +134 -63
  59. package/dist/lib/cloudCli.js +512 -220
  60. package/dist/lib/clusterSetupDefaults.d.ts +30 -0
  61. package/dist/lib/clusterSetupDefaults.js +64 -0
  62. package/dist/lib/commandApproval.d.ts +26 -0
  63. package/dist/lib/commandApproval.js +114 -0
  64. package/dist/lib/config.d.ts +12 -10
  65. package/dist/lib/config.js +91 -33
  66. package/dist/lib/configFixtures.d.ts +5 -0
  67. package/dist/lib/configFixtures.js +513 -0
  68. package/dist/lib/deploymentHealth.d.ts +32 -0
  69. package/dist/lib/deploymentHealth.js +157 -0
  70. package/dist/lib/dns.d.ts +1 -1
  71. package/dist/lib/dns.js +19 -1
  72. package/dist/lib/dns.test.d.ts +1 -0
  73. package/dist/lib/dns.test.js +27 -0
  74. package/dist/lib/dockerHub.d.ts +12 -1
  75. package/dist/lib/dockerHub.js +18 -8
  76. package/dist/lib/helm.d.ts +4 -0
  77. package/dist/lib/helm.js +16 -0
  78. package/dist/lib/helmValues.d.ts +25 -0
  79. package/dist/lib/helmValues.js +1937 -259
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +966 -0
  82. package/dist/lib/htpasswd.d.ts +1 -0
  83. package/dist/lib/htpasswd.js +15 -0
  84. package/dist/lib/kubernetes.d.ts +126 -13
  85. package/dist/lib/kubernetes.js +624 -134
  86. package/dist/lib/secrets.d.ts +23 -0
  87. package/dist/lib/secrets.js +158 -0
  88. package/dist/lib/validateValues.d.ts +31 -0
  89. package/dist/lib/validateValues.js +253 -0
  90. package/dist/lib/versions.d.ts +82 -11
  91. package/dist/lib/versions.js +131 -31
  92. package/dist/lib/versions.test.d.ts +1 -0
  93. package/dist/lib/versions.test.js +81 -0
  94. package/dist/lib/wizardSteps.d.ts +14 -0
  95. package/dist/lib/wizardSteps.js +23 -0
  96. package/dist/lib/workloadIdentity.d.ts +26 -0
  97. package/dist/lib/workloadIdentity.js +323 -0
  98. package/dist/lib/workloadIdentity.test.d.ts +1 -0
  99. package/dist/lib/workloadIdentity.test.js +57 -0
  100. package/dist/types/index.d.ts +2152 -95
  101. package/dist/types/index.js +554 -286
  102. package/package.json +10 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  105. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  106. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  107. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  108. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  109. package/dist/components/Wizard/steps/TierStep.js +0 -29
  110. package/dist/lib/terraform.d.ts +0 -66
  111. package/dist/lib/terraform.js +0 -754
  112. package/terraform/aws/main.tf +0 -355
  113. package/terraform/azure/main.tf +0 -371
  114. 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, useInput } from "ink";
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 { LOGGING_SINK_INFO, CLOUD_REGIONS, DEFAULT_EMAIL_SUBJECTS, } from "../../../types/index.js";
10
- import { listBucketsInRegion, listRegions } from "../../../lib/cloudCli.js";
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
- const LOGGING_CATEGORIES = [
20
- { label: "Cloud Storage (S3, Azure Blob, GCS)", value: "cloud-storage" },
21
- {
22
- label: "Logging Platform (Datadog, Splunk, etc.)",
23
- value: "logging-platform",
24
- },
25
- ];
26
- const CLOUD_STORAGE_SINKS = [
27
- { label: "AWS S3", value: "s3" },
28
- { label: "Azure Blob Storage", value: "azure-blob" },
29
- { label: "Google Cloud Storage", value: "gcs" },
30
- ];
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
- function BucketSelector({ loggingSink, loggingRegion, availableBuckets, isRefreshing, onSelect, onRefresh, colors, }) {
48
- useInput((input) => {
49
- if (input.toLowerCase() === "r") {
50
- onRefresh();
51
- }
52
- });
53
- const bucketItems = availableBuckets.map((b) => ({ label: b, value: b }));
54
- const hasBuckets = availableBuckets.length > 0;
55
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [loggingSink === "s3" && "Select S3 Bucket", loggingSink === "azure-blob" && "Select Azure Storage Account", loggingSink === "gcs" && "Select GCS Bucket"] }), _jsxs(Text, { color: "gray", dimColor: true, children: ["Select an existing bucket in ", loggingRegion] }), isRefreshing ? (_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Refreshing bucket list..." }) })) : hasBuckets ? (_jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: bucketItems, onSelect: onSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["No buckets found in ", loggingRegion, "."] }), _jsx(Text, { color: "gray", dimColor: true, children: "Create a bucket in your cloud console, then press R to refresh." })] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Sink: ", LOGGING_SINK_INFO[loggingSink]?.name] })] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" Region: ", loggingRegion] })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "R to refresh list \u2022 \u2191/\u2193 to navigate \u2022 Enter to select" }) })] }));
56
- }
57
- const YES_NO_OPTIONS = [
58
- { label: "No, just collect metrics locally", value: false },
59
- { label: "Yes, send metrics to external system", value: true },
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
- export function FeatureConfigStep({ onComplete, onBack, }) {
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
- const needsAI = state.aiEnabled && !state.openaiApiKey;
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.monitoringEnabled;
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: AI -> SSO -> Monitoring -> Logging -> Custom Emails
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-ask";
87
+ return "monitoring-remote-write-destination";
78
88
  if (needsLogging)
79
- return "logging-category";
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
- const [subStep, setSubStep] = useState(getInitialStep);
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
- // Dynamic bucket/region lists
111
- const [availableBuckets, setAvailableBuckets] = useState([]);
112
- const [availableRegions, setAvailableRegions] = useState([]);
113
- const [isRefreshing, setIsRefreshing] = useState(false);
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
- useInput((input, key) => {
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-ask":
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
- setSubStep("monitoring-remote-write-ask");
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-category":
343
+ case "logging-sink":
174
344
  if (needsMonitoring)
175
- setSubStep("monitoring-remote-write-ask");
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 (needsLogging)
206
- setSubStep("logging-category");
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-ask");
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
- onComplete();
524
+ goToMonitoringOrAfter();
252
525
  break;
253
526
  case "sso-client-secret":
254
- if (needsMonitoring)
255
- setSubStep("monitoring-remote-write-ask");
256
- else if (needsLogging)
257
- setSubStep("logging-category");
258
- else if (needsCustomEmails)
259
- setSubStep("email-subject-invite");
260
- else
261
- onComplete();
527
+ goToMonitoringOrAfter();
262
528
  break;
263
- case "monitoring-remote-write-ask":
529
+ case "monitoring-remote-write-destination":
264
530
  case "monitoring-remote-write-url":
265
- if (needsLogging)
266
- setSubStep("logging-category");
267
- else if (needsCustomEmails)
268
- setSubStep("email-subject-invite");
269
- else
270
- onComplete();
271
- break;
272
- case "logging-bucket":
273
- // Cloud storage config complete, check for custom emails
274
- if (needsCustomEmails)
275
- setSubStep("email-subject-invite");
276
- else
277
- onComplete();
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
- // Platform config complete, check for custom emails
286
- if (needsCustomEmails)
287
- setSubStep("email-subject-invite");
288
- else
289
- onComplete();
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 handleRemoteWriteAsk = (item) => {
365
- if (item.value) {
366
- setSubStep("monitoring-remote-write-url");
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
- dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: "" });
370
- advanceToNext("monitoring-remote-write-ask");
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
- try {
376
- new URL(remoteWriteUrl);
377
- }
378
- catch {
379
- setError("Invalid URL format");
380
- return;
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
- advanceToNext("monitoring-remote-write-url");
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
- // === Logging Configuration ===
388
- // Map logging sink to cloud provider
389
- const sinkToProvider = (sink) => {
390
- if (sink === "s3")
391
- return "aws";
392
- if (sink === "azure-blob")
393
- return "azure";
394
- if (sink === "gcs")
395
- return "gcp";
396
- return null;
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
- // Is sink a cloud storage type?
399
- const isCloudStorageSink = (sink) => {
400
- return sink === "s3" || sink === "azure-blob" || sink === "gcs";
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
- // Step 1: Select logging category
403
- const handleLoggingCategorySelect = (item) => {
404
- const category = item.value;
405
- setLoggingCategory(category);
406
- setSubStep("logging-sink");
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
- // Step 2: Select logging sink based on category
409
- const handleLoggingSinkSelect = async (item) => {
410
- const sink = item.value;
411
- setLoggingSink(sink);
412
- dispatch({ type: "SET_LOGGING_SINK", sink });
413
- if (isCloudStorageSink(sink)) {
414
- // Cloud storage: go to region selection
415
- loadRegionsForLogging(sink);
416
- }
417
- else {
418
- // Logging platform: go to platform-specific config
419
- switch (sink) {
420
- case "datadog":
421
- setSubStep("logging-datadog-config");
422
- break;
423
- case "splunk":
424
- setSubStep("logging-splunk-config");
425
- break;
426
- case "elasticsearch":
427
- setSubStep("logging-elasticsearch-config");
428
- break;
429
- case "loki":
430
- setSubStep("logging-loki-config");
431
- break;
432
- case "newrelic":
433
- setSubStep("logging-newrelic-config");
434
- break;
435
- case "axiom":
436
- setSubStep("logging-axiom-config");
437
- break;
438
- }
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
- // Load regions for cloud storage
442
- const loadRegionsForLogging = async (sink) => {
443
- const provider = sinkToProvider(sink);
444
- if (!provider) {
445
- setSubStep("logging-region");
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
- setSubStep("logging-region-loading");
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 regions = await listRegions(provider);
451
- if (regions.length > 0) {
452
- setAvailableRegions(regions);
453
- }
454
- else {
455
- setAvailableRegions(CLOUD_REGIONS[provider]);
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
- setAvailableRegions(CLOUD_REGIONS[provider]);
848
+ setRwIdentities([]);
460
849
  }
461
- setSubStep("logging-region");
850
+ setSubStep("monitoring-remote-write-client-id");
462
851
  };
463
- // After region is selected, load buckets in that region
464
- const handleLoggingRegionSelect = async (item) => {
465
- setLoggingRegion(item.value);
466
- dispatch({
467
- type: "SET_LOGGING_CONFIG",
468
- config: { loggingRegion: item.value },
469
- });
470
- // Now load buckets in this region
471
- const provider = sinkToProvider(loggingSink);
472
- if (provider) {
473
- setSubStep("logging-bucket-loading");
474
- try {
475
- const buckets = await listBucketsInRegion(provider, item.value);
476
- setAvailableBuckets(buckets);
477
- }
478
- catch {
479
- setAvailableBuckets([]);
480
- }
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
- // Refresh bucket list
485
- const refreshBuckets = async () => {
486
- if (isRefreshing)
883
+ const handleRemoteWriteClientIdSubmit = () => {
884
+ if (!remoteWriteClientId) {
885
+ setError("Client ID is required");
487
886
  return;
488
- const provider = sinkToProvider(loggingSink);
489
- if (!provider || !loggingRegion)
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
- catch {
497
- // Keep existing list on error
895
+ if (remoteWriteAuthType === "workload-identity") {
896
+ saveRemoteWriteConfig("workload-identity", {
897
+ clientId: remoteWriteClientId,
898
+ tenantId: remoteWriteTenantId,
899
+ });
900
+ return;
498
901
  }
499
- setIsRefreshing(false);
902
+ setError(null);
903
+ setSubStep("monitoring-remote-write-secret-ref");
500
904
  };
501
- // Select bucket (no create option)
502
- const handleLoggingBucketSelect = (item) => {
503
- setLoggingBucket(item.value);
504
- dispatch({
505
- type: "SET_LOGGING_CONFIG",
506
- config: { loggingBucket: item.value },
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
- advanceToNext("logging-bucket");
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
- loggingBucket: datadogApiKey, // Repurpose bucket field for API key
521
- loggingRegion: datadogSite, // Repurpose region field for site
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
- loggingBucket: splunkHecToken,
547
- loggingRegion: splunkUrl,
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 bucket field for complex config
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
- loggingBucket: JSON.stringify({
1028
+ loggingPlatformCredential: JSON.stringify({
570
1029
  url: elasticsearchUrl,
571
1030
  user: elasticsearchUser,
572
1031
  password: elasticsearchPass,
573
1032
  index: elasticsearchIndex,
574
1033
  }),
575
- loggingRegion: elasticsearchIndex,
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
- loggingBucket: lokiUrl,
597
- loggingRegion: "",
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
- loggingBucket: newrelicLicenseKey,
616
- loggingRegion: newrelicAccountId,
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
- loggingBucket: axiomApiToken,
635
- loggingRegion: axiomDataset,
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-ask" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Prometheus Remote Write" }), _jsx(Text, { color: "gray", dimColor: true, children: "Do you want to send metrics to an external monitoring system?" }), _jsx(Text, { color: "gray", dimColor: true, children: "(e.g., Datadog, Grafana Cloud, Chronosphere)" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: YES_NO_OPTIONS, onSelect: handleRemoteWriteAsk, itemComponent: ({ isSelected, label }) => (_jsx(Text, { color: isSelected ? colors.accent : undefined, children: label })) }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-url" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Remote Write URL" }), _jsx(Text, { color: "gray", dimColor: true, children: "Prometheus remote_write endpoint URL" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteUrl, onChange: setRemoteWriteUrl, onSubmit: handleRemoteWriteUrlSubmit, placeholder: "https://metrics.example.com/api/v1/write" }) }), _jsx(ProgressSummary, {})] })), subStep === "logging-category" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "External Logging Destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Choose how you want to store decision logs (Console logging is always included)" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: LOGGING_CATEGORIES, onSelect: handleLoggingCategorySelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsx(ProgressSummary, {})] })), subStep === "logging-sink" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: loggingCategory === "cloud-storage"
756
- ? "Select Cloud Storage"
757
- : "Select Logging Platform" }), _jsx(Text, { color: "gray", dimColor: true, children: loggingCategory === "cloud-storage"
758
- ? "Store logs in cloud object storage"
759
- : "Send logs to a centralized logging platform" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: loggingCategory === "cloud-storage"
760
- ? CLOUD_STORAGE_SINKS
761
- : LOGGING_PLATFORM_SINKS, onSelect: handleLoggingSinkSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.infrastructureMode === "existing" &&
762
- loggingCategory === "cloud-storage" && (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: _jsx(Text, { color: "yellow", children: "Note: Cloud storage requires IRSA/Workload Identity in your cluster." }) })), _jsx(ProgressSummary, {})] })), subStep === "logging-region-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: "Loading available regions..." }) })), subStep === "logging-region" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [loggingSink === "s3" && "Select AWS Region", loggingSink === "azure-blob" && "Select Azure Region", loggingSink === "gcs" && "Select GCP Region"] }), _jsx(Text, { color: "gray", dimColor: true, children: "Select the region where logs will be stored" }), _jsx(Box, { marginTop: 1, height: 10, flexDirection: "column", overflowY: "hidden", children: _jsx(SelectInput, { items: getLoggingRegions(), onSelect: handleLoggingRegionSelect, limit: 8, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.success, children: "\u2713" }), _jsxs(Text, { color: "gray", children: [" ", "Sink: ", LOGGING_SINK_INFO[loggingSink]?.name] })] })] })), subStep === "logging-bucket-loading" && (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(Spinner, { label: `Loading buckets in ${loggingRegion}...` }) })), subStep === "logging-bucket" && (_jsx(BucketSelector, { loggingSink: loggingSink, loggingRegion: loggingRegion, availableBuckets: availableBuckets, isRefreshing: isRefreshing, onSelect: handleLoggingBucketSelect, onRefresh: refreshBuckets, colors: colors })), subStep === "logging-datadog-config" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Datadog Configuration" }), _jsx(Text, { color: "gray", dimColor: true, children: "Configure Datadog Logs integration" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "API Key:" }), _jsx(Box, { children: _jsx(TextInput, { value: datadogApiKey, onChange: setDatadogApiKey, placeholder: "your-api-key", mask: "*" }) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Datadog Site:" }), _jsx(SelectInput, { items: DATADOG_SITES, initialIndex: DATADOG_SITES.findIndex((s) => s.value === datadogSite), onSelect: (item) => setDatadogSite(item.value), indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press Enter after selecting site to continue" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: datadogApiKey ? colors.accent : colors.muted, bold: !!datadogApiKey, children: datadogApiKey
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
  }