mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
feat(system): path to upgrade standalone to BE [EE-4071] (#8095)
This commit is contained in:
parent
756ac034ec
commit
5cbf52377d
73 changed files with 1374 additions and 421 deletions
52
app/react/sidebar/UpgradeBEBanner/LoadingDialog.tsx
Normal file
52
app/react/sidebar/UpgradeBEBanner/LoadingDialog.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Loader2 } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useSystemStatus } from '@/react/portainer/system/useSystemStatus';
|
||||
|
||||
import { Modal } from '@@/modals/Modal';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
export function LoadingDialog() {
|
||||
useWaitForServerStatus();
|
||||
|
||||
return (
|
||||
<Modal aria-label="Upgrade Portainer to Business Edition">
|
||||
<Modal.Body>
|
||||
<div className="flex flex-col items-center justify-center w-full">
|
||||
<Icon
|
||||
icon={Loader2}
|
||||
className="animate-spin-slow !text-8xl !text-blue-8"
|
||||
aria-label="loading"
|
||||
/>
|
||||
|
||||
<h1 className="!text-2xl">Upgrading Portainer...</h1>
|
||||
|
||||
<p className="text-center text-gray-6 text-xl">
|
||||
Please wait while we upgrade your Portainer to Business Edition.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function useWaitForServerStatus() {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
useSystemStatus({
|
||||
enabled,
|
||||
retry: true,
|
||||
onSuccess() {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setEnabled(true);
|
||||
}, 3000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
});
|
||||
}
|
52
app/react/sidebar/UpgradeBEBanner/NonAdminUpgradeDialog.tsx
Normal file
52
app/react/sidebar/UpgradeBEBanner/NonAdminUpgradeDialog.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { ExternalLink } from 'lucide-react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { Modal } from '@@/modals/Modal';
|
||||
import { ModalType } from '@@/modals/Modal/types';
|
||||
|
||||
export function NonAdminUpgradeDialog({
|
||||
onDismiss,
|
||||
}: {
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Modal aria-label="Upgrade Portainer to Business Edition">
|
||||
<Modal.Header
|
||||
title="Contact your administrator"
|
||||
modalType={ModalType.Warn}
|
||||
/>
|
||||
<Modal.Body>
|
||||
You need to be logged in as an admin to upgrade Portainer to Business
|
||||
Edition.
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<div className="flex gap-2 w-full">
|
||||
<Button
|
||||
color="default"
|
||||
size="medium"
|
||||
className="w-1/3"
|
||||
onClick={() => onDismiss()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<a
|
||||
href="https://www.portainer.io/take-5"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="no-link w-2/3"
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
className="w-full"
|
||||
icon={ExternalLink}
|
||||
>
|
||||
Learn about Business Edition
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
78
app/react/sidebar/UpgradeBEBanner/UpgradeBEBanner.tsx
Normal file
78
app/react/sidebar/UpgradeBEBanner/UpgradeBEBanner.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { ArrowRight } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useAnalytics } from '@/angulartics.matomo/analytics-services';
|
||||
import { useNodesCount } from '@/react/portainer/system/useNodesCount';
|
||||
import {
|
||||
ContainerPlatform,
|
||||
useSystemInfo,
|
||||
} from '@/react/portainer/system/useSystemInfo';
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
import { withEdition } from '@/react/portainer/feature-flags/withEdition';
|
||||
import { withHideOnExtension } from '@/react/hooks/withHideOnExtension';
|
||||
|
||||
import { useSidebarState } from '../useSidebarState';
|
||||
|
||||
import { UpgradeDialog } from './UpgradeDialog';
|
||||
|
||||
export const UpgradeBEBannerWrapper = withHideOnExtension(
|
||||
withEdition(UpgradeBEBanner, 'CE')
|
||||
);
|
||||
|
||||
const enabledPlatforms: Array<ContainerPlatform> = ['Docker Standalone'];
|
||||
|
||||
function UpgradeBEBanner() {
|
||||
const { isAdmin } = useUser();
|
||||
const { trackEvent } = useAnalytics();
|
||||
const { isOpen: isSidebarOpen } = useSidebarState();
|
||||
const nodesCountQuery = useNodesCount();
|
||||
const systemInfoQuery = useSystemInfo();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
if (!nodesCountQuery.isSuccess || !systemInfoQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nodesCount = nodesCountQuery.data;
|
||||
const systemInfo = systemInfoQuery.data;
|
||||
|
||||
const metadata = {
|
||||
upgrade: false,
|
||||
nodeCount: nodesCount,
|
||||
platform: systemInfo.platform,
|
||||
edgeAgents: systemInfo.edgeAgents,
|
||||
edgeDevices: systemInfo.edgeDevices,
|
||||
agents: systemInfo.agents,
|
||||
};
|
||||
|
||||
if (!enabledPlatforms.includes(systemInfo.platform)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="border-0 bg-warning-5 text-warning-9 w-full min-h-[48px] h-12 font-semibold flex justify-center items-center gap-3"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{isSidebarOpen && <>Upgrade to Business Edition</>}
|
||||
<ArrowRight className="text-lg lucide" />
|
||||
</button>
|
||||
|
||||
{isOpen && <UpgradeDialog onDismiss={() => setIsOpen(false)} />}
|
||||
</>
|
||||
);
|
||||
|
||||
function handleClick() {
|
||||
trackEvent(
|
||||
isAdmin ? 'portainer-upgrade-admin' : 'portainer-upgrade-non-admin',
|
||||
{
|
||||
category: 'portainer',
|
||||
metadata,
|
||||
}
|
||||
);
|
||||
setIsOpen(true);
|
||||
}
|
||||
}
|
41
app/react/sidebar/UpgradeBEBanner/UpgradeDialog.tsx
Normal file
41
app/react/sidebar/UpgradeBEBanner/UpgradeDialog.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
|
||||
import { UploadLicenseDialog } from './UploadLicenseDialog';
|
||||
import { LoadingDialog } from './LoadingDialog';
|
||||
import { NonAdminUpgradeDialog } from './NonAdminUpgradeDialog';
|
||||
|
||||
type Step = 'uploadLicense' | 'loading' | 'getLicense';
|
||||
|
||||
export function UpgradeDialog({ onDismiss }: { onDismiss: () => void }) {
|
||||
const { isAdmin } = useUser();
|
||||
const [currentStep, setCurrentStep] = useState<Step>('uploadLicense');
|
||||
|
||||
const component = getDialog();
|
||||
|
||||
return component;
|
||||
|
||||
function getDialog() {
|
||||
if (!isAdmin) {
|
||||
return <NonAdminUpgradeDialog onDismiss={onDismiss} />;
|
||||
}
|
||||
|
||||
switch (currentStep) {
|
||||
case 'getLicense':
|
||||
throw new Error('Not implemented');
|
||||
// return <GetLicense setCurrentStep={setCurrentStep} />;
|
||||
case 'uploadLicense':
|
||||
return (
|
||||
<UploadLicenseDialog
|
||||
goToLoading={() => setCurrentStep('loading')}
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
);
|
||||
case 'loading':
|
||||
return <LoadingDialog />;
|
||||
default:
|
||||
throw new Error('step type not found');
|
||||
}
|
||||
}
|
||||
}
|
107
app/react/sidebar/UpgradeBEBanner/UploadLicenseDialog.tsx
Normal file
107
app/react/sidebar/UpgradeBEBanner/UploadLicenseDialog.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { Field, Form, Formik } from 'formik';
|
||||
import { object, SchemaOf, string } from 'yup';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
|
||||
import { useUpgradeEditionMutation } from '@/react/portainer/system/useUpgradeEditionMutation';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import { Button, LoadingButton } from '@@/buttons';
|
||||
import { FormControl } from '@@/form-components/FormControl';
|
||||
import { Input } from '@@/form-components/Input';
|
||||
import { Modal } from '@@/modals/Modal';
|
||||
|
||||
interface FormValues {
|
||||
license: string;
|
||||
}
|
||||
|
||||
const initialValues: FormValues = {
|
||||
license: '',
|
||||
};
|
||||
|
||||
export function UploadLicenseDialog({
|
||||
onDismiss,
|
||||
goToLoading,
|
||||
}: {
|
||||
onDismiss: () => void;
|
||||
goToLoading: () => void;
|
||||
}) {
|
||||
const upgradeMutation = useUpgradeEditionMutation();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onDismiss={onDismiss}
|
||||
aria-label="Upgrade Portainer to Business Edition"
|
||||
>
|
||||
<Modal.Header
|
||||
title={<h4 className="font-medium text-xl">Upgrade Portainer</h4>}
|
||||
/>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validation}
|
||||
>
|
||||
{({ errors }) => (
|
||||
<Form noValidate>
|
||||
<Modal.Body>
|
||||
<p className="font-semibold text-gray-7">
|
||||
Please enter your Portainer License Below
|
||||
</p>
|
||||
<FormControl
|
||||
label="License"
|
||||
errors={errors.license}
|
||||
required
|
||||
size="vertical"
|
||||
>
|
||||
<Field name="license" as={Input} required />
|
||||
</FormControl>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<div className="flex gap-2 [&>*]:w-1/2 w-full">
|
||||
<a
|
||||
href="https://www.portainer.io/take-5"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="no-link"
|
||||
>
|
||||
<Button
|
||||
color="default"
|
||||
size="medium"
|
||||
className="w-full"
|
||||
icon={ExternalLink}
|
||||
>
|
||||
Get a license
|
||||
</Button>
|
||||
</a>
|
||||
<LoadingButton
|
||||
color="primary"
|
||||
size="medium"
|
||||
loadingText="Validating License"
|
||||
isLoading={upgradeMutation.isLoading}
|
||||
>
|
||||
Start upgrade
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
function handleSubmit(values: FormValues) {
|
||||
upgradeMutation.mutate(values, {
|
||||
onSuccess() {
|
||||
notifySuccess('Starting upgrade', 'License validated successfully');
|
||||
goToLoading();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function validation(): SchemaOf<FormValues> {
|
||||
return object().shape({
|
||||
license: string()
|
||||
.required('License is required')
|
||||
.matches(/^\d-.+/, 'License is invalid'),
|
||||
});
|
||||
}
|
1
app/react/sidebar/UpgradeBEBanner/index.ts
Normal file
1
app/react/sidebar/UpgradeBEBanner/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { UpgradeBEBannerWrapper } from './UpgradeBEBanner';
|
Loading…
Add table
Add a link
Reference in a new issue