mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(system/upgrade): add upgrade banner [EE-4564] (#8046)
This commit is contained in:
parent
c21921a08d
commit
eccc8131dd
16 changed files with 366 additions and 33 deletions
|
@ -4,15 +4,20 @@ import { usePublicSettings } from '@/react/portainer/settings/queries';
|
|||
|
||||
export enum FeatureFlag {
|
||||
EdgeRemoteUpdate = 'edgeRemoteUpdate',
|
||||
BEUpgrade = 'beUpgrade',
|
||||
}
|
||||
|
||||
export function useFeatureFlag(
|
||||
flag: FeatureFlag,
|
||||
{ onSuccess }: { onSuccess?: (isEnabled: boolean) => void } = {}
|
||||
{
|
||||
onSuccess,
|
||||
enabled = true,
|
||||
}: { onSuccess?: (isEnabled: boolean) => void; enabled?: boolean } = {}
|
||||
) {
|
||||
return usePublicSettings<boolean>({
|
||||
select: (settings) => settings.Features[flag],
|
||||
onSuccess,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
9
app/react/portainer/status/build-url.ts
Normal file
9
app/react/portainer/status/build-url.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export function buildUrl(action?: string) {
|
||||
let url = '/status';
|
||||
|
||||
if (action) {
|
||||
url += `/${action}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
25
app/react/portainer/status/useNodesCount.ts
Normal file
25
app/react/portainer/status/useNodesCount.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { buildUrl } from './build-url';
|
||||
|
||||
export interface NodesCountResponse {
|
||||
nodes: number;
|
||||
}
|
||||
|
||||
async function getNodesCount() {
|
||||
try {
|
||||
const { data } = await axios.get<NodesCountResponse>(buildUrl('nodes'));
|
||||
return data.nodes;
|
||||
} catch (error) {
|
||||
throw parseAxiosError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export function useNodesCount() {
|
||||
return useQuery(['status', 'nodes'], getNodesCount, {
|
||||
...withError('Unable to retrieve nodes count'),
|
||||
});
|
||||
}
|
28
app/react/portainer/status/useSystemInfo.ts
Normal file
28
app/react/portainer/status/useSystemInfo.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { buildUrl } from './build-url';
|
||||
|
||||
export interface SystemInfoResponse {
|
||||
platform: string;
|
||||
agents: number;
|
||||
edgeAgents: number;
|
||||
edgeDevices: number;
|
||||
}
|
||||
|
||||
async function getSystemInfo() {
|
||||
try {
|
||||
const { data } = await axios.get<SystemInfoResponse>(buildUrl('system'));
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw parseAxiosError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export function useSystemInfo() {
|
||||
return useQuery(['status', 'system'], getSystemInfo, {
|
||||
...withError('Unable to retrieve system info'),
|
||||
});
|
||||
}
|
|
@ -32,6 +32,9 @@
|
|||
|
||||
z-index: 999;
|
||||
transition: all 0.4s ease 0s;
|
||||
}
|
||||
|
||||
.nav {
|
||||
background-color: var(--bg-sidebar-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { SidebarItem } from './SidebarItem';
|
|||
import { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
import { SidebarProvider } from './useSidebarState';
|
||||
import { UpgradeBEBanner } from './UpgradeBEBanner';
|
||||
|
||||
export function Sidebar() {
|
||||
const { isAdmin, user } = useUser();
|
||||
|
@ -29,31 +30,35 @@ export function Sidebar() {
|
|||
return (
|
||||
/* in the future (when we remove r2a) this should wrap the whole app - to change root styles */
|
||||
<SidebarProvider>
|
||||
<nav className={clsx(styles.root, 'p-5 flex flex-col')} aria-label="Main">
|
||||
<Header logo={LogoURL} />
|
||||
|
||||
{/* negative margin + padding -> scrollbar won't hide the content */}
|
||||
<div className="mt-6 overflow-y-auto flex-1 -mr-4 pr-4">
|
||||
<ul className="space-y-9">
|
||||
<SidebarItem
|
||||
to="portainer.home"
|
||||
icon={Home}
|
||||
label="Home"
|
||||
data-cy="portainerSidebar-home"
|
||||
/>
|
||||
|
||||
<EnvironmentSidebar />
|
||||
|
||||
{isAdmin && EnableEdgeComputeFeatures && <EdgeComputeSidebar />}
|
||||
|
||||
<SettingsSidebar isAdmin={isAdmin} isTeamLeader={isTeamLeader} />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto pt-8">
|
||||
<Footer />
|
||||
</div>
|
||||
</nav>
|
||||
<div className={clsx(styles.root, 'flex flex-col')}>
|
||||
<UpgradeBEBanner />
|
||||
<nav
|
||||
className={clsx(
|
||||
styles.nav,
|
||||
'p-5 flex flex-col flex-1 overflow-y-auto'
|
||||
)}
|
||||
aria-label="Main"
|
||||
>
|
||||
<Header logo={LogoURL} />
|
||||
{/* negative margin + padding -> scrollbar won't hide the content */}
|
||||
<div className="mt-6 overflow-y-auto flex-1 -mr-4 pr-4">
|
||||
<ul className="space-y-9">
|
||||
<SidebarItem
|
||||
to="portainer.home"
|
||||
icon={Home}
|
||||
label="Home"
|
||||
data-cy="portainerSidebar-home"
|
||||
/>
|
||||
<EnvironmentSidebar />
|
||||
{isAdmin && EnableEdgeComputeFeatures && <EdgeComputeSidebar />}
|
||||
<SettingsSidebar isAdmin={isAdmin} isTeamLeader={isTeamLeader} />
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-auto pt-8">
|
||||
<Footer />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
|
63
app/react/sidebar/UpgradeBEBanner.tsx
Normal file
63
app/react/sidebar/UpgradeBEBanner.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { ArrowRight } from 'react-feather';
|
||||
|
||||
import { useAnalytics } from '@/angulartics.matomo/analytics-services';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import {
|
||||
useFeatureFlag,
|
||||
FeatureFlag,
|
||||
} from '@/react/portainer/feature-flags/useRedirectFeatureFlag';
|
||||
import { useNodesCount } from '@/react/portainer/status/useNodesCount';
|
||||
import { useSystemInfo } from '@/react/portainer/status/useSystemInfo';
|
||||
|
||||
import { useSidebarState } from './useSidebarState';
|
||||
|
||||
export function UpgradeBEBanner() {
|
||||
const { data } = useFeatureFlag(FeatureFlag.BEUpgrade, { enabled: !isBE });
|
||||
|
||||
if (isBE || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Inner />;
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const { trackEvent } = useAnalytics();
|
||||
const { isOpen } = useSidebarState();
|
||||
const nodesCountQuery = useNodesCount();
|
||||
const systemInfoQuery = useSystemInfo();
|
||||
|
||||
if (!nodesCountQuery.data || !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,
|
||||
};
|
||||
|
||||
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}
|
||||
>
|
||||
{isOpen && <>Upgrade to Business Edition</>}
|
||||
<ArrowRight className="text-lg feather" />
|
||||
</button>
|
||||
);
|
||||
|
||||
function handleClick() {
|
||||
trackEvent('portainer-upgrade-admin', {
|
||||
category: 'portainer',
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue