优化邮件服务页面并迁移邮件策略
This commit is contained in:
parent
c39a86a750
commit
8c9c9b1568
@ -158,3 +158,33 @@
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 半宽桌面抽屉内保持邮件服务标题与操作同行,避免无意义的纵向占位。 */
|
||||
@media (min-width: 769px) and (max-height: 800px) {
|
||||
.config-page[data-config-page="mail"] .config-overview {
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.config-page[data-config-page="mail"] .overview-heading {
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.config-page[data-config-page="mail"] .overview-copy {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.config-page[data-config-page="mail"] .overview-actions {
|
||||
justify-content: flex-end;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.config-page[data-config-page="mail"] .editor-panel {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.config-page[data-config-page="mail"] .editor-footer .action-row {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,11 +80,32 @@
|
||||
|
||||
.config-form[data-form-section="base"] .form-grid,
|
||||
.config-form[data-form-section="mail"] .form-grid,
|
||||
.config-form[data-form-section="mail_policy"] .form-grid,
|
||||
.form-group[data-group="verification"] .form-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* 邮件服务页在桌面抽屉中采用紧凑双列,移除策略后可在常见高度内完整展示。 */
|
||||
@container config-main (min-width: 360px) {
|
||||
.config-form[data-form-section="mail"] {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.config-form[data-form-section="mail"] .form-grid {
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.config-form[data-form-section="mail"] .form-group + .form-group {
|
||||
margin-top: 8px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.config-form[data-form-section="mail"] .form-group-heading {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 800px) {
|
||||
.config-form,
|
||||
.form-grid {
|
||||
|
||||
@ -140,7 +140,10 @@ const mailGroups = [
|
||||
field('relay.password', '中继密码', 'password', { placeholder: '留空保持原值' }),
|
||||
field('relay.from', '发件邮箱', 'text', { placeholder: 'noreply@example.com' }),
|
||||
field('relay.fromDomain', '发件域名', 'text', { placeholder: 'example.com' })
|
||||
]),
|
||||
])
|
||||
]
|
||||
|
||||
const mailPolicyGroups = [
|
||||
group('policy', '邮件策略', '', [
|
||||
field('policy.maxMessageBytes', '最大邮件大小(字节)', 'number', { min: 0, max: Number.MAX_SAFE_INTEGER, integer: true, hint: '0 使用后端默认限制;1 MiB = 1048576 字节。' }),
|
||||
field('policy.spamFilterLevel', '垃圾邮件过滤级别', 'select', { fallback: 0, options: options([[0, '不过滤'], [1, 'SPF 与 DKIM 均失败时过滤'], [2, 'SPF 失败时过滤'], [3, 'DKIM 失败时过滤']]) })
|
||||
@ -205,7 +208,7 @@ const pluginDefinitions = {
|
||||
export function getFieldGroups(section, config) {
|
||||
const pluginId = Object.hasOwn(pluginDefinitions, section) ? section : ''
|
||||
if (section !== 'plugins' && !pluginId) {
|
||||
return ({ base: baseGroups, mail: mailGroups, security_policy: securityPolicyGroups, ssl: sslGroups }[section] || [])
|
||||
return ({ base: baseGroups, mail: mailGroups, mail_policy: mailPolicyGroups, security_policy: securityPolicyGroups, ssl: sslGroups }[section] || [])
|
||||
.map(item => ({
|
||||
...item,
|
||||
fields: item.fields
|
||||
|
||||
@ -14,7 +14,7 @@ export function useNavigation() {
|
||||
const switching = ref(false)
|
||||
const pages = [
|
||||
{ value: 'base', label: '基础配置', component: BaseConfig },
|
||||
{ value: 'mail', label: '邮件服务与策略', component: EmailConfig },
|
||||
{ value: 'mail', label: '邮件服务', component: EmailConfig },
|
||||
{ value: 'ssl', label: 'SSL 配置', component: SslConfig },
|
||||
{ value: 'plugins', label: '插件配置', children: [
|
||||
{ value: 'spam_block', label: '垃圾邮件拦截', component: PluginsConfig },
|
||||
|
||||
@ -7,7 +7,8 @@ import { readFormConfig, updateFormConfig, validateFormConfig } from './ConfigFo
|
||||
|
||||
const sections = [
|
||||
{ value: 'base', label: '基础配置' },
|
||||
{ value: 'mail', label: '邮件服务与策略' },
|
||||
{ value: 'mail', label: '邮件服务' },
|
||||
{ value: 'mail_policy', label: '邮件策略' },
|
||||
{ value: 'security_policy', label: '安全策略' },
|
||||
{ value: 'ssl', label: 'SSL 配置' },
|
||||
{ value: 'plugins', label: '插件配置' },
|
||||
@ -56,7 +57,7 @@ export function usePage(pageKey = 'base') {
|
||||
// 独立页面复用原配置分区的原子保存与候选保护,不丢弃其他页面的字段。
|
||||
const pluginPages = ['spam_block', 'telegram_push', 'wechat_push']
|
||||
const apiSection = () => pluginPages.includes(selectedSection.value) ? 'plugins'
|
||||
: selectedSection.value === 'security_policy' ? 'mail' : selectedSection.value
|
||||
: ['mail_policy', 'security_policy'].includes(selectedSection.value) ? 'mail' : selectedSection.value
|
||||
const currentSection = computed(() => sections.find(item => item.value === selectedSection.value) || sections[0])
|
||||
const sectionLoading = ref(false)
|
||||
let sectionRequestId = 0
|
||||
@ -91,17 +92,17 @@ export function usePage(pageKey = 'base') {
|
||||
['listener', 'SMTP 监听'],
|
||||
['imap', 'IMAP 监听'],
|
||||
['pop3', 'POP3 监听'],
|
||||
['relay', 'SMTP 中继与登录验证'],
|
||||
['policy', '邮件与账号策略']
|
||||
['relay', 'SMTP 中继'],
|
||||
['policy', '邮件策略']
|
||||
].filter(([key]) => {
|
||||
const before = active[key] || {}
|
||||
const after = candidate[key] || {}
|
||||
return Object.keys({ ...before, ...after }).some(field => JSON.stringify(before[field]) !== JSON.stringify(after[field]))
|
||||
}).map(([, label]) => label)
|
||||
const scope = pending.payload && loadedText.value
|
||||
? (changed.length ? `上次未完成的保存涉及:${changed.join('、')}。` : '上次未完成保存的邮件服务与安全策略未变化。')
|
||||
? (changed.length ? `上次未完成的保存涉及:${changed.join('、')}。` : '上次未完成保存的邮件配置未变化。')
|
||||
: '上次未完成保存的内容尚未完整读取,请刷新后核对。'
|
||||
return `${scope}邮件服务页和安全策略页共用一组运行配置;完成保存时全部变更一起生效,监听端口变更仍需重启后端。`
|
||||
return `${scope}邮件服务、邮件策略和安全策略共用一组运行配置;完成保存时全部变更一起生效,监听端口变更仍需重启后端。`
|
||||
})
|
||||
let savedInput = null
|
||||
const validationResult = ref(null)
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
.mail-policy-settings {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--pm-border-color);
|
||||
border-radius: 12px;
|
||||
background: var(--pm-bg-secondary);
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.config-page),
|
||||
.mail-policy-settings :deep(.config-page-body) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.config-overview) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.overview-title h3) {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.editor-panel) {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.form-group-heading) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mail-policy-settings :deep(.editor-footer) {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@container settings-content (max-width: 560px) {
|
||||
.mail-policy-settings {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export const mailPolicySection = 'mail_policy'
|
||||
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<section class="mail-policy-settings" aria-label="邮件策略">
|
||||
<ConfigEditor />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { provide } from 'vue'
|
||||
import { usePage } from '../../ConfigSettings/ConfigSettings.js'
|
||||
import ConfigEditor from '../../ConfigSettings/ConfigEditor/ConfigEditor.vue'
|
||||
import { mailPolicySection } from './MailPolicySettings.js'
|
||||
|
||||
const page = usePage(mailPolicySection)
|
||||
provide('smail-config-page', page)
|
||||
|
||||
defineExpose({ beforeLeave: page.beforeLeave })
|
||||
</script>
|
||||
|
||||
<style scoped src="./MailPolicySettings.css"></style>
|
||||
@ -2,15 +2,33 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.rule-settings-card {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.personal-rules {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rule-mail-policy + .personal-rules {
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--pm-border-color);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--pm-text-primary);
|
||||
margin: 0 0 8px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-desc {
|
||||
@ -23,7 +41,7 @@
|
||||
border: 1px solid var(--pm-border-color);
|
||||
border-radius: var(--pm-radius-sm);
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.modern-table :deep(th.el-table__cell) {
|
||||
@ -119,3 +137,18 @@
|
||||
margin: 24px 0;
|
||||
border-color: var(--pm-border-color);
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-height: 800px) {
|
||||
.rule-settings-card {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rule-mail-policy + .personal-rules {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.settings-header,
|
||||
.table-container {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {reactive, ref} from 'vue';
|
||||
import {computed, reactive, ref} from 'vue';
|
||||
|
||||
import lang from '../../../i18n/i18n';
|
||||
|
||||
@ -7,8 +7,11 @@ import {Delete, Edit, InfoFilled, Plus} from '@element-plus/icons-vue'
|
||||
import {http} from "@/utils/axios";
|
||||
|
||||
import {ElNotification} from "element-plus";
|
||||
import {useGlobalStatusStore} from '@/stores/useGlobalStatusStore'
|
||||
|
||||
export function usePage() {
|
||||
const globalStatus = useGlobalStatusStore()
|
||||
const canReadGlobalPolicy = computed(() => globalStatus.hasCapability('config.read'))
|
||||
const data = ref([])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
@ -154,7 +157,8 @@ export function usePage() {
|
||||
removeRuleLine,
|
||||
addRule,
|
||||
submitRule,
|
||||
ruleTypeChange
|
||||
ruleTypeChange,
|
||||
canReadGlobalPolicy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
<template>
|
||||
<div class="settings-card">
|
||||
<div class="settings-card rule-settings-card">
|
||||
<MailPolicySettings
|
||||
v-if="canReadGlobalPolicy"
|
||||
ref="mailPolicyPage"
|
||||
class="rule-mail-policy"
|
||||
/>
|
||||
|
||||
<section class="personal-rules" aria-labelledby="personal-rules-title">
|
||||
<div class="settings-header">
|
||||
<h3>{{ lang.auto_rules }}</h3>
|
||||
<h3 id="personal-rules-title">{{ lang.auto_rules }}</h3>
|
||||
<p class="settings-desc">{{ lang.auto_rules_desc }}</p>
|
||||
</div>
|
||||
|
||||
@ -38,6 +45,7 @@
|
||||
<el-icon><Plus /></el-icon> {{ lang.new_rule }}
|
||||
</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="addRuleForm.id > 0 ? '编辑规则' : lang.new_rule" width="700px" class="premium-dialog">
|
||||
<div class="dialog-content">
|
||||
@ -110,9 +118,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { usePage, Plus } from './RuleSettings.js'
|
||||
import { usePagination } from '../ConfigSettings/ConfigPagination/ConfigPagination.js'
|
||||
import ConfigPagination from '../ConfigSettings/ConfigPagination/ConfigPagination.vue'
|
||||
import MailPolicySettings from './MailPolicySettings/MailPolicySettings.vue'
|
||||
|
||||
const {
|
||||
lang,
|
||||
@ -136,9 +146,15 @@ const {
|
||||
DELETE,
|
||||
FORWARD,
|
||||
reflushGroupInfos,
|
||||
groupData
|
||||
groupData,
|
||||
canReadGlobalPolicy
|
||||
} = usePage()
|
||||
const { currentPage, pageCount, pageItems } = usePagination(data, 6)
|
||||
|
||||
const mailPolicyPage = ref(null)
|
||||
const beforeLeave = () => mailPolicyPage.value?.beforeLeave?.() ?? true
|
||||
|
||||
defineExpose({ beforeLeave })
|
||||
</script>
|
||||
|
||||
<style scoped src="./RuleSettings.css"></style>
|
||||
|
||||
@ -39,7 +39,7 @@ test('配置与安全策略页面使用分组表单,控件和样式能编译
|
||||
telegram_push: { settings: null },
|
||||
wechat_push: { settings: null }
|
||||
} }
|
||||
for (const section of ['base', 'mail', 'security_policy', 'ssl', 'plugins', 'spam_block', 'telegram_push', 'wechat_push']) {
|
||||
for (const section of ['base', 'mail', 'mail_policy', 'security_policy', 'ssl', 'plugins', 'spam_block', 'telegram_push', 'wechat_push']) {
|
||||
const groups = getFieldGroups(section, config)
|
||||
assert.ok(groups.length)
|
||||
const keys = groups.flatMap(group => group.fields.map(field => field.path.join('.')))
|
||||
@ -49,21 +49,30 @@ test('配置与安全策略页面使用分组表单,控件和样式能编译
|
||||
}
|
||||
})
|
||||
|
||||
test('邮件服务页面使用统一加密方式管理监听端口,中继和策略字段保持在同一配置分区', () => {
|
||||
const config = { listener: { port: 25, starttlsPort: 587, tlsPort: 465 }, relay: { relayPort: 465 }, imap: { tlsPort: 993 }, pop3: { port: 110, tlsPort: 995 }, policy: { smtpPort: 25, imapPort: 143, pop3Port: 110 } }
|
||||
test('邮件服务页面只管理监听和中继,邮件策略使用独立表单并保持同一配置分区', () => {
|
||||
const config = {
|
||||
listener: { port: 25, starttlsPort: 587, tlsPort: 465 },
|
||||
relay: { relayPort: 465 },
|
||||
imap: { tlsPort: 993 },
|
||||
pop3: { port: 110, tlsPort: 995 },
|
||||
policy: { maxMessageBytes: 10485760, spamFilterLevel: 1, smtpPort: 25, imapPort: 143, pop3Port: 110 }
|
||||
}
|
||||
const text = JSON.stringify(config)
|
||||
const groups = getFieldGroups('mail', config)
|
||||
const policyGroups = getFieldGroups('mail_policy', config)
|
||||
const listener = groups.find(group => group.id === 'listener')
|
||||
const relay = groups.find(group => group.id === 'relay')
|
||||
const keys = groups.flatMap(group => group.fields.map(input => input.path.join('.')))
|
||||
const policyKeys = policyGroups.flatMap(group => group.fields.map(input => input.path.join('.')))
|
||||
assert.deepEqual(keys.filter(key => /^(listener|imap|pop3)\./.test(key)), [
|
||||
'listener.security', 'listener.port', 'listener.starttlsPort', 'listener.tlsPort',
|
||||
'imap.tlsPort', 'pop3.port', 'pop3.tlsPort'
|
||||
])
|
||||
assert.ok(keys.includes('relay.relayHost'))
|
||||
assert.ok(keys.includes('relay.password'))
|
||||
assert.ok(keys.includes('policy.maxMessageBytes'))
|
||||
assert.ok(keys.includes('policy.spamFilterLevel'))
|
||||
assert.ok(!keys.includes('policy.maxMessageBytes'))
|
||||
assert.ok(!keys.includes('policy.spamFilterLevel'))
|
||||
assert.deepEqual(policyKeys, ['policy.maxMessageBytes', 'policy.spamFilterLevel'])
|
||||
assert.ok(!keys.includes('policy.smtpPort'))
|
||||
assert.ok(!keys.includes('policy.imapPort'))
|
||||
assert.ok(!keys.includes('policy.pop3Port'))
|
||||
|
||||
@ -222,7 +222,7 @@ test('独立页互不覆盖,页面卸载时清除密码并丢弃较慢响应',
|
||||
const emailRequest = emailPage.loadSection()
|
||||
email.resolve({ errorNo: 0, data: { policy: { smtpPort: 25 } } })
|
||||
await emailRequest
|
||||
assert.equal(emailPage.currentSection.value.label, '邮件服务与策略')
|
||||
assert.equal(emailPage.currentSection.value.label, '邮件服务')
|
||||
assert.equal(emailPage.sectionLoading.value, false)
|
||||
base.resolve({ errorNo: 0, data: { database: { type: 'sqlite' } } })
|
||||
await baseRequest
|
||||
@ -249,9 +249,9 @@ test('同一页重复加载时,过期失败不覆盖最新请求', async () =>
|
||||
assert.deepEqual(JSON.parse(page.editorText.value), { enabled: true })
|
||||
})
|
||||
|
||||
test('配置页按需加载,邮件页和安全策略页共用 mail 分区', async () => {
|
||||
test('配置页按需加载,邮件服务、邮件策略和安全策略页共用 mail 分区', async () => {
|
||||
const requests = []
|
||||
for (const key of ['base', 'mail', 'security_policy', 'ssl', 'plugins']) {
|
||||
for (const key of ['base', 'mail', 'mail_policy', 'security_policy', 'ssl', 'plugins']) {
|
||||
const { page } = fixture(async path => { requests.push(path); return { errorNo: 0, data: {} } }, key)
|
||||
await page.loadAll()
|
||||
assert.equal(page.selectedSection.value, key)
|
||||
@ -260,11 +260,11 @@ test('配置页按需加载,邮件页和安全策略页共用 mail 分区', as
|
||||
assert.equal(page.confirmed.value, false)
|
||||
assert.equal(page.savedGeneration.value, 0)
|
||||
}
|
||||
assert.deepEqual(requests, ['base', 'mail', 'security_policy', 'ssl', 'plugins']
|
||||
.flatMap(key => ['/api/v1/config/status', `/api/v1/config/${key === 'security_policy' ? 'mail' : key}`]))
|
||||
assert.deepEqual(requests, ['base', 'mail', 'mail_policy', 'security_policy', 'ssl', 'plugins']
|
||||
.flatMap(key => ['/api/v1/config/status', `/api/v1/config/${['mail_policy', 'security_policy'].includes(key) ? 'mail' : key}`]))
|
||||
})
|
||||
|
||||
for (const key of ['mail', 'security_policy']) {
|
||||
for (const key of ['mail', 'mail_policy', 'security_policy']) {
|
||||
test(`${key} 页面验证、保存、候选恢复及应用均使用 mail 分区且保留未展示字段`, async () => {
|
||||
const requests = []
|
||||
const payloads = []
|
||||
@ -295,21 +295,25 @@ for (const key of ['mail', 'security_policy']) {
|
||||
}
|
||||
} })
|
||||
await page.loadAll()
|
||||
const path = key === 'mail' ? ['imap', 'tlsPort'] : ['relay', 'loginPolicy']
|
||||
page.updateField(path, key === 'mail' ? 1993 : 'always')
|
||||
const edit = {
|
||||
mail: { path: ['imap', 'tlsPort'], value: 1993, rejected: 8888 },
|
||||
mail_policy: { path: ['policy', 'maxMessageBytes'], value: 5678, rejected: 8765 },
|
||||
security_policy: { path: ['relay', 'loginPolicy'], value: 'always', rejected: 'off' }
|
||||
}[key]
|
||||
page.updateField(edit.path, edit.value)
|
||||
await page.validateConfig()
|
||||
await page.saveConfig()
|
||||
assert.equal(page.savedGeneration.value, 2)
|
||||
assert.equal(payloads[0].expectedGeneration, 1)
|
||||
assert.deepEqual(payloads[0].payload.listener, active.listener)
|
||||
assert.deepEqual(payloads[0].payload.pop3, active.pop3)
|
||||
if (key === 'security_policy') assert.equal(payloads[0].secretChanges['relay.password'], undefined)
|
||||
if (key !== 'mail') assert.equal(payloads[0].secretChanges['relay.password'], undefined)
|
||||
if (key === 'security_policy') {
|
||||
await page.refreshPage()
|
||||
assert.equal(page.savedGeneration.value, 2)
|
||||
}
|
||||
page.updateField(path, key === 'mail' ? 8888 : 'off')
|
||||
assert.notEqual(key === 'mail' ? page.formConfig.value.imap.tlsPort : page.formConfig.value.relay.loginPolicy, key === 'mail' ? 8888 : 'off')
|
||||
page.updateField(edit.path, edit.rejected)
|
||||
assert.notEqual(edit.path.reduce((value, part) => value?.[part], page.formConfig.value), edit.rejected)
|
||||
await page.applyConfig()
|
||||
assert.equal(page.status.activeGeneration, 2)
|
||||
assert.equal(page.savedGeneration.value, 0)
|
||||
@ -844,21 +848,21 @@ test('保存后更改密码或填写内容不能应用旧候选', async () => {
|
||||
assert.deepEqual(requests, [])
|
||||
})
|
||||
|
||||
test('邮件页与安全策略页共享候选并明确提示改动范围,忽略代次与字段顺序', async () => {
|
||||
test('邮件服务、邮件策略和安全策略页共享候选并明确提示改动范围,忽略代次与字段顺序', async () => {
|
||||
const active = {
|
||||
meta: { generation: 1 }, listener: { port: 25, starttlsPort: 587, tlsPort: 465 },
|
||||
relay: { verificationEnabled: true, loginPolicy: 'new_device' },
|
||||
imap: { tlsPort: 993 }, pop3: { port: 110, tlsPort: 995 },
|
||||
policy: { maxMessageBytes: 1234, externalRecoveryEnabled: true }
|
||||
}
|
||||
for (const key of ['mail', 'security_policy']) {
|
||||
for (const key of ['mail', 'mail_policy', 'security_policy']) {
|
||||
const { page } = fixture(async () => ({ errorNo: 0, data: active }), key)
|
||||
await page.loadSection()
|
||||
page.pendingCandidate.value = { section: 'mail', payload: { ...active, meta: { generation: 2 }, pop3: { tlsPort: 995, port: 110 } } }
|
||||
assert.match(page.pendingScopeHint.value, /邮件服务与安全策略未变化/)
|
||||
assert.match(page.pendingScopeHint.value, /邮件配置未变化/)
|
||||
page.pendingCandidate.value.payload = { ...active, pop3: { port: 1110, tlsPort: 1995 }, policy: { maxMessageBytes: 5678 } }
|
||||
assert.match(page.pendingScopeHint.value, /上次未完成的保存涉及:POP3 监听、邮件与账号策略。/)
|
||||
assert.match(page.pendingScopeHint.value, /邮件服务页和安全策略页共用一组运行配置/)
|
||||
assert.match(page.pendingScopeHint.value, /上次未完成的保存涉及:POP3 监听、邮件策略。/)
|
||||
assert.match(page.pendingScopeHint.value, /邮件服务、邮件策略和安全策略共用一组运行配置/)
|
||||
page.pendingCandidate.value = { section: 'smtp' }
|
||||
assert.equal(page.pendingScopeHint.value, '')
|
||||
page.pendingCandidate.value = { section: 'mail' }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user