refactor(components): migrate shared components to core-ui and remove unused ones
This commit is contained in:
@@ -1,68 +0,0 @@
|
|||||||
import { IconFont } from '@gpustack/core-ui';
|
|
||||||
import { Collapse, CollapseProps } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const CollapseInner = styled(Collapse)`
|
|
||||||
.ant-collapse-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px !important;
|
|
||||||
padding-inline: 5px !important;
|
|
||||||
padding-block: 8px !important;
|
|
||||||
border-radius: var(--border-radius-base) !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--ant-color-fill-tertiary) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-collapse-body {
|
|
||||||
padding-inline: 0 !important;
|
|
||||||
padding-block: 0 !important;
|
|
||||||
}
|
|
||||||
.ant-collapse-header-text {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CollapsePanel: React.FC<{
|
|
||||||
items: CollapseProps['items'];
|
|
||||||
activeKey: string | string[];
|
|
||||||
accordion?: boolean;
|
|
||||||
defaultActiveKey?: string | string[];
|
|
||||||
onChange?: (key: string | string[]) => void;
|
|
||||||
styles?: Record<string, React.CSSProperties>;
|
|
||||||
}> = ({ items, activeKey, accordion, defaultActiveKey, onChange, styles }) => {
|
|
||||||
return (
|
|
||||||
<CollapseInner
|
|
||||||
expandIconPlacement="start"
|
|
||||||
bordered={false}
|
|
||||||
ghost
|
|
||||||
accordion={accordion}
|
|
||||||
activeKey={activeKey}
|
|
||||||
defaultActiveKey={defaultActiveKey}
|
|
||||||
onChange={onChange}
|
|
||||||
destroyOnHidden={false}
|
|
||||||
styles={{
|
|
||||||
...styles,
|
|
||||||
header: {
|
|
||||||
backgroundColor: 'var(--ant-collapse-header-bg)'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
expandIcon={({ isActive }) => (
|
|
||||||
<IconFont
|
|
||||||
type="icon-down"
|
|
||||||
rotate={isActive ? 0 : -90}
|
|
||||||
style={{ fontSize: '14px' }}
|
|
||||||
></IconFont>
|
|
||||||
)}
|
|
||||||
items={items}
|
|
||||||
></CollapseInner>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CollapsePanel;
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
import {
|
|
||||||
readColumnSettings,
|
|
||||||
writeColumnSettings
|
|
||||||
} from '@/utils/localstore/index';
|
|
||||||
import { SettingOutlined } from '@ant-design/icons';
|
|
||||||
import { OverlayScroller } from '@gpustack/core-ui';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Button, Checkbox, Col, Popover, Row, Tooltip } from 'antd';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
padding: 8px 12px;
|
|
||||||
padding-right: 4px;
|
|
||||||
.title {
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.btn-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 12px;
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Title = styled.div`
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
margin-top: 4px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LabelWrapper = styled.span`
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
> span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
.sub-title {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ColumnSettings: React.FC<{
|
|
||||||
width?: number;
|
|
||||||
fixedColumns?: string[];
|
|
||||||
tableName: string;
|
|
||||||
contentHeight: number;
|
|
||||||
columns: {
|
|
||||||
title: React.ReactNode;
|
|
||||||
dataIndex?: string;
|
|
||||||
children?: { title: React.ReactNode; dataIndex?: string }[];
|
|
||||||
}[];
|
|
||||||
selectedColumns?: string[];
|
|
||||||
defaultSelectedColumns?: string[];
|
|
||||||
grouped?: boolean;
|
|
||||||
onReset?: () => void;
|
|
||||||
onChange?: (selectedColumns: string[]) => void;
|
|
||||||
}> = (props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const {
|
|
||||||
tableName,
|
|
||||||
contentHeight,
|
|
||||||
width = 420,
|
|
||||||
columns,
|
|
||||||
selectedColumns,
|
|
||||||
defaultSelectedColumns,
|
|
||||||
grouped,
|
|
||||||
onReset,
|
|
||||||
onChange,
|
|
||||||
fixedColumns
|
|
||||||
} = props;
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
const [innerSelectedColumns, setInnerSelectedColumns] = React.useState<
|
|
||||||
string[]
|
|
||||||
>(defaultSelectedColumns || []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
setInnerSelectedColumns(selectedColumns ?? defaultSelectedColumns ?? []);
|
|
||||||
}
|
|
||||||
}, [open, selectedColumns, defaultSelectedColumns]);
|
|
||||||
|
|
||||||
const handleToggle = () => {
|
|
||||||
setOpen(!open);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDraftChange = (columns: string[]) => {
|
|
||||||
setInnerSelectedColumns(columns);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
|
||||||
if (grouped) {
|
|
||||||
const allCols: string[] = [];
|
|
||||||
columns.forEach((group) => {
|
|
||||||
group.children?.forEach((col) => {
|
|
||||||
if (col.dataIndex) {
|
|
||||||
allCols.push(col.dataIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
handleDraftChange(allCols);
|
|
||||||
} else {
|
|
||||||
const allCols = columns
|
|
||||||
.map((col) => col.dataIndex)
|
|
||||||
.filter((dataIndex): dataIndex is string => Boolean(dataIndex));
|
|
||||||
handleDraftChange(allCols);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
setOpen(false);
|
|
||||||
writeColumnSettings(tableName, innerSelectedColumns);
|
|
||||||
onChange?.(innerSelectedColumns);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
const resetCols = defaultSelectedColumns ?? [];
|
|
||||||
writeColumnSettings(tableName, resetCols);
|
|
||||||
onChange?.(resetCols);
|
|
||||||
setInnerSelectedColumns(resetCols);
|
|
||||||
onReset?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenChange = (isOpen: boolean) => {
|
|
||||||
setOpen(isOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const initColumns = async () => {
|
|
||||||
const stored = await readColumnSettings(tableName);
|
|
||||||
if (stored && stored.length > 0) {
|
|
||||||
setInnerSelectedColumns(stored);
|
|
||||||
onChange?.(stored);
|
|
||||||
} else {
|
|
||||||
setInnerSelectedColumns(defaultSelectedColumns || []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
initColumns();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const contentRender = () => {
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
{!grouped && (
|
|
||||||
<div className="title">
|
|
||||||
{intl.formatMessage({ id: 'benchmark.table.columnSettings' })}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<OverlayScroller
|
|
||||||
maxHeight={contentHeight}
|
|
||||||
styles={{
|
|
||||||
wrapper: {
|
|
||||||
paddingInlineStart: 0
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Checkbox.Group
|
|
||||||
value={innerSelectedColumns}
|
|
||||||
onChange={handleDraftChange}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
{grouped ? (
|
|
||||||
columns.map((row, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
<Title>{row.title}</Title>
|
|
||||||
<Row>
|
|
||||||
{row.children?.map((col) => (
|
|
||||||
<Col key={col.dataIndex} span={12}>
|
|
||||||
<Checkbox
|
|
||||||
disabled={fixedColumns?.includes(
|
|
||||||
col.dataIndex || ''
|
|
||||||
)}
|
|
||||||
value={col.dataIndex}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
>
|
|
||||||
<LabelWrapper>{col.title}</LabelWrapper>
|
|
||||||
</Checkbox>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<Row>
|
|
||||||
{columns.map((col) => (
|
|
||||||
<Col key={col.dataIndex} span={12}>
|
|
||||||
<Checkbox
|
|
||||||
disabled={fixedColumns?.includes(col.dataIndex || '')}
|
|
||||||
value={col.dataIndex}
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
>
|
|
||||||
<LabelWrapper>{col.title}</LabelWrapper>
|
|
||||||
</Checkbox>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</Checkbox.Group>
|
|
||||||
</OverlayScroller>
|
|
||||||
|
|
||||||
<div className="btn-wrapper">
|
|
||||||
<Button size="middle" onClick={handleReset}>
|
|
||||||
{intl.formatMessage({ id: 'common.button.resetdefault' })}
|
|
||||||
</Button>
|
|
||||||
<div className="buttons">
|
|
||||||
<Button size="middle" type="primary" onClick={handleSelectAll}>
|
|
||||||
{intl.formatMessage({ id: 'common.checkbox.all' })}
|
|
||||||
</Button>
|
|
||||||
<Button size="middle" type="primary" onClick={handleConfirm}>
|
|
||||||
{intl.formatMessage({ id: 'common.button.save' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
open={open}
|
|
||||||
onOpenChange={handleOpenChange}
|
|
||||||
trigger={'click'}
|
|
||||||
arrow={false}
|
|
||||||
placement="bottomRight"
|
|
||||||
content={contentRender()}
|
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
width: width
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
title={intl.formatMessage({ id: 'benchmark.table.columnSettings' })}
|
|
||||||
>
|
|
||||||
<Button onClick={handleToggle} icon={<SettingOutlined />}></Button>
|
|
||||||
</Tooltip>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ColumnSettings;
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import { useOverlayScroller } from '@gpustack/core-ui';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { WrapperContext } from './use-wrapper-context';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ContentWrapper = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
overflow-y: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Footer = styled.div`
|
|
||||||
padding-block: 0;
|
|
||||||
background-color: var(--ant-color-bg-elevated);
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ColumnWrapperProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
footer?: React.ReactNode;
|
|
||||||
maxHeight?: string | number;
|
|
||||||
paddingBottom?: number;
|
|
||||||
styles?: {
|
|
||||||
wrapper?: React.CSSProperties;
|
|
||||||
container?: React.CSSProperties;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ColumnWrapper: React.FC<ColumnWrapperProps> = ({
|
|
||||||
children,
|
|
||||||
footer,
|
|
||||||
maxHeight,
|
|
||||||
styles = {}
|
|
||||||
}) => {
|
|
||||||
const scroller = React.useRef<any>(null);
|
|
||||||
const footerRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
|
||||||
const {
|
|
||||||
initialize,
|
|
||||||
instance,
|
|
||||||
scrollEventElement,
|
|
||||||
scrollToBottom,
|
|
||||||
scrollToTarget,
|
|
||||||
getScrollElementScrollableHeight
|
|
||||||
} = useOverlayScroller({
|
|
||||||
options: {
|
|
||||||
scrollbars: {
|
|
||||||
autoHide: 'move'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (scroller.current) {
|
|
||||||
initialize(scroller.current);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setContentPaddingBottom = useCallback((padding: number) => {
|
|
||||||
contentRef.current!.style.paddingBottom = `${padding}px`;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WrapperContext.Provider
|
|
||||||
value={{
|
|
||||||
scroller: scroller,
|
|
||||||
osInstance: instance,
|
|
||||||
scrollEventElement,
|
|
||||||
getScrollElementScrollableHeight,
|
|
||||||
scrollToBottom,
|
|
||||||
scrollToTarget,
|
|
||||||
setSScrollContentPaddingBottom: setContentPaddingBottom
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Wrapper style={{ height: maxHeight || '100%', ...styles.wrapper }}>
|
|
||||||
<ContentWrapper
|
|
||||||
ref={scroller}
|
|
||||||
style={{
|
|
||||||
padding: '16px 24px',
|
|
||||||
...styles.container
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div ref={contentRef}>{children}</div>
|
|
||||||
</ContentWrapper>
|
|
||||||
{footer && <Footer ref={footerRef}>{footer}</Footer>}
|
|
||||||
</Wrapper>
|
|
||||||
</WrapperContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ColumnWrapper;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { createContext, useContext } from 'react';
|
|
||||||
|
|
||||||
interface WrapperContextProps {
|
|
||||||
osInstance?: any;
|
|
||||||
scroller?: any;
|
|
||||||
scrollEventElement?: any;
|
|
||||||
scrollToBottom?: () => void;
|
|
||||||
scrollToTop?: () => void;
|
|
||||||
getScrollElementScrollableHeight?: () => {
|
|
||||||
scrollHeight: number;
|
|
||||||
scrollTop: number;
|
|
||||||
};
|
|
||||||
scrollToTarget?: (target: any, offset?: number) => void;
|
|
||||||
getScrollElement?: () => HTMLElement | null;
|
|
||||||
setSScrollContentPaddingBottom?: (padding: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WrapperContext = createContext<WrapperContextProps>(
|
|
||||||
{} as WrapperContextProps
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useWrapperContext = () => {
|
|
||||||
const context = useContext(WrapperContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('useWrapperContext must be used within a WrapperProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { CloseOutlined } from '@ant-design/icons';
|
|
||||||
import { OverlayScroller } from '@gpustack/core-ui';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Button, Form } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
|
||||||
import filterFormCss from '../styles/filter-form.less';
|
|
||||||
|
|
||||||
const FilterForm: React.FC<
|
|
||||||
React.PropsWithChildren & {
|
|
||||||
ref?: any;
|
|
||||||
width?: number;
|
|
||||||
contentHeight?: number | string;
|
|
||||||
initialValues?: any;
|
|
||||||
hasFilters?: boolean;
|
|
||||||
open?: boolean;
|
|
||||||
onClear?: () => void;
|
|
||||||
onClose?: () => void;
|
|
||||||
onValuesChange?: (ChangeValues: any, allValues: any) => void;
|
|
||||||
styles?: {
|
|
||||||
container?: React.CSSProperties;
|
|
||||||
wrapper?: React.CSSProperties;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
> = forwardRef(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
children,
|
|
||||||
open,
|
|
||||||
width = 300,
|
|
||||||
contentHeight = 400,
|
|
||||||
initialValues = {},
|
|
||||||
styles,
|
|
||||||
onClose,
|
|
||||||
onClear,
|
|
||||||
onValuesChange
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const handleOnReset = () => {
|
|
||||||
form.resetFields(Object.keys(initialValues));
|
|
||||||
form.resetFields();
|
|
||||||
onValuesChange?.({}, form.getFieldsValue());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnClose = () => {
|
|
||||||
onClose?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnValuesChange = (changedValues: any, allValues: any) => {
|
|
||||||
onValuesChange?.(changedValues, allValues);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnClear = () => {
|
|
||||||
handleOnReset();
|
|
||||||
};
|
|
||||||
|
|
||||||
const filtersCount = Object.values(form.getFieldsValue()).filter(
|
|
||||||
(value) => value !== undefined && value !== null && value !== ''
|
|
||||||
).length;
|
|
||||||
|
|
||||||
const renderFooter = () => {
|
|
||||||
return (
|
|
||||||
<div className={filterFormCss['btn-wrapper']}>
|
|
||||||
<Button size="middle" onClick={handleOnReset}>
|
|
||||||
{intl.formatMessage({ id: 'common.button.reset' })}
|
|
||||||
</Button>
|
|
||||||
<div className={filterFormCss.buttons}>
|
|
||||||
<Button size="middle" type="primary" onClick={handleOnClose}>
|
|
||||||
{intl.formatMessage({ id: 'common.button.close' })}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
form,
|
|
||||||
reset: handleOnReset,
|
|
||||||
getValues: () => form.getFieldsValue(),
|
|
||||||
setValues: (values: any) => form.setFieldsValue(values)
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(filterFormCss.wrapper, {
|
|
||||||
[filterFormCss.show]: open
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
width: open ? width : 0,
|
|
||||||
height: contentHeight,
|
|
||||||
...styles?.wrapper
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{ width: width, ...styles?.container }}
|
|
||||||
className={filterFormCss.container}
|
|
||||||
>
|
|
||||||
<div className={filterFormCss.title}>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontWeight: 500,
|
|
||||||
color: 'var(--ant-color-text-tertiary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'common.filter.label' })}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
icon={<CloseOutlined />}
|
|
||||||
onClick={handleOnClose}
|
|
||||||
type="text"
|
|
||||||
style={{
|
|
||||||
color: 'var(--ant-color-text-secondary)'
|
|
||||||
}}
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
<OverlayScroller
|
|
||||||
maxHeight={contentHeight}
|
|
||||||
styles={{
|
|
||||||
wrapper: {
|
|
||||||
paddingInline: 8
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
onValuesChange={handleOnValuesChange}
|
|
||||||
initialValues={initialValues}
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
styles={{
|
|
||||||
label: {
|
|
||||||
lineHeight: 1,
|
|
||||||
height: 'auto',
|
|
||||||
marginBottom: 8,
|
|
||||||
fontWeight: 500
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
minHeight: 0
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Form>
|
|
||||||
</OverlayScroller>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default FilterForm;
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
|
||||||
import type { DrawerProps } from 'antd';
|
|
||||||
import { Tag } from 'antd';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const ModalFooterStyle = {
|
|
||||||
padding: '16px 24px 8px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end'
|
|
||||||
};
|
|
||||||
|
|
||||||
type AddModalProps = {
|
|
||||||
title: React.ReactNode;
|
|
||||||
open: boolean;
|
|
||||||
onCancel?: () => void;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
width?: number | string;
|
|
||||||
footer?: React.ReactNode;
|
|
||||||
subTitle?: React.ReactNode;
|
|
||||||
push?: DrawerProps['push'];
|
|
||||||
};
|
|
||||||
const FormDrawer: React.FC<AddModalProps> = ({
|
|
||||||
title,
|
|
||||||
open,
|
|
||||||
onCancel,
|
|
||||||
onSubmit,
|
|
||||||
children,
|
|
||||||
width = 600,
|
|
||||||
subTitle,
|
|
||||||
footer,
|
|
||||||
push
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<GSDrawer
|
|
||||||
title={
|
|
||||||
<>
|
|
||||||
{title}
|
|
||||||
{subTitle && (
|
|
||||||
<Tag
|
|
||||||
variant="outlined"
|
|
||||||
style={{
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 400,
|
|
||||||
marginLeft: 8,
|
|
||||||
borderRadius: 4,
|
|
||||||
borderColor: 'var(--ant-color-border-secondary)',
|
|
||||||
color: 'var(--ant-color-text-secondary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{subTitle}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
open={open}
|
|
||||||
onClose={onCancel}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
closeIcon={false}
|
|
||||||
mask={{
|
|
||||||
closable: false
|
|
||||||
}}
|
|
||||||
keyboard={false}
|
|
||||||
push={push}
|
|
||||||
styles={{
|
|
||||||
wrapper: { width }
|
|
||||||
}}
|
|
||||||
footer={false}
|
|
||||||
>
|
|
||||||
<ColumnWrapper
|
|
||||||
styles={{
|
|
||||||
container: { paddingBlock: 0 }
|
|
||||||
}}
|
|
||||||
footer={
|
|
||||||
footer ?? (
|
|
||||||
<ModalFooter
|
|
||||||
onOk={onSubmit}
|
|
||||||
onCancel={onCancel}
|
|
||||||
style={ModalFooterStyle}
|
|
||||||
></ModalFooter>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ColumnWrapper>
|
|
||||||
</GSDrawer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FormDrawer;
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
.mask {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 1004;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--ant-color-bg-mask);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity var(--ant-motion-duration-slow)
|
|
||||||
var(--ant-motion-ease-in-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.maskOpen {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: auto;
|
|
||||||
left: auto;
|
|
||||||
z-index: 1005;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background: var(--ant-color-bg-elevated);
|
|
||||||
border-radius: var(--ant-border-radius-lg) 0 0 var(--ant-border-radius-lg);
|
|
||||||
box-shadow:
|
|
||||||
-6px 0 16px 0 rgb(0 0 0 / 8%),
|
|
||||||
-3px 0 6px -4px rgb(0 0 0 / 12%),
|
|
||||||
-9px 0 28px 8px rgb(0 0 0 / 5%);
|
|
||||||
transform: translateX(100%);
|
|
||||||
transition: transform var(--ant-motion-duration-slow)
|
|
||||||
var(--ant-motion-ease-in-out);
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlayOpen {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
min-height: 56px;
|
|
||||||
padding: var(--ant-padding) var(--ant-padding-lg);
|
|
||||||
border-bottom: 1px solid var(--ant-color-split);
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subTitle {
|
|
||||||
margin-left: 8px;
|
|
||||||
border-color: var(--ant-color-border-secondary);
|
|
||||||
border-radius: 4px;
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding-block: 16px;
|
|
||||||
flex: 1;
|
|
||||||
height: calc(100vh - 57px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding: 16px 24px 8px;
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import { ColumnWrapper, IconFont } from '@gpustack/core-ui';
|
|
||||||
import { Button, Tag } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import styles from './form-overlay-view.module.less';
|
|
||||||
|
|
||||||
type FormOverlayViewProps = {
|
|
||||||
title: React.ReactNode;
|
|
||||||
open: boolean;
|
|
||||||
onCancel?: () => void;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
onSubmit?: () => void;
|
|
||||||
footer?: React.ReactNode;
|
|
||||||
subTitle?: React.ReactNode;
|
|
||||||
width?: number | string;
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
maskClosable?: boolean;
|
|
||||||
getContainer?: () => HTMLElement | null | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormOverlayView: React.FC<FormOverlayViewProps> = ({
|
|
||||||
title,
|
|
||||||
open,
|
|
||||||
onCancel,
|
|
||||||
onSubmit,
|
|
||||||
children,
|
|
||||||
subTitle,
|
|
||||||
footer,
|
|
||||||
width = 600,
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
maskClosable = false,
|
|
||||||
getContainer
|
|
||||||
}) => {
|
|
||||||
const [container, setContainer] = React.useState<HTMLElement | null>(null);
|
|
||||||
const [mounted, setMounted] = React.useState(false);
|
|
||||||
const [active, setActive] = React.useState(false);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (open) {
|
|
||||||
setContainer(getContainer?.() ?? null);
|
|
||||||
setMounted(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setActive(false);
|
|
||||||
}, [getContainer, open]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!mounted || !container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = requestAnimationFrame(() => setActive(true));
|
|
||||||
return () => cancelAnimationFrame(id);
|
|
||||||
}, [mounted, container]);
|
|
||||||
|
|
||||||
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
|
||||||
if (e.target !== e.currentTarget || e.propertyName !== 'transform') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!open && !active) {
|
|
||||||
setMounted(false);
|
|
||||||
setContainer(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!mounted || !container) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return createPortal(
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className={classNames(styles.mask, { [styles.maskOpen]: active })}
|
|
||||||
onClick={maskClosable ? onCancel : undefined}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
styles.overlay,
|
|
||||||
{ [styles.overlayOpen]: active },
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
style={{ width, ...style }}
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
onTransitionEnd={handleTransitionEnd}
|
|
||||||
>
|
|
||||||
<div className={styles.header}>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
style={{ fontWeight: 600, fontSize: 16 }}
|
|
||||||
icon={<IconFont type="icon-down2" rotate={90} />}
|
|
||||||
onClick={onCancel}
|
|
||||||
/>
|
|
||||||
<div className={styles.title}>
|
|
||||||
{title}
|
|
||||||
{subTitle && (
|
|
||||||
<Tag variant="outlined" className={styles.subTitle}>
|
|
||||||
{subTitle}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ColumnWrapper
|
|
||||||
styles={{
|
|
||||||
container: { paddingBlock: 16 }
|
|
||||||
}}
|
|
||||||
footer={footer}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ColumnWrapper>
|
|
||||||
</div>
|
|
||||||
</>,
|
|
||||||
container
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FormOverlayView;
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,26 +0,0 @@
|
|||||||
import { update } from 'jdenticon';
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
interface IdenticonProps {
|
|
||||||
value: string;
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Identicon: React.FC<IdenticonProps> = ({
|
|
||||||
value = 'test',
|
|
||||||
size = 28
|
|
||||||
}: IdenticonProps) => {
|
|
||||||
const icon = useRef<any>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
update(icon.current, value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<svg data-jdenticon-value={value} height={size} ref={icon} width={size} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Identicon;
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import {
|
|
||||||
digitReg,
|
|
||||||
lowercaseReg,
|
|
||||||
specialCharacterReg,
|
|
||||||
uppercaseReg
|
|
||||||
} from '@/config';
|
|
||||||
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Space } from 'antd';
|
|
||||||
|
|
||||||
const PasswordValidate: React.FC<{ value: string }> = ({ value = '' }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const renderIcon = ({ valid, text }: { valid: boolean; text: string }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{valid ? (
|
|
||||||
<CheckCircleFilled style={{ color: 'green' }} />
|
|
||||||
) : (
|
|
||||||
<CloseCircleFilled style={{ color: 'red' }} />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className="m-l-5"
|
|
||||||
style={{ color: 'var(--ant-color-text-description)' }}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Space direction="vertical" style={{ paddingTop: '10px' }}>
|
|
||||||
<span>
|
|
||||||
{renderIcon({
|
|
||||||
valid: uppercaseReg.test(value),
|
|
||||||
text: intl.formatMessage({ id: 'users.password.uppcase' })
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{renderIcon({
|
|
||||||
valid: lowercaseReg.test(value),
|
|
||||||
text: intl.formatMessage({ id: 'users.password.lowercase' })
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{renderIcon({
|
|
||||||
valid: digitReg.test(value),
|
|
||||||
text: intl.formatMessage({ id: 'users.password.number' })
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{renderIcon({
|
|
||||||
valid: value.length >= 6 && value.length <= 12,
|
|
||||||
text: intl.formatMessage({ id: 'users.password.length' })
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{renderIcon({
|
|
||||||
valid: specialCharacterReg.test(value),
|
|
||||||
text: intl.formatMessage({ id: 'users.password.special' })
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PasswordValidate;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { AutoTooltip } from '@gpustack/core-ui';
|
|
||||||
import React from 'react';
|
|
||||||
import pillButtonCss from './styles.less';
|
|
||||||
type Option = {
|
|
||||||
label: string;
|
|
||||||
value: string | number | null;
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
value?: string | number;
|
|
||||||
onChange?: (value: string | number | undefined | null) => void;
|
|
||||||
options: Option[];
|
|
||||||
disabled?: boolean;
|
|
||||||
variant?: 'filled' | 'outlined' | 'solid';
|
|
||||||
};
|
|
||||||
|
|
||||||
const PillButtonGroup: React.FC<Props> = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
options,
|
|
||||||
disabled,
|
|
||||||
variant = 'outlined'
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className={pillButtonCss['wrapper']}>
|
|
||||||
{options.map((item) => {
|
|
||||||
const active = value === item.value;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AutoTooltip
|
|
||||||
ghost
|
|
||||||
title={item.label}
|
|
||||||
key={item.value}
|
|
||||||
maxWidth={'100%'}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`${pillButtonCss['pill-item']} ${active ? pillButtonCss['active'] : ''}`}
|
|
||||||
key={item.value}
|
|
||||||
color="default"
|
|
||||||
onClick={() => {
|
|
||||||
if (item.value !== value) {
|
|
||||||
onChange?.(item.value);
|
|
||||||
}
|
|
||||||
if (item.value === value) {
|
|
||||||
onChange?.(undefined);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
</AutoTooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PillButtonGroup;
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
.pill-item {
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: var(--ant-border-radius);
|
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
|
||||||
color: var(--ant-color-text-secondary);
|
|
||||||
padding: 0 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: var(--ant-color-fill);
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.active):hover {
|
|
||||||
background-color: var(--ant-color-fill-secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { SegmentLine } from '@gpustack/core-ui';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import useFieldScroll from './use-field-scroll';
|
|
||||||
|
|
||||||
const SegmentedHeader = styled.div<{ $top?: number }>`
|
|
||||||
position: sticky;
|
|
||||||
top: ${(props) => props.$top || 0}px;
|
|
||||||
z-index: 10;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-bottom: 1px solid var(--ant-color-split);
|
|
||||||
background-color: var(--ant-color-bg-elevated);
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ScrollSpyTabs component
|
|
||||||
* defaultTarget: The default active target tab
|
|
||||||
* segmentedTop: { // Mostlty, It's always a constants.
|
|
||||||
* top: number; // The top offset for the sticky header
|
|
||||||
* offsetTop: number; // The offset top for the target
|
|
||||||
* }
|
|
||||||
* getScrollElementScrollableHeight: function to get the scrollable height of the scroll element
|
|
||||||
* segmentOptions.field: The target data-field={segmentOptions.field} to scroll to
|
|
||||||
* activeKey: The current active keys for collapsible sections
|
|
||||||
* setActiveKey: The function to set active keys for collapsible sections
|
|
||||||
*/
|
|
||||||
interface ScrollSpyTabsProps {
|
|
||||||
ref?: any;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
defaultTarget?: string;
|
|
||||||
segmentedTop: {
|
|
||||||
top: number;
|
|
||||||
offsetTop: number;
|
|
||||||
};
|
|
||||||
activeKey: string[];
|
|
||||||
setActiveKey: (keys: string[]) => void;
|
|
||||||
getScrollElementScrollableHeight?: () => {
|
|
||||||
scrollHeight: number;
|
|
||||||
scrollTop: number;
|
|
||||||
};
|
|
||||||
segmentOptions: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
field: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ScrollSpyTabs: React.FC<ScrollSpyTabsProps> = forwardRef(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
getScrollElementScrollableHeight,
|
|
||||||
segmentedTop,
|
|
||||||
segmentOptions,
|
|
||||||
defaultTarget,
|
|
||||||
activeKey,
|
|
||||||
setActiveKey,
|
|
||||||
children
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const [target, setTarget] = React.useState<string>(
|
|
||||||
defaultTarget || segmentOptions[0]?.value || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
const { scrollToSegment, holderHeight } = useFieldScroll({
|
|
||||||
activeKey,
|
|
||||||
setActiveKey,
|
|
||||||
segmentOptions,
|
|
||||||
segmentedTop: segmentedTop,
|
|
||||||
getScrollElementScrollableHeight: getScrollElementScrollableHeight
|
|
||||||
});
|
|
||||||
|
|
||||||
const throttleScrollToSegment = useMemoizedFn(
|
|
||||||
_.throttle(
|
|
||||||
async (val: string) => {
|
|
||||||
setTarget(val);
|
|
||||||
scrollToSegment(val, { offsetTop: segmentedTop.offsetTop });
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
{ trailing: true }
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleTargetChange = async (val: any) => {
|
|
||||||
throttleScrollToSegment(val);
|
|
||||||
};
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
handleTargetChange
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{segmentOptions.length > 0 && (
|
|
||||||
<SegmentedHeader $top={segmentedTop.top}>
|
|
||||||
<SegmentLine
|
|
||||||
theme={'light'}
|
|
||||||
defaultValue={target}
|
|
||||||
value={target}
|
|
||||||
onChange={handleTargetChange}
|
|
||||||
options={segmentOptions}
|
|
||||||
/>
|
|
||||||
</SegmentedHeader>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
<div className="holder" style={{ height: holderHeight }}></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ScrollSpyTabs;
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
|
||||||
import { useCallback, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
interface ScrollOptions {
|
|
||||||
wait?: number;
|
|
||||||
behavior?: 'smooth' | 'auto';
|
|
||||||
block?: 'start' | 'end' | 'center';
|
|
||||||
offsetTop?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function useScrollAfterExpand({
|
|
||||||
activeKey,
|
|
||||||
setActiveKey,
|
|
||||||
segmentOptions,
|
|
||||||
defaultWait = 300,
|
|
||||||
segmentedTop = { top: 0, offsetTop: 96 },
|
|
||||||
getScrollElementScrollableHeight
|
|
||||||
}: {
|
|
||||||
activeKey: string[];
|
|
||||||
setActiveKey: (keys: string[]) => void;
|
|
||||||
segmentOptions: { value: string; field: string }[];
|
|
||||||
getScrollElementScrollableHeight?: () => {
|
|
||||||
scrollHeight: number;
|
|
||||||
scrollTop: number;
|
|
||||||
};
|
|
||||||
defaultWait?: number;
|
|
||||||
segmentedTop: {
|
|
||||||
top: number; // The top offset for the sticky header
|
|
||||||
offsetTop: number; // The offset top for the target
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const [holderHeight, setHolderHeight] = useState<number>(0);
|
|
||||||
const boxHeightRef = useRef<number>(0);
|
|
||||||
|
|
||||||
const scrollToElement = useCallback(
|
|
||||||
(
|
|
||||||
el: HTMLElement,
|
|
||||||
{ behavior = 'smooth', offsetTop = 0 }: ScrollOptions = {}
|
|
||||||
) => {
|
|
||||||
// find the nearest scrollable parent
|
|
||||||
const scrollParent = (() => {
|
|
||||||
let node: HTMLElement | null = el;
|
|
||||||
while (node) {
|
|
||||||
const { overflowY } = getComputedStyle(node);
|
|
||||||
if (overflowY === 'auto' || overflowY === 'scroll') return node;
|
|
||||||
node = node.parentElement;
|
|
||||||
}
|
|
||||||
return document.scrollingElement || document.documentElement;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const parentRect = scrollParent.getBoundingClientRect();
|
|
||||||
const elRect = el.getBoundingClientRect();
|
|
||||||
const top =
|
|
||||||
elRect.top - parentRect.top + scrollParent.scrollTop - offsetTop;
|
|
||||||
|
|
||||||
scrollParent.scrollTo({ top, behavior });
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* due to the scrollheight changes after expanding the segment and including the holder height.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
const scrollToSegment = useMemoizedFn(
|
|
||||||
async (val: string, options?: ScrollOptions) => {
|
|
||||||
if (!activeKey.includes(val)) {
|
|
||||||
setActiveKey([...activeKey, val]);
|
|
||||||
await new Promise((r) => {
|
|
||||||
setTimeout(r, options?.wait ?? defaultWait);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = segmentOptions.find((item) => item.value === val);
|
|
||||||
if (!current?.field) return;
|
|
||||||
|
|
||||||
await new Promise(requestAnimationFrame);
|
|
||||||
|
|
||||||
const el: HTMLElement | null = document.querySelector(
|
|
||||||
`[data-field="${current.field}"]`
|
|
||||||
) as HTMLElement | null;
|
|
||||||
|
|
||||||
const targetRectTop = el?.getBoundingClientRect().top || 0;
|
|
||||||
|
|
||||||
const scroller = getScrollElementScrollableHeight?.() || {
|
|
||||||
scrollHeight: 0,
|
|
||||||
scrollTop: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// remaining scroll height
|
|
||||||
const remainingScrollHeight = scroller.scrollHeight - scroller.scrollTop;
|
|
||||||
|
|
||||||
// total distance from the top of the scroller to the target element
|
|
||||||
const offsetDistance =
|
|
||||||
targetRectTop - segmentedTop.offsetTop - segmentedTop.top;
|
|
||||||
|
|
||||||
let boxHeight = 0;
|
|
||||||
|
|
||||||
// verify boxHeight is correct, if setting the boxHeight causes the element to be hidden, use the previous boxHeight
|
|
||||||
if (offsetDistance <= 0) {
|
|
||||||
boxHeight = boxHeightRef.current;
|
|
||||||
} else {
|
|
||||||
boxHeight =
|
|
||||||
offsetDistance - remainingScrollHeight + boxHeightRef.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update boxHeightRef
|
|
||||||
boxHeightRef.current = boxHeight;
|
|
||||||
|
|
||||||
setHolderHeight(boxHeight);
|
|
||||||
await new Promise(requestAnimationFrame);
|
|
||||||
|
|
||||||
if (el) scrollToElement(el, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { scrollToSegment, holderHeight };
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
interface FinishFailedOptions {
|
|
||||||
requiredFields: {
|
|
||||||
[tab: string]: {
|
|
||||||
sort: number;
|
|
||||||
fields: string[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
onTargetChange: (key: string) => void;
|
|
||||||
updateActiveKey: (key: string[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFinishFailed = (options: FinishFailedOptions) => {
|
|
||||||
const { requiredFields, onTargetChange, updateActiveKey } = options;
|
|
||||||
const handleOnFinishFailed = (errorInfo: any) => {
|
|
||||||
const { errorFields } = errorInfo;
|
|
||||||
|
|
||||||
console.log('Finish failed:', errorInfo);
|
|
||||||
|
|
||||||
if (errorFields && errorFields.length > 0) {
|
|
||||||
const collapseKeys: { sort: number; key: string }[] = [];
|
|
||||||
const names = errorFields.map((item: any) => item.name[0]);
|
|
||||||
Object.entries(requiredFields).forEach(([tab, { fields, sort }]) => {
|
|
||||||
const hasError = fields.some((field: string) => names.includes(field));
|
|
||||||
if (hasError) {
|
|
||||||
collapseKeys.push({ sort, key: tab });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (collapseKeys.length > 0) {
|
|
||||||
const keys = collapseKeys
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map((item) => item.key);
|
|
||||||
|
|
||||||
updateActiveKey(keys);
|
|
||||||
onTargetChange(collapseKeys[0].key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleOnFinishFailed
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFinishFailed;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const useScrollActiveChange = (options: {
|
|
||||||
initalActiveKeys: string[];
|
|
||||||
initialCollapseKeys?: string[];
|
|
||||||
}) => {
|
|
||||||
const [activeKey, setActiveKey] = useState<string[]>(
|
|
||||||
options.initalActiveKeys
|
|
||||||
);
|
|
||||||
const [collapseKeys, setCollapseKeys] = useState<string[]>(
|
|
||||||
options.initialCollapseKeys || options.initalActiveKeys
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleActiveChange = (key: string[]) => {
|
|
||||||
setActiveKey(key);
|
|
||||||
setCollapseKeys(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnCollapseChange = (keys: string | string[]) => {
|
|
||||||
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
||||||
setActiveKey(keysArray);
|
|
||||||
setCollapseKeys(keysArray);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateActiveKey = (keys: string[]) => {
|
|
||||||
setActiveKey((prev: string[]) => [...new Set([...prev, ...keys])]);
|
|
||||||
setCollapseKeys((prev) => [...new Set([...prev, ...keys])]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeKey,
|
|
||||||
collapseKeys,
|
|
||||||
setCollapseKeys,
|
|
||||||
handleActiveChange,
|
|
||||||
handleOnCollapseChange,
|
|
||||||
updateActiveKey
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useScrollActiveChange;
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { CloseCircleFilled } from '@ant-design/icons';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const StyledButton = styled(Button)`
|
|
||||||
background-color: transparent !important;
|
|
||||||
padding: 0;
|
|
||||||
.anticon {
|
|
||||||
color: var(--ant-color-text-quaternary);
|
|
||||||
font-size: 12px !important;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
.anticon {
|
|
||||||
color: var(--ant-color-text-tertiary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface SmallCloseButtonProps {
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SmallCloseButton: React.FC<SmallCloseButtonProps> = ({ onClick }) => {
|
|
||||||
return (
|
|
||||||
<StyledButton
|
|
||||||
icon={<CloseCircleFilled />}
|
|
||||||
shape="circle"
|
|
||||||
type="text"
|
|
||||||
onClick={onClick}
|
|
||||||
size="small"
|
|
||||||
></StyledButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SmallCloseButton;
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
.wrapper {
|
|
||||||
flex-shrink: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 0;
|
|
||||||
transition: all var(--ant-motion-duration-slow) var(--ant-motion-ease-in-out);
|
|
||||||
border-color: var(--ant-color-split);
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
width: 232px;
|
|
||||||
border-right: 1px solid var(--ant-color-split);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 0 16px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-inline: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--ant-color-text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 24px;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { MoreOutlined } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Transfer, TransferProps } from 'antd';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
type TransferKey = string | number | bigint;
|
|
||||||
|
|
||||||
const TransferWrap = styled.div`
|
|
||||||
.ant-transfer-section {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
.anticon-more {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.ant-input-outlined {
|
|
||||||
height: 32px;
|
|
||||||
padding-block: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-transfer-actions {
|
|
||||||
margin: 0 16px;
|
|
||||||
gap: 12px;
|
|
||||||
.ant-btn-icon-only {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-transfer-list-content {
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: var(--scrollbar-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background-color: var(--color-scrollbar-thumb);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-transfer-list-content-item {
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--ant-control-item-bg-hover);
|
|
||||||
}
|
|
||||||
&.ant-transfer-list-content-item-checked {
|
|
||||||
background-color: unset;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--ant-control-item-bg-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-pagination {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface TransferInnerProps extends TransferProps {
|
|
||||||
total?: number;
|
|
||||||
perPage?: number;
|
|
||||||
onPageChange?: (page: number, perPage?: number) => void;
|
|
||||||
dataSource?: Array<{ key: TransferKey; title: string }>;
|
|
||||||
targetKeys?: TransferKey[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TransferInner: React.FC<TransferInnerProps> = (props) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const renderAllLabels = (info: {
|
|
||||||
selectedCount: number;
|
|
||||||
totalCount: number;
|
|
||||||
}) => {
|
|
||||||
if (info.selectedCount) {
|
|
||||||
return (
|
|
||||||
<span style={{ color: 'var(--ant-color-text-secondary)' }}>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: 'common.select.count' },
|
|
||||||
{ count: info.selectedCount }
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<TransferWrap>
|
|
||||||
<Transfer
|
|
||||||
{...props}
|
|
||||||
selectAllLabels={
|
|
||||||
props.selectAllLabels || [renderAllLabels, renderAllLabels]
|
|
||||||
}
|
|
||||||
selectionsIcon={
|
|
||||||
<MoreOutlined style={{ fontSize: 14, marginBottom: 3 }} />
|
|
||||||
}
|
|
||||||
></Transfer>
|
|
||||||
</TransferWrap>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TransferInner;
|
|
||||||
@@ -3,6 +3,7 @@ import { PageActionType } from '@/config/types';
|
|||||||
import useSubmitLock from '@/hooks/use-submit-lock';
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import {
|
import {
|
||||||
AlertBlockInfo,
|
AlertBlockInfo,
|
||||||
|
ColumnWrapper,
|
||||||
GSDrawer,
|
GSDrawer,
|
||||||
IconFont,
|
IconFont,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
@@ -13,7 +14,6 @@ import { Tabs } from 'antd';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useId, useMemo, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import ColumnWrapper from '../../_components/column-wrapper';
|
|
||||||
import {
|
import {
|
||||||
BackendSourceValueMap,
|
BackendSourceValueMap,
|
||||||
builtInBackendFields,
|
builtInBackendFields,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
import { ModalFooter, SubDrawer } from '@gpustack/core-ui';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { FormData as PublicKeyFormData } from '../../public-keys/config/types';
|
import { FormData as PublicKeyFormData } from '../../public-keys/config/types';
|
||||||
@@ -45,7 +44,7 @@ const PublicKeyOverlay: React.FC<PublicKeyOverlayProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormOverlayView
|
<SubDrawer
|
||||||
title={intl.formatMessage({ id: 'gpuservice.publicKey.add' })}
|
title={intl.formatMessage({ id: 'gpuservice.publicKey.add' })}
|
||||||
open={open}
|
open={open}
|
||||||
width={drawerWidth}
|
width={drawerWidth}
|
||||||
@@ -70,7 +69,7 @@ const PublicKeyOverlay: React.FC<PublicKeyOverlayProps> = ({
|
|||||||
open={open}
|
open={open}
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
/>
|
/>
|
||||||
</FormOverlayView>
|
</SubDrawer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
|
||||||
import { FormContext } from '@/pages/gpu-service/storage/config/form-context';
|
import { FormContext } from '@/pages/gpu-service/storage/config/form-context';
|
||||||
import useQueryStorageClass from '@/pages/gpu-service/storage/services/use-query-storage-class';
|
import useQueryStorageClass from '@/pages/gpu-service/storage/services/use-query-storage-class';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { ModalFooter, SubDrawer } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { FormData as StorageFormData } from '../../storage/config/types';
|
import { FormData as StorageFormData } from '../../storage/config/types';
|
||||||
@@ -52,7 +51,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormOverlayView
|
<SubDrawer
|
||||||
title={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
title={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||||
open={open}
|
open={open}
|
||||||
width={drawerWidth}
|
width={drawerWidth}
|
||||||
@@ -79,7 +78,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
|||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
/>
|
/>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
</FormOverlayView>
|
</SubDrawer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useSubmitLock from '@/hooks/use-submit-lock';
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { FormDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import FormDrawer from '../../../_components/form-drawer';
|
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import GPUServicePublicKeyForm from '../forms';
|
import GPUServicePublicKeyForm from '../forms';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useSubmitLock from '@/hooks/use-submit-lock';
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { FormDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import FormDrawer from '../../../_components/form-drawer';
|
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import GPUServiceStorageTypeForm from '../forms';
|
import GPUServiceStorageTypeForm from '../forms';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useSubmitLock from '@/hooks/use-submit-lock';
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { FormDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import FormDrawer from '../../../_components/form-drawer';
|
|
||||||
import { FormContext } from '../config/form-context';
|
import { FormContext } from '../config/form-context';
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import GPUServiceStorageForm from '../forms';
|
import GPUServiceStorageForm from '../forms';
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useSubmitLock from '@/hooks/use-submit-lock';
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { FormDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import FormDrawer from '../../../_components/form-drawer';
|
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
import GPUServiceTemplateForm from '../forms';
|
import GPUServiceTemplateForm from '../forms';
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ import { getRequestId } from '@/atoms/models';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useDeferredRequest from '@/hooks/use-deferred-request';
|
import useDeferredRequest from '@/hooks/use-deferred-request';
|
||||||
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
|
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
|
||||||
import { GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
import { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import ColumnWrapper from '../../../_components/column-wrapper';
|
|
||||||
import {
|
import {
|
||||||
defaultFormValues,
|
defaultFormValues,
|
||||||
DeployFormKeyMap,
|
DeployFormKeyMap,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||||
import { GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import ColumnWrapper from '../../../_components/column-wrapper';
|
|
||||||
import { modelSourceMap } from '../../config';
|
import { modelSourceMap } from '../../config';
|
||||||
import { FormData } from '../../config/types';
|
import { FormData } from '../../config/types';
|
||||||
import HFModelFile from '../model-source/hf-model-file';
|
import HFModelFile from '../model-source/hf-model-file';
|
||||||
|
|||||||
Reference in New Issue
Block a user