@rulebricks/cli 2.1.7 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +51 -16
  2. package/cluster-setup/aws/README.md +96 -47
  3. package/cluster-setup/aws/check-aws-access.sh +216 -52
  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 +103 -55
  7. package/cluster-setup/azure/check-aks-prereqs.sh +236 -56
  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 +51 -34
  11. package/cluster-setup/gcp/check-gke-prereqs.sh +222 -60
  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 -54
  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 +157 -36
  33. package/dist/components/Wizard/WizardContext.js +872 -160
  34. package/dist/components/Wizard/steps/CloudProviderStep.js +192 -107
  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 +739 -425
  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 -12
  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 +1841 -289
  80. package/dist/lib/helmValues.test.d.ts +1 -0
  81. package/dist/lib/helmValues.test.js +1012 -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 +124 -17
  85. package/dist/lib/kubernetes.js +576 -145
  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 +1860 -164
  101. package/dist/types/index.js +518 -295
  102. package/package.json +9 -4
  103. package/schema/values.schema.json +1934 -0
  104. package/cluster-setup/aws/cluster.yaml +0 -33
  105. package/cluster-setup/azure/main.bicep +0 -282
  106. package/cluster-setup/azure/main.parameters.json +0 -21
  107. package/dist/components/Wizard/steps/CredentialsStep.d.ts +0 -6
  108. package/dist/components/Wizard/steps/CredentialsStep.js +0 -22
  109. package/dist/components/Wizard/steps/DeploymentModeStep.d.ts +0 -5
  110. package/dist/components/Wizard/steps/DeploymentModeStep.js +0 -26
  111. package/dist/components/Wizard/steps/TierStep.d.ts +0 -6
  112. package/dist/components/Wizard/steps/TierStep.js +0 -29
  113. package/dist/lib/terraform.d.ts +0 -66
  114. package/dist/lib/terraform.js +0 -754
  115. package/terraform/aws/main.tf +0 -355
  116. package/terraform/azure/main.tf +0 -371
  117. 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,62 +41,113 @@ 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 MONITORING_DESTINATIONS = [
58
- { label: "Local Grafana (bundled)", value: "local-grafana" },
44
+ const REMOTE_WRITE_DESTINATIONS = [
59
45
  { label: "AWS Managed Prometheus (AMP)", value: "aws-amp" },
60
46
  { label: "Azure Monitor managed Prometheus", value: "azure-monitor" },
61
47
  { label: "Grafana Cloud", value: "grafana-cloud" },
62
48
  { label: "Generic Prometheus remote_write", value: "generic" },
63
49
  ];
64
- const REMOTE_WRITE_DESTINATIONS = MONITORING_DESTINATIONS.filter((destination) => destination.value !== "local-grafana");
65
50
  const AZURE_REMOTE_WRITE_AUTH = [
66
51
  { label: "Workload identity", value: "workload-identity" },
67
52
  { label: "Managed identity", value: "managed-identity" },
68
53
  { label: "OAuth client secret", value: "oauth" },
69
54
  ];
70
- const AZURE_LOGGING_AUTH = [
71
- { label: "Workload identity", value: "workload-identity" },
72
- { label: "Connection string Secret (fallback)", value: "secret" },
73
- ];
74
55
  const GENERIC_REMOTE_WRITE_AUTH = [
75
56
  { label: "No additional auth", value: "none" },
76
57
  { label: "Basic auth from Kubernetes Secret", value: "basic" },
77
58
  { label: "Bearer token from Kubernetes Secret", value: "bearer" },
78
59
  ];
79
- export function FeatureConfigStep({ onComplete, onBack, }) {
60
+ export function FeatureConfigStep({ onComplete, onBack, entryDirection, }) {
80
61
  const { state, dispatch } = useWizard();
81
62
  const { colors } = useTheme();
82
- // Determine what needs to be configured
83
- 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;
84
72
  const needsSSO = state.ssoEnabled;
85
- const needsMonitoring = state.monitoringEnabled;
73
+ const needsMonitoring = !state.clickStackEnabled && state.metricsExportEnabled;
86
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;
87
78
  const needsCustomEmails = state.customEmailsEnabled;
88
- // Configuration order: AI -> SSO -> Monitoring -> Logging -> Custom Emails
79
+ // Configuration order:
80
+ // AI -> SSO -> Monitoring -> Logging -> Tracing -> AppLogs -> Valkey Admin -> Custom Emails
89
81
  const getInitialStep = () => {
90
82
  if (needsAI)
91
83
  return "openai-key";
92
84
  if (needsSSO)
93
85
  return "sso-provider";
94
86
  if (needsMonitoring)
95
- return "monitoring-remote-write-ask";
87
+ return "monitoring-remote-write-destination";
96
88
  if (needsLogging)
97
- 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";
98
96
  if (needsCustomEmails)
99
97
  return "email-subject-invite";
100
98
  return "done";
101
99
  };
102
- 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);
103
151
  const [openaiKey, setOpenaiKey] = useState(state.openaiApiKey || "");
104
152
  const [ssoProvider, setSsoProvider] = useState(state.ssoProvider);
105
153
  const [ssoUrl, setSsoUrl] = useState(state.ssoUrl || "");
@@ -109,7 +157,6 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
109
157
  const [remoteWriteDestination, setRemoteWriteDestination] = useState(state.prometheusRemoteWriteDestination);
110
158
  const [remoteWriteAuthType, setRemoteWriteAuthType] = useState(state.prometheusRemoteWriteAuthType);
111
159
  const [remoteWriteAwsRegion, setRemoteWriteAwsRegion] = useState(state.prometheusRemoteWriteAwsRegion || state.region || "us-east-1");
112
- const [remoteWriteAwsRoleArn, setRemoteWriteAwsRoleArn] = useState(state.prometheusRemoteWriteAwsRoleArn || "");
113
160
  const [remoteWriteAzureCloud] = useState(state.prometheusRemoteWriteAzureCloud || "AzurePublic");
114
161
  const [remoteWriteClientId, setRemoteWriteClientId] = useState(state.prometheusRemoteWriteClientId || "");
115
162
  const [remoteWriteTenantId, setRemoteWriteTenantId] = useState(state.prometheusRemoteWriteTenantId || "");
@@ -118,16 +165,6 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
118
165
  const [remoteWritePasswordSecretRef, setRemoteWritePasswordSecretRef] = useState(state.prometheusRemoteWritePasswordSecretRef || "");
119
166
  const [remoteWriteBearerSecretRef, setRemoteWriteBearerSecretRef] = useState(state.prometheusRemoteWriteBearerTokenSecretRef || "");
120
167
  const [loggingSink, setLoggingSink] = useState(state.loggingSink);
121
- const [loggingBucket, setLoggingBucket] = useState(state.loggingBucket || "");
122
- const [loggingRegion, setLoggingRegion] = useState(state.loggingRegion || "");
123
- const [loggingCloudAuthMode, setLoggingCloudAuthMode] = useState(state.loggingCloudAuthMode || "workload-identity");
124
- const [s3RoleArn, setS3RoleArn] = useState(state.loggingAwsIamRoleArn || "");
125
- const [azureBlobContainer, setAzureBlobContainer] = useState(state.loggingAzureBlobContainer || "rulebricks-logs");
126
- const [azureBlobClientId, setAzureBlobClientId] = useState(state.loggingAzureBlobClientId || "");
127
- const [azureBlobTenantId, setAzureBlobTenantId] = useState(state.loggingAzureBlobTenantId || "");
128
- const [azureBlobConnectionStringSecretRef, setAzureBlobConnectionStringSecretRef,] = useState(state.loggingAzureBlobConnectionStringSecretRef || "");
129
- const [gcpServiceAccountEmail, setGcpServiceAccountEmail] = useState(state.loggingGcpServiceAccountEmail || "");
130
- const [loggingCategory, setLoggingCategory] = useState(null);
131
168
  const [error, setError] = useState(null);
132
169
  // Logging platform config
133
170
  const [datadogApiKey, setDatadogApiKey] = useState("");
@@ -143,10 +180,46 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
143
180
  const [newrelicAccountId, setNewrelicAccountId] = useState("");
144
181
  const [axiomApiToken, setAxiomApiToken] = useState("");
145
182
  const [axiomDataset, setAxiomDataset] = useState("rulebricks");
146
- // Dynamic bucket/region lists
147
- const [availableBuckets, setAvailableBuckets] = useState([]);
148
- const [availableRegions, setAvailableRegions] = useState([]);
149
- 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);
150
223
  // Custom email templates
151
224
  const [emailSubjectInvite, setEmailSubjectInvite] = useState(state.emailSubjects?.invite || DEFAULT_EMAIL_SUBJECTS.invite);
152
225
  const [emailSubjectConfirm, setEmailSubjectConfirm] = useState(state.emailSubjects?.confirmation || DEFAULT_EMAIL_SUBJECTS.confirmation);
@@ -162,11 +235,14 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
162
235
  !needsSSO &&
163
236
  !needsMonitoring &&
164
237
  !needsLogging &&
238
+ !needsTracing &&
239
+ !needsAppLogs &&
240
+ !needsValkeyAdmin &&
165
241
  !needsCustomEmails) {
166
242
  onComplete();
167
243
  }
168
244
  }, []);
169
- useInput((input, key) => {
245
+ useGatedInput((input, key) => {
170
246
  if (key.escape) {
171
247
  setError(null);
172
248
  handleBack();
@@ -195,7 +271,7 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
195
271
  case "sso-client-secret":
196
272
  setSubStep("sso-client-id");
197
273
  break;
198
- case "monitoring-remote-write-ask":
274
+ case "monitoring-remote-write-destination":
199
275
  if (needsSSO)
200
276
  setSubStep("sso-client-secret");
201
277
  else if (needsAI)
@@ -203,22 +279,37 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
203
279
  else
204
280
  onBack();
205
281
  break;
206
- case "monitoring-remote-write-destination":
207
- setSubStep("monitoring-remote-write-ask");
208
- break;
209
- case "monitoring-remote-write-url":
210
- setSubStep("monitoring-remote-write-destination");
211
- break;
282
+ case "monitoring-azure-target-loading":
283
+ case "monitoring-azure-target":
284
+ case "monitoring-aws-region-loading":
212
285
  case "monitoring-aws-region":
213
- setSubStep("monitoring-remote-write-url");
286
+ // Azure target discovery and AWS region both branch directly off the
287
+ // destination choice.
288
+ setSubStep("monitoring-remote-write-destination");
214
289
  break;
215
- case "monitoring-aws-role-arn":
290
+ case "monitoring-aws-workspace-loading":
291
+ case "monitoring-aws-workspace":
216
292
  setSubStep("monitoring-aws-region");
217
293
  break;
294
+ case "monitoring-remote-write-url":
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;
218
306
  case "monitoring-remote-write-azure-auth":
307
+ setSubStep("monitoring-azure-target");
308
+ break;
219
309
  case "monitoring-remote-write-generic-auth":
220
310
  setSubStep("monitoring-remote-write-url");
221
311
  break;
312
+ case "monitoring-azure-identity-loading":
222
313
  case "monitoring-remote-write-client-id":
223
314
  if (remoteWriteDestination === "azure-monitor") {
224
315
  setSubStep("monitoring-remote-write-azure-auth");
@@ -227,6 +318,9 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
227
318
  setSubStep("monitoring-remote-write-url");
228
319
  }
229
320
  break;
321
+ case "monitoring-remote-write-client-id-manual":
322
+ setSubStep("monitoring-remote-write-client-id");
323
+ break;
230
324
  case "monitoring-remote-write-tenant-id":
231
325
  setSubStep("monitoring-remote-write-client-id");
232
326
  break;
@@ -246,9 +340,9 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
246
340
  case "monitoring-remote-write-bearer-secret-ref":
247
341
  setSubStep("monitoring-remote-write-generic-auth");
248
342
  break;
249
- case "logging-category":
343
+ case "logging-sink":
250
344
  if (needsMonitoring)
251
- setSubStep("monitoring-remote-write-ask");
345
+ setSubStep("monitoring-remote-write-destination");
252
346
  else if (needsSSO)
253
347
  setSubStep("sso-client-secret");
254
348
  else if (needsAI)
@@ -256,53 +350,102 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
256
350
  else
257
351
  onBack();
258
352
  break;
259
- case "logging-sink":
260
- setSubStep("logging-category");
261
- break;
262
- case "logging-region":
263
- case "logging-region-loading":
353
+ // Logging platform config steps
354
+ case "logging-datadog-config":
355
+ case "logging-splunk-config":
356
+ case "logging-elasticsearch-config":
357
+ case "logging-loki-config":
358
+ case "logging-newrelic-config":
359
+ case "logging-axiom-config":
264
360
  setSubStep("logging-sink");
265
361
  break;
266
- case "logging-bucket":
267
- case "logging-bucket-loading":
268
- setSubStep("logging-region");
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();
269
374
  break;
270
- case "logging-s3-role-arn":
271
- setSubStep("logging-bucket");
375
+ case "tracing-endpoint":
376
+ case "tracing-otlp-endpoint":
377
+ case "tracing-azure-connection":
378
+ setSubStep("tracing-destination");
272
379
  break;
273
- case "logging-azure-container":
274
- setSubStep("logging-bucket");
380
+ case "tracing-token":
381
+ setSubStep("tracing-endpoint");
275
382
  break;
276
- case "logging-azure-auth":
277
- setSubStep("logging-azure-container");
383
+ case "tracing-otlp-auth":
384
+ setSubStep("tracing-otlp-endpoint");
278
385
  break;
279
- case "logging-azure-client-id":
280
- setSubStep("logging-azure-auth");
386
+ case "tracing-otlp-cred":
387
+ setSubStep("tracing-otlp-auth");
281
388
  break;
282
- case "logging-azure-tenant-id":
283
- setSubStep("logging-azure-client-id");
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();
284
403
  break;
285
- case "logging-azure-connection-string-secret":
286
- setSubStep("logging-azure-auth");
404
+ case "applogs-user":
405
+ setSubStep("applogs-endpoint");
287
406
  break;
288
- case "logging-gcp-service-account":
289
- setSubStep("logging-bucket");
407
+ case "applogs-pass":
408
+ setSubStep("applogs-user");
290
409
  break;
291
- // Logging platform config steps
292
- case "logging-datadog-config":
293
- case "logging-splunk-config":
294
- case "logging-elasticsearch-config":
295
- case "logging-loki-config":
296
- case "logging-newrelic-config":
297
- case "logging-axiom-config":
298
- setSubStep("logging-sink");
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");
299
435
  break;
300
436
  // Email template steps
301
437
  case "email-subject-invite":
302
- if (needsLogging)
303
- 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());
304
447
  else if (needsMonitoring)
305
- setSubStep("monitoring-remote-write-ask");
448
+ setSubStep("monitoring-remote-write-destination");
306
449
  else if (needsSSO)
307
450
  setSubStep("sso-client-secret");
308
451
  else if (needsAI)
@@ -333,35 +476,59 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
333
476
  break;
334
477
  }
335
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
+ };
336
518
  const advanceToNext = (from) => {
337
519
  switch (from) {
338
520
  case "openai-key":
339
521
  if (needsSSO)
340
522
  setSubStep("sso-provider");
341
- else if (needsMonitoring)
342
- setSubStep("monitoring-remote-write-ask");
343
- else if (needsLogging)
344
- setSubStep("logging-category");
345
- else if (needsCustomEmails)
346
- setSubStep("email-subject-invite");
347
523
  else
348
- onComplete();
524
+ goToMonitoringOrAfter();
349
525
  break;
350
526
  case "sso-client-secret":
351
- if (needsMonitoring)
352
- setSubStep("monitoring-remote-write-ask");
353
- else if (needsLogging)
354
- setSubStep("logging-category");
355
- else if (needsCustomEmails)
356
- setSubStep("email-subject-invite");
357
- else
358
- onComplete();
527
+ goToMonitoringOrAfter();
359
528
  break;
360
- case "monitoring-remote-write-ask":
361
529
  case "monitoring-remote-write-destination":
362
530
  case "monitoring-remote-write-url":
363
531
  case "monitoring-aws-region":
364
- case "monitoring-aws-role-arn":
365
532
  case "monitoring-remote-write-azure-auth":
366
533
  case "monitoring-remote-write-generic-auth":
367
534
  case "monitoring-remote-write-client-id":
@@ -370,23 +537,7 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
370
537
  case "monitoring-remote-write-username-secret-ref":
371
538
  case "monitoring-remote-write-password-secret-ref":
372
539
  case "monitoring-remote-write-bearer-secret-ref":
373
- if (needsLogging)
374
- setSubStep("logging-category");
375
- else if (needsCustomEmails)
376
- setSubStep("email-subject-invite");
377
- else
378
- onComplete();
379
- break;
380
- case "logging-bucket":
381
- case "logging-s3-role-arn":
382
- case "logging-azure-tenant-id":
383
- case "logging-azure-connection-string-secret":
384
- case "logging-gcp-service-account":
385
- // Cloud storage config complete, check for custom emails
386
- if (needsCustomEmails)
387
- setSubStep("email-subject-invite");
388
- else
389
- onComplete();
540
+ goToLoggingOrAfter();
390
541
  break;
391
542
  case "logging-datadog-config":
392
543
  case "logging-splunk-config":
@@ -394,11 +545,19 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
394
545
  case "logging-loki-config":
395
546
  case "logging-newrelic-config":
396
547
  case "logging-axiom-config":
397
- // Platform config complete, check for custom emails
398
- if (needsCustomEmails)
399
- setSubStep("email-subject-invite");
400
- else
401
- 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();
402
561
  break;
403
562
  case "email-template-change":
404
563
  // All email config complete
@@ -473,24 +632,6 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
473
632
  advanceToNext("sso-client-secret");
474
633
  };
475
634
  // === Monitoring Configuration ===
476
- const handleRemoteWriteAsk = (item) => {
477
- const destination = item.value;
478
- if (destination === "local-grafana") {
479
- setRemoteWriteDestination(null);
480
- dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: "" });
481
- dispatch({
482
- type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
483
- config: {
484
- prometheusMonitoringDestination: "local-grafana",
485
- prometheusRemoteWriteDestination: null,
486
- prometheusRemoteWriteAuthType: null,
487
- },
488
- });
489
- advanceToNext("monitoring-remote-write-ask");
490
- return;
491
- }
492
- handleRemoteWriteDestinationSelect({ value: destination });
493
- };
494
635
  const handleRemoteWriteDestinationSelect = (item) => {
495
636
  const destination = item.value;
496
637
  setRemoteWriteDestination(destination);
@@ -502,22 +643,100 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
502
643
  prometheusRemoteWriteDestination: destination,
503
644
  },
504
645
  });
505
- setSubStep("monitoring-remote-write-url");
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();
653
+ }
654
+ else {
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([]);
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("");
506
709
  };
507
710
  const handleRemoteWriteUrlSubmit = () => {
508
- if (remoteWriteUrl) {
509
- try {
510
- new URL(remoteWriteUrl);
511
- }
512
- catch {
513
- setError("Invalid URL format");
514
- return;
515
- }
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;
516
733
  }
517
734
  setError(null);
518
735
  dispatch({ type: "SET_PROMETHEUS_REMOTE_WRITE", url: remoteWriteUrl });
519
736
  if (remoteWriteDestination === "aws-amp") {
520
- setSubStep("monitoring-aws-region");
737
+ // Region was already chosen before this manual-URL fallback (it's reached
738
+ // from the workspace picker), so save directly.
739
+ saveAwsAmpConfig("");
521
740
  }
522
741
  else if (remoteWriteDestination === "azure-monitor") {
523
742
  setSubStep("monitoring-remote-write-azure-auth");
@@ -537,19 +756,25 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
537
756
  setError("Select a remote_write destination first");
538
757
  }
539
758
  };
540
- const handleAwsRemoteWriteRegionSubmit = () => {
541
- if (!remoteWriteAwsRegion) {
542
- setError("AWS region is required");
543
- return;
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);
544
765
  }
545
- setError(null);
546
- setSubStep("monitoring-aws-role-arn");
547
- };
548
- const handleAwsRemoteWriteRoleArnSubmit = () => {
549
- if (remoteWriteAwsRoleArn && !remoteWriteAwsRoleArn.startsWith("arn:")) {
550
- setError("Enter a valid IAM role ARN or leave blank");
551
- return;
766
+ catch {
767
+ setRwRegions(CLOUD_REGIONS.aws);
552
768
  }
769
+ setSubStep("monitoring-aws-region");
770
+ };
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);
776
+ };
777
+ const saveAwsAmpConfig = (roleArn) => {
553
778
  dispatch({
554
779
  type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
555
780
  config: {
@@ -557,11 +782,11 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
557
782
  prometheusMonitoringDestination: "aws-amp",
558
783
  prometheusRemoteWriteAuthType: "none",
559
784
  prometheusRemoteWriteAwsRegion: remoteWriteAwsRegion,
560
- prometheusRemoteWriteAwsRoleArn: remoteWriteAwsRoleArn,
785
+ prometheusRemoteWriteAwsRoleArn: roleArn,
561
786
  },
562
787
  });
563
788
  setError(null);
564
- advanceToNext("monitoring-aws-role-arn");
789
+ advanceToNext("monitoring-aws-region");
565
790
  };
566
791
  const saveRemoteWriteConfig = (authType, overrides = {}) => {
567
792
  if (!remoteWriteDestination || !remoteWriteUrl) {
@@ -593,8 +818,55 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
593
818
  type: "SET_PROMETHEUS_REMOTE_WRITE_CONFIG",
594
819
  config: { prometheusRemoteWriteAuthType: authType },
595
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);
826
+ return;
827
+ }
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");
834
+ try {
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);
845
+ }
846
+ }
847
+ catch {
848
+ setRwIdentities([]);
849
+ }
596
850
  setSubStep("monitoring-remote-write-client-id");
597
851
  };
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
+ };
598
870
  const handleGenericRemoteWriteAuthSelect = (item) => {
599
871
  const authType = item.value;
600
872
  setRemoteWriteAuthType(authType);
@@ -613,14 +885,7 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
613
885
  setError("Client ID is required");
614
886
  return;
615
887
  }
616
- if (remoteWriteAuthType === "managed-identity") {
617
- saveRemoteWriteConfig("managed-identity", {
618
- clientId: remoteWriteClientId,
619
- });
620
- return;
621
- }
622
- setError(null);
623
- setSubStep("monitoring-remote-write-tenant-id");
888
+ proceedAfterClientId();
624
889
  };
625
890
  const handleRemoteWriteTenantIdSubmit = () => {
626
891
  if (!remoteWriteTenantId) {
@@ -675,304 +940,318 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
675
940
  bearerTokenSecretRef: remoteWriteBearerSecretRef,
676
941
  });
677
942
  };
678
- // === Logging Configuration ===
679
- // Map logging sink to cloud provider
680
- const sinkToProvider = (sink) => {
681
- if (sink === "s3")
682
- return "aws";
683
- if (sink === "azure-blob")
684
- return "azure";
685
- if (sink === "gcs")
686
- return "gcp";
687
- return null;
688
- };
689
- // Is sink a cloud storage type?
690
- const isCloudStorageSink = (sink) => {
691
- return sink === "s3" || sink === "azure-blob" || sink === "gcs";
692
- };
693
- // Step 1: Select logging category
694
- const handleLoggingCategorySelect = (item) => {
695
- const category = item.value;
696
- setLoggingCategory(category);
697
- setSubStep("logging-sink");
698
- };
699
- // Step 2: Select logging sink based on category
700
- const handleLoggingSinkSelect = async (item) => {
943
+ // === Logging Configuration (external logging platforms) ===
944
+ const handleLoggingSinkSelect = (item) => {
701
945
  const sink = item.value;
702
946
  setLoggingSink(sink);
703
947
  dispatch({ type: "SET_LOGGING_SINK", sink });
704
- if (isCloudStorageSink(sink)) {
705
- // Cloud storage: go to region selection
706
- loadRegionsForLogging(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;
707
967
  }
708
- else {
709
- // Logging platform: go to platform-specific config
710
- switch (sink) {
711
- case "datadog":
712
- setSubStep("logging-datadog-config");
713
- break;
714
- case "splunk":
715
- setSubStep("logging-splunk-config");
716
- break;
717
- case "elasticsearch":
718
- setSubStep("logging-elasticsearch-config");
719
- break;
720
- case "loki":
721
- setSubStep("logging-loki-config");
722
- break;
723
- case "newrelic":
724
- setSubStep("logging-newrelic-config");
725
- break;
726
- case "axiom":
727
- setSubStep("logging-axiom-config");
728
- break;
729
- }
968
+ };
969
+ // === Logging Platform Config Handlers ===
970
+ const handleDatadogConfigSubmit = () => {
971
+ if (!datadogApiKey) {
972
+ setError("Datadog API key is required");
973
+ return;
730
974
  }
975
+ setError(null);
976
+ dispatch({
977
+ type: "SET_LOGGING_CONFIG",
978
+ config: {
979
+ loggingPlatformCredential: datadogApiKey, // platform API key
980
+ loggingPlatformDetail: datadogSite, // platform site
981
+ },
982
+ });
983
+ advanceToNext("logging-datadog-config");
731
984
  };
732
- // Load regions for cloud storage
733
- const loadRegionsForLogging = async (sink) => {
734
- const provider = sinkToProvider(sink);
735
- if (!provider) {
736
- setSubStep("logging-region");
985
+ const handleSplunkConfigSubmit = () => {
986
+ if (!splunkUrl) {
987
+ setError("Splunk HEC URL is required");
988
+ return;
989
+ }
990
+ if (!splunkHecToken) {
991
+ setError("Splunk HEC token is required");
737
992
  return;
738
993
  }
739
- setSubStep("logging-region-loading");
740
994
  try {
741
- const regions = await listRegions(provider);
742
- if (regions.length > 0) {
743
- setAvailableRegions(regions);
744
- }
745
- else {
746
- setAvailableRegions(CLOUD_REGIONS[provider]);
747
- }
995
+ new URL(splunkUrl);
748
996
  }
749
997
  catch {
750
- setAvailableRegions(CLOUD_REGIONS[provider]);
998
+ setError("Invalid URL format");
999
+ return;
751
1000
  }
752
- setSubStep("logging-region");
753
- };
754
- // After region is selected, load buckets in that region
755
- const handleLoggingRegionSelect = async (item) => {
756
- setLoggingRegion(item.value);
1001
+ setError(null);
757
1002
  dispatch({
758
1003
  type: "SET_LOGGING_CONFIG",
759
- config: { loggingRegion: item.value },
1004
+ config: {
1005
+ loggingPlatformCredential: splunkHecToken,
1006
+ loggingPlatformDetail: splunkUrl,
1007
+ },
760
1008
  });
761
- // Now load buckets in this region
762
- const provider = sinkToProvider(loggingSink);
763
- if (provider) {
764
- setSubStep("logging-bucket-loading");
765
- try {
766
- const buckets = await listBucketsInRegion(provider, item.value);
767
- setAvailableBuckets(buckets);
768
- }
769
- catch {
770
- setAvailableBuckets([]);
771
- }
772
- }
773
- setSubStep("logging-bucket");
1009
+ advanceToNext("logging-splunk-config");
774
1010
  };
775
- // Refresh bucket list
776
- const refreshBuckets = async () => {
777
- if (isRefreshing)
778
- return;
779
- const provider = sinkToProvider(loggingSink);
780
- if (!provider || !loggingRegion)
1011
+ const handleElasticsearchConfigSubmit = () => {
1012
+ if (!elasticsearchUrl) {
1013
+ setError("Elasticsearch URL is required");
781
1014
  return;
782
- setIsRefreshing(true);
1015
+ }
783
1016
  try {
784
- const buckets = await listBucketsInRegion(provider, loggingRegion);
785
- setAvailableBuckets(buckets);
1017
+ new URL(elasticsearchUrl);
786
1018
  }
787
1019
  catch {
788
- // Keep existing list on error
1020
+ setError("Invalid URL format");
1021
+ return;
789
1022
  }
790
- setIsRefreshing(false);
791
- };
792
- // Select bucket (no create option)
793
- const handleLoggingBucketSelect = (item) => {
794
- setLoggingBucket(item.value);
1023
+ setError(null);
1024
+ // Store the connection as JSON in the credential field for complex config.
795
1025
  dispatch({
796
1026
  type: "SET_LOGGING_CONFIG",
797
- config: { loggingBucket: item.value },
1027
+ config: {
1028
+ loggingPlatformCredential: JSON.stringify({
1029
+ url: elasticsearchUrl,
1030
+ user: elasticsearchUser,
1031
+ password: elasticsearchPass,
1032
+ index: elasticsearchIndex,
1033
+ }),
1034
+ loggingPlatformDetail: elasticsearchIndex,
1035
+ },
798
1036
  });
799
- if (loggingSink === "s3") {
800
- setSubStep("logging-s3-role-arn");
801
- return;
802
- }
803
- if (loggingSink === "azure-blob") {
804
- setSubStep("logging-azure-container");
1037
+ advanceToNext("logging-elasticsearch-config");
1038
+ };
1039
+ // === Distributed Tracing (Elastic APM) ===
1040
+ const handleTracingEndpointSubmit = () => {
1041
+ if (!tracingEndpoint) {
1042
+ setError("Elastic APM OTLP endpoint is required");
805
1043
  return;
806
1044
  }
807
- if (loggingSink === "gcs") {
808
- setSubStep("logging-gcp-service-account");
809
- return;
1045
+ try {
1046
+ new URL(tracingEndpoint);
810
1047
  }
811
- advanceToNext("logging-bucket");
812
- };
813
- const handleS3RoleArnSubmit = () => {
814
- if (!s3RoleArn.startsWith("arn:")) {
815
- setError("IAM role ARN is required for S3 IRSA");
1048
+ catch {
1049
+ setError("Invalid URL format");
816
1050
  return;
817
1051
  }
818
1052
  setError(null);
819
1053
  dispatch({
820
- type: "SET_LOGGING_CONFIG",
1054
+ type: "SET_TRACING_CONFIG",
821
1055
  config: {
822
- loggingCloudAuthMode: "workload-identity",
823
- loggingAwsIamRoleArn: s3RoleArn,
1056
+ tracingElasticEndpoint: tracingEndpoint,
1057
+ tracingElasticAuthMode: "secret-token",
824
1058
  },
825
1059
  });
826
- advanceToNext("logging-s3-role-arn");
1060
+ setSubStep("tracing-token");
827
1061
  };
828
- const handleAzureBlobContainerSubmit = () => {
829
- if (!azureBlobContainer) {
830
- setError("Azure Blob container name is required");
1062
+ const handleTracingTokenSubmit = () => {
1063
+ if (!tracingToken) {
1064
+ setError("Elastic APM secret token is required");
831
1065
  return;
832
1066
  }
833
1067
  setError(null);
834
1068
  dispatch({
835
- type: "SET_LOGGING_CONFIG",
836
- config: { loggingAzureBlobContainer: azureBlobContainer },
1069
+ type: "SET_TRACING_CONFIG",
1070
+ config: { tracingElasticSecretToken: tracingToken },
837
1071
  });
838
- setSubStep("logging-azure-auth");
1072
+ advanceToNext("tracing-token");
839
1073
  };
840
- const handleAzureBlobAuthSelect = (item) => {
841
- const authMode = item.value;
842
- setLoggingCloudAuthMode(authMode);
1074
+ const handleTracingDestinationSelect = (item) => {
1075
+ const destination = item.value;
1076
+ setTracingDestination(destination);
843
1077
  dispatch({
844
- type: "SET_LOGGING_CONFIG",
845
- config: { loggingCloudAuthMode: authMode },
1078
+ type: "SET_TRACING_CONFIG",
1079
+ config: { tracingDestination: destination },
846
1080
  });
847
- setSubStep(authMode === "workload-identity"
848
- ? "logging-azure-client-id"
849
- : "logging-azure-connection-string-secret");
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");
850
1088
  };
851
- const handleAzureBlobClientIdSubmit = () => {
852
- if (!azureBlobClientId) {
853
- setError("Managed identity client ID is required");
1089
+ const handleTracingOtlpEndpointSubmit = () => {
1090
+ if (!tracingOtlpEndpoint) {
1091
+ setError("OTLP endpoint is required");
854
1092
  return;
855
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
+ });
856
1115
  setError(null);
857
- setSubStep("logging-azure-tenant-id");
1116
+ if (mode === "none") {
1117
+ // No credential needed; the section is complete.
1118
+ goToAppLogsOrAfter();
1119
+ }
1120
+ else {
1121
+ setSubStep("tracing-otlp-cred");
1122
+ }
858
1123
  };
859
- const handleAzureBlobTenantIdSubmit = () => {
860
- if (!azureBlobTenantId) {
861
- setError("Azure tenant ID is required");
1124
+ const handleTracingOtlpCredSubmit = () => {
1125
+ if (!tracingOtlpToken) {
1126
+ setError("A credential is required for the selected OTLP auth mode");
862
1127
  return;
863
1128
  }
864
1129
  setError(null);
865
1130
  dispatch({
866
- type: "SET_LOGGING_CONFIG",
867
- config: {
868
- loggingCloudAuthMode: "workload-identity",
869
- loggingAzureBlobContainer: azureBlobContainer,
870
- loggingAzureBlobClientId: azureBlobClientId,
871
- loggingAzureBlobTenantId: azureBlobTenantId,
872
- },
1131
+ type: "SET_TRACING_CONFIG",
1132
+ config: { tracingOtlpToken },
873
1133
  });
874
- advanceToNext("logging-azure-tenant-id");
1134
+ advanceToNext("tracing-otlp-cred");
875
1135
  };
876
- const handleAzureBlobConnectionStringSecretSubmit = () => {
877
- if (!azureBlobConnectionStringSecretRef.includes(":")) {
878
- setError("Use secret-name:key format");
1136
+ const handleTracingAzureConnectionSubmit = () => {
1137
+ if (!tracingAzureConnectionString) {
1138
+ setError("Azure Monitor connection string is required");
879
1139
  return;
880
1140
  }
881
1141
  setError(null);
882
1142
  dispatch({
883
- type: "SET_LOGGING_CONFIG",
884
- config: {
885
- loggingAzureBlobContainer: azureBlobContainer,
886
- loggingCloudAuthMode: "secret",
887
- loggingAzureBlobConnectionStringSecretRef: azureBlobConnectionStringSecretRef,
888
- },
1143
+ type: "SET_TRACING_CONFIG",
1144
+ config: { tracingAzureConnectionString },
889
1145
  });
890
- advanceToNext("logging-azure-connection-string-secret");
1146
+ advanceToNext("tracing-azure-connection");
891
1147
  };
892
- const handleGcpServiceAccountSubmit = () => {
893
- if (!gcpServiceAccountEmail.includes("@")) {
894
- setError("Google service account email is required");
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");
895
1159
  return;
896
1160
  }
897
1161
  setError(null);
898
1162
  dispatch({
899
- type: "SET_LOGGING_CONFIG",
1163
+ type: "SET_APP_LOGS_CONFIG",
900
1164
  config: {
901
- loggingCloudAuthMode: "workload-identity",
902
- loggingGcpServiceAccountEmail: gcpServiceAccountEmail,
1165
+ appLogsElasticEndpoint: appLogsEndpoint,
1166
+ appLogsElasticAuthMode: "basic",
903
1167
  },
904
1168
  });
905
- advanceToNext("logging-gcp-service-account");
1169
+ setSubStep("applogs-user");
906
1170
  };
907
- // === Logging Platform Config Handlers ===
908
- const handleDatadogConfigSubmit = () => {
909
- if (!datadogApiKey) {
910
- setError("Datadog API key is required");
1171
+ const handleAppLogsUserSubmit = () => {
1172
+ if (!appLogsUser) {
1173
+ setError("Elasticsearch username is required");
911
1174
  return;
912
1175
  }
913
1176
  setError(null);
914
1177
  dispatch({
915
- type: "SET_LOGGING_CONFIG",
916
- config: {
917
- loggingBucket: datadogApiKey, // Repurpose bucket field for API key
918
- loggingRegion: datadogSite, // Repurpose region field for site
919
- },
1178
+ type: "SET_APP_LOGS_CONFIG",
1179
+ config: { appLogsElasticUsername: appLogsUser },
920
1180
  });
921
- advanceToNext("logging-datadog-config");
1181
+ setSubStep("applogs-pass");
922
1182
  };
923
- const handleSplunkConfigSubmit = () => {
924
- if (!splunkUrl) {
925
- setError("Splunk HEC URL is required");
1183
+ const handleAppLogsPassSubmit = () => {
1184
+ if (!appLogsPass) {
1185
+ setError("Elasticsearch password is required");
926
1186
  return;
927
1187
  }
928
- if (!splunkHecToken) {
929
- setError("Splunk HEC token is required");
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");
930
1209
  return;
931
1210
  }
932
- try {
933
- new URL(splunkUrl);
934
- }
935
- catch {
936
- setError("Invalid URL format");
1211
+ if (username.includes(":")) {
1212
+ setError("Username cannot contain ':'");
937
1213
  return;
938
1214
  }
1215
+ setValkeyAdminUsername(username);
939
1216
  setError(null);
940
- dispatch({
941
- type: "SET_LOGGING_CONFIG",
942
- config: {
943
- loggingBucket: splunkHecToken,
944
- loggingRegion: splunkUrl,
945
- },
946
- });
947
- advanceToNext("logging-splunk-config");
1217
+ setSubStep("valkey-admin-password");
948
1218
  };
949
- const handleElasticsearchConfigSubmit = () => {
950
- if (!elasticsearchUrl) {
951
- setError("Elasticsearch URL is required");
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");
952
1224
  return;
953
1225
  }
954
1226
  try {
955
- new URL(elasticsearchUrl);
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
+ });
956
1236
  }
957
- catch {
958
- setError("Invalid URL format");
1237
+ catch (err) {
1238
+ setError(err instanceof Error ? err.message : "Unable to hash password");
959
1239
  return;
960
1240
  }
961
1241
  setError(null);
962
- // Store as JSON in bucket field for complex config
1242
+ setSubStep("valkey-admin-allowed-ips");
1243
+ };
1244
+ const handleValkeyAdminAllowedIPsSubmit = () => {
1245
+ const allowedIPs = valkeyAdminAllowedIPs
1246
+ .split(",")
1247
+ .map((value) => value.trim())
1248
+ .filter(Boolean);
963
1249
  dispatch({
964
- type: "SET_LOGGING_CONFIG",
965
- config: {
966
- loggingBucket: JSON.stringify({
967
- url: elasticsearchUrl,
968
- user: elasticsearchUser,
969
- password: elasticsearchPass,
970
- index: elasticsearchIndex,
971
- }),
972
- loggingRegion: elasticsearchIndex,
973
- },
1250
+ type: "SET_EXTERNAL_SERVICES",
1251
+ config: { valkeyAdminAllowedIPs: allowedIPs },
974
1252
  });
975
- advanceToNext("logging-elasticsearch-config");
1253
+ setError(null);
1254
+ advanceToNext("valkey-admin-allowed-ips");
976
1255
  };
977
1256
  const handleLokiConfigSubmit = () => {
978
1257
  if (!lokiUrl) {
@@ -990,8 +1269,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
990
1269
  dispatch({
991
1270
  type: "SET_LOGGING_CONFIG",
992
1271
  config: {
993
- loggingBucket: lokiUrl,
994
- loggingRegion: "",
1272
+ loggingPlatformCredential: lokiUrl,
1273
+ loggingPlatformDetail: "",
995
1274
  },
996
1275
  });
997
1276
  advanceToNext("logging-loki-config");
@@ -1009,8 +1288,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
1009
1288
  dispatch({
1010
1289
  type: "SET_LOGGING_CONFIG",
1011
1290
  config: {
1012
- loggingBucket: newrelicLicenseKey,
1013
- loggingRegion: newrelicAccountId,
1291
+ loggingPlatformCredential: newrelicLicenseKey,
1292
+ loggingPlatformDetail: newrelicAccountId,
1014
1293
  },
1015
1294
  });
1016
1295
  advanceToNext("logging-newrelic-config");
@@ -1028,8 +1307,8 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
1028
1307
  dispatch({
1029
1308
  type: "SET_LOGGING_CONFIG",
1030
1309
  config: {
1031
- loggingBucket: axiomApiToken,
1032
- loggingRegion: axiomDataset,
1310
+ loggingPlatformCredential: axiomApiToken,
1311
+ loggingPlatformDetail: axiomDataset,
1033
1312
  },
1034
1313
  });
1035
1314
  advanceToNext("logging-axiom-config");
@@ -1122,41 +1401,76 @@ export function FeatureConfigStep({ onComplete, onBack, }) {
1122
1401
  });
1123
1402
  advanceToNext("email-template-change");
1124
1403
  };
1125
- // Build bucket items for selection (no create option - just existing buckets)
1126
- const getBucketItems = () => {
1127
- return availableBuckets.map((b) => ({ label: b, value: b }));
1128
- };
1129
- // Get regions based on logging sink (use dynamic regions if available)
1130
- const getLoggingRegions = () => {
1131
- if (availableRegions.length > 0) {
1132
- return availableRegions.map((r) => ({ label: r, value: r }));
1133
- }
1134
- const provider = sinkToProvider(loggingSink);
1135
- if (!provider)
1136
- return [];
1137
- return CLOUD_REGIONS[provider].map((r) => ({ label: r, value: r }));
1138
- };
1139
1404
  // If nothing to configure, don't render
1140
1405
  if (!needsAI &&
1141
1406
  !needsSSO &&
1142
1407
  !needsMonitoring &&
1143
1408
  !needsLogging &&
1409
+ !needsTracing &&
1410
+ !needsAppLogs &&
1411
+ !needsValkeyAdmin &&
1144
1412
  !needsCustomEmails) {
1145
1413
  return null;
1146
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] }));
1147
1417
  // Progress summary
1148
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" })] }))] }));
1149
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" &&
1150
1420
  "e.g., https://login.microsoftonline.com/your-tenant-id", ssoProvider === "okta" && "e.g., https://your-org.okta.com", ssoProvider === "keycloak" &&
1151
1421
  "e.g., https://keycloak.example.com/realms/your-realm", ssoProvider === "ory" &&
1152
- "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: "Monitoring Destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Choose where Prometheus metrics should be viewed or sent." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: MONITORING_DESTINATIONS, onSelect: handleRemoteWriteAsk, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", 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" }), 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" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "AWS Managed Prometheus Region" }), _jsx(Text, { color: "gray", dimColor: true, children: "Region used for SigV4 signing." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteAwsRegion, onChange: setRemoteWriteAwsRegion, onSubmit: handleAwsRemoteWriteRegionSubmit, placeholder: "us-east-1" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-aws-role-arn" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Prometheus IRSA Role ARN" }), _jsx(Text, { color: "gray", dimColor: true, children: "Optional. If provided, the Prometheus service account will be annotated with this role." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: remoteWriteAwsRoleArn, onChange: setRemoteWriteAwsRoleArn, onSubmit: handleAwsRemoteWriteRoleArnSubmit, placeholder: "arn:aws:iam::123456789012:role/rulebricks-prometheus" }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-destination" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Remote Write Destination" }), _jsx(Text, { color: "gray", dimColor: true, children: "Select the monitoring backend so required auth fields can be collected." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: REMOTE_WRITE_DESTINATIONS, onSelect: handleRemoteWriteDestinationSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _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: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _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: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), _jsx(ProgressSummary, {})] })), subStep === "monitoring-remote-write-client-id" && (_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: "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-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"
1153
- ? "Select Cloud Storage"
1154
- : "Select Logging Platform" }), _jsx(Text, { color: "gray", dimColor: true, children: loggingCategory === "cloud-storage"
1155
- ? "Store logs in cloud object storage"
1156
- : "Send logs to a centralized logging platform" }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: loggingCategory === "cloud-storage"
1157
- ? CLOUD_STORAGE_SINKS
1158
- : LOGGING_PLATFORM_SINKS, onSelect: handleLoggingSinkSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) }), state.infrastructureMode === "existing" &&
1159
- 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-s3-role-arn" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Vector S3 IRSA Role ARN" }), _jsx(Text, { color: "gray", dimColor: true, children: "IAM role trusted by the Vector Kubernetes service account." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: s3RoleArn, onChange: setS3RoleArn, onSubmit: handleS3RoleArnSubmit, placeholder: "arn:aws:iam::123456789012:role/rulebricks-vector" }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.success, children: ["\u2713 S3 bucket: ", loggingBucket] }) })] })), subStep === "logging-azure-container" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Blob Container" }), _jsx(Text, { color: "gray", dimColor: true, children: "Enter the container where Rulebricks decision logs should be written." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: azureBlobContainer, onChange: setAzureBlobContainer, onSubmit: handleAzureBlobContainerSubmit, placeholder: "rulebricks-logs" }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Text, { color: colors.success, children: ["\u2713 Storage account: ", loggingBucket] }) })] })), subStep === "logging-azure-auth" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Blob Authentication" }), _jsx(Text, { color: "gray", dimColor: true, children: "Workload identity is recommended. Connection string Secret is a fallback for clusters without Azure Workload Identity." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: AZURE_LOGGING_AUTH, onSelect: handleAzureBlobAuthSelect, indicatorComponent: () => null, itemComponent: ({ isSelected, label }) => (_jsxs(Text, { color: isSelected ? colors.accent : undefined, children: [isSelected ? "❯ " : " ", label] })) }) })] })), subStep === "logging-azure-client-id" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Managed Identity Client ID" }), _jsx(Text, { color: "gray", dimColor: true, children: "Client ID for the identity federated to Vector's service account." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: azureBlobClientId, onChange: setAzureBlobClientId, onSubmit: handleAzureBlobClientIdSubmit, placeholder: "00000000-0000-0000-0000-000000000000" }) })] })), subStep === "logging-azure-tenant-id" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Tenant ID" }), _jsx(Text, { color: "gray", dimColor: true, children: "Tenant ID used by Azure Workload Identity." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: azureBlobTenantId, onChange: setAzureBlobTenantId, onSubmit: handleAzureBlobTenantIdSubmit, placeholder: "00000000-0000-0000-0000-000000000000" }) })] })), subStep === "logging-azure-connection-string-secret" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Azure Storage Connection String Secret" }), _jsx(Text, { color: "gray", dimColor: true, children: "Enter an existing Kubernetes Secret key in the format name:key." }), _jsx(Text, { color: "gray", dimColor: true, children: "The key should contain the Azure Storage connection string." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: azureBlobConnectionStringSecretRef, onChange: setAzureBlobConnectionStringSecretRef, onSubmit: handleAzureBlobConnectionStringSecretSubmit, placeholder: "azure-blob-logs:connection-string" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: colors.success, children: ["\u2713 Storage account: ", loggingBucket] }), _jsxs(Text, { color: colors.success, children: ["\u2713 Container: ", azureBlobContainer] })] })] })), subStep === "logging-gcp-service-account" && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: "Google Service Account Email" }), _jsx(Text, { color: "gray", dimColor: true, children: "GSA bound to the Vector Kubernetes service account through GKE Workload Identity." }), _jsx(Box, { marginTop: 1, children: _jsx(TextInput, { value: gcpServiceAccountEmail, onChange: setGcpServiceAccountEmail, onSubmit: handleGcpServiceAccountSubmit, placeholder: "rulebricks-vector@project.iam.gserviceaccount.com" }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.success, children: ["\u2713 GCS bucket: ", loggingBucket] }) })] })), 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
1160
1466
  ? "→ Press Enter to continue"
1161
- : "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" }) })] }));
1162
1476
  }