mirror of https://github.com/portainer/portainer
feat(home): change layout of env tile [EE-4479] (#8061)
parent
b48aa1274d
commit
eba5879ec8
|
@ -205,7 +205,6 @@ input[type='checkbox'] {
|
|||
.blocklist-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-blocklist);
|
||||
border-radius: 8px;
|
||||
margin-right: 10px;
|
||||
|
@ -222,8 +221,9 @@ input[type='checkbox'] {
|
|||
color: var(--text-blocklist-item-selected-color);
|
||||
}
|
||||
|
||||
.blocklist-item:hover {
|
||||
.blocklist-item:not(.blocklist-item-not-interactive):hover {
|
||||
@apply border border-blue-7;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: var(--bg-blocklist-hover-color);
|
||||
color: var(--text-blocklist-hover-color);
|
||||
|
@ -233,10 +233,6 @@ input[type='checkbox'] {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.blocklist-item-line.endpoint-item {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.blocklist-item-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -248,24 +244,11 @@ input[type='checkbox'] {
|
|||
max-height: 60px;
|
||||
}
|
||||
|
||||
.blocklist-item-logo.endpoint-item {
|
||||
margin: 10px 4px 0 6px;
|
||||
}
|
||||
|
||||
.blocklist-item-logo.endpoint-item.azure {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.blocklist-item-title {
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.blocklist-item-title.endpoint-item {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.blocklist-item-subtitle {
|
||||
font-size: 0.9em;
|
||||
padding-right: 1em;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
export type UserId = number;
|
||||
import { UserId } from './types/user-id';
|
||||
|
||||
export { type UserId };
|
||||
|
||||
export enum Role {
|
||||
Admin = 1,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export type UserId = number;
|
|
@ -78,6 +78,7 @@ export function createMockEnvironment(): Environment {
|
|||
AllowNoneIngressClass: false,
|
||||
},
|
||||
},
|
||||
Nomad: { Snapshots: [] },
|
||||
EdgeKey: '',
|
||||
Id: 3,
|
||||
UserTrusted: false,
|
||||
|
|
|
@ -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,
|
||||
})}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -9,20 +9,19 @@ interface Props extends IconProps {
|
|||
iconClass?: string;
|
||||
}
|
||||
|
||||
export function EnvironmentStatsItem({
|
||||
export function StatsItem({
|
||||
value,
|
||||
icon,
|
||||
children,
|
||||
iconClass,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<span className="vertical-center space-right">
|
||||
<Icon
|
||||
className={clsx('icon icon-sm space-right', iconClass)}
|
||||
icon={icon}
|
||||
/>
|
||||
<span className="flex gap-1 items-center">
|
||||
<Icon className={clsx('icon icon-sm', iconClass)} icon={icon} />
|
||||
<span>{value}</span>
|
||||
{children && <span className="space-left">{children}</span>}
|
||||
{children && (
|
||||
<span className="ml-1 flex gap-2 items-center">{children}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { DockerContainer } from '@/react/docker/containers/types';
|
||||
|
||||
export type DockerSnapshotRaw = {
|
||||
Containers: DockerContainer[];
|
||||
SnapshotTime: string;
|
||||
};
|
||||
|
||||
export interface DockerSnapshot {
|
||||
TotalCPU: number;
|
||||
TotalMemory: number;
|
||||
NodeCount: number;
|
||||
ImageCount: number;
|
||||
VolumeCount: number;
|
||||
RunningContainerCount: number;
|
||||
StoppedContainerCount: number;
|
||||
HealthyContainerCount: number;
|
||||
UnhealthyContainerCount: number;
|
||||
Time: number;
|
||||
StackCount: number;
|
||||
ServiceCount: number;
|
||||
Swarm: boolean;
|
||||
DockerVersion: string;
|
||||
GpuUseAll: boolean;
|
||||
GpuUseList: string[];
|
||||
SnapshotRaw: DockerSnapshotRaw;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { CellProps, Column } from 'react-table';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
|
||||
import { EdgeIndicator } from '@@/EdgeIndicator';
|
||||
import { useHasHeartbeat } from '@/react/edge/hooks/useHasHeartbeat';
|
||||
|
||||
export const heartbeat: Column<Environment> = {
|
||||
Header: 'Heartbeat',
|
||||
|
@ -18,3 +18,36 @@ export function StatusCell({
|
|||
}: CellProps<Environment>) {
|
||||
return <EdgeIndicator environment={environment} />;
|
||||
}
|
||||
|
||||
function EdgeIndicator({ environment }: { environment: Environment }) {
|
||||
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">
|
||||
<s>associated</s>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span role="status" aria-label="edge-status">
|
||||
<span
|
||||
className={clsx('label', {
|
||||
'label-danger': !isValid,
|
||||
'label-success': isValid,
|
||||
})}
|
||||
aria-label="edge-heartbeat"
|
||||
>
|
||||
heartbeat
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||
import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
||||
|
||||
export function useHasHeartbeat(environment: Environment) {
|
||||
const associated = !!environment.EdgeID;
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Power } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { StatsItem } from '@@/StatsItem';
|
||||
|
||||
interface Props {
|
||||
running: number;
|
||||
|
@ -11,11 +11,19 @@ export function RunningStatus({ running, stopped }: Props) {
|
|||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Icon icon={Power} mode="success" />
|
||||
<StatsItem
|
||||
value={`${running || '-'} running`}
|
||||
icon={Power}
|
||||
iconClass="icon-success"
|
||||
/>
|
||||
{`${running || '-'} running`}
|
||||
</div>
|
||||
<div>
|
||||
<Icon icon={Power} mode="danger" />
|
||||
<StatsItem
|
||||
value={`${stopped || '-'} stopped`}
|
||||
icon={Power}
|
||||
iconClass="icon-danger"
|
||||
/>
|
||||
{`${stopped || '-'} stopped`}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,9 +18,8 @@ export function AgentVersionTag({ type, version }: Props) {
|
|||
|
||||
return (
|
||||
<span className="space-x-1">
|
||||
<span>
|
||||
<Zap className="icon icon-xs vertical-center" aria-hidden="true" />
|
||||
</span>
|
||||
|
||||
<span>{isEdgeEnvironment(type) ? 'Edge Agent' : 'Agent'}</span>
|
||||
|
||||
<span>{version}</span>
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
import { Edit2, Settings } from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
import {
|
||||
Environment,
|
||||
PlatformType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import {
|
||||
isEdgeAsync as checkEdgeAsync,
|
||||
getPlatformType,
|
||||
} from '@/react/portainer/environments/utils';
|
||||
|
||||
import { LinkButton } from '@@/LinkButton';
|
||||
|
||||
export function EditButtons({ environment }: { environment: Environment }) {
|
||||
const { isAdmin } = useUser();
|
||||
|
||||
const isEdgeAsync = checkEdgeAsync(environment);
|
||||
|
||||
const configRoute = getConfigRoute(environment);
|
||||
return (
|
||||
<ButtonsGrid className="w-11 -m-[11px] ml-3">
|
||||
<LinkButton
|
||||
disabled={!isAdmin}
|
||||
to="portainer.endpoints.endpoint"
|
||||
params={{ id: environment.Id }}
|
||||
color="none"
|
||||
icon={Edit2}
|
||||
size="medium"
|
||||
className="w-full h-full !ml-0 hover:bg-gray-3 !rounded-none"
|
||||
title="Edit"
|
||||
/>
|
||||
|
||||
<LinkButton
|
||||
disabled={!configRoute || isEdgeAsync || !isAdmin}
|
||||
to={configRoute}
|
||||
params={{ endpointId: environment.Id }}
|
||||
color="none"
|
||||
icon={Settings}
|
||||
size="medium"
|
||||
className="w-full h-full !ml-0 hover:bg-gray-3 !rounded-none"
|
||||
title="Configuration"
|
||||
/>
|
||||
</ButtonsGrid>
|
||||
);
|
||||
}
|
||||
|
||||
function getConfigRoute(environment: Environment) {
|
||||
const platform = getPlatformType(environment.Type);
|
||||
|
||||
switch (platform) {
|
||||
case PlatformType.Docker:
|
||||
return getDockerConfigRoute(environment);
|
||||
case PlatformType.Kubernetes:
|
||||
return 'kubernetes.cluster';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getDockerConfigRoute(environment: Environment) {
|
||||
const snapshot = environment.Snapshots?.[0];
|
||||
if (!snapshot) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return snapshot.Swarm ? 'docker.swarm' : 'docker.host';
|
||||
}
|
||||
|
||||
function ButtonsGrid({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode[];
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'grid grid-rows-3 border border-solid border-gray-5 rounded-r-lg',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div>{children[0] || null}</div>
|
||||
<div className="border-x-0 border-y border-gray-5 border-solid">
|
||||
{children[1] || null}
|
||||
</div>
|
||||
<div>{children[2] || null}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
||||
import {
|
||||
Environment,
|
||||
PlatformType,
|
||||
KubernetesSnapshot,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { getPlatformType } from '@/react/portainer/environments/utils';
|
||||
|
||||
export function EngineVersion({ environment }: { environment: Environment }) {
|
||||
const platform = getPlatformType(environment.Type);
|
||||
|
||||
switch (platform) {
|
||||
case PlatformType.Docker:
|
||||
return <DockerEngineVersion snapshot={environment.Snapshots[0]} />;
|
||||
case PlatformType.Kubernetes:
|
||||
return (
|
||||
<KubernetesEngineVersion
|
||||
snapshot={environment.Kubernetes.Snapshots?.[0]}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function DockerEngineVersion({ snapshot }: { snapshot?: DockerSnapshot }) {
|
||||
if (!snapshot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="small text-muted vertical-center">
|
||||
{snapshot.Swarm ? 'Swarm' : 'Standalone'} {snapshot.DockerVersion}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function KubernetesEngineVersion({
|
||||
snapshot,
|
||||
}: {
|
||||
snapshot?: KubernetesSnapshot;
|
||||
}) {
|
||||
if (!snapshot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="small text-muted vertical-center">
|
||||
Kubernetes {snapshot.KubernetesVersion}
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import { Wifi, WifiOff } from 'lucide-react';
|
||||
|
||||
import ClockRewind from '@/assets/ico/clock-rewind.svg?c';
|
||||
import { Environment } from '@/react/portainer/environments/types';
|
||||
import {
|
||||
getDashboardRoute,
|
||||
isEdgeAsync as checkEdgeAsync,
|
||||
} from '@/react/portainer/environments/utils';
|
||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { LinkButton } from '@@/LinkButton';
|
||||
|
||||
export function EnvironmentBrowseButtons({
|
||||
environment,
|
||||
onClickBrowse,
|
||||
isActive,
|
||||
}: {
|
||||
environment: Environment;
|
||||
onClickBrowse(): void;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
const isEdgeAsync = checkEdgeAsync(environment);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 ml-auto [&>*]:flex-1">
|
||||
{isBE && (
|
||||
<LinkButton
|
||||
icon={ClockRewind}
|
||||
disabled={!isEdgeAsync}
|
||||
to="edge.browse.dashboard"
|
||||
params={{
|
||||
environmentId: environment.Id,
|
||||
}}
|
||||
color="light"
|
||||
className="w-full py-1"
|
||||
>
|
||||
Browse snapshot
|
||||
</LinkButton>
|
||||
)}
|
||||
|
||||
<LinkButton
|
||||
icon={Wifi}
|
||||
disabled={isEdgeAsync}
|
||||
to={getDashboardRoute(environment)}
|
||||
params={{
|
||||
endpointId: environment.Id,
|
||||
}}
|
||||
onClick={onClickBrowse}
|
||||
color="primary"
|
||||
className="w-full py-1"
|
||||
>
|
||||
Live connect
|
||||
</LinkButton>
|
||||
|
||||
{!isActive ? (
|
||||
<div className="min-h-[30px] vertical-center justify-center">
|
||||
<Icon icon={WifiOff} />
|
||||
Disconnected
|
||||
</div>
|
||||
) : (
|
||||
<div className="min-h-[30px]" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
.root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wrapperButton {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
outline: initial;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 5px;
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import _ from 'lodash';
|
||||
import { Edit2, Tag, Cpu } from 'lucide-react';
|
||||
import { Tag, Globe, Activity } from 'lucide-react';
|
||||
|
||||
import {
|
||||
isoDateFromTimestamp,
|
||||
humanize,
|
||||
stripProtocol,
|
||||
} from '@/portainer/filters/filters';
|
||||
import {
|
||||
|
@ -12,24 +10,22 @@ import {
|
|||
PlatformType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import {
|
||||
getDashboardRoute,
|
||||
getPlatformType,
|
||||
isDockerEnvironment,
|
||||
isEdgeEnvironment,
|
||||
} from '@/react/portainer/environments/utils';
|
||||
import type { TagId } from '@/portainer/tags/types';
|
||||
import { useTags } from '@/portainer/tags/queries';
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
import Memory from '@/assets/ico/memory.svg?c';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Link } from '@@/Link';
|
||||
import { Button } from '@@/buttons';
|
||||
import { EdgeIndicator } from '@@/EdgeIndicator';
|
||||
import { EnvironmentStatusBadge } from '@@/EnvironmentStatusBadge';
|
||||
import { Link } from '@@/Link';
|
||||
|
||||
import { EnvironmentIcon } from './EnvironmentIcon';
|
||||
import { EnvironmentStats } from './EnvironmentStats';
|
||||
import styles from './EnvironmentItem.module.css';
|
||||
import { EnvironmentStatusBadge } from './EnvironmentStatusBadge';
|
||||
import { EngineVersion } from './EngineVersion';
|
||||
import { AgentVersionTag } from './AgentVersionTag';
|
||||
import { EditButtons } from './EditButtons';
|
||||
|
||||
interface Props {
|
||||
environment: Environment;
|
||||
|
@ -38,118 +34,102 @@ interface Props {
|
|||
}
|
||||
|
||||
export function EnvironmentItem({ environment, onClick, groupName }: Props) {
|
||||
const { isAdmin } = useUser();
|
||||
const isEdge = isEdgeEnvironment(environment.Type);
|
||||
|
||||
const snapshotTime = getSnapshotTime(environment);
|
||||
|
||||
const tags = useEnvironmentTagNames(environment.TagIds);
|
||||
const route = getRoute(environment);
|
||||
const route = getDashboardRoute(environment);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onClick(environment)}
|
||||
className={styles.wrapperButton}
|
||||
className="bg-transparent border-0 !p-0 !m-0"
|
||||
>
|
||||
<Link
|
||||
className={clsx('blocklist-item no-link', styles.item)}
|
||||
className="blocklist-item flex no-link overflow-hidden min-h-[100px]"
|
||||
to={route}
|
||||
params={{
|
||||
endpointId: environment.Id,
|
||||
id: environment.Id,
|
||||
}}
|
||||
>
|
||||
<div className="blocklist-item-box">
|
||||
<span className={clsx('blocklist-item-logo', 'endpoint-item')}>
|
||||
<div className="ml-2 self-center flex justify-center">
|
||||
<EnvironmentIcon type={environment.Type} />
|
||||
</span>
|
||||
<span className="col-sm-12">
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span>
|
||||
<span className="blocklist-item-title endpoint-item">
|
||||
{environment.Name}
|
||||
</span>
|
||||
<span className="space-left blocklist-item-subtitle">
|
||||
</div>
|
||||
<div className="ml-3 mr-auto flex justify-center gap-3 flex-col items-start">
|
||||
<div className="space-x-3 flex items-center">
|
||||
<span className="font-bold">{environment.Name}</span>
|
||||
|
||||
{isEdge ? (
|
||||
<EdgeIndicator
|
||||
environment={environment}
|
||||
showLastCheckInDate
|
||||
/>
|
||||
<EdgeIndicator environment={environment} showLastCheckInDate />
|
||||
) : (
|
||||
<>
|
||||
<EnvironmentStatusBadge status={environment.Status} />
|
||||
<span className="space-left small text-muted">
|
||||
{snapshotTime && (
|
||||
<span
|
||||
className="space-left small text-muted vertical-center"
|
||||
title="Last snapshot time"
|
||||
>
|
||||
<Activity
|
||||
className="icon icon-sm space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{snapshotTime}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
{groupName && (
|
||||
<span className="small space-right">
|
||||
<span>Group: </span>
|
||||
<span>{groupName}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EnvironmentStats environment={environment} />
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="small text-muted space-x-2">
|
||||
{isDockerEnvironment(environment.Type) && (
|
||||
<span>
|
||||
{environment.Snapshots.length > 0 && (
|
||||
<span className="small text-muted vertical-center">
|
||||
<Cpu
|
||||
className="icon icon-sm space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{environment.Snapshots[0].TotalCPU} CPU
|
||||
<Icon
|
||||
icon={Memory}
|
||||
className="icon icon-sm space-right"
|
||||
/>
|
||||
{humanize(environment.Snapshots[0].TotalMemory)} RAM
|
||||
<Cpu
|
||||
className="icon icon-sm space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{environment.Gpus?.length} GPU
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<span className="vertical-center">
|
||||
<Tag
|
||||
className="icon icon-sm space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{tags}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<EngineVersion environment={environment} />
|
||||
|
||||
{!isEdge && (
|
||||
<span className="small text-muted">
|
||||
<span className="text-muted small vertical-center">
|
||||
{stripProtocol(environment.URL)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="small text-muted space-x-2 vertical-center">
|
||||
{groupName && (
|
||||
<span className="font-semibold">
|
||||
<span>Group: </span>
|
||||
<span>{groupName}</span>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</button>
|
||||
{isAdmin && (
|
||||
<Link
|
||||
to="portainer.endpoints.endpoint"
|
||||
params={{ id: environment.Id }}
|
||||
className={styles.editButton}
|
||||
>
|
||||
<Button color="link">
|
||||
<Edit2 className="icon icon-md" aria-hidden="true" />
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<span className="vertical-center">
|
||||
<Tag className="icon icon-sm space-right" aria-hidden="true" />
|
||||
{tags}
|
||||
</span>
|
||||
|
||||
{isEdge && (
|
||||
<>
|
||||
<AgentVersionTag
|
||||
type={environment.Type}
|
||||
version={environment.Agent.Version}
|
||||
/>
|
||||
|
||||
{environment.Edge.AsyncMode && (
|
||||
<span className="vertical-center gap-1">
|
||||
<Globe
|
||||
className="icon icon-sm space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Async Environment
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<EnvironmentStats environment={environment} />
|
||||
</div>
|
||||
|
||||
<EditButtons environment={environment} />
|
||||
</Link>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -193,22 +173,3 @@ function getSnapshotTime(environment: Environment) {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getRoute(environment: Environment) {
|
||||
if (isEdgeEnvironment(environment.Type) && !environment.EdgeID) {
|
||||
return 'portainer.endpoints.endpoint';
|
||||
}
|
||||
|
||||
const platform = getPlatformType(environment.Type);
|
||||
|
||||
switch (platform) {
|
||||
case PlatformType.Azure:
|
||||
return 'azure.dashboard';
|
||||
case PlatformType.Docker:
|
||||
return 'docker.dashboard';
|
||||
case PlatformType.Kubernetes:
|
||||
return 'kubernetes.dashboard';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { getPlatformType } from '@/react/portainer/environments/utils';
|
|||
|
||||
import { EnvironmentStatsDocker } from './EnvironmentStatsDocker';
|
||||
import { EnvironmentStatsKubernetes } from './EnvironmentStatsKubernetes';
|
||||
import { EnvironmentStatsNomad } from './EnvironmentStatsNomad';
|
||||
|
||||
interface Props {
|
||||
environment: Environment;
|
||||
|
@ -13,28 +14,31 @@ interface Props {
|
|||
|
||||
export function EnvironmentStats({ environment }: Props) {
|
||||
const platform = getPlatformType(environment.Type);
|
||||
|
||||
const component = getComponent(platform, environment);
|
||||
|
||||
return (
|
||||
<span className="blocklist-item-desc flex items-center gap-10">
|
||||
{component}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function getComponent(platform: PlatformType, environment: Environment) {
|
||||
switch (platform) {
|
||||
case PlatformType.Kubernetes:
|
||||
return (
|
||||
<EnvironmentStatsKubernetes
|
||||
snapshots={environment.Kubernetes.Snapshots || []}
|
||||
type={environment.Type}
|
||||
agentVersion={environment.Agent.Version}
|
||||
snapshot={environment.Kubernetes.Snapshots?.[0]}
|
||||
/>
|
||||
);
|
||||
case PlatformType.Docker:
|
||||
return <EnvironmentStatsDocker snapshot={environment.Snapshots?.[0]} />;
|
||||
case PlatformType.Nomad:
|
||||
return (
|
||||
<EnvironmentStatsDocker
|
||||
snapshots={environment.Snapshots}
|
||||
type={environment.Type}
|
||||
agentVersion={environment.Agent.Version}
|
||||
/>
|
||||
<EnvironmentStatsNomad snapshot={environment.Nomad.Snapshots?.[0]} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="blocklist-item-desc">-</span>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,46 +9,29 @@ import {
|
|||
Heart,
|
||||
} from 'lucide-react';
|
||||
|
||||
import {
|
||||
DockerSnapshot,
|
||||
EnvironmentType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { addPlural } from '@/portainer/helpers/strings';
|
||||
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
||||
|
||||
import { AgentVersionTag } from './AgentVersionTag';
|
||||
import { EnvironmentStatsItem } from './EnvironmentStatsItem';
|
||||
import { StatsItem } from '@@/StatsItem';
|
||||
|
||||
interface Props {
|
||||
snapshots: DockerSnapshot[];
|
||||
type: EnvironmentType;
|
||||
agentVersion: string;
|
||||
snapshot?: DockerSnapshot;
|
||||
}
|
||||
|
||||
export function EnvironmentStatsDocker({
|
||||
snapshots = [],
|
||||
type,
|
||||
agentVersion,
|
||||
}: Props) {
|
||||
if (snapshots.length === 0) {
|
||||
return (
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="blocklist-item-desc">No snapshot available</span>
|
||||
</div>
|
||||
);
|
||||
export function EnvironmentStatsDocker({ snapshot }: Props) {
|
||||
if (!snapshot) {
|
||||
return <>No snapshot available</>;
|
||||
}
|
||||
|
||||
const snapshot = snapshots[0];
|
||||
|
||||
return (
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="blocklist-item-desc">
|
||||
<EnvironmentStatsItem
|
||||
<>
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.StackCount, 'stack')}
|
||||
icon={Layers}
|
||||
/>
|
||||
|
||||
{!!snapshot.Swarm && (
|
||||
<EnvironmentStatsItem
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.ServiceCount, 'service')}
|
||||
icon={Shuffle}
|
||||
/>
|
||||
|
@ -60,29 +43,19 @@ export function EnvironmentStatsDocker({
|
|||
healthy={snapshot.HealthyContainerCount}
|
||||
unhealthy={snapshot.UnhealthyContainerCount}
|
||||
/>
|
||||
<EnvironmentStatsItem
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.VolumeCount, 'volume')}
|
||||
icon={Database}
|
||||
/>
|
||||
<EnvironmentStatsItem
|
||||
value={addPlural(snapshot.ImageCount, 'image')}
|
||||
icon={List}
|
||||
/>
|
||||
</span>
|
||||
<StatsItem value={addPlural(snapshot.ImageCount, 'image')} icon={List} />
|
||||
|
||||
<span className="small text-muted space-x-2 vertical-center">
|
||||
<span>
|
||||
{snapshot.Swarm ? 'Swarm' : 'Standalone'} {snapshot.DockerVersion}
|
||||
</span>
|
||||
{snapshot.Swarm && (
|
||||
<EnvironmentStatsItem
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.NodeCount, 'node')}
|
||||
icon={HardDrive}
|
||||
/>
|
||||
)}
|
||||
<AgentVersionTag version={agentVersion} type={type} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -102,34 +75,15 @@ function ContainerStats({
|
|||
const containersCount = running + stopped;
|
||||
|
||||
return (
|
||||
<EnvironmentStatsItem
|
||||
value={addPlural(containersCount, 'container')}
|
||||
icon={Box}
|
||||
>
|
||||
<StatsItem value={addPlural(containersCount, 'container')} icon={Box}>
|
||||
{containersCount > 0 && (
|
||||
<span className="space-x-2 space-right">
|
||||
<EnvironmentStatsItem
|
||||
value={running}
|
||||
icon={Power}
|
||||
iconClass="icon-success"
|
||||
/>
|
||||
<EnvironmentStatsItem
|
||||
value={stopped}
|
||||
icon={Power}
|
||||
iconClass="icon-danger"
|
||||
/>
|
||||
<EnvironmentStatsItem
|
||||
value={healthy}
|
||||
icon={Heart}
|
||||
iconClass="icon-success"
|
||||
/>
|
||||
<EnvironmentStatsItem
|
||||
value={unhealthy}
|
||||
icon={Heart}
|
||||
iconClass="icon-warning"
|
||||
/>
|
||||
</span>
|
||||
<>
|
||||
<StatsItem value={running} icon={Power} iconClass="icon-success" />
|
||||
<StatsItem value={stopped} icon={Power} iconClass="icon-danger" />
|
||||
<StatsItem value={healthy} icon={Heart} iconClass="icon-success" />
|
||||
<StatsItem value={unhealthy} icon={Heart} iconClass="icon-warning" />
|
||||
</>
|
||||
)}
|
||||
</EnvironmentStatsItem>
|
||||
</StatsItem>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,56 +1,34 @@
|
|||
import { Cpu, HardDrive } from 'lucide-react';
|
||||
|
||||
import {
|
||||
EnvironmentType,
|
||||
KubernetesSnapshot,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import { KubernetesSnapshot } from '@/react/portainer/environments/types';
|
||||
import { humanize } from '@/portainer/filters/filters';
|
||||
import { addPlural } from '@/portainer/helpers/strings';
|
||||
import Memory from '@/assets/ico/memory.svg?c';
|
||||
|
||||
import { AgentVersionTag } from './AgentVersionTag';
|
||||
import { EnvironmentStatsItem } from './EnvironmentStatsItem';
|
||||
import { StatsItem } from '@@/StatsItem';
|
||||
|
||||
interface Props {
|
||||
snapshots?: KubernetesSnapshot[];
|
||||
type: EnvironmentType;
|
||||
agentVersion: string;
|
||||
snapshot?: KubernetesSnapshot;
|
||||
}
|
||||
|
||||
export function EnvironmentStatsKubernetes({
|
||||
snapshots = [],
|
||||
type,
|
||||
agentVersion,
|
||||
}: Props) {
|
||||
if (snapshots.length === 0) {
|
||||
return (
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="blocklist-item-desc">No snapshot available</span>
|
||||
</div>
|
||||
);
|
||||
export function EnvironmentStatsKubernetes({ snapshot }: Props) {
|
||||
if (!snapshot) {
|
||||
return <>No snapshot available</>;
|
||||
}
|
||||
|
||||
const snapshot = snapshots[0];
|
||||
|
||||
return (
|
||||
<div className="blocklist-item-line endpoint-item">
|
||||
<span className="blocklist-item-desc space-x-1">
|
||||
<EnvironmentStatsItem icon={Cpu} value={`${snapshot.TotalCPU} CPU`} />
|
||||
<>
|
||||
<StatsItem icon={Cpu} value={`${snapshot.TotalCPU} CPU`} />
|
||||
|
||||
<EnvironmentStatsItem
|
||||
<StatsItem
|
||||
icon={Memory}
|
||||
value={`${humanize(snapshot.TotalMemory)} RAM`}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span className="small text-muted space-x-2 vertical-center">
|
||||
<span>Kubernetes {snapshot.KubernetesVersion}</span>
|
||||
<EnvironmentStatsItem
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.NodeCount, 'node')}
|
||||
icon={HardDrive}
|
||||
/>
|
||||
<AgentVersionTag type={type} version={agentVersion} />
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { Box, Dice4, HardDrive, List, Power } from 'lucide-react';
|
||||
|
||||
import { NomadSnapshot } from '@/react/portainer/environments/types';
|
||||
import { addPlural } from '@/portainer/helpers/strings';
|
||||
|
||||
import { StatsItem } from '@@/StatsItem';
|
||||
|
||||
interface Props {
|
||||
snapshot?: NomadSnapshot;
|
||||
}
|
||||
|
||||
export function EnvironmentStatsNomad({ snapshot }: Props) {
|
||||
if (!snapshot) {
|
||||
return <>No snapshot available</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatsItem value={addPlural(snapshot.JobCount, 'job')} icon={List} />
|
||||
<StatsItem value={addPlural(snapshot.GroupCount, 'group')} icon={Dice4} />
|
||||
<StatsItem value={addPlural(snapshot.TaskCount, 'task')} icon={Box}>
|
||||
{snapshot.TaskCount > 0 && (
|
||||
<>
|
||||
<StatsItem
|
||||
value={snapshot.RunningTaskCount}
|
||||
icon={Power}
|
||||
iconClass="icon-success"
|
||||
/>
|
||||
<StatsItem
|
||||
value={snapshot.TaskCount - snapshot.RunningTaskCount}
|
||||
icon={Power}
|
||||
iconClass="icon-danger"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StatsItem>
|
||||
|
||||
<StatsItem
|
||||
value={addPlural(snapshot.NodeCount, 'node')}
|
||||
icon={HardDrive}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { EnvironmentStatus } from '@/react/portainer/environments/types';
|
||||
|
||||
interface Props {
|
||||
status: EnvironmentStatus;
|
||||
}
|
||||
|
||||
export function EnvironmentStatusBadge({ status }: Props) {
|
||||
return (
|
||||
<span className={clsx('label', `label-${environmentStatusBadge(status)}`)}>
|
||||
{status === EnvironmentStatus.Up ? 'up' : 'down'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function environmentStatusBadge(status: EnvironmentStatus) {
|
||||
if (status === EnvironmentStatus.Down) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
}
|
|
@ -69,7 +69,6 @@ const storageKey = 'home_endpoints';
|
|||
|
||||
export function EnvironmentList({ onClickItem, onRefresh }: Props) {
|
||||
const { isAdmin } = useUser();
|
||||
|
||||
const [platformTypes, setPlatformTypes] = useHomePageFilter<
|
||||
Filter<PlatformType>[]
|
||||
>('platformType', []);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TeamId } from '@/react/portainer/users/teams/types';
|
||||
import { UserId } from '@/portainer/users/types';
|
||||
import { UserId } from '@/portainer/users/types/user-id';
|
||||
|
||||
export type ResourceControlId = number;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { TagId } from '@/portainer/tags/types';
|
||||
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
|
||||
import { Job } from '@/react/nomad/types';
|
||||
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
||||
|
||||
export type EnvironmentId = number;
|
||||
|
||||
|
@ -25,30 +27,14 @@ export enum EnvironmentType {
|
|||
export const EdgeTypes = [
|
||||
EnvironmentType.EdgeAgentOnDocker,
|
||||
EnvironmentType.EdgeAgentOnKubernetes,
|
||||
EnvironmentType.EdgeAgentOnNomad,
|
||||
] as const;
|
||||
|
||||
export enum EnvironmentStatus {
|
||||
Up = 1,
|
||||
Down,
|
||||
}
|
||||
|
||||
export interface DockerSnapshot {
|
||||
TotalCPU: number;
|
||||
TotalMemory: number;
|
||||
NodeCount: number;
|
||||
ImageCount: number;
|
||||
VolumeCount: number;
|
||||
RunningContainerCount: number;
|
||||
StoppedContainerCount: number;
|
||||
HealthyContainerCount: number;
|
||||
UnhealthyContainerCount: number;
|
||||
Time: number;
|
||||
StackCount: number;
|
||||
ServiceCount: number;
|
||||
Swarm: boolean;
|
||||
DockerVersion: string;
|
||||
GpuUseAll: boolean;
|
||||
GpuUseList: string[];
|
||||
Provisioning,
|
||||
Error,
|
||||
}
|
||||
|
||||
export interface KubernetesSnapshot {
|
||||
|
@ -80,6 +66,20 @@ export interface KubernetesSettings {
|
|||
Configuration: KubernetesConfiguration;
|
||||
}
|
||||
|
||||
export interface NomadSnapshot {
|
||||
JobCount: number;
|
||||
GroupCount: number;
|
||||
TaskCount: number;
|
||||
RunningTaskCount: number;
|
||||
NodeCount: number;
|
||||
Time: number;
|
||||
Jobs: Job[];
|
||||
}
|
||||
|
||||
export interface NomadSettings {
|
||||
Snapshots: NomadSnapshot[];
|
||||
}
|
||||
|
||||
export type EnvironmentEdge = {
|
||||
AsyncMode: boolean;
|
||||
PingInterval: number;
|
||||
|
@ -124,6 +124,7 @@ export type Environment = {
|
|||
URL: string;
|
||||
Snapshots: DockerSnapshot[];
|
||||
Kubernetes: KubernetesSettings;
|
||||
Nomad: NomadSettings;
|
||||
PublicURL?: string;
|
||||
IsEdgeDevice?: boolean;
|
||||
UserTrusted: boolean;
|
||||
|
|
|
@ -51,6 +51,10 @@ export function isEdgeEnvironment(envType: EnvironmentType) {
|
|||
].includes(envType);
|
||||
}
|
||||
|
||||
export function isEdgeAsync(env?: Environment | null) {
|
||||
return !!env && env.Edge.AsyncMode;
|
||||
}
|
||||
|
||||
export function isUnassociatedEdgeEnvironment(env: Environment) {
|
||||
return isEdgeEnvironment(env.Type) && !env.EdgeID;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue