From 90111bc97b8e288cdcdbcc87ff3d308ac150a061 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A0=81=E5=86=9C=E5=BE=90?=
<10495708+xu-zhonglin@user.noreply.gitee.com>
Date: Fri, 10 Apr 2026 18:52:05 +0800
Subject: [PATCH] feat: vm style change
---
src/components/CreateComponentModal/index.js | 17 +-
src/components/GlobalRouter/index.js | 128 +-
src/components/GlobalRouter/index.less | 77 +
.../ImageVirtualMachineForm/index.js | 1701 +++++++++--------
.../ImageVirtualMachineForm/index.less | 288 ++-
src/components/VMAssetCatalogModal/index.js | 263 +--
src/components/VMAssetCatalogModal/index.less | 150 +-
src/locales/en-US/enterprise.js | 4 +-
src/locales/en-US/team.js | 18 +-
src/locales/zh-CN/enterprise.js | 2 +-
src/locales/zh-CN/team.js | 18 +-
11 files changed, 1536 insertions(+), 1130 deletions(-)
diff --git a/src/components/CreateComponentModal/index.js b/src/components/CreateComponentModal/index.js
index 17f48145a..0f0583888 100644
--- a/src/components/CreateComponentModal/index.js
+++ b/src/components/CreateComponentModal/index.js
@@ -108,6 +108,7 @@ const CreateComponentModal = ({ visible, onCancel, dispatch, currentEnterprise,
const [currentDatabaseType, setCurrentDatabaseType] = useState(null);
const [showDatabaseForm, setShowDatabaseForm] = useState(false);
const [virtualMachineImages, setVirtualMachineImages] = useState([]);
+ const [vmAssetCatalogVisible, setVmAssetCatalogVisible] = useState(false);
// 插件相关状态
const [availablePlugins, setAvailablePlugins] = useState([]);
@@ -1398,6 +1399,7 @@ const CreateComponentModal = ({ visible, onCancel, dispatch, currentEnterprise,
setLocalMarketPage(1);
setLocalMarketActiveTab('all');
setCurrentFormType('');
+ setVmAssetCatalogVisible(false);
setHasInitialized(false); // 重置初始化标志
}
}, [visible, initialView, hasInitialized]);
@@ -1524,6 +1526,10 @@ const CreateComponentModal = ({ visible, onCancel, dispatch, currentEnterprise,
const handleBack = () => {
if (currentView === 'form') {
+ if (currentFormType === 'vm' && vmAssetCatalogVisible) {
+ setVmAssetCatalogVisible(false);
+ return;
+ }
setCurrentFormType('');
popViewHistory();
} else if (currentView === 'plugin') {
@@ -2021,6 +2027,7 @@ const CreateComponentModal = ({ visible, onCancel, dispatch, currentEnterprise,
case 'thirdList':
return selectedOauthService ? formatMessage({ id: 'componentOverview.body.CreateComponentModal.repo' }, { name: selectedOauthService.name }) : formatMessage({ id: 'componentOverview.body.CreateComponentModal.source_repo' });
case 'form':
+ if (currentFormType === 'vm' && vmAssetCatalogVisible) return formatMessage({ id: 'Vm.assetCatalog.title' });
if (currentFormType === 'docker') return formatMessage({ id: 'componentOverview.body.CreateComponentModal.container' });
if (currentFormType === 'docker-compose') return formatMessage({ id: 'componentOverview.body.CreateComponentModal.docker_compose' });
if (currentFormType === 'code-custom') return formatMessage({ id: 'componentOverview.body.CreateComponentModal.source_code' });
@@ -2076,6 +2083,9 @@ const CreateComponentModal = ({ visible, onCancel, dispatch, currentEnterprise,
const showSaaSPrice = PluginUtils.isInstallPlugin(pluginsList, 'rainbond-bill');
// 表单视图需要显示提交按钮
if (currentView === 'form') {
+ if (currentFormType === 'vm' && vmAssetCatalogVisible) {
+ return null;
+ }
return (
- {/* 底部:收起按钮 */}
+ {/* 底部:升级提示和收起按钮 */}
+ {this.renderUpgradeEntry()}
{this.renderCollapseButton()}
diff --git a/src/components/GlobalRouter/index.less b/src/components/GlobalRouter/index.less
index 5ab4cb8e1..ec37c9dc3 100644
--- a/src/components/GlobalRouter/index.less
+++ b/src/components/GlobalRouter/index.less
@@ -89,6 +89,79 @@
// ==================== 底部区域 ====================
.menuFooter {
flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+}
+
+.upgradeCard {
+ display: flex;
+ align-items: center;
+ margin: 8px 12px 0;
+ padding: 10px 12px;
+ border-radius: 10px;
+ border: 1px solid fade(@success-color, 24%);
+ background: linear-gradient(180deg, fade(@success-color, 12%) 0%, fade(@success-color, 4%) 100%);
+ cursor: pointer;
+ transition: background @transition-duration @transition-timing,
+ box-shadow @transition-duration @transition-timing,
+ border-color @transition-duration @transition-timing;
+
+ &:hover {
+ background: linear-gradient(180deg, fade(@success-color, 16%) 0%, fade(@success-color, 8%) 100%);
+ border-color: fade(@success-color, 36%);
+ box-shadow: 0 8px 20px fade(@success-color, 14%);
+ }
+}
+
+.upgradeCardIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ background: fade(@success-color, 14%);
+ color: @success-color;
+ font-size: 16px;
+}
+
+.upgradeCardContent {
+ min-width: 0;
+ margin-left: 8px;
+}
+
+.upgradeCardTitle {
+ color: @heading-color;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 18px;
+}
+
+.upgradeShortcut {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ margin: 12px 8px 0;
+ border-radius: 8px;
+ color: @success-color;
+ cursor: pointer;
+ transition: background @transition-duration @transition-timing,
+ color @transition-duration @transition-timing;
+
+ &:hover {
+ background: fade(@success-color, 12%);
+ color: @success-color;
+ }
+}
+
+.upgradeStatusIcon {
+ display: block;
+ width: 18px;
+ height: 18px;
+ color: currentColor;
}
// ==================== 视图切换器 ====================
@@ -492,6 +565,10 @@
background: #d9d9d9;
}
}
+
+ .upgradeCard {
+ display: none;
+ }
}
// ==================== 折叠状态下的视图切换器 ====================
diff --git a/src/components/ImageVirtualMachineForm/index.js b/src/components/ImageVirtualMachineForm/index.js
index 076cc1739..7bdd95378 100644
--- a/src/components/ImageVirtualMachineForm/index.js
+++ b/src/components/ImageVirtualMachineForm/index.js
@@ -1,51 +1,69 @@
/* eslint-disable react/jsx-indent */
/* eslint-disable no-nested-ternary */
-import { Alert, Button, Form, Input, Select, Radio, Upload, Icon, Tooltip, notification, Switch, Progress, message } from 'antd';
+import {
+ Alert,
+ Button,
+ Form,
+ Input,
+ Select,
+ Radio,
+ Upload,
+ Icon,
+ Tooltip,
+ notification,
+ Switch,
+ message
+} from 'antd';
import { connect } from 'dva';
import React, { Fragment, PureComponent } from 'react';
import { formatMessage } from '@/utils/intl';
-import AddGroup from '../../components/AddOrEditGroup';
-import cookie from '../../utils/cookie';
-import anolisOS from '../../../public/images/anolis.png';
-import centOS from '../../../public/images/centos.png';
-import deepinOS from '../../../public/images/deepin.png';
-import ubuntuOS from '../../../public/images/ubuntu.png';
import { pinyin } from 'pinyin-pro';
import globalUtil from '../../utils/global';
import role from '@/utils/newRole';
import handleAPIError from '../../utils/error';
-import ChunkUploader from '../../utils/ChunkUploader';
import VMAssetCatalogModal from '../VMAssetCatalogModal';
import styles from './index.less';
-
+import anolisOS from '../../../public/images/anolis.png';
+import centOS from '../../../public/images/centos.png';
+import deepinOS from '../../../public/images/deepin.png';
+import ubuntuOS from '../../../public/images/ubuntu.png';
const { Option } = Select;
-const { TextArea } = Input;
-const formItemLayout = {
- labelCol: {
- span: 7
+const PUBLIC_VM_OPTIONS = [
+ {
+ key: 'centos7.9',
+ vm_url: 'https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso',
+ image_name: 'centos7.9',
+ icon: centOS
},
- wrapperCol: {
- span: 15
- }
-};
-const formItemLayouts = {
- labelCol: {
- span: 7
+ {
+ key: 'anolisos7.9',
+ vm_url: 'https://mirrors.aliyun.com/anolis/7.9/isos/GA/x86_64/AnolisOS-7.9-Minimal-x86_64-dvd.iso',
+ image_name: 'anolisos7.9',
+ icon: anolisOS
},
- wrapperCol: {
- span: 15
+ {
+ key: 'deepin20.9',
+ vm_url: 'https://mirrors.aliyun.com/deepin-cd/20.9/deepin-desktop-community-20.9-amd64.iso',
+ image_name: 'deepin20.9',
+ icon: deepinOS
+ },
+ {
+ key: 'ubuntu23.10',
+ vm_url: 'https://mirrors.aliyun.com/ubuntu-releases/mantic/ubuntu-23.10-live-server-amd64.iso',
+ image_name: 'ubuntu23.10',
+ icon: ubuntuOS
}
-};
+];
@connect(
({ global, loading, user, teamControl }) => ({
currUser: user.currentUser,
groups: global.groups,
rainbondInfo: global.rainbondInfo,
- createAppByDockerrunLoading:
- loading.effects['createApp/createAppByVirtualMachine'],
+ createAppByVirtualMachineLoading:
+ loading.effects['createApp/createAppByVirtualMachine'],
currentTeamPermissionsInfo: teamControl.currentTeamPermissionsInfo
}),
null,
@@ -56,21 +74,13 @@ const formItemLayouts = {
export default class Index extends PureComponent {
constructor(props) {
super(props);
+ const defaultPublicVm = PUBLIC_VM_OPTIONS[0];
this.state = {
- showUsernameAndPass: false,
- addGroup: false,
- language: cookie.get('language') === 'zh-CN' ? true : false,
radioKey: 'public',
- assetCatalogVisible: false,
- uploadMode: 'normal',
fileList: [],
- percents: false,
vmShow: false,
existFileList: [],
- chunkUploadProgress: 0,
- isChunkUploading: false,
- currentFile: null,
- chunkUploader: null,
+ showAdvanced: false,
vmCapabilities: {
chunk_upload_supported: false,
gpu_supported: false,
@@ -80,84 +90,113 @@ export default class Index extends PureComponent {
usb_resources: [],
networks: []
},
- PublicVm: [
- { vm_url: 'https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso', image_name: 'centos7.9' },
- { vm_url: 'https://mirrors.aliyun.com/anolis/7.9/isos/GA/x86_64/AnolisOS-7.9-Minimal-x86_64-dvd.iso', image_name: 'anolisos7.9' },
- { vm_url: 'https://mirrors.aliyun.com/deepin-cd/20.9/deepin-desktop-community-20.9-amd64.iso', image_name: 'deepin20.9' },
- { vm_url: 'https://mirrors.aliyun.com/ubuntu-releases/mantic/ubuntu-23.10-live-server-amd64.iso', image_name: 'ubuntu23.10' },
- ],
- selectName: 'centos7.9',
- selectUrl: 'https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso',
+ publicVmOptions: PUBLIC_VM_OPTIONS,
+ selectedPublicVm: defaultPublicVm,
comNames: [],
creatComPermission: {}
};
this.appliedTemplateVersionId = null;
}
+
componentWillMount() {
this.loop = false;
}
+
componentWillUnmount() {
this.loop = false;
}
+
componentDidMount() {
this.fetchPipePipeline();
this.fetchVMCapabilities();
this.handleJarWarUpload();
- const { handleType, groupId } = this.props;
- const group_id = globalUtil.getAppID()
- if(group_id){
+ const fixedGroupId = this.getFixedGroupId();
+ if (fixedGroupId) {
this.setState({
- creatComPermission: role.queryPermissionsInfo(this.props.currentTeamPermissionsInfo?.team, 'app_overview', `app_${globalUtil.getAppID() || group_id}`)
- })
- }
- if (handleType && handleType === 'Service') {
- this.fetchComponentNames(Number(groupId));
+ creatComPermission: role.queryPermissionsInfo(
+ this.props.currentTeamPermissionsInfo?.team,
+ 'app_overview',
+ `app_${fixedGroupId}`
+ )
+ });
+ this.fetchComponentNames(fixedGroupId);
}
if (this.props.templatePreset) {
this.applyTemplatePreset(this.props.templatePreset);
}
}
+
componentDidUpdate(prevProps) {
if (
this.props.templatePreset &&
(!prevProps.templatePreset ||
- prevProps.templatePreset.template_version_id !== this.props.templatePreset.template_version_id)
+ prevProps.templatePreset.template_version_id !==
+ this.props.templatePreset.template_version_id)
) {
this.applyTemplatePreset(this.props.templatePreset);
}
}
+
+ getFixedGroupId = () => {
+ const { handleType, groupId, data = {} } = this.props;
+ const currentAppId = globalUtil.getAppID();
+ if (handleType === 'Service' && groupId) {
+ return Number(groupId);
+ }
+ if (currentAppId) {
+ return Number(currentAppId);
+ }
+ if (data.group_id) {
+ return Number(data.group_id);
+ }
+ return undefined;
+ };
+
+ getCurrentGroupName = () => {
+ const { groups = [] } = this.props;
+ const fixedGroupId = this.getFixedGroupId();
+ if (!fixedGroupId) {
+ return '';
+ }
+ const target = (groups || []).find(
+ item => Number(item.group_id) === Number(fixedGroupId)
+ );
+ return target ? target.group_name : `${fixedGroupId}`;
+ };
+
handleJarWarUpload = () => {
- const { dispatch } = this.props
- const teamName = globalUtil.getCurrTeamName()
- const regionName = globalUtil.getCurrRegionName()
- //获取上传事件
+ const { dispatch } = this.props;
dispatch({
- type: "createApp/createJarWarServices",
+ type: 'createApp/createJarWarServices',
payload: {
- region: regionName,
- team_name: teamName,
- component_id: '',
+ region: globalUtil.getCurrRegionName(),
+ team_name: globalUtil.getCurrTeamName(),
+ component_id: ''
},
- callback: (res) => {
+ callback: res => {
if (res && res.status_code === 200) {
- this.setState({
- record: res.bean,
- event_id: res.bean.event_id,
- region_name: res.bean && res.bean.region,
- team_name: res.bean && res.bean.team_name
- }, () => {
- if (res.bean.region !== '') {
- this.loop = true;
- this.handleJarWarUploadStatus();
+ this.setState(
+ {
+ record: res.bean,
+ event_id: res.bean.event_id,
+ region_name: res.bean && res.bean.region,
+ team_name: res.bean && res.bean.team_name
+ },
+ () => {
+ if (res.bean.region !== '') {
+ this.loop = true;
+ this.handleJarWarUploadStatus();
+ }
}
- })
+ );
}
},
handleError: err => {
handleAPIError(err);
}
});
- }
+ };
+
fetchVMCapabilities = () => {
const { dispatch } = this.props;
dispatch({
@@ -167,54 +206,32 @@ export default class Index extends PureComponent {
},
callback: res => {
const capabilities = (res && res.bean) || this.state.vmCapabilities;
- this.setState(prevState => {
- const nextState = {
- vmCapabilities: capabilities
- };
- if (!capabilities.chunk_upload_supported && prevState.uploadMode === 'chunk') {
- nextState.uploadMode = 'normal';
- }
- if (
- capabilities.chunk_upload_supported &&
- prevState.uploadMode === 'normal' &&
- prevState.fileList.length === 0 &&
- prevState.existFileList.length === 0 &&
- !prevState.currentFile
- ) {
- nextState.uploadMode = 'chunk';
- }
- return nextState;
+ this.setState({
+ vmCapabilities: capabilities
});
},
handleError: err => {
handleAPIError(err);
}
});
- }
- //查询上传状态
+ };
+
handleJarWarUploadStatus = () => {
- const {
- dispatch
- } = this.props;
- const { event_id } = this.state
+ const { dispatch } = this.props;
+ const { event_id } = this.state;
dispatch({
type: 'createApp/createJarWarUploadStatus',
payload: {
region: globalUtil.getCurrRegionName(),
team_name: globalUtil.getCurrTeamName(),
- event_id: event_id
+ event_id
},
callback: data => {
- if (data) {
- if (data.bean.package_name && data.bean.package_name.length > 0) {
- this.setState({
- existFileList: data.bean.package_name
- });
- // notification.success({
- // message: formatMessage({id:'notification.success.upload_file'})
- // })
- this.loop = false
- }
+ if (data && data.bean.package_name && data.bean.package_name.length > 0) {
+ this.setState({
+ existFileList: data.bean.package_name
+ });
+ this.loop = false;
}
if (this.loop) {
setTimeout(() => {
@@ -227,78 +244,67 @@ export default class Index extends PureComponent {
}
});
};
- //删除上传文件
+
handleJarWarUploadDelete = () => {
- const { event_id } = this.state
- const { dispatch, form } = this.props
+ const { event_id } = this.state;
+ const { dispatch, form } = this.props;
dispatch({
- type: "createApp/deleteJarWarUploadStatus",
+ type: 'createApp/deleteJarWarUploadStatus',
payload: {
team_name: globalUtil.getCurrTeamName(),
event_id
},
- callback: (data) => {
- if (data.bean.res == 'ok') {
+ callback: data => {
+ if (data.bean.res === 'ok') {
this.setState({
existFileList: [],
- fileList: [],
- currentFile: null,
- chunkUploader: null,
- chunkUploadProgress: 0,
- isChunkUploading: false
+ fileList: []
});
form.setFieldsValue({ packageTarFile: [] });
notification.success({
message: formatMessage({ id: 'notification.success.delete_file' })
- })
- this.handleJarWarUpload()
+ });
+ this.handleJarWarUpload();
}
},
handleError: err => {
handleAPIError(err);
}
});
- }
- onAddGroup = () => {
- this.setState({ addGroup: true });
- };
- cancelAddGroup = () => {
- this.setState({ addGroup: false });
};
- handleAddGroup = groupId => {
- const { setFieldsValue } = this.props.form;
- setFieldsValue({ group_id: groupId });
- role.refreshPermissionsInfo(groupId, false, this.callbcak)
- this.cancelAddGroup();
- };
- callbcak=(val)=>{
- this.setState({ creatComPermission: val })
- }
+
handleSubmit = e => {
e.preventDefault();
- const { event_id, radioKey, uploadMode, existFileList, isChunkUploading } = this.state
+ const { event_id, radioKey, existFileList, selectedPublicVm } = this.state;
const { form, onSubmit, archInfo } = this.props;
+ const fixedGroupId = this.getFixedGroupId();
+
form.validateFields((err, fieldsValue) => {
if (!err && onSubmit) {
- if (radioKey === 'upload' && uploadMode === 'chunk' && isChunkUploading) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.waitForCompletion' }));
- return;
- }
if (radioKey === 'upload' && existFileList.length === 0) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.finishBeforeSubmit' }));
+ message.warning(
+ formatMessage({ id: 'teamAdd.create.upload.finishBeforeSubmit' })
+ );
return;
}
- if (archInfo && archInfo.length != 2 && archInfo.length != 0) {
- fieldsValue.arch = archInfo[0]
+
+ if (archInfo && archInfo.length !== 2 && archInfo.length !== 0) {
+ fieldsValue.arch = archInfo[0];
+ }
+
+ if (fixedGroupId) {
+ fieldsValue.group_id = fixedGroupId;
}
- if (radioKey == 'public') {
- fieldsValue.vm_url = this.state.selectUrl
- fieldsValue.image_name = this.state.selectName
+
+ if (radioKey === 'public') {
+ fieldsValue.vm_url = selectedPublicVm.vm_url;
+ fieldsValue.image_name = selectedPublicVm.image_name;
fieldsValue.source_type = 'public';
- fieldsValue.asset_id = this.findAssetByName(this.state.selectName)?.id || '';
+ fieldsValue.asset_id =
+ this.findAssetByName(selectedPublicVm.image_name)?.id || '';
fieldsValue.template_id = '';
fieldsValue.template_version_id = '';
- } else if (radioKey === 'address') {
+ } else if (radioKey === 'url') {
fieldsValue.source_type = 'url';
fieldsValue.asset_id = '';
fieldsValue.template_id = '';
@@ -310,15 +316,28 @@ export default class Index extends PureComponent {
fieldsValue.template_version_id = '';
} else {
const selectedAsset = this.findAssetByName(fieldsValue.image_name);
- if (selectedAsset && selectedAsset.status !== 'ready' && selectedAsset.status !== 'partial') {
+ if (
+ selectedAsset &&
+ selectedAsset.status !== 'ready' &&
+ selectedAsset.status !== 'partial'
+ ) {
message.warning(formatMessage({ id: 'Vm.assetCatalog.useDisabled' }));
return;
}
fieldsValue.source_type = 'existing';
- fieldsValue.asset_id = selectedAsset ? selectedAsset.id : fieldsValue.asset_id || '';
- fieldsValue.template_id = selectedAsset && selectedAsset.template_id ? selectedAsset.template_id : fieldsValue.template_id || '';
- fieldsValue.template_version_id = selectedAsset && selectedAsset.template_version_id ? selectedAsset.template_version_id : fieldsValue.template_version_id || '';
+ fieldsValue.asset_id = selectedAsset
+ ? selectedAsset.id
+ : fieldsValue.asset_id || '';
+ fieldsValue.template_id =
+ selectedAsset && selectedAsset.template_id
+ ? selectedAsset.template_id
+ : fieldsValue.template_id || '';
+ fieldsValue.template_version_id =
+ selectedAsset && selectedAsset.template_version_id
+ ? selectedAsset.template_version_id
+ : fieldsValue.template_version_id || '';
}
+
if (!fieldsValue.gpu_enabled) {
fieldsValue.gpu_resources = [];
}
@@ -329,76 +348,104 @@ export default class Index extends PureComponent {
fieldsValue.network_name = '';
fieldsValue.fixed_ip = '';
}
- onSubmit(fieldsValue, radioKey == 'upload' ? event_id : '');
+
+ if (!fieldsValue.group_id) {
+ fieldsValue.group_name =
+ fieldsValue.group_name || fieldsValue.service_cname;
+ fieldsValue.k8s_app =
+ fieldsValue.k8s_app ||
+ this.generateEnglishName(fieldsValue.group_name || fieldsValue.service_cname);
+ }
+
+ onSubmit(fieldsValue, radioKey === 'upload' ? event_id : '');
}
});
};
- handleValiateNameSpace = (_, value, callback) => {
+
+ handleValidateK8sName = (_, value, callback) => {
if (!value) {
- return callback(new Error(formatMessage({ id: 'placeholder.k8s_component_name' })));
- }
- if (value && value.length <= 32) {
- const Reg = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;
- if (!Reg.test(value)) {
- return callback(
- new Error(formatMessage({ id: 'placeholder.nameSpaceReg' }))
- );
- }
- callback();
+ return callback(
+ new Error(formatMessage({ id: 'placeholder.k8s_component_name' }))
+ );
}
if (value.length > 16) {
return callback(new Error(formatMessage({ id: 'placeholder.max16' })));
}
+ const reg = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;
+ if (!reg.test(value)) {
+ return callback(new Error(formatMessage({ id: 'placeholder.nameSpaceReg' })));
+ }
+ callback();
};
- handleChangeImageSource = (key) => {
+
+ handleChangeImageSource = e => {
+ const radioKey = e.target.value;
+ const { form } = this.props;
this.setState({
- radioKey: key.target.value
+ radioKey
});
- if (this.props.form) {
- this.props.form.setFieldsValue({
- asset_id: '',
- template_id: '',
- template_version_id: ''
- });
- }
- }
- applyTemplatePreset = (preset) => {
+ form.setFieldsValue({
+ imagefrom: radioKey,
+ asset_id: '',
+ template_id: '',
+ template_version_id: ''
+ });
+ };
+
+ applyTemplatePreset = preset => {
const { form } = this.props;
if (!preset || !form || this.appliedTemplateVersionId === preset.template_version_id) {
return;
}
- const runtimeSnapshot = preset && preset.extra && preset.extra.runtime_snapshot ? preset.extra.runtime_snapshot : {};
+ const runtimeSnapshot =
+ preset && preset.extra && preset.extra.runtime_snapshot
+ ? preset.extra.runtime_snapshot
+ : {};
this.appliedTemplateVersionId = preset.template_version_id;
- this.setState({
- radioKey: 'ok'
- });
- form.setFieldsValue({
- imagefrom: 'ok',
- image_name: preset.name,
- asset_id: preset.id,
- template_id: preset.template_id,
- template_version_id: preset.template_version_id,
- boot_mode: runtimeSnapshot.boot_mode || undefined,
- gpu_enabled: !!runtimeSnapshot.gpu_enabled,
- gpu_resources: runtimeSnapshot.gpu_resources || [],
- usb_enabled: !!runtimeSnapshot.usb_enabled,
- usb_resources: runtimeSnapshot.usb_resources || [],
- network_mode: runtimeSnapshot.network_mode || 'random',
- network_name: runtimeSnapshot.network_name || undefined,
- fixed_ip: runtimeSnapshot.fixed_ip || undefined
- });
+ this.setState(
+ {
+ radioKey: 'existing'
+ },
+ () => {
+ form.setFieldsValue({
+ imagefrom: 'existing',
+ image_name: preset.name,
+ asset_id: preset.id,
+ template_id: preset.template_id,
+ template_version_id: preset.template_version_id,
+ boot_mode: runtimeSnapshot.boot_mode || undefined,
+ gpu_enabled: !!runtimeSnapshot.gpu_enabled,
+ gpu_resources: runtimeSnapshot.gpu_resources || [],
+ usb_enabled: !!runtimeSnapshot.usb_enabled,
+ usb_resources: runtimeSnapshot.usb_resources || [],
+ network_mode: runtimeSnapshot.network_mode || 'random',
+ network_name: runtimeSnapshot.network_name || undefined,
+ fixed_ip: runtimeSnapshot.fixed_ip || undefined
+ });
+ }
+ );
};
+
openAssetCatalog = () => {
- this.setState({ assetCatalogVisible: true });
+ const { onOpenAssetCatalog } = this.props;
+ if (onOpenAssetCatalog) {
+ onOpenAssetCatalog();
+ }
};
+
closeAssetCatalog = () => {
- this.setState({ assetCatalogVisible: false });
+ const { onCloseAssetCatalog } = this.props;
+ if (onCloseAssetCatalog) {
+ onCloseAssetCatalog();
+ }
};
- findAssetByName = (name) => {
+
+ findAssetByName = name => {
const { virtualMachineImage = [] } = this.props;
return (virtualMachineImage || []).find(item => item.name === name);
};
- getAssetSourceLabel = (sourceType) => {
+
+ getAssetSourceLabel = sourceType => {
const sourceMap = {
public: 'Vm.createVm.public',
url: 'Vm.createVm.add',
@@ -407,39 +454,51 @@ export default class Index extends PureComponent {
clone: 'Vm.createVm.clone',
vm_template: 'Vm.template.center.entry'
};
- return formatMessage({ id: sourceMap[sourceType] || 'Vm.assetCatalog.sourceUnknown' });
+ return formatMessage({
+ id: sourceMap[sourceType] || 'Vm.assetCatalog.sourceUnknown'
+ });
};
- renderAssetOptionLabel = (asset) => {
- return `${asset.name} / ${this.getAssetSourceLabel(asset.source_type)} / ${asset.arch || '-'} / ${asset.format || '-'} / ${asset.status || '-'}`;
+
+ renderAssetOptionLabel = asset => {
+ return `${asset.name}`;
};
- handleUseAsset = (asset) => {
+
+ handleUseAsset = asset => {
const { form } = this.props;
if (!asset || asset.status !== 'ready') {
message.warning(formatMessage({ id: 'Vm.assetCatalog.useDisabled' }));
return;
}
- const runtimeSnapshot = asset && asset.extra && asset.extra.runtime_snapshot ? asset.extra.runtime_snapshot : {};
- this.setState({
- radioKey: 'ok',
- assetCatalogVisible: false
- });
- form.setFieldsValue({
- imagefrom: 'ok',
- image_name: asset.name,
- asset_id: asset.id,
- template_id: asset.template_id || undefined,
- template_version_id: asset.template_version_id || undefined,
- boot_mode: runtimeSnapshot.boot_mode || undefined,
- gpu_enabled: !!runtimeSnapshot.gpu_enabled,
- gpu_resources: runtimeSnapshot.gpu_resources || [],
- usb_enabled: !!runtimeSnapshot.usb_enabled,
- usb_resources: runtimeSnapshot.usb_resources || [],
- network_mode: runtimeSnapshot.network_mode || 'random',
- network_name: runtimeSnapshot.network_name || undefined,
- fixed_ip: runtimeSnapshot.fixed_ip || undefined
- });
+ const runtimeSnapshot =
+ asset && asset.extra && asset.extra.runtime_snapshot
+ ? asset.extra.runtime_snapshot
+ : {};
+ this.setState(
+ {
+ radioKey: 'existing'
+ },
+ () => {
+ form.setFieldsValue({
+ imagefrom: 'existing',
+ image_name: asset.name,
+ asset_id: asset.id,
+ template_id: asset.template_id || undefined,
+ template_version_id: asset.template_version_id || undefined,
+ boot_mode: runtimeSnapshot.boot_mode || undefined,
+ gpu_enabled: !!runtimeSnapshot.gpu_enabled,
+ gpu_resources: runtimeSnapshot.gpu_resources || [],
+ usb_enabled: !!runtimeSnapshot.usb_enabled,
+ usb_resources: runtimeSnapshot.usb_resources || [],
+ network_mode: runtimeSnapshot.network_mode || 'random',
+ network_name: runtimeSnapshot.network_name || undefined,
+ fixed_ip: runtimeSnapshot.fixed_ip || undefined
+ });
+ this.closeAssetCatalog();
+ }
+ );
};
- handleDeleteAsset = (asset) => {
+
+ handleDeleteAsset = asset => {
const { dispatch, onRefreshAssets } = this.props;
return new Promise((resolve, reject) => {
dispatch({
@@ -464,13 +523,7 @@ export default class Index extends PureComponent {
});
});
};
- onUploadModeChange = (e) => {
- if (e.target.value === 'chunk' && !this.state.vmCapabilities.chunk_upload_supported) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.chunkUnsupported' }));
- return;
- }
- this.setState({ uploadMode: e.target.value });
- };
+
validateRuntimeResources = (enabledField, messageId) => (_, value, callback) => {
if (!this.props.form.getFieldValue(enabledField)) {
callback();
@@ -482,124 +535,7 @@ export default class Index extends PureComponent {
}
callback(new Error(formatMessage({ id: messageId })));
};
- handleChunkFileSelect = (file) => {
- const { event_id, record, vmCapabilities } = this.state;
- if (!vmCapabilities.chunk_upload_supported) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.chunkUnsupported' }));
- return false;
- }
- if (!event_id || !record || !record.upload_url) {
- message.error(formatMessage({ id: 'teamAdd.create.upload.uploaderInitFailed' }));
- return false;
- }
- const allowedTypes = ['.img', '.qcow2', '.iso', '.tar', '.gz', '.xz'];
- const fileExt = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
- if (!allowedTypes.includes(fileExt)) {
- message.error(formatMessage({ id: 'Vm.createVm.package' }));
- return false;
- }
- this.setState({ currentFile: file });
- const uploader = new ChunkUploader(file, event_id, {
- uploadUrl: record.upload_url,
- chunkSize: 5 * 1024 * 1024,
- concurrency: 5
- });
- this.setState({ chunkUploader: uploader });
- return false;
- };
- handleStartChunkUpload = async () => {
- const { chunkUploader, currentFile, vmCapabilities } = this.state;
- const { form } = this.props;
- if (!vmCapabilities.chunk_upload_supported) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.chunkUnsupported' }));
- return;
- }
- if (!currentFile) {
- message.warning(formatMessage({ id: 'teamAdd.create.upload.selectFileFirst' }));
- return;
- }
- if (!chunkUploader) {
- message.error(formatMessage({ id: 'teamAdd.create.upload.uploaderInitFailed' }));
- return;
- }
- this.setState({ isChunkUploading: true, chunkUploadProgress: 0 });
- try {
- await chunkUploader.upload((progress) => {
- this.setState({ chunkUploadProgress: progress });
- });
- notification.success({
- message: formatMessage({ id: 'notification.success.upload_file' })
- });
- const virtualFileList = [{
- uid: '-1',
- name: currentFile.name,
- status: 'done',
- response: { msg: 'success' }
- }];
- this.setState({
- isChunkUploading: false,
- chunkUploadProgress: 100,
- existFileList: [currentFile.name],
- fileList: virtualFileList
- });
- form.setFieldsValue({
- packageTarFile: virtualFileList
- });
- this.handleJarWarUploadStatus();
- } catch (error) {
- message.error(formatMessage({ id: 'teamAdd.create.upload.uploadFailed' }) + ': ' + (error.message || 'Unknown error'));
- this.setState({ isChunkUploading: false });
- }
- };
- handlePauseChunkUpload = () => {
- const { chunkUploader } = this.state;
- if (chunkUploader) {
- chunkUploader.pause();
- this.setState({ isChunkUploading: false });
- message.info(formatMessage({ id: 'teamAdd.create.upload.pauseSuccess' }));
- }
- };
- handleResumeChunkUpload = async () => {
- const { chunkUploader } = this.state;
- if (!chunkUploader) {
- message.error(formatMessage({ id: 'teamAdd.create.upload.noUploadTask' }));
- return;
- }
- this.setState({ isChunkUploading: true });
- try {
- await chunkUploader.resume((progress) => {
- this.setState({ chunkUploadProgress: progress });
- });
- notification.success({
- message: formatMessage({ id: 'teamAdd.create.upload.resumeSuccess' })
- });
- this.setState({
- isChunkUploading: false,
- chunkUploadProgress: 100
- });
- this.handleJarWarUploadStatus();
- } catch (error) {
- message.error(formatMessage({ id: 'teamAdd.create.upload.resumeFailed' }) + ': ' + (error.message || 'Unknown error'));
- this.setState({ isChunkUploading: false });
- }
- };
- handleCancelChunkUpload = async () => {
- const { chunkUploader } = this.state;
- const { form } = this.props;
- if (chunkUploader) {
- await chunkUploader.cancel();
- this.setState({
- isChunkUploading: false,
- chunkUploadProgress: 0,
- currentFile: null,
- chunkUploader: null,
- fileList: []
- });
- form.setFieldsValue({ packageTarFile: [] });
- message.info(formatMessage({ id: 'teamAdd.create.upload.cancelSuccess' }));
- }
- };
- //上传
+
onChangeUpload = info => {
let { fileList } = info;
fileList = fileList.filter(file => {
@@ -608,26 +544,14 @@ export default class Index extends PureComponent {
}
return true;
});
- if (info && info.event && info.event.percent) {
- this.setState({
- percents: info.event.percent
- });
- }
-
- const { status } = info.file;
- if (status === 'done') {
- this.setState({
- percents: false
- });
- }
this.setState({ fileList });
};
- //删除
+
onRemove = () => {
this.setState({ fileList: [] });
};
- // 获取插件列表
- fetchPipePipeline = (eid) => {
+
+ fetchPipePipeline = () => {
const { dispatch, currUser } = this.props;
dispatch({
type: 'teamControl/fetchPluginUrl',
@@ -637,30 +561,33 @@ export default class Index extends PureComponent {
},
callback: res => {
if (res && res.list) {
- res.list.map(item => {
- if (item.name == "rainbond-vm") {
+ res.list.forEach(item => {
+ if (item.name === 'rainbond-vm') {
this.setState({
- vmShow: true,
- })
+ vmShow: true
+ });
}
- }
- )
+ });
}
}
- })
- }
- PublicVmSelect = (item) => {
+ });
+ };
+
+ handleSelectPublicVm = item => {
this.setState({
- selectName: item.image_name,
- selectUrl: item.vm_url
- })
- }
- // 获取当前选取的app的所有组件的英文名称
- fetchComponentNames = (group_id) => {
+ selectedPublicVm: item
+ });
+ };
+
+ fetchComponentNames = group_id => {
const { dispatch } = this.props;
this.setState({
- creatComPermission: role.queryPermissionsInfo(this.props.currentTeamPermissionsInfo?.team, 'app_overview', `app_${group_id}`)
- })
+ creatComPermission: role.queryPermissionsInfo(
+ this.props.currentTeamPermissionsInfo?.team,
+ 'app_overview',
+ `app_${group_id}`
+ )
+ });
dispatch({
type: 'appControl/getComponentNames',
payload: {
@@ -670,531 +597,613 @@ export default class Index extends PureComponent {
callback: res => {
if (res && res.bean) {
this.setState({
- comNames: res.bean.component_names && res.bean.component_names.length > 0 ? res.bean.component_names : []
- })
+ comNames:
+ res.bean.component_names && res.bean.component_names.length > 0
+ ? res.bean.component_names
+ : []
+ });
}
}
- });
+ });
};
- // 生成英文名
- generateEnglishName = (name) => {
- if(name != undefined){
- const { appNames } = this.props;
- const pinyinName = pinyin(name, {toneType: 'none'}).replace(/\s/g, '');
- const cleanedPinyinName = pinyinName.toLowerCase();
- if (appNames && appNames.length > 0) {
- const isExist = appNames.some(item => item === cleanedPinyinName);
- if (isExist) {
- const random = Math.floor(Math.random() * 10000);
- return `${cleanedPinyinName}${random}`;
- }
- return cleanedPinyinName;
+
+ generateEnglishName = name => {
+ if (name === undefined || name === null || name === '') {
+ return '';
+ }
+ const { comNames } = this.state;
+ const pinyinName = pinyin(name, { toneType: 'none' }).replace(/\s/g, '');
+ const cleanedPinyinName = pinyinName.toLowerCase();
+ if (comNames && comNames.length > 0) {
+ const isExist = comNames.some(item => item === cleanedPinyinName);
+ if (isExist) {
+ const random = Math.floor(Math.random() * 10000);
+ return `${cleanedPinyinName}${random}`;
}
- return cleanedPinyinName;
}
- return ''
- }
- render() {
- const {
- groups,
- createAppByDockerrunLoading,
- form,
- groupId,
- handleType,
- ButtonGroupState,
- showSubmitBtn = true,
- showCreateGroup = true,
- archInfo,
- virtualMachineImage,
- rainbondInfo
- } = this.props;
+ return cleanedPinyinName;
+ };
+
+ renderFileList = () => {
+ const { existFileList } = this.state;
+ if (existFileList.length === 0) {
+ return (
+
+ {formatMessage({ id: 'teamAdd.create.null_data' })}
+
+ );
+ }
+ return (
+
+ {existFileList.map(item => (
+
+
+
+
+
{item}
+
+ {formatMessage({ id: 'Vm.createVm.uploadSuccessHint' })}
+
+
+
+
+
+ ))}
+
+ );
+ };
+
+ renderPublicVmCards = () => {
+ const { publicVmOptions, selectedPublicVm } = this.state;
+ return (
+
+ {publicVmOptions.map(item => {
+ const active = selectedPublicVm.image_name === item.image_name;
+ return (
+
this.handleSelectPublicVm(item)}
+ >
+
+

+
+
{item.image_name}
+
+ );
+ })}
+
+ );
+ };
+
+ renderSourceFields = () => {
+ const { form, virtualMachineImage = [] } = this.props;
const { getFieldDecorator } = form;
- const myheaders = {};
- const data = this.props.data || {};
- const isService = handleType && handleType === 'Service';
- const host = rainbondInfo.document?.enable ? rainbondInfo.document.value.platform_url : 'https://www.rainbond.com'
- const { language, radioKey, fileList, vmShow, existFileList, PublicVm, selectName, uploadMode, vmCapabilities, isChunkUploading, chunkUploadProgress, currentFile, creatComPermission: {
- isCreate
- } } = this.state;
- const is_language = language ? formItemLayout : formItemLayouts;
- let arch = 'amd64'
- let archLegnth = archInfo.length
- if (archLegnth == 2) {
- arch = 'amd64'
- } else if (archInfo.length == 1) {
- arch = archInfo && archInfo[0]
+ const { radioKey } = this.state;
+
+ if (radioKey === 'public') {
+ return this.renderPublicVmCards();
+ }
+
+ if (radioKey === 'url') {
+ return (
+
+
+ {getFieldDecorator('vm_url', {
+ rules: [{ required: true, message: formatMessage({ id: 'Vm.createVm.InputInstall' }) }]
+ })(
+
+ )}
+
+
+ {getFieldDecorator('image_name', {
+ rules: [{ required: true, message: formatMessage({ id: 'Vm.createVm.inputName' }) }]
+ })(
+
+ )}
+
+
+ );
+ }
+
+ if (radioKey === 'upload') {
+ return (
+
+
+ {getFieldDecorator('packageTarFile', { initialValue: [] })(
+
+
+
+
+
+ )}
+
+
+ {this.renderFileList()}
+
+
+ {getFieldDecorator('image_name', {
+ rules: [{ required: true, message: formatMessage({ id: 'Vm.createVm.inputName' }) }]
+ })(
+
+ )}
+
+
+ );
}
- const group_id = globalUtil.getAppID()
- const initialGroupId = isService
- ? Number(groupId)
- : data.group_id || (group_id ? Number(group_id) : undefined)
+
return (
-
-
-
+
+ {getFieldDecorator('image_name', {
+ rules: [{ required: true, message: formatMessage({ id: 'Vm.createVm.selectImg' }) }]
+ })(
+
)}
-
- {getFieldDecorator('group_id', {
- initialValue: initialGroupId,
- rules: [{ required: true, message: formatMessage({ id: 'placeholder.select' }) }]
+
+
+ );
+ };
+
+ renderRuntimeFields = (archLength, arch, form) => {
+ const { getFieldDecorator } = form;
+ const { vmCapabilities } = this.state;
+ return (
+
+ {archLength === 2 ? (
+
+ {getFieldDecorator('arch', {
+ initialValue: arch,
+ rules: [{ required: true, message: formatMessage({ id: 'placeholder.code_version' }) }]
})(
-
+
+ amd64
+ arm64
+
)}
-
- {getFieldDecorator('service_cname', {
- initialValue: data.service_cname || '',
+ ) : null}
+
+ {vmCapabilities.gpu_supported ? (
+
+
+
+
+ {formatMessage({ id: 'Vm.createVm.gpu' })}
+
+
+ {getFieldDecorator('gpu_enabled', {
+ valuePropName: 'checked',
+ initialValue: false
+ })(
)}
+
+
+ ) : null}
+ {vmCapabilities.gpu_supported && form.getFieldValue('gpu_enabled') ? (
+
+ {getFieldDecorator('gpu_resources', {
+ initialValue: [],
rules: [
- { required: true, message: formatMessage({ id: 'placeholder.service_cname' }) },
{
- max: 24,
- message: formatMessage({ id: 'placeholder.max24' })
+ validator: this.validateRuntimeResources(
+ 'gpu_enabled',
+ 'Vm.createVm.gpuResourcesRequired'
+ )
}
]
- })()}
+ })(
+
+ )}
+ ) : null}
-
- {getFieldDecorator('k8s_component_name', {
- initialValue: this.generateEnglishName(form.getFieldValue('service_cname')),
+ {vmCapabilities.usb_supported ? (
+
+
+
+
+ {formatMessage({ id: 'Vm.createVm.usb' })}
+
+
+ {getFieldDecorator('usb_enabled', {
+ valuePropName: 'checked',
+ initialValue: false
+ })(
)}
+
+
+ ) : null}
+ {vmCapabilities.usb_supported && form.getFieldValue('usb_enabled') ? (
+
+ {getFieldDecorator('usb_resources', {
+ initialValue: [],
rules: [
- { required: true, validator: this.handleValiateNameSpace }
+ {
+ validator: this.validateRuntimeResources(
+ 'usb_enabled',
+ 'Vm.createVm.usbResourcesRequired'
+ )
+ }
]
- })()}
-
-
- {getFieldDecorator('imagefrom', {
- initialValue: 'public',
- rules: [{ required: true, message: formatMessage({ id: 'placeholder.code_version' }) }]
})(
-
-
- {formatMessage({ id: 'Vm.createVm.public' })}
- {formatMessage({ id: 'Vm.createVm.add' })}
- {formatMessage({ id: 'Vm.createVm.upload' })}
- {virtualMachineImage && virtualMachineImage.length > 0 && {formatMessage({ id: 'Vm.createVm.have' })}}
-
- {virtualMachineImage && virtualMachineImage.length > 0 && (
-
-
-
-
- )}
-
+
)}
- {radioKey != 'ok' ? (
- <>
- {radioKey == 'public' &&
-
- {getFieldDecorator('url', {
- })(
-
- {PublicVm && PublicVm.map((item, index) => {
- return (
-
this.PublicVmSelect(item)}>
-
-
- {item.image_name}
-
- {item.image_name == 'centos7.9' &&

}
- {item.image_name == 'anolisos7.9' &&

}
- {item.image_name == 'deepin20.9' &&

}
- {item.image_name == 'ubuntu23.10' &&

}
-
-
- )
- })
- }
-
- )}
-
+ ) : null}
- }
- {radioKey == 'address' &&
-
- {getFieldDecorator('vm_url', {
- rules: [
- { required: true }
- ]
- })()}
-
- }
- {radioKey == 'upload' &&
- <>
-
-
- {formatMessage({ id: 'teamAdd.create.upload.mode.normal' })}
-
- {formatMessage({ id: 'teamAdd.create.upload.mode.chunk' })}
-
-
-
-
- {getFieldDecorator('packageTarFile', {
- rules: [
- ]
- })(
- uploadMode === 'normal' ? (
-
-
-
- ) : (
-
-
-
-
- {currentFile && (
-
-
-
- {currentFile.name}
-
- ({(currentFile.size / 1024 / 1024).toFixed(2)} MB)
-
-
-
-
- {!isChunkUploading && chunkUploadProgress === 0 && (
-
- )}
- {isChunkUploading && (
-
- )}
- {!isChunkUploading && chunkUploadProgress > 0 && chunkUploadProgress < 100 && (
-
- )}
-
-
-
- )}
-
- )
- )}
-
-
-
-
- {existFileList.length > 0 ?
- (existFileList.map((item) => {
- return (
-
-
-
- {item}
-
-
- )
- })) : (
-
- {formatMessage({ id: 'teamAdd.create.null_data' })}
-
- )}
-
- {existFileList.length > 0 &&
-
-
-
- }
-
-
- >
- }
- {radioKey != 'public' &&
-
- {getFieldDecorator('image_name', {
- rules: [
- { required: true }
- ]
- })()}
-
- }
- >
- ) : (
-
- {getFieldDecorator('image_name', {
- rules: [
- { required: true, }
- ]
- })()}
-
+ {formatMessage({ id: 'Vm.createVm.networkFixed' })}
+
+
)}
- {archLegnth == 2 &&
-
- {getFieldDecorator('arch', {
- initialValue: arch,
- rules: [{ required: true, message: formatMessage({ id: 'placeholder.code_version' }) }]
- })(
-
- amd64
- arm64
-
- )}
- }
+
- {vmCapabilities.gpu_supported && (
-
- {getFieldDecorator('gpu_enabled', {
- valuePropName: 'checked',
- initialValue: false
- })(
-
- )}
-
- )}
- {vmCapabilities.gpu_supported && form.getFieldValue('gpu_enabled') && (
-
- {getFieldDecorator('gpu_resources', {
- initialValue: [],
+ {form.getFieldValue('network_mode') === 'fixed' ? (
+
+
+ {getFieldDecorator('network_name', {
rules: [
{
- validator: this.validateRuntimeResources('gpu_enabled', 'Vm.createVm.gpuResourcesRequired')
+ required: true,
+ message: formatMessage({ id: 'Vm.createVm.networkNamePlaceholder' })
}
]
})(
)}
- )}
-
- {vmCapabilities.usb_supported && (
-
- {getFieldDecorator('usb_enabled', {
- valuePropName: 'checked',
- initialValue: false
- })(
-
- )}
-
- )}
- {vmCapabilities.usb_supported && form.getFieldValue('usb_enabled') && (
-
- {getFieldDecorator('usb_resources', {
- initialValue: [],
+
+ {getFieldDecorator('fixed_ip', {
rules: [
{
- validator: this.validateRuntimeResources('usb_enabled', 'Vm.createVm.usbResourcesRequired')
+ required: true,
+ message: formatMessage({ id: 'Vm.createVm.fixedIPPlaceholder' })
}
]
})(
-
+
)}
- )}
+
+ ) : null}
+
+ );
+ };
-
- {getFieldDecorator('network_mode', {
- initialValue: 'random'
- })(
-
- {formatMessage({ id: 'Vm.createVm.networkRandom' })}
-
- {formatMessage({ id: 'Vm.createVm.networkFixed' })}
-
-
- )}
-
+ renderSubmitButton = fixedGroupId => {
+ const {
+ handleType,
+ ButtonGroupState,
+ rainbondInfo,
+ createAppByVirtualMachineLoading
+ } = this.props;
+ const {
+ vmShow,
+ creatComPermission: { isCreate }
+ } = this.state;
+ const host = rainbondInfo.document?.enable
+ ? rainbondInfo.document.value.platform_url
+ : 'https://www.rainbond.com';
- {form.getFieldValue('network_mode') === 'fixed' && (
+ if (handleType && ButtonGroupState) {
+ return this.props.handleServiceBotton(
+
+
+ ,
+ false
+ );
+ }
+
+ return (
+
-
- {getFieldDecorator('network_name', {
- rules: [
- { required: true, message: formatMessage({ id: 'Vm.createVm.networkNamePlaceholder' }) }
- ]
- })(
-
- )}
-
-
- {getFieldDecorator('fixed_ip', {
- rules: [
- { required: true, message: formatMessage({ id: 'Vm.createVm.fixedIPPlaceholder' }) }
- ]
- })(
-
- )}
-
+ {formatMessage({ id: 'Vm.createVm.unInstall' })}
+
+ {formatMessage({ id: 'Vm.createVm.doc' })}
+
- )}
+ )
+ }
+ key={`${vmShow}-${fixedGroupId || 'new'}`}
+ >
+
+
+ );
+ };
- {showSubmitBtn ? (
+ render() {
+ const {
+ form,
+ showSubmitBtn = true,
+ archInfo = [],
+ virtualMachineImage = [],
+ showAssetCatalog = false
+ } = this.props;
+ const { getFieldDecorator } = form;
+ const {
+ radioKey,
+ showAdvanced
+ } = this.state;
+ const fixedGroupId = this.getFixedGroupId();
+ let arch = 'amd64';
+ const archLength = archInfo.length;
+ if (archLength === 1) {
+ arch = archInfo[0];
+ }
+
+ return (
+
+
+
+
- {isService && ButtonGroupState
- ? this.props.handleServiceBotton(
-
-
-
- , false
- )
- : !handleType && (
- {formatMessage({ id: 'Vm.createVm.unInstall' })}{formatMessage({id:'Vm.createVm.doc'})}>} key={vmShow}>
+ label={
+
+
+ {formatMessage({ id: 'teamAdd.create.form.service_cname' })}
+
+ {virtualMachineImage && virtualMachineImage.length > 0 ? (
-
- )}
+ ) : null}
+
+ }
+ >
+ {getFieldDecorator('service_cname', {
+ initialValue: '',
+ rules: [
+ {
+ required: true,
+ message: formatMessage({ id: 'placeholder.service_cname' })
+ },
+ {
+ max: 24,
+ message: formatMessage({ id: 'placeholder.max24' })
+ }
+ ]
+ })()}
+
+
+ {getFieldDecorator('k8s_component_name', {
+ initialValue: this.generateEnglishName(
+ form.getFieldValue('service_cname')
+ ),
+ rules: [{ required: true, validator: this.handleValidateK8sName }]
+ })()}
+
+
+
+ {getFieldDecorator('imagefrom', {
+ initialValue: radioKey,
+ rules: [
+ {
+ required: true,
+ message: formatMessage({ id: 'placeholder.code_version' })
+ }
+ ]
+ })(
+
+
+ {formatMessage({ id: 'Vm.createVm.public' })}
+
+
+ {formatMessage({ id: 'Vm.createVm.add' })}
+
+
+ {formatMessage({ id: 'Vm.createVm.upload' })}
+
+ {virtualMachineImage && virtualMachineImage.length > 0 ? (
+
+ {formatMessage({ id: 'Vm.createVm.have' })}
+
+ ) : null}
+
+ )}
+ {this.renderSourceFields()}
+
+ {this.renderRuntimeFields(archLength, arch, form)}
+
+ {!fixedGroupId ? (
+
+
+
+ ) : null}
+
+ {!fixedGroupId && showAdvanced ? (
+
+ ) : null}
+
+ {showSubmitBtn ? (
+
+ {this.renderSubmitButton(fixedGroupId)}
+
+ ) : null}
+
+
+
+ {showAssetCatalog ? (
+
) : null}
-
- {this.state.addGroup && (
-
- )}
-
+
);
}
diff --git a/src/components/ImageVirtualMachineForm/index.less b/src/components/ImageVirtualMachineForm/index.less
index 824782c77..be0567d0e 100644
--- a/src/components/ImageVirtualMachineForm/index.less
+++ b/src/components/ImageVirtualMachineForm/index.less
@@ -1,73 +1,241 @@
-.yaml_container {
- :global {
- .ant-upload {
- width: 200px;
- }
- .ant-form-item-children{
- display: flex;
- }
- .ant-upload-list-item-info{
- margin-right: 12px;
- }
+@import '~antd/lib/style/themes/default.less';
+
+.vmForm {
+ padding: 4px 2px 12px;
+
+ :global {
+ .ant-form-item {
+ margin-bottom: 20px;
}
- .update{
- width: 300px;
+
+ .ant-form-item-label>label {
+ font-size: 14px;
+ font-weight: 600;
+ color: @text-color;
+ }
+
+ .ant-radio-group {
display: flex;
- max-height: 200px;
- overflow: hidden;
- padding: 16px 24px;
- border: 1px solid #e3e3e3;
- position: relative;
- .delete{
- position: absolute;
- top: -5%;
- right: 2%;
- }
- .fileName{
- width: 250px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- line-height: 24px;
- }
+ flex-wrap: wrap;
+ gap: 12px;
+ }
+
+ .ant-select,
+ .ant-input {
+ width: 100%;
+ }
+
+ .ant-upload {
+ display: inline-block;
}
+ }
}
-.file{
- width: 250px;
- white-space: nowrap;
+
+.noticeAlert {
+ margin-bottom: 20px;
+}
+
+.fieldLabelRow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ width: 100%;
+}
+
+.fieldLabelText {
+ color: @text-color;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.assetCatalogTrigger {
+ padding-right: 0 !important;
+}
+
+.inlineGrid {
+ display: grid;
+ grid-template-columns: 1fr;
+}
+
+.publicVmGrid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.publicVmCard {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 12px 0;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border: 1px solid @border-color-base;
+ margin-bottom: 24px;
+
+ &:hover {
+ transform: translateY(-1px);
+ }
+}
+
+.publicVmCardActive {
+ color: @primary-color;
+}
+
+.publicVmCardIconWrap {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 44px;
+ height: 44px;
+ background: #f6f8fb;
+ border-radius: 12px;
+}
+
+.publicVmCardIcon {
+ width: 28px;
+ height: 28px;
+ object-fit: contain;
+}
+
+.publicVmCardName {
+ color: @heading-color;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 22px;
+ text-align: center;
+}
+
+.uploadPanel {
+ padding: 6px 0 0;
+ background: transparent;
+}
+
+.fileIcon {
+ color: @primary-color;
+}
+
+.fileList {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.fileCard {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 8px 0;
+}
+
+.fileMain {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ flex: 1;
+}
+
+.fileMeta {
+ min-width: 0;
+ flex: 1;
+}
+
+.fileName {
overflow: hidden;
+ color: @heading-color;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 20px;
text-overflow: ellipsis;
- padding: 0 24px;
- border: 1px solid #d9d9d9;
+ white-space: nowrap;
+}
+
+.fileHint {
+ margin-top: 2px;
+ color: @text-color-secondary;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.fileDelete {
+ padding: 0 !important;
+}
+
+.emptyState {
+ padding: 12px 0;
+ color: @text-color-secondary;
+ font-size: 12px;
+ line-height: 20px;
+ text-align: left;
}
-.public{
+
+.switchPanel {
display: flex;
- margin-bottom: 24px;
- &>div{
- cursor: pointer;
- margin: 0 4px;
- width: 100px;
- height: 30px;
- .publicItemName{
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 4px 0;
+}
+
+.switchPanelMeta {
+ flex: 1;
+}
+
+.switchPanelTitle {
+ color: @heading-color;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 20px;
+}
+
+.switchPanelDesc {
+ margin-top: 4px;
+ color: @text-color-secondary;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.advancedToggle {
+ margin-top: 8px;
+ margin-bottom: 12px;
+
+ :global {
+ .ant-btn {
+ padding-left: 0;
+ color: @primary-color;
font-weight: 500;
- line-height: 30px;
- font-size: 12px;
- text-align: center;
- color: #000;
- border: 1px solid #e3e3e3;
- background-color: #fff;
- border-radius: 5px;
- padding: 4px;
- img{
- width: 30px;
- height: 30px;
- }
}
}
- .active{
- .publicItemName{
- color: #1890ff;
- border: 1px solid #1890ff;
- }
+}
+
+.advancedPanel {
+ margin-bottom: 20px;
+ padding: 4px 2px 0;
+}
+
+.advancedDivider {
+ margin-bottom: 18px;
+ border-top: 1px solid fade(@border-color-base, 92%);
+}
+
+.submitRow {
+ margin-bottom: 0 !important;
+ text-align: center;
+}
+
+@media (max-width: 640px) {
+ .inlineGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .fileCard {
+ align-items: flex-start;
+ flex-direction: column;
}
}
\ No newline at end of file
diff --git a/src/components/VMAssetCatalogModal/index.js b/src/components/VMAssetCatalogModal/index.js
index 6894c5719..f4169a50e 100644
--- a/src/components/VMAssetCatalogModal/index.js
+++ b/src/components/VMAssetCatalogModal/index.js
@@ -1,4 +1,4 @@
-import { Empty, Input, Modal, Popconfirm, Table, Tag, Tooltip } from 'antd';
+import { Empty, Input, Popconfirm, Tooltip, Button, List, Tag } from 'antd';
import React, { Fragment, PureComponent } from 'react';
import { formatMessage } from '@/utils/intl';
import styles from './index.less';
@@ -8,12 +8,11 @@ class VMAssetCatalogModal extends PureComponent {
super(props);
this.state = {
keyword: '',
- detailAsset: null,
deleteLoadingId: null
};
}
- getSourceLabel = (sourceType) => {
+ getSourceLabel = sourceType => {
const sourceMap = {
public: 'Vm.createVm.public',
url: 'Vm.createVm.add',
@@ -25,22 +24,7 @@ class VMAssetCatalogModal extends PureComponent {
return formatMessage({ id: sourceMap[sourceType] || 'Vm.assetCatalog.sourceUnknown' });
};
- formatBytes = (size) => {
- const value = Number(size || 0);
- if (!value) {
- return '-';
- }
- const units = ['B', 'KB', 'MB', 'GB', 'TB'];
- let current = value;
- let unitIndex = 0;
- while (current >= 1024 && unitIndex < units.length - 1) {
- current /= 1024;
- unitIndex += 1;
- }
- return `${current.toFixed(current >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
- };
-
- handleDelete = async (asset) => {
+ handleDelete = async asset => {
const { onDelete } = this.props;
if (!onDelete) {
return;
@@ -67,196 +51,123 @@ class VMAssetCatalogModal extends PureComponent {
});
};
- renderDetailLine = (label, value) => (
-
-
{label}
-
{value === 0 ? '0' : value || '-'}
-
- );
-
render() {
- const { visible, onCancel, onUseAsset, assets = [] } = this.props;
- const { keyword, detailAsset, deleteLoadingId } = this.state;
+ const { assets = [], onUseAsset } = this.props;
+ const { keyword, deleteLoadingId } = this.state;
const filteredAssets = this.getFilteredAssets();
- const columns = [
- {
- title: formatMessage({ id: 'Vm.assetCatalog.name' }),
- dataIndex: 'name',
- key: 'name',
- width: 160,
- render: value => (
-
- {value}
-
- )
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.source' }),
- dataIndex: 'source_type',
- key: 'source_type',
- width: 110,
- render: value => {this.getSourceLabel(value)}
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.archFormat' }),
- key: 'arch_format',
- width: 130,
- render: (_, record) => (
-
- {`${record.arch || '-'} / ${record.format || '-'}`}
-
- )
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.size' }),
- dataIndex: 'size_bytes',
- key: 'size_bytes',
- width: 100,
- render: value => this.formatBytes(value)
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.status' }),
- dataIndex: 'status',
- key: 'status',
- width: 110,
- render: value => {value || formatMessage({ id: 'Vm.assetCatalog.statusUnknown' })}
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.diskCount' }),
- dataIndex: 'disk_count',
- key: 'disk_count',
- width: 90,
- render: value => value || '-'
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.references' }),
- dataIndex: 'reference_count',
- key: 'reference_count',
- width: 90,
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.createdAt' }),
- dataIndex: 'create_time',
- key: 'create_time',
- width: 160,
- render: value => {value || '-'}
- },
- {
- title: formatMessage({ id: 'Vm.assetCatalog.actions' }),
- key: 'actions',
- width: 220,
- render: (_, record) => (
+ const renderAssetItem = asset => {
+ const canUse = asset.status === 'ready';
+ const metaItems = [
+ {
+ label: formatMessage({ id: 'Vm.assetCatalog.source' }),
+ value: this.getSourceLabel(asset.source_type)
+ },
+ {
+ label: formatMessage({ id: 'Vm.assetCatalog.status' }),
+ value: asset.status || formatMessage({ id: 'Vm.assetCatalog.statusUnknown' })
+ },
+ {
+ label: formatMessage({ id: 'Vm.assetCatalog.references' }),
+ value: asset.reference_count || 0
+ },
+ {
+ label: formatMessage({ id: 'Vm.assetCatalog.createdAt' }),
+ value: asset.create_time || '-'
+ }
+ ];
+
+ return (
+
+
+
+
+ {asset.name || '-'}
+
+
+
+ {metaItems.map(item => (
+
+
{item.label}:
+
+ {item.label === formatMessage({ id: 'Vm.assetCatalog.status' }) ? (
+
+ {item.value}
+
+ ) : (
+ item.value
+ )}
+
+
+ ))}
+
+
- )
- }
- ];
- const tableScrollX = columns.reduce((total, column) => total + (Number(column.width) || 0), 0);
+
+ );
+ };
return (
-
+
this.setState({ keyword: e.target.value })}
/>
-
-
+
+ emptyText: (
+
+
+
+ )
}}
/>
-
-
- this.setState({ detailAsset: null })}
- destroyOnClose
- >
- {detailAsset && (
-
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.name' }), detailAsset.name)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.diskCount' }), detailAsset.disk_count)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.source' }),
- this.getSourceLabel(detailAsset.source_type))}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.sourceUri' }), detailAsset.source_uri)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.status' }), detailAsset.status)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.archFormat' }),
- `${detailAsset.arch || '-'} / ${detailAsset.format || '-'}`)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.size' }),
- this.formatBytes(detailAsset.size_bytes))}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.bootMode' }), detailAsset.boot_mode)}
- {this.renderDetailLine(formatMessage({ id: 'Vm.assetCatalog.references' }), detailAsset.reference_count)}
- {detailAsset.source_asset && this.renderDetailLine(
- formatMessage({ id: 'Vm.assetCatalog.sourceAsset' }),
- detailAsset.source_asset.name
- )}
-
- )}
-
+
);
}
diff --git a/src/components/VMAssetCatalogModal/index.less b/src/components/VMAssetCatalogModal/index.less
index 433f53aeb..25d23f8b9 100644
--- a/src/components/VMAssetCatalogModal/index.less
+++ b/src/components/VMAssetCatalogModal/index.less
@@ -1,68 +1,142 @@
@import '~antd/lib/style/themes/default.less';
-.assetCatalogModal {
- :global {
- .ant-modal {
- max-width: calc(100vw - 32px);
- }
-
- .ant-modal-body {
- overflow-x: hidden;
- }
- }
+.catalogSurface {
+ padding: 4px 0 0;
}
.toolbar {
display: flex;
- justify-content: space-between;
- margin-bottom: 16px;
+ justify-content: flex-end;
+ margin-bottom: 24px;
}
-.tableWrap {
- width: 100%;
- min-width: 0;
- overflow-x: auto;
+.searchInput {
+ width: 300px;
+}
+
+.listWrap {
+ max-height: 520px;
+ overflow-y: auto;
:global {
- .ant-spin-nested-loading,
- .ant-spin-container,
- .ant-table,
- .ant-table-content,
- .ant-table-scroll,
- .ant-table-body {
- min-width: 0;
+ .ant-list-items {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding-right: 4px;
}
+ }
+}
- .ant-table-body {
- overflow-x: auto !important;
- }
+.assetItem {
+ display: flex !important;
+ align-items: stretch;
+ justify-content: space-between;
+ gap: 16px;
+ padding: 14px 16px !important;
+ background: #fff;
+ border: 1px solid fade(@border-color-base, 92%);
+ border-radius: 12px;
+ transition: border-color 0.2s ease;
+ border-bottom: 1px solid #e8e8e8 !important;
+
+ &:hover {
+ border-color: fade(@primary-color, 24%);
}
}
+.assetMain {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-width: 0;
+ flex: 1;
+}
+
+.assetNameWrap {
+ min-width: 0;
+}
+
.nameText {
display: inline-block;
max-width: 100%;
overflow: hidden;
+ color: @heading-color;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
- font-weight: 500;
}
-.textWrap {
- display: inline-block;
- max-width: 100%;
- white-space: normal;
- word-break: break-word;
- line-height: 20px;
+.assetMetaGrid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px 12px;
+}
+
+.assetMetaItem {
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.assetMetaLabel {
+ color: @text-color-secondary;
+ font-size: 12px;
+ line-height: 18px;
+ white-space: nowrap;
+}
+
+.assetMetaValue {
+ overflow: hidden;
+ color: @heading-color;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 18px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
.actionCell {
display: flex;
- flex-wrap: wrap;
- gap: 8px 12px;
- white-space: normal;
+ flex-direction: column;
+ gap: 6px;
+ justify-content: flex-end;
+ align-self: flex-end;
+
+ > * {
+ width: 100%;
+ }
+}
+
+.actionButton {
+ display: block;
+ width: 100%;
+ min-width: 100%;
+ max-width: 100%;
+}
+
+.emptyWrap {
+ padding: 48px 0;
}
-.actionLink {
- margin-left: 0 !important;
+@media (max-width: 768px) {
+ .searchInput {
+ width: 100%;
+ }
+
+ .assetItem {
+ flex-direction: column;
+ }
+
+ .assetMetaGrid {
+ grid-template-columns: 1fr;
+ }
+
+ .actionCell {
+ flex-direction: row;
+ width: 100%;
+ }
}
diff --git a/src/locales/en-US/enterprise.js b/src/locales/en-US/enterprise.js
index 2c995abf0..54ec3f7bb 100644
--- a/src/locales/en-US/enterprise.js
+++ b/src/locales/en-US/enterprise.js
@@ -141,7 +141,7 @@ const enterpriseOverview = {
'enterpriseOverview.overview.InstallStep.demo': 'Demo',
'enterpriseOverview.overview.InstallStep.start': 'Start now',
// Version update popup window
- 'enterpriseOverview.overview.UpdateVersion.title': 'There are new version updates',
+ 'enterpriseOverview.overview.UpdateVersion.title': 'New version update',
'enterpriseOverview.overview.UpdateVersion.tip': 'Want to update a new version?',
}
@@ -1571,4 +1571,4 @@ const monitarEnterprise = {
'monitarEnterprise.error.desc.scan': 'The installed patrol plug-in was not detected',
}
-export default Object.assign({}, enterpriseOverview, applicationMarket, enterpriseTeamManagement, enterpriseColony, enterpriseUser, enterpriseSetting, otherEnterprise, LogEnterprise, extensionEnterprise, platformUpgrade, auditEnterprise, containerLog, monitarEnterprise);
\ No newline at end of file
+export default Object.assign({}, enterpriseOverview, applicationMarket, enterpriseTeamManagement, enterpriseColony, enterpriseUser, enterpriseSetting, otherEnterprise, LogEnterprise, extensionEnterprise, platformUpgrade, auditEnterprise, containerLog, monitarEnterprise);
diff --git a/src/locales/en-US/team.js b/src/locales/en-US/team.js
index ab8925739..48392fed9 100644
--- a/src/locales/en-US/team.js
+++ b/src/locales/en-US/team.js
@@ -723,6 +723,19 @@ const Vm = {
'Vm.createVm.docker': 'Build from container',
'Vm.createVm.creatCom': 'Create components from a virtual machine',
'Vm.createVm.creatApp': 'Create an application using a VM image',
+ 'Vm.createVm.createInApp': 'Create a VM component inside the current app',
+ 'Vm.createVm.modalDesc': 'Support public images, direct download links, file uploads, and existing assets in one unified VM creation flow.',
+ 'Vm.createVm.currentApp': 'Current app: {name}',
+ 'Vm.createVm.basicInfo': 'Basic Information',
+ 'Vm.createVm.basicInfoDesc': 'Define the component identity first, then continue with runtime and network settings.',
+ 'Vm.createVm.sourceDesc': 'Choose the VM source and the form will reveal the fields required for that source type.',
+ 'Vm.createVm.publicTemplate': 'Official public template',
+ 'Vm.createVm.assetCount': '{count} assets are available. Open the asset catalog to inspect details and status.',
+ 'Vm.createVm.runtimeConfig': 'Runtime Configuration',
+ 'Vm.createVm.runtimeConfigDesc': 'Choose arch, GPU, USB, and network settings based on current cluster capabilities.',
+ 'Vm.createVm.gpuDesc': 'Enable GPU acceleration for the virtual machine and select the GPU resources below.',
+ 'Vm.createVm.usbDesc': 'Enable USB passthrough for the virtual machine and bind the USB resources below.',
+ 'Vm.createVm.uploadSuccessHint': 'This uploaded file will be used as the image source for the current creation flow.',
'Vm.createVm.specification': 'Specification',
'Vm.createVm.distribution': 'Specifies the amount of memory allocated to this virtual machine. The memory size must be a multiple of 4 MB.',
'Vm.createVm.memory': 'Internal memory',
@@ -777,6 +790,7 @@ const Vm = {
'Vm.createVm.cloneSourcePlaceholder': 'Please select an existing image to clone',
'Vm.assetCatalog.manage': 'Asset Catalog',
'Vm.assetCatalog.title': 'Image Asset Catalog',
+ 'Vm.assetCatalog.total': 'Showing {count} items out of {total} assets',
'Vm.assetCatalog.name': 'Asset Name',
'Vm.assetCatalog.source': 'Source',
'Vm.assetCatalog.sourceUnknown': 'Unknown',
@@ -787,14 +801,14 @@ const Vm = {
'Vm.assetCatalog.references': 'References',
'Vm.assetCatalog.createdAt': 'Created At',
'Vm.assetCatalog.actions': 'Actions',
- 'Vm.assetCatalog.useAsset': 'Create Component',
+ 'Vm.assetCatalog.useAsset': 'Select',
'Vm.assetCatalog.clone': 'Quick Copy',
'Vm.assetCatalog.cloneSuccess': 'Image asset copied successfully',
'Vm.assetCatalog.delete': 'Delete',
'Vm.assetCatalog.deleteConfirm': 'Delete this image asset?',
'Vm.assetCatalog.deleteDisabled': 'This image asset is still referenced by virtual machines',
'Vm.assetCatalog.useDisabled': 'This image asset is not ready yet and cannot be used to create a virtual machine',
- 'Vm.assetCatalog.detail': 'Details',
+ 'Vm.assetCatalog.detail': 'Detail',
'Vm.assetCatalog.detailTitle': 'Image Asset Detail',
'Vm.assetCatalog.searchPlaceholder': 'Search by name, source or status',
'Vm.assetCatalog.empty': 'No image assets',
diff --git a/src/locales/zh-CN/enterprise.js b/src/locales/zh-CN/enterprise.js
index 2cbce6606..e1237c6f8 100644
--- a/src/locales/zh-CN/enterprise.js
+++ b/src/locales/zh-CN/enterprise.js
@@ -142,7 +142,7 @@ const enterpriseOverview = {
'enterpriseOverview.overview.InstallStep.demo': '查看演示示例',
'enterpriseOverview.overview.InstallStep.start': '马上开始',
// 版本更新弹窗
- 'enterpriseOverview.overview.UpdateVersion.title': '有新版本更新',
+ 'enterpriseOverview.overview.UpdateVersion.title': '新版本更新',
'enterpriseOverview.overview.UpdateVersion.tip': '要去更新新版本吗?',
}
diff --git a/src/locales/zh-CN/team.js b/src/locales/zh-CN/team.js
index 29b1371f7..b5136ab28 100644
--- a/src/locales/zh-CN/team.js
+++ b/src/locales/zh-CN/team.js
@@ -733,6 +733,19 @@ const Vm = {
'Vm.createVm.docker': '从容器构建',
'Vm.createVm.creatCom': '从虚拟机创建组件',
'Vm.createVm.creatApp': '通过虚拟机镜像创建应用。',
+ 'Vm.createVm.createInApp': '在当前应用内创建虚拟机组件',
+ 'Vm.createVm.modalDesc': '统一支持公共镜像、链接下载、文件上传和已有资产四种来源,并保持与镜像容器一致的创建体验。',
+ 'Vm.createVm.currentApp': '当前应用:{name}',
+ 'Vm.createVm.basicInfo': '基础信息',
+ 'Vm.createVm.basicInfoDesc': '先确定组件名称,创建后可继续补充运行配置和连接方式。',
+ 'Vm.createVm.sourceDesc': '选择虚拟机来源后,平台会按来源类型自动组织所需字段。',
+ 'Vm.createVm.publicTemplate': '官方公共模板',
+ 'Vm.createVm.assetCount': '共 {count} 个可用资产,可通过资产目录查看详情与状态。',
+ 'Vm.createVm.runtimeConfig': '运行配置',
+ 'Vm.createVm.runtimeConfigDesc': '按集群能力选择架构、GPU、USB 与网络参数,不需要的能力可以保持关闭。',
+ 'Vm.createVm.gpuDesc': '为虚拟机开启 GPU 加速,并在下方选择可用的 GPU 资源。',
+ 'Vm.createVm.usbDesc': '为虚拟机开启 USB 透传,并绑定可用的 USB 资源。',
+ 'Vm.createVm.uploadSuccessHint': '上传完成后将作为当前创建使用的镜像文件。',
'Vm.createVm.specification': '规格',
'Vm.createVm.distribution': '指定分配给此虚拟机的内存量。内存大小必须为 4 MB的倍数。',
'Vm.createVm.memory': '内存',
@@ -787,6 +800,7 @@ const Vm = {
'Vm.createVm.cloneSourcePlaceholder': '请选择要复制的已有镜像',
'Vm.assetCatalog.manage': '镜像资产目录',
'Vm.assetCatalog.title': '镜像资产目录',
+ 'Vm.assetCatalog.total': '当前显示 {count} 项,共 {total} 项资产',
'Vm.assetCatalog.name': '资产名称',
'Vm.assetCatalog.source': '来源',
'Vm.assetCatalog.sourceUnknown': '未知来源',
@@ -797,14 +811,14 @@ const Vm = {
'Vm.assetCatalog.references': '引用数',
'Vm.assetCatalog.createdAt': '创建时间',
'Vm.assetCatalog.actions': '操作',
- 'Vm.assetCatalog.useAsset': '创建组件',
+ 'Vm.assetCatalog.useAsset': '选择',
'Vm.assetCatalog.clone': '快速复制',
'Vm.assetCatalog.cloneSuccess': '镜像资产复制成功',
'Vm.assetCatalog.delete': '删除',
'Vm.assetCatalog.deleteConfirm': '确认删除该镜像资产吗?',
'Vm.assetCatalog.deleteDisabled': '该镜像资产仍被虚拟机引用,暂不支持删除',
'Vm.assetCatalog.useDisabled': '镜像资产尚未就绪,暂时不能用于创建虚拟机',
- 'Vm.assetCatalog.detail': '查看详情',
+ 'Vm.assetCatalog.detail': '详情',
'Vm.assetCatalog.detailTitle': '镜像资产详情',
'Vm.assetCatalog.searchPlaceholder': '按名称、来源或状态搜索',
'Vm.assetCatalog.empty': '暂无镜像资产',