mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
feat: cloud host supports GPU (#4728)
* add region * feat add gpu * fix systemDiskSize price * support create * fix password * fix message * add i18n * fix launchpad protocol db connection
This commit is contained in:
@@ -271,9 +271,9 @@ export const adaptAppDetail = (configs: DeployKindsType[]): AppDetailType => {
|
||||
protocol:
|
||||
(ingress?.metadata?.annotations?.[
|
||||
'nginx.ingress.kubernetes.io/backend-protocol'
|
||||
] as AppEditType['networks'][0]['protocol']) ||
|
||||
item?.protocol ||
|
||||
'HTTP',
|
||||
] as AppEditType['networks'][0]['protocol']) || item.protocol === 'TCP'
|
||||
? 'HTTP'
|
||||
: (item.protocol as AppEditType['networks'][number]['protocol']),
|
||||
openPublicDomain: !!ingress,
|
||||
...(domain.endsWith(SEALOS_DOMAIN)
|
||||
? {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"entryLocale": "zh",
|
||||
"output": "./public/locales",
|
||||
"outputLocales": [
|
||||
"en",
|
||||
"jp"
|
||||
"en"
|
||||
]
|
||||
}
|
||||
@@ -49,7 +49,7 @@
|
||||
"Prompt": "Prompt",
|
||||
"Public IP": "Public IP",
|
||||
"Public network bandwidth": "Public network bandwidth",
|
||||
"Quantity is 1 to 100": "Quantity is 1 to 100",
|
||||
"Quantity tips": "Quantity is {{amount1}} to {{amount2}}",
|
||||
"ReStart": "Restart",
|
||||
"Reference fee": "Reference fee",
|
||||
"Reference fee disk tips": "{{price}}/GiB/hour",
|
||||
@@ -70,6 +70,7 @@
|
||||
"Supports changing the operating system": "Note: After purchase, you can change the operating system according to your business needs",
|
||||
"SystemDisk": "System Disk",
|
||||
"The maximum number of instances is 100": "Maximum number of instances is 100",
|
||||
"The maximum number of instances is 20": "The maximum number is 20",
|
||||
"The maximum number of instances is 500": "Maximum number of instances is 500",
|
||||
"The minimum number of instances is 1": "Minimum number of instances is 1",
|
||||
"The minimum number of instances is 20": "Minimum number of instances is 20",
|
||||
@@ -87,5 +88,23 @@
|
||||
"sshPassword": "SSH Password",
|
||||
"storage fees": "Storage fees",
|
||||
"success": "Success",
|
||||
"terminal": "Terminal"
|
||||
"terminal": "Terminal",
|
||||
"Billing model": "Billing model",
|
||||
"Availability Zone": "Availability Zone",
|
||||
"Architecture": "Architecture",
|
||||
"Instance Family": "Instance Family",
|
||||
"postPaidByHour": "Pay as you go",
|
||||
"heterogeneous": "Heterogeneous",
|
||||
"prePaid": "Yearly and monthly subscription",
|
||||
"x86_64": "x86_64",
|
||||
"costEffective": "Cost Effective",
|
||||
"gpu": "GPU",
|
||||
"Password does not meet requirements": "Password does not meet requirements",
|
||||
"Lowercase letters az": "Lowercase letters a ~ z",
|
||||
"Capital letters az": "Capital letters A ~ Z",
|
||||
"Numbers 09": "Numbers 0 ~ 9",
|
||||
"Contains at least three of these": "Contains at least three of these",
|
||||
"Cannot start with": "Cannot start with \"/\"",
|
||||
"cannot contain spaces": "cannot contain spaces",
|
||||
"Within 8 to 30 characters": "Within 8 to 30 characters (12 or more characters recommended)"
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
"Add Data Disk": "データディスクを追加する",
|
||||
"Amount": "数量",
|
||||
"Are you sure to create a cloud host?": "クラウドホストを作成してもよろしいですか",
|
||||
"Assign Public IP": "パブリックIPを割り当てる",
|
||||
"Auto Generate Password": "パスワードを自動生成する",
|
||||
"Automatic Generated": "自動生成",
|
||||
"BandWidth": "帯域幅",
|
||||
"Bandwidth by hour": "(時間ごとの帯域幅)",
|
||||
"Basic Config": "基本設定",
|
||||
"Billing items": "請求項目",
|
||||
"Billing rules": "請求規則",
|
||||
"CPU": "CPU",
|
||||
"Cancel": "キャンセル",
|
||||
"Changing": "変更中",
|
||||
"Cloud host list": "クラウドホストリスト",
|
||||
"Configuration fee details": "設定料金の詳細",
|
||||
"Confirm": "確認",
|
||||
"Copy Failed": "コピーに失敗しました",
|
||||
"Create Time": "作成時間",
|
||||
"Created": "作成済み",
|
||||
"Creating": "作成中",
|
||||
"Creation Time": "作成時間",
|
||||
"DataDisk": "データディスク",
|
||||
"Delete": "削除",
|
||||
"Deleted": "削除済み",
|
||||
"Deleting": "削除中",
|
||||
"Deletion warning": "削除警告",
|
||||
"Describe": "説明",
|
||||
"Details": "詳細",
|
||||
"Disk Size": "ディスクサイズ",
|
||||
"Empty List": "空のリスト",
|
||||
"Fee inquiry in progress": "料金照会中...",
|
||||
"GPU": "GPU",
|
||||
"IP Address": "IPアドレス",
|
||||
"Image": "イメージ",
|
||||
"Instance": "インスタンス",
|
||||
"Login Method": "ログイン方法",
|
||||
"Memory": "メモリ",
|
||||
"Name": "名前",
|
||||
"New Server": "新しいサーバー",
|
||||
"Operate": "操作",
|
||||
"Operating System": "オペレーティングシステム",
|
||||
"Operation": "操作",
|
||||
"Password copied to clipboard": "パスワードがクリップボードにコピーされました",
|
||||
"Please enter": "入力してください",
|
||||
"Please enter your password": "パスワードを入力してください",
|
||||
"Please select a mirror": "ミラーを選択してください",
|
||||
"Prompt": "プロンプト",
|
||||
"Public IP": "パブリックIP",
|
||||
"Public network bandwidth": "パブリックネットワーク帯域幅",
|
||||
"Quantity is 1 to 100": "数量は1から100までです",
|
||||
"ReStart": "再起動",
|
||||
"Reference fee": "参考料金",
|
||||
"Reference fee disk tips": "{{price}}/GiB/時間あたり",
|
||||
"Reference fee network tips": "パブリックネットワークの帯域幅が5 Mbps未満の場合、価格は{{price1}}/Mbps/時間です。帯域幅が5 Mbps以上の場合、価格は{{price2}}/Mbps/時間です。",
|
||||
"Reference fee tip": "{{price}}/時間",
|
||||
"Restarting": "再起動中",
|
||||
"Set Password": "パスワードを設定する",
|
||||
"Start": "開始",
|
||||
"Started": "実行中",
|
||||
"Starting": "起動中",
|
||||
"Status": "ステータス",
|
||||
"Stop": "停止",
|
||||
"Stopped": "停止済み",
|
||||
"Stopping": "停止中",
|
||||
"Storage": "ストレージ",
|
||||
"Submit": "提出する",
|
||||
"Submit Error": "エラーを送信する",
|
||||
"Supports changing the operating system": "注意:購入後、オペレーティングシステムの変更がサポートされます。ビジネスのニーズに応じてシステムを変更できます",
|
||||
"SystemDisk": "システムディスク",
|
||||
"The maximum number of instances is 100": "最大値は100です",
|
||||
"The maximum number of instances is 500": "最大値は500です",
|
||||
"The minimum number of instances is 1": "最小値は1です",
|
||||
"The minimum number of instances is 20": "最小値は20です",
|
||||
"The number of instances cannot be empty": "インスタンスの数は空にできません",
|
||||
"The purpose of the disk": "ディスクの目的",
|
||||
"Type": "タイプ",
|
||||
"capacity": "容量",
|
||||
"close cloud server tip": "クラウドサーバーをシャットダウンしても、ストレージの料金は発生しますので、ご確認ください!",
|
||||
"delete cloud server tip": "このクラウドサーバーを削除してもよろしいですか",
|
||||
"hour": "時間",
|
||||
"price": "価格",
|
||||
"publicIpAssigned tips": "注意:独立したパブリックIPが割り当てられていないため、外部との通信にはインターネットIPを使用することができません。サーバーは登録に使用できませんので、慎重に選択してください。",
|
||||
"ssh port": "SSHポート",
|
||||
"sshName": "SSHユーザー名",
|
||||
"sshPassword": "SSHパスワード",
|
||||
"storage fees": "ストレージ料金",
|
||||
"success": "成功",
|
||||
"terminal": "ターミナル"
|
||||
}
|
||||
@@ -69,7 +69,7 @@
|
||||
"Are you sure to create a cloud host?": "确认创建一台云主机吗",
|
||||
"Confirm": "确认",
|
||||
"close cloud server tip": "请注意,关机状态下存储仍会收费,请确认!",
|
||||
"Quantity is 1 to 100": "数量是1到100",
|
||||
"Quantity tips": "数量是 {{amount1}} 到 {{amount2}}",
|
||||
"Password copied to clipboard": "密码已复制到剪切板",
|
||||
"Copy Failed": "复制失败",
|
||||
"Changing": "变更中",
|
||||
@@ -87,5 +87,24 @@
|
||||
"hour": "小时",
|
||||
"Reference fee disk tips": "{{price}}/GiB/小时",
|
||||
"Reference fee network tips": "当公网带宽小于5 Mbps时,价格为{{price1}}/Mbps/小时;当公网带宽大于等于5 Mbps时,价格为{{price2}}/Mbps/小时。",
|
||||
"Billing rules": "计费规则"
|
||||
}
|
||||
"Billing rules": "计费规则",
|
||||
"The maximum number of instances is 20": "最大数量是20",
|
||||
"Billing model": "计费模式",
|
||||
"Availability Zone": "可用区",
|
||||
"Architecture": "架构",
|
||||
"Instance Family": "实例族",
|
||||
"postPaidByHour": "按量计费",
|
||||
"heterogeneous": "异构计算",
|
||||
"prePaid": "包年包月",
|
||||
"x86_64": "x86计算",
|
||||
"costEffective": "实惠型",
|
||||
"gpu": "GPU",
|
||||
"Password does not meet requirements": "密码不符合要求",
|
||||
"Lowercase letters az": "小写字母 a ~ z",
|
||||
"Capital letters az": "大写字母 A ~ Z",
|
||||
"Numbers 09": "数字 0 ~ 9",
|
||||
"Contains at least three of these": "至少包含其中三项",
|
||||
"Cannot start with": "不能以\" / \"开头",
|
||||
"cannot contain spaces": "不能包含空格",
|
||||
"Within 8 to 30 characters": "在 8 ~ 30 位字符数以内(推荐12位以上)"
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { ServerTypePayload } from '@/pages/api/cloudserver/listType';
|
||||
import { UpdateStatusPayload } from '@/pages/api/cloudserver/updateStatus';
|
||||
import { POST } from '@/services/request';
|
||||
import adaptCloudServerListItem from '@/types/adapt';
|
||||
import { CloudServerPrice, CloudServerType, EditForm, OperatingSystems } from '@/types/cloudserver';
|
||||
import { CVMInstanceType } from '@/types/cloudserver';
|
||||
import { CVMRegionType } from '@/types/region';
|
||||
|
||||
export const getCloudServerType = () => POST<CloudServerType[]>('/api/cloudserver/listType');
|
||||
export const getCloudServerRegion = () => POST<CVMRegionType[]>('/api/cloudserver/getRegion');
|
||||
|
||||
export const getCloudServerType = (payload: ServerTypePayload) =>
|
||||
POST<CloudServerType[]>('/api/cloudserver/listType', payload);
|
||||
|
||||
export const getCloudServerImage = () => POST<OperatingSystems>('/api/cloudserver/listImage');
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2559 4.4107C17.5814 4.73614 17.5814 5.26378 17.2559 5.58922L8.08926 14.7559C7.76382 15.0813 7.23618 15.0813 6.91074 14.7559L2.74408 10.5892C2.41864 10.2638 2.41864 9.73614 2.74408 9.4107C3.06951 9.08527 3.59715 9.08527 3.92259 9.4107L7.5 12.9881L16.0774 4.4107C16.4028 4.08527 16.9305 4.08527 17.2559 4.4107Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 468 B |
@@ -1 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 10"><path d="M4.64844 9.50692V5.50692H0.648438V4.17358H4.64844V0.173584H5.98177V4.17358H9.98177V5.50692H5.98177V9.50692H4.64844Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4999 3.83337C10.9602 3.83337 11.3333 4.20647 11.3333 4.66671V9.66671H16.3333C16.7935 9.66671 17.1666 10.0398 17.1666 10.5C17.1666 10.9603 16.7935 11.3334 16.3333 11.3334H11.3333V16.3334C11.3333 16.7936 10.9602 17.1667 10.4999 17.1667C10.0397 17.1667 9.66658 16.7936 9.66658 16.3334V11.3334H4.66659C4.20635 11.3334 3.83325 10.9603 3.83325 10.5C3.83325 10.0398 4.20635 9.66671 4.66659 9.66671H9.66658V4.66671C9.66658 4.20647 10.0397 3.83337 10.4999 3.83337Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 613 B |
@@ -30,6 +30,7 @@ const map = {
|
||||
info: require('./icons/info.svg').default,
|
||||
restore: require('./icons/restore.svg').default,
|
||||
download: require('./icons/download.svg').default,
|
||||
check: require('./icons/check.svg').default,
|
||||
close: require('./icons/close.svg').default,
|
||||
help: require('./icons/help.svg').default,
|
||||
link: require('./icons/link.svg').default
|
||||
|
||||
@@ -5,7 +5,7 @@ const MyTooltip = ({ children, ...props }: TooltipProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
bg={'white'}
|
||||
arrowShadowColor={' rgba(0,0,0,0.1)'}
|
||||
arrowShadowColor={' rgba(0,0,0,0.05)'}
|
||||
hasArrow
|
||||
arrowSize={12}
|
||||
offset={[-15, 15]}
|
||||
@@ -14,6 +14,7 @@ const MyTooltip = ({ children, ...props }: TooltipProps) => {
|
||||
py={2}
|
||||
borderRadius={'8px'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
boxShadow={'1px 1px 10px rgba(0,0,0,0.2)'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -12,13 +12,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const dataDisks = form.storages
|
||||
.filter(({ use }) => use === 'DataDisk')
|
||||
.flatMap(({ size, amount }) => Array(amount).fill(size));
|
||||
console.log(dataDisks);
|
||||
const systemDiskSize = form.storages.find(({ use }) => use === 'SystemDisk')?.size || 20;
|
||||
|
||||
const payload: CreateCloudServerPayload = {
|
||||
virtualMachinePackageFamily: form.virtualMachinePackageFamily,
|
||||
virtualMachinePackageName: form.virtualMachinePackageName,
|
||||
imageId: form.systemImageId,
|
||||
systemDisk: form.systemDiskSize,
|
||||
systemDisk: systemDiskSize,
|
||||
dataDisks: dataDisks,
|
||||
...(form.publicIpAssigned
|
||||
? {
|
||||
@@ -27,7 +27,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
: { internetMaxBandwidthOut: 0 }),
|
||||
loginPassword: form.password,
|
||||
loginName: '',
|
||||
metaData: {}
|
||||
metaData: {},
|
||||
zone: form.zone,
|
||||
virtualMachineType: form.virtualMachineType,
|
||||
virtualMachineArch: form.virtualMachineArch,
|
||||
chareType: form.chargeType
|
||||
};
|
||||
|
||||
const { data } = await POST('/action/create', payload, {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { verifyAccessToken } from '@/services/backend/auth';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { POST } from '@/services/requestLaf';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await verifyAccessToken(req);
|
||||
|
||||
const { data, error } = await POST(
|
||||
'/action/get-region-details',
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: req.headers.authorization
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return jsonRes(res, { code: 500, error: error });
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
data: data
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(res, { code: 500, error: error });
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
);
|
||||
|
||||
return jsonRes(res, {
|
||||
jsonRes(res, {
|
||||
data: data
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
import { verifyAccessToken } from '@/services/backend/auth';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { POST } from '@/services/requestLaf';
|
||||
import { CVMChargeType } from '@/types/region';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export type ServerTypePayload = {
|
||||
zone: string;
|
||||
virtualMachinePackageFamily: string;
|
||||
chargeType: CVMChargeType;
|
||||
virtualMachineType: string;
|
||||
virtualMachineArch: string;
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await verifyAccessToken(req);
|
||||
|
||||
const { data, error } = await POST(
|
||||
'/action/get-virtual-machine-package',
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: req.headers.authorization
|
||||
}
|
||||
}
|
||||
);
|
||||
const payload = req.body as ServerTypePayload;
|
||||
console.log(payload);
|
||||
|
||||
return jsonRes(res, {
|
||||
data: data || error
|
||||
const { data, error } = await POST('/action/get-virtual-machine-package', payload, {
|
||||
headers: {
|
||||
Authorization: req.headers.authorization
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return jsonRes(res, { code: 500, error: error });
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
data: data
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(res, { code: 500, error: error });
|
||||
|
||||
@@ -12,12 +12,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const dataDisks = form.storages
|
||||
.filter(({ use }) => use === 'DataDisk')
|
||||
.flatMap(({ size, amount }) => Array(amount).fill(size));
|
||||
const systemDiskSize = form.storages.find(({ use }) => use === 'SystemDisk')?.size || 20;
|
||||
|
||||
const payload: CreateCloudServerPayload = {
|
||||
virtualMachinePackageFamily: form.virtualMachinePackageFamily,
|
||||
virtualMachinePackageName: form.virtualMachinePackageName,
|
||||
imageId: form.systemImageId,
|
||||
systemDisk: form.systemDiskSize,
|
||||
systemDisk: systemDiskSize,
|
||||
dataDisks: dataDisks,
|
||||
...(form.publicIpAssigned
|
||||
? {
|
||||
@@ -26,7 +27,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
: { internetMaxBandwidthOut: 0 }),
|
||||
loginPassword: form.password,
|
||||
loginName: '',
|
||||
metaData: {}
|
||||
metaData: {},
|
||||
zone: form.zone,
|
||||
virtualMachineType: form.virtualMachineType,
|
||||
virtualMachineArch: form.virtualMachineArch,
|
||||
chareType: form.chargeType
|
||||
};
|
||||
|
||||
const result = await POST('/action/get-price', payload, {
|
||||
|
||||
@@ -1,33 +1,81 @@
|
||||
import { getCloudServerImage, getCloudServerType } from '@/api/cloudserver';
|
||||
import { getCloudServerImage, getCloudServerRegion, getCloudServerType } from '@/api/cloudserver';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { MyTable, TableColumnsType } from '@/components/MyTable';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { CloudServerType, EditForm, StorageType } from '@/types/cloudserver';
|
||||
import { CVMArchType, CVMRegionType, CVMZoneType, VirtualMachineType } from '@/types/region';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Divider,
|
||||
Flex,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Image,
|
||||
Input,
|
||||
ListItem,
|
||||
Radio,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Switch,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
TabPanelsProps,
|
||||
TabProps,
|
||||
Tabs,
|
||||
Text,
|
||||
UnorderedList,
|
||||
useDisclosure,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { MySelect, RangeInput, Tabs } from '@sealos/ui';
|
||||
import { MySelect, RangeInput, Tabs as SealosTabs } from '@sealos/ui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 4);
|
||||
const nanoidUpper = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ', 4);
|
||||
const nanoidNumber = customAlphabet('0123456789', 4);
|
||||
|
||||
const tabPanelStyles: TabPanelsProps = {
|
||||
padding: '0px',
|
||||
my: '24px'
|
||||
};
|
||||
|
||||
const tabStyles: TabProps = {
|
||||
bg: 'grayModern.50',
|
||||
_hover: {
|
||||
opacity: 0.9,
|
||||
color: 'brightBlue.700',
|
||||
borderColor: '#85ccff'
|
||||
},
|
||||
border: '1px solid',
|
||||
borderRadius: '2px',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '400',
|
||||
color: 'grayModern.900',
|
||||
borderColor: 'grayModern.200',
|
||||
width: '160px',
|
||||
css: {
|
||||
svg: {
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
_selected: {
|
||||
bg: '#F9FDFE',
|
||||
borderColor: 'brightBlue.400',
|
||||
svg: {
|
||||
opacity: 1,
|
||||
color: 'brightBlue.600'
|
||||
}
|
||||
},
|
||||
mr: '8px'
|
||||
};
|
||||
|
||||
const Label = ({
|
||||
children,
|
||||
w = 120,
|
||||
@@ -48,37 +96,253 @@ const Label = ({
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default function Form({
|
||||
formHook,
|
||||
refresh,
|
||||
function VirtualMachinePackageTabs({
|
||||
virtualMachinePackageFamily
|
||||
}: {
|
||||
virtualMachinePackageFamily: string[];
|
||||
}) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
const index = virtualMachinePackageFamily?.findIndex(
|
||||
(item) => item === formHook?.getValues('virtualMachinePackageFamily')
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
index={index !== -1 ? index : 0}
|
||||
variant="unstyled"
|
||||
onChange={(e) => {
|
||||
const item = virtualMachinePackageFamily[e];
|
||||
formHook?.setValue('virtualMachinePackageFamily', item);
|
||||
}}
|
||||
>
|
||||
<TabList alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Type')}</Text>
|
||||
</Label>
|
||||
{virtualMachinePackageFamily?.map((item) => (
|
||||
<Tab {...tabStyles} key={item}>
|
||||
{item}
|
||||
<MyIcon key={item} ml={'auto'} name="check" width={'16px'} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
function VirtualMachineTabs({ virtualMachine }: { virtualMachine: VirtualMachineType[] }) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
const index = virtualMachine?.findIndex(
|
||||
(item) => item.virtualMachineType === formHook?.getValues('virtualMachineType')
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
index={index !== -1 ? index : 0}
|
||||
variant="unstyled"
|
||||
onChange={(e) => {
|
||||
const item = virtualMachine[e];
|
||||
formHook?.setValue('virtualMachineType', item.virtualMachineType);
|
||||
formHook?.setValue('virtualMachinePackageFamily', item.virtualMachinePackageFamily[0]);
|
||||
}}
|
||||
>
|
||||
<TabList alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Instance Family')}</Text>
|
||||
</Label>
|
||||
{virtualMachine?.map((item) => (
|
||||
<Tab {...tabStyles} key={item.virtualMachineType}>
|
||||
{t(item.virtualMachineType)}
|
||||
<MyIcon key={item.virtualMachineType} ml={'auto'} name="check" width={'16px'} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{virtualMachine?.map((item) => (
|
||||
<TabPanel {...tabPanelStyles} key={item.virtualMachineType}>
|
||||
<VirtualMachinePackageTabs
|
||||
virtualMachinePackageFamily={item.virtualMachinePackageFamily}
|
||||
/>
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
function ArchTabs({ arch }: { arch: CVMArchType[] }) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
const index = arch?.findIndex((item) => item.arch === formHook?.getValues('virtualMachineArch'));
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tabs
|
||||
index={index !== -1 ? index : 0}
|
||||
variant="unstyled"
|
||||
onChange={(e) => {
|
||||
const item = arch[e];
|
||||
formHook?.setValue('virtualMachineArch', item.arch);
|
||||
formHook?.setValue('virtualMachineType', item.virtualMachineType[0].virtualMachineType);
|
||||
formHook?.setValue(
|
||||
'virtualMachinePackageFamily',
|
||||
item.virtualMachineType[0].virtualMachinePackageFamily[0]
|
||||
);
|
||||
}}
|
||||
>
|
||||
<TabList alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Architecture')}</Text>
|
||||
</Label>
|
||||
{arch?.map((item) => (
|
||||
<Tab key={item.arch} {...tabStyles}>
|
||||
{t(item.arch)}
|
||||
<MyIcon ml={'auto'} name="check" width={'16px'} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{arch?.map((item) => (
|
||||
<TabPanel {...tabPanelStyles} key={item.arch}>
|
||||
<VirtualMachineTabs virtualMachine={item.virtualMachineType} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
function ZoneTabs({ zone }: { zone: CVMZoneType[] }) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
const { t } = useTranslation();
|
||||
const index = zone?.findIndex((item) => item.zone === formHook?.getValues('zone'));
|
||||
|
||||
return (
|
||||
<Tabs index={index !== -1 ? index : 0} variant="unstyled">
|
||||
<TabList alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Availability Zone')}</Text>
|
||||
</Label>
|
||||
{zone?.map((item) => (
|
||||
<Tab
|
||||
key={item.zone}
|
||||
{...tabStyles}
|
||||
onClick={() => {
|
||||
formHook?.setValue('zone', item.zone);
|
||||
}}
|
||||
>
|
||||
{item.zone}
|
||||
<MyIcon ml={'auto'} name="check" width={'16px'} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{zone?.map((item) => (
|
||||
<TabPanel {...tabPanelStyles} key={item.zone}>
|
||||
<ArchTabs arch={item.arch} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
function OuterTabs({ systemRegion }: { systemRegion: CVMRegionType[] }) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
const { t } = useTranslation();
|
||||
const index = systemRegion?.findIndex(
|
||||
(item) => item.chargeType === formHook?.getValues('chargeType')
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
variant="unstyled"
|
||||
index={index}
|
||||
onChange={(e) => {
|
||||
const item = systemRegion[e];
|
||||
formHook?.setValue('chargeType', item.chargeType);
|
||||
}}
|
||||
>
|
||||
<TabList alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Billing model')}</Text>
|
||||
</Label>
|
||||
{systemRegion?.map((item) => (
|
||||
<Tab key={item.chargeType} {...tabStyles}>
|
||||
{t(item.chargeType)}
|
||||
<MyIcon ml={'auto'} name="check" width={'16px'} />
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{systemRegion?.map((item) => (
|
||||
<TabPanel {...tabPanelStyles} key={item.chargeType}>
|
||||
<ZoneTabs zone={item.zone} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Form({
|
||||
refresh,
|
||||
setInstanceType
|
||||
}: {
|
||||
formHook: UseFormReturn<EditForm, any>;
|
||||
refresh: boolean;
|
||||
|
||||
setInstanceType: Dispatch<SetStateAction<CloudServerType | undefined>>;
|
||||
}) {
|
||||
const formHook = useFormContext<EditForm>();
|
||||
if (!formHook) return <></>;
|
||||
const [clientRender, setClientRender] = useState(false);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const specialTips = useMemo(() => {
|
||||
return {
|
||||
specialChars: "()`~!@#$%^&*-+=_|{}[]:;'<>,.?/",
|
||||
regex:
|
||||
/^(?!.*\s)(?!\/)(?=[^\/]{8,30}$)(?:(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])|(?=.*[a-z])(?=.*[A-Z])(?=.*[\(\)`~!@#$%^&*\-+=_|{}\[\]:;'<>,.?/])|(?=.*[a-z])(?=.*[0-9])(?=.*[\(\)`~!@#$%^&*\-+=_|{}\[\]:;'<>,.?/])|(?=.*[A-Z])(?=.*[0-9])(?=.*[\(\)`~!@#$%^&*\-+=_|{}\[\]:;'<>,.?/])).*$/
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setClientRender(true);
|
||||
}, []);
|
||||
|
||||
const { data: SystemImage } = useQuery(['getCloudServerImage'], getCloudServerImage, {
|
||||
const { data: systemRegion } = useQuery(['getCloudServerRegion'], getCloudServerRegion, {
|
||||
staleTime: 5 * 60 * 1000
|
||||
});
|
||||
const { data: systemImage } = useQuery(['getCloudServerImage'], getCloudServerImage, {
|
||||
staleTime: 5 * 60 * 1000
|
||||
});
|
||||
|
||||
const { data: ServersData } = useQuery(['getCloudServerType'], getCloudServerType, {
|
||||
staleTime: 5 * 60 * 1000,
|
||||
onSuccess(data) {
|
||||
if (data?.[0]) {
|
||||
formHook.setValue('virtualMachinePackageName', data[0].virtualMachinePackageName);
|
||||
setInstanceType(data?.[0]);
|
||||
const { data: serverTypeData } = useQuery(
|
||||
[
|
||||
'getCloudServerType',
|
||||
formHook.getValues('zone'),
|
||||
formHook.getValues('virtualMachinePackageFamily'),
|
||||
formHook.getValues('chargeType'),
|
||||
formHook.getValues('virtualMachineType'),
|
||||
formHook.getValues('virtualMachineArch')
|
||||
],
|
||||
() =>
|
||||
getCloudServerType({
|
||||
zone: formHook.getValues('zone'),
|
||||
virtualMachinePackageFamily: formHook.getValues('virtualMachinePackageFamily'),
|
||||
chargeType: formHook.getValues('chargeType'),
|
||||
virtualMachineType: formHook.getValues('virtualMachineType'),
|
||||
virtualMachineArch: formHook.getValues('virtualMachineArch')
|
||||
}),
|
||||
{
|
||||
staleTime: 5 * 60 * 1000,
|
||||
onSuccess(data) {
|
||||
if (data?.[0]) {
|
||||
formHook.setValue('virtualMachinePackageName', data[0].virtualMachinePackageName);
|
||||
setInstanceType(data?.[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -170,7 +434,7 @@ export default function Form({
|
||||
value={getValues(`storages.${rowNumber}.size`)}
|
||||
min={20}
|
||||
max={500}
|
||||
// hoverText={t('Number of instances: 1 to 20') || 'Number of instances: 1 to 20'}
|
||||
hoverText={t('Quantity tips', { amount1: 20, amount2: 500 }) || ''}
|
||||
setVal={(val) => {
|
||||
register(`storages.${rowNumber}.size`, {
|
||||
required:
|
||||
@@ -216,7 +480,7 @@ export default function Form({
|
||||
value={getValues(`storages.${rowNumber}.amount`)}
|
||||
min={1}
|
||||
max={20}
|
||||
// hoverText={t('Number of instances: 1 to 20') || 'Number of instances: 1 to 20'}
|
||||
hoverText={t('Quantity tips', { amount1: 1, amount2: 20 }) || ''}
|
||||
setVal={(val) => {
|
||||
register(`storages.${rowNumber}.amount`, {
|
||||
required:
|
||||
@@ -281,16 +545,17 @@ export default function Form({
|
||||
</Text>
|
||||
</Flex>
|
||||
<Box pt="24px" px="42px" pb="64px" flex={1} h="0" overflow={'auto'}>
|
||||
<OuterTabs systemRegion={systemRegion || []} />
|
||||
<Flex alignItems={'center'} mb={'24px'}>
|
||||
<Label alignSelf={'self-start'}>
|
||||
<Text>{t('Type')}</Text>
|
||||
<Text></Text>
|
||||
</Label>
|
||||
<Box flex={1}>
|
||||
{ServersData ? (
|
||||
{serverTypeData ? (
|
||||
<MyTable
|
||||
itemClass="appItem"
|
||||
columns={columns}
|
||||
data={ServersData}
|
||||
data={serverTypeData}
|
||||
openSelected
|
||||
onRowClick={(item: CloudServerType) => {
|
||||
setValue('virtualMachinePackageName', item.virtualMachinePackageName);
|
||||
@@ -305,38 +570,42 @@ export default function Form({
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* systemImage */}
|
||||
<Flex alignItems={'center'} mb={'24px'} flex={1}>
|
||||
<Label alignSelf={'self-start'}>
|
||||
<Text>{t('Image')}</Text>
|
||||
</Label>
|
||||
<Box>
|
||||
<Flex flexWrap={'wrap'} gap={'12px'}>
|
||||
{SystemImage &&
|
||||
Object.keys(SystemImage)?.map((key) => {
|
||||
{systemImage &&
|
||||
Object.keys(systemImage)?.map((key) => {
|
||||
return (
|
||||
<Button
|
||||
variant={'outline'}
|
||||
flexDirection={'column'}
|
||||
<Center
|
||||
key={key}
|
||||
flexDirection={'column'}
|
||||
w={'140px'}
|
||||
height={'88px'}
|
||||
_hover={{
|
||||
filter: 'grayscale(0%)',
|
||||
background: 'rgba(33, 155, 244, 0.05)',
|
||||
opacity: 0.9,
|
||||
color: 'brightBlue.700',
|
||||
borderColor: 'brightBlue.300'
|
||||
}}
|
||||
border={'1px solid'}
|
||||
borderRadius={'2px'}
|
||||
cursor={'pointer'}
|
||||
fontWeight={'bold'}
|
||||
color={'grayModern.900'}
|
||||
{...(getValues('system') === key
|
||||
? {
|
||||
filter: 'grayscale(0%)',
|
||||
background: 'rgba(33, 155, 244, 0.05)',
|
||||
opacity: 0.9,
|
||||
color: 'brightBlue.700',
|
||||
borderColor: 'brightBlue.300'
|
||||
// background: '#FFF',
|
||||
bg: 'rgba(33, 155, 244, 0.05)',
|
||||
borderColor: 'brightBlue.500'
|
||||
// boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)'
|
||||
}
|
||||
: {
|
||||
filter: 'grayscale(100%)'
|
||||
bg: '#F7F8FA',
|
||||
borderColor: 'grayModern.200',
|
||||
filter: 'grayscale(100%)',
|
||||
_hover: {
|
||||
borderColor: '#85ccff',
|
||||
filter: 'grayscale(0%)'
|
||||
}
|
||||
})}
|
||||
onClick={() => {
|
||||
setValue('system', key);
|
||||
@@ -346,12 +615,12 @@ export default function Form({
|
||||
width={'32px'}
|
||||
height={'32px'}
|
||||
alt={key}
|
||||
src={SystemImage[key].url || ''}
|
||||
src={systemImage[key].url || ''}
|
||||
/>
|
||||
<Text mt={'4px'} textAlign={'center'}>
|
||||
{key}
|
||||
</Text>
|
||||
</Button>
|
||||
</Center>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
@@ -362,8 +631,8 @@ export default function Form({
|
||||
width={'400px'}
|
||||
value={getValues('systemImageId')}
|
||||
list={
|
||||
SystemImage && getValues('system')
|
||||
? SystemImage[getValues('system')].images.map((item) => {
|
||||
systemImage && getValues('system')
|
||||
? systemImage[getValues('system')].images.map((item) => {
|
||||
return {
|
||||
value: item.id,
|
||||
label: `${item.os} | ${item.version} | ${item.architect}`
|
||||
@@ -385,6 +654,7 @@ export default function Form({
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* systemStorage */}
|
||||
<Flex alignItems={'center'} mb={'24px'} position={'relative'}>
|
||||
<Label alignSelf={'self-start'}>
|
||||
<Text>{t('Storage')}</Text>
|
||||
@@ -396,7 +666,7 @@ export default function Form({
|
||||
data={storages}
|
||||
borderBottomRadius={'0px'}
|
||||
borderBottom={'none'}
|
||||
></MyTable>
|
||||
/>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
w={'100%'}
|
||||
@@ -436,7 +706,6 @@ export default function Form({
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{getValues('publicIpAssigned') && (
|
||||
<Flex alignItems={'center'} mb={'24px'}>
|
||||
<Flex fontSize={'md'} width={'120px'} alignItems={'center'} fontWeight={'bold'}>
|
||||
@@ -449,7 +718,7 @@ export default function Form({
|
||||
value={getValues('internetMaxBandWidthOut')}
|
||||
min={1}
|
||||
max={100}
|
||||
hoverText={t('Quantity is 1 to 100') || 'Quantity is 1 to 100'}
|
||||
// hoverText={t('Quantity tips', { amount1: 1, amount2: 100 }) || ''}
|
||||
setVal={(val) => {
|
||||
register('internetMaxBandWidthOut', {
|
||||
required:
|
||||
@@ -470,13 +739,12 @@ export default function Form({
|
||||
<Text>Mbps</Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box mb={'24px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Label>
|
||||
<Text>{t('Login Method')}</Text>
|
||||
</Label>
|
||||
<Tabs
|
||||
<SealosTabs
|
||||
size={'sm'}
|
||||
list={[
|
||||
{
|
||||
@@ -504,30 +772,48 @@ export default function Form({
|
||||
<></>
|
||||
) : (
|
||||
<Box mt={'12px'} pl={'120px'}>
|
||||
<FormControl isInvalid={!!errors.password}>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
maxLength={60}
|
||||
placeholder={t('Please enter your password') || 'Please enter your password'}
|
||||
{...register('password', {
|
||||
required: {
|
||||
value: true,
|
||||
message: '不能为空'
|
||||
},
|
||||
maxLength: 60,
|
||||
pattern: {
|
||||
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,30}$/,
|
||||
message: `在 8 ~ 30 位字符数以内(推荐12位以上)
|
||||
不能包含空格
|
||||
不能以" / "开头\n
|
||||
至少包含
|
||||
小写字母 a ~ z;
|
||||
大写字母 A ~ Z;
|
||||
数字 0 ~ 9`
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
<MyTooltip
|
||||
placement="right"
|
||||
offset={[0, 15]}
|
||||
isOpen={isOpen}
|
||||
label={
|
||||
<Flex>
|
||||
<UnorderedList>
|
||||
<ListItem>{t('Within 8 to 30 characters')}</ListItem>
|
||||
<ListItem>{t('cannot contain spaces')}</ListItem>
|
||||
<ListItem>{t('Cannot start with')}</ListItem>
|
||||
<ListItem>{t('Contains at least three of these')}</ListItem>
|
||||
<UnorderedList>
|
||||
<ListItem>{t('Lowercase letters az')}</ListItem>
|
||||
<ListItem>{t('Capital letters az')}</ListItem>
|
||||
<ListItem>{t('Numbers 09')}</ListItem>
|
||||
<ListItem>{specialTips.specialChars}</ListItem>
|
||||
</UnorderedList>
|
||||
</UnorderedList>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<FormControl width={'300px'} isInvalid={!!errors.password}>
|
||||
<Input
|
||||
onFocus={onOpen}
|
||||
maxLength={60}
|
||||
placeholder={t('Please enter your password') || 'Please enter your password'}
|
||||
{...register('password', {
|
||||
required: {
|
||||
value: true,
|
||||
message: '不能为空'
|
||||
},
|
||||
pattern: {
|
||||
value: specialTips.regex,
|
||||
message: t('Password does not meet requirements')
|
||||
},
|
||||
onBlur(event) {
|
||||
onClose();
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</FormControl>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -4,13 +4,14 @@ import { useLoading } from '@/hooks/useLoading';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { CloudServerType, EditForm } from '@/types/cloudserver';
|
||||
import { CVMChargeType } from '@/types/region';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import ErrorModal from './components/ErrorModal';
|
||||
import Form from './components/Form';
|
||||
import Header from './components/Header';
|
||||
@@ -33,18 +34,21 @@ export default function EditOrder() {
|
||||
const formHook = useForm<EditForm>({
|
||||
defaultValues: {
|
||||
system: 'ubuntu',
|
||||
systemDiskSize: 50,
|
||||
publicIpAssigned: false,
|
||||
internetMaxBandWidthOut: 1,
|
||||
storages: [
|
||||
{
|
||||
use: 'SystemDisk',
|
||||
size: 50,
|
||||
size: 20,
|
||||
amount: 1
|
||||
}
|
||||
],
|
||||
virtualMachinePackageFamily: 'A',
|
||||
systemImageId: ''
|
||||
systemImageId: '',
|
||||
chargeType: CVMChargeType.postPaidByHour,
|
||||
zone: 'Guangzhou-6',
|
||||
virtualMachineArch: 'x86_64',
|
||||
virtualMachineType: 'costEffective',
|
||||
virtualMachinePackageFamily: 'A'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,9 +63,9 @@ export default function EditOrder() {
|
||||
});
|
||||
|
||||
const submitSuccess = async (data: EditForm) => {
|
||||
console.log(data);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
console.log(data);
|
||||
await createCloudServer(data);
|
||||
toast({
|
||||
status: 'success',
|
||||
@@ -112,7 +116,9 @@ export default function EditOrder() {
|
||||
applyBtnText="Submit"
|
||||
/>
|
||||
<Flex h={'calc(100% - 126px)'} justifyContent={'center'} borderRadius={'4px'}>
|
||||
<Form formHook={formHook} refresh={forceUpdate} setInstanceType={setInstanceType} />
|
||||
<FormProvider {...formHook}>
|
||||
<Form refresh={forceUpdate} setInstanceType={setInstanceType} />
|
||||
</FormProvider>
|
||||
</Flex>
|
||||
<ConfirmChild />
|
||||
<Loading />
|
||||
|
||||
@@ -3,7 +3,7 @@ import AppStatusTag from '@/components/AppStatusTag';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { CVMInstanceType, HandleEnum } from '@/types/cloudserver';
|
||||
import { formatTime } from '@/utils/tools';
|
||||
import { Box, Button, Flex, FlexProps, MenuButton } from '@chakra-ui/react';
|
||||
import { Box, Button, Center, Flex, FlexProps, MenuButton } from '@chakra-ui/react';
|
||||
import { LogoutIcon, MyTable, SealosMenu, useMessage } from '@sealos/ui';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -104,7 +104,18 @@ const OrderList = ({ apps = [], refetchApps }: { apps: any[]; refetchApps: () =>
|
||||
title: t('sshPassword'),
|
||||
key: 'sshPassword',
|
||||
render: (item: CVMInstanceType) => {
|
||||
return <Box onClick={() => copyData(item.loginPassword)}>***********</Box>;
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
gap={'4px'}
|
||||
onClick={() => copyData(item.loginPassword)}
|
||||
height={'12px'}
|
||||
>
|
||||
<Box pt={'2px'}>***********</Box>
|
||||
<MyIcon name="copy" color={'grayModern.500'} width={'12px'} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -118,8 +129,24 @@ const OrderList = ({ apps = [], refetchApps }: { apps: any[]; refetchApps: () =>
|
||||
render: (item: CVMInstanceType) => {
|
||||
return (
|
||||
<Flex flexDirection={'column'}>
|
||||
<Box>{item?.privateIpAddresses?.join(',')}(内)</Box>
|
||||
<Box>{item?.publicIpAddresses?.join(',')}(公)</Box>
|
||||
<Center
|
||||
gap={'4px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => copyData(item?.privateIpAddresses?.join(',') || '')}
|
||||
>
|
||||
{item?.privateIpAddresses?.join(',')}(内)
|
||||
<MyIcon name="copy" color={'grayModern.500'} width={'12px'} />
|
||||
</Center>
|
||||
{item?.publicIpAddresses && (
|
||||
<Center
|
||||
gap={'4px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => copyData(item?.publicIpAddresses?.join(',') || '')}
|
||||
>
|
||||
{item?.publicIpAddresses?.join(',')}(公)
|
||||
<MyIcon name="copy" color={'grayModern.500'} width={'12px'} />
|
||||
</Center>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,19 +62,35 @@ function Home() {
|
||||
({data?.total})
|
||||
</Box>
|
||||
<Box flex={1}></Box>
|
||||
<Button w="156px" h="42px" onClick={() => router.push('/cloudserver/create')}>
|
||||
<Button
|
||||
w="156px"
|
||||
h="42px"
|
||||
lineHeight={'20px'}
|
||||
leftIcon={<MyIcon name="plus" width={'20px'} />}
|
||||
onClick={() => router.push('/cloudserver/create')}
|
||||
>
|
||||
{t('New Server')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{MockInstance && MockInstance?.length > 0 ? (
|
||||
{data?.list && data?.list?.length > 0 ? (
|
||||
<Box flex={'1 0 0'}>
|
||||
<List refetchApps={refetch} apps={data?.list || []} />
|
||||
</Box>
|
||||
) : (
|
||||
<Flex alignItems={'center'} justifyContent={'center'} flexDirection={'column'} flex={1}>
|
||||
<MyIcon name={'noEvents'} color={'transparent'} width={'80px'} height={'80px'} />
|
||||
<Box py={8}>{t('Empty List')}</Box>
|
||||
<Box mt={'25px'}>{t('Empty List')}</Box>
|
||||
<Button
|
||||
lineHeight={'20px'}
|
||||
leftIcon={<MyIcon name="plus" width={'20px'} />}
|
||||
mt={'40px'}
|
||||
w="156px"
|
||||
h="42px"
|
||||
onClick={() => router.push('/cloudserver/create')}
|
||||
>
|
||||
{t('New Server')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CVMChargeType } from './region';
|
||||
|
||||
export type EditForm = {
|
||||
system: string;
|
||||
systemImageId: string;
|
||||
systemDiskSize: number;
|
||||
publicIpAssigned: boolean;
|
||||
internetMaxBandWidthOut: number;
|
||||
password: string;
|
||||
@@ -9,13 +10,17 @@ export type EditForm = {
|
||||
storages: StorageType[];
|
||||
virtualMachinePackageFamily: string;
|
||||
virtualMachinePackageName: string;
|
||||
virtualMachineType: string;
|
||||
virtualMachineArch: string;
|
||||
chargeType: CVMChargeType;
|
||||
zone: string;
|
||||
};
|
||||
|
||||
export type StorageType = {
|
||||
type?: string;
|
||||
size: number;
|
||||
amount: number;
|
||||
use: string;
|
||||
use: 'SystemDisk' | 'DataDisk';
|
||||
};
|
||||
|
||||
export type CloudServerType = {
|
||||
@@ -137,4 +142,8 @@ export type CreateCloudServerPayload = {
|
||||
metaData: {
|
||||
[key: string]: any;
|
||||
};
|
||||
zone: string;
|
||||
virtualMachineType: string;
|
||||
virtualMachineArch: string;
|
||||
chareType: CVMChargeType;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export type VirtualMachineType = {
|
||||
virtualMachineType: string;
|
||||
virtualMachinePackageFamily: string[];
|
||||
};
|
||||
|
||||
export type CVMArchType = {
|
||||
arch: string;
|
||||
virtualMachineType: VirtualMachineType[];
|
||||
};
|
||||
|
||||
export type CVMZoneType = {
|
||||
zone: string;
|
||||
arch: CVMArchType[];
|
||||
};
|
||||
|
||||
export enum CVMChargeType {
|
||||
postPaidByHour = 'postPaidByHour',
|
||||
prePaid = 'prePaid'
|
||||
}
|
||||
|
||||
export type CVMRegionType = {
|
||||
regionId: string;
|
||||
chargeType: CVMChargeType;
|
||||
regionName: string;
|
||||
zone: CVMZoneType[];
|
||||
};
|
||||
@@ -90,7 +90,7 @@ const AppBaseInfo = ({ db = defaultDBDetail }: { db: DBDetailType }) => {
|
||||
}@${host}:${port}`;
|
||||
|
||||
if (db?.dbType === 'mongodb' || db?.dbType === 'postgresql') {
|
||||
connection += '?directConnection=true';
|
||||
connection += '/?directConnection=true';
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user