dev #3
@ -8,7 +8,7 @@
|
||||
<!-- 当前套餐卡片 -->
|
||||
<div class="rounded-md border">
|
||||
<div class="h-12 border-b px-5 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900">当前套餐</h2>
|
||||
<h2 class="text-lg font-medium text-gray-900">{{ $t('Current Plan') }}</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="flex h-full flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
<div v-if="$jsiteServer.pg.end_date" class="mt-2 inline-flex items-center rounded-full bg-amber-50 px-4 py-2 text-sm font-medium text-amber-800">
|
||||
<ClockIcon class="mr-1.5 h-4 w-4 text-amber-500" />
|
||||
到期时间:{{ $format.date($jsiteServer.pg.end_date) }}
|
||||
{{ $t('Expiry Date') }}: {{ $format.date($jsiteServer.pg.end_date) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@ -28,14 +28,14 @@
|
||||
:loading="$jsiteServer.renew?.loading"
|
||||
class="px-5 !bg-[#1fc76f] !hover:bg-[#1bb85f] !text-white"
|
||||
>
|
||||
续费
|
||||
{{ $t('Renew') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="upgradeServer"
|
||||
:loading="upgradeLoading"
|
||||
class="px-5 !bg-[#3b82f6] !hover:bg-[#2563eb] !text-white"
|
||||
>
|
||||
升级
|
||||
{{ $t('Upgrade') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -45,25 +45,25 @@
|
||||
<!-- 服务器配置 -->
|
||||
<div class="rounded-md border">
|
||||
<div class="h-12 border-b px-5 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900">服务器配置</h2>
|
||||
<h2 class="text-lg font-medium text-gray-900">{{ $t('Server Configuration') }}</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">CPU:</span>
|
||||
<span class="text-lg font-semibold text-blue-600">{{ $jsiteServer.pg.cpu || '未知' }}核</span>
|
||||
<span class="text-gray-600">{{ $t('CPU') }}:</span>
|
||||
<span class="text-lg font-semibold text-blue-600">{{ $jsiteServer.pg.cpu || $t('Unknown') }} {{ $t('cores') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">内存:</span>
|
||||
<span class="text-lg font-semibold text-green-600">{{ $jsiteServer.pg.memory || '未知' }}GB</span>
|
||||
<span class="text-gray-600">{{ $t('Memory') }}:</span>
|
||||
<span class="text-lg font-semibold text-green-600">{{ $jsiteServer.pg.memory || $t('Unknown') }}GB</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">系统盘:</span>
|
||||
<span class="text-lg font-semibold text-purple-600">{{ $jsiteServer.pg.disk_size || '未知' }}GB</span>
|
||||
<span class="text-gray-600">{{ $t('System Disk') }}:</span>
|
||||
<span class="text-lg font-semibold text-purple-600">{{ $jsiteServer.pg.disk_size || $t('Unknown') }}GB</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">带宽:</span>
|
||||
<span class="text-lg font-semibold text-orange-600">{{ $jsiteServer.pg.bandwidth || '未知' }}Mbps</span>
|
||||
<span class="text-gray-600">{{ $t('Bandwidth') }}:</span>
|
||||
<span class="text-lg font-semibold text-orange-600">{{ $jsiteServer.pg.bandwidth || $t('Unknown') }}Mbps</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -72,7 +72,7 @@
|
||||
<!-- 服务器信息 -->
|
||||
<div class="rounded-md border">
|
||||
<div class="h-12 border-b px-5 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900">服务器信息</h2>
|
||||
<h2 class="text-lg font-medium text-gray-900">{{ $t('Server Information') }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
@ -92,11 +92,11 @@
|
||||
<Badge :label="info.value" />
|
||||
</div>
|
||||
<!-- 服务器名称特殊处理 - 支持内联编辑 -->
|
||||
<div v-else-if="info.label === '服务器名称'" class="flex-1 min-w-0">
|
||||
<div v-else-if="info.label === $t('Server Name')" class="flex-1 min-w-0">
|
||||
<div v-if="!editingServerName"
|
||||
@click="startEditServerName"
|
||||
class="group flex items-center cursor-pointer rounded-md px-2 py-1 -mx-2 -my-1 hover:bg-gray-100 transition-colors duration-200"
|
||||
:title="'点击编辑服务器名称'"
|
||||
:title="$t('Click to edit server name')"
|
||||
>
|
||||
<span class="truncate">{{ info.value }}</span>
|
||||
<svg class="ml-2 h-4 w-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@ -113,13 +113,13 @@
|
||||
class="flex-1 px-2 py-1 text-sm border border-blue-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm"
|
||||
:disabled="saveServerNameLoading"
|
||||
maxlength="100"
|
||||
placeholder="请输入服务器名称"
|
||||
:placeholder="$t('Please enter server name')"
|
||||
/>
|
||||
<button
|
||||
@click="saveServerName"
|
||||
:disabled="saveServerNameLoading"
|
||||
class="p-1 text-green-600 hover:text-green-700 disabled:opacity-50 transition-colors duration-200"
|
||||
title="保存"
|
||||
:title="$t('Save')"
|
||||
>
|
||||
<svg v-if="!saveServerNameLoading" class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
@ -133,7 +133,7 @@
|
||||
@click="cancelEditServerName"
|
||||
:disabled="saveServerNameLoading"
|
||||
class="p-1 text-gray-500 hover:text-gray-700 disabled:opacity-50 transition-colors duration-200"
|
||||
title="取消"
|
||||
:title="$t('Cancel')"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
@ -159,7 +159,7 @@
|
||||
<!-- 操作 -->
|
||||
<div class="rounded-md border">
|
||||
<div class="h-12 border-b px-5 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900">操作</h2>
|
||||
<h2 class="text-lg font-medium text-gray-900">{{ $t('Actions') }}</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@ -169,7 +169,7 @@
|
||||
variant="outline"
|
||||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
>
|
||||
重启
|
||||
{{ $t('Restart') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="forceRestartServer"
|
||||
@ -177,7 +177,7 @@
|
||||
variant="outline"
|
||||
class="bg-orange-50 text-orange-700 hover:bg-orange-100 border-orange-200"
|
||||
>
|
||||
强制重启
|
||||
{{ $t('Force Restart') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="resetPassword"
|
||||
@ -185,7 +185,7 @@
|
||||
variant="outline"
|
||||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
>
|
||||
重置密码
|
||||
{{ $t('Reset Password') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="resetKeyPair"
|
||||
@ -193,7 +193,7 @@
|
||||
variant="outline"
|
||||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||
>
|
||||
重置密钥对
|
||||
{{ $t('Reset Key Pair') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="deleteKeyPair"
|
||||
@ -201,7 +201,7 @@
|
||||
variant="outline"
|
||||
class="bg-red-50 text-red-700 hover:bg-red-100 border-red-200"
|
||||
>
|
||||
删除密钥对
|
||||
{{ $t('Delete Key Pair') }}
|
||||
</Button>
|
||||
<Button
|
||||
@click="resetSystem"
|
||||
@ -209,7 +209,7 @@
|
||||
variant="outline"
|
||||
class="bg-red-50 text-red-700 hover:bg-red-100 border-red-200"
|
||||
>
|
||||
重置系统
|
||||
{{ $t('Reset System') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -218,29 +218,29 @@
|
||||
<!-- SSH连接信息 -->
|
||||
<div class="rounded-md border">
|
||||
<div class="h-12 border-b px-5 py-4">
|
||||
<h2 class="text-lg font-medium text-gray-900">SSH连接</h2>
|
||||
<h2 class="text-lg font-medium text-gray-900">{{ $t('SSH Connection') }}</h2>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">SSH端口:</span>
|
||||
<span class="text-gray-600">{{ $t('SSH Port') }}:</span>
|
||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.ssh_port || '22' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">SSH用户:</span>
|
||||
<span class="text-gray-600">{{ $t('SSH User') }}:</span>
|
||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.ssh_user || 'root' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">服务器密码:</span>
|
||||
<span class="text-gray-600">{{ $t('Server Password') }}:</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="font-mono text-gray-900">
|
||||
{{ showPassword ? decryptedPassword : ($jsiteServer.pg.password || '未设置') }}
|
||||
{{ showPassword ? decryptedPassword : ($jsiteServer.pg.password || $t('Not Set')) }}
|
||||
</span>
|
||||
<button
|
||||
v-if="$jsiteServer.pg.password"
|
||||
@click="togglePassword"
|
||||
class="text-gray-500 hover:text-gray-700 transition-colors"
|
||||
:title="showPassword ? '隐藏密码' : '显示密码'"
|
||||
:title="showPassword ? $t('Hide Password') : $t('Show Password')"
|
||||
>
|
||||
<EyeIcon v-if="!showPassword" class="h-4 w-4" />
|
||||
<EyeOffIcon v-else class="h-4 w-4" />
|
||||
@ -248,22 +248,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">密钥对名称:</span>
|
||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.key_pair_name || '未设置' }}</span>
|
||||
<span class="text-gray-600">{{ $t('Key Pair Name') }}:</span>
|
||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.key_pair_name || $t('Not Set') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-gray-600 mb-2">私钥:</span>
|
||||
<span class="text-gray-600 mb-2">{{ $t('Private Key') }}:</span>
|
||||
<div v-if="$jsiteServer.pg.private_key" class="bg-gray-50 p-3 rounded border relative">
|
||||
<button
|
||||
@click="copyPrivateKey"
|
||||
class="absolute top-2 right-2 p-1 text-gray-500 hover:text-gray-700 transition-colors rounded"
|
||||
:title="copySuccess ? '已复制' : '复制私钥'"
|
||||
:title="copySuccess ? $t('Copied!') : $t('Copy Private Key')"
|
||||
>
|
||||
<CopyIcon class="h-4 w-4" />
|
||||
</button>
|
||||
<pre class="font-mono text-xs text-gray-900 whitespace-pre-wrap break-all pr-8">{{ $jsiteServer.pg.private_key }}</pre>
|
||||
</div>
|
||||
<span v-else class="font-mono text-gray-900">未设置</span>
|
||||
<span v-else class="font-mono text-gray-900">{{ $t('Not Set') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -316,14 +316,14 @@ export default {
|
||||
methods: {
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'Pending': '准备中',
|
||||
'Starting': '启动中',
|
||||
'Running': '运行中',
|
||||
'Stopping': '停止中',
|
||||
'Stopped': '已停止',
|
||||
'Resetting': '重置中',
|
||||
'Upgrading': '升级中',
|
||||
'Disabled': '已禁用'
|
||||
'Pending': this.$t('Pending'),
|
||||
'Starting': this.$t('Starting'),
|
||||
'Running': this.$t('Running'),
|
||||
'Stopping': this.$t('Stopping'),
|
||||
'Stopped': this.$t('Stopped'),
|
||||
'Resetting': this.$t('Resetting'),
|
||||
'Upgrading': this.$t('Upgrading'),
|
||||
'Disabled': this.$t('Disabled')
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
},
|
||||
@ -342,32 +342,32 @@ export default {
|
||||
},
|
||||
getRegionText(region) {
|
||||
const regionMap = {
|
||||
'cn-qingdao': '华北1(青岛)',
|
||||
'cn-beijing': '华北2(北京)',
|
||||
'cn-zhangjiakou': '华北3(张家口)',
|
||||
'cn-huhehaote': '华北5(呼和浩特)',
|
||||
'cn-hangzhou': '华东1(杭州)',
|
||||
'cn-shanghai': '华东2(上海)',
|
||||
'cn-shenzhen': '华南1(深圳)',
|
||||
'cn-heyuan': '华南2(河源)',
|
||||
'cn-chengdu': '西南1(成都)',
|
||||
'cn-guangzhou': '华南3(广州)',
|
||||
'cn-wulanchabu': '华北6(乌兰察布)',
|
||||
'cn-nanjing': '华东5(南京)',
|
||||
'cn-fuzhou': '华东6(福州)',
|
||||
'cn-wuhan-lr': '华中1(武汉)',
|
||||
'cn-hongkong': '中国香港',
|
||||
'ap-southeast-1': '新加坡',
|
||||
'ap-southeast-3': '马来西亚(吉隆坡)',
|
||||
'ap-southeast-5': '印度尼西亚(雅加达)',
|
||||
'ap-northeast-1': '日本(东京)',
|
||||
'us-west-1': '美国(硅谷)',
|
||||
'us-east-1': '美国(弗吉尼亚)',
|
||||
'eu-central-1': '德国(法兰克福)',
|
||||
'eu-west-1': '英国(伦敦)',
|
||||
'ap-southeast-6': '菲律宾(马尼拉)',
|
||||
'ap-southeast-7': '泰国(曼谷)',
|
||||
'ap-northeast-2': '韩国(首尔)'
|
||||
'cn-qingdao': this.$t('North China 1 (Qingdao)'),
|
||||
'cn-beijing': this.$t('North China 2 (Beijing)'),
|
||||
'cn-zhangjiakou': this.$t('North China 3 (Zhangjiakou)'),
|
||||
'cn-huhehaote': this.$t('North China 5 (Hohhot)'),
|
||||
'cn-hangzhou': this.$t('East China 1 (Hangzhou)'),
|
||||
'cn-shanghai': this.$t('East China 2 (Shanghai)'),
|
||||
'cn-shenzhen': this.$t('South China 1 (Shenzhen)'),
|
||||
'cn-heyuan': this.$t('South China 2 (Heyuan)'),
|
||||
'cn-chengdu': this.$t('Southwest China 1 (Chengdu)'),
|
||||
'cn-guangzhou': this.$t('South China 3 (Guangzhou)'),
|
||||
'cn-wulanchabu': this.$t('North China 6 (Ulanqab)'),
|
||||
'cn-nanjing': this.$t('East China 5 (Nanjing)'),
|
||||
'cn-fuzhou': this.$t('East China 6 (Fuzhou)'),
|
||||
'cn-wuhan-lr': this.$t('Central China 1 (Wuhan)'),
|
||||
'cn-hongkong': this.$t('Hong Kong'),
|
||||
'ap-southeast-1': this.$t('Singapore'),
|
||||
'ap-southeast-3': this.$t('Malaysia (Kuala Lumpur)'),
|
||||
'ap-southeast-5': this.$t('Indonesia (Jakarta)'),
|
||||
'ap-northeast-1': this.$t('Japan (Tokyo)'),
|
||||
'us-west-1': this.$t('US (Silicon Valley)'),
|
||||
'us-east-1': this.$t('US (Virginia)'),
|
||||
'eu-central-1': this.$t('Germany (Frankfurt)'),
|
||||
'eu-west-1': this.$t('UK (London)'),
|
||||
'ap-southeast-6': this.$t('Philippines (Manila)'),
|
||||
'ap-southeast-7': this.$t('Thailand (Bangkok)'),
|
||||
'ap-northeast-2': this.$t('South Korea (Seoul)')
|
||||
};
|
||||
return regionMap[region] || region;
|
||||
},
|
||||
@ -391,18 +391,18 @@ export default {
|
||||
},
|
||||
async restartServer() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog({
|
||||
title: '重启',
|
||||
message: '确定要重启服务器吗?重启过程中服务器将暂时不可用。',
|
||||
title: this.$t('Restart'),
|
||||
message: this.$t('Are you sure you want to restart the server? The server will be temporarily unavailable during the restart process.'),
|
||||
primaryAction: {
|
||||
label: '确定',
|
||||
label: this.$t('Confirm'),
|
||||
onClick: ({ hide }) => {
|
||||
// 立即显示成功提示并关闭弹窗
|
||||
toast.success('重启请求已提交');
|
||||
toast.success(this.$t('Restart request submitted'));
|
||||
hide();
|
||||
|
||||
// 异步提交请求
|
||||
@ -428,17 +428,17 @@ export default {
|
||||
},
|
||||
async forceRestartServer() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog({
|
||||
title: '强制重启',
|
||||
message: '确定要强制重启服务器吗?该操作可能会导致未保存的数据丢失。',
|
||||
title: this.$t('Force Restart'),
|
||||
message: this.$t('Are you sure you want to force restart the server? This operation may cause unsaved data loss.'),
|
||||
primaryAction: {
|
||||
label: '确定',
|
||||
label: this.$t('Confirm'),
|
||||
onClick: ({ hide }) => {
|
||||
toast.success('强制重启请求已提交');
|
||||
toast.success(this.$t('Force restart request submitted'));
|
||||
hide();
|
||||
this.forceRestartLoading = true;
|
||||
const req = createResource({
|
||||
@ -462,7 +462,7 @@ export default {
|
||||
},
|
||||
async resetPassword() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -470,11 +470,11 @@ export default {
|
||||
const PasswordDialog = defineAsyncComponent(() => import('../dialogs/PasswordDialog.vue'));
|
||||
|
||||
renderDialog(h(PasswordDialog, {
|
||||
title: '重置服务器密码',
|
||||
description: '长度为 8 至 30 个字符,必须同时包含大小写英文字母、数字和特殊符号。',
|
||||
title: this.$t('Reset Server Password'),
|
||||
description: this.$t('Length must be between 8 and 30 characters, and must include uppercase and lowercase letters, numbers, and special characters.'),
|
||||
onConfirm: (password) => {
|
||||
// 立即显示成功提示
|
||||
toast.success('密码重置请求已提交');
|
||||
toast.success(this.$t('Password reset request submitted'));
|
||||
|
||||
// 异步提交请求
|
||||
this.resetPasswordLoading = true;
|
||||
@ -499,18 +499,18 @@ export default {
|
||||
},
|
||||
async resetKeyPair() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog({
|
||||
title: '重置密钥对',
|
||||
message: '确定要重置密钥对吗?这将删除旧的密钥对并创建新的密钥对。重置后需要使用新的私钥才能连接服务器。',
|
||||
title: this.$t('Reset Key Pair'),
|
||||
message: this.$t('Are you sure you want to reset the key pair? This will delete the old key pair and create a new one. After resetting, you will need to use the new private key to connect to the server.'),
|
||||
primaryAction: {
|
||||
label: '确定',
|
||||
label: this.$t('Confirm'),
|
||||
onClick: ({ hide }) => {
|
||||
// 立即显示成功提示并关闭弹窗
|
||||
toast.success('密钥对重置请求已提交');
|
||||
toast.success(this.$t('Key pair reset request submitted'));
|
||||
hide();
|
||||
|
||||
// 异步提交请求
|
||||
@ -535,18 +535,18 @@ export default {
|
||||
},
|
||||
async deleteKeyPair() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog({
|
||||
title: '删除密钥对',
|
||||
message: '确定要删除密钥对吗?删除后将无法使用私钥连接服务器,建议先设置服务器密码。',
|
||||
title: this.$t('Delete Key Pair'),
|
||||
message: this.$t('Are you sure you want to delete the key pair? After deletion, you will not be able to connect to the server using the private key. It is recommended to set a server password first.'),
|
||||
primaryAction: {
|
||||
label: '确定',
|
||||
label: this.$t('Confirm'),
|
||||
onClick: ({ hide }) => {
|
||||
// 立即显示成功提示并关闭弹窗
|
||||
toast.success('密钥对删除请求已提交');
|
||||
toast.success(this.$t('Key pair deletion request submitted'));
|
||||
hide();
|
||||
|
||||
// 异步提交请求
|
||||
@ -573,18 +573,18 @@ export default {
|
||||
},
|
||||
async resetSystem() {
|
||||
if (!this.$jsiteServer.pg.instance_id) {
|
||||
toast.error('服务器实例ID不存在');
|
||||
toast.error(this.$t('Server instance ID does not exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmDialog({
|
||||
title: '重置系统',
|
||||
message: '确定要重置系统吗?这将清除所有数据并重新安装系统,操作不可逆!',
|
||||
title: this.$t('Reset System'),
|
||||
message: this.$t('Are you sure you want to reset the system? This will clear all data and reinstall the system. This operation is irreversible!'),
|
||||
primaryAction: {
|
||||
label: '确定',
|
||||
label: this.$t('Confirm'),
|
||||
onClick: ({ hide }) => {
|
||||
// 立即显示成功提示并关闭弹窗
|
||||
toast.success('系统重置请求已提交');
|
||||
toast.success(this.$t('System reset request submitted'));
|
||||
hide();
|
||||
|
||||
// 异步提交请求
|
||||
@ -625,11 +625,11 @@ export default {
|
||||
this.decryptedPassword = response;
|
||||
this.showPassword = true;
|
||||
} else {
|
||||
toast.warning('当前没有保存的密码');
|
||||
toast.warning(this.$t('No password is currently saved'));
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('获取密码失败');
|
||||
toast.error(this.$t('Failed to get password'));
|
||||
}
|
||||
});
|
||||
getPasswordRequest.submit();
|
||||
@ -639,22 +639,22 @@ export default {
|
||||
if (this.$jsiteServer.pg.private_key) {
|
||||
navigator.clipboard.writeText(this.$jsiteServer.pg.private_key).then(() => {
|
||||
this.copySuccess = true;
|
||||
toast.success('私钥已复制到剪贴板');
|
||||
toast.success(this.$t('Private key copied to clipboard'));
|
||||
setTimeout(() => {
|
||||
this.copySuccess = false;
|
||||
}, 2000);
|
||||
}).catch(() => {
|
||||
toast.error('复制失败,请手动复制');
|
||||
toast.error(this.$t('Copy failed, please copy manually'));
|
||||
});
|
||||
}
|
||||
},
|
||||
onRenewalSuccess(data) {
|
||||
toast.success('服务器续费成功!');
|
||||
toast.success(this.$t('Server renewal successful!'));
|
||||
// 刷新服务器数据
|
||||
this.$jsiteServer.reload();
|
||||
},
|
||||
onUpgradeSuccess(data) {
|
||||
toast.success('服务器升级成功!');
|
||||
toast.success(this.$t('Server upgrade successful!'));
|
||||
// 刷新服务器数据
|
||||
this.$jsiteServer.reload();
|
||||
},
|
||||
@ -688,17 +688,17 @@ export default {
|
||||
|
||||
const newName = this.editServerNameValue.trim();
|
||||
if (!newName) {
|
||||
toast.error('服务器名称不能为空');
|
||||
toast.error(this.$t('Server name cannot be empty'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName.length < 2) {
|
||||
toast.error('服务器名称至少需要2个字符');
|
||||
toast.error(this.$t('Server name must be at least 2 characters'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName.length > 100) {
|
||||
toast.error('服务器名称不能超过100个字符');
|
||||
toast.error(this.$t('Server name cannot exceed 100 characters'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -714,7 +714,7 @@ export default {
|
||||
{ title: newName },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('服务器名称已更新');
|
||||
toast.success(this.$t('Server name updated'));
|
||||
this.editingServerName = false;
|
||||
this.editServerNameValue = '';
|
||||
this.saveServerNameLoading = false;
|
||||
@ -726,7 +726,7 @@ export default {
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error('更新失败,请重试');
|
||||
toast.error(this.$t('Update failed, please try again'));
|
||||
this.saveServerNameLoading = false;
|
||||
}
|
||||
},
|
||||
@ -735,31 +735,31 @@ export default {
|
||||
serverInformation() {
|
||||
return [
|
||||
{
|
||||
label: '状态',
|
||||
label: this.$t('Status'),
|
||||
value: this.getStatusText(this.$jsiteServer.pg?.status),
|
||||
},
|
||||
{
|
||||
label: '服务器名称',
|
||||
label: this.$t('Server Name'),
|
||||
value: this.$jsiteServer.pg?.title || this.$jsiteServer.pg?.name,
|
||||
},
|
||||
{
|
||||
label: '实例ID',
|
||||
label: this.$t('Instance ID'),
|
||||
value: this.$jsiteServer.pg?.instance_id || '',
|
||||
},
|
||||
{
|
||||
label: '公网IP',
|
||||
label: this.$t('Public IP'),
|
||||
value: this.$jsiteServer.pg?.public_ip || '',
|
||||
},
|
||||
{
|
||||
label: '内网IP',
|
||||
label: this.$t('Private IP'),
|
||||
value: this.$jsiteServer.pg?.private_ip || '',
|
||||
},
|
||||
{
|
||||
label: '区域',
|
||||
label: this.$t('Region'),
|
||||
value: this.getRegionText(this.$jsiteServer.pg?.region),
|
||||
},
|
||||
{
|
||||
label: '系统',
|
||||
label: this.$t('System'),
|
||||
value: this.$jsiteServer.pg?.system || '',
|
||||
},
|
||||
];
|
||||
|
||||
@ -7,6 +7,7 @@ import { duration, planTitle, userCurrency } from '../utils/format';
|
||||
import { trialDays } from '../utils/site';
|
||||
import { getJobsTab } from './common/jobs';
|
||||
import { tagTab } from './common/tags';
|
||||
import { t } from '../utils/i18n';
|
||||
|
||||
export default {
|
||||
pagetype: 'Jsite Server',
|
||||
@ -19,7 +20,7 @@ export default {
|
||||
},
|
||||
list: {
|
||||
route: '/jsite-servers',
|
||||
title: '服务器',
|
||||
title: t('Servers'),
|
||||
fields: [
|
||||
'name',
|
||||
'title',
|
||||
@ -43,52 +44,52 @@ export default {
|
||||
return [
|
||||
{
|
||||
type: 'select',
|
||||
label: '状态',
|
||||
label: t('Status'),
|
||||
fieldname: 'status',
|
||||
options: [
|
||||
{ label: '', value: '' },
|
||||
{ label: '准备中', value: 'Pending' },
|
||||
{ label: '启动中', value: 'Starting' },
|
||||
{ label: '运行中', value: 'Running' },
|
||||
{ label: '停止中', value: 'Stopping' },
|
||||
{ label: '已停止', value: 'Stopped' },
|
||||
{ label: '重置中', value: 'Resetting' },
|
||||
{ label: '升级中', value: 'Upgrading' },
|
||||
{ label: '已禁用', value: 'Disabled' }
|
||||
{ label: t('Pending'), value: 'Pending' },
|
||||
{ label: t('Starting'), value: 'Starting' },
|
||||
{ label: t('Running'), value: 'Running' },
|
||||
{ label: t('Stopping'), value: 'Stopping' },
|
||||
{ label: t('Stopped'), value: 'Stopped' },
|
||||
{ label: t('Resetting'), value: 'Resetting' },
|
||||
{ label: t('Upgrading'), value: 'Upgrading' },
|
||||
{ label: t('Disabled'), value: 'Disabled' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: '区域',
|
||||
label: t('Region'),
|
||||
fieldname: 'region',
|
||||
options: [
|
||||
{ label: '', value: '' },
|
||||
{ label: '华北1(青岛)', value: 'cn-qingdao' },
|
||||
{ label: '华北2(北京)', value: 'cn-beijing' },
|
||||
{ label: '华北3(张家口)', value: 'cn-zhangjiakou' },
|
||||
{ label: '华北5(呼和浩特)', value: 'cn-huhehaote' },
|
||||
{ label: '华东1(杭州)', value: 'cn-hangzhou' },
|
||||
{ label: '华东2(上海)', value: 'cn-shanghai' },
|
||||
{ label: '华南1(深圳)', value: 'cn-shenzhen' },
|
||||
{ label: '华南2(河源)', value: 'cn-heyuan' },
|
||||
{ label: '西南1(成都)', value: 'cn-chengdu' },
|
||||
{ label: '华南3(广州)', value: 'cn-guangzhou' },
|
||||
{ label: '华北6(乌兰察布)', value: 'cn-wulanchabu' },
|
||||
{ label: '华东5(南京)', value: 'cn-nanjing' },
|
||||
{ label: '华东6(福州)', value: 'cn-fuzhou' },
|
||||
{ label: '华中1(武汉)', value: 'cn-wuhan-lr' },
|
||||
{ label: '中国香港', value: 'cn-hongkong' },
|
||||
{ label: '新加坡', value: 'ap-southeast-1' },
|
||||
{ label: '马来西亚(吉隆坡)', value: 'ap-southeast-3' },
|
||||
{ label: '印度尼西亚(雅加达)', value: 'ap-southeast-5' },
|
||||
{ label: '日本(东京)', value: 'ap-northeast-1' },
|
||||
{ label: '美国(硅谷)', value: 'us-west-1' },
|
||||
{ label: '美国(弗吉尼亚)', value: 'us-east-1' },
|
||||
{ label: '德国(法兰克福)', value: 'eu-central-1' },
|
||||
{ label: '英国(伦敦)', value: 'eu-west-1' },
|
||||
{ label: '菲律宾(马尼拉)', value: 'ap-southeast-6' },
|
||||
{ label: '泰国(曼谷)', value: 'ap-southeast-7' },
|
||||
{ label: '韩国(首尔)', value: 'ap-northeast-2' }
|
||||
{ label: t('North China 1 (Qingdao)'), value: 'cn-qingdao' },
|
||||
{ label: t('North China 2 (Beijing)'), value: 'cn-beijing' },
|
||||
{ label: t('North China 3 (Zhangjiakou)'), value: 'cn-zhangjiakou' },
|
||||
{ label: t('North China 5 (Hohhot)'), value: 'cn-huhehaote' },
|
||||
{ label: t('East China 1 (Hangzhou)'), value: 'cn-hangzhou' },
|
||||
{ label: t('East China 2 (Shanghai)'), value: 'cn-shanghai' },
|
||||
{ label: t('South China 1 (Shenzhen)'), value: 'cn-shenzhen' },
|
||||
{ label: t('South China 2 (Heyuan)'), value: 'cn-heyuan' },
|
||||
{ label: t('Southwest China 1 (Chengdu)'), value: 'cn-chengdu' },
|
||||
{ label: t('South China 3 (Guangzhou)'), value: 'cn-guangzhou' },
|
||||
{ label: t('North China 6 (Ulanqab)'), value: 'cn-wulanchabu' },
|
||||
{ label: t('East China 5 (Nanjing)'), value: 'cn-nanjing' },
|
||||
{ label: t('East China 6 (Fuzhou)'), value: 'cn-fuzhou' },
|
||||
{ label: t('Central China 1 (Wuhan)'), value: 'cn-wuhan-lr' },
|
||||
{ label: t('Hong Kong'), value: 'cn-hongkong' },
|
||||
{ label: t('Singapore'), value: 'ap-southeast-1' },
|
||||
{ label: t('Malaysia (Kuala Lumpur)'), value: 'ap-southeast-3' },
|
||||
{ label: t('Indonesia (Jakarta)'), value: 'ap-southeast-5' },
|
||||
{ label: t('Japan (Tokyo)'), value: 'ap-northeast-1' },
|
||||
{ label: t('US (Silicon Valley)'), value: 'us-west-1' },
|
||||
{ label: t('US (Virginia)'), value: 'us-east-1' },
|
||||
{ label: t('Germany (Frankfurt)'), value: 'eu-central-1' },
|
||||
{ label: t('UK (London)'), value: 'eu-west-1' },
|
||||
{ label: t('Philippines (Manila)'), value: 'ap-southeast-6' },
|
||||
{ label: t('Thailand (Bangkok)'), value: 'ap-southeast-7' },
|
||||
{ label: t('South Korea (Seoul)'), value: 'ap-northeast-2' }
|
||||
]
|
||||
}
|
||||
];
|
||||
@ -97,7 +98,7 @@ export default {
|
||||
searchField: 'title',
|
||||
columns: [
|
||||
{
|
||||
label: '服务器',
|
||||
label: t('Server'),
|
||||
fieldname: 'name',
|
||||
width: 1.5,
|
||||
class: 'font-medium',
|
||||
@ -106,83 +107,82 @@ export default {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
label: t('Status'),
|
||||
fieldname: 'status',
|
||||
type: 'Badge',
|
||||
width: 0.8,
|
||||
format(value) {
|
||||
const statusMap = {
|
||||
'Pending': '准备中',
|
||||
'Starting': '启动中',
|
||||
'Running': '运行中',
|
||||
'Stopping': '停止中',
|
||||
'Stopped': '已停止',
|
||||
'Resetting': '重置中',
|
||||
'Upgrading': '升级中',
|
||||
'Disabled': '已禁用'
|
||||
'Pending': t('Pending'),
|
||||
'Starting': t('Starting'),
|
||||
'Running': t('Running'),
|
||||
'Stopping': t('Stopping'),
|
||||
'Stopped': t('Stopped'),
|
||||
'Resetting': t('Resetting'),
|
||||
'Upgrading': t('Upgrading'),
|
||||
'Disabled': t('Disabled')
|
||||
};
|
||||
return statusMap[value] || value;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '配置',
|
||||
label: t('Configuration'),
|
||||
fieldname: 'cpu',
|
||||
format(value, row) {
|
||||
const cpu = row.cpu || '未知';
|
||||
const memory = row.memory || '未知';
|
||||
const disk = row.disk_size || '未知';
|
||||
return `${cpu}核/${memory}GB/${disk}GB`;
|
||||
const cpu = row.cpu || t('Unknown');
|
||||
const memory = row.memory || t('Unknown');
|
||||
const disk = row.disk_size || t('Unknown');
|
||||
return `${cpu}${t('cores')}/${memory}GB/${disk}GB`;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '公网IP',
|
||||
label: t('Public IP'),
|
||||
fieldname: 'public_ip',
|
||||
format(value) {
|
||||
return value || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '区域',
|
||||
label: t('Region'),
|
||||
fieldname: 'region',
|
||||
format(value) {
|
||||
if (!value) return '-';
|
||||
|
||||
// 区域ID到中文名称的映射表
|
||||
// Region ID to name mapping
|
||||
const regionMap = {
|
||||
'cn-qingdao': '华北1(青岛)',
|
||||
'cn-beijing': '华北2(北京)',
|
||||
'cn-zhangjiakou': '华北3(张家口)',
|
||||
'cn-huhehaote': '华北5(呼和浩特)',
|
||||
'cn-hangzhou': '华东1(杭州)',
|
||||
'cn-shanghai': '华东2(上海)',
|
||||
'cn-shenzhen': '华南1(深圳)',
|
||||
'cn-heyuan': '华南2(河源)',
|
||||
'cn-chengdu': '西南1(成都)',
|
||||
'cn-guangzhou': '华南3(广州)',
|
||||
'cn-wulanchabu': '华北6(乌兰察布)',
|
||||
'cn-nanjing': '华东5(南京)',
|
||||
'cn-fuzhou': '华东6(福州)',
|
||||
'cn-wuhan-lr': '华中1(武汉)',
|
||||
'cn-hongkong': '中国香港',
|
||||
'ap-southeast-1': '新加坡',
|
||||
'ap-southeast-3': '马来西亚(吉隆坡)',
|
||||
'ap-southeast-5': '印度尼西亚(雅加达)',
|
||||
'ap-northeast-1': '日本(东京)',
|
||||
'us-west-1': '美国(硅谷)',
|
||||
'us-east-1': '美国(弗吉尼亚)',
|
||||
'eu-central-1': '德国(法兰克福)',
|
||||
'eu-west-1': '英国(伦敦)',
|
||||
'ap-southeast-6': '菲律宾(马尼拉)',
|
||||
'ap-southeast-7': '泰国(曼谷)',
|
||||
'ap-northeast-2': '韩国(首尔)'
|
||||
'cn-qingdao': t('North China 1 (Qingdao)'),
|
||||
'cn-beijing': t('North China 2 (Beijing)'),
|
||||
'cn-zhangjiakou': t('North China 3 (Zhangjiakou)'),
|
||||
'cn-huhehaote': t('North China 5 (Hohhot)'),
|
||||
'cn-hangzhou': t('East China 1 (Hangzhou)'),
|
||||
'cn-shanghai': t('East China 2 (Shanghai)'),
|
||||
'cn-shenzhen': t('South China 1 (Shenzhen)'),
|
||||
'cn-heyuan': t('South China 2 (Heyuan)'),
|
||||
'cn-chengdu': t('Southwest China 1 (Chengdu)'),
|
||||
'cn-guangzhou': t('South China 3 (Guangzhou)'),
|
||||
'cn-wulanchabu': t('North China 6 (Ulanqab)'),
|
||||
'cn-nanjing': t('East China 5 (Nanjing)'),
|
||||
'cn-fuzhou': t('East China 6 (Fuzhou)'),
|
||||
'cn-wuhan-lr': t('Central China 1 (Wuhan)'),
|
||||
'cn-hongkong': t('Hong Kong'),
|
||||
'ap-southeast-1': t('Singapore'),
|
||||
'ap-southeast-3': t('Malaysia (Kuala Lumpur)'),
|
||||
'ap-southeast-5': t('Indonesia (Jakarta)'),
|
||||
'ap-northeast-1': t('Japan (Tokyo)'),
|
||||
'us-west-1': t('US (Silicon Valley)'),
|
||||
'us-east-1': t('US (Virginia)'),
|
||||
'eu-central-1': t('Germany (Frankfurt)'),
|
||||
'eu-west-1': t('UK (London)'),
|
||||
'ap-southeast-6': t('Philippines (Manila)'),
|
||||
'ap-southeast-7': t('Thailand (Bangkok)'),
|
||||
'ap-northeast-2': t('South Korea (Seoul)')
|
||||
};
|
||||
|
||||
// 如果是RegionId,返回对应的中文名称;如果已经是中文名称,直接返回
|
||||
return regionMap[value] || value;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '到期时间',
|
||||
label: t('Expiry Date'),
|
||||
fieldname: 'end_date',
|
||||
format(value) {
|
||||
if (!value) return '-';
|
||||
@ -192,7 +192,7 @@ export default {
|
||||
],
|
||||
primaryAction({ listResource: jsiteServers }) {
|
||||
return {
|
||||
label: '新建服务器',
|
||||
label: t('New Server'),
|
||||
variant: 'solid',
|
||||
slots: {
|
||||
prefix: icon('plus')
|
||||
@ -205,14 +205,14 @@ export default {
|
||||
statusBadge({ documentResource: jsiteServer }) {
|
||||
const status = jsiteServer.pg?.status;
|
||||
const statusMap = {
|
||||
'Pending': '准备中',
|
||||
'Starting': '启动中',
|
||||
'Running': '运行中',
|
||||
'Stopping': '停止中',
|
||||
'Stopped': '已停止',
|
||||
'Resetting': '重置中',
|
||||
'Upgrading': '升级中',
|
||||
'Disabled': '已禁用'
|
||||
'Pending': t('Pending'),
|
||||
'Starting': t('Starting'),
|
||||
'Running': t('Running'),
|
||||
'Stopping': t('Stopping'),
|
||||
'Stopped': t('Stopped'),
|
||||
'Resetting': t('Resetting'),
|
||||
'Upgrading': t('Upgrading'),
|
||||
'Disabled': t('Disabled')
|
||||
};
|
||||
return {
|
||||
label: statusMap[status] || status
|
||||
@ -221,7 +221,7 @@ export default {
|
||||
breadcrumbs({ documentResource: jsiteServer }) {
|
||||
return [
|
||||
{
|
||||
label: '服务器',
|
||||
label: t('Servers'),
|
||||
route: '/jsite-servers'
|
||||
},
|
||||
{
|
||||
@ -235,7 +235,7 @@ export default {
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: '重启',
|
||||
label: t('Restart'),
|
||||
icon: 'refresh-cw',
|
||||
onClick() {
|
||||
jsiteServer.reboot.submit();
|
||||
@ -243,14 +243,14 @@ export default {
|
||||
condition: () => jsiteServer.pg?.status === 'Running'
|
||||
},
|
||||
{
|
||||
label: '重命名',
|
||||
label: t('Rename'),
|
||||
icon: 'edit-3',
|
||||
onClick() {
|
||||
jsiteServer.rename.submit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
label: t('Delete'),
|
||||
icon: 'trash-2',
|
||||
onClick() {
|
||||
jsiteServer.dropServer.submit();
|
||||
@ -263,10 +263,10 @@ export default {
|
||||
},
|
||||
detail: {
|
||||
route: '/jsite-servers/:name',
|
||||
title: '服务器详细信息',
|
||||
title: t('Server Details'),
|
||||
tabs: [
|
||||
{
|
||||
label: '概览',
|
||||
label: t('Overview'),
|
||||
route: '',
|
||||
type: 'Component',
|
||||
component: defineAsyncComponent(() => import('../components/JsiteServerOverview.vue')),
|
||||
@ -275,7 +275,7 @@ export default {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '防火墙',
|
||||
label: t('Firewall'),
|
||||
route: 'firewall',
|
||||
type: 'Component',
|
||||
component: defineAsyncComponent(() => import('../components/JsiteServerFirewallRules.vue')),
|
||||
@ -286,7 +286,7 @@ export default {
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: '基本信息',
|
||||
label: t('Basic Information'),
|
||||
fields: [
|
||||
'title',
|
||||
'status',
|
||||
@ -296,7 +296,7 @@ export default {
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '服务器配置',
|
||||
label: t('Server Configuration'),
|
||||
fields: [
|
||||
'cpu',
|
||||
'memory',
|
||||
@ -309,7 +309,7 @@ export default {
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'SSH连接',
|
||||
label: t('SSH Connection'),
|
||||
fields: [
|
||||
'ssh_user',
|
||||
'ssh_port',
|
||||
@ -319,7 +319,7 @@ export default {
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '其他信息',
|
||||
label: t('Other Information'),
|
||||
fields: [
|
||||
'end_date',
|
||||
'period'
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
<Header>
|
||||
<Breadcrumbs
|
||||
:items="[
|
||||
{ label: '服务器', route: '/jsite-servers' },
|
||||
{ label: '新建服务器', route: '/jsite-servers/new' }
|
||||
{ label: $t('Servers'), route: '/jsite-servers' },
|
||||
{ label: $t('New Server'), route: '/jsite-servers/new' }
|
||||
]"
|
||||
/>
|
||||
</Header>
|
||||
@ -17,17 +17,17 @@
|
||||
<!-- 第一步:选择服务器配置和支付方式 -->
|
||||
<div v-if="!showPaymentProcessing" class="space-y-12 pb-[50vh] pt-12">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">新建服务器</h1>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">{{ $t('New Server') }}</h1>
|
||||
<p class="text-sm text-gray-600">
|
||||
请选择服务器配置和支付方式,点击"创建"后将自动为您开通。
|
||||
{{ $t('Please select server configuration and payment method. Click "Create" to automatically provision the server.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">区域选择</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ $t('Region Selection') }}</label>
|
||||
<select v-model="selectedRegionId" class="w-full border rounded px-3 py-2" required @change="onRegionChange">
|
||||
<option value="">请选择区域</option>
|
||||
<option value="">{{ $t('Please select a region') }}</option>
|
||||
<option v-for="region in regions" :key="getRegionId(region)" :value="getRegionId(region)">
|
||||
{{ getRegionName(region) }}
|
||||
</option>
|
||||
@ -35,9 +35,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">镜像选择(Jsite服务器推荐选择Ubuntu-22.04)</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ $t('Image Selection (Jsite servers recommend Ubuntu-22.04)') }}</label>
|
||||
<select v-model="selectedImageId" class="w-full border rounded px-3 py-2" required :disabled="!selectedRegionId" @change="onImageChange">
|
||||
<option value="">请选择镜像</option>
|
||||
<option value="">{{ $t('Please select an image') }}</option>
|
||||
<option v-for="image in images" :key="getImageId(image)" :value="getImageId(image)">
|
||||
{{ getImageDisplayName(image) }}
|
||||
</option>
|
||||
@ -45,9 +45,9 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">套餐选择</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ $t('Plan Selection') }}</label>
|
||||
<select v-model="selectedPlanId" class="w-full border rounded px-3 py-2" required :disabled="!selectedImageId">
|
||||
<option value="">请选择套餐</option>
|
||||
<option value="">{{ $t('Please select a plan') }}</option>
|
||||
<option v-for="plan in filteredPlans" :key="plan.plan_id" :value="plan.plan_id">
|
||||
{{ getPlanDisplayName(plan) }}
|
||||
</option>
|
||||
@ -55,7 +55,7 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">购买时长</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{{ $t('Purchase Duration') }}</label>
|
||||
<select v-model="period" class="w-full border rounded px-3 py-2">
|
||||
<option v-for="p in periods" :key="p.value" :value="p.value">{{ p.label }}</option>
|
||||
</select>
|
||||
@ -64,18 +64,18 @@
|
||||
<!-- 价格信息显示 -->
|
||||
<div v-if="selectedPlanId && selectedImageId" class="border-t border-gray-200 pt-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-sm text-gray-600">月度费用</div>
|
||||
<div class="text-sm text-gray-600">{{ $t('Monthly Fee') }}</div>
|
||||
<div class="font-medium">
|
||||
¥ {{ getSelectedPlanPrice() }}
|
||||
<span class="text-gray-500 text-sm">(月付)</span>
|
||||
<span class="text-gray-500 text-sm">({{ $t('Monthly') }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mt-2">
|
||||
<div class="text-sm text-gray-600">购买时长</div>
|
||||
<div class="font-medium">{{ period }} 个月</div>
|
||||
<div class="text-sm text-gray-600">{{ $t('Purchase Duration') }}</div>
|
||||
<div class="font-medium">{{ period }} {{ $t('months') }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mt-2 text-lg font-bold">
|
||||
<div>总计</div>
|
||||
<div>{{ $t('Total') }}</div>
|
||||
<div>¥ {{ getTotalAmount() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -83,7 +83,7 @@
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">
|
||||
选择支付方式
|
||||
{{ $t('Select Payment Method') }}
|
||||
</label>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
|
||||
<button
|
||||
@ -91,7 +91,7 @@
|
||||
:class="{'border-blue-500 border-2 shadow-sm': selectedPaymentMethod === 'balance', 'border-gray-200': selectedPaymentMethod !== 'balance'}"
|
||||
@click="selectedPaymentMethod = 'balance'"
|
||||
>
|
||||
<span class="text-gray-800 font-medium">余额支付</span>
|
||||
<span class="text-gray-800 font-medium">{{ $t('Balance Payment') }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@ -132,7 +132,7 @@
|
||||
@click="createInstance"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
{{ isLoading ? '处理中...' : '创建' }}
|
||||
{{ isLoading ? $t('Processing...') : $t('Create') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -145,7 +145,7 @@
|
||||
<div class="h-12 w-12 mx-auto animate-spin text-blue-600 mb-4 flex items-center justify-center">
|
||||
<i class="fe fe-loader text-3xl"></i>
|
||||
</div>
|
||||
<p class="text-gray-700 text-lg">正在处理支付,请稍候...</p>
|
||||
<p class="text-gray-700 text-lg">{{ $t('Processing payment, please wait...') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 支付成功状态 -->
|
||||
@ -157,12 +157,12 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-xl font-medium text-gray-900 mb-2">支付成功!</h3>
|
||||
<h3 class="text-xl font-medium text-gray-900 mb-2">{{ $t('Payment Successful!') }}</h3>
|
||||
<p class="text-gray-600 mb-4">
|
||||
您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。
|
||||
{{ $t('Your order has been paid successfully and the server record has been created. The server is being created in the background. Creation and startup will take 3-5 minutes, please be patient.') }}
|
||||
</p>
|
||||
<p class="text-gray-500 text-sm">
|
||||
您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"运行中"。
|
||||
{{ $t('You can check the server status in the server list. After creation is complete, the status will be updated to "Running".') }}
|
||||
</p>
|
||||
<div class="mt-6">
|
||||
<button
|
||||
@ -170,7 +170,7 @@
|
||||
class="px-4 py-2 bg-[#1fc76f] border border-transparent rounded-md text-sm font-medium text-white hover:bg-[#19b862] focus:outline-none"
|
||||
@click="goToServerList"
|
||||
>
|
||||
返回服务器列表
|
||||
{{ $t('Back to Server List') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -184,8 +184,8 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center mb-6">
|
||||
<div class="text-sm text-gray-600 mb-2">扫一扫付款(元)</div>
|
||||
<div class="text-3xl font-bold text-[#FF0036]">{{ order?.total_amount }} 元</div>
|
||||
<div class="text-sm text-gray-600 mb-2">{{ $t('Scan to Pay (CNY)') }}</div>
|
||||
<div class="text-3xl font-bold text-[#FF0036]">{{ order?.total_amount }} {{ $t('CNY') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
@ -200,9 +200,9 @@
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-gray-600">
|
||||
请使用微信扫描二维码完成支付
|
||||
{{ $t('Please scan the QR code with WeChat to complete payment') }}
|
||||
</p>
|
||||
<p class="text-gray-500 text-sm mt-2">二维码有效期 15 分钟</p>
|
||||
<p class="text-gray-500 text-sm mt-2">{{ $t('QR code valid for 15 minutes') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -212,19 +212,19 @@
|
||||
<AlipayLogo class="h-10 mx-auto" />
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">请在新页面完成支付宝支付</h3>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">{{ $t('Please complete Alipay payment in the new page') }}</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
如果没有自动跳转,请点击下方按钮打开支付页面
|
||||
{{ $t('If it does not automatically redirect, please click the button below to open the payment page') }}
|
||||
</p>
|
||||
<div class="space-y-4">
|
||||
<button
|
||||
class="w-full px-6 py-3 bg-[#1677FF] text-white rounded-lg hover:bg-[#0E5FD8] transition-colors shadow-sm"
|
||||
@click="window.open(paymentUrl, '_blank')"
|
||||
>
|
||||
打开支付页面
|
||||
{{ $t('Open Payment Page') }}
|
||||
</button>
|
||||
<p class="text-gray-600 text-sm mt-4">
|
||||
支付完成后,请稍等片刻,系统会自动刷新页面
|
||||
{{ $t('After payment is completed, please wait a moment, the system will automatically refresh the page') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -260,14 +260,7 @@ export default {
|
||||
regions: [],
|
||||
plans: [],
|
||||
images: [],
|
||||
periods: [
|
||||
{ value: 1, label: '1个月' },
|
||||
{ value: 3, label: '3个月' },
|
||||
{ value: 6, label: '6个月' },
|
||||
{ value: 12, label: '1年' },
|
||||
{ value: 24, label: '2年' },
|
||||
{ value: 36, label: '3年' }
|
||||
],
|
||||
periods: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
success: false,
|
||||
@ -314,11 +307,11 @@ export default {
|
||||
if (Array.isArray(regionsData) && regionsData.length > 0) {
|
||||
this.regions = regionsData;
|
||||
} else {
|
||||
this.error = '区域数据格式不正确或为空';
|
||||
this.error = this.$t('Region data format is incorrect or empty');
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '获取区域失败';
|
||||
this.error = error.message || this.$t('Failed to get regions');
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -330,7 +323,7 @@ export default {
|
||||
this.plans = data.plans || [];
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '获取套餐失败';
|
||||
this.error = error.message || this.$t('Failed to get plans');
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -363,11 +356,11 @@ export default {
|
||||
if (Array.isArray(imagesData) && imagesData.length > 0) {
|
||||
this.images = imagesData;
|
||||
} else {
|
||||
this.error = '镜像数据格式不正确或为空';
|
||||
this.error = this.$t('Image data format is incorrect or empty');
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '获取镜像失败';
|
||||
this.error = error.message || this.$t('Failed to get images');
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -377,15 +370,15 @@ export default {
|
||||
url: 'jcloud.api.aliyun_server_light.create_server_order',
|
||||
validate() {
|
||||
if (!this.selectedRegionId || !this.selectedPlanId || !this.selectedImageId) {
|
||||
throw new DashboardError('请选择完整配置');
|
||||
throw new DashboardError(this.$t('Please select complete configuration'));
|
||||
}
|
||||
if (!this.selectedPaymentMethod) {
|
||||
throw new DashboardError('请选择支付方式');
|
||||
throw new DashboardError(this.$t('Please select payment method'));
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
if (!data.success) {
|
||||
this.error = data.message || '创建服务器订单失败';
|
||||
this.error = data.message || this.$t('Failed to create server order');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -398,7 +391,7 @@ export default {
|
||||
this.processPayment();
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '创建服务器订单失败';
|
||||
this.error = error.message || this.$t('Failed to create server order');
|
||||
}
|
||||
};
|
||||
},
|
||||
@ -408,13 +401,13 @@ export default {
|
||||
url: 'jcloud.api.billing.process_balance_payment_for_server_order',
|
||||
validate() {
|
||||
if (!this.order || !this.order.order_id) {
|
||||
throw new DashboardError('缺少订单信息');
|
||||
throw new DashboardError(this.$t('Missing order information'));
|
||||
}
|
||||
},
|
||||
onSuccess(response) {
|
||||
if (response.status === "Error" || response.success === false) {
|
||||
toast.error(response.message || '支付失败,请确保余额充足');
|
||||
this.error = response.message || '余额不足';
|
||||
toast.error(response.message || this.$t('Payment failed, please ensure sufficient balance'));
|
||||
this.error = response.message || this.$t('Insufficient balance');
|
||||
this.isProcessingPayment = false;
|
||||
return;
|
||||
}
|
||||
@ -439,7 +432,7 @@ export default {
|
||||
}, 30000);
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '余额支付处理失败';
|
||||
this.error = error.message || this.$t('Balance payment processing failed');
|
||||
this.isProcessingPayment = false;
|
||||
}
|
||||
};
|
||||
@ -450,20 +443,20 @@ export default {
|
||||
url: 'jcloud.api.billing.process_alipay_order',
|
||||
validate() {
|
||||
if (!this.order || !this.order.order_id) {
|
||||
throw new DashboardError('缺少订单信息');
|
||||
throw new DashboardError(this.$t('Missing order information'));
|
||||
}
|
||||
},
|
||||
onSuccess(response) {
|
||||
this.paymentUrl = response.payment_url;
|
||||
window.open(response.payment_url, '_blank');
|
||||
|
||||
toast.success('支付页面已在新窗口打开');
|
||||
toast.success(this.$t('Payment page opened in new window'));
|
||||
|
||||
// 开始轮询支付状态
|
||||
this.startPaymentCheck();
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '支付宝支付处理失败';
|
||||
this.error = error.message || this.$t('Alipay payment processing failed');
|
||||
this.isProcessingPayment = false;
|
||||
}
|
||||
};
|
||||
@ -474,7 +467,7 @@ export default {
|
||||
url: 'jcloud.api.billing.process_wechatpay_order',
|
||||
validate() {
|
||||
if (!this.order || !this.order.order_id) {
|
||||
throw new DashboardError('缺少订单信息');
|
||||
throw new DashboardError(this.$t('Missing order information'));
|
||||
}
|
||||
},
|
||||
onSuccess(response) {
|
||||
@ -485,7 +478,7 @@ export default {
|
||||
this.startPaymentCheck();
|
||||
},
|
||||
onError(error) {
|
||||
this.error = error.message || '微信支付处理失败';
|
||||
this.error = error.message || this.$t('WeChat Pay payment processing failed');
|
||||
this.isProcessingPayment = false;
|
||||
}
|
||||
};
|
||||
@ -499,7 +492,7 @@ export default {
|
||||
},
|
||||
validate() {
|
||||
if (!this.order || !this.order.order_id) {
|
||||
throw new DashboardError('缺少订单信息');
|
||||
throw new DashboardError(this.$t('Missing order information'));
|
||||
}
|
||||
},
|
||||
onSuccess(data) {
|
||||
@ -565,6 +558,15 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.$resources.aliyunRegions.submit();
|
||||
// Initialize periods with translations
|
||||
this.periods = [
|
||||
{ value: 1, label: this.$t('1 month') },
|
||||
{ value: 3, label: this.$t('3 months') },
|
||||
{ value: 6, label: this.$t('6 months') },
|
||||
{ value: 12, label: this.$t('1 year') },
|
||||
{ value: 24, label: this.$t('2 years') },
|
||||
{ value: 36, label: this.$t('3 years') }
|
||||
];
|
||||
},
|
||||
methods: {
|
||||
async onRegionChange() {
|
||||
@ -592,11 +594,11 @@ export default {
|
||||
},
|
||||
async createInstance() {
|
||||
if (!this.selectedRegionId || !this.selectedPlanId || !this.selectedImageId) {
|
||||
this.error = '请选择完整配置';
|
||||
this.error = this.$t('Please select complete configuration');
|
||||
return;
|
||||
}
|
||||
if (!this.selectedPaymentMethod) {
|
||||
this.error = '请选择支付方式';
|
||||
this.error = this.$t('Please select payment method');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -637,7 +639,7 @@ export default {
|
||||
this.error = null;
|
||||
|
||||
if (!this.order || !this.order.order_id) {
|
||||
this.error = '订单信息不完整,请重试';
|
||||
this.error = this.$t('Order information is incomplete, please try again');
|
||||
this.isProcessingPayment = false;
|
||||
return;
|
||||
}
|
||||
@ -694,22 +696,22 @@ export default {
|
||||
let typePrefix = '';
|
||||
switch (planType) {
|
||||
case 'NORMAL':
|
||||
typePrefix = '通用型 - ';
|
||||
typePrefix = this.$t('General Type - ');
|
||||
break;
|
||||
case 'MULTI_IP':
|
||||
typePrefix = '多公网IP型 - ';
|
||||
typePrefix = this.$t('Multi Public IP Type - ');
|
||||
break;
|
||||
case 'INTERNATIONAL':
|
||||
typePrefix = '国际型 - ';
|
||||
typePrefix = this.$t('International Type - ');
|
||||
break;
|
||||
case 'CAPACITY':
|
||||
typePrefix = '容量型 - ';
|
||||
typePrefix = this.$t('Capacity Type - ');
|
||||
break;
|
||||
default:
|
||||
typePrefix = '';
|
||||
}
|
||||
|
||||
return `${typePrefix}${cpu}核/${memoryDisplay}/${disk}GB/${bandwidthDisplay}/${publicIpNum}个公网IP - ¥${price}/月`;
|
||||
return `${typePrefix}${cpu}${this.$t('cores')}/${memoryDisplay}/${disk}GB/${bandwidthDisplay}/${publicIpNum}${this.$t('public IPs')} - ¥${price}/${this.$t('month')}`;
|
||||
},
|
||||
getSelectedPlanPrice() {
|
||||
const selectedPlan = this.filteredPlans.find(plan => plan.plan_id === this.selectedPlanId);
|
||||
@ -728,7 +730,7 @@ export default {
|
||||
},
|
||||
getImageDisplayName(image) {
|
||||
// 根据阿里云API返回的数据结构显示镜像信息
|
||||
const name = image.image_name || image.name || image.ImageName || image.Name || '未知镜像';
|
||||
const name = image.image_name || image.name || image.ImageName || image.Name || this.$t('Unknown image');
|
||||
const platform = image.platform || '';
|
||||
const description = image.description || '';
|
||||
|
||||
@ -740,11 +742,11 @@ export default {
|
||||
},
|
||||
getPaymentMethodName(method) {
|
||||
const methodNames = {
|
||||
'balance': '余额支付',
|
||||
'alipay': '支付宝',
|
||||
'wechatpay': '微信支付'
|
||||
'balance': this.$t('Balance Payment'),
|
||||
'alipay': this.$t('Alipay'),
|
||||
'wechatpay': this.$t('WeChat Pay')
|
||||
};
|
||||
return methodNames[method] || '未知方式';
|
||||
return methodNames[method] || this.$t('Unknown method');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user