feat: paste multiple lines in parameters input
This commit is contained in:
@@ -7,6 +7,7 @@ interface HintInputProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onBlur?: (e: any) => void;
|
onBlur?: (e: any) => void;
|
||||||
|
onPaste?: (e: any) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
trim?: boolean;
|
trim?: boolean;
|
||||||
sourceOptions?: Global.HintOptions[];
|
sourceOptions?: Global.HintOptions[];
|
||||||
@@ -15,7 +16,15 @@ interface HintInputProps {
|
|||||||
const matchReg = /[^=]+=[^=]*$/;
|
const matchReg = /[^=]+=[^=]*$/;
|
||||||
|
|
||||||
const HintInput: React.FC<HintInputProps> = (props) => {
|
const HintInput: React.FC<HintInputProps> = (props) => {
|
||||||
const { value, label, onChange, onBlur, sourceOptions, trim = true } = props;
|
const {
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onPaste,
|
||||||
|
sourceOptions,
|
||||||
|
trim = true
|
||||||
|
} = props;
|
||||||
const cursorPosRef = React.useRef(0);
|
const cursorPosRef = React.useRef(0);
|
||||||
const contextBeforeCursorRef = React.useRef('');
|
const contextBeforeCursorRef = React.useRef('');
|
||||||
const [options, setOptions] = React.useState<
|
const [options, setOptions] = React.useState<
|
||||||
@@ -92,6 +101,7 @@ const HintInput: React.FC<HintInputProps> = (props) => {
|
|||||||
options={options}
|
options={options}
|
||||||
trim={trim}
|
trim={trim}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
|
onPaste={onPaste}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,6 +79,41 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
setList(values);
|
setList(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnPaste = (e: any, index: number) => {
|
||||||
|
const pastedText = e.clipboardData?.getData('text');
|
||||||
|
if (!pastedText) return;
|
||||||
|
|
||||||
|
const lines = pastedText.split(/\r?\n/).filter((line: string) => {
|
||||||
|
const trimmedLine = trim ? line.trim() : line;
|
||||||
|
return trimmedLine.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lines.length <= 1) {
|
||||||
|
// if there's only one line, let the default paste behavior handle it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const values = _.cloneDeep(list);
|
||||||
|
|
||||||
|
// replace the current item with the first line of the pasted text
|
||||||
|
values[index].value = trim ? lines[0].trim() : lines[0];
|
||||||
|
|
||||||
|
// create new list items for the remaining lines
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
updateCountRef();
|
||||||
|
values.splice(index + i, 0, {
|
||||||
|
value: trim ? lines[i].trim() : lines[i],
|
||||||
|
uid: countRef.current
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueList = _.map(values, 'value').filter((val: string) => !!val);
|
||||||
|
setList(values);
|
||||||
|
onChange(valueList);
|
||||||
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const valueList = _.map(list, 'value').filter((val: string) => !!val);
|
const valueList = _.map(list, 'value').filter((val: string) => !!val);
|
||||||
if (!_.isEqual(valueList, dataList)) {
|
if (!_.isEqual(valueList, dataList)) {
|
||||||
@@ -122,6 +157,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
onBlur={(e) => onBlur?.(e, index)}
|
onBlur={(e) => onBlur?.(e, index)}
|
||||||
onRemove={() => handleOnRemove(index)}
|
onRemove={() => handleOnRemove(index)}
|
||||||
onChange={(val) => handleOnChange(val, index)}
|
onChange={(val) => handleOnChange(val, index)}
|
||||||
|
onPaste={(e) => handleOnPaste(e, index)}
|
||||||
trim={trim}
|
trim={trim}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ interface LabelItemProps {
|
|||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onBlur?: (e: any) => void;
|
onBlur?: (e: any) => void;
|
||||||
|
onPaste?: (e: any) => void;
|
||||||
renderItem?: (
|
renderItem?: (
|
||||||
data: any,
|
data: any,
|
||||||
props: {
|
props: {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onBlur?: (e: any) => void;
|
onBlur?: (e: any) => void;
|
||||||
|
onPaste?: (e: any) => void;
|
||||||
}
|
}
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -29,6 +31,7 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
|||||||
onRemove,
|
onRemove,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
onPaste,
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
@@ -47,13 +50,15 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
|
|||||||
{renderItem ? (
|
{renderItem ? (
|
||||||
renderItem(data, {
|
renderItem(data, {
|
||||||
onChange: handleOnChange,
|
onChange: handleOnChange,
|
||||||
onBlur
|
onBlur,
|
||||||
|
onPaste
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<HintInput
|
<HintInput
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
onPaste={onPaste}
|
||||||
label={label}
|
label={label}
|
||||||
sourceOptions={options}
|
sourceOptions={options}
|
||||||
trim={trim}
|
trim={trim}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const Link = Typography.Link;
|
|||||||
const SealAutoComplete: React.FC<
|
const SealAutoComplete: React.FC<
|
||||||
AutoCompleteProps &
|
AutoCompleteProps &
|
||||||
SealFormItemProps & {
|
SealFormItemProps & {
|
||||||
|
onPaste?: (e: any) => void;
|
||||||
onInput?: (e: Event) => void;
|
onInput?: (e: Event) => void;
|
||||||
clearSpaceOnBlur?: boolean;
|
clearSpaceOnBlur?: boolean;
|
||||||
}
|
}
|
||||||
@@ -148,6 +149,7 @@ const SealAutoComplete: React.FC<
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
popupRender={popupRender}
|
popupRender={popupRender}
|
||||||
onInput={handleOnInput}
|
onInput={handleOnInput}
|
||||||
|
onPaste={props.onPaste}
|
||||||
></AutoComplete>
|
></AutoComplete>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</SelectWrapper>
|
</SelectWrapper>
|
||||||
|
|||||||
@@ -46,27 +46,19 @@ const ClusterDetailModal = () => {
|
|||||||
key: 'workers',
|
key: 'workers',
|
||||||
label: `Workers`,
|
label: `Workers`,
|
||||||
icon: <IconFont type="icon-resources" />,
|
icon: <IconFont type="icon-resources" />,
|
||||||
children: (
|
children: <WorkerList />
|
||||||
<WorkerList
|
|
||||||
clusterId={id}
|
|
||||||
showAddButton={false}
|
|
||||||
showSelect={false}
|
|
||||||
widths={{ input: 360 }}
|
|
||||||
sourceType="cluster"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'deployments',
|
key: 'deployments',
|
||||||
label: `Deployments`,
|
label: `Deployments`,
|
||||||
icon: <IconFont type="icon-rocket-launch1" />,
|
icon: <IconFont type="icon-rocket-launch1" />,
|
||||||
children: <Deployments clusterId={Number(id)}></Deployments>
|
children: <Deployments></Deployments>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'gpus',
|
key: 'gpus',
|
||||||
label: `GPUs`,
|
label: `GPUs`,
|
||||||
icon: <IconFont type="icon-gpu1" />,
|
icon: <IconFont type="icon-gpu1" />,
|
||||||
children: <GPUList clusterId={Number(id)} widths={{ input: 360 }} />
|
children: <GPUList />
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { tableSorter } from '@/config/settings';
|
|||||||
import GrafanaIcon from '@/pages/_components/grafana-icon';
|
import GrafanaIcon from '@/pages/_components/grafana-icon';
|
||||||
import { StarFilled } from '@ant-design/icons';
|
import { StarFilled } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip, Typography } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -100,9 +100,9 @@ const useClusterColumns = (
|
|||||||
span: 3,
|
span: 3,
|
||||||
render: (text: string, record: ClusterListItem) => (
|
render: (text: string, record: ClusterListItem) => (
|
||||||
<>
|
<>
|
||||||
<AutoTooltip ghost title={text}>
|
<Typography.Link onClick={() => onCellClick?.(record, 'name')}>
|
||||||
{text}
|
{record.name}
|
||||||
</AutoTooltip>
|
</Typography.Link>
|
||||||
{record.is_default && (
|
{record.is_default && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
}}
|
}}
|
||||||
options={modelFilesSortOptions.current}
|
options={modelFilesSortOptions.current}
|
||||||
size="middle"
|
size="middle"
|
||||||
style={{ width: '120px' }}
|
style={{ width: '120px', fontWeight: 400 }}
|
||||||
></BaseSelect>
|
></BaseSelect>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
{dataSource.loading && (
|
{dataSource.loading && (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
MODELS_API,
|
MODELS_API,
|
||||||
MODEL_INSTANCE_API,
|
MODEL_INSTANCE_API,
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import TableList from './components/table-list';
|
import TableList from './components/table-list';
|
||||||
import { ListItem } from './config/types';
|
import { ListItem } from './config/types';
|
||||||
|
|
||||||
const Models: React.FC<{ clusterId?: number }> = ({ clusterId }) => {
|
const Models = () => {
|
||||||
const { pagination, setPagination } = usePaginationStatus(
|
const { pagination, setPagination } = usePaginationStatus(
|
||||||
PaginationKey.Deployments
|
PaginationKey.Deployments
|
||||||
);
|
);
|
||||||
@@ -49,7 +49,7 @@ const Models: React.FC<{ clusterId?: number }> = ({ clusterId }) => {
|
|||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10,
|
perPage: 10,
|
||||||
search: '',
|
search: '',
|
||||||
cluster_id: clusterId || 0,
|
cluster_id: 0,
|
||||||
categories: [],
|
categories: [],
|
||||||
state: '',
|
state: '',
|
||||||
sort_by: '',
|
sort_by: '',
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ import { useQueryClusterList } from '@/pages/cluster-management/services/use-que
|
|||||||
import { useIntl, useSearchParams } from '@umijs/max';
|
import { useIntl, useSearchParams } from '@umijs/max';
|
||||||
import { ConfigProvider, Table } from 'antd';
|
import { ConfigProvider, Table } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { GPU_DEVICES_API, queryGpuDevicesList } from '../apis';
|
import { GPU_DEVICES_API, queryGpuDevicesList } from '../apis';
|
||||||
import { GPUDeviceItem } from '../config/types';
|
import { GPUDeviceItem } from '../config/types';
|
||||||
import useGPUColumns from '../hooks/use-gpu-columns';
|
import useGPUColumns from '../hooks/use-gpu-columns';
|
||||||
|
|
||||||
const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
|
const GPUList = () => {
|
||||||
clusterId,
|
|
||||||
widths
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
dataSource,
|
dataSource,
|
||||||
queryParams,
|
queryParams,
|
||||||
@@ -31,10 +28,7 @@ const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
|
|||||||
key: PaginationKey.GPUs,
|
key: PaginationKey.GPUs,
|
||||||
fetchAPI: queryGpuDevicesList,
|
fetchAPI: queryGpuDevicesList,
|
||||||
polling: true,
|
polling: true,
|
||||||
API: GPU_DEVICES_API,
|
API: GPU_DEVICES_API
|
||||||
defaultQueryParams: {
|
|
||||||
cluster_id: clusterId
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const page = searchParams.get('page');
|
const page = searchParams.get('page');
|
||||||
@@ -108,8 +102,7 @@ const GPUList: React.FC<{ clusterId?: number; widths?: { input: number } }> = ({
|
|||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
handleSelectChange={handleClusterChange}
|
handleSelectChange={handleClusterChange}
|
||||||
selectOptions={clusterList}
|
selectOptions={clusterList}
|
||||||
showSelect={page !== 'clusters'}
|
showSelect={true}
|
||||||
widths={{ input: widths?.input || 200 }}
|
|
||||||
></FilterBar>
|
></FilterBar>
|
||||||
<ConfigProvider renderEmpty={renderEmpty}>
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Table, message } from 'antd';
|
import { ConfigProvider, Table, message } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
WORKERS_API,
|
WORKERS_API,
|
||||||
deleteWorker,
|
deleteWorker,
|
||||||
@@ -27,19 +27,7 @@ import UpdateLabels from './update-labels';
|
|||||||
import WorkerDetailModal from './worker-detail-modal';
|
import WorkerDetailModal from './worker-detail-modal';
|
||||||
import WorkerRightActions from './worker-right-actions';
|
import WorkerRightActions from './worker-right-actions';
|
||||||
|
|
||||||
const Workers: React.FC<{
|
const Workers = () => {
|
||||||
clusterId?: string | number | null;
|
|
||||||
showSelect?: boolean;
|
|
||||||
showAddButton?: boolean;
|
|
||||||
widths?: { input: number };
|
|
||||||
sourceType?: string;
|
|
||||||
}> = ({
|
|
||||||
clusterId,
|
|
||||||
showSelect = true,
|
|
||||||
showAddButton = true,
|
|
||||||
widths = { input: 200 },
|
|
||||||
sourceType = 'resources'
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
dataSource,
|
dataSource,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
@@ -63,10 +51,7 @@ const Workers: React.FC<{
|
|||||||
contentForDelete: 'resources.worker',
|
contentForDelete: 'resources.worker',
|
||||||
watch: true,
|
watch: true,
|
||||||
API: WORKERS_API,
|
API: WORKERS_API,
|
||||||
updateManually: true,
|
updateManually: true
|
||||||
defaultQueryParams: {
|
|
||||||
cluster_id: clusterId
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const { goToGrafana, ActionButton } = useGranfanaLink({
|
const { goToGrafana, ActionButton } = useGranfanaLink({
|
||||||
type: 'worker'
|
type: 'worker'
|
||||||
@@ -255,8 +240,7 @@ const Workers: React.FC<{
|
|||||||
loadend: dataSource.loadend,
|
loadend: dataSource.loadend,
|
||||||
firstLoad: extraStatus.firstLoad,
|
firstLoad: extraStatus.firstLoad,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
handleSelect,
|
handleSelect
|
||||||
sourceType
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -268,7 +252,7 @@ const Workers: React.FC<{
|
|||||||
<>
|
<>
|
||||||
<PageBox>
|
<PageBox>
|
||||||
<FilterBar
|
<FilterBar
|
||||||
showSelect={showSelect}
|
showSelect={true}
|
||||||
selectHolder={intl.formatMessage({ id: 'clusters.filterBy.cluster' })}
|
selectHolder={intl.formatMessage({ id: 'clusters.filterBy.cluster' })}
|
||||||
marginBottom={22}
|
marginBottom={22}
|
||||||
marginTop={30}
|
marginTop={30}
|
||||||
@@ -280,7 +264,6 @@ const Workers: React.FC<{
|
|||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
selectOptions={clusterData.list}
|
selectOptions={clusterData.list}
|
||||||
widths={widths}
|
|
||||||
right={
|
right={
|
||||||
<WorkerRightActions
|
<WorkerRightActions
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
|||||||
@@ -235,7 +235,6 @@ const useWorkerColumns = ({
|
|||||||
loadend,
|
loadend,
|
||||||
firstLoad,
|
firstLoad,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
sourceType,
|
|
||||||
handleSelect
|
handleSelect
|
||||||
}: {
|
}: {
|
||||||
clusterData: {
|
clusterData: {
|
||||||
@@ -245,7 +244,6 @@ const useWorkerColumns = ({
|
|||||||
loadend: boolean;
|
loadend: boolean;
|
||||||
firstLoad: boolean;
|
firstLoad: boolean;
|
||||||
sortOrder: string[];
|
sortOrder: string[];
|
||||||
sourceType: string;
|
|
||||||
handleSelect: (action: string, record: ListItem) => void;
|
handleSelect: (action: string, record: ListItem) => void;
|
||||||
}): ColumnsType<ListItem> => {
|
}): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -316,7 +314,6 @@ const useWorkerColumns = ({
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'clusters.title' }),
|
title: intl.formatMessage({ id: 'clusters.title' }),
|
||||||
dataIndex: 'cluster_id',
|
dataIndex: 'cluster_id',
|
||||||
hidden: sourceType === 'cluster',
|
|
||||||
render: (id: number) => (
|
render: (id: number) => (
|
||||||
<AutoTooltip ghost maxWidth={240}>
|
<AutoTooltip ghost maxWidth={240}>
|
||||||
{_.get(clusterData.data, id, '')}
|
{_.get(clusterData.data, id, '')}
|
||||||
@@ -440,7 +437,7 @@ const useWorkerColumns = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[intl, sourceType, sortOrder, clusterData, loadend, firstLoad, handleSelect]
|
[intl, sortOrder, clusterData, loadend, firstLoad, handleSelect]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user