style: model list buttons
This commit is contained in:
+1
-1
@@ -89,7 +89,7 @@ export default [
|
|||||||
name: 'apikeys',
|
name: 'apikeys',
|
||||||
path: '/api-keys',
|
path: '/api-keys',
|
||||||
key: 'apikeys',
|
key: 'apikeys',
|
||||||
icon: 'LockOutlined',
|
icon: 'KeyOutlined',
|
||||||
component: './api-keys'
|
component: './api-keys'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ interface DropdownButtonsProps {
|
|||||||
items: MenuProps['items'];
|
items: MenuProps['items'];
|
||||||
size?: 'small' | 'middle' | 'large';
|
size?: 'small' | 'middle' | 'large';
|
||||||
trigger?: Trigger[];
|
trigger?: Trigger[];
|
||||||
|
showText?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
variant?: 'filled' | 'outlined';
|
||||||
|
color?: string;
|
||||||
|
extra?: React.ReactNode;
|
||||||
onSelect: (val: any, item?: any) => void;
|
onSelect: (val: any, item?: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,9 +22,16 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
items,
|
items,
|
||||||
size = 'middle',
|
size = 'middle',
|
||||||
trigger = ['hover'],
|
trigger = ['hover'],
|
||||||
|
showText,
|
||||||
|
disabled,
|
||||||
|
variant,
|
||||||
|
color,
|
||||||
|
extra,
|
||||||
onSelect
|
onSelect
|
||||||
}) => {
|
}) => {
|
||||||
|
const headItem = _.head(items);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleMenuClick = (item: any) => {
|
const handleMenuClick = (item: any) => {
|
||||||
const selectItem = _.find(items, { key: item.key });
|
const selectItem = _.find(items, { key: item.key });
|
||||||
onSelect(item.key, selectItem);
|
onSelect(item.key, selectItem);
|
||||||
@@ -37,19 +49,20 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items?.length === 1 ? (
|
{items?.length === 1 ? (
|
||||||
<Tooltip title={intl.formatMessage({ id: _.head(items)?.label })}>
|
<Tooltip title={intl.formatMessage({ id: headItem?.label })}>
|
||||||
<Button
|
<Button
|
||||||
className={classNames('dropdown-button', size)}
|
className={classNames('dropdown-button', size)}
|
||||||
{..._.head(items)}
|
icon={headItem?.icon}
|
||||||
icon={_.get(items, '0.icon')}
|
|
||||||
size={size}
|
size={size}
|
||||||
{..._.get(items, '0.props')}
|
{...headItem?.props}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
></Button>
|
></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Dropdown.Button
|
<Dropdown.Button
|
||||||
|
disabled={disabled}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
|
type="primary"
|
||||||
dropdownRender={(menus: any) => {
|
dropdownRender={(menus: any) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -67,9 +80,10 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
{...item.props}
|
{...item.props}
|
||||||
type="text"
|
type="text"
|
||||||
size="middle"
|
size={size}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
key={item.key}
|
key={item.key}
|
||||||
|
disabled={item.disabled}
|
||||||
onClick={() => handleMenuClick(item)}
|
onClick={() => handleMenuClick(item)}
|
||||||
style={{ width: '100%', justifyContent: 'flex-start' }}
|
style={{ width: '100%', justifyContent: 'flex-start' }}
|
||||||
>
|
>
|
||||||
@@ -81,21 +95,45 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
buttonsRender={([leftButton, rightButton]) => [
|
buttonsRender={([leftButton, rightButton]) => [
|
||||||
<Tooltip
|
<>
|
||||||
title={intl.formatMessage({ id: _.head(items)?.label })}
|
{showText ? (
|
||||||
key="leftButton"
|
<Button
|
||||||
>
|
{...headItem?.props}
|
||||||
<Button
|
disabled={headItem?.disabled || disabled}
|
||||||
className={classNames('dropdown-button', size)}
|
className={classNames('dropdown-button', size)}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
size={size}
|
size={size}
|
||||||
icon={_.head(items)?.icon}
|
icon={headItem?.icon}
|
||||||
></Button>
|
variant={variant}
|
||||||
</Tooltip>,
|
color={color}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: headItem?.label
|
||||||
|
})}
|
||||||
|
{extra}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
title={intl.formatMessage({ id: headItem?.label })}
|
||||||
|
key="leftButton"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
{...headItem?.props}
|
||||||
|
className={classNames('dropdown-button', size)}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
size={size}
|
||||||
|
icon={headItem?.icon}
|
||||||
|
disabled={headItem?.disabled}
|
||||||
|
></Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</>,
|
||||||
<Button
|
<Button
|
||||||
icon={<MoreOutlined />}
|
icon={<MoreOutlined />}
|
||||||
size={size}
|
size={size}
|
||||||
key="menu"
|
key="menu"
|
||||||
|
variant={variant}
|
||||||
|
color="default"
|
||||||
className={classNames('dropdown-button', size)}
|
className={classNames('dropdown-button', size)}
|
||||||
></Button>
|
></Button>
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -3,15 +3,20 @@ import React from 'react';
|
|||||||
import styles from './styles/wrapper.less';
|
import styles from './styles/wrapper.less';
|
||||||
|
|
||||||
const Wrapper: React.FC<{
|
const Wrapper: React.FC<{
|
||||||
label?: string;
|
label?: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
|
labelExtra?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ children, label, description, ...rest }) => {
|
}> = ({ children, label, description, labelExtra, ...rest }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles['wrapper']}>
|
<div className={styles['wrapper']}>
|
||||||
{label && (
|
{label && (
|
||||||
<span className="label">
|
<span className="label">
|
||||||
<LabelInfo label={label} description={description}></LabelInfo>
|
<LabelInfo
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
labelExtra={labelExtra}
|
||||||
|
></LabelInfo>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{React.isValidElement(children)
|
{React.isValidElement(children)
|
||||||
|
|||||||
@@ -8,17 +8,26 @@ import ListItem from './list-item';
|
|||||||
|
|
||||||
interface ListInputProps {
|
interface ListInputProps {
|
||||||
dataList: string[];
|
dataList: string[];
|
||||||
label: string;
|
label: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
btnText?: string;
|
btnText?: string;
|
||||||
options?: Global.HintOptions[];
|
options?: Global.HintOptions[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
labelExtra?: React.ReactNode;
|
||||||
onChange: (data: string[]) => void;
|
onChange: (data: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListInput: React.FC<ListInputProps> = (props) => {
|
const ListInput: React.FC<ListInputProps> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { dataList, label, description, onChange, btnText, options } = props;
|
const {
|
||||||
|
dataList,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
onChange,
|
||||||
|
btnText,
|
||||||
|
options,
|
||||||
|
labelExtra
|
||||||
|
} = props;
|
||||||
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
|
||||||
const countRef = React.useRef(0);
|
const countRef = React.useRef(0);
|
||||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||||
@@ -71,7 +80,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
}, [dataList]);
|
}, [dataList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper label={label} description={description}>
|
<Wrapper label={label} description={description} labelExtra={labelExtra}>
|
||||||
<>
|
<>
|
||||||
{_.map(list, (item: any, index: number) => {
|
{_.map(list, (item: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ interface NoteInfoProps {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
|
labelExtra?: React.ReactNode;
|
||||||
}
|
}
|
||||||
const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
||||||
const { required, description, label } = props || {};
|
const { required, description, label, labelExtra } = props || {};
|
||||||
if (!label) return null;
|
if (!label) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,6 +44,7 @@ const NoteInfo: React.FC<NoteInfoProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{labelExtra}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface WrapperProps {
|
|||||||
variant?: string;
|
variant?: string;
|
||||||
hasPrefix?: boolean;
|
hasPrefix?: boolean;
|
||||||
classList?: string;
|
classList?: string;
|
||||||
|
labelExtra?: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ const Wrapper: React.FC<WrapperProps> = ({
|
|||||||
hasPrefix,
|
hasPrefix,
|
||||||
noWrapperStyle,
|
noWrapperStyle,
|
||||||
classList,
|
classList,
|
||||||
|
labelExtra,
|
||||||
onClick
|
onClick
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@@ -74,6 +76,7 @@ const Wrapper: React.FC<WrapperProps> = ({
|
|||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
description={description}
|
description={description}
|
||||||
|
labelExtra={labelExtra}
|
||||||
></LabelInfo>
|
></LabelInfo>
|
||||||
</label>
|
</label>
|
||||||
{extra && <div className={wrapperStyle.extra}>{extra}</div>}
|
{extra && <div className={wrapperStyle.extra}>{extra}</div>}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
|||||||
checkStatus,
|
checkStatus,
|
||||||
trim = true,
|
trim = true,
|
||||||
loading,
|
loading,
|
||||||
|
labelExtra,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const [isFocus, setIsFocus] = useState(false);
|
const [isFocus, setIsFocus] = useState(false);
|
||||||
@@ -74,6 +75,7 @@ const SealInput: React.FC<InputProps & SealFormItemProps> = (props) => {
|
|||||||
<Wrapper
|
<Wrapper
|
||||||
status={checkStatus || status}
|
status={checkStatus || status}
|
||||||
label={label}
|
label={label}
|
||||||
|
labelExtra={labelExtra}
|
||||||
isFocus={isFocus}
|
isFocus={isFocus}
|
||||||
required={required}
|
required={required}
|
||||||
description={description}
|
description={description}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface SealFormItemProps {
|
|||||||
addAfter?: React.ReactNode;
|
addAfter?: React.ReactNode;
|
||||||
allowNull?: boolean;
|
allowNull?: boolean;
|
||||||
loading?: React.ReactNode;
|
loading?: React.ReactNode;
|
||||||
|
labelExtra?: React.ReactNode;
|
||||||
trim?: boolean;
|
trim?: boolean;
|
||||||
checkStatus?: 'success' | 'error' | 'warning' | '';
|
checkStatus?: 'success' | 'error' | 'warning' | '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ const TableRow: React.FC<
|
|||||||
childrenDataRef.current = childrenData;
|
childrenDataRef.current = childrenData;
|
||||||
const axiosToken = useRef<any>(null);
|
const axiosToken = useRef<any>(null);
|
||||||
const [updateChild, setUpdateChild] = useState(true);
|
const [updateChild, setUpdateChild] = useState(true);
|
||||||
|
const [currentExpand, setCurrentExpand] = useState(false);
|
||||||
|
|
||||||
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
||||||
dataList: childrenData,
|
dataList: childrenData,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
setDataList: setChildrenData
|
setDataList: setChildrenData
|
||||||
// callback: (list) => renderChildren?.(list)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,7 +92,10 @@ const TableRow: React.FC<
|
|||||||
></Empty>
|
></Empty>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return renderChildren?.(childrenData, record);
|
return renderChildren?.(childrenData, {
|
||||||
|
parent: record,
|
||||||
|
currentExpanded: currentExpand
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePolling = async () => {
|
const handlePolling = async () => {
|
||||||
@@ -163,6 +166,7 @@ const TableRow: React.FC<
|
|||||||
|
|
||||||
const handleRowExpand = async () => {
|
const handleRowExpand = async () => {
|
||||||
onExpand?.(!expanded, record, record[rowKey]);
|
onExpand?.(!expanded, record, record[rowKey]);
|
||||||
|
setCurrentExpand(!expanded);
|
||||||
|
|
||||||
if (pollTimer.current) {
|
if (pollTimer.current) {
|
||||||
clearInterval(pollTimer.current);
|
clearInterval(pollTimer.current);
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 54px;
|
height: 54px;
|
||||||
padding-block: 5px;
|
|
||||||
border-radius: var(--border-radius-mdium);
|
border-radius: var(--border-radius-mdium);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,10 @@ export interface SealTableProps {
|
|||||||
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
|
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
|
||||||
onExpand?: (expanded: boolean, record: any, rowKey: any) => void;
|
onExpand?: (expanded: boolean, record: any, rowKey: any) => void;
|
||||||
onExpandAll?: (expanded: boolean) => void;
|
onExpandAll?: (expanded: boolean) => void;
|
||||||
renderChildren?: (data: any, parent?: any) => React.ReactNode;
|
renderChildren?: (
|
||||||
|
data: any,
|
||||||
|
options: { parent?: any; [key: string]: any }
|
||||||
|
) => React.ReactNode;
|
||||||
loadChildren?: (record: any, options?: any) => Promise<any[]>;
|
loadChildren?: (record: any, options?: any) => Promise<any[]>;
|
||||||
loadChildrenAPI?: (record: any) => string;
|
loadChildrenAPI?: (record: any) => string;
|
||||||
contentRendered?: () => void;
|
contentRendered?: () => void;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const TableHeader = ({ columns }: TableHeaderProps) => {
|
|||||||
{columns.map((column: any, index: number) => {
|
{columns.map((column: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<th key={index}>
|
<th key={index}>
|
||||||
<span className="cell-span">
|
<span className="cell-span cell-header">
|
||||||
{column.locale
|
{column.locale
|
||||||
? intl.formatMessage({ id: column.title })
|
? intl.formatMessage({ id: column.title })
|
||||||
: column.title}
|
: column.title}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr:last-child td {
|
tr:last-child td {
|
||||||
border-bottom: none;
|
// border-bottom: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,9 +25,17 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: var(--color-white-quaternary);
|
||||||
|
}
|
||||||
|
|
||||||
.cell-span {
|
.cell-span {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell-header {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,31 @@ import TableHeader from './header';
|
|||||||
import './index.less';
|
import './index.less';
|
||||||
import TableRow from './row';
|
import TableRow from './row';
|
||||||
|
|
||||||
interface ColumnProps {
|
export interface ColumnProps {
|
||||||
title: string;
|
title: string;
|
||||||
key: string;
|
key: string;
|
||||||
render?: (data: { dataIndex: string; row: any }) => any;
|
render?: (data: {
|
||||||
|
dataIndex: string;
|
||||||
|
dataList?: any[];
|
||||||
|
row: any;
|
||||||
|
rowIndex?: number;
|
||||||
|
colIndex?: number;
|
||||||
|
}) => any;
|
||||||
locale?: boolean;
|
locale?: boolean;
|
||||||
|
colSpan?: (params: {
|
||||||
|
row: any;
|
||||||
|
rowIndex: number;
|
||||||
|
colIndex: number;
|
||||||
|
dataIndex: string;
|
||||||
|
dataList: any[];
|
||||||
|
}) => number;
|
||||||
|
rowSpan?: (params: {
|
||||||
|
row: any;
|
||||||
|
rowIndex: number;
|
||||||
|
colIndex: number;
|
||||||
|
dataIndex: string;
|
||||||
|
dataList: any[];
|
||||||
|
}) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SimpleTableProps {
|
interface SimpleTableProps {
|
||||||
@@ -42,7 +62,9 @@ const SimpleTabel: React.FC<SimpleTableProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
row={item}
|
row={item}
|
||||||
|
rowIndex={index}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
dataList={dataSource}
|
||||||
key={rowKey ? item[rowKey] : index}
|
key={rowKey ? item[rowKey] : index}
|
||||||
></TableRow>
|
></TableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +1,88 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
interface TableRowProps {
|
interface TableRowProps {
|
||||||
row: any;
|
row: any;
|
||||||
columns: any;
|
columns: any;
|
||||||
|
rowIndex: number;
|
||||||
|
dataList: any[];
|
||||||
}
|
}
|
||||||
const TableRow = ({ row, columns }: TableRowProps) => {
|
|
||||||
|
interface TableCellProps {
|
||||||
|
row: any;
|
||||||
|
column: any;
|
||||||
|
rowIndex: number;
|
||||||
|
colIndex: number;
|
||||||
|
dataList: any[];
|
||||||
|
}
|
||||||
|
const TableCell: React.FC<TableCellProps> = (props: TableCellProps) => {
|
||||||
|
const { row, column, rowIndex, colIndex, dataList } = props;
|
||||||
|
|
||||||
|
const renderContent = useMemo(() => {
|
||||||
|
return column.render
|
||||||
|
? column.render({
|
||||||
|
dataIndex: column.key,
|
||||||
|
dataList: dataList,
|
||||||
|
row: row,
|
||||||
|
rowIndex,
|
||||||
|
colIndex: colIndex
|
||||||
|
})
|
||||||
|
: row[column.key];
|
||||||
|
}, [column, row, rowIndex, colIndex, dataList]);
|
||||||
|
|
||||||
|
if (renderContent === null && (column.colSpan || column.rowSpan)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
key={colIndex}
|
||||||
|
rowSpan={column.rowSpan?.({
|
||||||
|
row,
|
||||||
|
rowIndex,
|
||||||
|
colIndex: colIndex,
|
||||||
|
dataIndex: column.key,
|
||||||
|
dataList: dataList
|
||||||
|
})}
|
||||||
|
colSpan={column.colSpan?.({
|
||||||
|
row,
|
||||||
|
rowIndex,
|
||||||
|
colIndex: colIndex,
|
||||||
|
dataIndex: column.key,
|
||||||
|
dataList: dataList
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span className="cell-span">
|
||||||
|
{column.render
|
||||||
|
? column.render({
|
||||||
|
dataIndex: column.key,
|
||||||
|
dataList: dataList,
|
||||||
|
row: row,
|
||||||
|
rowIndex,
|
||||||
|
colIndex: colIndex
|
||||||
|
})
|
||||||
|
: row[column.key]}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableRow = ({ row, columns, rowIndex, dataList }: TableRowProps) => {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
{columns.map((column: any, index: number) => {
|
{columns.map((column: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<td key={index}>
|
<TableCell
|
||||||
<span className="cell-span">
|
key={index}
|
||||||
{column.render
|
rowIndex={rowIndex}
|
||||||
? column.render({ dataIndex: column.key, row: row })
|
colIndex={index}
|
||||||
: row[column.key]}
|
dataList={dataList}
|
||||||
</span>
|
row={row}
|
||||||
</td>
|
column={column}
|
||||||
|
></TableCell>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(TableRow);
|
export default TableRow;
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import { useCallback, useState } from 'react';
|
|||||||
export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
||||||
const [expandedRowKeys, setExpandedRowKeys] =
|
const [expandedRowKeys, setExpandedRowKeys] =
|
||||||
useState<React.Key[]>(defaultKeys);
|
useState<React.Key[]>(defaultKeys);
|
||||||
|
const [currentExpand, setCurrentExpand] = useState<React.Key | null>(null);
|
||||||
|
|
||||||
const handleExpandChange = useCallback(
|
const handleExpandChange = useCallback(
|
||||||
(expanded: boolean, record: any, rowKey: any) => {
|
(expanded: boolean, record: any, rowKey: any) => {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
setExpandedRowKeys((keys) => [...keys, rowKey]);
|
setExpandedRowKeys((keys) => [...keys, rowKey]);
|
||||||
|
setCurrentExpand(rowKey);
|
||||||
} else {
|
} else {
|
||||||
setExpandedRowKeys((keys) => keys.filter((key) => key !== rowKey));
|
setExpandedRowKeys((keys) => keys.filter((key) => key !== rowKey));
|
||||||
|
setCurrentExpand(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@@ -19,6 +22,7 @@ export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
|||||||
(expanded: boolean, keys: React.Key[] = []) => {
|
(expanded: boolean, keys: React.Key[] = []) => {
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
setExpandedRowKeys([]);
|
setExpandedRowKeys([]);
|
||||||
|
setCurrentExpand(null);
|
||||||
}
|
}
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
setExpandedRowKeys((prevKeys) => [...new Set([...prevKeys, ...keys])]);
|
setExpandedRowKeys((prevKeys) => [...new Set([...prevKeys, ...keys])]);
|
||||||
@@ -41,10 +45,13 @@ export default function useExpandedRowKeys(defaultKeys: React.Key[] = []) {
|
|||||||
|
|
||||||
const clearExpandedRowKeys = () => {
|
const clearExpandedRowKeys = () => {
|
||||||
setExpandedRowKeys([]);
|
setExpandedRowKeys([]);
|
||||||
|
setCurrentExpand(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expandedRowKeys,
|
expandedRowKeys,
|
||||||
|
currentExpand,
|
||||||
|
setCurrentExpand,
|
||||||
clearExpandedRowKeys,
|
clearExpandedRowKeys,
|
||||||
updateExpandedRowKeys,
|
updateExpandedRowKeys,
|
||||||
removeExpandedRowKey,
|
removeExpandedRowKey,
|
||||||
|
|||||||
@@ -108,5 +108,7 @@ export default {
|
|||||||
'models.table.list.getStart':
|
'models.table.list.getStart':
|
||||||
'<span style="margin-right: 5px;font-size: 13px;">Get started with</span> <span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span>',
|
'<span style="margin-right: 5px;font-size: 13px;">Get started with</span> <span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span>',
|
||||||
'models.table.llamaAcrossworker': 'Llama-box Across Workers',
|
'models.table.llamaAcrossworker': 'Llama-box Across Workers',
|
||||||
'models.table.vllmAcrossworker': 'vLLM Across Workers'
|
'models.table.vllmAcrossworker': 'vLLM Across Workers',
|
||||||
|
'models.form.releases': 'Releases',
|
||||||
|
'models.form.moreparameters': 'More Parameters'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -109,5 +109,8 @@ export default {
|
|||||||
'models.table.llamaAcrossworker':
|
'models.table.llamaAcrossworker':
|
||||||
'TODO: Translate key "models.table.llamaAcrossworker"',
|
'TODO: Translate key "models.table.llamaAcrossworker"',
|
||||||
'models.table.vllmAcrossworker':
|
'models.table.vllmAcrossworker':
|
||||||
'TODO: Translate key "models.table.vllmAcrossworker"'
|
'TODO: Translate key "models.table.vllmAcrossworker"',
|
||||||
|
'models.form.releases': 'TODO: Translate key "models.form.releases"',
|
||||||
|
'models.form.moreparameters':
|
||||||
|
'TODO: Translate key "models.form.moreparameters"'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -104,5 +104,7 @@ export default {
|
|||||||
'models.table.list.getStart':
|
'models.table.list.getStart':
|
||||||
'<span style="margin-right: 5px;font-size: 13px;">一键部署</span><span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span><span style="margin-left: 5px;font-size: 13px;">立即使用!</span>',
|
'<span style="margin-right: 5px;font-size: 13px;">一键部署</span><span style="font-size: 14px;font-weight: 700">DeepSeek-R1-Distill-Qwen-1.5B</span><span style="margin-left: 5px;font-size: 13px;">立即使用!</span>',
|
||||||
'models.table.llamaAcrossworker': 'Llama-box 跨节点',
|
'models.table.llamaAcrossworker': 'Llama-box 跨节点',
|
||||||
'models.table.vllmAcrossworker': 'vLLM 跨节点'
|
'models.table.vllmAcrossworker': 'vLLM 跨节点',
|
||||||
|
'models.form.releases': '版本',
|
||||||
|
'models.form.moreparameters': '更多参数'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,12 +101,14 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
if (backend === backendOptionsMap.llamaBox) {
|
if (backend === backendOptionsMap.llamaBox) {
|
||||||
return {
|
return {
|
||||||
backend: 'llama-box',
|
backend: 'llama-box',
|
||||||
|
releases: 'https://github.com/gpustack/llama-box/releases',
|
||||||
link: 'https://github.com/gpustack/llama-box?tab=readme-ov-file#usage'
|
link: 'https://github.com/gpustack/llama-box?tab=readme-ov-file#usage'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (backend === backendOptionsMap.vllm) {
|
if (backend === backendOptionsMap.vllm) {
|
||||||
return {
|
return {
|
||||||
backend: 'vLLM',
|
backend: 'vLLM',
|
||||||
|
releases: 'https://github.com/vllm-project/vllm/releases',
|
||||||
link: 'https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html#command-line-arguments-for-the-server'
|
link: 'https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html#command-line-arguments-for-the-server'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -327,6 +329,25 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
description={intl.formatMessage({
|
description={intl.formatMessage({
|
||||||
id: 'models.form.backendVersion.tips'
|
id: 'models.form.backendVersion.tips'
|
||||||
})}
|
})}
|
||||||
|
labelExtra={
|
||||||
|
backendParamsTips?.releases && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
marginLeft: 5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(
|
||||||
|
<Typography.Link
|
||||||
|
style={{ lineHeight: 1 }}
|
||||||
|
href={backendParamsTips?.releases}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'models.form.releases' })}
|
||||||
|
</Typography.Link>
|
||||||
|
)
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@@ -346,19 +367,20 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
dataList={form.getFieldValue('backend_parameters') || []}
|
dataList={form.getFieldValue('backend_parameters') || []}
|
||||||
onChange={handleBackendParametersChange}
|
onChange={handleBackendParametersChange}
|
||||||
options={paramsConfig}
|
options={paramsConfig}
|
||||||
description={
|
labelExtra={
|
||||||
backendParamsTips && (
|
backendParamsTips?.link && (
|
||||||
<span>
|
<span style={{ marginLeft: 2 }}>
|
||||||
{intl.formatMessage(
|
(
|
||||||
{ id: 'models.form.backend_parameters.vllm.tips' },
|
|
||||||
{ backend: backendParamsTips.backend || '' }
|
|
||||||
)}{' '}
|
|
||||||
<Typography.Link
|
<Typography.Link
|
||||||
href={backendParamsTips.link}
|
style={{ lineHeight: 1 }}
|
||||||
|
href={backendParamsTips?.link}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: 'common.text.here' })}
|
{intl.formatMessage({
|
||||||
|
id: 'models.form.moreparameters'
|
||||||
|
})}
|
||||||
</Typography.Link>
|
</Typography.Link>
|
||||||
|
)
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import AutoTooltip from '@/components/auto-tooltip';
|
|||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import RowChildren from '@/components/seal-table/components/row-children';
|
import RowChildren from '@/components/seal-table/components/row-children';
|
||||||
import SimpleTabel from '@/components/simple-table';
|
import SimpleTabel, { ColumnProps } from '@/components/simple-table';
|
||||||
import StatusTag from '@/components/status-tag';
|
import StatusTag from '@/components/status-tag';
|
||||||
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
|
import { HandlerOptions } from '@/hooks/use-chunk-fetch';
|
||||||
import useDownloadStream from '@/hooks/use-download-stream';
|
import useDownloadStream from '@/hooks/use-download-stream';
|
||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
HddFilled,
|
HddFilled,
|
||||||
InfoCircleOutlined
|
InfoCircleOutlined,
|
||||||
|
ThunderboltFilled
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
@@ -27,17 +28,48 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { MODEL_INSTANCE_API } from '../apis';
|
import { MODEL_INSTANCE_API } from '../apis';
|
||||||
import { InstanceStatusMap, InstanceStatusMapValue, status } from '../config';
|
import { InstanceStatusMap, InstanceStatusMapValue, status } from '../config';
|
||||||
import { ModelInstanceListItem } from '../config/types';
|
import { ModelInstanceListItem } from '../config/types';
|
||||||
import '../style/instance-item.less';
|
import '../style/instance-item.less';
|
||||||
|
|
||||||
|
const WorkerInfo = (props: {
|
||||||
|
title: React.ReactNode;
|
||||||
|
defaultOpen: boolean;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = React.useState(props.defaultOpen);
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.defaultOpen) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpen(false);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, [props.defaultOpen]);
|
||||||
|
return (
|
||||||
|
<span className="server-info-wrapper">
|
||||||
|
<Tooltip
|
||||||
|
open={open}
|
||||||
|
onOpenChange={setOpen}
|
||||||
|
title={props.title}
|
||||||
|
overlayInnerStyle={{
|
||||||
|
width: 'max-content',
|
||||||
|
maxWidth: '400px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="server-info">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
interface InstanceItemProps {
|
interface InstanceItemProps {
|
||||||
instanceData: ModelInstanceListItem;
|
instanceData: ModelInstanceListItem;
|
||||||
workerList: WorkerListItem[];
|
workerList: WorkerListItem[];
|
||||||
modelData?: any;
|
modelData?: any;
|
||||||
|
defaultOpenId: string;
|
||||||
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +108,7 @@ const childActionList = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const distributeCols = [
|
const distributeCols: ColumnProps[] = [
|
||||||
{
|
{
|
||||||
title: 'Worker',
|
title: 'Worker',
|
||||||
key: 'worker_name'
|
key: 'worker_name'
|
||||||
@@ -84,7 +116,7 @@ const distributeCols = [
|
|||||||
{
|
{
|
||||||
title: 'IP',
|
title: 'IP',
|
||||||
key: 'worker_ip',
|
key: 'worker_ip',
|
||||||
render: ({ row }: { row: ModelInstanceListItem }) => {
|
render: ({ row }) => {
|
||||||
return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip;
|
return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -94,9 +126,21 @@ const distributeCols = [
|
|||||||
key: 'gpu_index'
|
key: 'gpu_index'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'resources.table.memory',
|
title: 'resources.table.vram',
|
||||||
locale: true,
|
locale: true,
|
||||||
key: 'ram'
|
key: 'vram',
|
||||||
|
rowSpan: ({ row, rowIndex, colIndex, dataIndex, dataList }) => {
|
||||||
|
return rowIndex === 0 ? dataList.length : 0;
|
||||||
|
},
|
||||||
|
render: ({ rowIndex, dataList }) => {
|
||||||
|
if (rowIndex === 0) {
|
||||||
|
return convertFileSize(
|
||||||
|
_.sumBy(dataList, (item: any) => item.vram),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -153,6 +197,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
instanceData,
|
instanceData,
|
||||||
workerList,
|
workerList,
|
||||||
modelData,
|
modelData,
|
||||||
|
defaultOpenId,
|
||||||
handleChildSelect
|
handleChildSelect
|
||||||
}) => {
|
}) => {
|
||||||
const [api, contextHolder] = notification.useNotification({
|
const [api, contextHolder] = notification.useNotification({
|
||||||
@@ -224,55 +269,32 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
? `${instanceData.worker_ip}:${instanceData.port}`
|
? `${instanceData.worker_ip}:${instanceData.port}`
|
||||||
: instanceData.worker_ip;
|
: instanceData.worker_ip;
|
||||||
}
|
}
|
||||||
let backend = modelData?.backend || '';
|
|
||||||
if (modelData.backend_version) {
|
|
||||||
backend += ` (${modelData.backend_version})`;
|
|
||||||
}
|
|
||||||
const vrams = instanceData.computed_resource_claim?.vram || {};
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 5 }}>
|
<div>{instanceData.worker_name}</div>
|
||||||
|
<div className="flex-center">
|
||||||
<HddFilled className="m-r-5" />
|
<HddFilled className="m-r-5" />
|
||||||
{instanceData.worker_name}
|
{workerIp}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex m-b-6 gap-6">
|
<div className="flex-center">
|
||||||
<InfoItem label="IP" value={workerIp} width={180}></InfoItem>
|
<IconFont type="icon-filled-gpu" className="m-r-5" />
|
||||||
<InfoItem
|
{intl.formatMessage({ id: 'models.table.gpuindex' })}: [
|
||||||
width={90}
|
{_.join(instanceData.gpu_indexes?.sort?.(), ',')}]
|
||||||
label={intl.formatMessage({ id: 'models.form.backend' })}
|
|
||||||
value={backend}
|
|
||||||
></InfoItem>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-6">
|
<div className="flex-center">
|
||||||
<InfoItem
|
<ThunderboltFilled className="m-r-5" />
|
||||||
width={180}
|
{intl.formatMessage({ id: 'models.form.backend' })}:{' '}
|
||||||
label={intl.formatMessage({ id: 'models.table.gpuindex' })}
|
{modelData?.backend || ''}
|
||||||
value={Object.keys(vrams)
|
{modelData.backend_version ? `(${modelData.backend_version})` : ''}
|
||||||
?.sort?.()
|
|
||||||
.map((index) => {
|
|
||||||
return (
|
|
||||||
<span className="flex-1" key={index}>
|
|
||||||
<span className="index">
|
|
||||||
[{index}] {''}
|
|
||||||
</span>
|
|
||||||
{convertFileSize(vrams?.[index], 0)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
></InfoItem>
|
|
||||||
<InfoItem
|
|
||||||
width={90}
|
|
||||||
label={intl.formatMessage({ id: 'resources.table.memory' })}
|
|
||||||
value={convertFileSize(
|
|
||||||
instanceData.computed_resource_claim?.ram,
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
></InfoItem>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [modelData, instanceData, intl]);
|
}, [modelData, instanceData, intl]);
|
||||||
|
|
||||||
|
const calcTotalVram = (vram: Record<string, number>) => {
|
||||||
|
return _.sum(_.values(vram));
|
||||||
|
};
|
||||||
|
|
||||||
const renderDistributedServer = useCallback(
|
const renderDistributedServer = useCallback(
|
||||||
(severList: any[]) => {
|
(severList: any[]) => {
|
||||||
const list = _.map(severList, (item: any) => {
|
const list = _.map(severList, (item: any) => {
|
||||||
@@ -281,20 +303,35 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
worker_name: data?.name,
|
worker_name: data?.name,
|
||||||
worker_ip: data?.ip,
|
worker_ip: data?.ip,
|
||||||
port: '',
|
port: '',
|
||||||
ram: convertFileSize(item.computed_resource_claim?.ram, 0),
|
vram: calcTotalVram(item.computed_resource_claim?.vram || {}),
|
||||||
gpu_index: displayGPUs(item.computed_resource_claim?.vram || {})
|
gpu_index: _.keys(item.computed_resource_claim?.vram).join(',')
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// const list = [
|
||||||
|
// {
|
||||||
|
// worker_name: 'worker1',
|
||||||
|
// worker_ip: '192.168.50.23',
|
||||||
|
// port: '',
|
||||||
|
// vram: 21555525632,
|
||||||
|
// gpu_index: '0,1'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// worker_name: 'worker2',
|
||||||
|
// worker_ip: '192.168.50.25',
|
||||||
|
// port: '',
|
||||||
|
// vram: 21555525632,
|
||||||
|
// gpu_index: '2,3'
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
|
||||||
const mainWorker = [
|
const mainWorker = [
|
||||||
{
|
{
|
||||||
worker_name: `${instanceData.worker_name} (main)`,
|
worker_name: `${instanceData.worker_name}`,
|
||||||
worker_ip: `${instanceData.worker_ip}`,
|
worker_ip: `${instanceData.worker_ip}`,
|
||||||
port: '',
|
port: '',
|
||||||
ram: convertFileSize(instanceData.computed_resource_claim?.ram, 0),
|
vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}),
|
||||||
gpu_index: displayGPUs(
|
gpu_index: `${instanceData.gpu_indexes?.join?.(',')}(main)`
|
||||||
instanceData.computed_resource_claim?.vram || {}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -443,18 +480,12 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
<AutoTooltip title={instanceData.name} ghost>
|
<AutoTooltip title={instanceData.name} ghost>
|
||||||
<span className="m-r-5">{instanceData.name}</span>
|
<span className="m-r-5">{instanceData.name}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
<Tooltip
|
{!!instanceData.worker_id && (
|
||||||
title={renderWorkerInfo}
|
<WorkerInfo
|
||||||
overlayInnerStyle={{
|
title={renderWorkerInfo}
|
||||||
width: 'max-content',
|
defaultOpen={defaultOpenId === instanceData.name}
|
||||||
maxWidth: '400px'
|
></WorkerInfo>
|
||||||
}}
|
)}
|
||||||
>
|
|
||||||
<span className="server-info">
|
|
||||||
<InfoCircleOutlined className="m-r-2" />{' '}
|
|
||||||
{intl.formatMessage({ id: 'common.button.moreInfo' })}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
@@ -468,10 +499,9 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
>
|
>
|
||||||
{renderOffloadInfo}
|
{renderOffloadInfo}
|
||||||
{renderDistributionInfo(
|
{renderDistributionInfo(
|
||||||
instanceData.distributed_servers?.rpc_servers || []
|
instanceData.distributed_servers?.rpc_servers ||
|
||||||
)}
|
instanceData.distributed_servers?.ray_actors ||
|
||||||
{renderDistributionInfo(
|
[]
|
||||||
instanceData.distributed_servers?.ray_actors || []
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { ModelInstanceListItem } from '../config/types';
|
import { ModelInstanceListItem } from '../config/types';
|
||||||
import '../style/instance-item.less';
|
import '../style/instance-item.less';
|
||||||
import InstanceItem from './instance-item';
|
import InstanceItem from './instance-item';
|
||||||
@@ -10,6 +10,7 @@ interface InstanceItemProps {
|
|||||||
list: ModelInstanceListItem[];
|
list: ModelInstanceListItem[];
|
||||||
workerList: WorkerListItem[];
|
workerList: WorkerListItem[];
|
||||||
modelData?: any;
|
modelData?: any;
|
||||||
|
currentExpanded?: string;
|
||||||
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,8 +18,26 @@ const Instances: React.FC<InstanceItemProps> = ({
|
|||||||
list,
|
list,
|
||||||
workerList,
|
workerList,
|
||||||
modelData,
|
modelData,
|
||||||
|
currentExpanded,
|
||||||
handleChildSelect
|
handleChildSelect
|
||||||
}) => {
|
}) => {
|
||||||
|
const [firstLoad, setFirstLoad] = React.useState(true);
|
||||||
|
|
||||||
|
const defaultOpenId = useMemo(() => {
|
||||||
|
if (!currentExpanded) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const current = _.find(
|
||||||
|
list,
|
||||||
|
(item: ModelInstanceListItem) => item.worker_id
|
||||||
|
);
|
||||||
|
return current ? current.name : '';
|
||||||
|
}, [currentExpanded, list]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFirstLoad(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||||
{_.map(list, (item: ModelInstanceListItem, index: number) => {
|
{_.map(list, (item: ModelInstanceListItem, index: number) => {
|
||||||
@@ -28,6 +47,7 @@ const Instances: React.FC<InstanceItemProps> = ({
|
|||||||
modelData={modelData}
|
modelData={modelData}
|
||||||
workerList={workerList}
|
workerList={workerList}
|
||||||
instanceData={item}
|
instanceData={item}
|
||||||
|
defaultOpenId={firstLoad ? defaultOpenId : ''}
|
||||||
handleChildSelect={handleChildSelect}
|
handleChildSelect={handleChildSelect}
|
||||||
></InstanceItem>
|
></InstanceItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
SyncOutlined
|
SyncOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { Access, useAccess, useIntl, useNavigate } from '@umijs/max';
|
import { useAccess, useIntl, useNavigate } from '@umijs/max';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
@@ -68,7 +68,6 @@ import {
|
|||||||
} from '../config';
|
} from '../config';
|
||||||
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
|
import { FormData, ListItem, ModelInstanceListItem } from '../config/types';
|
||||||
import { useGenerateFormEditInitialValues } from '../hooks';
|
import { useGenerateFormEditInitialValues } from '../hooks';
|
||||||
import DeployDropdown from './deploy-dropdown';
|
|
||||||
import DeployModal from './deploy-modal';
|
import DeployModal from './deploy-modal';
|
||||||
import Instances from './instances';
|
import Instances from './instances';
|
||||||
import ModelTag from './model-tag';
|
import ModelTag from './model-tag';
|
||||||
@@ -120,7 +119,7 @@ const ActionList = [
|
|||||||
{
|
{
|
||||||
label: 'common.button.start',
|
label: 'common.button.start',
|
||||||
key: 'start',
|
key: 'start',
|
||||||
icon: <IconFont type="icon-playcircle"></IconFont>
|
icon: <IconFont type="icon-outline-play"></IconFont>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.delete',
|
label: 'common.button.delete',
|
||||||
@@ -132,6 +131,27 @@ const ActionList = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ButtonList = [
|
||||||
|
{
|
||||||
|
label: 'common.button.start',
|
||||||
|
key: 'start',
|
||||||
|
icon: <IconFont type="icon-outline-play"></IconFont>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.button.stop',
|
||||||
|
key: 'stop',
|
||||||
|
icon: <IconFont type="icon-stop1"></IconFont>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.button.delete',
|
||||||
|
key: 'delete',
|
||||||
|
icon: <DeleteOutlined />,
|
||||||
|
props: {
|
||||||
|
danger: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const generateSource = (record: ListItem) => {
|
const generateSource = (record: ListItem) => {
|
||||||
if (record.source === modelSourceMap.modelscope_value) {
|
if (record.source === modelSourceMap.modelscope_value) {
|
||||||
return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`;
|
return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`;
|
||||||
@@ -327,6 +347,12 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sourceOptions = [
|
const sourceOptions = [
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
|
||||||
|
value: 'catalog',
|
||||||
|
key: 'catalog',
|
||||||
|
icon: <IconFont type="icon-catalog"></IconFont>
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Hugging Face',
|
label: 'Hugging Face',
|
||||||
value: modelSourceMap.huggingface_value,
|
value: modelSourceMap.huggingface_value,
|
||||||
@@ -345,12 +371,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
key: 'modelscope',
|
key: 'modelscope',
|
||||||
icon: <IconFont type="icon-tu2"></IconFont>
|
icon: <IconFont type="icon-tu2"></IconFont>
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: intl.formatMessage({ id: 'menu.models.modelCatalog' }),
|
|
||||||
value: 'catalog',
|
|
||||||
key: 'catalog',
|
|
||||||
icon: <IconFont type="icon-catalog"></IconFont>
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({ id: 'models.form.localPath' }),
|
label: intl.formatMessage({ id: 'models.form.localPath' }),
|
||||||
value: modelSourceMap.local_path_value,
|
value: modelSourceMap.local_path_value,
|
||||||
@@ -659,11 +679,12 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderChildren = useCallback(
|
const renderChildren = useCallback(
|
||||||
(list: any, parent?: any) => {
|
(list: any, options: { parent?: any; [key: string]: any }) => {
|
||||||
return (
|
return (
|
||||||
<Instances
|
<Instances
|
||||||
list={list}
|
list={list}
|
||||||
modelData={parent}
|
currentExpanded={options.currentExpanded}
|
||||||
|
modelData={options.parent}
|
||||||
workerList={workerList}
|
workerList={workerList}
|
||||||
handleChildSelect={handleChildSelect}
|
handleChildSelect={handleChildSelect}
|
||||||
></Instances>
|
></Instances>
|
||||||
@@ -739,6 +760,18 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleActionSelect = (val: any) => {
|
||||||
|
if (val === 'delete') {
|
||||||
|
handleDeleteBatch();
|
||||||
|
}
|
||||||
|
if (val === 'start') {
|
||||||
|
handleStartBatch();
|
||||||
|
}
|
||||||
|
if (val === 'stop') {
|
||||||
|
handleStopBatch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const columns: SealColumnProps[] = useMemo(() => {
|
const columns: SealColumnProps[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -939,12 +972,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
onClick: handleClickDropdown
|
onClick: handleClickDropdown
|
||||||
}}
|
}}
|
||||||
trigger={['hover']}
|
trigger={['hover']}
|
||||||
dropdownRender={() => (
|
|
||||||
<DeployDropdown
|
|
||||||
items={sourceOptions}
|
|
||||||
onSelect={handleClickDropdown}
|
|
||||||
></DeployDropdown>
|
|
||||||
)}
|
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -955,7 +982,19 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
|
{intl?.formatMessage?.({ id: 'models.button.deploy' })}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Button
|
<DropdownButtons
|
||||||
|
items={ButtonList}
|
||||||
|
extra={
|
||||||
|
rowSelection.selectedRowKeys.length > 0 && (
|
||||||
|
<span>({rowSelection.selectedRowKeys.length})</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
showText={true}
|
||||||
|
disabled={!rowSelection.selectedRowKeys.length}
|
||||||
|
onSelect={handleActionSelect}
|
||||||
|
/>
|
||||||
|
{/* <Button
|
||||||
icon={<IconFont type="icon-outline-play"></IconFont>}
|
icon={<IconFont type="icon-outline-play"></IconFont>}
|
||||||
onClick={handleStartBatch}
|
onClick={handleStartBatch}
|
||||||
disabled={!rowSelection.selectedRows.length}
|
disabled={!rowSelection.selectedRows.length}
|
||||||
@@ -993,7 +1032,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Access>
|
</Access> */}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
></PageTools>
|
></PageTools>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import AutoTooltip from '@/components/auto-tooltip';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import ProgressBar from '@/components/progress-bar';
|
import ProgressBar from '@/components/progress-bar';
|
||||||
|
import InfoColumn from '@/components/simple-table/info-column';
|
||||||
import useTableSort from '@/hooks/use-table-sort';
|
import useTableSort from '@/hooks/use-table-sort';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import { SyncOutlined } from '@ant-design/icons';
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
@@ -12,6 +13,33 @@ import { queryGpuDevicesList } from '../apis';
|
|||||||
import { GPUDeviceItem } from '../config/types';
|
import { GPUDeviceItem } from '../config/types';
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
|
const fieldList = [
|
||||||
|
{
|
||||||
|
label: 'resources.table.total',
|
||||||
|
key: 'total',
|
||||||
|
locale: true,
|
||||||
|
render: (val: any) => {
|
||||||
|
return convertFileSize(val, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'resources.table.used',
|
||||||
|
key: 'used',
|
||||||
|
locale: true,
|
||||||
|
render: (val: any) => {
|
||||||
|
return convertFileSize(val, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'resources.table.allocated',
|
||||||
|
key: 'allocated',
|
||||||
|
locale: true,
|
||||||
|
render: (val: any) => {
|
||||||
|
return convertFileSize(val, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const GPUList: React.FC = () => {
|
const GPUList: React.FC = () => {
|
||||||
console.log('GPUList======');
|
console.log('GPUList======');
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -228,19 +256,10 @@ const GPUList: React.FC = () => {
|
|||||||
) * 100
|
) * 100
|
||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<span className="flex-column">
|
<InfoColumn
|
||||||
<span>
|
fieldList={fieldList}
|
||||||
{intl.formatMessage({ id: 'resources.table.total' })}:{' '}
|
data={record.memory}
|
||||||
{convertFileSize(record.memory?.total, 0)}
|
></InfoColumn>
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{intl.formatMessage({ id: 'resources.table.used' })}:{' '}
|
|
||||||
{convertFileSize(
|
|
||||||
record.memory?.used || record.memory?.allocated,
|
|
||||||
0
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
></ProgressBar>
|
></ProgressBar>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user