Skip to content

Commit fcf01cc

Browse files
authored
Add Kubernetes installation and completion pages (#2403)
* Add Kubernetes installation and completion pages * fix web unit tests * f
1 parent 8d7b74e commit fcf01cc

17 files changed

+935
-141
lines changed

web/src/components/common/Button.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface ButtonProps {
1010
disabled?: boolean;
1111
className?: string;
1212
icon?: React.ReactNode;
13+
dataTestId?: string;
1314
}
1415

1516
const Button: React.FC<ButtonProps> = ({
@@ -21,6 +22,7 @@ const Button: React.FC<ButtonProps> = ({
2122
disabled = false,
2223
className = '',
2324
icon,
25+
dataTestId
2426
}) => {
2527
const { settings } = useSettings();
2628
const themeColor = settings.themeColor;
@@ -53,6 +55,7 @@ const Button: React.FC<ButtonProps> = ({
5355
backgroundColor: variant === 'primary' ? themeColor : undefined,
5456
borderColor: variant === 'outline' ? 'currentColor' : undefined,
5557
} as React.CSSProperties}
58+
data-testid={dataTestId}
5659
>
5760
{icon && <span className="mr-2">{icon}</span>}
5861
{children}

web/src/components/wizard/InstallWizard.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@ import LinuxSetupStep from "./setup/LinuxSetupStep";
55
import KubernetesSetupStep from "./setup/KubernetesSetupStep";
66
import LinuxValidationStep from "./validation/LinuxValidationStep";
77
import LinuxInstallationStep from "./installation/LinuxInstallationStep";
8+
import KubernetesInstallationStep from "./installation/KubernetesInstallationStep";
9+
import LinuxCompletionStep from "./completion/LinuxCompletionStep";
10+
import KubernetesCompletionStep from "./completion/KubernetesCompletionStep";
811
import { WizardStep } from "../../types";
912
import { AppIcon } from "../common/Logo";
1013
import { useWizard } from "../../contexts/WizardModeContext";
11-
import CompletionStep from "./CompletionStep";
1214

1315
const InstallWizard: React.FC = () => {
1416
const [currentStep, setCurrentStep] = useState<WizardStep>("welcome");
1517
const { text, target } = useWizard();
1618

1719
const getSteps = (): WizardStep[] => {
1820
if (target === "kubernetes") {
19-
return ["welcome", "kubernetes-setup", "kubernetes-installation", "completion"];
21+
return ["welcome", "kubernetes-setup", "kubernetes-installation", "kubernetes-completion"];
2022
} else {
21-
return ["welcome", "linux-setup", "linux-validation", "linux-installation", "completion"];
23+
return ["welcome", "linux-setup", "linux-validation", "linux-installation", "linux-completion"];
2224
}
2325
}
2426

@@ -50,10 +52,12 @@ const InstallWizard: React.FC = () => {
5052
return <LinuxValidationStep onNext={goToNextStep} onBack={goToPreviousStep} />;
5153
case "linux-installation":
5254
return <LinuxInstallationStep onNext={goToNextStep} />;
53-
// case "kubernetes-installation":
54-
// return <KubernetesInstallationStep onNext={goToNextStep} />;
55-
case "completion":
56-
return <CompletionStep />;
55+
case "kubernetes-installation":
56+
return <KubernetesInstallationStep onNext={goToNextStep} />;
57+
case "linux-completion":
58+
return <LinuxCompletionStep />;
59+
case "kubernetes-completion":
60+
return <KubernetesCompletionStep />;
5761
default:
5862
return null;
5963
}

web/src/components/wizard/StepNavigation.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ const StepNavigation: React.FC<StepNavigationProps> = ({ currentStep: currentSte
2727
{ id: 'welcome', name: 'Welcome', icon: ClipboardList },
2828
{ id: 'kubernetes-setup', name: 'Setup', icon: Settings },
2929
{ id: 'kubernetes-installation', name: mode === 'upgrade' ? 'Upgrade' : 'Installation', icon: Download },
30-
{ id: 'completion', name: 'Completion', icon: CheckCircle },
30+
{ id: 'kubernetes-completion', name: 'Completion', icon: CheckCircle },
3131
];
3232
} else {
3333
return [
3434
{ id: 'welcome', name: 'Welcome', icon: ClipboardList },
3535
{ id: 'linux-setup', name: 'Setup', icon: Settings },
3636
{ id: 'linux-validation', name: 'Validation', icon: Shield, hidden: true, parentId: 'linux-setup' },
3737
{ id: 'linux-installation', name: mode === 'upgrade' ? 'Upgrade' : 'Installation', icon: Download },
38-
{ id: 'completion', name: 'Completion', icon: CheckCircle },
38+
{ id: 'linux-completion', name: 'Completion', icon: CheckCircle },
3939
];
4040
}
4141
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import Card from "../../common/Card";
3+
import Button from "../../common/Button";
4+
import { useKubernetesConfig } from "../../../contexts/KubernetesConfigContext";
5+
import { useBranding } from "../../../contexts/BrandingContext";
6+
import { CheckCircle, ExternalLink } from "lucide-react";
7+
8+
const KubernetesCompletionStep: React.FC = () => {
9+
const { config } = useKubernetesConfig();
10+
const { title } = useBranding();
11+
12+
return (
13+
<div className="space-y-6">
14+
<Card>
15+
<div className="flex flex-col items-center text-center py-6">
16+
<div className="flex flex-col items-center justify-center mb-6">
17+
<div className="w-16 h-16 rounded-full flex items-center justify-center mb-4">
18+
<CheckCircle className="w-10 h-10" style={{ color: "blue" }} />
19+
</div>
20+
<p className="text-gray-600 mt-2" data-testid="completion-message">
21+
Visit the Admin Console to configure and install {title}
22+
</p>
23+
<Button
24+
className="mt-4"
25+
dataTestId="admin-console-button"
26+
onClick={() => window.open(`http://${window.location.hostname}:${config.adminConsolePort}`, "_blank")}
27+
icon={<ExternalLink className="ml-2 mr-1 h-5 w-5" />}
28+
>
29+
Visit Admin Console
30+
</Button>
31+
</div>
32+
</div>
33+
</Card>
34+
</div>
35+
);
36+
};
37+
38+
export default KubernetesCompletionStep;

web/src/components/wizard/CompletionStep.tsx renamed to web/src/components/wizard/completion/LinuxCompletionStep.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React from "react";
2-
import Card from "../common/Card";
3-
import Button from "../common/Button";
4-
import { useLinuxConfig } from "../../contexts/LinuxConfigContext";
5-
import { useBranding } from "../../contexts/BrandingContext";
2+
import Card from "../../common/Card";
3+
import Button from "../../common/Button";
4+
import { useLinuxConfig } from "../../../contexts/LinuxConfigContext";
5+
import { useBranding } from "../../../contexts/BrandingContext";
66
import { CheckCircle, ExternalLink } from "lucide-react";
77

8-
const CompletionStep: React.FC = () => {
8+
const LinuxCompletionStep: React.FC = () => {
99
const { config } = useLinuxConfig();
1010
const { title } = useBranding();
1111

@@ -17,11 +17,12 @@ const CompletionStep: React.FC = () => {
1717
<div className="w-16 h-16 rounded-full flex items-center justify-center mb-4">
1818
<CheckCircle className="w-10 h-10" style={{ color: "blue" }} />
1919
</div>
20-
<p className="text-gray-600 mt-2">
20+
<p className="text-gray-600 mt-2" data-testid="completion-message">
2121
Visit the Admin Console to configure and install {title}
2222
</p>
2323
<Button
2424
className="mt-4"
25+
dataTestId="admin-console-button"
2526
onClick={() => window.open(`http://${window.location.hostname}:${config.adminConsolePort}`, "_blank")}
2627
icon={<ExternalLink className="ml-2 mr-1 h-5 w-5" />}
2728
>
@@ -34,4 +35,4 @@ const CompletionStep: React.FC = () => {
3435
);
3536
};
3637

37-
export default CompletionStep;
38+
export default LinuxCompletionStep;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useState, useEffect } from 'react';
2+
import Card from '../../common/Card';
3+
import Button from '../../common/Button';
4+
import { useQuery } from "@tanstack/react-query";
5+
import { useSettings } from '../../../contexts/SettingsContext';
6+
import { useAuth } from "../../../contexts/AuthContext";
7+
import { InfraStatusResponse } from '../../../types';
8+
import { ChevronRight } from 'lucide-react';
9+
import InstallationProgress from './shared/InstallationProgress';
10+
import LogViewer from './shared/LogViewer';
11+
import StatusIndicator from './shared/StatusIndicator';
12+
import ErrorMessage from './shared/ErrorMessage';
13+
14+
interface KubernetesInstallationStepProps {
15+
onNext: () => void;
16+
}
17+
18+
const KubernetesInstallationStep: React.FC<KubernetesInstallationStepProps> = ({ onNext }) => {
19+
const { token } = useAuth();
20+
const { settings } = useSettings();
21+
const [isInfraPolling, setIsInfraPolling] = useState(true);
22+
const [installComplete, setInstallComplete] = useState(false);
23+
const [showLogs, setShowLogs] = useState(false);
24+
const themeColor = settings.themeColor;
25+
26+
// Query to poll infra status
27+
const { data: infraStatusResponse, error: infraStatusError } = useQuery<InfraStatusResponse, Error>({
28+
queryKey: ["infraStatus"],
29+
queryFn: async () => {
30+
const response = await fetch("/api/kubernetes/install/infra/status", {
31+
headers: {
32+
"Content-Type": "application/json",
33+
Authorization: `Bearer ${token}`,
34+
},
35+
});
36+
if (!response.ok) {
37+
const errorData = await response.json().catch(() => ({}));
38+
throw new Error(errorData.message || "Failed to get infra status");
39+
}
40+
return response.json() as Promise<InfraStatusResponse>;
41+
},
42+
enabled: isInfraPolling,
43+
refetchInterval: 2000,
44+
});
45+
46+
// Handle infra status changes
47+
useEffect(() => {
48+
if (infraStatusResponse?.status?.state === "Failed") {
49+
setIsInfraPolling(false);
50+
return;
51+
}
52+
if (infraStatusResponse?.status?.state === "Succeeded") {
53+
setIsInfraPolling(false);
54+
setInstallComplete(true);
55+
}
56+
}, [infraStatusResponse]);
57+
58+
const getProgress = () => {
59+
const components = infraStatusResponse?.components || [];
60+
if (components.length === 0) {
61+
return 0;
62+
}
63+
const completedComponents = components.filter(component => component.status?.state === 'Succeeded').length;
64+
return Math.round((completedComponents / components.length) * 100);
65+
}
66+
67+
const renderInfrastructurePhase = () => (
68+
<div className="space-y-6">
69+
<InstallationProgress
70+
progress={getProgress()}
71+
currentMessage={infraStatusResponse?.status?.description || ''}
72+
themeColor={themeColor}
73+
status={infraStatusResponse?.status?.state}
74+
/>
75+
76+
<div className="space-y-2 divide-y divide-gray-200">
77+
{(infraStatusResponse?.components || []).map((component, index) => (
78+
<StatusIndicator
79+
key={index}
80+
title={component.name}
81+
status={component.status?.state}
82+
themeColor={themeColor}
83+
/>
84+
))}
85+
</div>
86+
87+
<LogViewer
88+
title="Installation Logs"
89+
logs={infraStatusResponse?.logs ? [infraStatusResponse.logs] : []}
90+
isExpanded={showLogs}
91+
onToggle={() => setShowLogs(!showLogs)}
92+
/>
93+
94+
{infraStatusError && <ErrorMessage error={infraStatusError?.message} />}
95+
{infraStatusResponse?.status?.state === 'Failed' && <ErrorMessage error={infraStatusResponse?.status?.description} />}
96+
</div>
97+
);
98+
99+
return (
100+
<div className="space-y-6">
101+
<Card>
102+
<div className="mb-6">
103+
<h2 className="text-2xl font-bold text-gray-900">Installation</h2>
104+
<p className="text-gray-600 mt-1">Installing infrastructure components</p>
105+
</div>
106+
107+
{renderInfrastructurePhase()}
108+
</Card>
109+
110+
<div className="flex justify-end">
111+
<Button
112+
onClick={onNext}
113+
disabled={!installComplete}
114+
icon={<ChevronRight className="w-5 h-5" />}
115+
>
116+
Next: Finish
117+
</Button>
118+
</div>
119+
</div>
120+
);
121+
};
122+
123+
export default KubernetesInstallationStep;

web/src/components/wizard/setup/KubernetesSetupStep.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,41 @@ const KubernetesSetupStep: React.FC<KubernetesSetupStepProps> = ({ onNext }) =>
8989
return response.json();
9090
},
9191
onSuccess: () => {
92-
onNext();
92+
setError(null); // Clear any previous errors
93+
startInstallation();
9394
},
9495
onError: (err: ConfigError) => {
9596
setError(err.message || "Failed to submit config");
9697
return err;
9798
},
9899
});
99100

101+
// Mutation for starting the installation
102+
const { mutate: startInstallation } = useMutation({
103+
mutationFn: async () => {
104+
const response = await fetch("/api/kubernetes/install/infra/setup", {
105+
method: "POST",
106+
headers: {
107+
"Content-Type": "application/json",
108+
Authorization: `Bearer ${token}`,
109+
},
110+
});
111+
112+
if (!response.ok) {
113+
const errorData = await response.json().catch(() => ({}));
114+
throw new Error(errorData.message || "Failed to start installation");
115+
}
116+
return response.json();
117+
},
118+
onSuccess: () => {
119+
setError(null); // Clear any previous errors
120+
onNext();
121+
},
122+
onError: (err: Error) => {
123+
setError(err.message || "Failed to start installation");
124+
},
125+
});
126+
100127
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
101128
const { id, value } = e.target;
102129
if (id === "adminConsolePort") {
@@ -174,7 +201,10 @@ const KubernetesSetupStep: React.FC<KubernetesSetupStepProps> = ({ onNext }) =>
174201

175202
{error && (
176203
<div className="mt-4 p-3 bg-red-50 text-red-500 rounded-md">
177-
Please fix the errors in the form above before proceeding. {error}
204+
{submitError?.errors && submitError.errors.length > 0
205+
? "Please fix the errors in the form above before proceeding."
206+
: error
207+
}
178208
</div>
179209
)}
180210
</>

web/src/components/wizard/setup/LinuxSetupStep.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ const LinuxSetupStep: React.FC<LinuxSetupStepProps> = ({ onNext }) => {
295295

296296
{error && (
297297
<div className="mt-4 p-3 bg-red-50 text-red-500 rounded-md">
298-
Please fix the errors in the form above before proceeding.
298+
{submitError?.errors && submitError.errors.length > 0
299+
? "Please fix the errors in the form above before proceeding."
300+
: error
301+
}
299302
</div>
300303
)}
301304
</>

0 commit comments

Comments
 (0)