729 lines
23 KiB
Vue
729 lines
23 KiB
Vue
<template>
|
||
<div
|
||
v-if="$jsiteServer?.pg"
|
||
class="grid grid-cols-1 items-start gap-5 lg:grid-cols-2"
|
||
>
|
||
<!-- 左侧区块:基本信息 -->
|
||
<div class="col-span-1 space-y-5">
|
||
<!-- 当前套餐卡片 -->
|
||
<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>
|
||
</div>
|
||
<div class="p-5">
|
||
<div class="flex h-full flex-col sm:flex-row sm:items-center sm:justify-between">
|
||
<div class="mb-4 sm:mb-0">
|
||
<div v-if="$jsiteServer.pg.plan_price" class="text-lg font-bold text-green-600">
|
||
¥{{ $jsiteServer.pg.plan_price }}/月
|
||
</div>
|
||
|
||
<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) }}
|
||
</div>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<Button
|
||
@click="renewServer"
|
||
:loading="$jsiteServer.renew?.loading"
|
||
class="px-5 !bg-[#1fc76f] !hover:bg-[#1bb85f] !text-white"
|
||
>
|
||
续费
|
||
</Button>
|
||
<Button
|
||
@click="upgradeServer"
|
||
:loading="upgradeLoading"
|
||
class="px-5 !bg-[#3b82f6] !hover:bg-[#2563eb] !text-white"
|
||
>
|
||
升级
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 服务器配置 -->
|
||
<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>
|
||
</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>
|
||
</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>
|
||
</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>
|
||
</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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 服务器信息 -->
|
||
<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>
|
||
</div>
|
||
<div>
|
||
<div
|
||
v-for="info in serverInformation"
|
||
:key="info.label"
|
||
class="flex items-center px-5 py-3 last:pb-5 even:bg-gray-50/70"
|
||
>
|
||
<div class="w-1/3 text-base text-gray-600">{{ info.label }}</div>
|
||
<div
|
||
class="flex w-2/3 items-center space-x-2 text-base text-gray-900"
|
||
>
|
||
<div v-if="info.prefix">
|
||
<component :is="info.prefix" />
|
||
</div>
|
||
<!-- 状态字段使用Badge组件 -->
|
||
<div v-if="info.label === '状态'">
|
||
<Badge :label="info.value" />
|
||
</div>
|
||
<!-- 服务器名称特殊处理 - 支持内联编辑 -->
|
||
<div v-else-if="info.label === '服务器名称'" 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="'点击编辑服务器名称'"
|
||
>
|
||
<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">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"></path>
|
||
</svg>
|
||
</div>
|
||
<div v-else class="flex items-center space-x-2">
|
||
<input
|
||
ref="serverNameInput"
|
||
v-model="editServerNameValue"
|
||
@keyup.enter="saveServerName"
|
||
@keyup.escape="cancelEditServerName"
|
||
@blur="handleInputBlur"
|
||
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="请输入服务器名称"
|
||
/>
|
||
<button
|
||
@click="saveServerName"
|
||
:disabled="saveServerNameLoading"
|
||
class="p-1 text-green-600 hover:text-green-700 disabled:opacity-50 transition-colors duration-200"
|
||
title="保存"
|
||
>
|
||
<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>
|
||
</svg>
|
||
<svg v-else class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||
<path class="opacity-75" fill="currentColor" d="m12 6a6 6 0 0 1 6 6h4a10 10 0 0 0-10-10v4z"></path>
|
||
</svg>
|
||
</button>
|
||
<button
|
||
@click="cancelEditServerName"
|
||
:disabled="saveServerNameLoading"
|
||
class="p-1 text-gray-500 hover:text-gray-700 disabled:opacity-50 transition-colors duration-200"
|
||
title="取消"
|
||
>
|
||
<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>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- 其他信息正常显示 -->
|
||
<span v-else>
|
||
{{ info.value }}
|
||
</span>
|
||
<div v-if="info.suffix">
|
||
<component :is="info.suffix" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧区块:配置信息和操作 -->
|
||
<div class="col-span-1 space-y-5">
|
||
<!-- 操作 -->
|
||
<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>
|
||
</div>
|
||
<div class="p-5">
|
||
<div class="flex flex-wrap gap-2">
|
||
<Button
|
||
@click="restartServer"
|
||
:loading="restartLoading"
|
||
variant="outline"
|
||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||
>
|
||
重启
|
||
</Button>
|
||
<Button
|
||
@click="resetPassword"
|
||
:loading="resetPasswordLoading"
|
||
variant="outline"
|
||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||
>
|
||
重置密码
|
||
</Button>
|
||
<Button
|
||
@click="resetKeyPair"
|
||
:loading="resetKeyPairLoading"
|
||
variant="outline"
|
||
class="bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||
>
|
||
重置密钥对
|
||
</Button>
|
||
<Button
|
||
@click="deleteKeyPair"
|
||
:loading="deleteKeyPairLoading"
|
||
variant="outline"
|
||
class="bg-red-50 text-red-700 hover:bg-red-100 border-red-200"
|
||
>
|
||
删除密钥对
|
||
</Button>
|
||
<Button
|
||
@click="resetSystem"
|
||
:loading="resetSystemLoading"
|
||
variant="outline"
|
||
class="bg-red-50 text-red-700 hover:bg-red-100 border-red-200"
|
||
>
|
||
重置系统
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
</div>
|
||
<div class="p-5">
|
||
<div class="space-y-3">
|
||
<div class="flex justify-between">
|
||
<span class="text-gray-600">SSH端口:</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="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>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="font-mono text-gray-900">
|
||
{{ showPassword ? decryptedPassword : ($jsiteServer.pg.password || '未设置') }}
|
||
</span>
|
||
<button
|
||
v-if="$jsiteServer.pg.password"
|
||
@click="togglePassword"
|
||
class="text-gray-500 hover:text-gray-700 transition-colors"
|
||
:title="showPassword ? '隐藏密码' : '显示密码'"
|
||
>
|
||
<EyeIcon v-if="!showPassword" class="h-4 w-4" />
|
||
<EyeOffIcon v-else class="h-4 w-4" />
|
||
</button>
|
||
</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>
|
||
</div>
|
||
<div class="flex flex-col">
|
||
<span class="text-gray-600 mb-2">私钥:</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 ? '已复制' : '复制私钥'"
|
||
>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getCachedDocumentResource, Badge, Tooltip, Button, createResource } from 'jingrow-ui';
|
||
import { h, defineAsyncComponent } from 'vue';
|
||
import { toast } from 'vue-sonner';
|
||
import { getToastErrorMessage } from '../utils/toast';
|
||
import { renderDialog, confirmDialog } from '../utils/components';
|
||
import EyeIcon from '~icons/lucide/eye';
|
||
import EyeOffIcon from '~icons/lucide/eye-off';
|
||
import ClockIcon from '~icons/lucide/clock';
|
||
import InfoIcon from '~icons/lucide/info';
|
||
import CopyIcon from '~icons/lucide/copy';
|
||
|
||
export default {
|
||
name: 'JsiteServerOverview',
|
||
props: ['server'],
|
||
components: {
|
||
Badge,
|
||
Button,
|
||
EyeIcon,
|
||
EyeOffIcon,
|
||
ClockIcon,
|
||
InfoIcon,
|
||
CopyIcon,
|
||
},
|
||
data() {
|
||
return {
|
||
showPassword: false,
|
||
decryptedPassword: null,
|
||
restartLoading: false,
|
||
resetPasswordLoading: false,
|
||
resetKeyPairLoading: false,
|
||
deleteKeyPairLoading: false,
|
||
resetSystemLoading: false,
|
||
upgradeLoading: false,
|
||
copySuccess: false,
|
||
editingServerName: false,
|
||
editServerNameValue: '',
|
||
saveServerNameLoading: false,
|
||
};
|
||
},
|
||
methods: {
|
||
getStatusText(status) {
|
||
const statusMap = {
|
||
'Pending': '准备中',
|
||
'Starting': '启动中',
|
||
'Running': '运行中',
|
||
'Stopping': '停止中',
|
||
'Stopped': '已停止',
|
||
'Resetting': '重置中',
|
||
'Upgrading': '升级中',
|
||
'Disabled': '已禁用'
|
||
};
|
||
return statusMap[status] || status;
|
||
},
|
||
getStatusVariant(status) {
|
||
const variantMap = {
|
||
'Pending': 'warning',
|
||
'Starting': 'warning',
|
||
'Running': 'success',
|
||
'Stopping': 'warning',
|
||
'Stopped': 'danger',
|
||
'Resetting': 'warning',
|
||
'Upgrading': 'warning',
|
||
'Disabled': 'danger'
|
||
};
|
||
return variantMap[status] || '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': '韩国(首尔)'
|
||
};
|
||
return regionMap[region] || region;
|
||
},
|
||
renewServer() {
|
||
const JsiteServerRenewalDialog = defineAsyncComponent(() => import('./JsiteServerRenewalDialog.vue'));
|
||
|
||
renderDialog(h(JsiteServerRenewalDialog, {
|
||
server: this.server,
|
||
serverDoc: this.$jsiteServer.pg,
|
||
onSuccess: this.onRenewalSuccess
|
||
}));
|
||
},
|
||
upgradeServer() {
|
||
const JsiteServerUpgradeDialog = defineAsyncComponent(() => import('./JsiteServerUpgradeDialog.vue'));
|
||
|
||
renderDialog(h(JsiteServerUpgradeDialog, {
|
||
server: this.server,
|
||
serverDoc: this.$jsiteServer.pg,
|
||
onSuccess: this.onUpgradeSuccess
|
||
}));
|
||
},
|
||
async restartServer() {
|
||
if (!this.$jsiteServer.pg.instance_id) {
|
||
toast.error('服务器实例ID不存在');
|
||
return;
|
||
}
|
||
|
||
confirmDialog({
|
||
title: '重启',
|
||
message: '确定要重启服务器吗?重启过程中服务器将暂时不可用。',
|
||
primaryAction: {
|
||
label: '确定',
|
||
onClick: ({ hide }) => {
|
||
// 立即显示成功提示并关闭弹窗
|
||
toast.success('重启请求已提交');
|
||
hide();
|
||
|
||
// 异步提交请求
|
||
this.restartLoading = true;
|
||
const restartRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.reboot_aliyun_instance',
|
||
params: {
|
||
instance_id: this.$jsiteServer.pg.instance_id,
|
||
region_id: this.$jsiteServer.pg.region
|
||
},
|
||
onSuccess: () => {
|
||
this.restartLoading = false;
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.restartLoading = false;
|
||
}
|
||
});
|
||
restartRequest.submit();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
async resetPassword() {
|
||
if (!this.$jsiteServer.pg.instance_id) {
|
||
toast.error('服务器实例ID不存在');
|
||
return;
|
||
}
|
||
|
||
// 弹出密码输入对话框
|
||
const PasswordDialog = defineAsyncComponent(() => import('../dialogs/PasswordDialog.vue'));
|
||
|
||
renderDialog(h(PasswordDialog, {
|
||
title: '重置服务器密码',
|
||
description: '长度为 8 至 30 个字符,必须同时包含大小写英文字母、数字和特殊符号。',
|
||
onConfirm: (password) => {
|
||
// 立即显示成功提示
|
||
toast.success('密码重置请求已提交');
|
||
|
||
// 异步提交请求
|
||
this.resetPasswordLoading = true;
|
||
const resetPasswordRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.reset_aliyun_instance_password',
|
||
params: {
|
||
instance_id: this.$jsiteServer.pg.instance_id,
|
||
password: password,
|
||
region_id: this.$jsiteServer.pg.region
|
||
},
|
||
onSuccess: () => {
|
||
this.resetPasswordLoading = false;
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.resetPasswordLoading = false;
|
||
}
|
||
});
|
||
resetPasswordRequest.submit();
|
||
}
|
||
}));
|
||
},
|
||
async resetKeyPair() {
|
||
if (!this.$jsiteServer.pg.instance_id) {
|
||
toast.error('服务器实例ID不存在');
|
||
return;
|
||
}
|
||
|
||
confirmDialog({
|
||
title: '重置密钥对',
|
||
message: '确定要重置密钥对吗?这将删除旧的密钥对并创建新的密钥对。重置后需要使用新的私钥才能连接服务器。',
|
||
primaryAction: {
|
||
label: '确定',
|
||
onClick: ({ hide }) => {
|
||
// 立即显示成功提示并关闭弹窗
|
||
toast.success('密钥对重置请求已提交');
|
||
hide();
|
||
|
||
// 异步提交请求
|
||
this.resetKeyPairLoading = true;
|
||
const resetKeyPairRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.reset_server_key_pair',
|
||
params: {
|
||
instance_id: this.$jsiteServer.pg.instance_id
|
||
},
|
||
onSuccess: () => {
|
||
this.resetKeyPairLoading = false;
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.resetKeyPairLoading = false;
|
||
}
|
||
});
|
||
resetKeyPairRequest.submit();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
async deleteKeyPair() {
|
||
if (!this.$jsiteServer.pg.instance_id) {
|
||
toast.error('服务器实例ID不存在');
|
||
return;
|
||
}
|
||
|
||
confirmDialog({
|
||
title: '删除密钥对',
|
||
message: '确定要删除密钥对吗?删除后将无法使用私钥连接服务器,建议先设置服务器密码。',
|
||
primaryAction: {
|
||
label: '确定',
|
||
onClick: ({ hide }) => {
|
||
// 立即显示成功提示并关闭弹窗
|
||
toast.success('密钥对删除请求已提交');
|
||
hide();
|
||
|
||
// 异步提交请求
|
||
this.deleteKeyPairLoading = true;
|
||
const deleteKeyPairRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.delete_aliyun_instance_key_pair',
|
||
params: {
|
||
instance_id: this.$jsiteServer.pg.instance_id
|
||
},
|
||
onSuccess: () => {
|
||
this.deleteKeyPairLoading = false;
|
||
// 刷新服务器数据以更新界面
|
||
this.$jsiteServer.reload();
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.deleteKeyPairLoading = false;
|
||
}
|
||
});
|
||
deleteKeyPairRequest.submit();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
async resetSystem() {
|
||
if (!this.$jsiteServer.pg.instance_id) {
|
||
toast.error('服务器实例ID不存在');
|
||
return;
|
||
}
|
||
|
||
confirmDialog({
|
||
title: '重置系统',
|
||
message: '确定要重置系统吗?这将清除所有数据并重新安装系统,操作不可逆!',
|
||
primaryAction: {
|
||
label: '确定',
|
||
onClick: ({ hide }) => {
|
||
// 立即显示成功提示并关闭弹窗
|
||
toast.success('系统重置请求已提交');
|
||
hide();
|
||
|
||
// 异步提交请求
|
||
this.resetSystemLoading = true;
|
||
const resetSystemRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.reset_aliyun_instance_system',
|
||
params: {
|
||
instance_id: this.$jsiteServer.pg.instance_id,
|
||
region_id: this.$jsiteServer.pg.region
|
||
},
|
||
onSuccess: () => {
|
||
this.resetSystemLoading = false;
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.resetSystemLoading = false;
|
||
}
|
||
});
|
||
resetSystemRequest.submit();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
togglePassword() {
|
||
if (this.showPassword) {
|
||
this.showPassword = false;
|
||
this.decryptedPassword = null;
|
||
} else {
|
||
const getPasswordRequest = createResource({
|
||
url: '/api/action/jcloud.api.aliyun_server_light.get_password',
|
||
params: {
|
||
pagetype: 'Jsite Server',
|
||
name: this.$jsiteServer.pg.name,
|
||
fieldname: 'password'
|
||
},
|
||
onSuccess: (response) => {
|
||
if (response && response !== '') {
|
||
this.decryptedPassword = response;
|
||
this.showPassword = true;
|
||
} else {
|
||
toast.warning('当前没有保存的密码');
|
||
}
|
||
},
|
||
onError: (error) => {
|
||
toast.error('获取密码失败');
|
||
}
|
||
});
|
||
getPasswordRequest.submit();
|
||
}
|
||
},
|
||
copyPrivateKey() {
|
||
if (this.$jsiteServer.pg.private_key) {
|
||
navigator.clipboard.writeText(this.$jsiteServer.pg.private_key).then(() => {
|
||
this.copySuccess = true;
|
||
toast.success('私钥已复制到剪贴板');
|
||
setTimeout(() => {
|
||
this.copySuccess = false;
|
||
}, 2000);
|
||
}).catch(() => {
|
||
toast.error('复制失败,请手动复制');
|
||
});
|
||
}
|
||
},
|
||
onRenewalSuccess(data) {
|
||
toast.success('服务器续费成功!');
|
||
// 刷新服务器数据
|
||
this.$jsiteServer.reload();
|
||
},
|
||
onUpgradeSuccess(data) {
|
||
toast.success('服务器升级成功!');
|
||
// 刷新服务器数据
|
||
this.$jsiteServer.reload();
|
||
},
|
||
startEditServerName() {
|
||
this.editServerNameValue = this.$jsiteServer.pg?.title || this.$jsiteServer.pg?.name || '';
|
||
this.editingServerName = true;
|
||
this.$nextTick(() => {
|
||
const input = this.$refs.serverNameInput;
|
||
if (input && typeof input.focus === 'function') {
|
||
input.focus();
|
||
if (typeof input.select === 'function') {
|
||
input.select();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
cancelEditServerName() {
|
||
this.editingServerName = false;
|
||
this.editServerNameValue = '';
|
||
},
|
||
handleInputBlur() {
|
||
// 延迟处理,防止点击按钮时触发
|
||
setTimeout(() => {
|
||
if (this.editingServerName && !this.saveServerNameLoading) {
|
||
this.saveServerName();
|
||
}
|
||
}, 150);
|
||
},
|
||
async saveServerName() {
|
||
if (this.saveServerNameLoading) return;
|
||
|
||
const newName = this.editServerNameValue.trim();
|
||
if (!newName) {
|
||
toast.error('服务器名称不能为空');
|
||
return;
|
||
}
|
||
|
||
if (newName.length < 2) {
|
||
toast.error('服务器名称至少需要2个字符');
|
||
return;
|
||
}
|
||
|
||
if (newName.length > 100) {
|
||
toast.error('服务器名称不能超过100个字符');
|
||
return;
|
||
}
|
||
|
||
if (newName === (this.$jsiteServer.pg?.title || this.$jsiteServer.pg?.name)) {
|
||
this.cancelEditServerName();
|
||
return;
|
||
}
|
||
|
||
this.saveServerNameLoading = true;
|
||
|
||
try {
|
||
this.$jsiteServer.setValue.submit(
|
||
{ title: newName },
|
||
{
|
||
onSuccess: () => {
|
||
toast.success('服务器名称已更新');
|
||
this.editingServerName = false;
|
||
this.editServerNameValue = '';
|
||
this.saveServerNameLoading = false;
|
||
},
|
||
onError: (error) => {
|
||
toast.error(getToastErrorMessage(error));
|
||
this.saveServerNameLoading = false;
|
||
}
|
||
}
|
||
);
|
||
} catch (error) {
|
||
toast.error('更新失败,请重试');
|
||
this.saveServerNameLoading = false;
|
||
}
|
||
},
|
||
},
|
||
computed: {
|
||
serverInformation() {
|
||
return [
|
||
{
|
||
label: '状态',
|
||
value: this.getStatusText(this.$jsiteServer.pg?.status),
|
||
},
|
||
{
|
||
label: '服务器名称',
|
||
value: this.$jsiteServer.pg?.title || this.$jsiteServer.pg?.name,
|
||
},
|
||
{
|
||
label: '实例ID',
|
||
value: this.$jsiteServer.pg?.instance_id || '',
|
||
},
|
||
{
|
||
label: '公网IP',
|
||
value: this.$jsiteServer.pg?.public_ip || '',
|
||
},
|
||
{
|
||
label: '内网IP',
|
||
value: this.$jsiteServer.pg?.private_ip || '',
|
||
},
|
||
{
|
||
label: '区域',
|
||
value: this.getRegionText(this.$jsiteServer.pg?.region),
|
||
},
|
||
{
|
||
label: '系统',
|
||
value: this.$jsiteServer.pg?.system || '',
|
||
},
|
||
];
|
||
},
|
||
$jsiteServer() {
|
||
return getCachedDocumentResource('Jsite Server', this.server);
|
||
},
|
||
},
|
||
};
|
||
</script> |