fix: 面板设置证书改为抽屉实现 (#823)

This commit is contained in:
ssongliu 2023-04-28 14:48:19 +08:00 committed by GitHub
parent 2c8b19bff2
commit 566a4f3568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 263 additions and 197 deletions

View File

@ -56,7 +56,6 @@ func Routers() *gin.Engine {
Router.SetFuncMap(template.FuncMap{
"Localize": ginI18n.GetMessage,
})
Router.Use(middleware.JwtAuth())
systemRouter := rou.RouterGroupApp
@ -73,7 +72,6 @@ func Routers() *gin.Engine {
PrivateGroup := Router.Group("/api/v1")
PrivateGroup.Use(middleware.GlobalLoading())
//PrivateGroup.Use(middleware.SafetyAuth())
{
systemRouter.InitBaseRouter(PrivateGroup)
systemRouter.InitDashboardRouter(PrivateGroup)

View File

@ -901,12 +901,13 @@ const message = {
'If the https service is disabled, you need to restart the panel for it to take effect. Do you want to continue?',
https: 'Setting up HTTPS protocol access for the panel can enhance the security of panel access.',
certType: 'Certificate type',
selfSigned: 'Self signed',
selfSignedHelper:
'It is normal for self-signed certificates to be not trusted by browsers and display a security warning as the certificate is not issued by a trusted third party.',
import: 'Import',
select: 'Select',
domainOrIP: 'Domain/IP',
domainOrIP: 'Domain or IP',
timeOut: 'Timeout',
rootCrtDownload: 'Root certificate download',
primaryKey: 'Primary key',

View File

@ -937,11 +937,12 @@ const message = {
sslDisableHelper: '禁用 https 服务需要重启面板才能生效是否继续',
https: '为面板设置 https 协议访问提升面板访问安全性',
certType: '证书类型',
selfSigned: '自签名',
selfSignedHelper: '自签证书不被浏览器信任显示不安全是正常现象',
import: '导入',
select: '选择已有',
domainOrIP: '域名/IP',
domainOrIP: '域名IP',
timeOut: '过期时间',
rootCrtDownload: '根证书下载',
primaryKey: '密钥',

View File

@ -5,7 +5,7 @@
<el-form :model="form" ref="panelFormRef" label-position="left" label-width="160px">
<el-row>
<el-col :span="1"><br /></el-col>
<el-col :span="16">
<el-col :span="12">
<el-form-item
:label="$t('setting.enableMonitor')"
:rules="Rules.requiredInput"

View File

@ -5,7 +5,7 @@
<el-form :model="form" ref="panelFormRef" label-position="left" label-width="160px">
<el-row>
<el-col :span="1"><br /></el-col>
<el-col :span="16">
<el-col :span="12">
<el-form-item :label="$t('setting.user')" :rules="Rules.userName" prop="userName">
<el-input clearable v-model="form.userName">
<template #append>

View File

@ -1,6 +1,6 @@
<template>
<div v-loading="loading">
<el-drawer v-model="passwordVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<el-drawer v-model="passwordVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('setting.changePassword')" :back="handleClose" />
</template>

View File

@ -5,19 +5,23 @@
<DrawerHeader :header="$t('setting.entrance')" :back="handleClose" />
</template>
<el-form label-position="top" v-loading="loading">
<el-form-item :label="$t('setting.entrance')" prop="days">
<el-input clearable v-model="securityEntrance">
<template #append>
<el-button @click="random" icon="RefreshRight"></el-button>
</template>
</el-input>
<span class="input-help">
{{ $t('setting.entranceInputHelper') }}
</span>
<span class="input-error" v-if="codeError">
{{ $t('setting.entranceError') }}
</span>
</el-form-item>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('setting.entrance')" prop="days">
<el-input clearable v-model="securityEntrance">
<template #append>
<el-button @click="random" icon="RefreshRight"></el-button>
</template>
</el-input>
<span class="input-help">
{{ $t('setting.entranceInputHelper') }}
</span>
<span class="input-error" v-if="codeError">
{{ $t('setting.entranceError') }}
</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">

View File

@ -5,7 +5,7 @@
<el-form :model="form" ref="panelFormRef" v-loading="loading" label-position="left" label-width="180px">
<el-row>
<el-col :span="1"><br /></el-col>
<el-col :span="16">
<el-col :span="12">
<el-form-item :label="$t('setting.panelPort')" :rules="Rules.port" prop="serverPort">
<el-input clearable v-model.number="form.serverPort">
<template #append>
@ -94,12 +94,17 @@
inactive-value="disable"
/>
<span class="input-help">{{ $t('setting.https') }}</span>
<SSLSetting
:type="form.sslType"
:status="form.ssl"
v-if="sslShow"
style="width: 100%"
/>
<div v-if="form.ssl === 'enable' && sslInfo">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
<el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}
</el-tag>
<div>
<el-button link type="primary" @click="handleSSL">
{{ $t('commons.button.view') }}
</el-button>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
@ -108,6 +113,7 @@
</LayoutContent>
<MfaSetting ref="mfaRef" @search="search" />
<SSLSetting ref="sslRef" @search="search" />
<EntranceSetting ref="entranceRef" @search="search" />
<TimeoutSetting ref="timeoutref" @search="search" />
</div>
@ -121,16 +127,27 @@ import SSLSetting from '@/views/setting/safe/ssl/index.vue';
import MfaSetting from '@/views/setting/safe/mfa/index.vue';
import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
import { updateSetting, getSettingInfo, updatePort, getSystemAvailable, updateSSL } from '@/api/modules/setting';
import {
updateSetting,
getSettingInfo,
updatePort,
getSystemAvailable,
updateSSL,
loadSSLInfo,
} from '@/api/modules/setting';
import i18n from '@/lang';
import { Rules } from '@/global/form-rules';
import { MsgSuccess } from '@/utils/message';
import { Setting } from '@/api/interface/setting';
const loading = ref(false);
const entranceRef = ref();
const timeoutref = ref();
const mfaRef = ref();
const sslRef = ref();
const sslInfo = ref<Setting.SSLInfo>();
const form = reactive({
serverPort: 9999,
ssl: 'disable',
@ -143,19 +160,15 @@ const form = reactive({
});
type FormInstance = InstanceType<typeof ElForm>;
const sslShow = ref();
const oldSSLStatus = ref();
const unset = ref(i18n.global.t('setting.unSetting'));
const search = async () => {
const res = await getSettingInfo();
form.serverPort = Number(res.data.serverPort);
form.ssl = res.data.ssl;
oldSSLStatus.value = res.data.ssl;
form.sslType = res.data.sslType;
if (form.ssl === 'enable') {
sslShow.value = true;
loadInfo();
}
form.securityEntrance = res.data.securityEntrance;
form.expirationDays = Number(res.data.expirationDays);
@ -227,31 +240,31 @@ const onSavePort = async (formEl: FormInstance | undefined, key: string, val: an
const handleMFA = async () => {
if (form.mfaStatus === 'enable') {
mfaRef.value.acceptParams();
} else {
loading.value = true;
await updateSetting({ key: 'MFAStatus', value: 'disable' })
.then(() => {
loading.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
return;
}
loading.value = true;
await updateSetting({ key: 'MFAStatus', value: 'disable' })
.then(() => {
loading.value = false;
search();
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
};
const onChangeEntrance = async () => {
entranceRef.value.acceptParams({ securityEntrance: form.securityEntrance });
};
const handleSSL = async () => {
if (form.ssl === 'enable') {
sslShow.value = true;
return;
}
if (form.ssl === oldSSLStatus.value) {
sslShow.value = false;
let params = {
ssl: form.ssl,
sslType: form.sslType,
sslInfo: sslInfo.value,
};
sslRef.value!.acceptParams(params);
return;
}
ElMessageBox.confirm(i18n.global.t('setting.sslDisableHelper'), i18n.global.t('setting.sslDisable'), {
@ -260,7 +273,6 @@ const handleSSL = async () => {
type: 'info',
})
.then(async () => {
sslShow.value = false;
await updateSSL({ ssl: 'disable', domain: '', sslType: '', key: '', cert: '', sslID: 0 });
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
let href = window.location.href;
@ -272,6 +284,12 @@ const handleSSL = async () => {
});
};
const loadInfo = async () => {
await loadSSLInfo().then(async (res) => {
sslInfo.value = res.data;
});
};
const onChangeExpirationTime = async () => {
timeoutref.value.acceptParams({ expirationDays: form.expirationDays });
};

View File

@ -11,40 +11,44 @@
<DrawerHeader :header="$t('setting.mfa')" :back="handleClose" />
</template>
<el-form :model="form" ref="formRef" v-loading="loading" label-position="top">
<el-form-item :label="$t('setting.mfaHelper1')">
<ul>
<li>Google Authenticator</li>
<li>Microsoft Authenticator</li>
<li>1Password</li>
<li>LastPass</li>
<li>Authenticator</li>
</ul>
</el-form-item>
<el-form-item :label="$t('setting.mfaTypeOption')">
<el-radio-group v-model="mode" @change="form.secret = ''">
<el-radio label="scan">{{ $t('setting.qrCode') }}</el-radio>
<el-radio label="input">{{ $t('setting.manualInput') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('setting.mfaHelper2')" v-if="mode === 'scan'">
<el-image style="width: 120px; height: 120px" :src="qrImage" />
</el-form-item>
<el-form-item
:label="$t('setting.mfaSecret')"
v-if="mode === 'input'"
prop="secret"
:rules="Rules.requiredInput"
>
<el-input v-model="form.secret"></el-input>
</el-form-item>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('setting.mfaHelper1')">
<ul>
<li>Google Authenticator</li>
<li>Microsoft Authenticator</li>
<li>1Password</li>
<li>LastPass</li>
<li>Authenticator</li>
</ul>
</el-form-item>
<el-form-item :label="$t('setting.mfaTypeOption')">
<el-radio-group v-model="mode" @change="form.secret = ''">
<el-radio label="scan">{{ $t('setting.qrCode') }}</el-radio>
<el-radio label="input">{{ $t('setting.manualInput') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('setting.mfaHelper2')" v-if="mode === 'scan'">
<el-image style="width: 120px; height: 120px" :src="qrImage" />
</el-form-item>
<el-form-item
:label="$t('setting.mfaSecret')"
v-if="mode === 'input'"
prop="secret"
:rules="Rules.requiredInput"
>
<el-input v-model="form.secret"></el-input>
</el-form-item>
<el-form-item
:label="mode === 'scan' ? $t('setting.mfaHelper3') : $t('setting.mfaCode')"
prop="code"
:rules="Rules.requiredInput"
>
<el-input v-model="form.code"></el-input>
</el-form-item>
<el-form-item
:label="mode === 'scan' ? $t('setting.mfaHelper3') : $t('setting.mfaCode')"
prop="code"
:rules="Rules.requiredInput"
>
<el-input v-model="form.code"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">

View File

@ -1,94 +1,120 @@
<template>
<div>
<el-card>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules">
<el-radio-group v-model="sslItemType">
<el-radio label="self">{{ $t('setting.selfSigned') }}</el-radio>
<el-radio label="select">{{ $t('setting.select') }}</el-radio>
<el-radio label="import">{{ $t('setting.import') }}</el-radio>
</el-radio-group>
<span class="input-help" v-if="sslItemType === 'self'">{{ $t('setting.selfSignedHelper') }}</span>
<div v-if="sslInfo.timeout">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}</el-tag>
<el-button
@click="onDownload"
style="margin-left: 5px"
v-if="sslItemType === 'self'"
type="primary"
link
icon="Download"
>
{{ $t('setting.rootCrtDownload') }}
</el-button>
</div>
<el-drawer
v-model="drawerVisiable"
:destroy-on-close="true"
@close="handleClose"
:close-on-click-modal="false"
size="50%"
>
<template #header>
<DrawerHeader header="https" :back="handleClose" />
</template>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('setting.certType')">
<el-radio-group v-model="form.sslType">
<el-radio label="self">{{ $t('setting.selfSigned') }}</el-radio>
<el-radio label="select">{{ $t('setting.select') }}</el-radio>
<el-radio label="import">{{ $t('setting.import') }}</el-radio>
</el-radio-group>
<span class="input-help" v-if="form.sslType === 'self'">
{{ $t('setting.selfSignedHelper') }}
</span>
</el-form-item>
<div v-if="sslItemType === 'import'">
<el-form-item :label="$t('setting.primaryKey')" prop="key">
<el-input v-model="form.key" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" />
</el-form-item>
<el-form-item class="margintop" :label="$t('setting.certificate')" prop="cert">
<el-input v-model="form.cert" :autosize="{ minRows: 2, maxRows: 6 }" type="textarea" />
</el-form-item>
</div>
<el-form-item v-if="form.timeout">
<el-tag>{{ $t('setting.domainOrIP') }} {{ form.domain }}</el-tag>
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ form.timeout }}</el-tag>
<el-button
@click="onDownload"
style="margin-left: 5px"
v-if="form.sslType === 'self'"
type="primary"
link
icon="Download"
>
{{ $t('setting.rootCrtDownload') }}
</el-button>
</el-form-item>
<div v-if="sslItemType === 'select'">
<el-form-item :label="$t('setting.certificate')" prop="sslID">
<el-select v-model="form.sslID" @change="changeSSl(form.sslID)">
<el-option
v-for="(item, index) in sslList"
:key="index"
:label="item.primaryDomain"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-descriptions
class="margintop"
:column="5"
border
direction="vertical"
v-if="form.sslID > 0 && itemSSL"
>
<el-descriptions-item :label="$t('website.primaryDomain')">
{{ itemSSL.primaryDomain }}
</el-descriptions-item>
<el-descriptions-item :label="$t('website.otherDomains')">
{{ itemSSL.domains }}
</el-descriptions-item>
<el-descriptions-item :label="$t('ssl.provider')">
{{ getProvider(itemSSL.provider) }}
</el-descriptions-item>
<el-descriptions-item
:label="$t('ssl.acmeAccount')"
v-if="itemSSL.acmeAccount?.email && itemSSL.provider !== 'manual'"
>
{{ itemSSL.acmeAccount.email }}
</el-descriptions-item>
<el-descriptions-item :label="$t('website.expireDate')">
{{ dateFormatSimple(itemSSL.expireDate) }}
</el-descriptions-item>
</el-descriptions>
</div>
<el-button style="margin-top: 20px" type="primary" @click="onSaveSSL(formRef)">
{{ $t('commons.button.saveAndEnable') }}
</el-button>
<div v-if="form.sslType === 'import'">
<el-form-item :label="$t('setting.primaryKey')" prop="key">
<el-input v-model="form.key" :autosize="{ minRows: 5, maxRows: 10 }" type="textarea" />
</el-form-item>
<el-form-item class="margintop" :label="$t('setting.certificate')" prop="cert">
<el-input v-model="form.cert" :autosize="{ minRows: 5, maxRows: 10 }" type="textarea" />
</el-form-item>
</div>
<div v-if="form.sslType === 'select'">
<el-form-item :label="$t('setting.certificate')" prop="sslID">
<el-select v-model="form.sslID" @change="changeSSl(form.sslID)">
<el-option
v-for="(item, index) in sslList"
:key="index"
:label="item.primaryDomain"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-descriptions
class="margintop"
:column="5"
border
direction="vertical"
v-if="form.sslID > 0 && itemSSL"
>
<el-descriptions-item :label="$t('website.primaryDomain')">
{{ itemSSL.primaryDomain }}
</el-descriptions-item>
<el-descriptions-item :label="$t('website.otherDomains')">
{{ itemSSL.domains }}
</el-descriptions-item>
<el-descriptions-item :label="$t('ssl.provider')">
{{ getProvider(itemSSL.provider) }}
</el-descriptions-item>
<el-descriptions-item
:label="$t('ssl.acmeAccount')"
v-if="itemSSL.acmeAccount?.email && itemSSL.provider !== 'manual'"
>
{{ itemSSL.acmeAccount.email }}
</el-descriptions-item>
<el-descriptions-item :label="$t('website.expireDate')">
{{ dateFormatSimple(itemSSL.expireDate) }}
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSaveSSL(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { Website } from '@/api/interface/website';
import { loadSSLInfo } from '@/api/modules/setting';
import { dateFormatSimple, getProvider } from '@/utils/util';
import { ListSSL } from '@/api/modules/website';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { updateSSL } from '@/api/modules/setting';
import { DownloadByPath } from '@/api/modules/files';
import { Rules } from '@/global/form-rules';
import { FormInstance } from 'element-plus';
import { Setting } from '@/api/interface/setting';
const loading = ref();
const drawerVisiable = ref();
const form = reactive({
ssl: 'enable',
@ -98,6 +124,7 @@ const form = reactive({
cert: '',
key: '',
rootPath: '',
timeout: '',
});
const rules = reactive({
@ -108,36 +135,32 @@ const rules = reactive({
const formRef = ref<FormInstance>();
const props = defineProps({
type: {
type: String,
default: 'self',
},
});
const sslInfo = reactive({
domain: '',
timeout: '',
});
const sslList = ref();
const itemSSL = ref();
const sslItemType = ref('self');
const loadInfo = async () => {
await loadSSLInfo().then(async (res) => {
sslInfo.domain = res.data.domain || '';
sslInfo.timeout = res.data.timeout || '';
form.cert = res.data.cert;
form.key = res.data.key;
form.rootPath = res.data.rootPath;
if (res.data.sslID) {
form.sslID = res.data.sslID;
const ssls = await ListSSL({});
sslList.value = ssls.data || [];
changeSSl(form.sslID);
}
});
interface DialogProps {
sslType: string;
sslInfo?: Setting.SSLInfo;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
form.sslType = params.sslType;
form.cert = params.sslInfo?.cert || '';
form.key = params.sslInfo?.key || '';
form.rootPath = params.sslInfo?.rootPath || '';
form.domain = params.sslInfo?.domain || '';
form.timeout = params.sslInfo?.timeout || '';
if (params.sslInfo?.sslID) {
form.sslID = params.sslInfo.sslID;
const ssls = await ListSSL({});
sslList.value = ssls.data || [];
changeSSl(params.sslInfo?.sslID);
} else {
loadSSLs();
}
drawerVisiable.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
const loadSSLs = async () => {
const res = await ListSSL({});
@ -163,13 +186,21 @@ const onDownload = async () => {
};
const onSaveSSL = async (formEl: FormInstance | undefined) => {
onDownload();
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
form.sslType = sslItemType.value;
let param = {
ssl: 'enable',
sslType: form.sslType,
domain: '',
sslID: form.sslID,
cert: form.cert,
key: form.key,
};
let href = window.location.href;
form.domain = href.split('//')[1].split(':')[0];
await updateSSL(form).then(() => {
param.domain = href.split('//')[1].split(':')[0];
await updateSSL(param).then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
let href = window.location.href;
let address = href.split('://')[1];
@ -178,12 +209,13 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
});
};
onMounted(() => {
nextTick(() => {
sslItemType.value = props.type;
loadInfo();
});
loadSSLs();
const handleClose = () => {
emit('search');
drawerVisiable.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -5,10 +5,18 @@
<DrawerHeader :header="$t('setting.expirationTime')" :back="handleClose" />
</template>
<el-form ref="timeoutFormRef" label-position="top" :model="form">
<el-form-item :label="$t('setting.days')" prop="days" :rules="[Rules.number, checkNumberRange(0, 60)]">
<el-input clearable v-model.number="form.days" />
<span class="input-help">{{ $t('setting.expirationHelper') }}</span>
</el-form-item>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item
:label="$t('setting.days')"
prop="days"
:rules="[Rules.number, checkNumberRange(0, 60)]"
>
<el-input clearable v-model.number="form.days" />
<span class="input-help">{{ $t('setting.expirationHelper') }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">