chore: create cluster ux
This commit is contained in:
@@ -54,16 +54,6 @@ export default [
|
|||||||
defaultIcon: 'icon-reranker',
|
defaultIcon: 'icon-reranker',
|
||||||
component: './playground/rerank'
|
component: './playground/rerank'
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// name: 'text2images',
|
|
||||||
// title: 'Text2Images',
|
|
||||||
// path: keepAliveRoutes.text2images,
|
|
||||||
// key: 'text2images',
|
|
||||||
// icon: 'icon-image1',
|
|
||||||
// selectedIcon: 'icon-image-filled',
|
|
||||||
// defaultIcon: 'icon-image1',
|
|
||||||
// component: './playground/images'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
name: 'speech',
|
name: 'speech',
|
||||||
title: 'Speech',
|
title: 'Speech',
|
||||||
|
|||||||
@@ -324,3 +324,7 @@ textarea:hover {
|
|||||||
.ant-result-extra {
|
.ant-result-extra {
|
||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: var(--ant-color-success);
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export default function useChartConfig() {
|
|||||||
splitLineColor: token.colorBorder,
|
splitLineColor: token.colorBorder,
|
||||||
tickLineColor: token.colorSplit,
|
tickLineColor: token.colorSplit,
|
||||||
axislabelColor: token.colorTextTertiary,
|
axislabelColor: token.colorTextTertiary,
|
||||||
|
colorSecondary: token.colorTextSecondary,
|
||||||
|
colorTertiary: token.colorTextTertiary,
|
||||||
gaugeBgColor: token.colorFillSecondary,
|
gaugeBgColor: token.colorFillSecondary,
|
||||||
gaugeSplitLineColor: isDarkTheme
|
gaugeSplitLineColor: isDarkTheme
|
||||||
? 'rgba(255,255,255,.3)'
|
? 'rgba(255,255,255,.3)'
|
||||||
@@ -172,8 +174,8 @@ export default function useChartConfig() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
distance: -11,
|
distance: -10,
|
||||||
length: 6,
|
length: 5,
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
@@ -206,13 +208,13 @@ export default function useChartConfig() {
|
|||||||
rich: {
|
rich: {
|
||||||
value: {
|
value: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
color: chartColorMap.titleColor
|
color: chartColorMap.titleColor
|
||||||
},
|
},
|
||||||
unit: {
|
unit: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: chartColorMap.titleColor,
|
color: chartColorMap.titleColor,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
padding: [0, 0, 0, 2]
|
padding: [0, 0, 0, 2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Chart from '@/components/echarts/chart';
|
import Chart from '@/components/echarts/chart';
|
||||||
import useChartConfig from '@/components/echarts/config';
|
import useChartConfig from '@/components/echarts/config';
|
||||||
import EmptyData from '@/components/empty-data';
|
import EmptyData from '@/components/empty-data';
|
||||||
import React, { memo } from 'react';
|
import React from 'react';
|
||||||
import { ChartProps } from './types';
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
const strokeColorFunc = (percent: number) => {
|
const strokeColorFunc = (percent: number) => {
|
||||||
@@ -34,10 +34,19 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
...gaugeItemConfig,
|
...gaugeItemConfig,
|
||||||
...gaugeConfig
|
...gaugeConfig
|
||||||
};
|
};
|
||||||
|
|
||||||
|
combineGaugeConfig.detail.rich.value.color = colorValue;
|
||||||
|
combineGaugeConfig.detail.rich.unit.color = colorValue;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: {
|
title: {
|
||||||
...titleConfig,
|
...titleConfig,
|
||||||
text: title,
|
text: title,
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: chartColorMap.colorSecondary,
|
||||||
|
fontWeight: 400
|
||||||
|
},
|
||||||
top: '0',
|
top: '0',
|
||||||
left: 'center'
|
left: 'center'
|
||||||
},
|
},
|
||||||
@@ -58,7 +67,7 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
color: 'transparent'
|
color: 'transparent'
|
||||||
},
|
},
|
||||||
detail: {
|
detail: {
|
||||||
...gaugeItemConfig.detail,
|
...combineGaugeConfig.detail,
|
||||||
formatter: labelFormatter || gaugeItemConfig.detail.formatter
|
formatter: labelFormatter || gaugeItemConfig.detail.formatter
|
||||||
},
|
},
|
||||||
data: [{ value }]
|
data: [{ value }]
|
||||||
@@ -78,4 +87,4 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(GaugeChart);
|
export default GaugeChart;
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import OverlayScroller from '@/components/overlay-scroller';
|
|
||||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||||
|
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||||
import { Modal, type ModalProps } from 'antd';
|
import { Modal, type ModalProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div<{ $maxHeight?: number }>`
|
||||||
|
max-height: ${({ $maxHeight }) =>
|
||||||
|
typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight};
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => {
|
const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => {
|
||||||
|
const scroller = React.useRef<any>(null);
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
|
const { initialize, destroyInstance } = useOverlayScroller();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (props.open) {
|
if (props.open) {
|
||||||
@@ -14,6 +24,24 @@ const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => {
|
|||||||
}
|
}
|
||||||
}, [props.open]);
|
}, [props.open]);
|
||||||
|
|
||||||
|
// init scroller, delay to ensure modal is fully open
|
||||||
|
React.useEffect(() => {
|
||||||
|
let timeout = null;
|
||||||
|
if (props.open) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
if (scroller.current) {
|
||||||
|
initialize(scroller.current);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
destroyInstance();
|
||||||
|
};
|
||||||
|
}, [props.open, initialize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
{...props}
|
{...props}
|
||||||
@@ -34,12 +62,16 @@ const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OverlayScroller
|
<Wrapper
|
||||||
style={{ paddingInline: 24, paddingBlockEnd: props.footer ? 0 : 32 }}
|
ref={scroller}
|
||||||
maxHeight={props.maxContentHeight || 500}
|
data-overlayscrollbars-initialize
|
||||||
|
className="overlay-scroller-wrapper"
|
||||||
|
$maxHeight={props.maxContentHeight || 500}
|
||||||
|
hidden={false}
|
||||||
|
style={{ paddingInline: 24, paddingBlockEnd: props.footer ? 0 : 24 }}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</OverlayScroller>
|
</Wrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import useUserSettings from '@/hooks/use-user-settings';
|
import useUserSettings from '@/hooks/use-user-settings';
|
||||||
import { Tag, TagProps } from 'antd';
|
import { Tag, TagProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const TagWrapper = styled(Tag)`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 22px;
|
||||||
|
opacity: 0.7;
|
||||||
|
`;
|
||||||
|
|
||||||
const ThemeTag: React.FC<TagProps & { opacity?: number }> = ({
|
const ThemeTag: React.FC<TagProps & { opacity?: number }> = ({
|
||||||
opacity,
|
opacity,
|
||||||
@@ -11,7 +23,7 @@ const ThemeTag: React.FC<TagProps & { opacity?: number }> = ({
|
|||||||
const { userSettings } = useUserSettings();
|
const { userSettings } = useUserSettings();
|
||||||
const { isDarkTheme } = userSettings;
|
const { isDarkTheme } = userSettings;
|
||||||
return (
|
return (
|
||||||
<Tag
|
<TagWrapper
|
||||||
{...restProps}
|
{...restProps}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
@@ -19,7 +31,7 @@ const ThemeTag: React.FC<TagProps & { opacity?: number }> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tag>
|
</TagWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ interface CardProps {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
clickable?: boolean;
|
clickable?: boolean;
|
||||||
ghost?: boolean;
|
ghost?: boolean;
|
||||||
|
header?: React.ReactNode;
|
||||||
|
footer?: React.ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px 20px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -39,6 +40,12 @@ const CardWrapper = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const CardContent = styled.div`
|
||||||
|
padding: 16px 20px;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
const Card: React.FC<CardProps> = (props) => {
|
const Card: React.FC<CardProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
@@ -46,6 +53,8 @@ const Card: React.FC<CardProps> = (props) => {
|
|||||||
children,
|
children,
|
||||||
clickable = true,
|
clickable = true,
|
||||||
ghost = false,
|
ghost = false,
|
||||||
|
header,
|
||||||
|
footer,
|
||||||
onClick
|
onClick
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -58,7 +67,9 @@ const Card: React.FC<CardProps> = (props) => {
|
|||||||
style={{ height: height || '180px' }}
|
style={{ height: height || '180px' }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{children}
|
{header}
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
{footer}
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,5 +55,9 @@ export default function useBodyScroll() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return { saveScrollHeight, restoreScrollHeight };
|
return {
|
||||||
|
saveScrollHeight,
|
||||||
|
restoreScrollHeight,
|
||||||
|
bodyScroller: bodyScroller.current
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ export default function useOverlayScroller(data?: {
|
|||||||
[initialize, instance]
|
[initialize, instance]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const destroyInstance = () => {
|
||||||
|
instanceRef.current?.destroy?.();
|
||||||
|
removeWheelEvent();
|
||||||
|
instanceRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
instanceRef.current?.destroy?.();
|
instanceRef.current?.destroy?.();
|
||||||
@@ -212,6 +218,7 @@ export default function useOverlayScroller(data?: {
|
|||||||
scrollEventElement: scrollEventElement.current,
|
scrollEventElement: scrollEventElement.current,
|
||||||
initialized: initialized.current,
|
initialized: initialized.current,
|
||||||
generateInstance,
|
generateInstance,
|
||||||
|
destroyInstance: destroyInstance,
|
||||||
updateScrollerPosition: throttledUpdateScrollerPosition,
|
updateScrollerPosition: throttledUpdateScrollerPosition,
|
||||||
updateScrollerPositionToTop: updateScrollerPositionToTop
|
updateScrollerPositionToTop: updateScrollerPositionToTop
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ export default {
|
|||||||
'resources.worker.add.step1':
|
'resources.worker.add.step1':
|
||||||
'Get Token <span class="note-text">(Run on the server)</span>',
|
'Get Token <span class="note-text">(Run on the server)</span>',
|
||||||
'resources.worker.add.step2': 'Register Worker',
|
'resources.worker.add.step2': 'Register Worker',
|
||||||
'resources.worker.add.step2.tips':
|
'resources.worker.add.step2.tips': '(Run on the worker to be added.)',
|
||||||
'(Run on the worker to be added, <span class="bold-text">token</span> is the value obtained in the first step.)',
|
|
||||||
'resources.worker.add.step3':
|
'resources.worker.add.step3':
|
||||||
'After success, refresh the workers list to view the new worker.',
|
'After success, refresh the workers list to view the new worker.',
|
||||||
'resources.worker.container.supported': 'Do not support macOS or Windows.',
|
'resources.worker.container.supported': 'Do not support macOS or Windows.',
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ export default {
|
|||||||
'resources.worker.add.step1':
|
'resources.worker.add.step1':
|
||||||
'トークンを取得 <span class="note-text">(サーバーで実行)</span>',
|
'トークンを取得 <span class="note-text">(サーバーで実行)</span>',
|
||||||
'resources.worker.add.step2': 'ワーカーを登録',
|
'resources.worker.add.step2': 'ワーカーを登録',
|
||||||
'resources.worker.add.step2.tips':
|
'resources.worker.add.step2.tips': '(追加するワーカーで実行し。)',
|
||||||
'(追加するワーカーで実行し、<span class="bold-text">トークン</span> は最初のステップで取得した値です。)',
|
|
||||||
'resources.worker.add.step3':
|
'resources.worker.add.step3':
|
||||||
'成功後、ワーカーリストを更新して新しいワーカーを確認してください。',
|
'成功後、ワーカーリストを更新して新しいワーカーを確認してください。',
|
||||||
'resources.worker.container.supported':
|
'resources.worker.container.supported':
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ export default {
|
|||||||
'resources.worker.add.step1':
|
'resources.worker.add.step1':
|
||||||
'Получить токен <span class="note-text">(Запустить на сервере)</span>',
|
'Получить токен <span class="note-text">(Запустить на сервере)</span>',
|
||||||
'resources.worker.add.step2': 'Зарегистрировать воркер',
|
'resources.worker.add.step2': 'Зарегистрировать воркер',
|
||||||
'resources.worker.add.step2.tips':
|
'resources.worker.add.step2.tips': '(Запустить на добавляемом воркере.)', // Translated
|
||||||
'(Запустить на добавляемом воркере, <span class="bold-text">token</span> — это значение, полученное на первом шаге.)', // Translated
|
|
||||||
'resources.worker.add.step3':
|
'resources.worker.add.step3':
|
||||||
'После успешной регистрации обновите список воркеров.',
|
'После успешной регистрации обновите список воркеров.',
|
||||||
'resources.worker.container.supported': 'Только для Linux.',
|
'resources.worker.container.supported': 'Только для Linux.',
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ export default {
|
|||||||
'resources.worker.add.step1':
|
'resources.worker.add.step1':
|
||||||
'获取 Token<span class="note-text">(在 Server 上运行)</span>',
|
'获取 Token<span class="note-text">(在 Server 上运行)</span>',
|
||||||
'resources.worker.add.step2': '注册 Worker',
|
'resources.worker.add.step2': '注册 Worker',
|
||||||
'resources.worker.add.step2.tips':
|
'resources.worker.add.step2.tips': '(在需要添加的 Worker 上运行。)',
|
||||||
'(在需要添加的 Worker 上运行,<span class="bold-text">token</span> 为第一步获取到的值。)',
|
|
||||||
'resources.worker.add.step3': '成功后,刷新 Worker 列表即可看到新的 Worker',
|
'resources.worker.add.step3': '成功后,刷新 Worker 列表即可看到新的 Worker',
|
||||||
'resources.worker.container.supported': '不支持 macOS 和 Windows',
|
'resources.worker.container.supported': '不支持 macOS 和 Windows',
|
||||||
'resources.worker.current.version': '当前版本为 {version}',
|
'resources.worker.current.version': '当前版本为 {version}',
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
updateCredential
|
updateCredential
|
||||||
} from './apis';
|
} from './apis';
|
||||||
import AddCluster from './components/add-cluster';
|
import AddCluster from './components/add-cluster';
|
||||||
import AddPool from './components/add-pool';
|
|
||||||
import ClusterItem from './components/cluster-item';
|
import ClusterItem from './components/cluster-item';
|
||||||
import { ClusterDataList } from './config';
|
import { ClusterDataList } from './config';
|
||||||
import {
|
import {
|
||||||
@@ -146,7 +145,10 @@ const Credentials: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [open, setOpen] = useState(false);
|
const [openAddWorker, setOpenAddWorker] = useState({
|
||||||
|
open: false,
|
||||||
|
token: ''
|
||||||
|
});
|
||||||
const [openAddModal, setOpenAddModal] = useState(false);
|
const [openAddModal, setOpenAddModal] = useState(false);
|
||||||
const [provider, setProvider] = useState<string>('custom');
|
const [provider, setProvider] = useState<string>('custom');
|
||||||
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
|
||||||
@@ -222,7 +224,7 @@ const Credentials: React.FC = () => {
|
|||||||
setOpenAddModal(false);
|
setOpenAddModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditUser = (row: ListItem) => {
|
const handleEditCluster = (row: ListItem) => {
|
||||||
setCurrentData(row);
|
setCurrentData(row);
|
||||||
setOpenAddModal(true);
|
setOpenAddModal(true);
|
||||||
setAction(PageAction.EDIT);
|
setAction(PageAction.EDIT);
|
||||||
@@ -231,11 +233,14 @@ const Credentials: React.FC = () => {
|
|||||||
|
|
||||||
const handleSelect = (val: any, row: ListItem) => {
|
const handleSelect = (val: any, row: ListItem) => {
|
||||||
if (val === 'edit') {
|
if (val === 'edit') {
|
||||||
handleEditUser(row);
|
handleEditCluster(row);
|
||||||
} else if (val === 'delete') {
|
} else if (val === 'delete') {
|
||||||
handleDelete({ ...row, name: row.name });
|
handleDelete({ ...row, name: row.name });
|
||||||
} else if (val === 'add_worker') {
|
} else if (val === 'add_worker') {
|
||||||
setOpen(true);
|
setOpenAddWorker({
|
||||||
|
open: true,
|
||||||
|
token: '${token}'
|
||||||
|
});
|
||||||
setCurrentData(row);
|
setCurrentData(row);
|
||||||
} else if (val === 'addPool') {
|
} else if (val === 'addPool') {
|
||||||
handleAddPool(row.provider);
|
handleAddPool(row.provider);
|
||||||
@@ -308,29 +313,11 @@ const Credentials: React.FC = () => {
|
|||||||
onCancel={handleModalCancel}
|
onCancel={handleModalCancel}
|
||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
></AddCluster>
|
></AddCluster>
|
||||||
<AddPool
|
<AddWorker
|
||||||
provider={addPoolStatus.provider}
|
open={openAddWorker.open}
|
||||||
open={addPoolStatus.open}
|
onCancel={() => setOpenAddWorker({ open: false, token: '' })}
|
||||||
action={addPoolStatus.action}
|
token={openAddWorker.token}
|
||||||
title={addPoolStatus.title}
|
></AddWorker>
|
||||||
onCancel={() => {
|
|
||||||
setAddPoolStatus({
|
|
||||||
open: false,
|
|
||||||
action: PageAction.CREATE,
|
|
||||||
title: '',
|
|
||||||
provider: 'digitalocean'
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onOk={() => {
|
|
||||||
setAddPoolStatus({
|
|
||||||
open: false,
|
|
||||||
action: PageAction.CREATE,
|
|
||||||
title: '',
|
|
||||||
provider: 'digitalocean'
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
></AddPool>
|
|
||||||
<AddWorker open={open} onCancel={() => setOpen(false)}></AddWorker>
|
|
||||||
<DeleteModal ref={modalRef}></DeleteModal>
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,15 +2,18 @@ import ModalFooter from '@/components/modal-footer';
|
|||||||
import ScrollerModal from '@/components/scroller-modal/index';
|
import ScrollerModal from '@/components/scroller-modal/index';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
import SealInput from '@/components/seal-form/seal-input';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import ContainerInstall from '@/pages/resources/components/container-install';
|
||||||
|
import { CheckCircleFilled } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
import { ProviderValueMap } from '../config';
|
||||||
import {
|
import {
|
||||||
ClusterFormData as FormData,
|
ClusterFormData as FormData,
|
||||||
ClusterListItem as ListItem
|
ClusterListItem as ListItem
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
import CloudProvider from './cloud-provider-form';
|
import CloudProvider from './cloud-provider-form';
|
||||||
|
import RegisterClusterInner from './resiter-cluster-inner';
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -18,7 +21,6 @@ type AddModalProps = {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
|
provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
|
||||||
onOk: (values: FormData) => void;
|
onOk: (values: FormData) => void;
|
||||||
data?: ListItem;
|
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
const AddCluster: React.FC<AddModalProps> = ({
|
const AddCluster: React.FC<AddModalProps> = ({
|
||||||
@@ -27,11 +29,14 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
open,
|
open,
|
||||||
provider,
|
provider,
|
||||||
onOk,
|
onOk,
|
||||||
data,
|
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [submissionStatus, setSubmissionStatus] = React.useState<{
|
||||||
|
success: boolean;
|
||||||
|
data: ListItem;
|
||||||
|
}>({ success: true, data: {} as ListItem });
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
@@ -42,56 +47,98 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderAddWorkerContent = () => {
|
||||||
|
if (provider === ProviderValueMap.Custom) {
|
||||||
|
return <ContainerInstall token="${token}" />;
|
||||||
|
}
|
||||||
|
if (provider === ProviderValueMap.Kubernetes) {
|
||||||
|
return (
|
||||||
|
<RegisterClusterInner
|
||||||
|
data={submissionStatus.data}
|
||||||
|
></RegisterClusterInner>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const modalTitle = useMemo(() => {
|
||||||
|
if (submissionStatus.success) {
|
||||||
|
return (
|
||||||
|
<div className="flex-center">
|
||||||
|
<CheckCircleFilled
|
||||||
|
className="text-success font-size-20"
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
|
/>
|
||||||
|
<span>Cluster added! Now you can register a worker.</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return title;
|
||||||
|
}, [submissionStatus.success, title]);
|
||||||
|
|
||||||
|
const renderFooter = () => {
|
||||||
|
if (submissionStatus.success) {
|
||||||
|
return (
|
||||||
|
<ModalFooter
|
||||||
|
onOk={onCancel}
|
||||||
|
onCancel={onCancel}
|
||||||
|
showCancelBtn={false}
|
||||||
|
okText="Skip for Now"
|
||||||
|
okBtnProps={{
|
||||||
|
style: {
|
||||||
|
width: 'auto'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></ModalFooter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <ModalFooter onOk={handleSubmit} onCancel={onCancel}></ModalFooter>;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
title={
|
title={modalTitle}
|
||||||
<div className="flex-between flex-center">
|
|
||||||
<span>{title}</span>
|
|
||||||
<Button type="text" size="small" onClick={handleCancel}>
|
|
||||||
<CloseOutlined></CloseOutlined>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onCancel}
|
onCancel={handleCancel}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
closeIcon={false}
|
closeIcon={true}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={600}
|
width={680}
|
||||||
footer={
|
footer={renderFooter()}
|
||||||
<ModalFooter onOk={handleSubmit} onCancel={onCancel}></ModalFooter>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={onOk} preserve={false}>
|
{submissionStatus.success ? (
|
||||||
<Form.Item<FormData>
|
renderAddWorkerContent()
|
||||||
name="display_name"
|
) : (
|
||||||
rules={[
|
<Form form={form} onFinish={onOk} preserve={false}>
|
||||||
{
|
<Form.Item<FormData>
|
||||||
required: true,
|
name="display_name"
|
||||||
message: intl.formatMessage(
|
rules={[
|
||||||
{ id: 'common.form.rule.input' },
|
{
|
||||||
{
|
required: true,
|
||||||
name: intl.formatMessage({ id: 'common.table.name' })
|
message: intl.formatMessage(
|
||||||
}
|
{ id: 'common.form.rule.input' },
|
||||||
)
|
{
|
||||||
}
|
name: intl.formatMessage({ id: 'common.table.name' })
|
||||||
]}
|
}
|
||||||
>
|
)
|
||||||
<SealInput.Input
|
}
|
||||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
]}
|
||||||
required
|
>
|
||||||
></SealInput.Input>
|
<SealInput.Input
|
||||||
</Form.Item>
|
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
{provider === 'digitalocean' && (
|
required
|
||||||
<CloudProvider provider={provider}></CloudProvider>
|
></SealInput.Input>
|
||||||
)}
|
</Form.Item>
|
||||||
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
{provider === 'digitalocean' && (
|
||||||
<SealInput.TextArea
|
<CloudProvider provider={provider}></CloudProvider>
|
||||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
)}
|
||||||
></SealInput.TextArea>
|
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||||
</Form.Item>
|
<SealInput.TextArea
|
||||||
</Form>
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
></SealInput.TextArea>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import GaugeChart from '@/components/echarts/gauge';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ProviderValueMap } from '../config';
|
||||||
|
import { ClusterListItem, NodePoolListItem } from '../config/types';
|
||||||
|
import AddPool from './add-pool';
|
||||||
|
import WorkerPools from './worker-pools';
|
||||||
|
|
||||||
|
const SubTitle = styled.div`
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
margin-block: 24px 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ClusterDetailProps {
|
||||||
|
provider: string;
|
||||||
|
data: ClusterListItem;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gaugeConfig = {
|
||||||
|
radius: '100%',
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
roundCap: false,
|
||||||
|
width: 8
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
roundCap: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 8,
|
||||||
|
color: [
|
||||||
|
[0.5, 'rgba(84, 204, 152, 80%)'],
|
||||||
|
[0.8, 'rgba(250, 173, 20, 80%)'],
|
||||||
|
[1, 'rgba(255, 77, 79, 80%)']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClusterDetail: React.FC<ClusterDetailProps> = ({ data, show }) => {
|
||||||
|
const chartHeight = 160;
|
||||||
|
const [addPoolStatus, setAddPoolStatus] = React.useState({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
|
||||||
|
// pool action handler
|
||||||
|
const handleOnAction = (action: string, record: NodePoolListItem) => {
|
||||||
|
if (action === 'edit') {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: true,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: 'Edit Worker Pool',
|
||||||
|
provider: data.provider
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="chart-wrapper">
|
||||||
|
<Row gutter={16} style={{ width: '100%' }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="GPU Utilization"
|
||||||
|
value={85}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="CPU Utilization"
|
||||||
|
value={50}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="RAM Utilization"
|
||||||
|
value={70}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="VRAM Utilization"
|
||||||
|
value={60}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
{data.provider === ProviderValueMap.DigitalOcean && (
|
||||||
|
<>
|
||||||
|
<SubTitle>Worker Pools</SubTitle>
|
||||||
|
<WorkerPools
|
||||||
|
provider={data.provider}
|
||||||
|
workerPools={data.worker_pools}
|
||||||
|
height={show ? 'auto' : 0}
|
||||||
|
onAction={handleOnAction}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<AddPool
|
||||||
|
provider={addPoolStatus.provider}
|
||||||
|
open={addPoolStatus.open}
|
||||||
|
action={addPoolStatus.action}
|
||||||
|
title={addPoolStatus.title}
|
||||||
|
onCancel={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onOk={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: addPoolStatus.action,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></AddPool>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClusterDetail;
|
||||||
@@ -2,13 +2,15 @@ import DropdownButtons from '@/components/drop-down-buttons';
|
|||||||
import GaugeChart from '@/components/echarts/gauge';
|
import GaugeChart from '@/components/echarts/gauge';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import StatusTag from '@/components/status-tag';
|
import StatusTag from '@/components/status-tag';
|
||||||
|
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||||
import Card from '@/components/templates/card';
|
import Card from '@/components/templates/card';
|
||||||
|
import { PageAction } from '@/config';
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
KubernetesOutlined
|
KubernetesOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Card as ACard, Button, Col, Row, Tag } from 'antd';
|
import { Card as ACard, Col, Collapse, Row } from 'antd';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
@@ -18,65 +20,21 @@ import {
|
|||||||
ProviderValueMap
|
ProviderValueMap
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
|
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
|
||||||
|
import AddPool from './add-pool';
|
||||||
|
import RegisterCluster from './register-cluster';
|
||||||
import WorkerPools from './worker-pools';
|
import WorkerPools from './worker-pools';
|
||||||
|
|
||||||
const CollapseTitle = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: var(--font-size-middle);
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
height: 32px;
|
|
||||||
cursor: pointer;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Content = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
.chart-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CardWrapper = styled(ACard)`
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: none;
|
|
||||||
flex: 1;
|
|
||||||
.ant-card {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.ant-card-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: var(--font-size-middle);
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--ant-color-text);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CardBox = styled.div`
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
flex: 0.5;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const actionItems = [
|
const actionItems = [
|
||||||
{
|
{
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
label: 'common.button.edit',
|
label: 'common.button.edit',
|
||||||
icon: <EditOutlined />
|
icon: <EditOutlined />
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'view',
|
||||||
|
label: 'common.button.view',
|
||||||
|
icon: <EditOutlined />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'add_worker',
|
key: 'add_worker',
|
||||||
label: 'Add Worker',
|
label: 'Add Worker',
|
||||||
@@ -108,6 +66,60 @@ const actionItems = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const CollapseTitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Content = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.chart-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardWrapper = styled(ACard)`
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: none !important;
|
||||||
|
flex: 1;
|
||||||
|
.ant-card {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.ant-card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardBox = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
const Inner = styled.div`
|
const Inner = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -116,7 +128,7 @@ const Inner = styled.div`
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -139,6 +151,38 @@ const Inner = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const CollapseWrapper = styled(Collapse)`
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
background-color: var(--ant-color-fill-quaternary);
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
|
}
|
||||||
|
.ant-collapse-header {
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
padding-block: 10px !important;
|
||||||
|
.ant-collapse-expand-icon {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table .ant-table-thead tr {
|
||||||
|
background-color: transparent !important;
|
||||||
|
th {
|
||||||
|
border-bottom: 1px solid var(--ant-color-split) !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubTitle = styled.div`
|
||||||
|
font-size: var(--font-size-middle);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
margin-block: 24px 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
data: ListItem;
|
data: ListItem;
|
||||||
onSelect?: (key: string, row: ListItem) => void;
|
onSelect?: (key: string, row: ListItem) => void;
|
||||||
@@ -165,14 +209,51 @@ const gaugeConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CardItem: React.FC<CardProps> = (props) => {
|
const CardItem: React.FC<CardProps> = (props) => {
|
||||||
|
const chartHeight = 160;
|
||||||
const { data, onSelect } = props;
|
const { data, onSelect } = props;
|
||||||
const [show, setShow] = React.useState(false);
|
const [show, setShow] = React.useState(false);
|
||||||
|
const [addPoolStatus, setAddPoolStatus] = React.useState({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
const [registerClusterStatus, setRegisterClusterStatus] = React.useState({
|
||||||
|
open: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// cluster action handler
|
||||||
const handleOnSelect = (key: string) => {
|
const handleOnSelect = (key: string) => {
|
||||||
|
if (key === 'addPool') {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: true,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: 'Add Worker Pool',
|
||||||
|
provider: data.provider
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'register_cluster') {
|
||||||
|
setRegisterClusterStatus({
|
||||||
|
open: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
onSelect?.(key, data);
|
onSelect?.(key, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnAction = (action: string, record: NodePoolListItem) => {};
|
// pool action handler
|
||||||
|
const handleOnAction = (action: string, record: NodePoolListItem) => {
|
||||||
|
if (action === 'edit') {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: true,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: 'Edit Worker Pool',
|
||||||
|
provider: data.provider
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
return actionItems.filter((item) => {
|
return actionItems.filter((item) => {
|
||||||
@@ -184,14 +265,82 @@ const CardItem: React.FC<CardProps> = (props) => {
|
|||||||
}, [data.provider]);
|
}, [data.provider]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card height={'auto'} clickable={false} ghost>
|
<Card
|
||||||
|
height={'auto'}
|
||||||
|
clickable={false}
|
||||||
|
ghost
|
||||||
|
footer={
|
||||||
|
<CollapseWrapper
|
||||||
|
onChange={() => setShow(!show)}
|
||||||
|
expandIconPosition="end"
|
||||||
|
expandIcon={({ isActive }) => (
|
||||||
|
<IconFont type="icon-down" rotate={isActive ? 0 : -90} />
|
||||||
|
)}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: 'More Information',
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<div className="chart-wrapper">
|
||||||
|
<Row gutter={16} style={{ width: '100%' }}>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="GPU Utilization"
|
||||||
|
value={85}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="CPU Utilization"
|
||||||
|
value={50}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="RAM Utilization"
|
||||||
|
value={70}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<GaugeChart
|
||||||
|
title="VRAM Utilization"
|
||||||
|
value={60}
|
||||||
|
height={chartHeight}
|
||||||
|
gaugeConfig={gaugeConfig}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
{data.provider === ProviderValueMap.DigitalOcean && (
|
||||||
|
<>
|
||||||
|
<SubTitle>Worker Pools</SubTitle>
|
||||||
|
<WorkerPools
|
||||||
|
provider={data.provider}
|
||||||
|
workerPools={data.worker_pools}
|
||||||
|
height={show ? 'auto' : 0}
|
||||||
|
onAction={handleOnAction}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
></CollapseWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Inner>
|
<Inner>
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<span className="flex-center gap-8">
|
<span className="flex-center gap-8">
|
||||||
<span className="text">{data.name}</span>
|
<span className="text">{data.name}</span>
|
||||||
<Tag style={{ borderRadius: 4 }}>
|
<ThemeTag>{ProviderLabelMap[data.provider]}</ThemeTag>
|
||||||
{ProviderLabelMap[data.provider]}
|
|
||||||
</Tag>
|
|
||||||
<StatusTag
|
<StatusTag
|
||||||
statusValue={{
|
statusValue={{
|
||||||
status: ClusterStatus[data.status],
|
status: ClusterStatus[data.status],
|
||||||
@@ -210,7 +359,7 @@ const CardItem: React.FC<CardProps> = (props) => {
|
|||||||
<CardBox>
|
<CardBox>
|
||||||
<CardWrapper bordered={false}>
|
<CardWrapper bordered={false}>
|
||||||
<div className="label">Workers</div>
|
<div className="label">Workers</div>
|
||||||
<div className="value">1</div>
|
<div className="value">1/1</div>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
<CardWrapper bordered={false}>
|
<CardWrapper bordered={false}>
|
||||||
<div className="label">GPUs</div>
|
<div className="label">GPUs</div>
|
||||||
@@ -221,64 +370,40 @@ const CardItem: React.FC<CardProps> = (props) => {
|
|||||||
<div className="value">2</div>
|
<div className="value">2</div>
|
||||||
</CardWrapper>
|
</CardWrapper>
|
||||||
</CardBox>
|
</CardBox>
|
||||||
<div className="chart-wrapper">
|
|
||||||
<Row gutter={16} style={{ width: '100%' }}>
|
|
||||||
<Col span={6}>
|
|
||||||
<GaugeChart
|
|
||||||
title="GPU Utilization"
|
|
||||||
value={85}
|
|
||||||
height={160}
|
|
||||||
gaugeConfig={gaugeConfig}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<GaugeChart
|
|
||||||
title="CPU Utilization"
|
|
||||||
value={50}
|
|
||||||
height={160}
|
|
||||||
gaugeConfig={gaugeConfig}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<GaugeChart
|
|
||||||
title="RAM Utilization"
|
|
||||||
value={70}
|
|
||||||
height={160}
|
|
||||||
gaugeConfig={gaugeConfig}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<GaugeChart
|
|
||||||
title="VRAM Utilization"
|
|
||||||
value={60}
|
|
||||||
height={160}
|
|
||||||
gaugeConfig={gaugeConfig}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</Content>
|
</Content>
|
||||||
{data.provider === ProviderValueMap.DigitalOcean && (
|
|
||||||
<>
|
|
||||||
<CollapseTitle>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
icon={<IconFont type="icon-down" rotate={show ? 0 : -90} />}
|
|
||||||
onClick={() => setShow(!show)}
|
|
||||||
>
|
|
||||||
Worker Pools
|
|
||||||
</Button>
|
|
||||||
</CollapseTitle>
|
|
||||||
<WorkerPools
|
|
||||||
provider={data.provider}
|
|
||||||
workerPools={data.worker_pools}
|
|
||||||
height={show ? 'auto' : 0}
|
|
||||||
onAction={handleOnAction}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Inner>
|
</Inner>
|
||||||
|
<AddPool
|
||||||
|
provider={addPoolStatus.provider}
|
||||||
|
open={addPoolStatus.open}
|
||||||
|
action={addPoolStatus.action}
|
||||||
|
title={addPoolStatus.title}
|
||||||
|
onCancel={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: PageAction.CREATE,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onOk={() => {
|
||||||
|
setAddPoolStatus({
|
||||||
|
open: false,
|
||||||
|
action: addPoolStatus.action,
|
||||||
|
title: '',
|
||||||
|
provider: 'digitalocean'
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></AddPool>
|
||||||
|
<RegisterCluster
|
||||||
|
title="Register Cluster"
|
||||||
|
open={registerClusterStatus.open}
|
||||||
|
data={data}
|
||||||
|
onCancel={() => {
|
||||||
|
setRegisterClusterStatus({
|
||||||
|
open: false
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
></RegisterCluster>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,44 +1,21 @@
|
|||||||
import ModalFooter from '@/components/modal-footer';
|
|
||||||
import ScrollerModal from '@/components/scroller-modal/index';
|
import ScrollerModal from '@/components/scroller-modal/index';
|
||||||
import SealInput from '@/components/seal-form/seal-input';
|
|
||||||
import { PageActionType } from '@/config/types';
|
|
||||||
import { useIntl } from '@umijs/max';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { ClusterListItem as ListItem } from '../config/types';
|
||||||
ClusterFormData as FormData,
|
import RegisterClusterInner from './resiter-cluster-inner';
|
||||||
ClusterListItem as ListItem
|
|
||||||
} from '../config/types';
|
|
||||||
import CloudProvider from './cloud-provider-form';
|
|
||||||
import K8SProvider from './k8s-provider-form';
|
|
||||||
|
|
||||||
type AddModalProps = {
|
type AddModalProps = {
|
||||||
title: string;
|
title: string;
|
||||||
action: PageActionType;
|
|
||||||
open: boolean;
|
open: boolean;
|
||||||
provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
|
data: ListItem;
|
||||||
onOk: (values: FormData) => void;
|
|
||||||
data?: ListItem;
|
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
const AddCluster: React.FC<AddModalProps> = ({
|
const AddCluster: React.FC<AddModalProps> = ({
|
||||||
title,
|
title,
|
||||||
action,
|
|
||||||
open,
|
open,
|
||||||
provider,
|
|
||||||
onOk,
|
|
||||||
data,
|
data,
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
form.submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
form.resetFields();
|
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,42 +29,9 @@ const AddCluster: React.FC<AddModalProps> = ({
|
|||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={600}
|
width={600}
|
||||||
footer={
|
footer={false}
|
||||||
<ModalFooter onOk={handleSubmit} onCancel={onCancel}></ModalFooter>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={onOk} preserve={false}>
|
<RegisterClusterInner data={data} />
|
||||||
<Form.Item<FormData>
|
|
||||||
name="display_name"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: intl.formatMessage(
|
|
||||||
{ id: 'common.form.rule.input' },
|
|
||||||
{
|
|
||||||
name: intl.formatMessage({ id: 'common.table.name' })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<SealInput.Input
|
|
||||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
|
||||||
required
|
|
||||||
></SealInput.Input>
|
|
||||||
</Form.Item>
|
|
||||||
{provider === 'digitalocean' && (
|
|
||||||
<CloudProvider provider={provider}></CloudProvider>
|
|
||||||
)}
|
|
||||||
{provider === 'kubernetes' && (
|
|
||||||
<K8SProvider provider={provider}></K8SProvider>
|
|
||||||
)}
|
|
||||||
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
|
||||||
<SealInput.TextArea
|
|
||||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
|
||||||
></SealInput.TextArea>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</ScrollerModal>
|
</ScrollerModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import HighlightCode from '@/components/highlight-code';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { queryClusterToken } from '../apis';
|
||||||
|
import { generateRegisterCommand } from '../config';
|
||||||
|
import { ClusterListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
|
type AddModalProps = {
|
||||||
|
data: ListItem;
|
||||||
|
};
|
||||||
|
const AddCluster: React.FC<AddModalProps> = ({ data }) => {
|
||||||
|
const [code, setCode] = React.useState<string>('');
|
||||||
|
const getToken = async () => {
|
||||||
|
const res = await queryClusterToken(data?.id);
|
||||||
|
return res.data?.token || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCode = async () => {
|
||||||
|
try {
|
||||||
|
const token = await getToken();
|
||||||
|
const command = generateRegisterCommand({
|
||||||
|
server: window.location.origin,
|
||||||
|
clusterId: data?.id || 0,
|
||||||
|
registrationToken: token
|
||||||
|
});
|
||||||
|
setCode(command);
|
||||||
|
} catch (error) {
|
||||||
|
setCode(
|
||||||
|
generateRegisterCommand({
|
||||||
|
server: window.location.origin,
|
||||||
|
clusterId: data?.id || 0,
|
||||||
|
registrationToken: '{token}'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getCode();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<HighlightCode
|
||||||
|
theme="dark"
|
||||||
|
code={code.replace(/\\/g, '')}
|
||||||
|
copyValue={code}
|
||||||
|
lang="bash"
|
||||||
|
></HighlightCode>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddCluster;
|
||||||
@@ -71,9 +71,9 @@ const WorkerPools: React.FC<WorkerPoolsProps> = ({
|
|||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Type',
|
title: 'Instance Type',
|
||||||
dataIndex: 'type',
|
dataIndex: 'instance_type',
|
||||||
key: 'type'
|
key: 'instance_type'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Replicas',
|
title: 'Replicas',
|
||||||
@@ -126,7 +126,7 @@ const WorkerPools: React.FC<WorkerPoolsProps> = ({
|
|||||||
const mockData = Array.from({ length: 3 }, (_, index) => ({
|
const mockData = Array.from({ length: 3 }, (_, index) => ({
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
key: index,
|
key: index,
|
||||||
type: `Type ${index + 1}`,
|
instance_type: `Type ${index + 1}`,
|
||||||
replicas: Math.floor(Math.random() * 10) + 1,
|
replicas: Math.floor(Math.random() * 10) + 1,
|
||||||
batchSize: Math.floor(Math.random() * 100) + 1,
|
batchSize: Math.floor(Math.random() * 100) + 1,
|
||||||
gpu: `NVIDIA 4090`,
|
gpu: `NVIDIA 4090`,
|
||||||
|
|||||||
@@ -59,3 +59,12 @@ export const ProviderLabelMap = {
|
|||||||
[ProviderValueMap.DigitalOcean]: 'Digital Ocean',
|
[ProviderValueMap.DigitalOcean]: 'Digital Ocean',
|
||||||
[ProviderValueMap.Custom]: 'Custom'
|
[ProviderValueMap.Custom]: 'Custom'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateRegisterCommand = (params: {
|
||||||
|
server: string;
|
||||||
|
clusterId: number;
|
||||||
|
registrationToken: string;
|
||||||
|
}) => {
|
||||||
|
return `curl -k -L '${params.server}/v2/clusters/${params.clusterId}/manifests' \\
|
||||||
|
--header 'Authorization: Bearer ${params.registrationToken}'`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ const renderCardItem = (data: {
|
|||||||
className={styles['card-body']}
|
className={styles['card-body']}
|
||||||
>
|
>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className="label font-500">{label}</div>
|
<div className="label text-secondary">{label}</div>
|
||||||
<div className="value font-500">{value}</div>
|
<div className="value font-600 font-size-16">{value}</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const GGUFResult: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title={false}
|
title={false}
|
||||||
subTitle="GGUF model is not supported yet."
|
subTitle="GGUF model is not supported."
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,64 +1,17 @@
|
|||||||
import ScrollerModal from '@/components/scroller-modal';
|
import ScrollerModal from '@/components/scroller-modal';
|
||||||
import {
|
|
||||||
AppleOutlined,
|
|
||||||
LinuxOutlined,
|
|
||||||
WindowsOutlined
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { TabsProps } from 'antd';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MacOS from './add-worker-macos';
|
|
||||||
import ContainerInstall from './container-install';
|
import ContainerInstall from './container-install';
|
||||||
|
|
||||||
type ViewModalProps = {
|
type ViewModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
token: string;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddWorker: React.FC<ViewModalProps> = (props) => {
|
const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||||
const { open, onCancel } = props || {};
|
const { open, onCancel, token = '${token}' } = props || {};
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [token, setToken] = React.useState('');
|
|
||||||
const [activeKey, setActiveKey] = React.useState('container');
|
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
|
||||||
{
|
|
||||||
key: 'container',
|
|
||||||
label: 'Linux',
|
|
||||||
icon: <LinuxOutlined />,
|
|
||||||
children: <ContainerInstall token={token} />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'macos',
|
|
||||||
label: 'macOS',
|
|
||||||
icon: <AppleOutlined />,
|
|
||||||
children: (
|
|
||||||
<MacOS
|
|
||||||
token={token}
|
|
||||||
platform={{
|
|
||||||
os: 'macOS',
|
|
||||||
downloadurl: 'https://gpustack.ai/',
|
|
||||||
supportVersions: 'resource.register.maos.support'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'windows',
|
|
||||||
label: 'Windows',
|
|
||||||
icon: <WindowsOutlined />,
|
|
||||||
children: (
|
|
||||||
<MacOS
|
|
||||||
token={token}
|
|
||||||
platform={{
|
|
||||||
os: 'Windows',
|
|
||||||
downloadurl: 'https://gpustack.ai/',
|
|
||||||
supportVersions: 'resource.register.windows.support'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollerModal
|
<ScrollerModal
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
|||||||
return commandCode?.registerWorker({
|
return commandCode?.registerWorker({
|
||||||
server: origin,
|
server: origin,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
token: '${token}',
|
token: props.token || '${token}',
|
||||||
workerip: '${workerip}'
|
workerip: '${workerip}'
|
||||||
});
|
});
|
||||||
}, [versionInfo, activeKey, props.token, npuKey]);
|
}, [versionInfo, activeKey, props.token, npuKey]);
|
||||||
@@ -53,38 +53,8 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-install">
|
<div className="container-install">
|
||||||
<ul className="notes">
|
|
||||||
<li>
|
|
||||||
{intl.formatMessage(
|
|
||||||
{ id: 'resources.worker.current.version' },
|
|
||||||
{ version: versionInfo.version }
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: intl.formatMessage({
|
|
||||||
id: 'resources.worker.driver.install'
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
></span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3 className="font-size-14 font-600">
|
|
||||||
1.{' '}
|
|
||||||
<span
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: intl.formatMessage({ id: 'resources.worker.add.step1' })
|
|
||||||
}}
|
|
||||||
></span>
|
|
||||||
</h3>
|
|
||||||
<HighlightCode
|
|
||||||
code={addWorkerGuide.container.getToken}
|
|
||||||
theme="dark"
|
|
||||||
lang="bash"
|
|
||||||
></HighlightCode>
|
|
||||||
<h3 className="m-t-10 font-size-14 font-600">
|
<h3 className="m-t-10 font-size-14 font-600">
|
||||||
2. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
|
1. {intl.formatMessage({ id: 'resources.worker.add.step2' })}{' '}
|
||||||
<span
|
<span
|
||||||
className="font-size-12"
|
className="font-size-12"
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
@@ -146,7 +116,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
|||||||
></HighlightCode>
|
></HighlightCode>
|
||||||
)}
|
)}
|
||||||
<h3 className="m-b-0 m-t-10 font-size-14 font-600">
|
<h3 className="m-b-0 m-t-10 font-size-14 font-600">
|
||||||
3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
|
2. {intl.formatMessage({ id: 'resources.worker.add.step3' })}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
mac: {
|
mac: {
|
||||||
getToken: 'cat /var/lib/gpustack/token',
|
getToken: 'cat /var/lib/gpustack/token',
|
||||||
registerWorker(params: { server: string; token: string }) {
|
registerWorker(params: { server: string; token: string }) {
|
||||||
return `curl -sfL https://get.gpustack.ai | sh -s - --server-url ${params.server} --token ${params.token}`;
|
return `curl -sfL https://get.gpustack.ai | sh -s - --server-url ${params.server} --registration ${params.token}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
win: {
|
win: {
|
||||||
getToken:
|
getToken:
|
||||||
'Get-Content -Path (Join-Path -Path $env:APPDATA -ChildPath "gpustack\\token") -Raw',
|
'Get-Content -Path (Join-Path -Path $env:APPDATA -ChildPath "gpustack\\token") -Raw',
|
||||||
registerWorker(params: { server: string; token: string }) {
|
registerWorker(params: { server: string; token: string }) {
|
||||||
return `Invoke-Expression "& { $((Invoke-WebRequest -Uri 'https://get.gpustack.ai' -UseBasicParsing).Content) } --server-url '${params.server}' --token '${params.token}'"`;
|
return `Invoke-Expression "& { $((Invoke-WebRequest -Uri 'https://get.gpustack.ai' -UseBasicParsing).Content) } --server-url '${params.server}' --registration '${params.token}'"`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cuda: {
|
cuda: {
|
||||||
@@ -41,11 +41,14 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
token: string;
|
token: string;
|
||||||
workerip: string;
|
workerip: string;
|
||||||
}) {
|
}) {
|
||||||
return `docker run -d \\
|
return `docker run -d --name gpustack \\
|
||||||
--net=host \\
|
--restart=unless-stopped \\
|
||||||
-v /var/lib/gpustack:/var/lib/gpustack \\
|
--gpus all \\
|
||||||
--privileged gpustack/gpustack:xxxx \\
|
--network=host \\
|
||||||
--registration ${params.token}`;
|
--ipc=host \\
|
||||||
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
|
gpustack/gpustack:${params.tag} \\
|
||||||
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
npu: {
|
npu: {
|
||||||
@@ -73,7 +76,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--ipc=host \\
|
--ipc=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
npu310p: {
|
npu310p: {
|
||||||
@@ -101,7 +104,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--ipc=host \\
|
--ipc=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag}-310p \\
|
gpustack/gpustack:${params.tag}-310p \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
musa: {
|
musa: {
|
||||||
@@ -119,7 +122,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--ipc=host \\
|
--ipc=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cpu: {
|
cpu: {
|
||||||
@@ -136,7 +139,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--network=host \\
|
--network=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rocm: {
|
rocm: {
|
||||||
@@ -157,7 +160,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--security-opt seccomp=unconfined \\
|
--security-opt seccomp=unconfined \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dcu: {
|
dcu: {
|
||||||
@@ -180,7 +183,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--security-opt seccomp=unconfined \\
|
--security-opt seccomp=unconfined \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
corex: {
|
corex: {
|
||||||
@@ -201,7 +204,7 @@ export const addWorkerGuide: Record<string, any> = {
|
|||||||
--ipc=host \\
|
--ipc=host \\
|
||||||
-v gpustack-data:/var/lib/gpustack \\
|
-v gpustack-data:/var/lib/gpustack \\
|
||||||
gpustack/gpustack:${params.tag} \\
|
gpustack/gpustack:${params.tag} \\
|
||||||
--server-url ${params.server} --token ${params.token} --worker-ip ${params.workerip}`;
|
--server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
@@ -216,8 +219,8 @@ export const containerInstallOptions = [
|
|||||||
{ label: 'Ascend CANN', value: 'npu' },
|
{ label: 'Ascend CANN', value: 'npu' },
|
||||||
{ label: 'Hygon DTK', value: 'dcu' },
|
{ label: 'Hygon DTK', value: 'dcu' },
|
||||||
{ label: 'Moore Threads MUSA', value: 'musa' },
|
{ label: 'Moore Threads MUSA', value: 'musa' },
|
||||||
{ label: 'Iluvatar Corex', value: 'corex' },
|
{ label: 'Iluvatar Corex', value: 'corex' }
|
||||||
{ label: 'CPU', value: 'cpu' }
|
// { label: 'CPU', value: 'cpu' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ModelfileStateMap = {
|
export const ModelfileStateMap = {
|
||||||
|
|||||||
Reference in New Issue
Block a user