mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(home): change layout of env tile [EE-4479] (#8061)
This commit is contained in:
parent
b48aa1274d
commit
eba5879ec8
29 changed files with 717 additions and 445 deletions
|
@ -1,9 +1,10 @@
|
|||
import clsx from 'clsx';
|
||||
import { Activity } from 'lucide-react';
|
||||
|
||||
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
|
||||
import { useHasHeartbeat } from '@/react/edge/hooks/useHasHeartbeat';
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||
import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
||||
|
||||
import { EnvironmentStatusBadgeItem } from './EnvironmentStatusBadgeItem';
|
||||
|
||||
interface Props {
|
||||
showLastCheckInDate?: boolean;
|
||||
|
@ -15,100 +16,46 @@ export function EdgeIndicator({
|
|||
|
||||
showLastCheckInDate = false,
|
||||
}: Props) {
|
||||
const associated = !!environment.EdgeID;
|
||||
|
||||
const isValid = useHasHeartbeat(environment, associated);
|
||||
const isValid = useHasHeartbeat(environment);
|
||||
|
||||
if (isValid === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const associated = !!environment.EdgeID;
|
||||
if (!associated) {
|
||||
return (
|
||||
<span role="status" aria-label="edge-status">
|
||||
<span className="label label-default" aria-label="unassociated">
|
||||
<EnvironmentStatusBadgeItem aria-label="unassociated">
|
||||
<s>associated</s>
|
||||
</span>
|
||||
</EnvironmentStatusBadgeItem>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span role="status" aria-label="edge-status">
|
||||
<span
|
||||
className={clsx('label', {
|
||||
'label-danger': !isValid,
|
||||
'label-success': isValid,
|
||||
})}
|
||||
<span
|
||||
role="status"
|
||||
aria-label="edge-status"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<EnvironmentStatusBadgeItem
|
||||
color={isValid ? 'success' : 'danger'}
|
||||
aria-label="edge-heartbeat"
|
||||
>
|
||||
heartbeat
|
||||
</span>
|
||||
</EnvironmentStatusBadgeItem>
|
||||
|
||||
{showLastCheckInDate && !!environment.LastCheckInDate && (
|
||||
<span
|
||||
className="space-left small text-muted"
|
||||
className="small text-muted vertical-center"
|
||||
aria-label="edge-last-checkin"
|
||||
title="Last edge check-in"
|
||||
>
|
||||
<Activity className="icon icon-sm space-right" aria-hidden="true" />
|
||||
{isoDateFromTimestamp(environment.LastCheckInDate)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function useHasHeartbeat(environment: Environment, associated: boolean) {
|
||||
const settingsQuery = usePublicSettings({ enabled: associated });
|
||||
|
||||
if (!associated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { LastCheckInDate, QueryDate } = environment;
|
||||
|
||||
const settings = settingsQuery.data;
|
||||
|
||||
if (!settings) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const checkInInterval = getCheckinInterval(environment, settings);
|
||||
|
||||
if (checkInInterval && QueryDate && LastCheckInDate) {
|
||||
return QueryDate - LastCheckInDate <= checkInInterval * 2 + 20;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCheckinInterval(
|
||||
environment: Environment,
|
||||
settings: PublicSettingsViewModel
|
||||
) {
|
||||
const asyncMode = environment.Edge.AsyncMode;
|
||||
|
||||
if (asyncMode) {
|
||||
const intervals = [
|
||||
environment.Edge.PingInterval > 0
|
||||
? environment.Edge.PingInterval
|
||||
: settings.Edge.PingInterval,
|
||||
environment.Edge.SnapshotInterval > 0
|
||||
? environment.Edge.SnapshotInterval
|
||||
: settings.Edge.SnapshotInterval,
|
||||
environment.Edge.CommandInterval > 0
|
||||
? environment.Edge.CommandInterval
|
||||
: settings.Edge.CommandInterval,
|
||||
].filter((n) => n > 0);
|
||||
|
||||
return intervals.length > 0 ? Math.min(...intervals) : 60;
|
||||
}
|
||||
|
||||
if (
|
||||
!environment.EdgeCheckinInterval ||
|
||||
environment.EdgeCheckinInterval === 0
|
||||
) {
|
||||
return settings.Edge.CheckinInterval;
|
||||
}
|
||||
|
||||
return environment.EdgeCheckinInterval;
|
||||
}
|
||||
|
|
21
app/react/components/EnvironmentStatusBadge.tsx
Normal file
21
app/react/components/EnvironmentStatusBadge.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { CheckCircle, XCircle } from 'lucide-react';
|
||||
|
||||
import { EnvironmentStatus } from '@/react/portainer/environments/types';
|
||||
|
||||
import { EnvironmentStatusBadgeItem } from './EnvironmentStatusBadgeItem';
|
||||
|
||||
interface Props {
|
||||
status: EnvironmentStatus;
|
||||
}
|
||||
|
||||
export function EnvironmentStatusBadge({ status }: Props) {
|
||||
return status === EnvironmentStatus.Up ? (
|
||||
<EnvironmentStatusBadgeItem color="success" icon={CheckCircle}>
|
||||
Up
|
||||
</EnvironmentStatusBadgeItem>
|
||||
) : (
|
||||
<EnvironmentStatusBadgeItem color="danger" icon={XCircle}>
|
||||
Down
|
||||
</EnvironmentStatusBadgeItem>
|
||||
);
|
||||
}
|
48
app/react/components/EnvironmentStatusBadgeItem.tsx
Normal file
48
app/react/components/EnvironmentStatusBadgeItem.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import clsx from 'clsx';
|
||||
import { AriaAttributes, PropsWithChildren } from 'react';
|
||||
|
||||
import { Icon, IconProps } from '@@/Icon';
|
||||
|
||||
export function EnvironmentStatusBadgeItem({
|
||||
className,
|
||||
children,
|
||||
color = 'default',
|
||||
icon,
|
||||
...aria
|
||||
}: PropsWithChildren<
|
||||
{
|
||||
className?: string;
|
||||
color?: 'success' | 'danger' | 'default';
|
||||
icon?: IconProps['icon'];
|
||||
} & AriaAttributes
|
||||
>) {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
'flex items-center gap-1',
|
||||
'border-2 border-solid rounded',
|
||||
'w-fit py-px px-1',
|
||||
'text-xs font-semibold text-gray-7',
|
||||
{
|
||||
'border-green-3 bg-green-2': color === 'success',
|
||||
'border-error-3 bg-error-2': color === 'danger',
|
||||
},
|
||||
className
|
||||
)}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...aria}
|
||||
>
|
||||
{icon && (
|
||||
<Icon
|
||||
icon={icon}
|
||||
className={clsx({
|
||||
'!text-green-7': color === 'success',
|
||||
'!text-error-7': color === 'danger',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
33
app/react/components/LinkButton.tsx
Normal file
33
app/react/components/LinkButton.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ComponentProps } from 'react';
|
||||
|
||||
import { Button } from './buttons';
|
||||
import { Link } from './Link';
|
||||
|
||||
export function LinkButton({
|
||||
to,
|
||||
params,
|
||||
disabled,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof Button> & ComponentProps<typeof Link>) {
|
||||
const button = (
|
||||
<Button
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
size="medium"
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (disabled) {
|
||||
return button;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={to} params={params} className="text-inherit hover:no-underline">
|
||||
{button}
|
||||
</Link>
|
||||
);
|
||||
}
|
27
app/react/components/StatsItem.tsx
Normal file
27
app/react/components/StatsItem.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import clsx from 'clsx';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Icon, IconProps } from '@/react/components/Icon';
|
||||
|
||||
interface Props extends IconProps {
|
||||
value: string | number;
|
||||
icon: IconProps['icon'];
|
||||
iconClass?: string;
|
||||
}
|
||||
|
||||
export function StatsItem({
|
||||
value,
|
||||
icon,
|
||||
children,
|
||||
iconClass,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<span className="flex gap-1 items-center">
|
||||
<Icon className={clsx('icon icon-sm', iconClass)} icon={icon} />
|
||||
<span>{value}</span>
|
||||
{children && (
|
||||
<span className="ml-1 flex gap-2 items-center">{children}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue