jcloud/dashboard/src2/components/JsiteServerOverview.vue

535 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
</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>
<span>
{{ 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="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">公网IP:</span>
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.public_ip || '未分配' }}</span>
</div>
<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,
resetSystemLoading: false,
copySuccess: 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 renewRequest = createResource({
url: '/api/action/jcloud.api.aliyun_server_light.renew_aliyun_instance',
params: {
instance_id: this.$jsiteServer.pg.instance_id,
region_id: this.$jsiteServer.pg.region
},
onSuccess: () => {
toast.success('续费请求已提交');
},
onError: (error) => {
toast.error(getToastErrorMessage(error));
}
});
toast.promise(
renewRequest.submit(),
{
loading: '正在处理续费请求...',
success: '续费请求已提交',
error: (e) => getToastErrorMessage(e),
},
);
},
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 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('复制失败,请手动复制');
});
}
},
},
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: '区域',
value: this.getRegionText(this.$jsiteServer.pg?.region),
},
{
label: '系统',
value: this.$jsiteServer.pg?.system || '未知',
},
];
},
$jsiteServer() {
return getCachedDocumentResource('Jsite Server', this.server);
},
},
};
</script>