chore: add resource worker

main
jialin 2 years ago
parent 3842006f6e
commit 177ebd1bbe

@ -27,6 +27,7 @@
"dayjs": "^1.11.11",
"echarts": "^5.5.1",
"has-ansi": "^5.0.1",
"highlight.js": "^11.10.0",
"jotai": "^2.8.4",
"localforage": "^1.10.0",
"lodash": "^4.17.21",

@ -53,6 +53,9 @@ dependencies:
has-ansi:
specifier: ^5.0.1
version: 5.0.1
highlight.js:
specifier: ^11.10.0
version: 11.10.0
jotai:
specifier: ^2.8.4
version: 2.8.4(@types/react@18.3.1)(react@18.2.0)
@ -4438,7 +4441,7 @@ packages:
dependencies:
'@babel/core': 7.24.5
postcss: 7.0.39
postcss-syntax: 0.36.2(postcss@8.4.38)
postcss-syntax: 0.36.2(postcss@7.0.39)
transitivePeerDependencies:
- supports-color
dev: false
@ -4479,7 +4482,7 @@ packages:
postcss-syntax: '>=0.36.2'
dependencies:
postcss: 7.0.39
postcss-syntax: 0.36.2(postcss@8.4.38)
postcss-syntax: 0.36.2(postcss@7.0.39)
remark: 13.0.0
unist-util-find-all-after: 3.0.2
transitivePeerDependencies:
@ -10450,6 +10453,11 @@ packages:
hasBin: true
dev: false
/highlight.js@11.10.0:
resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==, tarball: https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz}
engines: {node: '>=12.0.0'}
dev: false
/history@4.10.1:
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==, tarball: https://registry.npmjs.org/history/-/history-4.10.1.tgz}
dependencies:
@ -13093,7 +13101,7 @@ packages:
dependencies:
htmlparser2: 3.10.1
postcss: 7.0.39
postcss-syntax: 0.36.2(postcss@8.4.38)
postcss-syntax: 0.36.2(postcss@7.0.39)
dev: false
/postcss-image-set-function@4.0.7(postcss@8.4.38):
@ -13567,6 +13575,30 @@ packages:
lodash: 4.17.21
postcss: 8.4.38
/postcss-syntax@0.36.2(postcss@7.0.39):
resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
peerDependencies:
postcss: '>=5.0.0'
postcss-html: '*'
postcss-jsx: '*'
postcss-less: '*'
postcss-markdown: '*'
postcss-scss: '*'
peerDependenciesMeta:
postcss-html:
optional: true
postcss-jsx:
optional: true
postcss-less:
optional: true
postcss-markdown:
optional: true
postcss-scss:
optional: true
dependencies:
postcss: 7.0.39
dev: false
/postcss-syntax@0.36.2(postcss@8.4.38):
resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
peerDependencies:
@ -16724,7 +16756,7 @@ packages:
postcss-sass: 0.4.4
postcss-scss: 2.1.1
postcss-selector-parser: 6.0.16
postcss-syntax: 0.36.2(postcss@8.4.38)
postcss-syntax: 0.36.2(postcss@7.0.39)
postcss-value-parser: 4.2.0
resolve-from: 5.0.0
slash: 3.0.0

@ -56,6 +56,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
return (
<Button
className="copy-button"
ref={buttonRef}
type={type}
shape={shape}

@ -0,0 +1,76 @@
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css';
import CopyButton from '../copy-button';
import { escapeHtml } from './utils';
interface CodeViewerProps {
code: string;
lang: string;
autodetect?: boolean;
ignoreIllegals?: boolean;
copyable?: boolean;
}
const CodeViewer: React.FC<CodeViewerProps> = (props) => {
const {
code,
lang,
autodetect = true,
ignoreIllegals = true,
copyable = true
} = props || {};
const renderCode = () => {
const autodetectLang = autodetect && !lang;
const cannotDetectLanguage = !autodetectLang && !hljs.getLanguage(lang);
let className = '';
if (!cannotDetectLanguage) {
className = `hljs ${lang}`;
}
// No idea what language to use, return raw code
if (cannotDetectLanguage) {
console.warn(`The language "${lang}" you specified could not be found.`);
return {
value: escapeHtml(code),
className: className
};
}
if (autodetectLang) {
const result = hljs.highlightAuto(code);
return {
value: result.value,
className: className
};
}
const result = hljs.highlight(code, {
language: lang,
ignoreIllegals: ignoreIllegals
});
return {
value: result.value,
className: className
};
};
const highlightedCode = renderCode();
return (
<pre className="code-pre">
<code
className={highlightedCode.className}
dangerouslySetInnerHTML={{
__html: highlightedCode.value
}}
></code>
<CopyButton
text={highlightedCode.value}
size="small"
style={{ color: '#abb2bf' }}
></CopyButton>
</pre>
);
};
export default CodeViewer;

@ -0,0 +1,17 @@
import CodeViewer from './code-viewer';
import './style.less';
const HighlightCode: React.FC<{
code: string;
lang?: string;
}> = (props) => {
const { code, lang = 'bash' } = props;
return (
<div className="high-light-wrapper">
<CodeViewer lang={lang} code={code} />
</div>
);
};
export default HighlightCode;

@ -0,0 +1,22 @@
.high-light-wrapper {
text-align: left;
.hljs {
font-weight: var(--font-weight-normal);
padding-inline: 0;
padding-block: 1.2em;
}
.code-pre {
padding-inline: 12px 32px;
position: relative;
background-color: #282c34;
border-radius: var(--border-radius-mini);
.copy-button {
position: absolute;
top: 6px;
right: 6px;
}
}
}

@ -0,0 +1,8 @@
export function escapeHtml(value: string): string {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
}

@ -114,6 +114,7 @@ html {
--ant-modal-header-margin-bottom: 20px;
--ant-modal-footer-margin-top: 30px;
--ant-modal-content-padding: 20px 24px 24px;
--ant-modal-title-color: var(--ant-color-text);
}
}

@ -24,7 +24,7 @@ export function useUpdateChunkedList(options: {
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
options.setDataList?.([...cacheDataListRef.current]);
}, 100);
}, 80);
};
const updateChunkedList = (
data: ChunkedCollection,

@ -1,6 +1,7 @@
export default {
'resources.title': 'Resources',
'resources.nodes': 'Nodes',
'resources.button.create': 'Add Worker',
'resources.table.hostname': 'Hostname',
'resources.table.ip': 'IP',
'resources.table.cpu': 'CPU',
@ -18,5 +19,9 @@ export default {
'resources.table.total': 'Total',
'resources.table.used': 'Used',
'resources.table.wokers': 'workers',
'resources.table.unified': 'Unified Memory'
'resources.table.unified': 'Unified Memory',
'resources.worker.add.step1': 'Get Token',
'resources.worker.add.step2': 'Register Worker',
'resources.worker.add.step3':
'Refresh worker list, you can see the newly added worker'
};

@ -1,5 +1,6 @@
export default {
'resources.title': '资源',
'resources.button.create': '添加 Worker',
'resources.nodes': '节点',
'resources.table.hostname': '主机名',
'resources.table.ip': 'IP',
@ -18,5 +19,8 @@ export default {
'resources.table.total': '总量',
'resources.table.used': '已用',
'resources.table.wokers': 'workers',
'resources.table.unified': '统一内存'
'resources.table.unified': '统一内存',
'resources.worker.add.step1': '获取 Token',
'resources.worker.add.step2': '注册 Worker',
'resources.worker.add.step3': '刷新 worker 列表,可以看到新添加的 worker'
};

@ -194,12 +194,20 @@ const LoginForm = () => {
label={intl.formatMessage({ id: 'common.form.password' })}
/>
</Form.Item>
<Form.Item name="autoLogin" valuePropName="checked">
<Checkbox style={{ marginLeft: 10 }}>
{intl.formatMessage({ id: 'common.login.rember' })}
<Form.Item noStyle name="autoLogin" valuePropName="checked">
<Checkbox style={{ marginLeft: 5, marginBottom: 12 }}>
<span style={{ color: 'var(--ant-color-text-secondary)' }}>
{' '}
{intl.formatMessage({ id: 'common.login.rember' })}
</span>
</Checkbox>
</Form.Item>
<Button htmlType="submit" type="primary" block>
<Button
htmlType="submit"
type="primary"
block
style={{ height: '54px', fontSize: '16px' }}
>
{intl.formatMessage({ id: 'common.button.login' })}
</Button>
</Form>

@ -65,26 +65,6 @@ const PasswordForm: React.FC = () => {
{intl.formatMessage({ id: 'users.password.modify.description' })}
</span>
</h2>
{/*
<Form.Item
name="current_password"
rules={[
{
required: true,
message: intl.formatMessage(
{ id: 'common.form.rule.input' },
{
name: intl.formatMessage({ id: 'users.form.currentpassword' })
}
)
}
]}
>
<SealInput.Password
prefix={<LockOutlined />}
label={intl.formatMessage({ id: 'users.form.currentpassword' })}
/>
</Form.Item> */}
<Form.Item
name="new_password"
rules={[
@ -136,7 +116,7 @@ const PasswordForm: React.FC = () => {
htmlType="submit"
type="primary"
block
style={{ marginTop: 20 }}
style={{ height: '54px', fontSize: '16px', marginTop: 10 }}
>
{intl.formatMessage({ id: 'common.button.submit' })}
</Button>

@ -0,0 +1,52 @@
import HighlightCode from '@/components/highlight-code';
import { useIntl } from '@umijs/max';
import { Modal } from 'antd';
import React from 'react';
import { addWorkerGuide } from '../config';
type ViewModalProps = {
open: boolean;
onCancel: () => void;
};
const AddWorker: React.FC<ViewModalProps> = (props) => {
const { open, onCancel } = props || {};
const intl = useIntl();
const origin = window.location.origin;
return (
<Modal
title={intl.formatMessage({ id: 'resources.button.create' })}
open={open}
centered={true}
onCancel={onCancel}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={600}
footer={null}
>
<div>
<h3>1. {intl.formatMessage({ id: 'resources.worker.add.step1' })}</h3>
<h4>Linux Or MacOS </h4>
<HighlightCode code={addWorkerGuide.mac.getToken}></HighlightCode>
<h4>Windows </h4>
<HighlightCode code={addWorkerGuide.win.getToken}></HighlightCode>
<h3>2. {intl.formatMessage({ id: 'resources.worker.add.step2' })}</h3>
<h4>Linux Or MacOS </h4>
<HighlightCode
code={addWorkerGuide.mac.registerWorker(origin)}
></HighlightCode>
<h4>Windows </h4>
<HighlightCode
code={addWorkerGuide.win.registerWorker(origin)}
></HighlightCode>
<h3>3. {intl.formatMessage({ id: 'resources.worker.add.step3' })}</h3>
</div>
</Modal>
);
};
export default React.memo(AddWorker);

@ -8,6 +8,7 @@ import { convertFileSize, handleBatchRequest } from '@/utils';
import {
DeleteOutlined,
InfoCircleOutlined,
PlusOutlined,
SyncOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
@ -17,6 +18,7 @@ import { memo, useEffect, useRef, useState } from 'react';
import { deleteWorker, queryWorkersList } from '../apis';
import { WorkerStatusMapValue, status } from '../config';
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
import AddWorker from './add-worker';
const { Column } = Table;
const Resources: React.FC = () => {
@ -30,6 +32,7 @@ const Resources: React.FC = () => {
const intl = useIntl();
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [dataSource, setDataSource] = useState<ListItem[]>([]);
const [queryParams, setQueryParams] = useState({
page: 1,
@ -94,6 +97,10 @@ const Resources: React.FC = () => {
return mountRoot ? formateUtilazation(mountRoot.used, mountRoot.total) : 0;
};
const handleAddWorker = () => {
setOpen(true);
};
const handleDelete = (row: ListItem) => {
modalRef.current.show({
content: 'worker',
@ -166,14 +173,23 @@ const Resources: React.FC = () => {
</Space>
}
right={
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
{intl.formatMessage({ id: 'common.button.delete' })}
</Button>
<Space size={20}>
<Button
icon={<PlusOutlined></PlusOutlined>}
type="primary"
onClick={handleAddWorker}
>
{intl.formatMessage({ id: 'resources.button.create' })}
</Button>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
{intl.formatMessage({ id: 'common.button.delete' })}
</Button>
</Space>
}
></PageTools>
<Table
@ -371,6 +387,7 @@ const Resources: React.FC = () => {
/>
</Table>
<DeleteModal ref={modalRef}></DeleteModal>
<AddWorker open={open} onCancel={() => setOpen(false)}></AddWorker>
</>
);
};

@ -14,3 +14,19 @@ export const status: any = {
[WorkerStatusMap.ready]: StatusMaps.success,
[WorkerStatusMap.not_ready]: StatusMaps.error
};
export const addWorkerGuide = {
mac: {
getToken: 'cat /var/lib/gpustack/token',
registerWorker(server: string) {
return `curl -sfL https://get.gpustack.ai | sh -s - --server-url ${server} --token mytoken`;
}
},
win: {
getToken:
'Get-Content -Path (Join-Path -Path $env:APPDATA -ChildPath "gpustack\\token") -Raw',
registerWorker(server: string) {
return `Invoke-Expression "& { $((Invoke-WebRequest -Uri "https://get.gpustack.ai" -UseBasicParsing).Content) } -ServerURL ${server} -Token mytoken"`;
}
}
};

Loading…
Cancel
Save