docs+feat: record backlog tip and move custom protocols to More

Document delivered IA and backlog four items; put custom plugins under
the wizard More group per design.
This commit is contained in:
Codex committed 2026-10-23 16:17:43 +08:00
1 parent 9984e21930
commit 76bfda8627
8 files changed
+50 -10

No files matched your search

+2 -1
View File
@@ -365,7 +365,8 @@
"installFailed": "Failed to install {protocol}",
"groupEmployee": "Employee access",
"groupSite": "Site interconnect",
"groupPenetrate": "Intranet penetration"
"groupPenetrate": "Intranet penetration",
"groupMore": "More"
},
"users": {
"title": "Admins",
+2 -1
View File
@@ -365,7 +365,8 @@
"installFailed": "{protocol} 安装失败",
"groupEmployee": "员工接入",
"groupSite": "站点对接",
"groupPenetrate": "内网穿透"
"groupPenetrate": "内网穿透",
"groupMore": "更多"
},
"users": {
"title": "管理员",
+1 -1
View File
@@ -123,7 +123,7 @@ watch(() => route.query.tab, (name) => {
router.replace({ path: '/sessions', query: { tab: 'audit' } })
return
}
const next = TAB_NAMES.includes(name) ? name : 'audit'
const next = normalizeLogTab(name)
if (activeTab.value !== next) activeTab.value = next
}, { immediate: true })
+8 -3
View File
@@ -67,6 +67,7 @@
<div class="row-between">
<div class="filter-bar">
<el-input v-model="auditFilters.username" :placeholder="$t('common.username')" clearable size="small" style="width:120px" @clear="searchAudit" @keyup.enter="searchAudit" />
<el-input v-model="auditFilters.keyword" :placeholder="$t('logs.keywordPlaceholder')" clearable size="small" style="width:160px" @clear="searchAudit" @keyup.enter="searchAudit" />
<el-select v-model="auditFilters.event" :placeholder="$t('logs.eventType')" clearable multiple size="small" style="width:220px" @change="searchAudit">
<el-option :label="$t('sessions.eventConnect')" value="connect" />
<el-option :label="$t('sessions.eventDisconnect')" value="disconnect" />
@@ -146,7 +147,7 @@ const auditExporting = ref(false)
const auditPage = ref(1)
const auditPageSize = ref(20)
const auditTotal = ref(0)
const auditFilters = reactive({ username: '', event: [] })
const auditFilters = reactive({ username: '', keyword: '', event: [] })
const auditDateRange = ref(null)
watch(() => route.query.tab, (name) => {
@@ -209,7 +210,8 @@ async function loadAudit() {
const params = {
page: auditPage.value,
page_size: auditPageSize.value,
username: auditFilters.username,
username: auditFilters.username || undefined,
keyword: auditFilters.keyword || undefined,
}
if (auditFilters.event?.length) params.event = auditFilters.event.join(',')
if (auditDateRange.value) {
@@ -227,7 +229,10 @@ async function loadAudit() {
async function exportAudit() {
auditExporting.value = true
const params = { username: auditFilters.username }
const params = {
username: auditFilters.username || undefined,
keyword: auditFilters.keyword || undefined,
}
if (auditFilters.event?.length) params.event = auditFilters.event.join(',')
if (auditDateRange.value) {
params.start = auditDateRange.value[0]
+3 -2
View File
@@ -9,7 +9,7 @@
<!-- 第一步:协议选择 -->
<el-alert v-if="step === 0" type="info" :closable="false" :title="$t('tunnelWizard.onDemandHint')" style="margin-bottom:16px" />
<div v-if="step === 0" class="proto-groups">
<div v-for="group in protocolGroups" :key="group.id" class="proto-group">
<div v-for="group in protocolGroups.filter(g => g.keys.some(k => builtin[k]) || (g.id === 'more' && customProtocols.length))" :key="group.id" class="proto-group">
<div class="proto-group-title">{{ $t(group.titleKey) }}</div>
<div class="proto-grid">
<div v-for="key in group.keys.filter(k => builtin[k])" :key="key"
@@ -32,7 +32,7 @@
</div>
</div>
</div>
<template v-if="group.id === 'penetrate'">
<template v-if="group.id === 'more'">
<div v-for="p in customProtocols" :key="p.name" class="proto-card" :class="{ active: form.protocol === p.name }"
@click="selectProtocol(p.name)">
<div class="proto-icon"><ProtocolIcon protocol="custom" :size="36" /></div>
@@ -177,6 +177,7 @@ const protocolGroups = [
{ id: 'employee', titleKey: 'tunnelWizard.groupEmployee', keys: ['wireguard', 'ikev2', 'l2tp', 'openvpn'] },
{ id: 'site', titleKey: 'tunnelWizard.groupSite', keys: ['ipsec', 'gre'] },
{ id: 'penetrate', titleKey: 'tunnelWizard.groupPenetrate', keys: ['frp'] },
{ id: 'more', titleKey: 'tunnelWizard.groupMore', keys: [] },
]
const { t } = useI18n()
+17 -2
View File
@@ -118,7 +118,7 @@
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { Plus, InfoFilled, ArrowDown, Refresh } from '@element-plus/icons-vue'
@@ -141,7 +141,14 @@ const changingIds = ref(new Set())
let loadRevision = 0
const keyword = ref('')
const protocolFilter = ref('')
const stateFilter = ref(typeof route.query.state === 'string' ? route.query.state : '')
function initialStateFilter() {
const f = route.query.filter
const s = route.query.state
if (f === 'unhealthy') return 'error'
if (typeof s === 'string' && s) return s
return ''
}
const stateFilter = ref(initialStateFilter())
const protocolOptions = ref([])
// 批量操作
const selectedRows = ref([])
@@ -268,6 +275,14 @@ function download(row) {
})
.catch(e => ElMessage.error(e.message || t('tunnels.downloadFailed')))
}
watch(() => [route.query.filter, route.query.state], () => {
const next = initialStateFilter()
if (stateFilter.value !== next) {
stateFilter.value = next
load()
}
})
let timer
onMounted(async () => {
load()