Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions react/src/components/DeploymentOwnerInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
@license
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import { DeploymentOwnerInfo_deployment$key } from '../__generated__/DeploymentOwnerInfo_deployment.graphql';
import { UserOutlined } from '@ant-design/icons';
import { Avatar, Tooltip, Typography, theme } from 'antd';
import { BAIFlex } from 'backend.ai-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { graphql, useFragment } from 'react-relay';

interface DeploymentOwnerInfoProps {
deploymentFrgmt: DeploymentOwnerInfo_deployment$key | null | undefined;
}

const DeploymentOwnerInfo: React.FC<DeploymentOwnerInfoProps> = ({
deploymentFrgmt,
}) => {
'use memo';
const { t } = useTranslation();
const { token } = theme.useToken();

const deployment = useFragment(
graphql`
fragment DeploymentOwnerInfo_deployment on ModelDeployment {
id
creator @since(version: "26.4.3") {
id
basicInfo {
email
username
fullName
}
}
}
`,
deploymentFrgmt,
);

const email = deployment?.creator?.basicInfo?.email ?? '';
const fullName = deployment?.creator?.basicInfo?.fullName ?? '';
const username = deployment?.creator?.basicInfo?.username ?? '';

if (!email) {
return <Typography.Text type="secondary">-</Typography.Text>;
}

const initial = (fullName || username || email)
.trim()
.charAt(0)
.toUpperCase();
const tooltipLines = [
t('deployment.CreatedBy'),
fullName || username || email,
email,
]
.filter(Boolean)
.join('\n');

return (
<BAIFlex gap="xs" align="center">
<Tooltip
title={<span style={{ whiteSpace: 'pre-line' }}>{tooltipLines}</span>}
>
<Avatar
size="small"
style={{
backgroundColor: token.colorFillSecondary,
color: token.colorText,
flexShrink: 0,
}}
icon={!initial ? <UserOutlined /> : undefined}
>
{initial || null}
</Avatar>
</Tooltip>
<Typography.Text ellipsis={{ tooltip: email }} style={{ maxWidth: 200 }}>
{email}
</Typography.Text>
</BAIFlex>
);
};

export default DeploymentOwnerInfo;
86 changes: 86 additions & 0 deletions react/src/components/DeploymentStatusTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
@license
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import type { TagProps } from 'antd';
import { BAITag, type SemanticColor } from 'backend.ai-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';

export type DeploymentStatus =
| 'HEALTHY'
| 'UNHEALTHY'
| 'DEGRADED'
| 'NOT_CHECKED'
| 'DEPLOYING'
| 'SCALING'
| 'STOPPED'
| 'STOPPING'
| 'TERMINATED'
| 'PENDING'
| 'READY';

/**
* Maps each deployment status to a Tag color.
*
* - success: HEALTHY, READY — fully operational.
* - processing: DEPLOYING, SCALING, PENDING — transient, in-flight states.
* - warning: DEGRADED, UNHEALTHY, STOPPING — attention needed or transitioning away.
* - default: NOT_CHECKED, STOPPED, TERMINATED — neutral / inactive.
*/
const deploymentStatusColorMap: Record<DeploymentStatus, SemanticColor> = {
HEALTHY: 'success',
READY: 'success',
DEPLOYING: 'info',
SCALING: 'info',
PENDING: 'info',
DEGRADED: 'warning',
UNHEALTHY: 'warning',
STOPPING: 'warning',
NOT_CHECKED: 'default',
STOPPED: 'default',
TERMINATED: 'default',
};

const deploymentStatusI18nMap: Record<DeploymentStatus, string> = {
HEALTHY: 'deployment.status.Healthy',
UNHEALTHY: 'deployment.status.Unhealthy',
DEGRADED: 'deployment.status.Degraded',
NOT_CHECKED: 'deployment.status.NotChecked',
DEPLOYING: 'deployment.status.Deploying',
SCALING: 'deployment.status.Scaling',
STOPPED: 'deployment.status.Stopped',
STOPPING: 'deployment.status.Stopping',
TERMINATED: 'deployment.status.Terminated',
PENDING: 'deployment.status.Pending',
READY: 'deployment.status.Ready',
};

export interface DeploymentStatusTagProps extends Omit<TagProps, 'color'> {
/**
* The deployment-level status to display. Consolidates lifecycle (e.g.
* `DEPLOYING`, `STOPPED`, `TERMINATED`) and health (e.g. `HEALTHY`,
* `UNHEALTHY`, `DEGRADED`) into a single tag.
*/
status: DeploymentStatus;
}

/**
* DeploymentStatusTag — consolidated lifecycle + health status tag for a
* deployment. Used in list rows and detail page headers.
*/
const DeploymentStatusTag: React.FC<DeploymentStatusTagProps> = ({
status,
...tagProps
}) => {
'use memo';
const { t } = useTranslation();

return (
<BAITag {...tagProps} color={deploymentStatusColorMap[status]}>
{t(deploymentStatusI18nMap[status])}
</BAITag>
);
};

export default DeploymentStatusTag;
86 changes: 86 additions & 0 deletions react/src/components/ReplicaStatusTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
@license
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
*/
import { Tooltip } from 'antd';
import type { TagProps } from 'antd';
import { BAITag, type SemanticColor } from 'backend.ai-ui';
import React from 'react';
import { useTranslation } from 'react-i18next';

export type ReplicaStatus =
| 'HEALTHY'
| 'UNHEALTHY'
| 'DEGRADED'
| 'NOT_CHECKED'
| 'PROVISIONING'
| 'TERMINATING'
| 'TERMINATED';

export interface ReplicaStatusTagProps extends Omit<TagProps, 'color'> {
/**
* Replica health/lifecycle state.
* Health states: `HEALTHY`, `UNHEALTHY`, `DEGRADED`, `NOT_CHECKED`.
* Lifecycle states: `PROVISIONING`, `TERMINATING`, `TERMINATED`.
*/
status: ReplicaStatus;
/**
* When true, wraps the tag in a tooltip explaining the state.
* @default true
*/
showTooltip?: boolean;
}

const replicaStatusColorMap: Record<ReplicaStatus, SemanticColor> = {
HEALTHY: 'success',
UNHEALTHY: 'error',
DEGRADED: 'warning',
NOT_CHECKED: 'default',
PROVISIONING: 'info',
TERMINATING: 'warning',
TERMINATED: 'default',
};

const replicaStatusI18nKey: Record<ReplicaStatus, string> = {
HEALTHY: 'Healthy',
UNHEALTHY: 'Unhealthy',
DEGRADED: 'Degraded',
NOT_CHECKED: 'NotChecked',
PROVISIONING: 'Provisioning',
TERMINATING: 'Terminating',
TERMINATED: 'Terminated',
};

const ReplicaStatusTag: React.FC<ReplicaStatusTagProps> = ({
status,
showTooltip = true,
...tagProps
}) => {
'use memo';
const { t } = useTranslation();

const color = replicaStatusColorMap[status];
const i18nKey = replicaStatusI18nKey[status];
const label = t(`replicaStatus.${i18nKey}`);
const tooltipTitle = showTooltip
? t(`replicaStatus.tooltip.${i18nKey}`, { defaultValue: '' })
: undefined;

const tag = (
<BAITag {...tagProps} color={color}>
{label}
</BAITag>
);

if (!showTooltip || !tooltipTitle) {
return tag;
}

return (
<Tooltip title={tooltipTitle}>
<span>{tag}</span>
</Tooltip>
);
};

export default ReplicaStatusTag;
Loading