Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c6e1839fe |
@@ -124,24 +124,13 @@ Always check `@gpustack/core-ui` first. Frequently reused:
|
|||||||
- **Form fields**: `BaseSelect`, `Input` (labeled).
|
- **Form fields**: `BaseSelect`, `Input` (labeled).
|
||||||
- **Text overflow**: `AutoTooltip`.
|
- **Text overflow**: `AutoTooltip`.
|
||||||
- **Icons**: `IconFont`.
|
- **Icons**: `IconFont`.
|
||||||
- **Tags & status** (4 variants): see the section below.
|
- **Status display** (success/failed/processing/warning): `StatusTag`.
|
||||||
- **Permission-gated visibility**: `Access` / `useAccess`.
|
- **Permission-gated visibility**: `Access` / `useAccess`.
|
||||||
- **Request hooks**: `useRequest` / `useQueryData` / `useQueryDataList`.
|
- **Request hooks**: `useRequest` / `useQueryData` / `useQueryDataList`.
|
||||||
- **Table data fetching**: `useTableFetch`.
|
- **Table data fetching**: `useTableFetch`.
|
||||||
- **Submit guard** (prevent double-submit): `useSubmitLock`.
|
- **Submit guard** (prevent double-submit): `useSubmitLock`.
|
||||||
- **Tabbed forms**: `ScrollSpyTabs`.
|
- **Tabbed forms**: `ScrollSpyTabs`.
|
||||||
|
|
||||||
# Tags & status indicators
|
|
||||||
|
|
||||||
Four core-ui components cover tag/status display in tables and lists. Pick by **what the value means**, not by how it looks — don't reach for a generic antd `Tag`:
|
|
||||||
|
|
||||||
- **`StatusTag`** — semantic status with a **dynamic message/detail** (tooltip, download, extra content). Use when a row's status carries variable text, e.g. a failed job with an error message. Colors come from `StatusColorMap` (error/warning/transitioning/success/inactive).
|
|
||||||
- **`StatusDot`** — colored dot + short label, **no message**. Use for a plain status/type cell where the value is a fixed enum (e.g. an event-type or log column). Same `StatusColorMap` palette; `inactive` dot is quaternary. If the status needs dynamic text, use `StatusTag` instead.
|
|
||||||
- **`ThemeTag`** — a **standalone category label** (independent content, e.g. a permission scope or a model name). Default neutral; wraps antd `Tag`.
|
|
||||||
- **`TextAttribute`** — a small neutral pill that is a **subordinate annotation following a primary text** (e.g. `key-name [custom]`), not a standalone tag. Manages its own leading margin. Two variants: `filled` (default) and `outlined`. Ref the name column in `src/pages/api-keys/hooks/use-keys-columns.tsx`.
|
|
||||||
|
|
||||||
Rule of thumb: semantic + dynamic text → `StatusTag`; semantic + fixed enum → `StatusDot`; independent category → `ThemeTag`; annotation of nearby text → `TextAttribute`.
|
|
||||||
|
|
||||||
# Dynamic add-item form fields
|
# Dynamic add-item form fields
|
||||||
|
|
||||||
When building a form, select the add-item component from the **shape of the field's data** (its schema). Match the schema, don't hand-roll a list UI:
|
When building a form, select the add-item component from the **shape of the field's data** (its schema). Match the schema, don't hand-roll a list UI:
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ const ModelTag: React.FC<ModelTagProps> = ({ categoryKey, size }) => {
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
paddingInline: 8,
|
paddingInline: 8,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
transform: 'scale(0.9)',
|
transform: 'scale(0.9)'
|
||||||
backgroundColor: 'transparent'
|
|
||||||
}}
|
}}
|
||||||
color={config.color}
|
color={config.color}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import {
|
|||||||
AutoTooltip,
|
AutoTooltip,
|
||||||
DropdownButtons,
|
DropdownButtons,
|
||||||
IconFont,
|
IconFont,
|
||||||
icons,
|
icons
|
||||||
TextAttribute,
|
|
||||||
ThemeTag
|
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { MenuProps, Tooltip } from 'antd';
|
import { MenuProps, Tag, Tooltip } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -141,14 +139,23 @@ const useModelsColumns = ({
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<span className="flex items-center gap-8">
|
<span className="flex items-center">
|
||||||
<AutoTooltip ghost style={{ maxWidth: 400 }} title={text}>
|
<AutoTooltip ghost style={{ maxWidth: 400 }} title={text}>
|
||||||
<span className="text-primary">{text}</span>
|
<span className="text-primary">{text}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
{record.is_custom && (
|
{record.is_custom && (
|
||||||
<TextAttribute>
|
<Tag
|
||||||
|
style={{
|
||||||
|
marginLeft: 8,
|
||||||
|
borderRadius: 12,
|
||||||
|
color: 'var(--ant-color-text-tertiary)',
|
||||||
|
borderColor: 'var(--ant-color-split)',
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
{intl.formatMessage({ id: 'playground.params.custom' })}
|
{intl.formatMessage({ id: 'playground.params.custom' })}
|
||||||
</TextAttribute>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -196,13 +203,26 @@ const useModelsColumns = ({
|
|||||||
)}
|
)}
|
||||||
{(record.scope?.includes('inference') ||
|
{(record.scope?.includes('inference') ||
|
||||||
record.scope?.includes('*')) && (
|
record.scope?.includes('*')) && (
|
||||||
<ThemeTag>
|
<div
|
||||||
|
style={{
|
||||||
|
border: '1px solid var(--ant-color-split)',
|
||||||
|
color: 'var(--ant-color-text-tertiary)',
|
||||||
|
backgroundColor: 'var(--ant-color-fill-quaternary)',
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 13,
|
||||||
|
paddingInline: 8,
|
||||||
|
flexGrow: 0,
|
||||||
|
maxWidth: '100%',
|
||||||
|
width: 'max-content',
|
||||||
|
display: 'flex'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
{record.allowed_model_names?.length
|
{record.allowed_model_names?.length
|
||||||
? record.allowed_model_names.join(', ')
|
? record.allowed_model_names.join(', ')
|
||||||
: intl.formatMessage({ id: 'apikeys.models.all' })}
|
: intl.formatMessage({ id: 'apikeys.models.all' })}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
</ThemeTag>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,13 +28,21 @@ interface ColumnsHookProps {
|
|||||||
const getKindLabel = (record: ListItem) => {
|
const getKindLabel = (record: ListItem) => {
|
||||||
if (record.spec?.nfs)
|
if (record.spec?.nfs)
|
||||||
return (
|
return (
|
||||||
<ThemeTag color="cyan" icon={<FolderOutlined />}>
|
<ThemeTag
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
color="cyan"
|
||||||
|
icon={<FolderOutlined />}
|
||||||
|
>
|
||||||
{StorageTypeKindLabelMap.nfs}
|
{StorageTypeKindLabelMap.nfs}
|
||||||
</ThemeTag>
|
</ThemeTag>
|
||||||
);
|
);
|
||||||
if (record.spec?.s3)
|
if (record.spec?.s3)
|
||||||
return (
|
return (
|
||||||
<ThemeTag color="green" icon={<IconFont type="icon-database" />}>
|
<ThemeTag
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
color="green"
|
||||||
|
icon={<IconFont type="icon-database" />}
|
||||||
|
>
|
||||||
{StorageTypeKindLabelMap.s3}
|
{StorageTypeKindLabelMap.s3}
|
||||||
</ThemeTag>
|
</ThemeTag>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ const useModelsColumns = ({
|
|||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
sorter: tableSorter(5),
|
sorter: tableSorter(5),
|
||||||
width: 180,
|
span: spans.createTime,
|
||||||
render: (text: number) => (
|
render: (text: number) => (
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
|||||||
@@ -8,11 +8,10 @@
|
|||||||
* tab (date range + "filter by user" for managers); the resource-type /
|
* tab (date range + "filter by user" for managers); the resource-type /
|
||||||
* event-type selects ride in the bar's ``extra`` slot.
|
* event-type selects ride in the bar's ``extra`` slot.
|
||||||
*/
|
*/
|
||||||
import { type StatusType } from '@/config/types';
|
import { SimpleSelect } from '@gpustack/core-ui';
|
||||||
import { SimpleSelect, StatusDot } from '@gpustack/core-ui';
|
|
||||||
import { useAccess, useIntl } from '@umijs/max';
|
import { useAccess, useIntl } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Input, Select, Table } from 'antd';
|
import { Input, Select, Table, Tag } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@@ -24,11 +23,11 @@ import useQueryResourceEvents from './services/use-query-resource-events';
|
|||||||
// Only these four are ever emitted (see resource_event_logger): create/delete
|
// Only these four are ever emitted (see resource_event_logger): create/delete
|
||||||
// + the metering-window pair. updated/attached/detached exist as enum values
|
// + the metering-window pair. updated/attached/detached exist as enum values
|
||||||
// but are intentionally not recorded, so they're not offered as filters.
|
// but are intentionally not recorded, so they're not offered as filters.
|
||||||
const EVENT_STATUS: Record<string, StatusType> = {
|
const EVENT_COLOR: Record<string, string> = {
|
||||||
created: 'success',
|
created: 'green',
|
||||||
deleted: 'error',
|
deleted: 'red',
|
||||||
phase_to_metered: 'transitioning',
|
phase_to_metered: 'blue',
|
||||||
phase_left_metered: 'warning'
|
phase_left_metered: 'orange'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Humanize a failure phase enum for display, e.g. "SSHPublicKeyCreateFailed" →
|
// Humanize a failure phase enum for display, e.g. "SSHPublicKeyCreateFailed" →
|
||||||
@@ -187,12 +186,7 @@ const ResourceEvents: React.FC = () => {
|
|||||||
dataIndex: 'event_type',
|
dataIndex: 'event_type',
|
||||||
key: 'event_type',
|
key: 'event_type',
|
||||||
render: (v: string) => (
|
render: (v: string) => (
|
||||||
<StatusDot
|
<Tag color={EVENT_COLOR[v] ?? 'default'}>{EVENT_LABEL[v] ?? v}</Tag>
|
||||||
statusValue={{
|
|
||||||
status: EVENT_STATUS[v] ?? 'inactive',
|
|
||||||
text: EVENT_LABEL[v] ?? v
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
width: 180
|
width: 180
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user