feat: yaml editor
This commit is contained in:
+6
-13
@@ -1,5 +1,6 @@
|
||||
import { defineConfig } from '@umijs/max';
|
||||
import keepAlive from './keep-alive';
|
||||
import { compressionPluginConfig, monacoPluginConfig } from './plugins';
|
||||
import proxy from './proxy';
|
||||
import routes from './routes';
|
||||
import { getBranchInfo } from './utils';
|
||||
@@ -29,7 +30,7 @@ export default defineConfig({
|
||||
defaultSizes: 'parsed' // stat // gzip
|
||||
},
|
||||
mfsu: {
|
||||
exclude: ['lodash', 'ml-pca']
|
||||
exclude: ['lodash', 'ml-pca', 'monaco-editor']
|
||||
},
|
||||
base: process.env.npm_config_base || '/',
|
||||
...(isProduction
|
||||
@@ -56,21 +57,13 @@ export default defineConfig({
|
||||
config.output
|
||||
.filename(`js/[name].${t}.js`)
|
||||
.chunkFilename(`js/[name].${t}.chunk.js`);
|
||||
config
|
||||
.plugin('compression-webpack-plugin')
|
||||
.use(CompressionWebpackPlugin, [
|
||||
{
|
||||
filename: '[path][base].gz',
|
||||
algorithm: 'gzip',
|
||||
test: /\.(js|css|html|svg)$/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
}
|
||||
]);
|
||||
compressionPluginConfig(config);
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
|
||||
chainWebpack(config) {
|
||||
monacoPluginConfig(config);
|
||||
},
|
||||
favicons: ['/static/favicon.png'],
|
||||
jsMinifier: 'terser',
|
||||
cssMinifier: 'cssnano',
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin');
|
||||
|
||||
export function monacoPluginConfig(config: any) {
|
||||
return config
|
||||
.plugin('monaco-editor-webpack-plugin')
|
||||
.use(MonacoWebpackPlugin, [
|
||||
{
|
||||
languages: ['yaml'],
|
||||
customLanguages: [
|
||||
{
|
||||
label: 'yaml',
|
||||
entry: 'monaco-yaml',
|
||||
worker: {
|
||||
id: 'monaco-yaml/yamlWorker',
|
||||
entry: 'monaco-yaml/yaml.worker'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
export function compressionPluginConfig(config: any) {
|
||||
return config
|
||||
.plugin('compression-webpack-plugin')
|
||||
.use(CompressionWebpackPlugin, [
|
||||
{
|
||||
filename: '[path][base].gz',
|
||||
algorithm: 'gzip',
|
||||
test: /\.(js|css|html|svg)$/,
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -114,6 +114,7 @@ export default [
|
||||
selectedIcon: 'icon-backend-filled',
|
||||
defaultIcon: 'icon-backend',
|
||||
access: 'canSeeAdmin',
|
||||
subMenu: ['/models/backends/create'],
|
||||
component: './backends/list'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"has-ansi": "^5.0.1",
|
||||
"highlight.js": "^11.10.0",
|
||||
"jotai": "^2.8.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"katex": "^0.16.21",
|
||||
"lamejs": "github:zhuker/lamejs",
|
||||
@@ -55,6 +56,9 @@
|
||||
"marked": "^14.1.0",
|
||||
"ml-dataset-iris": "^1.2.1",
|
||||
"ml-pca": "^4.1.1",
|
||||
"monaco-editor": "^0.53.0",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"monaco-yaml": "^5.4.0",
|
||||
"numeral": "^2.0.6",
|
||||
"overlayscrollbars": "^2.10.0",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
|
||||
Generated
+111
-6
@@ -29,7 +29,7 @@ dependencies:
|
||||
version: 0.11.13
|
||||
'@monaco-editor/react':
|
||||
specifier: ^4.6.0
|
||||
version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1)(react@18.3.1)
|
||||
version: 4.7.0(monaco-editor@0.53.0)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@orcid/bibtex-parse-js':
|
||||
specifier: ^0.0.25
|
||||
version: 0.0.25
|
||||
@@ -99,6 +99,9 @@ dependencies:
|
||||
jotai:
|
||||
specifier: ^2.8.4
|
||||
version: 2.13.1(@babel/core@7.28.3)(@types/react@18.3.24)(react@18.3.1)
|
||||
js-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
@@ -126,6 +129,15 @@ dependencies:
|
||||
ml-pca:
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1
|
||||
monaco-editor:
|
||||
specifier: ^0.53.0
|
||||
version: 0.53.0
|
||||
monaco-editor-webpack-plugin:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(monaco-editor@0.53.0)(webpack@5.101.3)
|
||||
monaco-yaml:
|
||||
specifier: ^5.4.0
|
||||
version: 5.4.0(monaco-editor@0.53.0)
|
||||
numeral:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
@@ -2699,7 +2711,7 @@ packages:
|
||||
state-local: 1.0.7
|
||||
dev: false
|
||||
|
||||
/@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1)(react@18.3.1):
|
||||
/@monaco-editor/react@4.7.0(monaco-editor@0.53.0)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==, tarball: https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz}
|
||||
peerDependencies:
|
||||
monaco-editor: '>= 0.25.0 < 1'
|
||||
@@ -2707,7 +2719,7 @@ packages:
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
dependencies:
|
||||
'@monaco-editor/loader': 1.5.0
|
||||
monaco-editor: 0.52.2
|
||||
monaco-editor: 0.53.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
@@ -3637,6 +3649,10 @@ packages:
|
||||
/@types/stylis@4.2.7:
|
||||
resolution: {integrity: sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==, tarball: https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz}
|
||||
|
||||
/@types/trusted-types@1.0.6:
|
||||
resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz}
|
||||
dev: false
|
||||
|
||||
/@types/trusted-types@2.0.7:
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, tarball: https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz}
|
||||
requiresBuild: true
|
||||
@@ -9332,6 +9348,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
/jsonc-parser@3.3.1:
|
||||
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, tarball: https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz}
|
||||
dev: false
|
||||
|
||||
/jsonfile@6.2.0:
|
||||
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==, tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz}
|
||||
dependencies:
|
||||
@@ -10481,8 +10501,66 @@ packages:
|
||||
/moment@2.30.1:
|
||||
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==, tarball: https://registry.npmjs.org/moment/-/moment-2.30.1.tgz}
|
||||
|
||||
/monaco-editor@0.52.2:
|
||||
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==, tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz}
|
||||
/monaco-editor-webpack-plugin@7.1.0(monaco-editor@0.53.0)(webpack@5.101.3):
|
||||
resolution: {integrity: sha512-ZjnGINHN963JQkFqjjcBtn1XBtUATDZBMgNQhDQwd78w2ukRhFXAPNgWuacaQiDZsUr4h1rWv5Mv6eriKuOSzA==, tarball: https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz}
|
||||
peerDependencies:
|
||||
monaco-editor: '>= 0.31.0'
|
||||
webpack: ^4.5.0 || 5.x
|
||||
dependencies:
|
||||
loader-utils: 2.0.4
|
||||
monaco-editor: 0.53.0
|
||||
webpack: 5.101.3
|
||||
dev: false
|
||||
|
||||
/monaco-editor@0.53.0:
|
||||
resolution: {integrity: sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==, tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.53.0.tgz}
|
||||
dependencies:
|
||||
'@types/trusted-types': 1.0.6
|
||||
dev: false
|
||||
|
||||
/monaco-languageserver-types@0.4.0:
|
||||
resolution: {integrity: sha512-QQ3BZiU5LYkJElGncSNb5AKoJ/LCs6YBMCJMAz9EA7v+JaOdn3kx2cXpPTcZfKA5AEsR0vc97sAw+5mdNhVBmw==, tarball: https://registry.npmjs.org/monaco-languageserver-types/-/monaco-languageserver-types-0.4.0.tgz}
|
||||
dependencies:
|
||||
monaco-types: 0.1.0
|
||||
vscode-languageserver-protocol: 3.17.5
|
||||
vscode-uri: 3.1.0
|
||||
dev: false
|
||||
|
||||
/monaco-marker-data-provider@1.2.4:
|
||||
resolution: {integrity: sha512-4DsPgsAqpTyUDs3humXRBPUJoihTv+L6v9aupQWD80X2YXaCXUd11mWYeSCYHuPgdUmjFaNWCEOjQ6ewf/QA1Q==, tarball: https://registry.npmjs.org/monaco-marker-data-provider/-/monaco-marker-data-provider-1.2.4.tgz}
|
||||
dependencies:
|
||||
monaco-types: 0.1.0
|
||||
dev: false
|
||||
|
||||
/monaco-types@0.1.0:
|
||||
resolution: {integrity: sha512-aWK7SN9hAqNYi0WosPoMjenMeXJjwCxDibOqWffyQ/qXdzB/86xshGQobRferfmNz7BSNQ8GB0MD0oby9/5fTQ==, tarball: https://registry.npmjs.org/monaco-types/-/monaco-types-0.1.0.tgz}
|
||||
dev: false
|
||||
|
||||
/monaco-worker-manager@2.0.1(monaco-editor@0.53.0):
|
||||
resolution: {integrity: sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==, tarball: https://registry.npmjs.org/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz}
|
||||
peerDependencies:
|
||||
monaco-editor: '>=0.30.0'
|
||||
dependencies:
|
||||
monaco-editor: 0.53.0
|
||||
dev: false
|
||||
|
||||
/monaco-yaml@5.4.0(monaco-editor@0.53.0):
|
||||
resolution: {integrity: sha512-tuBVDy1KAPrgO905GHTItu8AaA5bIzF5S4X0JVRAE/D66FpRhkDUk7tKi5bwKMVTTugtpMLsXN4ewh4CgE/FtQ==, tarball: https://registry.npmjs.org/monaco-yaml/-/monaco-yaml-5.4.0.tgz}
|
||||
peerDependencies:
|
||||
monaco-editor: '>=0.36'
|
||||
dependencies:
|
||||
jsonc-parser: 3.3.1
|
||||
monaco-editor: 0.53.0
|
||||
monaco-languageserver-types: 0.4.0
|
||||
monaco-marker-data-provider: 1.2.4
|
||||
monaco-types: 0.1.0
|
||||
monaco-worker-manager: 2.0.1(monaco-editor@0.53.0)
|
||||
path-browserify: 1.0.1
|
||||
prettier: 3.6.2
|
||||
vscode-languageserver-textdocument: 1.0.12
|
||||
vscode-languageserver-types: 3.17.5
|
||||
vscode-uri: 3.1.0
|
||||
yaml: 2.8.1
|
||||
dev: false
|
||||
|
||||
/mrmime@2.0.1:
|
||||
@@ -11078,6 +11156,10 @@ packages:
|
||||
resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==, tarball: https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz}
|
||||
dev: false
|
||||
|
||||
/path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, tarball: https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz}
|
||||
dev: false
|
||||
|
||||
/path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, tarball: https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz}
|
||||
engines: {node: '>=8'}
|
||||
@@ -15580,6 +15662,30 @@ packages:
|
||||
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==, tarball: https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz}
|
||||
dev: false
|
||||
|
||||
/vscode-jsonrpc@8.2.0:
|
||||
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==, tarball: https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: false
|
||||
|
||||
/vscode-languageserver-protocol@3.17.5:
|
||||
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==, tarball: https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz}
|
||||
dependencies:
|
||||
vscode-jsonrpc: 8.2.0
|
||||
vscode-languageserver-types: 3.17.5
|
||||
dev: false
|
||||
|
||||
/vscode-languageserver-textdocument@1.0.12:
|
||||
resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==, tarball: https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz}
|
||||
dev: false
|
||||
|
||||
/vscode-languageserver-types@3.17.5:
|
||||
resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==, tarball: https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz}
|
||||
dev: false
|
||||
|
||||
/vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==, tarball: https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz}
|
||||
dev: false
|
||||
|
||||
/walker@1.0.8:
|
||||
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, tarball: https://registry.npmjs.org/walker/-/walker-1.0.8.tgz}
|
||||
dependencies:
|
||||
@@ -15873,7 +15979,6 @@ packages:
|
||||
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==, tarball: https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/yargs-parser@20.2.9:
|
||||
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==, tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -55,9 +55,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
|
||||
});
|
||||
|
||||
clipboardRef.current.on('error', (e: any) => {
|
||||
message.success(
|
||||
intl.formatMessage({ id: 'common.copy.fail' }) as string
|
||||
);
|
||||
message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string);
|
||||
e.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,74 +1,66 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import CopyButton from '../copy-button';
|
||||
import SegmentLine from '../segment-line';
|
||||
import './index.less';
|
||||
|
||||
const Wrapper = styled.div<{ $height: number }>`
|
||||
height: ${(props) => props.$height}px;
|
||||
const HeaderWrapper = styled.div<{ $height?: number }>`
|
||||
height: ${(props) => (props.$height ? `${props.$height}px` : 'auto')};
|
||||
display: flex;
|
||||
padding-block: 0;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border-radius: var(--border-radius-mini);
|
||||
overflow: hidden;
|
||||
|
||||
&.bordered {
|
||||
border: 1px solid var(--ant-color-border);
|
||||
}
|
||||
&.borderless {
|
||||
border: none;
|
||||
}
|
||||
.code-pre {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.scrollbar {
|
||||
.slider {
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface EditorwrapProps {
|
||||
headerHeight?: number;
|
||||
header?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
showHeader?: boolean;
|
||||
copyText: string;
|
||||
defaultValue?: string;
|
||||
langOptions?: Global.BaseOption<string | number>[];
|
||||
variant?: 'bordered' | 'borderless';
|
||||
styles?: {
|
||||
wrapper?: React.CSSProperties;
|
||||
header?: React.CSSProperties;
|
||||
content?: React.CSSProperties;
|
||||
};
|
||||
onChangeLang?: (value: string | number) => void;
|
||||
}
|
||||
const EditorWrap: React.FC<EditorwrapProps> = ({
|
||||
headerHeight = 40,
|
||||
header,
|
||||
children,
|
||||
copyText,
|
||||
langOptions = [],
|
||||
onChangeLang,
|
||||
defaultValue,
|
||||
styles = {},
|
||||
showHeader = true
|
||||
variant = 'borderless',
|
||||
styles = {}
|
||||
}) => {
|
||||
const handleChangeLang = (value: string | number) => {
|
||||
onChangeLang?.(value);
|
||||
};
|
||||
const renderHeader = () => {
|
||||
if (header) {
|
||||
return <div className="editor-header">{header}</div>;
|
||||
}
|
||||
if (showHeader) {
|
||||
return (
|
||||
<Wrapper className="editor-header" $height={headerHeight}>
|
||||
<SegmentLine
|
||||
height={headerHeight}
|
||||
defaultValue={defaultValue}
|
||||
size="small"
|
||||
options={langOptions}
|
||||
onChange={handleChangeLang}
|
||||
></SegmentLine>
|
||||
<CopyButton
|
||||
text={copyText}
|
||||
size="small"
|
||||
style={{
|
||||
color: 'rgba(255,255,255,.7)'
|
||||
}}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return (
|
||||
<div className="editor-wrap" style={{ ...styles.wrapper }}>
|
||||
{renderHeader()}
|
||||
<div className="editor-content">{children}</div>
|
||||
</div>
|
||||
<Wrapper
|
||||
style={{ ...styles.wrapper }}
|
||||
className={classNames({
|
||||
bordered: variant === 'bordered',
|
||||
borderless: variant === 'borderless'
|
||||
})}
|
||||
>
|
||||
{header && <HeaderWrapper $height={headerHeight}>{header}</HeaderWrapper>}
|
||||
<div>{children}</div>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(EditorWrap);
|
||||
export default EditorWrap;
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef
|
||||
} from 'react';
|
||||
import EditorWrap from '../editor-wrap';
|
||||
|
||||
interface ViewerProps {
|
||||
ref?: any;
|
||||
lang: string;
|
||||
defaultLang?: string;
|
||||
langOptions?: Global.BaseOption<string>[];
|
||||
config?: any;
|
||||
value: string;
|
||||
height?: string | number;
|
||||
theme?: string;
|
||||
showHeader?: boolean;
|
||||
header?: React.ReactNode;
|
||||
placeholder?: string;
|
||||
variant?: 'bordered' | 'borderless';
|
||||
}
|
||||
|
||||
const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
||||
const editorRef = useRef<any>(null);
|
||||
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
||||
const {
|
||||
lang,
|
||||
value,
|
||||
config,
|
||||
langOptions,
|
||||
defaultLang,
|
||||
height = 380,
|
||||
showHeader
|
||||
theme = 'vs-dark',
|
||||
header,
|
||||
variant = 'borderless',
|
||||
placeholder
|
||||
} = props;
|
||||
const [langType, setLangType] = useState(defaultLang);
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const handleBeforeMount = (monaco: any) => {
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
@@ -36,12 +45,8 @@ const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
||||
|
||||
const handleEditorDidMount = (editor: any, monaco: any) => {
|
||||
editorRef.current = editor;
|
||||
console.log('loaded====', editor, monaco);
|
||||
};
|
||||
|
||||
const handleOnChangeLang = (value: string) => {
|
||||
setLangType(value);
|
||||
};
|
||||
const formatCode = () => {
|
||||
if (editorRef.current) {
|
||||
setTimeout(() => {
|
||||
@@ -55,35 +60,50 @@ const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
format: () => {
|
||||
formatCode();
|
||||
},
|
||||
getValue: () => {
|
||||
return editorRef.current?.getValue?.();
|
||||
},
|
||||
setValue: (val: string) => {
|
||||
editorRef.current?.setValue?.(val);
|
||||
},
|
||||
editor: editorRef.current
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
formatCode();
|
||||
setTimeout(() => {
|
||||
const lineCount = editorRef.current?.getModel().getLineCount(); // 获取总行数
|
||||
editorRef.current?.revealLine(lineCount); // 滚动到最后一行
|
||||
}, 100); // 可以调整延时,确保编辑器完全加载
|
||||
const lineCount = editorRef.current?.getModel().getLineCount();
|
||||
editorRef.current?.revealLine(lineCount);
|
||||
}, 100);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<EditorWrap
|
||||
copyText={value}
|
||||
langOptions={langOptions}
|
||||
defaultValue={lang}
|
||||
showHeader={showHeader}
|
||||
onChangeLang={handleOnChangeLang}
|
||||
>
|
||||
<EditorWrap header={header} variant={variant}>
|
||||
<Editor
|
||||
height={height}
|
||||
theme="vs-dark"
|
||||
theme={theme}
|
||||
className="monaco-editor"
|
||||
defaultLanguage={defaultLang}
|
||||
language={langType}
|
||||
language={lang}
|
||||
value={value}
|
||||
options={config}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 6,
|
||||
horizontalScrollbarSize: 6
|
||||
},
|
||||
placeholder: placeholder
|
||||
}}
|
||||
loading={<LoadingOutlined style={{ fontSize: 24 }}></LoadingOutlined>}
|
||||
beforeMount={handleBeforeMount}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
</EditorWrap>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default React.memo(ViewerEditor);
|
||||
export default ViewerEditor;
|
||||
@@ -45,7 +45,11 @@ const icons = {
|
||||
Ascend: React.createElement(IconFont, { type: 'icon-ascend' }),
|
||||
Catalog1: React.createElement(IconFont, { type: 'icon-catalog1' }),
|
||||
AMD: React.createElement(IconFont, { type: 'icon-amd' }),
|
||||
KubernetesFilled: React.createElement(IconFont, { type: 'icon-k8s-filled' })
|
||||
KubernetesFilled: React.createElement(IconFont, { type: 'icon-k8s-filled' }),
|
||||
EditContent: React.createElement(IconFont, { type: 'icon-edit-content' }),
|
||||
Yaml: React.createElement(IconFont, { type: 'icon-code_block' }),
|
||||
Version: React.createElement(IconFont, { type: 'icon-version' }),
|
||||
Parameter: React.createElement(IconFont, { type: 'icon-parameters' })
|
||||
};
|
||||
|
||||
export default icons;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
||||
// import './iconfont/iconfont.js';
|
||||
|
||||
const IconFont = createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_wt33o3w25g.js'
|
||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_c06q7b0n32v.js'
|
||||
});
|
||||
|
||||
export default IconFont;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DeleteOutlined,
|
||||
DownOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SyncOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
@@ -170,6 +171,11 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
|
||||
left={
|
||||
<Space>
|
||||
<Input
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||
></SearchOutlined>
|
||||
}
|
||||
placeholder={
|
||||
inputHolder ||
|
||||
intl.formatMessage({
|
||||
|
||||
@@ -127,6 +127,7 @@ const SealTextArea: React.FC<InputTextareaProps & SealFormItemProps> = (
|
||||
>
|
||||
<Input.TextArea
|
||||
{...rest}
|
||||
spellCheck={rest.spellCheck ?? false}
|
||||
autoSize={autoSize}
|
||||
ref={inputRef}
|
||||
style={{ minHeight: '80px', ...style }}
|
||||
|
||||
@@ -48,6 +48,7 @@ const SegmentedQwrapper = styled.div<{ $height: number }>`
|
||||
border-radius: 2px;
|
||||
bottom: 0px;
|
||||
top: unset;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ant-segmented-item-selected {
|
||||
@@ -55,6 +56,7 @@ const SegmentedQwrapper = styled.div<{ $height: number }>`
|
||||
color: var(--color-white-primary);
|
||||
&::after {
|
||||
height: 2px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +70,7 @@ const SegmentLine: React.FC<SegmentLineProps> = (props) => {
|
||||
className = 'segment-line',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<SegmentedQwrapper $height={height}>
|
||||
<Segmented
|
||||
|
||||
@@ -588,13 +588,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// =============== segment start==============
|
||||
.ant-segmented .ant-segmented-item::after {
|
||||
border-radius: var(--border-radius-mini);
|
||||
}
|
||||
|
||||
// =============== segment end ===============
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { Collapse, CollapseProps } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CollapseInner = styled(Collapse)`
|
||||
.ant-collapse-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px !important;
|
||||
padding-inline: 5px !important;
|
||||
padding-block: 8px !important;
|
||||
border-radius: var(--border-radius-base) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 600 !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ant-color-fill-tertiary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding-inline: 0 !important;
|
||||
padding-block: 0 !important;
|
||||
}
|
||||
.ant-collapse-header-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CollapsePanel: React.FC<{ items: CollapseProps['items'] }> = ({
|
||||
items
|
||||
}) => {
|
||||
return (
|
||||
<CollapseInner
|
||||
expandIconPosition="start"
|
||||
bordered={false}
|
||||
ghost
|
||||
accordion
|
||||
destroyOnHidden={false}
|
||||
expandIcon={({ isActive }) => (
|
||||
<IconFont
|
||||
type="icon-down"
|
||||
rotate={isActive ? 0 : -90}
|
||||
style={{ fontSize: '14px' }}
|
||||
></IconFont>
|
||||
)}
|
||||
items={items}
|
||||
></CollapseInner>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapsePanel;
|
||||
+18
-17
@@ -1,6 +1,6 @@
|
||||
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||
import React from 'react';
|
||||
import '../style/column-wrapper.less';
|
||||
import './style.less';
|
||||
|
||||
const ColumnWrapper: React.FC<any> = ({
|
||||
children,
|
||||
@@ -25,24 +25,25 @@ const ColumnWrapper: React.FC<any> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{footer ? (
|
||||
<div className="column-wrapper-footer">
|
||||
<div className="column-wrapper" ref={scroller} style={{ maxHeight }}>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: paddingBottom
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className="column-wrapper-footer"
|
||||
style={{ height: maxHeight || '100%' }}
|
||||
>
|
||||
<div
|
||||
className="column-wrapper"
|
||||
ref={scroller}
|
||||
style={{ padding: '16px 24px' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: footer ? paddingBottom : 0
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{<div className="footer">{footer}</div>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="column-wrapper" ref={scroller} style={{ maxHeight }}>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)}
|
||||
{footer && <div className="footer">{footer}</div>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import CopyButton from '@/components/copy-button';
|
||||
import EditorWrap from '@/components/editor-wrap';
|
||||
import HighlightCode from '@/components/highlight-code';
|
||||
import SegmentLine from '@/components/segment-line';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface ViewerProps {
|
||||
code: string;
|
||||
copyText?: string;
|
||||
options?: Global.BaseOption<string>[];
|
||||
defaultValue?: string;
|
||||
headerHeight?: number;
|
||||
lang?: string;
|
||||
onChange?: (value: string | number) => void;
|
||||
}
|
||||
|
||||
const Header = styled.div`
|
||||
width: 100%;
|
||||
padding-inline: 12px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--color-editor-header-bg);
|
||||
`;
|
||||
|
||||
const CommandViewer: React.FC<ViewerProps> = (props) => {
|
||||
const {
|
||||
code,
|
||||
copyText = '',
|
||||
defaultValue,
|
||||
options = [],
|
||||
headerHeight = 40,
|
||||
lang,
|
||||
onChange
|
||||
} = props || {};
|
||||
|
||||
const [value, setValue] = React.useState<string>(defaultValue || '');
|
||||
|
||||
const handlOnChange = (value: string | number) => {
|
||||
setValue(value as string);
|
||||
onChange?.(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorWrap
|
||||
header={
|
||||
<Header>
|
||||
<SegmentLine
|
||||
height={headerHeight}
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
size="small"
|
||||
options={options}
|
||||
onChange={handlOnChange}
|
||||
></SegmentLine>
|
||||
|
||||
<CopyButton
|
||||
text={copyText || code}
|
||||
size="small"
|
||||
style={{
|
||||
color: 'rgba(255,255,255,.7)'
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
}
|
||||
>
|
||||
<HighlightCode
|
||||
height={380}
|
||||
theme="dark"
|
||||
code={code}
|
||||
lang={lang || value}
|
||||
copyable={false}
|
||||
></HighlightCode>
|
||||
</EditorWrap>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommandViewer;
|
||||
@@ -0,0 +1,249 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import ClusterSteps from '@/pages/cluster-management/components/cluster-steps';
|
||||
import FooterButtons from '@/pages/cluster-management/components/footer-buttons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||
import _ from 'lodash';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { createBackend } from './apis';
|
||||
import { FormData } from './config/types';
|
||||
import { moduleMap, moduleRegistry } from './step-forms/module-registry';
|
||||
import useStepList from './step-forms/use-step-list';
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Nav = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 72px;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
.level-2 {
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
width: 600px;
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
align-items: center;
|
||||
padding-inline: var(--layout-content-header-inlinepadding);
|
||||
.text {
|
||||
margin-right: 16px;
|
||||
padding-right: 16px;
|
||||
border-right: 1px solid var(--ant-color-split);
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin-right: 32px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ClusterCreate = () => {
|
||||
const intl = useIntl();
|
||||
const startStep = 0;
|
||||
const steps = useStepList();
|
||||
const [searchParams] = useSearchParams();
|
||||
const action =
|
||||
(searchParams.get('action') as PageActionType) || PageAction.CREATE;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<number>(startStep);
|
||||
|
||||
const [extraData, setExtraData] = useState<FormData>({
|
||||
default_version: ''
|
||||
} as FormData);
|
||||
const [formValues, setFormValues] = useState<Record<string, any>>({});
|
||||
|
||||
const formRefs: Record<string, any> = {
|
||||
[moduleMap.BasicForm]: useRef<any>(null),
|
||||
[moduleMap.ParamertersForm]: useRef<any>(null),
|
||||
[moduleMap.VersionConfigForm]: useRef<any>(null)
|
||||
};
|
||||
|
||||
const handleStepChange = (newStep: number) => {
|
||||
setCurrentStep(newStep);
|
||||
console.log('step========', newStep);
|
||||
};
|
||||
|
||||
const getFormFieldsValue = () => {
|
||||
setFormValues((prev) => {
|
||||
const newFormValues = _.cloneDeep(prev);
|
||||
for (const [formKey, formRef] of Object.entries(formRefs)) {
|
||||
const formValues = formRef?.current?.getFieldsValue?.();
|
||||
if (formValues) {
|
||||
newFormValues[formKey] = formValues;
|
||||
}
|
||||
}
|
||||
return newFormValues;
|
||||
});
|
||||
};
|
||||
|
||||
const onPrevious = () => {
|
||||
getFormFieldsValue();
|
||||
setCurrentStep((prev) => Math.max(prev - 1, 0));
|
||||
};
|
||||
|
||||
const validateForms = async () => {
|
||||
const step = steps[currentStep];
|
||||
const formKeys = step?.showForms || [];
|
||||
if (formKeys?.length > 0) {
|
||||
const results = await Promise.allSettled(
|
||||
formKeys.map((key) => formRefs[key].current?.validateFields())
|
||||
);
|
||||
|
||||
console.log('results========', results);
|
||||
|
||||
const resultsMap = new Map<string, any>();
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'rejected') {
|
||||
const values = result.reason?.values || {};
|
||||
resultsMap.set(formKeys[index], values);
|
||||
} else if (result.status === 'fulfilled') {
|
||||
resultsMap.set(formKeys[index], result.value);
|
||||
}
|
||||
});
|
||||
const newValues = _.merge(formValues, Object.fromEntries(resultsMap));
|
||||
|
||||
setFormValues(newValues);
|
||||
|
||||
const isValid = results.every((result) => result.status === 'fulfilled');
|
||||
if (!isValid) {
|
||||
return false;
|
||||
}
|
||||
return Object.assign({}, ...Object.values(newValues));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const onNext = async (callback?: (values: FormData) => void) => {
|
||||
try {
|
||||
const result = await validateForms();
|
||||
console.log('results========2', result);
|
||||
if (result) {
|
||||
await callback?.(result as FormData);
|
||||
const step = steps[currentStep];
|
||||
step.beforeNext?.();
|
||||
setCurrentStep((prev) => Math.min(prev + 1, steps.length - 1));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('next error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormValues({});
|
||||
for (const [formKey, formRef] of Object.entries(formRefs)) {
|
||||
formRefs[formKey] = React.createRef();
|
||||
}
|
||||
};
|
||||
|
||||
const renderForms = () => {
|
||||
const step = steps[currentStep];
|
||||
const formKeys = step?.showForms || [];
|
||||
if (formKeys.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return formKeys.map((key) => {
|
||||
console.log('renderForms key========', key, formValues);
|
||||
const FormComponent = moduleRegistry[key];
|
||||
return FormComponent ? (
|
||||
<FormComponent
|
||||
key={key}
|
||||
ref={formRefs[key]}
|
||||
action={action}
|
||||
currentData={formValues[key]}
|
||||
/>
|
||||
) : null;
|
||||
});
|
||||
};
|
||||
|
||||
const showButtons = useMemo(() => {
|
||||
const step = steps[currentStep];
|
||||
return step?.showButtons ? step?.showButtons?.() : {};
|
||||
}, [currentStep, steps]);
|
||||
|
||||
const submit = async (values: FormData) => {
|
||||
const data = {
|
||||
...extraData,
|
||||
...(typeof values === 'object' ? values : {})
|
||||
};
|
||||
const res = await createBackend({ data });
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
onNext(submit);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
footer={[
|
||||
<FooterButtons
|
||||
key="buttons"
|
||||
onPrevious={onPrevious}
|
||||
onNext={onNext}
|
||||
handleCancel={handleCancel}
|
||||
handleSubmit={handleSubmit}
|
||||
showButtons={showButtons}
|
||||
/>
|
||||
]}
|
||||
header={{
|
||||
title: false,
|
||||
style: {
|
||||
paddingInline: 'var(--layout-content-header-inlinepadding)'
|
||||
},
|
||||
breadcrumb: {}
|
||||
}}
|
||||
pageHeaderRender={() => (
|
||||
<HeaderContainer>
|
||||
<Nav>
|
||||
<span className="level-1">Backends</span>
|
||||
<span
|
||||
style={{
|
||||
marginInline: 20,
|
||||
color: 'var(--ant-color-split)'
|
||||
}}
|
||||
>
|
||||
/
|
||||
</span>
|
||||
<span className="level-2">
|
||||
{intl.formatMessage({ id: 'common.button.create' })}
|
||||
</span>
|
||||
</Nav>
|
||||
<ClusterSteps
|
||||
steps={steps}
|
||||
currentStep={currentStep}
|
||||
onChange={handleStepChange}
|
||||
></ClusterSteps>
|
||||
</HeaderContainer>
|
||||
)}
|
||||
>
|
||||
<Container>
|
||||
<Content>{renderForms()}</Content>
|
||||
</Container>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClusterCreate;
|
||||
@@ -0,0 +1,148 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import ModalFooter from '@/components/modal-footer';
|
||||
import GSDrawer from '@/components/scroller-modal/gs-drawer';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Segmented, Tabs } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import ColumnWrapper from '../../_components/column-wrapper';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import BackendForm from '../forms';
|
||||
import ImportYAML from './import-yaml';
|
||||
|
||||
const ModalFooterStyle = {
|
||||
padding: '16px 24px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
};
|
||||
|
||||
const SegmentedInner = styled(Segmented)`
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
.ant-segmented-item {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const SegmentedHeader = styled.div`
|
||||
margin-top: 16px;
|
||||
padding: 0 24px;
|
||||
`;
|
||||
|
||||
interface AddModalProps {
|
||||
action: PageActionType;
|
||||
currentData?: ListItem; // Used when action is EDIT
|
||||
onClose: () => void;
|
||||
onSubmit: (values: FormData) => void;
|
||||
open: boolean;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const AddModal: React.FC<AddModalProps> = (props) => {
|
||||
const { action, currentData, onClose, onSubmit, open, title } = props;
|
||||
const formRef = useRef<any>(null);
|
||||
const editorRef = useRef<any>(null);
|
||||
const intl = useIntl();
|
||||
const [activeKey, setActiveKey] = useState<string>('form');
|
||||
|
||||
const onOk = () => {
|
||||
if (activeKey === 'yaml') {
|
||||
const content = editorRef.current?.getContent();
|
||||
const valid = editorRef.current?.validate();
|
||||
if (valid) {
|
||||
onSubmit({ content: content });
|
||||
}
|
||||
} else {
|
||||
formRef.current?.submit();
|
||||
}
|
||||
};
|
||||
|
||||
const onFinish = (values: FormData) => {
|
||||
onSubmit(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<GSDrawer
|
||||
title={title}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={false}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
styles={{
|
||||
body: {
|
||||
padding: '0 0 16px',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
content: {
|
||||
borderRadius: '6px 0 0 6px'
|
||||
}
|
||||
}}
|
||||
width={600}
|
||||
footer={false}
|
||||
>
|
||||
<SegmentedHeader>
|
||||
<SegmentedInner
|
||||
shape="round"
|
||||
onChange={(value) => setActiveKey(value as string)}
|
||||
options={[
|
||||
{
|
||||
label: 'Form',
|
||||
value: 'form',
|
||||
icon: <IconFont type="icon-edit-content" />
|
||||
},
|
||||
{
|
||||
label: 'YAML',
|
||||
value: 'yaml',
|
||||
icon: <IconFont type="icon-code_block" />
|
||||
}
|
||||
]}
|
||||
size="middle"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</SegmentedHeader>
|
||||
<ColumnWrapper
|
||||
style={{ flex: 1 }}
|
||||
maxHeight={'calc(100vh - 123px)'}
|
||||
footer={
|
||||
<ModalFooter
|
||||
onCancel={onClose}
|
||||
onOk={onOk}
|
||||
style={ModalFooterStyle}
|
||||
></ModalFooter>
|
||||
}
|
||||
>
|
||||
<Tabs
|
||||
renderTabBar={() => <></>}
|
||||
activeKey={activeKey}
|
||||
defaultActiveKey={activeKey}
|
||||
items={[
|
||||
{
|
||||
key: 'form',
|
||||
label: 'Form',
|
||||
children: (
|
||||
<BackendForm
|
||||
onFinish={onFinish}
|
||||
action={action}
|
||||
currentData={currentData}
|
||||
ref={formRef}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'yaml',
|
||||
label: 'YAML',
|
||||
children: (
|
||||
<ImportYAML action={action} ref={editorRef}></ImportYAML>
|
||||
)
|
||||
}
|
||||
]}
|
||||
></Tabs>
|
||||
</ColumnWrapper>
|
||||
</GSDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddModal;
|
||||
@@ -1,6 +1,7 @@
|
||||
import DropDownActions from '@/components/drop-down-actions';
|
||||
import IconFont from '@/components/icon-font';
|
||||
import Card from '@/components/templates/card';
|
||||
import { DockerOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { backendActions } from '../config';
|
||||
@@ -22,8 +23,17 @@ const Content = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-top: 12x;
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
gap: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
.description {
|
||||
margin-block: 12px;
|
||||
color: var(--ant-color-text-tertiary);
|
||||
}
|
||||
.text {
|
||||
margin-left: 4px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
.title {
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 500;
|
||||
@@ -47,12 +57,13 @@ const BackendCard: React.FC<BackendCardProps> = ({
|
||||
onClick={() => onClick(data)}
|
||||
clickable={true}
|
||||
disabled={false}
|
||||
height={205}
|
||||
height={'auto'}
|
||||
ghost
|
||||
header={
|
||||
<Header>
|
||||
<div className="title">
|
||||
<img src={data.icon} alt={data.backend_name} height={20} />
|
||||
<span>{data.backend_name}</span>
|
||||
</div>
|
||||
<span>
|
||||
<DropDownActions
|
||||
@@ -72,11 +83,16 @@ const BackendCard: React.FC<BackendCardProps> = ({
|
||||
}
|
||||
>
|
||||
<Content>
|
||||
<div className="title">{data.backend_name}</div>
|
||||
<div>This is a description</div>
|
||||
<div>默认版本:{data.default_version}</div>
|
||||
<div className="description">This is a description</div>
|
||||
<div>
|
||||
镜像:{data.version_configs?.[data.default_version]?.image_name}
|
||||
<IconFont type="icon-version" style={{ marginRight: 4 }} /> Default
|
||||
version: <span className="text">{data.default_version}</span>
|
||||
</div>
|
||||
<div>
|
||||
<DockerOutlined style={{ marginRight: 4 }} /> Default Image:{' '}
|
||||
<span className="text">
|
||||
{data.version_configs?.[data.default_version]?.image_name}
|
||||
</span>
|
||||
</div>
|
||||
</Content>
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { PageAction } from '@/config';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useUserSettings from '@/hooks/use-user-settings';
|
||||
import { ImportOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, message, Typography, Upload } from 'antd';
|
||||
import { RcFile } from 'antd/lib/upload';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { yamlTemplate } from '../config';
|
||||
import YamlEditor from './yaml-editor';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const ErrorText = styled(Text)`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 4px 6px;
|
||||
background-color: var(--ant-color-bg-elevated);
|
||||
border-radius: 0 0 var(--ant-border-radius) var(--ant-border-radius);
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
padding-inline: 10px;
|
||||
border-bottom: 1px solid var(--ant-color-border);
|
||||
background-color: var(--ant-color-fill-tertiary);
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
position: relative;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--ant-border-radius);
|
||||
.monaco-editor .scroll-decoration {
|
||||
box-shadow: none;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ImportYAMLProps {
|
||||
ref?: any;
|
||||
action: PageActionType;
|
||||
content?: string;
|
||||
onSubmit?: (content: string) => void;
|
||||
}
|
||||
|
||||
const ImportYAML: React.FC<ImportYAMLProps> = forwardRef(
|
||||
({ action, content = '' }, ref) => {
|
||||
const intl = useIntl();
|
||||
const { userSettings } = useUserSettings();
|
||||
const editorRef = useRef<any>(null);
|
||||
const [fileContent, setFileContent] = useState<string>(
|
||||
action === PageAction.CREATE ? yamlTemplate : content
|
||||
);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const beforeUpload = (file: RcFile) => {
|
||||
const isYaml =
|
||||
file.type === 'application/x-yaml' ||
|
||||
file.type === 'text/yaml' ||
|
||||
file.name.endsWith('.yaml') ||
|
||||
file.name.endsWith('.yml');
|
||||
if (!isYaml) {
|
||||
message.error('You can only upload YAML file!');
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result;
|
||||
if (typeof content === 'string') {
|
||||
setFileContent(content);
|
||||
} else {
|
||||
message.error('Failed to read file content!');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
// Prevent upload
|
||||
return false;
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const markers = editorRef.current?.validate();
|
||||
if (markers && markers.length > 0) {
|
||||
setError(markers[0].message);
|
||||
}
|
||||
return markers.length === 0;
|
||||
};
|
||||
|
||||
const renderHeader = () => {
|
||||
return (
|
||||
<Header>
|
||||
<span className="title">YAML Editor</span>
|
||||
<Upload
|
||||
name="file"
|
||||
multiple={false}
|
||||
beforeUpload={beforeUpload}
|
||||
showUploadList={false}
|
||||
accept=".yaml,.yml,text/yaml,application/x-yaml"
|
||||
>
|
||||
<Button icon={<ImportOutlined />} type="text" size="small">
|
||||
Import
|
||||
</Button>
|
||||
</Upload>
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getContent: () => {
|
||||
return editorRef.current?.getValue();
|
||||
},
|
||||
setContent: (val: string) => {
|
||||
editorRef.current?.setValue?.(val);
|
||||
},
|
||||
validate() {
|
||||
return validate();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<YamlEditor
|
||||
ref={editorRef}
|
||||
lang="yaml"
|
||||
theme={userSettings?.isDarkTheme ? 'vs-dark' : 'vs-light'}
|
||||
value={fileContent}
|
||||
height={'calc(100vh - 255px)'}
|
||||
header={renderHeader()}
|
||||
></YamlEditor>
|
||||
{error && <ErrorText type="danger">{error}</ErrorText>}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ImportYAML;
|
||||
@@ -0,0 +1,167 @@
|
||||
import EditorWrap from '@/components/editor-wrap';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import Editor, { loader } from '@monaco-editor/react';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { configureMonacoYaml } from 'monaco-yaml';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef
|
||||
} from 'react';
|
||||
import schema from '../config/schema.json';
|
||||
|
||||
loader.config({ monaco });
|
||||
|
||||
interface ViewerProps {
|
||||
ref?: any;
|
||||
lang: string;
|
||||
defaultLang?: string;
|
||||
config?: any;
|
||||
value: string;
|
||||
height?: string | number;
|
||||
theme?: string;
|
||||
header?: React.ReactNode;
|
||||
placeholder?: string;
|
||||
variant?: 'bordered' | 'borderless';
|
||||
}
|
||||
|
||||
const path = 'inmemory://model/config.yaml';
|
||||
|
||||
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
||||
const {
|
||||
lang,
|
||||
value,
|
||||
config,
|
||||
defaultLang,
|
||||
height = 380,
|
||||
theme = 'vs-dark',
|
||||
header,
|
||||
variant = 'borderless',
|
||||
placeholder
|
||||
} = props;
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
const monacoRef = useRef<any>(null);
|
||||
const monacoYamlRef = useRef<any>(null);
|
||||
|
||||
const handleBeforeMount = (monaco: any) => {
|
||||
const yamlUri = monaco.Uri.parse(path);
|
||||
|
||||
configureMonacoYaml(monaco, {
|
||||
schemas: [
|
||||
{
|
||||
uri: 'http://example.com/schema-name.json',
|
||||
fileMatch: [yamlUri.toString()],
|
||||
schema
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditorDidMount = (editor: any, monaco: any) => {
|
||||
editorRef.current = editor;
|
||||
monacoRef.current = monaco;
|
||||
const uri = monaco.Uri.parse(path);
|
||||
|
||||
// monaco.editor.onDidChangeMarkers((uris: string[]) => {
|
||||
// if (!uris.some((u) => u.toString() === uri.toString())) return;
|
||||
|
||||
// const model = monaco.editor.getModel(uri);
|
||||
// if (!model) return;
|
||||
// console.log('Markers changed:', uris);
|
||||
// const markers = monaco.editor.getModelMarkers({ resource: uri });
|
||||
// const adjusted = markers.map((m: any) => {
|
||||
// if (m._adjusted) return m;
|
||||
// return {
|
||||
// ...m,
|
||||
// _adjusted: true,
|
||||
// severity: monaco.MarkerSeverity.ErrorText
|
||||
// };
|
||||
// });
|
||||
|
||||
// monaco.editor.setModelMarkers(
|
||||
// monaco.editor.getModel(uri),
|
||||
// 'yaml',
|
||||
// adjusted
|
||||
// );
|
||||
// });
|
||||
};
|
||||
|
||||
const formatCode = () => {
|
||||
if (editorRef.current) {
|
||||
setTimeout(() => {
|
||||
editorRef.current
|
||||
?.getAction?.('editor.action.formatDocument')
|
||||
?.run()
|
||||
.then(() => {
|
||||
console.log('format success');
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const getMarkers = () => {
|
||||
const uri = editorRef.current?.getModel()?.uri;
|
||||
const markers = monacoRef.current?.editor.getModelMarkers({
|
||||
resource: uri
|
||||
});
|
||||
console.log('Validation markers:', uri, markers);
|
||||
return markers;
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
format: () => {
|
||||
formatCode();
|
||||
},
|
||||
getValue: () => {
|
||||
return editorRef.current?.getValue?.();
|
||||
},
|
||||
setValue: (val: string) => {
|
||||
editorRef.current?.setValue?.(val);
|
||||
},
|
||||
dispose: () => {
|
||||
editorRef.current?.dispose?.();
|
||||
monacoYamlRef.current?.dispose?.();
|
||||
},
|
||||
validate() {
|
||||
return getMarkers();
|
||||
},
|
||||
editor: editorRef.current
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
formatCode();
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<EditorWrap header={header} variant={variant}>
|
||||
<Editor
|
||||
path={path}
|
||||
defaultPath={path}
|
||||
height={height}
|
||||
theme={theme}
|
||||
className="monaco-editor"
|
||||
defaultLanguage={'yaml'}
|
||||
language={'yaml'}
|
||||
value={value}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
quickSuggestions: true,
|
||||
suggestOnTriggerCharacters: true,
|
||||
fontSize: 14,
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 6,
|
||||
horizontalScrollbarSize: 6
|
||||
},
|
||||
placeholder: placeholder
|
||||
}}
|
||||
loading={<LoadingOutlined style={{ fontSize: 24 }}></LoadingOutlined>}
|
||||
beforeMount={handleBeforeMount}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
</EditorWrap>
|
||||
);
|
||||
});
|
||||
|
||||
export default ViewerEditor;
|
||||
@@ -5,21 +5,21 @@ export const backendActions = [
|
||||
label: 'Edit Versions',
|
||||
value: 'version',
|
||||
key: 'version',
|
||||
icon: icons.EditOutlined,
|
||||
icon: icons.EditContent,
|
||||
locale: false
|
||||
},
|
||||
{
|
||||
label: 'Edit Parameters',
|
||||
value: 'parameter',
|
||||
key: 'parameter',
|
||||
icon: icons.EditOutlined,
|
||||
icon: icons.Parameter,
|
||||
locale: false
|
||||
},
|
||||
{
|
||||
label: 'Edit by Yaml',
|
||||
value: 'yaml',
|
||||
key: 'edit',
|
||||
icon: icons.EditOutlined,
|
||||
icon: icons.Yaml,
|
||||
locale: false
|
||||
},
|
||||
{
|
||||
@@ -31,3 +31,21 @@ export const backendActions = [
|
||||
danger: true
|
||||
}
|
||||
];
|
||||
|
||||
export const yamlTemplate = `
|
||||
name: SGLang
|
||||
default_version: v0.5.1
|
||||
health_check_path: /health
|
||||
allowed_proxy_uris:
|
||||
- api/chat
|
||||
- api/v1
|
||||
versions:
|
||||
v0.0.1:
|
||||
image: lm/sglang
|
||||
command: run sglang
|
||||
v0.0.2:
|
||||
image: lm/sglang
|
||||
command: run sglang
|
||||
default_backend_parameters:
|
||||
- --host
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "backend name",
|
||||
"severity": "error"
|
||||
},
|
||||
"default_version": {
|
||||
"type": "string",
|
||||
"description": "default backend version"
|
||||
},
|
||||
"health_check_path": {
|
||||
"type": "string",
|
||||
"description": "health check path"
|
||||
},
|
||||
"allowed_proxy_uris": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "allowed proxy URIs"
|
||||
},
|
||||
"versions": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"description": "image name"
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "start command"
|
||||
}
|
||||
},
|
||||
"required": ["image", "command"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"description": "multiple backend versions"
|
||||
},
|
||||
"default_backend_parameters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "default backend parameters"
|
||||
}
|
||||
},
|
||||
"required": ["name", "default_version", "health_check_path", "versions"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import nvidiaLogo from '@/assets/logo/nvidia.png';
|
||||
import lmDeployLogo from '@/assets/logo/lmdeploy.png';
|
||||
import SGLangLogo from '@/assets/logo/sglang.png';
|
||||
import vLLMLogo from '@/assets/logo/vllm.png';
|
||||
|
||||
@@ -37,8 +37,8 @@ export default [
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
backend_name: 'TensorRT',
|
||||
icon: nvidiaLogo,
|
||||
backend_name: 'LMDeploy',
|
||||
icon: lmDeployLogo,
|
||||
version_configs: {
|
||||
'v1.0': {
|
||||
image_name: 'example/image:v1.0',
|
||||
@@ -49,6 +49,6 @@ export default [
|
||||
health_check_path: '/v1/models',
|
||||
default_version: 'v1.0',
|
||||
default_backend_param: ['--param1', '--param2'],
|
||||
description: 'TensorRT description'
|
||||
description: 'LMDeploy description'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface VersionConfigs {
|
||||
}
|
||||
|
||||
export interface FormData {
|
||||
backend_name: string;
|
||||
backend_name?: string;
|
||||
description?: string;
|
||||
version_configs?: VersionConfigs;
|
||||
compatibility_type?: string;
|
||||
@@ -14,10 +14,11 @@ export interface FormData {
|
||||
default_version?: string;
|
||||
default_backend_param?: string[];
|
||||
allowed_proxy_uris?: string[];
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export interface ListItem extends FormData {
|
||||
id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
id?: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import ListInput from '@/components/list-input';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
type AddModalProps = {
|
||||
action: PageActionType;
|
||||
};
|
||||
const BasicForm: React.FC<AddModalProps> = ({ action }) => {
|
||||
const form = Form.useFormInstance();
|
||||
const intl = useIntl();
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="backend_name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'common.table.name')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||
required
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FormData>
|
||||
name="compatibility_type"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('select', 'template', false)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
label={'Compatibility Template'}
|
||||
required
|
||||
options={[
|
||||
{ label: 'Template 1', value: 'tensorflow' },
|
||||
{ label: 'Template 2', value: 'torchserve' }
|
||||
]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<FormData>
|
||||
name="allowed_proxy_uris"
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<ListInput
|
||||
dataList={form.getFieldValue('allowed_proxy_uris') || []}
|
||||
onChange={(data: string[]) => {
|
||||
form.setFieldValue('allowed_proxy_uris', data);
|
||||
}}
|
||||
btnText={'Add Allowed Proxy URI'}
|
||||
label={'Proxy URI'}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
|
||||
<SealInput.TextArea
|
||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||
></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicForm;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { PageActionType } from '@/config/types';
|
||||
import CollapsePanel from '@/pages/_components/collapse-panel';
|
||||
import { Form } from 'antd';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||
import { FormData, ListItem } from '../config/types';
|
||||
import BasicForm from './basic';
|
||||
import ParametersForm from './parameters';
|
||||
import VersionsForm from './versions-config';
|
||||
|
||||
type AddModalProps = {
|
||||
action: PageActionType;
|
||||
currentData?: ListItem; // Used when action is EDIT
|
||||
onFinish: (values: FormData) => void;
|
||||
ref?: any;
|
||||
};
|
||||
const BackendForm: React.FC<AddModalProps> = forwardRef(
|
||||
({ action, currentData, onFinish }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentData) {
|
||||
form.setFieldsValue(currentData);
|
||||
}
|
||||
}, [currentData]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
resetFields: () => {
|
||||
form.resetFields();
|
||||
},
|
||||
submit: () => {
|
||||
form.submit();
|
||||
},
|
||||
setFieldsValue: (values: any) => {
|
||||
form.setFieldsValue(values);
|
||||
},
|
||||
getFieldsValue: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
validateFields: async () => {
|
||||
return await form.validateFields();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="basicForm"
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
preserve={false}
|
||||
scrollToFirstError={true}
|
||||
initialValues={currentData}
|
||||
>
|
||||
<BasicForm action={action}></BasicForm>
|
||||
<CollapsePanel
|
||||
items={[
|
||||
{
|
||||
key: 'versions',
|
||||
label: 'Versions',
|
||||
children: <VersionsForm action={action}></VersionsForm>
|
||||
},
|
||||
{
|
||||
key: 'parameters',
|
||||
label: 'Default Parameters',
|
||||
children: <ParametersForm action={action}></ParametersForm>
|
||||
}
|
||||
]}
|
||||
></CollapsePanel>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default BackendForm;
|
||||
@@ -0,0 +1,32 @@
|
||||
import ListInput from '@/components/list-input';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
type AddModalProps = {
|
||||
action: PageActionType;
|
||||
};
|
||||
const ParametersForm: React.FC<AddModalProps> = ({ action }) => {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="default_backend_param"
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<ListInput
|
||||
dataList={form.getFieldValue('default_backend_param') || []}
|
||||
onChange={(data: string[]) => {
|
||||
form.setFieldValue('default_backend_param', data);
|
||||
}}
|
||||
btnText={'Add Backend Parameter'}
|
||||
label={'Default Backend Parameters'}
|
||||
></ListInput>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParametersForm;
|
||||
@@ -0,0 +1,139 @@
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Divider, Form } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Box = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ItemWrapper = styled.div`
|
||||
&:hover {
|
||||
.divider {
|
||||
border-top: 1px dashed var(--ant-color-primary) !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
color: var(--ant-color-text-tertiary);
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
type AddModalProps = {
|
||||
action: PageActionType;
|
||||
};
|
||||
const VersionsForm: React.FC<AddModalProps> = ({ action }) => {
|
||||
const form = Form.useFormInstance();
|
||||
const versionList = [
|
||||
{
|
||||
version_no: '',
|
||||
image_name: '',
|
||||
run_command: '',
|
||||
is_default: false
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const versions = form.getFieldValue('version_configs') || [];
|
||||
if (Array.isArray(versions) && versions.length === 0) {
|
||||
form.setFieldValue('version_configs', versionList);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Form.List name="version_configs">
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid var(--ant-color-border)',
|
||||
borderRadius: 'var(--border-radius-base)',
|
||||
padding: '14px'
|
||||
}}
|
||||
>
|
||||
{fields.map(({ key, name, ...restField }, index) => (
|
||||
<ItemWrapper key={key}>
|
||||
<Box>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'version_no']}
|
||||
rules={[
|
||||
{ required: true, message: 'Version No. is required' }
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Version No."
|
||||
required
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'image_name']}
|
||||
rules={[
|
||||
{ required: true, message: 'Image Name is required' }
|
||||
]}
|
||||
>
|
||||
<SealInput.Input
|
||||
label="Image Name"
|
||||
required
|
||||
></SealInput.Input>
|
||||
</Form.Item>
|
||||
</Box>
|
||||
<Form.Item name={[name, 'run_command']} {...restField}>
|
||||
<SealInput.TextArea label="Execution Command"></SealInput.TextArea>
|
||||
</Form.Item>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="flex-center">
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'is_default']}
|
||||
valuePropName="checked"
|
||||
initialValue={false}
|
||||
noStyle
|
||||
>
|
||||
<Checkbox>Set as Default Version</Checkbox>
|
||||
</Form.Item>
|
||||
</span>
|
||||
<div className="flex-center gap-16">
|
||||
{fields.length > 1 && (
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => remove(name)}
|
||||
shape="circle"
|
||||
>
|
||||
<MinusOutlined />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Divider
|
||||
className="divider"
|
||||
style={{ borderTop: '1px dashed var(--ant-color-border)' }}
|
||||
/>
|
||||
{index === fields.length - 1 && (
|
||||
<Button
|
||||
onClick={() => add()}
|
||||
block
|
||||
variant="filled"
|
||||
color="default"
|
||||
>
|
||||
<PlusOutlined /> Add Version
|
||||
</Button>
|
||||
)}
|
||||
</ItemWrapper>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionsForm;
|
||||
+38
-38
@@ -1,21 +1,18 @@
|
||||
import { FilterBar } from '@/components/page-tools';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import useTableFetch from '@/hooks/use-table-fetch';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
ImportOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Space } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
deleteBackend,
|
||||
INFERENCE_BACKEND_API,
|
||||
queryBackendsList
|
||||
} from './apis';
|
||||
import AddModal from './components/add-modal';
|
||||
import BackendCardList from './components/backend-list';
|
||||
import dataList from './config/test';
|
||||
import { ListItem } from './config/types';
|
||||
import { FormData, ListItem } from './config/types';
|
||||
|
||||
const BackendList = () => {
|
||||
const intl = useIntl();
|
||||
@@ -40,10 +37,27 @@ const BackendList = () => {
|
||||
watch: false,
|
||||
contentForDelete: 'resources.modelfiles.modelfile'
|
||||
});
|
||||
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||
open: boolean;
|
||||
action: PageActionType;
|
||||
currentData?: Partial<FormData>;
|
||||
}>({ open: false, action: 'create' });
|
||||
|
||||
const handleClickDropdown = () => {};
|
||||
const handleOnSubmit = (values: FormData) => {
|
||||
console.log('yaml content:', values);
|
||||
setOpenModalStatus({
|
||||
open: false,
|
||||
action: 'create',
|
||||
currentData: undefined
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteByBatch = () => {};
|
||||
const handleAddBackend = () => {
|
||||
setOpenModalStatus({
|
||||
open: true,
|
||||
action: 'create'
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
@@ -63,40 +77,14 @@ const BackendList = () => {
|
||||
width={{
|
||||
input: 300
|
||||
}}
|
||||
actionType="dropdown"
|
||||
inputHolder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
buttonText={intl.formatMessage({
|
||||
id: 'resources.modelfiles.download'
|
||||
})}
|
||||
handleDeleteByBatch={handleDeleteByBatch}
|
||||
handleClickPrimary={handleClickDropdown}
|
||||
buttonText={'Add Backend'}
|
||||
handleDeleteByBatch={handleDeleteBatch}
|
||||
handleClickPrimary={handleAddBackend}
|
||||
handleSearch={handleSearch}
|
||||
handleInputChange={handleNameChange}
|
||||
rowSelection={rowSelection}
|
||||
showSelect={false}
|
||||
right={
|
||||
<Space size={20}>
|
||||
<Button type="primary" icon={<PlusOutlined />}>
|
||||
Add Backend
|
||||
</Button>
|
||||
<Button type="primary" icon={<ImportOutlined />}>
|
||||
Import backend from yaml
|
||||
</Button>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
onClick={handleDeleteByBatch}
|
||||
disabled={!rowSelection?.selectedRowKeys?.length}
|
||||
>
|
||||
<span>
|
||||
{intl?.formatMessage?.({ id: 'common.button.delete' })}
|
||||
{rowSelection?.selectedRowKeys?.length > 0 && (
|
||||
<span>({rowSelection?.selectedRowKeys?.length})</span>
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
></FilterBar>
|
||||
|
||||
<BackendCardList
|
||||
@@ -105,6 +93,18 @@ const BackendList = () => {
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
></BackendCardList>
|
||||
<AddModal
|
||||
action={openModalStatus.action}
|
||||
onClose={() => setOpenModalStatus({ open: false, action: 'create' })}
|
||||
onSubmit={handleOnSubmit}
|
||||
currentData={openModalStatus.currentData}
|
||||
open={openModalStatus.open}
|
||||
title={
|
||||
openModalStatus.action === 'create'
|
||||
? 'Create Backend'
|
||||
: 'Edit Backend'
|
||||
}
|
||||
></AddModal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import PageTools from '@/components/page-tools';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Basic from '../forms/basic';
|
||||
|
||||
const Title = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
.text {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
interface BasicFormProps {
|
||||
action: PageActionType;
|
||||
currentData?: any;
|
||||
}
|
||||
|
||||
const BasicForm = forwardRef((props: BasicFormProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const { action, currentData } = props;
|
||||
const formRef = useRef<any>(null);
|
||||
|
||||
const handleOnFinish = (values: any) => {
|
||||
console.log(values);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
validateFields: formRef.current?.validateFields,
|
||||
getFieldsValue: formRef.current?.getFieldsValue,
|
||||
submit: formRef.current?.submit
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageTools
|
||||
marginBottom={16}
|
||||
left={
|
||||
<Title>
|
||||
{intl.formatMessage({ id: 'clusters.create.configBasic' })}
|
||||
</Title>
|
||||
}
|
||||
marginTop={0}
|
||||
></PageTools>
|
||||
<Basic
|
||||
action={action}
|
||||
ref={formRef}
|
||||
onFinish={handleOnFinish}
|
||||
currentData={currentData}
|
||||
></Basic>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default BasicForm;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import BasicForm from './basic-form';
|
||||
import ParamertersForm from './parameters-form';
|
||||
import VersionConfigForm from './version-config-form';
|
||||
|
||||
export const moduleMap = {
|
||||
BasicForm: 'BasicForm',
|
||||
VersionConfigForm: 'VersionConfigForm',
|
||||
ParamertersForm: 'ParamertersForm'
|
||||
};
|
||||
|
||||
export const moduleRegistry: Record<string, React.ComponentType<any>> = {
|
||||
[moduleMap.BasicForm]: BasicForm,
|
||||
[moduleMap.VersionConfigForm]: VersionConfigForm,
|
||||
[moduleMap.ParamertersForm]: ParamertersForm
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import PageTools from '@/components/page-tools';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Parameters from '../forms/parameters';
|
||||
|
||||
const Title = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
.text {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
interface BasicFormProps {
|
||||
action: PageActionType;
|
||||
currentData?: any;
|
||||
}
|
||||
|
||||
const ParametersForm = forwardRef((props: BasicFormProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const { action, currentData } = props;
|
||||
const formRef = useRef<any>(null);
|
||||
|
||||
const handleOnFinish = (values: any) => {
|
||||
console.log(values);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
validateFields: formRef.current?.validateFields,
|
||||
getFieldsValue: formRef.current?.getFieldsValue,
|
||||
submit: formRef.current?.submit
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageTools
|
||||
marginBottom={16}
|
||||
left={<Title>Parameters Configuration</Title>}
|
||||
marginTop={0}
|
||||
></PageTools>
|
||||
<Parameters
|
||||
action={action}
|
||||
ref={formRef}
|
||||
onFinish={handleOnFinish}
|
||||
currentData={currentData}
|
||||
></Parameters>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ParametersForm;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useMemo } from 'react';
|
||||
import { moduleMap } from './module-registry';
|
||||
|
||||
export default function useStepList() {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleBack = useMemoizedFn(() => {
|
||||
navigate(-1);
|
||||
});
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
title: 'Basic Info',
|
||||
content: '',
|
||||
showButtons: () => {
|
||||
return {
|
||||
previous: false,
|
||||
next: true,
|
||||
save: false,
|
||||
skip: false
|
||||
};
|
||||
},
|
||||
defaultShow: true,
|
||||
showForms: [moduleMap.BasicForm],
|
||||
showModules: []
|
||||
},
|
||||
{
|
||||
title: 'Version Configuration',
|
||||
content: '',
|
||||
showButtons: () => {
|
||||
return {
|
||||
previous: true,
|
||||
next: true,
|
||||
save: false,
|
||||
skip: false
|
||||
};
|
||||
},
|
||||
defaultShow: true,
|
||||
showForms: [moduleMap.VersionConfigForm],
|
||||
showModules: []
|
||||
},
|
||||
{
|
||||
title: 'Parameters',
|
||||
content: '',
|
||||
showButtons: () => {
|
||||
return {
|
||||
previous: true,
|
||||
next: false,
|
||||
save: true,
|
||||
skip: false
|
||||
};
|
||||
},
|
||||
defaultShow: true,
|
||||
showForms: [moduleMap.ParamertersForm],
|
||||
showModules: [],
|
||||
beforeNext: handleBack
|
||||
}
|
||||
],
|
||||
[handleBack, intl]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import PageTools from '@/components/page-tools';
|
||||
import { PageActionType } from '@/config/types';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, FormInstance } from 'antd';
|
||||
import {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormData } from '../config/types';
|
||||
import VersionsConfig from '../forms/versions-config';
|
||||
|
||||
const PoolContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const PoolFormWrapper = styled.div`
|
||||
flex: 1;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const Title = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
.text {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
interface WorkerPoolsFormProps {
|
||||
action: PageActionType;
|
||||
currentData?: any;
|
||||
}
|
||||
|
||||
const WorkerPoolsForm = forwardRef((props: WorkerPoolsFormProps, ref) => {
|
||||
const intl = useIntl();
|
||||
const { action, currentData } = props;
|
||||
const countRef = useRef(0);
|
||||
const formRefs = useRef<Record<number, FormInstance<any> | null>>({});
|
||||
const [activeKey, setActiveKey] = useState<Set<number>>(new Set([0]));
|
||||
const [versionConfigs, setVersionConfigs] = useState<Map<number, FormData>>(
|
||||
new Map([
|
||||
[
|
||||
0,
|
||||
{
|
||||
version_name: '',
|
||||
image_name: '',
|
||||
run_command: ''
|
||||
}
|
||||
]
|
||||
]) as Map<number, FormData>
|
||||
);
|
||||
|
||||
const handleOnFinish = (values: any) => {};
|
||||
|
||||
const updateCount = () => {
|
||||
countRef.current += 1;
|
||||
return countRef.current;
|
||||
};
|
||||
|
||||
const handleAddPool = () => {
|
||||
const newId = updateCount();
|
||||
setVersionConfigs((prev) =>
|
||||
new Map(prev).set(newId, {
|
||||
name: ``
|
||||
} as FormData)
|
||||
);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setActiveKey((prev) => new Set([newId]));
|
||||
window.scrollTo({
|
||||
top: document.body.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemovePool = (id: number) => {
|
||||
if (versionConfigs.size === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVersionConfigs((prev) => {
|
||||
const newList = new Map(prev);
|
||||
newList.delete(id);
|
||||
return newList;
|
||||
});
|
||||
formRefs.current[id] = null;
|
||||
};
|
||||
|
||||
const gatherFormValues = (results: PromiseSettledResult<any>[]) => {
|
||||
const resultList = results.map((result: PromiseSettledResult<any>) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
return result.value;
|
||||
}
|
||||
if (result.status === 'rejected') {
|
||||
return result.reason?.values || {};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
console.log('gatherFormValues========', resultList);
|
||||
return resultList.filter((item) => item);
|
||||
};
|
||||
|
||||
const validateFields = async () => {
|
||||
const promises = Array.from(Object.values(formRefs.current)).map((form) =>
|
||||
form?.validateFields()
|
||||
);
|
||||
const results = await Promise.allSettled(promises);
|
||||
console.log('results========0', promises, results);
|
||||
if (results.some((result) => result.status === 'rejected')) {
|
||||
return Promise.reject({
|
||||
values: {
|
||||
worker_pools: gatherFormValues(results)
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
worker_pools: gatherFormValues(results)
|
||||
});
|
||||
};
|
||||
|
||||
const setFieldsValue = (data: any) => {
|
||||
const worker_pools = data.worker_pools || [];
|
||||
const newWorkerPools = worker_pools.map(
|
||||
(poolData: FormData, index: number) => [index, poolData]
|
||||
);
|
||||
console.log('newWorkerPools========', newWorkerPools);
|
||||
setVersionConfigs(new Map(newWorkerPools));
|
||||
};
|
||||
|
||||
const getFieldsValue = () => {
|
||||
const values = Object.values(formRefs.current).map((form) =>
|
||||
form?.getFieldsValue()
|
||||
);
|
||||
console.log('getFieldsValue========', values);
|
||||
return {
|
||||
worker_pools: values
|
||||
};
|
||||
};
|
||||
|
||||
const handleOnToggle = (open: boolean, key: number) => {
|
||||
console.log('Active keys changed:', key);
|
||||
if (open) {
|
||||
setActiveKey((prev) => new Set([key]));
|
||||
} else {
|
||||
setActiveKey((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(key);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submit: () => {},
|
||||
validateFields: validateFields,
|
||||
getFieldsValue: getFieldsValue
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (currentData) {
|
||||
console.log('currentData===========1=', currentData);
|
||||
setFieldsValue(currentData);
|
||||
}
|
||||
}, [currentData]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageTools
|
||||
marginBottom={22}
|
||||
marginTop={0}
|
||||
left={
|
||||
<span className="flex-center gap-16">
|
||||
<Title>Backend Versions</Title>
|
||||
</span>
|
||||
}
|
||||
right={
|
||||
<Button
|
||||
onClick={handleAddPool}
|
||||
variant="filled"
|
||||
color="default"
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Add Version
|
||||
</Button>
|
||||
}
|
||||
></PageTools>
|
||||
|
||||
{Array.from(versionConfigs.keys()).map((key, index) => (
|
||||
<PoolContainer key={key}>
|
||||
<PoolFormWrapper>
|
||||
<VersionsConfig
|
||||
name={`versionForm_${key}`}
|
||||
action={action}
|
||||
ref={(el: any) => {
|
||||
if (el) {
|
||||
formRefs.current[key] = el;
|
||||
}
|
||||
}}
|
||||
collapseProps={{
|
||||
collapsible: true,
|
||||
open: activeKey.has(key),
|
||||
defaultOpen: activeKey.has(key),
|
||||
onToggle: (open: boolean) => handleOnToggle(open, key)
|
||||
}}
|
||||
showDelete={versionConfigs.size > 1}
|
||||
onFinish={handleOnFinish}
|
||||
currentData={versionConfigs.get(key)}
|
||||
onDelete={() => handleRemovePool(key)}
|
||||
></VersionsConfig>
|
||||
</PoolFormWrapper>
|
||||
</PoolContainer>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default WorkerPoolsForm;
|
||||
@@ -5,7 +5,7 @@ import BaseSelect from '@/components/seal-form/base/select';
|
||||
import { PageAction } from '@/config';
|
||||
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||
import { IS_FIRST_LOGIN, writeState } from '@/utils/localstore/index';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { Button, Input, Pagination, Space, message } from 'antd';
|
||||
@@ -266,6 +266,11 @@ const Catalog: React.FC = () => {
|
||||
left={
|
||||
<Space>
|
||||
<Input
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||
></SearchOutlined>
|
||||
}
|
||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
style={{ width: 230 }}
|
||||
size="large"
|
||||
|
||||
@@ -161,7 +161,6 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
form={form}
|
||||
onFinish={handleOk}
|
||||
preserve={false}
|
||||
style={{ padding: '16px 24px' }}
|
||||
clearOnDestroy={true}
|
||||
onValuesChange={handleOnValuesChange}
|
||||
scrollToFirstError={true}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import ColumnWrapper from '../../_components/column-wrapper';
|
||||
import { queryCatalogItemSpec } from '../apis';
|
||||
import {
|
||||
defaultFormValues,
|
||||
@@ -21,7 +22,6 @@ import { CatalogSpec, FormData, ListItem, SourceType } from '../config/types';
|
||||
import { generateGPUIds } from '../config/utils';
|
||||
import { useCheckCompatibility } from '../hooks';
|
||||
import useFormInitialValues from '../hooks/use-form-initial-values';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import CompatibilityAlert from './compatible-alert';
|
||||
import DataForm from './data-form';
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import ColumnWrapper from '../../_components/column-wrapper';
|
||||
import { defaultFormValues, deployFormKeyMap, modelSourceMap } from '../config';
|
||||
import { backendOptionsMap } from '../config/backend-parameters';
|
||||
import { BackendOption, FormData, SourceType } from '../config/types';
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
useSelectModel
|
||||
} from '../hooks';
|
||||
import useCheckBackend from '../hooks/use-check-backend';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import CompatibilityAlert from './compatible-alert';
|
||||
import DataForm from './data-form';
|
||||
import GGUFResult from './gguf-result';
|
||||
|
||||
@@ -13,7 +13,7 @@ import useTableRowSelection from '@/hooks/use-table-row-selection';
|
||||
import useTableSort from '@/hooks/use-table-sort';
|
||||
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||
import { handleBatchRequest } from '@/utils';
|
||||
import { DownOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
@@ -579,6 +579,11 @@ const Models: React.FC<ModelsProps> = ({
|
||||
left={
|
||||
<Space>
|
||||
<Input
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||
></SearchOutlined>
|
||||
}
|
||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||
style={{ width: 160 }}
|
||||
size="large"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useIntl } from '@umijs/max';
|
||||
import { Button, Modal } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import ColumnWrapper from '../../_components/column-wrapper';
|
||||
import {
|
||||
deployFormKeyMap,
|
||||
modelSourceMap,
|
||||
@@ -14,7 +15,6 @@ import { backendOptionsMap } from '../config/backend-parameters';
|
||||
import { BackendOption, FormData } from '../config/types';
|
||||
import { generateGPUSelector } from '../config/utils';
|
||||
import { useCheckCompatibility } from '../hooks';
|
||||
import ColumnWrapper from './column-wrapper';
|
||||
import CompatibilityAlert from './compatible-alert';
|
||||
import DataForm from './data-form';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import ColumnWrapper from '../components/column-wrapper';
|
||||
import ColumnWrapper from '../../_components/column-wrapper';
|
||||
import CompatibilityAlert from '../components/compatible-alert';
|
||||
import ModelCard from '../components/model-card';
|
||||
import SearchModel from '../components/search-model';
|
||||
|
||||
@@ -87,7 +87,6 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
|
||||
form={form}
|
||||
onFinish={handleOk}
|
||||
preserve={false}
|
||||
style={{ padding: '16px 24px' }}
|
||||
clearOnDestroy={true}
|
||||
initialValues={{
|
||||
source: source
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import EditorWrap from '@/components/editor-wrap';
|
||||
import HighlightCode from '@/components/highlight-code';
|
||||
import { BulbOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Modal } from 'antd';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import CommandViewer from '../../_components/command-viewer';
|
||||
|
||||
type ViewModalProps = {
|
||||
title?: string;
|
||||
@@ -32,30 +31,30 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
const { title, open, onCancel, viewCodeContent } = props || {};
|
||||
|
||||
const intl = useIntl();
|
||||
const [lang, setLang] = useState<string>(langMap.shell);
|
||||
|
||||
const codeValue = useMemo(() => {
|
||||
if (lang === langMap.shell) {
|
||||
return viewCodeContent?.curlCode;
|
||||
}
|
||||
if (lang === langMap.javascript) {
|
||||
return viewCodeContent?.nodeJsCode;
|
||||
}
|
||||
if (lang === langMap.python) {
|
||||
return viewCodeContent?.pythonCode;
|
||||
}
|
||||
return '';
|
||||
}, [lang, viewCodeContent]);
|
||||
const [codeValue, setCodeValue] = useState<string>(viewCodeContent?.curlCode);
|
||||
|
||||
const handleOnChangeLang = (value: string | number) => {
|
||||
setLang(value as string);
|
||||
if (value === langMap.shell) {
|
||||
setCodeValue(viewCodeContent?.curlCode);
|
||||
}
|
||||
if (value === langMap.javascript) {
|
||||
setCodeValue(viewCodeContent?.nodeJsCode);
|
||||
}
|
||||
if (value === langMap.python) {
|
||||
setCodeValue(viewCodeContent?.pythonCode);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setLang(langMap.shell);
|
||||
onCancel();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setCodeValue(viewCodeContent?.curlCode);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -74,26 +73,13 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
{intl.formatMessage({ id: 'playground.viewcode.info' })}
|
||||
</div>
|
||||
<div>
|
||||
<EditorWrap
|
||||
<CommandViewer
|
||||
code={codeValue}
|
||||
copyText={codeValue}
|
||||
langOptions={langOptions}
|
||||
options={langOptions}
|
||||
defaultValue={langMap.shell}
|
||||
showHeader={true}
|
||||
onChangeLang={handleOnChangeLang}
|
||||
styles={{
|
||||
wrapper: {
|
||||
backgroundColor: 'var(--color-editor-dark)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HighlightCode
|
||||
height={380}
|
||||
theme="dark"
|
||||
code={codeValue}
|
||||
lang={lang}
|
||||
copyable={false}
|
||||
></HighlightCode>
|
||||
</EditorWrap>
|
||||
onChange={handleOnChangeLang}
|
||||
></CommandViewer>
|
||||
<div
|
||||
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
|
||||
>
|
||||
@@ -128,4 +114,4 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ViewCodeModal);
|
||||
export default ViewCodeModal;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GPUStackVersionAtom } from '@/atoms/user';
|
||||
import { getAtomStorage } from '@/atoms/utils';
|
||||
import EditorWrap from '@/components/editor-wrap';
|
||||
import HighlightCode from '@/components/highlight-code';
|
||||
import CommandViewer from '@/pages/_components/command-viewer';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Radio } from 'antd';
|
||||
import React from 'react';
|
||||
@@ -98,27 +98,15 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
></div>
|
||||
)}
|
||||
{activeKey === 'npu' ? (
|
||||
<EditorWrap
|
||||
<CommandViewer
|
||||
headerHeight={32}
|
||||
copyText={code}
|
||||
langOptions={npuOptions}
|
||||
options={npuOptions}
|
||||
defaultValue="npu"
|
||||
showHeader={true}
|
||||
onChangeLang={handleOnChange}
|
||||
styles={{
|
||||
wrapper: {
|
||||
backgroundColor: 'var(--color-editor-dark)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HighlightCode
|
||||
theme="dark"
|
||||
code={code.replace(/\\/g, '')}
|
||||
copyValue={code}
|
||||
lang="bash"
|
||||
copyable={false}
|
||||
></HighlightCode>
|
||||
</EditorWrap>
|
||||
onChange={handleOnChange}
|
||||
lang="bash"
|
||||
code={code.replace(/\\/g, '')}
|
||||
></CommandViewer>
|
||||
) : (
|
||||
<HighlightCode
|
||||
theme="dark"
|
||||
|
||||
Reference in New Issue
Block a user