diff --git a/src/assets/styles/common.less b/src/assets/styles/common.less index 8e141e85..8c2700b2 100644 --- a/src/assets/styles/common.less +++ b/src/assets/styles/common.less @@ -42,6 +42,10 @@ margin-right: 2px; } +.m-r-0 { + margin-right: 0; +} + .m-r-8 { margin-right: 8px; } diff --git a/src/components/auto-tooltip/index.tsx b/src/components/auto-tooltip/index.tsx new file mode 100644 index 00000000..4450d822 --- /dev/null +++ b/src/components/auto-tooltip/index.tsx @@ -0,0 +1,79 @@ +import { Tag, Tooltip } from 'antd'; +import debounce from 'lodash/debounce'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState +} from 'react'; + +interface AutoTooltipProps extends React.ComponentProps { + children: React.ReactNode; + maxWidth?: number | string; + color?: string; + style?: React.CSSProperties; + ghost?: boolean; +} + +const AutoTooltip: React.FC = ({ + children, + maxWidth = '100%', + ghost = false, + ...tagProps +}) => { + const contentRef = useRef(null); + const [isOverflowing, setIsOverflowing] = useState(false); + + const checkOverflow = useCallback(() => { + if (contentRef.current) { + const { scrollWidth, clientWidth } = contentRef.current; + setIsOverflowing(scrollWidth > clientWidth); + } + }, []); + + const debouncedCheckOverflow = useMemo( + () => debounce(checkOverflow, 200), + [checkOverflow] + ); + + useEffect(() => { + checkOverflow(); + window.addEventListener('resize', debouncedCheckOverflow); + return () => { + window.removeEventListener('resize', debouncedCheckOverflow); + debouncedCheckOverflow.cancel(); + }; + }, [checkOverflow, debouncedCheckOverflow]); + + useEffect(() => { + checkOverflow(); + }, [children, checkOverflow]); + + const tagStyle = useMemo( + () => ({ + maxWidth, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' as const, + ...tagProps.style + }), + [maxWidth, tagProps.style] + ); + + return ( + + {ghost ? ( +
+ {children} +
+ ) : ( + + {children} + + )} +
+ ); +}; + +export default React.memo(AutoTooltip); diff --git a/src/components/label-selector/index.tsx b/src/components/label-selector/index.tsx index 3b8ba5fc..4127942c 100644 --- a/src/components/label-selector/index.tsx +++ b/src/components/label-selector/index.tsx @@ -1,99 +1,58 @@ -import { PlusOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; import _ from 'lodash'; -import React, { useEffect } from 'react'; -import LabelItem from './label-item'; -import Wrapper from './wrapper'; +import React, { useCallback, useEffect, useState } from 'react'; +import Inner from './inner'; interface LabelSelectorProps { labels: Record; label?: string; + description?: React.ReactNode; onChange?: (labels: Record) => void; } const LabelSelector: React.FC = ({ labels, onChange, - label + label, + description }) => { - const [labelList, setLabelList] = React.useState([]); + const [labelsData, setLabelsData] = useState({}); + const [labelList, setLabelList] = useState<{ key: string; value: string }[]>( + [] + ); useEffect(() => { - const list = _.map(_.keys(labels), (key: string) => { - return { - key, - value: labels[key] - }; - }); - setLabelList(list); + if (!_.isEqual(labels, labelsData)) { + setLabelsData(labels || {}); + const list = _.map(_.keys(labels), (key: string) => { + return { + key, + value: labels[key] + }; + }); + setLabelList(list); + } }, [labels]); - const handleOnChange = (index: string, label: any) => { - const list = _.cloneDeep(labelList); - list[index] = label; - const newLabels = _.reduce( - list, - (result: any, item: any) => { - result[item.key] = item.value; - return result; - }, - {} - ); - onChange?.(newLabels); - }; - - const handleAddLabel = () => { - setLabelList([ - ...labelList, - { - key: '', - value: '' - } - ]); - }; - - const handleOnDelete = (index: string) => { - const list = _.cloneDeep(labelList); - list.splice(parseInt(index), 1); - setLabelList(list); - const newLabels = _.reduce( - list, - (result: any, item: any) => { - result[item.key] = item.value; - return result; - }, - {} - ); - onChange?.(newLabels); + const handleLabelListChange = useCallback( + (list: { key: string; value: string }[]) => { + setLabelList(list); + }, + [setLabelList] + ); + const handleLabelsChange = (data: Record) => { + setLabelsData(data); + onChange?.(data); }; return ( - - {_.map(labelList, (item: any, index: string) => { - return ( - handleOnDelete(index)} - onChange={(obj) => handleOnChange(index, obj)} - /> - ); - })} -
- -
-
+ ); }; diff --git a/src/components/label-selector/inner.tsx b/src/components/label-selector/inner.tsx new file mode 100644 index 00000000..55f02a14 --- /dev/null +++ b/src/components/label-selector/inner.tsx @@ -0,0 +1,109 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button } from 'antd'; +import _ from 'lodash'; +import React, { useRef } from 'react'; +import LabelItem from './label-item'; +import Wrapper from './wrapper'; +interface LabelSelectorProps { + labels: Record; + label?: string; + labelList: Array<{ key: string; value: string }>; + onLabelListChange: (list: { key: string; value: string }[]) => void; + onChange?: (labels: Record) => void; + description?: React.ReactNode; +} + +const Inner: React.FC = ({ + labels, + labelList, + onChange, + onLabelListChange, + label, + description +}) => { + const intl = useIntl(); + const buttonRef = useRef(null); + + const updateLabels = (list: { key: string; value: string }[]) => { + const newLabels = _.reduce( + list, + (result: any, item: any) => { + if (item.key) { + result[item.key] = item.value; + } + return result; + }, + {} + ); + onChange?.(newLabels); + }; + const handleOnChange = (index: string, label: any) => { + const list = _.cloneDeep(labelList); + list[index] = label; + onLabelListChange(list); + updateLabels(list); + }; + + const handleAddLabel = () => { + const newLabelList = [ + ...labelList, + { + key: '', + value: '' + } + ]; + onLabelListChange(newLabelList); + updateLabels(newLabelList); + + setTimeout(() => { + // button scroll to view + buttonRef.current?.scrollIntoView?.({ behavior: 'smooth' }); + }, 100); + }; + + const handleOnDelete = (index: string) => { + const list = _.cloneDeep(labelList); + list.splice(parseInt(index), 1); + onLabelListChange(list); + updateLabels(list); + }; + + return ( + + <> + {_.map(labelList, (item: any, index: string) => { + return ( + handleOnDelete(index)} + onChange={(obj) => handleOnChange(index, obj)} + /> + ); + })} +
+ +
+ +
+ ); +}; + +export default React.memo(Inner); diff --git a/src/components/label-selector/label-item.tsx b/src/components/label-selector/label-item.tsx index de38ed0a..be888bdf 100644 --- a/src/components/label-selector/label-item.tsx +++ b/src/components/label-selector/label-item.tsx @@ -1,9 +1,10 @@ import SealInput from '@/components/seal-form/seal-input'; import { MinusOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; -import React from 'react'; +import { useIntl } from '@umijs/max'; +import { Button, Tooltip } from 'antd'; +import _ from 'lodash'; +import React, { useState } from 'react'; import './styles/label-item.less'; - interface LabelItemProps { label: { key: string; @@ -15,16 +16,21 @@ interface LabelItemProps { valueAddon?: React.ReactNode; seperator?: string; onDelete?: () => void; + labelList: { key: string; value: string }[]; onChange?: (params: { key: string; value: string }) => void; } const LabelItem: React.FC = ({ label, + labelList, seperator, keyAddon, valueAddon, onChange, onDelete }) => { + const intl = useIntl(); + const [open, setOpen] = useState(false); + const handleOnValueChange = (e: any) => { const value = e.target.value; onChange?.({ @@ -36,20 +42,47 @@ const LabelItem: React.FC = ({ const handleOnKeyChange = (e: any) => { const key = e.target.value; onChange?.({ - key: key, + key, value: label.value }); }; + const handleKeyOnBlur = (e: any) => { + const val = e.target.value; + // has duplicate key + const duplicates = _.filter( + labelList, + (item: Global.BaseListItem) => val && val === item.key + ); + if (duplicates.length > 1) { + setOpen(true); + onChange?.({ + key: '', + value: label.value + }); + setTimeout(() => { + setOpen(false); + }, 1000); + } else { + setOpen(false); + } + }; + return (
{keyAddon ?? ( - + + + )}
{seperator && {seperator}} @@ -75,4 +108,4 @@ const LabelItem: React.FC = ({ ); }; -export default LabelItem; +export default React.memo(LabelItem); diff --git a/src/components/label-selector/styles/wrapper.less b/src/components/label-selector/styles/wrapper.less index 26239ced..cecf89b2 100644 --- a/src/components/label-selector/styles/wrapper.less +++ b/src/components/label-selector/styles/wrapper.less @@ -1,7 +1,7 @@ .wrapper { position: relative; - padding: 16px 24px; - padding-top: 30px; + padding: 16px; + padding-top: 34px; border: 1px solid var(--ant-color-border); border-radius: var(--border-radius-base); display: flex; @@ -12,7 +12,8 @@ position: absolute; left: 24px; line-height: 1; - top: 10px; + top: 12px; + color: var(--ant-color-text-tertiary); } } } diff --git a/src/components/label-selector/wrapper.tsx b/src/components/label-selector/wrapper.tsx index f379ef98..13ef15e1 100644 --- a/src/components/label-selector/wrapper.tsx +++ b/src/components/label-selector/wrapper.tsx @@ -4,13 +4,14 @@ import styles from './styles/wrapper.less'; const Wrapper: React.FC<{ label?: string; + description?: React.ReactNode; children: React.ReactNode; -}> = ({ children, label }) => { +}> = ({ children, label, description }) => { return (
{label && ( - + )} {children} diff --git a/src/components/seal-form/components/wrapper.less b/src/components/seal-form/components/wrapper.less index 190c284a..88ba100e 100644 --- a/src/components/seal-form/components/wrapper.less +++ b/src/components/seal-form/components/wrapper.less @@ -96,6 +96,10 @@ flex: 1; padding-block: 20px 0; + &.no-wrapper-style { + padding-block: 0; + } + .extra { position: absolute; right: 12px; diff --git a/src/components/seal-form/components/wrapper.tsx b/src/components/seal-form/components/wrapper.tsx index db6c350e..359ed2f9 100644 --- a/src/components/seal-form/components/wrapper.tsx +++ b/src/components/seal-form/components/wrapper.tsx @@ -4,8 +4,9 @@ import LabelInfo from './label-info'; import wrapperStyle from './wrapper.less'; interface WrapperProps { children: React.ReactNode; - label: React.ReactNode; - isFocus: boolean; + label?: React.ReactNode; + noWrapperStyle?: boolean; + isFocus?: boolean; status?: string; required?: boolean; description?: React.ReactNode; @@ -29,6 +30,7 @@ const Wrapper: React.FC = ({ extra, variant, addAfter, + noWrapperStyle, onClick }) => { return ( @@ -42,7 +44,12 @@ const Wrapper: React.FC = ({ className ? wrapperStyle[className] : '' )} > -
+
+ } + > + +
+ + + name="partial_offload" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + > + + + {intl.formatMessage({ + id: 'resources.form.enablePartialOffload' + })} + + + + +
+
+ + + name="distributed_inference_across_workers" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + > + + + {intl.formatMessage({ + id: 'resources.form.enableDistributedInferenceAcrossWorkers' + })} + + + + +
+ name="worker_selector"> + + {intl.formatMessage({ + id: 'resources.form.workerSelector.description' + })} + + } + > + + + ); + return [ + { + key: '1', + label: ( + + {intl.formatMessage({ id: 'resources.form.advanced' })} + + ), + children + } + ]; + }, []); + useEffect(() => { handleOnSelectModel(); }, [repo]); @@ -213,7 +369,13 @@ const DataForm: React.FC = forwardRef((props, ref) => { preserve={false} style={{ padding: '16px 24px' }} clearOnDestroy={true} - initialValues={{ replicas: 1, source: props.source }} + initialValues={{ + replicas: 1, + source: props.source, + placement_strategy: 'spread', + partial_offload: true, + distributed_inference_across_workers: true + }} > name="name" @@ -262,31 +424,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { } {renderFieldsBySource()} - - name="replicas" - rules={[ - { - required: true, - message: intl.formatMessage( - { - id: 'common.form.rule.input' - }, - { - name: intl.formatMessage({ id: 'models.form.replicas' }) - } - ) - } - ]} - > - - name="description"> = forwardRef((props, ref) => { })} > + + ( + + )} + items={collapseItems} + > ); }); diff --git a/src/pages/llmodels/components/update-modal.tsx b/src/pages/llmodels/components/update-modal.tsx index 109c2b57..a4c13680 100644 --- a/src/pages/llmodels/components/update-modal.tsx +++ b/src/pages/llmodels/components/update-modal.tsx @@ -1,17 +1,23 @@ +import LabelSelector from '@/components/label-selector'; import ModalFooter from '@/components/modal-footer'; import SealAutoComplete from '@/components/seal-form/auto-complete'; +import FormItemWrapper from '@/components/seal-form/components/wrapper'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { convertFileSize } from '@/utils'; +import { RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Form, Modal } from 'antd'; +import { Checkbox, Collapse, Form, Modal, Typography } from 'antd'; import _ from 'lodash'; -import { memo, useCallback, useEffect, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import SimpleBar from 'simplebar-react'; +import 'simplebar-react/dist/simplebar.min.css'; import { queryHuggingfaceModelFiles, queryHuggingfaceModels } from '../apis'; -import { modelSourceMap } from '../config'; +import { modelSourceMap, placementStrategyOptions } from '../config'; import { FormData, ListItem } from '../config/types'; +import dataformStyles from '../style/data-form.less'; type AddModalProps = { title: string; @@ -35,13 +41,14 @@ const sourceOptions = [ } ]; -const AddModal: React.FC = (props) => { +const UpdateModal: React.FC = (props) => { console.log('addmodel===='); const { title, action, open, onOk, onCancel } = props || {}; const [form] = Form.useForm(); const intl = useIntl(); const [loading, setLoading] = useState(false); const modelSource = Form.useWatch('source', form); + const wokerSelector = Form.useWatch('worker_selector', form); const [repoOptions, setRepoOptions] = useState< { label: string; value: string }[] >([]); @@ -254,6 +261,14 @@ const AddModal: React.FC = (props) => { } }; + const handleWorkerLabelsChange = useCallback( + (labels: Record) => { + console.log('labels========', labels); + form.setFieldValue('worker_selector', labels); + }, + [] + ); + const handleOnSelectModel = useCallback((item: any) => { const repo = item.name; if (form.getFieldValue('source') === modelSourceMap.huggingface_value) { @@ -272,71 +287,9 @@ const AddModal: React.FC = (props) => { form.submit(); }; - return ( - - } - > -
- - name="name" - rules={[ - { - required: true, - message: intl.formatMessage( - { - id: 'common.form.rule.input' - }, - { name: intl.formatMessage({ id: 'common.table.name' }) } - ) - } - ]} - > - - - - name="source" - rules={[ - { - required: true, - message: intl.formatMessage( - { - id: 'common.form.rule.select' - }, - { name: intl.formatMessage({ id: 'models.form.source' }) } - ) - } - ]} - > - {action === PageAction.EDIT && ( - - )} - - {renderFieldsBySource()} + const collapseItems = useMemo(() => { + const children = ( + <> name="replicas" rules={[ @@ -346,7 +299,9 @@ const AddModal: React.FC = (props) => { { id: 'common.form.rule.input' }, - { name: intl.formatMessage({ id: 'models.form.replicas' }) } + { + name: intl.formatMessage({ id: 'models.form.replicas' }) + } ) } ]} @@ -360,16 +315,229 @@ const AddModal: React.FC = (props) => { min={0} > - name="description"> - name="placement_strategy"> + + options={placementStrategyOptions} + description={ +
+
+ + Spread: + + + {intl.formatMessage({ + id: 'resources.form.spread.tips' + })} + +
+
+ + Binpack: + + + {intl.formatMessage({ + id: 'resources.form.binpack.tips' + })} + +
+
+ } + > - +
+ + + name="partial_offload" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + > + + + {intl.formatMessage({ + id: 'resources.form.enablePartialOffload' + })} + + + + +
+
+ + + name="distributed_inference_across_workers" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + > + + + {intl.formatMessage({ + id: 'resources.form.enableDistributedInferenceAcrossWorkers' + })} + + + + +
+ name="worker_selector"> + + {intl.formatMessage({ + id: 'resources.form.workerSelector.description' + })} + + } + > + + + ); + return [ + { + key: '1', + label: ( + + {intl.formatMessage({ id: 'resources.form.advanced' })} + + ), + children + } + ]; + }, []); + + return ( + + } + > + +
+ + name="name" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.input' + }, + { name: intl.formatMessage({ id: 'common.table.name' }) } + ) + } + ]} + > + + + + name="source" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.select' + }, + { name: intl.formatMessage({ id: 'models.form.source' }) } + ) + } + ]} + > + {action === PageAction.EDIT && ( + + )} + + {renderFieldsBySource()} + name="description"> + + + ( + + )} + items={collapseItems} + > + +
); }; -export default memo(AddModal); +export default memo(UpdateModal); diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index 913b60a2..d174c6f5 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -147,3 +147,14 @@ export const ModelSortType = { downloads: 'downloads', lastModified: 'lastModified' }; + +export const placementStrategyOptions = [ + { + label: 'Spread', + value: 'spread' + }, + { + label: 'Binpack', + value: 'binpack' + } +]; diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index 843b3e58..681b0409 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -21,6 +21,10 @@ export interface FormData { huggingface_filename: string; s3_address: string; ollama_library_model_name: 'string'; + distributed_inference_across_workers?: boolean; + placement_strategy?: string; + partial_offload?: boolean; + worker_selector?: object; name: string; replicas: number; description: string; diff --git a/src/pages/llmodels/style/data-form.less b/src/pages/llmodels/style/data-form.less new file mode 100644 index 00000000..0b976da3 --- /dev/null +++ b/src/pages/llmodels/style/data-form.less @@ -0,0 +1,22 @@ +.advanced-collapse { + :global { + .ant-collapse-header { + display: flex; + align-items: center; + margin-bottom: 20px !important; + padding-inline: 5px !important; + padding-block: 5px !important; + border-radius: var(--border-radius-base) !important; + font-size: var(--font-size-middle) !important; + + &:hover { + background-color: var(--color-fill-sider) !important; + } + } + + .ant-collapse-content-box { + padding-inline: 0 !important; + padding-block: 0 !important; + } + } +} diff --git a/src/pages/resources/apis/index.ts b/src/pages/resources/apis/index.ts index 15c1c5af..9b335597 100644 --- a/src/pages/resources/apis/index.ts +++ b/src/pages/resources/apis/index.ts @@ -29,3 +29,10 @@ export async function deleteWorker(id: string | number) { method: 'DELETE' }); } + +export async function updateWorker(id: string | number, data: any) { + return request(`${WORKERS_API}/${id}`, { + method: 'PUT', + data + }); +} diff --git a/src/pages/resources/components/update-labels.tsx b/src/pages/resources/components/update-labels.tsx new file mode 100644 index 00000000..c768ab30 --- /dev/null +++ b/src/pages/resources/components/update-labels.tsx @@ -0,0 +1,111 @@ +import LabelSelector from '@/components/label-selector'; +import ModalFooter from '@/components/modal-footer'; +import SealInput from '@/components/seal-form/seal-input'; +import { useIntl } from '@umijs/max'; +import { Form, Modal } from 'antd'; +import React from 'react'; +import SimpleBar from 'simplebar-react'; +import 'simplebar-react/dist/simplebar.min.css'; + +type ViewModalProps = { + open: boolean; + onCancel: () => void; + onOk: (values: FormData) => Promise; + data: { + name: string; + labels: object; + }; +}; +interface FormData { + labels: object; + name: string; +} + +const UpdateLabels: React.FC = (props) => { + const { open, onCancel, data, onOk } = props || {}; + const intl = useIntl(); + const [form] = Form.useForm(); + const labels = Form.useWatch('labels', form); + + const handleLabelsChange = (labels: object) => { + form.setFieldValue('labels', labels); + }; + + const handleSumit = () => { + form.submit(); + }; + + return ( + + } + > + +
+ name="name"> + + + name="labels"> + + + +
+
+ ); +}; + +export default React.memo(UpdateLabels); diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index 7fd875dd..d7322d55 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -1,4 +1,6 @@ +import AutoTooltip from '@/components/auto-tooltip'; import DeleteModal from '@/components/delete-modal'; +import DropdownButtons from '@/components/drop-down-buttons'; import PageTools from '@/components/page-tools'; import ProgressBar from '@/components/progress-bar'; import StatusTag from '@/components/status-tag'; @@ -10,19 +12,37 @@ import { DeleteOutlined, InfoCircleOutlined, PlusOutlined, - SyncOutlined + SyncOutlined, + TagsOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Input, Space, Table, Tooltip } from 'antd'; +import { Button, Input, Space, Table, Tooltip, message } from 'antd'; import _ from 'lodash'; import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import { deleteWorker, queryWorkersList } from '../apis'; +import { deleteWorker, queryWorkersList, updateWorker } from '../apis'; import { WorkerStatusMapValue, status } from '../config'; import { Filesystem, GPUDeviceItem, ListItem } from '../config/types'; import AddWorker from './add-worker'; +import UpdateLabels from './update-labels'; const { Column } = Table; +const ActionList = [ + { + label: 'resources.button.edittags', + key: 'edit', + icon: + }, + { + label: 'common.button.delete', + key: 'delete', + props: { + danger: true + }, + icon: + } +]; + const Resources: React.FC = () => { console.log('resources======workers'); @@ -47,6 +67,13 @@ const Resources: React.FC = () => { perPage: 10, search: '' }); + const [updateLabelsData, setUpdateLabelsData] = useState<{ + open: boolean; + data: ListItem; + }>({ + open: false, + data: {} as ListItem + }); const fetchData = useCallback(async () => { setDataSource((pre) => { @@ -164,6 +191,52 @@ const Resources: React.FC = () => { 0 ); }; + + const handleUpdateLabelsOk = useCallback( + async (values: Record) => { + try { + console.log('updateLabelsData.data', updateLabelsData.data); + await updateWorker(updateLabelsData.data.id, { + ...updateLabelsData.data, + labels: values.labels + }); + message.success(intl.formatMessage({ id: 'common.message.success' })); + fetchData(); + setUpdateLabelsData({ open: false, data: {} as ListItem }); + } catch (error) { + console.log('error', error); + } + }, + [updateLabelsData, fetchData] + ); + + const handleCancelUpdateLabels = useCallback(() => { + setUpdateLabelsData({ + ...updateLabelsData, + open: false + }); + }, []); + + const handleUpdateLabels = (record: ListItem) => { + console.log('record', record); + setUpdateLabelsData({ + open: true, + data: { + ...record + } + }); + }; + + const handleSelect = (val: any, record: ListItem) => { + if (val === 'edit') { + handleUpdateLabels(record); + return; + } + if (val === 'delete') { + handleDelete(record); + } + }; + useEffect(() => { fetchData(); }, [queryParams]); @@ -236,6 +309,32 @@ const Resources: React.FC = () => { dataIndex="name" key="name" /> + { + return ( +
+ {_.map(record.labels, (item: any, index: string) => { + return ( + + {index}:{item} + + ); + })} +
+ ); + }} + /> { key="operation" render={(text, record: ListItem) => { return ( - - - - - + handleSelect(val, record)} + > ); }} /> setOpen(false)}> + ); };