feat: community backends page
This commit is contained in:
@@ -72,6 +72,7 @@ interface Configuration {
|
|||||||
checked: boolean;
|
checked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default need to pass content and operation
|
||||||
const DeleteModal = forwardRef((props, ref) => {
|
const DeleteModal = forwardRef((props, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
|||||||
// import './iconfont/iconfont.js';
|
// import './iconfont/iconfont.js';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_339f1qeidmn.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_mis8x88kojd.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -156,6 +156,11 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
|
|||||||
{handleClickPrimary ? (
|
{handleClickPrimary ? (
|
||||||
actionType === 'dropdown' ? (
|
actionType === 'dropdown' ? (
|
||||||
<DropDownActions
|
<DropDownActions
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
minWidth: 140
|
||||||
|
}
|
||||||
|
}}
|
||||||
menu={{
|
menu={{
|
||||||
items: actionItems,
|
items: actionItems,
|
||||||
onClick: handleClickPrimary
|
onClick: handleClickPrimary
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Skeleton } from 'antd';
|
import { Skeleton } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
import ResizeContainer from '../resize-container';
|
import ResizeContainer from '../resize-container';
|
||||||
|
|
||||||
|
const SkeletonWrapper = styled.div`
|
||||||
|
.ant-skeleton-paragraph {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface CatalogSkeltonProps {
|
interface CatalogSkeltonProps {
|
||||||
skeletonProps?: any;
|
skeletonProps?: any;
|
||||||
skeletonStyle?: React.CSSProperties;
|
skeletonStyle?: React.CSSProperties;
|
||||||
@@ -12,19 +19,21 @@ const CardSkelton: React.FC<CatalogSkeltonProps> = (props) => {
|
|||||||
<ResizeContainer
|
<ResizeContainer
|
||||||
dataList={Array(6).fill({ label: 'skeleton' })}
|
dataList={Array(6).fill({ label: 'skeleton' })}
|
||||||
renderItem={() => (
|
renderItem={() => (
|
||||||
<Skeleton
|
<SkeletonWrapper>
|
||||||
avatar={{
|
<Skeleton
|
||||||
size: 32
|
avatar={{
|
||||||
}}
|
size: 32
|
||||||
paragraph={{ rows: 2 }}
|
}}
|
||||||
style={{
|
paragraph={{ rows: 2 }}
|
||||||
border: '1px solid var(--ant-color-border)',
|
style={{
|
||||||
borderRadius: 'var(--border-radius-base)',
|
border: '1px solid var(--ant-color-border)',
|
||||||
padding: '16px 16px',
|
borderRadius: 'var(--border-radius-base)',
|
||||||
...props.skeletonStyle
|
padding: '16px 16px',
|
||||||
}}
|
...props.skeletonStyle
|
||||||
{...(props.skeletonProps || {})}
|
}}
|
||||||
></Skeleton>
|
{...(props.skeletonProps || {})}
|
||||||
|
></Skeleton>
|
||||||
|
</SkeletonWrapper>
|
||||||
)}
|
)}
|
||||||
></ResizeContainer>
|
></ResizeContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,5 +41,8 @@ export default {
|
|||||||
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.',
|
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.',
|
||||||
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
||||||
'backend.recommendModels': 'Recommended Models',
|
'backend.recommendModels': 'Recommended Models',
|
||||||
'backend.filter.source': 'Filter by source'
|
'backend.filter.source': 'Filter by source',
|
||||||
|
'backend.add.custom': 'Custom',
|
||||||
|
'backend.add.community': 'Community Repository',
|
||||||
|
'backend.community.title': 'Community Backends'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,5 +41,8 @@ export default {
|
|||||||
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.',
|
'If specified, the ENTRYPOINT defined in the image will be ignored, and the command below will be used as the container startup entrypoint.',
|
||||||
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
||||||
'backend.recommendModels': 'Recommended Models',
|
'backend.recommendModels': 'Recommended Models',
|
||||||
'backend.filter.source': 'Filter by source'
|
'backend.filter.source': 'Filter by source',
|
||||||
|
'backend.add.custom': 'Custom',
|
||||||
|
'backend.add.community': 'Community Repository',
|
||||||
|
'backend.community.title': 'Community Backends'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,11 +41,17 @@ export default {
|
|||||||
'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.',
|
'Если указано, ENTRYPOINT, заданный в образе, будет проигнорирован, а вместо него для запуска контейнера будет использована команда ниже.',
|
||||||
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
'backend.form.defaultEnvironment': 'Default Environment Variables',
|
||||||
'backend.recommendModels': 'Recommended Models',
|
'backend.recommendModels': 'Recommended Models',
|
||||||
'backend.filter.source': 'Filter by source'
|
'backend.filter.source': 'Filter by source',
|
||||||
|
'backend.add.custom': 'Custom',
|
||||||
|
'backend.add.community': 'Community Repository',
|
||||||
|
'backend.community.title': 'Community Backends'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'backend.form.defaultEnvironment': 'Default Environment Variables',
|
// 1. 'backend.form.defaultEnvironment': 'Default Environment Variables',
|
||||||
// 2. 'backend.recommendModels': 'Recommended Models',
|
// 2. 'backend.recommendModels': 'Recommended Models',
|
||||||
// 3. 'backend.filter.source': 'Filter by source'
|
// 3. 'backend.filter.source': 'Filter by source',
|
||||||
|
// 4. 'backend.add.custom': 'Custom',
|
||||||
|
// 5. 'backend.add.community': 'Community Repository',
|
||||||
|
// 6. 'backend.community.title': 'Community Backends'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -38,5 +38,8 @@ export default {
|
|||||||
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。',
|
'如指定,镜像中定义的 ENTRYPOINT 将被忽略,下面的命令将作为容器启动入口命令使用。',
|
||||||
'backend.form.defaultEnvironment': '默认环境变量',
|
'backend.form.defaultEnvironment': '默认环境变量',
|
||||||
'backend.recommendModels': '推荐模型',
|
'backend.recommendModels': '推荐模型',
|
||||||
'backend.filter.source': '按来源过滤'
|
'backend.filter.source': '按来源过滤',
|
||||||
|
'backend.add.custom': '自定义',
|
||||||
|
'backend.add.community': '社区仓库',
|
||||||
|
'backend.community.title': '社区后端'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ export const INFERENCE_BACKEND_API = '/inference-backends';
|
|||||||
|
|
||||||
export const FROM_YAML_API = '/from-yaml';
|
export const FROM_YAML_API = '/from-yaml';
|
||||||
|
|
||||||
export async function queryBackendsList(params: Global.SearchParams) {
|
export async function queryBackendsList(
|
||||||
|
params: Global.SearchParams & { community?: number }
|
||||||
|
) {
|
||||||
return request<Global.PageResponse<ListItem>>(`${INFERENCE_BACKEND_API}`, {
|
return request<Global.PageResponse<ListItem>>(`${INFERENCE_BACKEND_API}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
||||||
|
import ColumnWrapper from '@/pages/_components/column-wrapper';
|
||||||
|
import Separator from '@/pages/llmodels/components/separator';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
import useEnableBackend from '../services/use-enable-backend';
|
||||||
|
import BackendDetail from './backend-detail';
|
||||||
|
import CommunityBackends from './commnity-backends';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AddCommunityModal: React.FC<{
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onRefresh?: () => void;
|
||||||
|
}> = ({ open, onClose, onRefresh }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { handleEnableBackend } = useEnableBackend();
|
||||||
|
const [currentData, setCurrentData] = React.useState<any>(null);
|
||||||
|
|
||||||
|
const handleOnEnable = async (checked: boolean, data: ListItem) => {
|
||||||
|
await handleEnableBackend({
|
||||||
|
id: data.id,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
enabled: checked
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onRefresh?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnClick = (data: ListItem) => {
|
||||||
|
setCurrentData(data);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<GSDrawer
|
||||||
|
open={open}
|
||||||
|
title={intl.formatMessage({ id: 'backend.community.title' })}
|
||||||
|
footer={false}
|
||||||
|
onClose={onClose}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
closeIcon={false}
|
||||||
|
maskClosable={false}
|
||||||
|
keyboard={false}
|
||||||
|
styles={{
|
||||||
|
wrapper: { width: 'max(50vw, 900px)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ColumnWrapper
|
||||||
|
maxHeight={'calc(100vh - 90px)'}
|
||||||
|
styles={{
|
||||||
|
container: {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CommunityBackends
|
||||||
|
onClick={handleOnClick}
|
||||||
|
onEnable={handleOnEnable}
|
||||||
|
></CommunityBackends>
|
||||||
|
</ColumnWrapper>
|
||||||
|
<Separator showArrow={false}></Separator>
|
||||||
|
</div>
|
||||||
|
<ColumnWrapper
|
||||||
|
maxHeight={'calc(100vh - 90px)'}
|
||||||
|
styles={{
|
||||||
|
container: {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BackendDetail
|
||||||
|
onEnable={handleOnEnable}
|
||||||
|
currentData={currentData}
|
||||||
|
></BackendDetail>
|
||||||
|
</ColumnWrapper>
|
||||||
|
</Container>
|
||||||
|
</GSDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCommunityModal;
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
|
import { BulbOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Tag, Typography } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { generateIcon } from '../components/backend-card';
|
||||||
|
|
||||||
|
const Title = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Subtitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 1px solid var(--ant-color-split);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
.icon {
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Section = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
padding: 0px 8px;
|
||||||
|
border-radius: var(--ant-border-radius);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const UL = styled.ul`
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 16px;
|
||||||
|
list-style-type: disc;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
.label {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BackendDetail: React.FC<{
|
||||||
|
onEnable: (checked: boolean, data: any) => void;
|
||||||
|
onDisable?: () => void;
|
||||||
|
currentData?: any;
|
||||||
|
}> = ({ onEnable, onDisable, currentData }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const renderSupportedFrameworks = () => {
|
||||||
|
const frameworks = currentData?.framework_index_map || {};
|
||||||
|
if (Object.keys(frameworks).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<UL>
|
||||||
|
{Object.entries(frameworks as Record<string, string[]>).map(
|
||||||
|
([key, value]) => {
|
||||||
|
return (
|
||||||
|
<li key={key}>
|
||||||
|
<div className="label">{_.upperCase(key)}</div>
|
||||||
|
{/* <Flex gap={8} wrap="wrap" style={{ marginBlock: 8 }}>
|
||||||
|
{value?.map?.((v: string, index: number) => (
|
||||||
|
<Tag key={index} style={{ margin: 0 }}>
|
||||||
|
{v}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Flex> */}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</UL>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title>
|
||||||
|
<span className="text">
|
||||||
|
{generateIcon(currentData || {}, 32)}
|
||||||
|
<AutoTooltip ghost>{currentData?.backend_name}</AutoTooltip>
|
||||||
|
</span>
|
||||||
|
{currentData && (
|
||||||
|
<span>
|
||||||
|
{currentData?.enabled ? (
|
||||||
|
<Tag color="geekblue" variant="filled">
|
||||||
|
{intl.formatMessage({ id: 'common.status.enabled' })}
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="middle"
|
||||||
|
onClick={() => onEnable(true, currentData)}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.button.enable' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Title>
|
||||||
|
{Object.keys(currentData?.framework_index_map || {}).length > 0 && (
|
||||||
|
<Section>
|
||||||
|
<Subtitle>
|
||||||
|
<BulbOutlined className="icon" />
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'backend.availableFrameworks' })}
|
||||||
|
</span>
|
||||||
|
</Subtitle>
|
||||||
|
<Content>{renderSupportedFrameworks()}</Content>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
{/* <Section>
|
||||||
|
<Subtitle>
|
||||||
|
<BulbOutlined className="icon" />
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'backend.form.defaultExecuteCommand'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</Subtitle>
|
||||||
|
<Content style={{ padding: 0, border: 'none' }}>
|
||||||
|
<HighlightCode
|
||||||
|
code={currentData?.default_run_command}
|
||||||
|
></HighlightCode>
|
||||||
|
</Content>
|
||||||
|
</Section> */}
|
||||||
|
{currentData?.description && (
|
||||||
|
<Section>
|
||||||
|
<Subtitle>
|
||||||
|
<BulbOutlined className="icon" />
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
</span>
|
||||||
|
</Subtitle>
|
||||||
|
<Content>
|
||||||
|
<Typography.Text>{currentData?.description}</Typography.Text>
|
||||||
|
</Content>
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BackendDetail;
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||||
|
import useTableFetch from '@/hooks/use-table-fetch';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Input, Switch, Tooltip } from 'antd';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ScrollerContext } from '../../_components/infinite-scroller/use-scroller-context';
|
||||||
|
import NoResult from '../../_components/no-result';
|
||||||
|
import { INFERENCE_BACKEND_API, queryBackendsList } from '../apis';
|
||||||
|
import BackendCard from '../components/backend-card';
|
||||||
|
import BackendCardList from '../components/backend-list';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const FilterBox = styled.div`
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background-color: var(--ant-color-bg-container);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CommunityBackends: React.FC<{
|
||||||
|
onEnable: (checked: boolean, data: ListItem) => void;
|
||||||
|
onClick: (data: ListItem) => void;
|
||||||
|
}> = ({ onEnable, onClick }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [currentData, setCurrentData] = React.useState<ListItem | null>(null);
|
||||||
|
const { dataSource, loadMore, queryParams, handleSearch, handleNameChange } =
|
||||||
|
useTableFetch<ListItem>({
|
||||||
|
fetchAPI: queryBackendsList,
|
||||||
|
API: INFERENCE_BACKEND_API,
|
||||||
|
watch: false,
|
||||||
|
isInfiniteScroll: true,
|
||||||
|
contentForDelete: 'backends.title',
|
||||||
|
defaultQueryParams: {
|
||||||
|
perPage: 24,
|
||||||
|
community: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOnClickItem = (data: ListItem) => {
|
||||||
|
onClick(data);
|
||||||
|
setCurrentData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEnable = async (checked: boolean, data: ListItem) => {
|
||||||
|
await onEnable(checked, data);
|
||||||
|
await handleSearch();
|
||||||
|
if (data.id === currentData?.id) {
|
||||||
|
handleOnClickItem({
|
||||||
|
...data,
|
||||||
|
enabled: checked
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionsRenderer = (data: ListItem) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
data.enabled
|
||||||
|
? false
|
||||||
|
: intl.formatMessage({ id: 'common.button.enable' })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
{data.enabled ? (
|
||||||
|
<ThemeTag style={{ margin: 0 }} color="geekblue" variant="filled">
|
||||||
|
{intl.formatMessage({ id: 'common.status.enabled' })}
|
||||||
|
</ThemeTag>
|
||||||
|
) : (
|
||||||
|
<span onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Switch
|
||||||
|
onChange={(checked) => handleOnEnable(checked, data)}
|
||||||
|
size="small"
|
||||||
|
></Switch>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataSource.dataList.length > 0 && dataSource.loadend && !currentData) {
|
||||||
|
handleOnClickItem(dataSource.dataList[0]);
|
||||||
|
}
|
||||||
|
}, [dataSource.dataList, dataSource.loadend]);
|
||||||
|
|
||||||
|
const renderItem = (item: ListItem) => {
|
||||||
|
return (
|
||||||
|
<BackendCard
|
||||||
|
active={currentData?.id === item.id}
|
||||||
|
data={item}
|
||||||
|
layout="community"
|
||||||
|
onClick={handleOnClickItem}
|
||||||
|
actionsRenderer={actionsRenderer}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FilterBox>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<SearchOutlined
|
||||||
|
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||||
|
></SearchOutlined>
|
||||||
|
}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'common.filter.name'
|
||||||
|
})}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
|
></Input>
|
||||||
|
</FilterBox>
|
||||||
|
<ScrollerContext.Provider
|
||||||
|
value={{
|
||||||
|
total: dataSource.totalPage,
|
||||||
|
current: queryParams.page!,
|
||||||
|
loading: dataSource.loading,
|
||||||
|
refresh: loadMore,
|
||||||
|
throttleDelay: 300
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BackendCardList
|
||||||
|
dataList={dataSource.dataList}
|
||||||
|
loading={dataSource.loading}
|
||||||
|
activeId={false}
|
||||||
|
isFirst={!dataSource.loadend}
|
||||||
|
renderItem={renderItem}
|
||||||
|
></BackendCardList>
|
||||||
|
<NoResult
|
||||||
|
loading={dataSource.loading}
|
||||||
|
loadend={dataSource.loadend}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
image={<IconFont type="icon-models" />}
|
||||||
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
|
noFoundText={intl.formatMessage({
|
||||||
|
id: 'noresult.backend.nofound'
|
||||||
|
})}
|
||||||
|
></NoResult>
|
||||||
|
</ScrollerContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommunityBackends;
|
||||||
@@ -124,7 +124,7 @@ const InfoItem = styled.div`
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
grid-template-columns: max-content 1fr minmax(0, max-content);
|
grid-template-columns: max-content 1fr minmax(0, max-content);
|
||||||
align-items: center;
|
align-items: end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--ant-color-text-tertiary);
|
color: var(--ant-color-text-tertiary);
|
||||||
.icon {
|
.icon {
|
||||||
@@ -140,10 +140,37 @@ const InfoItem = styled.div`
|
|||||||
interface BackendCardProps {
|
interface BackendCardProps {
|
||||||
onClick?: (data: any) => void;
|
onClick?: (data: any) => void;
|
||||||
onSelect?: (item: any) => void;
|
onSelect?: (item: any) => void;
|
||||||
|
actionsRenderer?: (data: ListItem) => React.ReactNode;
|
||||||
data: ListItem;
|
data: ListItem;
|
||||||
|
layout?: 'default' | 'community';
|
||||||
|
active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
export const generateIcon = (data: ListItem, height = 20) => {
|
||||||
|
if (builtInBackendLogos[data.backend_name]) {
|
||||||
|
return <img src={builtInBackendLogos[data.backend_name]} height={height} />;
|
||||||
|
}
|
||||||
|
if (data.icon) {
|
||||||
|
return <img src={data.icon} height={height} />;
|
||||||
|
}
|
||||||
|
const color = customColors[data.id % customColors.length];
|
||||||
|
const icon = customIcons[data.id % customIcons.length];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagInner color={color} variant="filled">
|
||||||
|
{icon}
|
||||||
|
</TagInner>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BackendCard: React.FC<BackendCardProps> = ({
|
||||||
|
data,
|
||||||
|
onClick,
|
||||||
|
onSelect,
|
||||||
|
actionsRenderer,
|
||||||
|
layout = 'default',
|
||||||
|
active
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleOnSelect = (item: any) => {
|
const handleOnSelect = (item: any) => {
|
||||||
@@ -152,23 +179,11 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onSelect?.({ action: 'view_versions', data: data });
|
onSelect?.({ action: 'view_versions', data: data });
|
||||||
|
onClick?.(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIcon = () => {
|
const renderIcon = () => {
|
||||||
if (builtInBackendLogos[data.backend_name]) {
|
return generateIcon(data);
|
||||||
return <img src={builtInBackendLogos[data.backend_name]} height={20} />;
|
|
||||||
}
|
|
||||||
if (data.icon) {
|
|
||||||
return <img src={data.icon} height={20} />;
|
|
||||||
}
|
|
||||||
const color = customColors[data.id % customColors.length];
|
|
||||||
const icon = customIcons[data.id % customIcons.length];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TagInner color={color} variant="filled">
|
|
||||||
{icon}
|
|
||||||
</TagInner>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
@@ -180,7 +195,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
});
|
});
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const onClick = (e: React.MouseEvent) => {
|
const handleonClickAction = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -278,6 +293,9 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderSource = () => {
|
const renderSource = () => {
|
||||||
|
if (layout === 'community') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const source = data.is_built_in
|
const source = data.is_built_in
|
||||||
? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || ''
|
? BackendSourceLabelMap[BackendSourceValueMap.BUILTIN] || ''
|
||||||
: BackendSourceLabelMap[data.backend_source] || '';
|
: BackendSourceLabelMap[data.backend_source] || '';
|
||||||
@@ -285,7 +303,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ThemeTag
|
<Tag
|
||||||
color={
|
color={
|
||||||
TagColorMap[
|
TagColorMap[
|
||||||
data.is_built_in
|
data.is_built_in
|
||||||
@@ -294,7 +312,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
className="font-400"
|
className="font-400"
|
||||||
variant="outlined"
|
variant="filled"
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 'var(--ant-border-radius)',
|
borderRadius: 'var(--ant-border-radius)',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
@@ -304,7 +322,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: source
|
id: source
|
||||||
})}
|
})}
|
||||||
</ThemeTag>
|
</Tag>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -330,49 +348,58 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderActions = () => {
|
||||||
|
return actionsRenderer ? (
|
||||||
|
actionsRenderer(data)
|
||||||
|
) : (
|
||||||
|
<span onClick={handleonClickAction} className="operations">
|
||||||
|
<DropDownActions
|
||||||
|
menu={{
|
||||||
|
items: actions,
|
||||||
|
onClick: handleOnSelect
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
icon={<IconFont type="icon-more"></IconFont>}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
></Button>
|
||||||
|
</DropDownActions>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCard
|
<StyledCard
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
clickable={true}
|
clickable={true}
|
||||||
hoverable={true}
|
hoverable={true}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
height={164}
|
active={active}
|
||||||
|
height={136}
|
||||||
ghost
|
ghost
|
||||||
header={
|
header={
|
||||||
<Header>
|
<Header>
|
||||||
<div className="title">{renderIcon()}</div>
|
<div className="title">{renderIcon()}</div>
|
||||||
<span onClick={onClick} className="operations">
|
{renderActions()}
|
||||||
<DropDownActions
|
|
||||||
menu={{
|
|
||||||
items: actions,
|
|
||||||
onClick: handleOnSelect
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={<IconFont type="icon-more"></IconFont>}
|
|
||||||
size="small"
|
|
||||||
type="text"
|
|
||||||
></Button>
|
|
||||||
</DropDownActions>
|
|
||||||
</span>
|
|
||||||
</Header>
|
</Header>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Content>
|
<Content>
|
||||||
<CardName>
|
<CardName>
|
||||||
<span>{data.backend_name}</span>
|
<span>{data.backend_name}</span>
|
||||||
|
{renderSource()}
|
||||||
</CardName>
|
</CardName>
|
||||||
<SourceWrapper>
|
{/* <SourceWrapper>
|
||||||
<InfoItem>
|
<InfoItem>
|
||||||
<span className="label">
|
<span className="label">
|
||||||
<IconFont type="icon-source" className="icon" />
|
<IconFont type="icon-source" className="icon" />
|
||||||
<span>{intl.formatMessage({ id: 'models.form.source' })}:</span>
|
<span>{intl.formatMessage({ id: 'models.form.source' })}:</span>
|
||||||
</span>
|
</span>
|
||||||
{renderSource()}
|
|
||||||
{renderEnabledTag()}
|
{renderEnabledTag()}
|
||||||
</InfoItem>
|
</InfoItem>
|
||||||
{renderRecommendModels()}
|
{renderRecommendModels()}
|
||||||
</SourceWrapper>
|
</SourceWrapper> */}
|
||||||
{renderFrameworks()}
|
{renderFrameworks()}
|
||||||
</Content>
|
</Content>
|
||||||
</StyledCard>
|
</StyledCard>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ interface BackendListProps {
|
|||||||
activeId: Global.WithFalse<number>;
|
activeId: Global.WithFalse<number>;
|
||||||
isFirst: boolean;
|
isFirst: boolean;
|
||||||
onSelect?: (item: any) => void;
|
onSelect?: (item: any) => void;
|
||||||
|
renderItem?: (item: any) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListSkeleton: React.FC<{
|
const ListSkeleton: React.FC<{
|
||||||
@@ -70,7 +71,7 @@ const ListSkeleton: React.FC<{
|
|||||||
skeletonStyle={{
|
skeletonStyle={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 26
|
gap: 24
|
||||||
}}
|
}}
|
||||||
></CardSkeleton>
|
></CardSkeleton>
|
||||||
</SkeletonWrapper>
|
</SkeletonWrapper>
|
||||||
@@ -89,7 +90,8 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
|||||||
isFirst,
|
isFirst,
|
||||||
defaultSpan = 8,
|
defaultSpan = 8,
|
||||||
resizable = true,
|
resizable = true,
|
||||||
onSelect
|
onSelect,
|
||||||
|
renderItem
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
total,
|
total,
|
||||||
@@ -111,7 +113,13 @@ const CardList: React.FC<BackendListProps> = (props) => {
|
|||||||
defaultSpan={defaultSpan}
|
defaultSpan={defaultSpan}
|
||||||
resizable={resizable}
|
resizable={resizable}
|
||||||
dataList={dataList}
|
dataList={dataList}
|
||||||
renderItem={(item) => <BackendCard data={item} onSelect={onSelect} />}
|
renderItem={(item) =>
|
||||||
|
renderItem ? (
|
||||||
|
renderItem(item)
|
||||||
|
) : (
|
||||||
|
<BackendCard data={item} onSelect={onSelect} />
|
||||||
|
)
|
||||||
|
}
|
||||||
></ResizeContainer>
|
></ResizeContainer>
|
||||||
<ListSkeleton loading={loading} isFirst={isFirst} />
|
<ListSkeleton loading={loading} isFirst={isFirst} />
|
||||||
</InfiniteScroller>
|
</InfiniteScroller>
|
||||||
|
|||||||
@@ -82,26 +82,26 @@ export const backendActions = [
|
|||||||
locale: true,
|
locale: true,
|
||||||
icon: icons.Yaml
|
icon: icons.Yaml
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'common.button.enable',
|
// label: 'common.button.enable',
|
||||||
value: 'enable',
|
// value: 'enable',
|
||||||
key: 'enable',
|
// key: 'enable',
|
||||||
locale: true,
|
// locale: true,
|
||||||
icon: icons.Charger,
|
// icon: icons.Charger,
|
||||||
show: (record: any) =>
|
// show: (record: any) =>
|
||||||
!record.enabled &&
|
// !record.enabled &&
|
||||||
record.backend_source === BackendSourceValueMap.COMMUNITY
|
// record.backend_source === BackendSourceValueMap.COMMUNITY
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
label: 'common.button.disable',
|
// label: 'common.button.disable',
|
||||||
value: 'disable',
|
// value: 'disable',
|
||||||
key: 'disable',
|
// key: 'disable',
|
||||||
locale: true,
|
// locale: true,
|
||||||
icon: icons.Disabled,
|
// icon: icons.Disabled,
|
||||||
show: (record: any) =>
|
// show: (record: any) =>
|
||||||
record.enabled &&
|
// record.enabled &&
|
||||||
record.backend_source === BackendSourceValueMap.COMMUNITY
|
// record.backend_source === BackendSourceValueMap.COMMUNITY
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'common.button.delete',
|
label: 'common.button.delete',
|
||||||
value: 'delete',
|
value: 'delete',
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { PageActionType } from '@/config/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const useCommunityBackend = () => {
|
||||||
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
action: PageActionType;
|
||||||
|
title: string;
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const openModal = (action: PageActionType, title: string) => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: true,
|
||||||
|
title: title,
|
||||||
|
action
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openCommunityModalStatus: openModalStatus,
|
||||||
|
setOpenCommunityModalStatus: setOpenModalStatus,
|
||||||
|
openCommunityModal: openModal,
|
||||||
|
closeCommunityModal: closeModal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCommunityBackend;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
import useCommunityBackend from './use-community-backend';
|
||||||
|
import useCustomBackend from './use-custom-backend';
|
||||||
|
|
||||||
|
const useCreateBackend = () => {
|
||||||
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
|
const { openCommunityModal, closeCommunityModal, openCommunityModalStatus } =
|
||||||
|
useCommunityBackend();
|
||||||
|
const { openCustomModal, closeCustomModal, openCustomModalStatus } =
|
||||||
|
useCustomBackend();
|
||||||
|
|
||||||
|
const addItems = [
|
||||||
|
{
|
||||||
|
label: 'backend.add.community',
|
||||||
|
locale: true,
|
||||||
|
key: 'community',
|
||||||
|
value: 'community',
|
||||||
|
icon: <IconFont type="icon-public" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'backend.add.custom',
|
||||||
|
locale: true,
|
||||||
|
key: 'custom',
|
||||||
|
value: 'custom',
|
||||||
|
icon: <IconFont type="icon-person" />
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleEditBackend = (
|
||||||
|
action: PageAction,
|
||||||
|
title: string,
|
||||||
|
row: ListItem
|
||||||
|
) => {
|
||||||
|
openCustomModal(action, title, row);
|
||||||
|
saveScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = (type: 'community' | 'custom') => {
|
||||||
|
if (type === 'community') {
|
||||||
|
closeCommunityModal();
|
||||||
|
} else {
|
||||||
|
closeCustomModal();
|
||||||
|
}
|
||||||
|
restoreScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddBackend = (type: 'community' | 'custom') => {
|
||||||
|
if (type === 'community') {
|
||||||
|
openCommunityModal(PageAction.CREATE, 'Add Community Backend');
|
||||||
|
} else {
|
||||||
|
openCustomModal(PageAction.CREATE, 'Add Custom Backend');
|
||||||
|
}
|
||||||
|
saveScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeBackendModal: handleCloseModal,
|
||||||
|
addBackend: handleAddBackend,
|
||||||
|
editBackend: handleEditBackend,
|
||||||
|
openBackendModalStatus: openCustomModalStatus, // for create custom backend and edit
|
||||||
|
openCommunityModalStatus: openCommunityModalStatus, // for create community backend
|
||||||
|
addActions: addItems
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCreateBackend;
|
||||||
+6
-10
@@ -1,11 +1,9 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
const useCreateBackend = () => {
|
const useCustomBackend = () => {
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
|
||||||
const [openModalStatus, setOpenModalStatus] = useState<{
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
action: PageActionType;
|
action: PageActionType;
|
||||||
@@ -26,7 +24,6 @@ const useCreateBackend = () => {
|
|||||||
action,
|
action,
|
||||||
currentData: row
|
currentData: row
|
||||||
});
|
});
|
||||||
saveScrollHeight();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
@@ -36,15 +33,14 @@ const useCreateBackend = () => {
|
|||||||
currentData: undefined,
|
currentData: undefined,
|
||||||
title: ''
|
title: ''
|
||||||
});
|
});
|
||||||
restoreScrollHeight();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openBackendModalStatus: openModalStatus,
|
openCustomModalStatus: openModalStatus,
|
||||||
setOpenBackendModalStatus: setOpenModalStatus,
|
setOpenCustomModalStatus: setOpenModalStatus,
|
||||||
openBackendModal: openModal,
|
openCustomModal: openModal,
|
||||||
closeBackendModal: closeModal
|
closeCustomModal: closeModal
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useCreateBackend;
|
export default useCustomBackend;
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
updateBackend,
|
updateBackend,
|
||||||
updateBackendFromYAML
|
updateBackendFromYAML
|
||||||
} from './apis';
|
} from './apis';
|
||||||
|
import AddCommunityModal from './community/add-community-modal';
|
||||||
import AddModal from './components/add-modal';
|
import AddModal from './components/add-modal';
|
||||||
import BackendCardList from './components/backend-list';
|
import BackendCardList from './components/backend-list';
|
||||||
import VersionInfoModal from './components/version-info-modal';
|
import VersionInfoModal from './components/version-info-modal';
|
||||||
@@ -63,8 +64,14 @@ const BackendList = () => {
|
|||||||
currentData?: ListItem;
|
currentData?: ListItem;
|
||||||
}>({ open: false });
|
}>({ open: false });
|
||||||
const { handleEnableBackend } = useEnableBackend();
|
const { handleEnableBackend } = useEnableBackend();
|
||||||
const { openBackendModal, closeBackendModal, openBackendModalStatus } =
|
const {
|
||||||
useCreateBackend();
|
addBackend,
|
||||||
|
editBackend,
|
||||||
|
closeBackendModal,
|
||||||
|
openCommunityModalStatus,
|
||||||
|
openBackendModalStatus,
|
||||||
|
addActions
|
||||||
|
} = useCreateBackend();
|
||||||
|
|
||||||
// built_in_version_configs is read-only, but needs to be included when updating
|
// built_in_version_configs is read-only, but needs to be included when updating
|
||||||
const handleOnSubmit = async (values: FormData) => {
|
const handleOnSubmit = async (values: FormData) => {
|
||||||
@@ -91,7 +98,7 @@ const BackendList = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
closeBackendModal();
|
closeBackendModal('custom');
|
||||||
handleSearch();
|
handleSearch();
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
@@ -116,7 +123,7 @@ const BackendList = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
closeBackendModal();
|
closeBackendModal('custom');
|
||||||
handleSearch();
|
handleSearch();
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
@@ -128,18 +135,54 @@ const BackendList = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddBackend = () => {
|
const handleAddBackend = (item: { key: 'community' | 'custom' }) => {
|
||||||
openBackendModal(PageAction.CREATE, '');
|
addBackend(item.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisableCommunityBackend = async (item: any) => {
|
||||||
|
// use disable for community backend deletion
|
||||||
|
handleEnableBackend({
|
||||||
|
id: item.data.id,
|
||||||
|
data: {
|
||||||
|
...item.data,
|
||||||
|
enabled: item.action === 'enable'
|
||||||
|
},
|
||||||
|
showMessage: false
|
||||||
|
});
|
||||||
|
// delay 200ms to refresh list
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 300);
|
||||||
|
});
|
||||||
|
handleSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnSelect = async (item: any) => {
|
const handleOnSelect = async (item: any) => {
|
||||||
|
// ================ Edit ================
|
||||||
if (item.action === 'edit') {
|
if (item.action === 'edit') {
|
||||||
openBackendModal(PageAction.EDIT, '', item.data);
|
editBackend(PageAction.EDIT, '', item.data);
|
||||||
} else if (item.action === 'delete') {
|
return;
|
||||||
handleDelete(item.data, {
|
}
|
||||||
name: item.data.backend_name
|
// ================ Delete ================
|
||||||
});
|
if (item.action === 'delete') {
|
||||||
} else if (item.action === 'export') {
|
if (item.data.backend_source === BackendSourceValueMap.COMMUNITY) {
|
||||||
|
modalRef.current?.show({
|
||||||
|
content: 'backends.title',
|
||||||
|
operation: 'common.delete.single.confirm',
|
||||||
|
name: item.data.backend_name,
|
||||||
|
async onOk() {
|
||||||
|
handleDisableCommunityBackend(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleDelete(item.data, {
|
||||||
|
name: item.data.backend_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ Export YAML ================
|
||||||
|
if (item.action === 'export') {
|
||||||
const currentData = structuredClone(item.data);
|
const currentData = structuredClone(item.data);
|
||||||
|
|
||||||
currentData.version_configs = _.mapValues(
|
currentData.version_configs = _.mapValues(
|
||||||
@@ -150,24 +193,15 @@ const BackendList = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
exportYAML(_.omit(currentData, ['id', 'created_at', 'updated_at']));
|
exportYAML(_.omit(currentData, ['id', 'created_at', 'updated_at']));
|
||||||
} else if (item.action === 'view_versions') {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ View Versions ================
|
||||||
|
if (item.action === 'view_versions') {
|
||||||
setOpenVersionInfoModal({
|
setOpenVersionInfoModal({
|
||||||
open: true,
|
open: true,
|
||||||
currentData: item.data
|
currentData: item.data
|
||||||
});
|
});
|
||||||
} else if (item.action === 'enable' || item.action === 'disable') {
|
|
||||||
handleEnableBackend({
|
|
||||||
id: item.data.id,
|
|
||||||
data: {
|
|
||||||
...item.data,
|
|
||||||
enabled: item.action === 'enable'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// delay 200ms to refresh list
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 300);
|
|
||||||
});
|
|
||||||
handleSearch();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +210,11 @@ const BackendList = () => {
|
|||||||
open: false,
|
open: false,
|
||||||
currentData: undefined
|
currentData: undefined
|
||||||
});
|
});
|
||||||
openBackendModal(PageAction.EDIT, '', openVersionInfoModal.currentData);
|
editBackend(
|
||||||
|
PageAction.EDIT,
|
||||||
|
'',
|
||||||
|
openVersionInfoModal.currentData as ListItem
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMore = useMemoizedFn((nextPage: number) => {
|
const loadMore = useMemoizedFn((nextPage: number) => {
|
||||||
@@ -197,6 +235,8 @@ const BackendList = () => {
|
|||||||
widths={{
|
widths={{
|
||||||
input: 300
|
input: 300
|
||||||
}}
|
}}
|
||||||
|
actionItems={addActions}
|
||||||
|
actionType="dropdown"
|
||||||
inputHolder={intl.formatMessage({ id: 'common.filter.name' })}
|
inputHolder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
selectHolder={intl.formatMessage({ id: 'backend.filter.source' })}
|
selectHolder={intl.formatMessage({ id: 'backend.filter.source' })}
|
||||||
buttonText={intl.formatMessage({ id: 'backend.button.add' })}
|
buttonText={intl.formatMessage({ id: 'backend.button.add' })}
|
||||||
@@ -211,7 +251,6 @@ const BackendList = () => {
|
|||||||
value: item.value
|
value: item.value
|
||||||
}))}
|
}))}
|
||||||
></FilterBar>
|
></FilterBar>
|
||||||
|
|
||||||
<ScrollerContext.Provider
|
<ScrollerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
total: dataSource.totalPage,
|
total: dataSource.totalPage,
|
||||||
@@ -239,13 +278,13 @@ const BackendList = () => {
|
|||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.backend.title' })}
|
title={intl.formatMessage({ id: 'noresult.backend.title' })}
|
||||||
subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })}
|
subTitle={intl.formatMessage({ id: 'noresult.backend.subTitle' })}
|
||||||
onClick={handleAddBackend}
|
onClick={() => handleAddBackend({ key: 'community' })}
|
||||||
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
></NoResult>
|
></NoResult>
|
||||||
</ScrollerContext.Provider>
|
</ScrollerContext.Provider>
|
||||||
<AddModal
|
<AddModal
|
||||||
action={openBackendModalStatus.action}
|
action={openBackendModalStatus.action}
|
||||||
onClose={closeBackendModal}
|
onClose={() => closeBackendModal('custom')}
|
||||||
onSubmit={handleOnSubmit}
|
onSubmit={handleOnSubmit}
|
||||||
onSubmitYaml={handleOnSubmitYaml}
|
onSubmitYaml={handleOnSubmitYaml}
|
||||||
currentData={openBackendModalStatus.currentData as ListItem}
|
currentData={openBackendModalStatus.currentData as ListItem}
|
||||||
@@ -256,6 +295,11 @@ const BackendList = () => {
|
|||||||
: intl.formatMessage({ id: 'backend.button.edit' })
|
: intl.formatMessage({ id: 'backend.button.edit' })
|
||||||
}
|
}
|
||||||
></AddModal>
|
></AddModal>
|
||||||
|
<AddCommunityModal
|
||||||
|
open={openCommunityModalStatus.open}
|
||||||
|
onRefresh={handleSearch}
|
||||||
|
onClose={() => closeBackendModal('community')}
|
||||||
|
></AddCommunityModal>
|
||||||
<VersionInfoModal
|
<VersionInfoModal
|
||||||
addVersion={handleAddVersion}
|
addVersion={handleAddVersion}
|
||||||
open={openVersionInfoModal.open}
|
open={openVersionInfoModal.open}
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ const useEnableBackend = () => {
|
|||||||
const handleEnableBackend = async (params: {
|
const handleEnableBackend = async (params: {
|
||||||
id: number;
|
id: number;
|
||||||
data: FormData;
|
data: FormData;
|
||||||
|
showMessage?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { showMessage = true } = params;
|
||||||
await updateBackend(params.id, {
|
await updateBackend(params.id, {
|
||||||
data: params.data
|
data: params.data
|
||||||
});
|
});
|
||||||
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
if (showMessage) {
|
||||||
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { handleEnableBackend };
|
return { handleEnableBackend };
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { queryBackendsList } from '../apis';
|
||||||
|
|
||||||
|
const useQueryCommunityBackends = () => {
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [dataList, setDataList] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const fetchCommunityBackends = async (params?: any) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await queryBackendsList({
|
||||||
|
page: -1,
|
||||||
|
community: 1,
|
||||||
|
...params
|
||||||
|
});
|
||||||
|
setDataList(response.items || []);
|
||||||
|
} catch (error) {
|
||||||
|
setDataList([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
dataList,
|
||||||
|
fetchCommunityBackends
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default useQueryCommunityBackends;
|
||||||
@@ -2,14 +2,14 @@ import { Divider } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import '../style/separator.less';
|
import '../style/separator.less';
|
||||||
|
|
||||||
const Separator: React.FC = () => {
|
const Separator: React.FC<{ showArrow?: boolean }> = ({ showArrow = true }) => {
|
||||||
return (
|
return (
|
||||||
<div className="separator">
|
<div className="separator">
|
||||||
<Divider
|
<Divider
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
style={{ height: 'calc(100vh - 89px)', marginInline: '0px' }}
|
style={{ height: 'calc(100vh - 89px)', marginInline: '0px' }}
|
||||||
></Divider>
|
></Divider>
|
||||||
<span className="shape"></span>
|
{showArrow && <span className="shape"></span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user