Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 15 additions & 21 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { LinuxConfigProvider } from "./providers/LinuxConfigProvider";
import { KubernetesConfigProvider } from "./providers/KubernetesConfigProvider";
import { SettingsProvider } from "./providers/SettingsProvider";
import { WizardProvider } from "./providers/WizardProvider";
import { InitialStateProvider } from "./providers/InitialStateProvider";
Expand All @@ -19,25 +17,21 @@ function App() {
<AuthProvider>
<SettingsProvider>
<InstallationProgressProvider>
<LinuxConfigProvider>
<KubernetesConfigProvider>
<div className="min-h-screen bg-gray-50 text-gray-900 font-sans">
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<WizardProvider>
<InstallWizard />
</WizardProvider>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</div>
</KubernetesConfigProvider>
</LinuxConfigProvider>
<div className="min-h-screen bg-gray-50 text-gray-900 font-sans">
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<WizardProvider>
<InstallWizard />
</WizardProvider>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</div>
</InstallationProgressProvider>
</SettingsProvider>
</AuthProvider>
Expand Down
73 changes: 64 additions & 9 deletions web/src/components/wizard/completion/KubernetesCompletionStep.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,82 @@
import React, { useState } from "react";
import Card from "../../common/Card";
import Button from "../../common/Button";
import { useKubernetesConfig } from "../../../contexts/KubernetesConfigContext";
import { useQuery } from "@tanstack/react-query";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useSettings } from "../../../contexts/SettingsContext";
import { CheckCircle, ClipboardCheck, Copy, Terminal } from "lucide-react";
import { useAuth } from "../../../contexts/AuthContext";
import { CheckCircle, ClipboardCheck, Copy, Terminal, Loader2, XCircle } from "lucide-react";
import { KubernetesConfigResponse } from "../../../types";
import { getApiBase } from '../../../utils/api-base';
import { ApiError } from '../../../utils/api-error';

const KubernetesCompletionStep: React.FC = () => {
const [copied, setCopied] = useState(false);
const { config } = useKubernetesConfig();
const { title } = useInitialState();
const { settings } = useSettings();
const themeColor = settings.themeColor;
const { title, installTarget, mode } = useInitialState();
const { settings: { themeColor } } = useSettings();
const { token } = useAuth();

const apiBase = getApiBase(installTarget, mode);
// Query for fetching install configuration
const { isLoading, error, data: config } = useQuery<KubernetesConfigResponse, Error>({
queryKey: ["installConfig"],
queryFn: async () => {
const response = await fetch(`${apiBase}/installation/config`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw await ApiError.fromResponse(response, "Failed to fetch install configuration")
}
const configResponse = await response.json() as KubernetesConfigResponse;
return configResponse;
},
});

const copyInstallCommand = () => {
if (config.installCommand) {
navigator.clipboard.writeText(config.installCommand).then(() => {
if (config?.resolved.installCommand) {
navigator.clipboard.writeText(config?.resolved.installCommand).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
}
};

// Loading state
if (isLoading) {
return (
<div className="space-y-6" data-testid="kubernetes-completion-loading">
<Card>
<div className="flex flex-col items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin mb-4" style={{ color: themeColor }} />
<p className="text-gray-600">Loading installation configuration...</p>
</div>
</Card>
</div>
);
}

// Error state
if (error) {
return (
<div className="space-y-6" data-testid="kubernetes-completion-error">
<Card>
<div className="flex flex-col items-center justify-center py-12">
<div className="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-4">
<XCircle className="w-6 h-6 text-red-600" />
</div>
<p className="text-lg font-medium text-gray-900">Failed to load installation configuration</p>
<p className="text-sm text-red-600 mt-2">
{error instanceof ApiError ? error.details || error.message : error.message}
</p>
</div>
</Card>
</div>
);
}

// Success state
return (
<div className="space-y-6">
<Card>
Expand Down Expand Up @@ -50,7 +105,7 @@ const KubernetesCompletionStep: React.FC = () => {
<div className="flex items-start space-x-2 p-2 bg-white rounded border border-gray-300">
<Terminal className="w-4 h-4 text-gray-400 mt-0.5 shrink-0" />
<code className="font-mono text-sm text-left">
{config.installCommand}
{config?.resolved.installCommand}
</code>
</div>
</div>
Expand Down
66 changes: 61 additions & 5 deletions web/src/components/wizard/completion/LinuxCompletionStep.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
import React from "react";
import Card from "../../common/Card";
import Button from "../../common/Button";
import { useLinuxConfig } from "../../../contexts/LinuxConfigContext";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useSettings } from "../../../contexts/SettingsContext";
import { CheckCircle, ExternalLink } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "../../../contexts/AuthContext";
import { CheckCircle, ExternalLink, Loader2, XCircle } from "lucide-react";
import { getApiBase } from '../../../utils/api-base';
import { LinuxConfigResponse } from "../../../types";
import { ApiError } from '../../../utils/api-error';

const LinuxCompletionStep: React.FC = () => {
const { config } = useLinuxConfig();
const { title } = useInitialState();
const { title, installTarget, mode } = useInitialState();
const { settings } = useSettings();
const themeColor = settings.themeColor;

const { token } = useAuth();
const apiBase = getApiBase(installTarget, mode);
// Query for fetching install configuration
const { isLoading, error, data: config } = useQuery<LinuxConfigResponse, Error>({
queryKey: ["installConfig"],
queryFn: async () => {
const response = await fetch(`${apiBase}/installation/config`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw await ApiError.fromResponse(response, "Failed to fetch install configuration")
}
const configResponse = await response.json() as LinuxConfigResponse;
return configResponse;
},
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: React Query Key Collision Across Install Types

Multiple completion and setup components (e.g., LinuxCompletionStep, KubernetesCompletionStep) use the same React Query key ["installConfig"]. This causes cache collisions because their API endpoints vary by installTarget and mode, leading to incorrect configuration data being displayed when navigating between different installation types.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be an issue for now, it's not expected to run different installation types in parallel.


// Loading state
if (isLoading) {
return (
<div className="space-y-6" data-testid="linux-completion-loading">
<Card>
<div className="flex flex-col items-center justify-center py-12">
<Loader2 className="w-8 h-8 animate-spin mb-4" style={{ color: themeColor }} />
<p className="text-gray-600">Loading installation configuration...</p>
</div>
</Card>
</div>
);
}

// Error state
if (error) {
return (
<div className="space-y-6" data-testid="linux-completion-error">
<Card>
<div className="flex flex-col items-center justify-center py-12">
<div className="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-4">
<XCircle className="w-6 h-6 text-red-600" />
</div>
<p className="text-lg font-medium text-gray-900">Failed to load installation configuration</p>
<p className="text-sm text-red-600 mt-2">
{error instanceof ApiError ? error.details || error.message : error.message}
</p>
</div>
</Card>
</div>
);
}

// Success state
return (
<div className="space-y-6">
<Card>
Expand All @@ -26,7 +82,7 @@ const LinuxCompletionStep: React.FC = () => {
<Button
className="mt-4"
dataTestId="admin-console-button"
onClick={() => window.open(`http://${window.location.hostname}:${config.adminConsolePort}`, "_blank")}
onClick={() => window.open(`http://${window.location.hostname}:${config?.resolved.adminConsolePort}`, "_blank")}
icon={<ExternalLink className="ml-2 mr-1 h-5 w-5" />}
>
Visit Admin Console
Expand Down
14 changes: 1 addition & 13 deletions web/src/components/wizard/setup/KubernetesSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import React, { useState } from "react";
import Input from "../../common/Input";
import Button from "../../common/Button";
import Card from "../../common/Card";
import { useKubernetesConfig } from "../../../contexts/KubernetesConfigContext";
import { useWizard } from "../../../contexts/WizardModeContext";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useAuth } from "../../../contexts/AuthContext";
import { formatErrorMessage } from "../../../utils/errorMessage";
import { ChevronRight, ChevronLeft } from "lucide-react";
import { KubernetesConfig } from "../../../types";
import { KubernetesConfig, KubernetesConfigResponse } from "../../../types";
import { getApiBase } from '../../../utils/api-base';
import { ApiError } from '../../../utils/api-error';

Expand Down Expand Up @@ -36,14 +35,7 @@ interface Status {
description?: string;
}

interface KubernetesConfigResponse {
values: KubernetesConfig;
defaults: KubernetesConfig;
resolved: KubernetesConfig;
}

const KubernetesSetupStep: React.FC<KubernetesSetupStepProps> = ({ onNext, onBack }) => {
const { updateConfig } = useKubernetesConfig(); // We need to make sure to update the global config
const { text, target, mode } = useWizard();
const [error, setError] = useState<string | null>(null);
const [defaults, setDefaults] = useState<KubernetesConfig>({});
Expand All @@ -64,8 +56,6 @@ const KubernetesSetupStep: React.FC<KubernetesSetupStepProps> = ({ onNext, onBac
throw await ApiError.fromResponse(response, "Failed to fetch install configuration")
}
const configResponse = await response.json();
// Update the global config with resolved config which includes user values and defaults.
updateConfig(configResponse.resolved);
// Store defaults for display in help text
setDefaults(configResponse.defaults);
// Store the config values for display in the form inputs
Expand All @@ -92,8 +82,6 @@ const KubernetesSetupStep: React.FC<KubernetesSetupStepProps> = ({ onNext, onBac
return response.json();
},
onSuccess: () => {
// Update the global (context) config we use accross the project
updateConfig(configValues);
// Clear any previous errors
setError(null);
startInstallation();
Expand Down
14 changes: 1 addition & 13 deletions web/src/components/wizard/setup/LinuxSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import Select from "../../common/Select";
import Button from "../../common/Button";
import Card from "../../common/Card";
import { useInitialState } from "../../../contexts/InitialStateContext";
import { useLinuxConfig } from "../../../contexts/LinuxConfigContext";
import { useWizard } from "../../../contexts/WizardModeContext";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useAuth } from "../../../contexts/AuthContext";
import { formatErrorMessage } from "../../../utils/errorMessage";
import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
import { LinuxConfig, State } from "../../../types";
import { LinuxConfigResponse, LinuxConfig, State } from "../../../types";
import { getApiBase } from '../../../utils/api-base';
import { ApiError } from '../../../utils/api-error';

Expand Down Expand Up @@ -45,12 +44,6 @@ interface Status {
description?: string;
}

interface LinuxConfigResponse {
values: LinuxConfig;
defaults: LinuxConfig;
resolved: LinuxConfig;
}

interface InstallationStatusResponse {
description: string;
lastUpdated: string;
Expand All @@ -62,7 +55,6 @@ interface NetworkInterfacesResponse {
}

const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
const { updateConfig } = useLinuxConfig(); // We need to make sure to update the global config
const { text, target, mode } = useWizard();
const { title } = useInitialState();
const [isInstallationStatusPolling, setIsInstallationStatusPolling] = useState(false);
Expand All @@ -86,8 +78,6 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
throw await ApiError.fromResponse(response, "Failed to fetch install configuration")
}
const configResponse = await response.json();
// Update the global config with resolved config which includes user values and defaults.
updateConfig(configResponse.resolved);
// Store defaults for display in help text
setDefaults(configResponse.defaults);
// Store the config values for display in the form inputs
Expand Down Expand Up @@ -150,8 +140,6 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext, onBack }) => {
return response.json();
},
onSuccess: () => {
// Update the global (context) config we use accross the project
updateConfig(configValues);
// Clear any previous errors
setError(null);
// Start polling installation status
Expand Down
Loading