前端服务器详情页增加常用操作按钮
This commit is contained in:
parent
f9b8f6229c
commit
82d28e3f94
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</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">
|
<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">
|
||||||
<i-lucide-clock class="mr-1.5 h-4 w-4 text-amber-500" />
|
<ClockIcon class="mr-1.5 h-4 w-4 text-amber-500" />
|
||||||
到期时间:{{ $format.date($jsiteServer.pg.end_date) }}
|
到期时间:{{ $format.date($jsiteServer.pg.end_date) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,8 +65,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧区块:配置信息 -->
|
<!-- 右侧区块:配置信息和操作 -->
|
||||||
<div class="col-span-1 space-y-5">
|
<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>
|
||||||
|
|
||||||
<!-- 服务器配置 -->
|
<!-- 服务器配置 -->
|
||||||
<div class="rounded-md border">
|
<div class="rounded-md border">
|
||||||
<div class="h-12 border-b px-5 py-4">
|
<div class="h-12 border-b px-5 py-4">
|
||||||
@ -113,9 +156,21 @@
|
|||||||
<span class="text-gray-600">SSH用户:</span>
|
<span class="text-gray-600">SSH用户:</span>
|
||||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.ssh_user || 'root' }}</span>
|
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.ssh_user || 'root' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-gray-600">服务器密码:</span>
|
<span class="text-gray-600">服务器密码:</span>
|
||||||
<span class="font-mono text-gray-900">{{ $jsiteServer.pg.password || '未设置' }}</span>
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="font-mono text-gray-900" :class="{ 'text-transparent select-none': !showPassword }">
|
||||||
|
{{ showPassword ? (decryptedPassword || '未设置') : '••••••••' }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
@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>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-600">密钥对名称:</span>
|
<span class="text-gray-600">密钥对名称:</span>
|
||||||
@ -133,17 +188,36 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getCachedDocumentResource, Badge, Tooltip } from 'jingrow-ui';
|
import { getCachedDocumentResource, Badge, Tooltip, Button, createResource } from 'jingrow-ui';
|
||||||
import { h, defineAsyncComponent } from 'vue';
|
import { h, defineAsyncComponent } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import InfoIcon from '~icons/lucide/info';
|
|
||||||
import { getToastErrorMessage } from '../utils/toast';
|
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';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'JsiteServerOverview',
|
name: 'JsiteServerOverview',
|
||||||
props: ['server'],
|
props: ['server'],
|
||||||
components: {
|
components: {
|
||||||
Badge,
|
Badge,
|
||||||
|
Button,
|
||||||
|
EyeIcon,
|
||||||
|
EyeOffIcon,
|
||||||
|
ClockIcon,
|
||||||
|
InfoIcon,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showPassword: false,
|
||||||
|
decryptedPassword: null,
|
||||||
|
restartLoading: false,
|
||||||
|
resetPasswordLoading: false,
|
||||||
|
resetKeyPairLoading: false,
|
||||||
|
resetSystemLoading: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getStatusText(status) {
|
getStatusText(status) {
|
||||||
@ -204,8 +278,21 @@ export default {
|
|||||||
return regionMap[region] || region;
|
return regionMap[region] || region;
|
||||||
},
|
},
|
||||||
renewServer() {
|
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(
|
toast.promise(
|
||||||
this.$jsiteServer.renew?.submit() || Promise.resolve(),
|
renewRequest.submit(),
|
||||||
{
|
{
|
||||||
loading: '正在处理续费请求...',
|
loading: '正在处理续费请求...',
|
||||||
success: '续费请求已提交',
|
success: '续费请求已提交',
|
||||||
@ -213,6 +300,182 @@ export default {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
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/jingrow.client.get_password',
|
||||||
|
params: {
|
||||||
|
pagetype: 'Jsite Server',
|
||||||
|
name: this.$jsiteServer.pg.name,
|
||||||
|
fieldname: 'password'
|
||||||
|
},
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.message) {
|
||||||
|
this.decryptedPassword = response.message;
|
||||||
|
this.showPassword = true;
|
||||||
|
} else {
|
||||||
|
toast.warning('当前没有保存的密码');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error('获取密码失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getPasswordRequest.submit();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
serverInformation() {
|
serverInformation() {
|
||||||
|
|||||||
123
dashboard/src2/dialogs/PasswordDialog.vue
Normal file
123
dashboard/src2/dialogs/PasswordDialog.vue
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="showDialog" :options="{ title }">
|
||||||
|
<template #body-content>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p class="text-p-base text-gray-800" v-if="description">{{ description }}</p>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<FormControl
|
||||||
|
v-model="password"
|
||||||
|
:fieldtype="'Password'"
|
||||||
|
:label="'新密码'"
|
||||||
|
:fieldname="'password'"
|
||||||
|
:reqd="1"
|
||||||
|
:description="'长度为 8 至 30 个字符,必须同时包含大小写英文字母、数字和特殊符号'"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-model="confirmPassword"
|
||||||
|
:fieldtype="'Password'"
|
||||||
|
:label="'确认密码'"
|
||||||
|
:fieldname="'confirm_password'"
|
||||||
|
:reqd="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ErrorMessage class="mt-2" :message="error" />
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex gap-2 w-full">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
class="flex-1"
|
||||||
|
@click="hide"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
class="flex-1"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="onConfirm"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ErrorMessage, FormControl } from 'jingrow-ui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PasswordDialog',
|
||||||
|
props: ['title', 'description', 'onConfirm'],
|
||||||
|
expose: ['show', 'hide'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showDialog: true,
|
||||||
|
error: null,
|
||||||
|
isLoading: false,
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: { FormControl, ErrorMessage },
|
||||||
|
methods: {
|
||||||
|
validatePassword() {
|
||||||
|
if (!this.password) {
|
||||||
|
this.error = '请输入新密码';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.password.length < 8 || this.password.length > 30) {
|
||||||
|
this.error = '密码长度必须在 8 至 30 个字符之间';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查密码复杂度:必须同时包含大小写英文字母、数字和特殊符号
|
||||||
|
const hasUpperCase = /[A-Z]/.test(this.password);
|
||||||
|
const hasLowerCase = /[a-z]/.test(this.password);
|
||||||
|
const hasNumbers = /\d/.test(this.password);
|
||||||
|
const hasSpecialChar = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(this.password);
|
||||||
|
|
||||||
|
if (!hasUpperCase || !hasLowerCase || !hasNumbers || !hasSpecialChar) {
|
||||||
|
this.error = '密码必须同时包含大小写英文字母、数字和特殊符号';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.password !== this.confirmPassword) {
|
||||||
|
this.error = '两次输入的密码不一致';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async onConfirm() {
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
if (!this.validatePassword()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
await this.onConfirm(this.password);
|
||||||
|
this.hide();
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message || '操作失败';
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
this.showDialog = true;
|
||||||
|
this.password = '';
|
||||||
|
this.confirmPassword = '';
|
||||||
|
this.error = null;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.showDialog = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -162,7 +162,7 @@
|
|||||||
您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。
|
您的订单已支付成功,服务器记录已创建。服务器正在后台创建中,创建并启动需要 3-5 分钟,请耐心等待。
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-sm">
|
<p class="text-gray-500 text-sm">
|
||||||
您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"Running"。
|
您可以在服务器列表中查看服务器状态,创建完成后状态将更新为"运行中"。
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user