feat(frontend): align console IA with integrations sidebar
Add 对接与扩展 (IPSec, Feishu/OIDC, Plugins, Nodes), move identity out of settings onto /identity, make sessions/logs tab-deep-linkable, and rename ops audit / dial-in / admin labels to match the locked nav.
This commit is contained in:
1 parent
7f80f0f7ce
commit
f01d3dc5ea
12 files changed
+584
-262
No files matched your search
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-alert v-if="loadError" type="error" :closable="false" :title="loadError" class="session-error"><el-button size="small" @click="load">{{ $t('common.refresh') }}</el-button></el-alert>
|
||||
<el-tabs v-model="activeTab" @tab-change="onTabChange">
|
||||
<el-tab-pane :label="$t('sessions.title')" name="online">
|
||||
<el-alert v-if="sessionError" type="error" :closable="false" :title="sessionError" class="session-error"><el-button size="small" @click="loadOnline">{{ $t('common.refresh') }}</el-button></el-alert>
|
||||
<!-- 在线会话 -->
|
||||
<el-card class="page-card" shadow="never" style="margin-bottom:16px">
|
||||
<template #header>
|
||||
@@ -9,7 +11,7 @@
|
||||
<b>{{ $t('sessions.title') }}</b>
|
||||
<el-tag size="small" type="success" style="margin-left:8px">{{ $t('sessions.onlineCount', { count: sessions.length }) }}</el-tag>
|
||||
</div>
|
||||
<el-button link @click="load">{{ $t('common.refresh') }}</el-button>
|
||||
<el-button link @click="loadOnline">{{ $t('common.refresh') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="sessions" size="default" v-loading="loading">
|
||||
@@ -59,10 +61,18 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('sessions.auditLog')" name="audit">
|
||||
<el-alert v-if="auditError" type="error" :closable="false" :title="auditError" class="session-error"><el-button size="small" @click="loadAudit">{{ $t('common.refresh') }}</el-button></el-alert>
|
||||
<!-- 连接审计日志 -->
|
||||
<el-card class="page-card" shadow="never">
|
||||
<template #header><b>{{ $t('sessions.auditLog') }}</b></template>
|
||||
<el-table :data="logs" size="small" max-height="500">
|
||||
<template #header>
|
||||
<div class="row-between">
|
||||
<b>{{ $t('sessions.auditLog') }}</b>
|
||||
<el-button link @click="loadAudit">{{ $t('common.refresh') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="logs" size="small" max-height="500" v-loading="auditLoading">
|
||||
<el-table-column prop="created_at" :label="$t('common.time')" width="170">
|
||||
<template #default="{ row }">{{ new Date(row.created_at).toLocaleString() }}</template>
|
||||
</el-table-column>
|
||||
@@ -78,11 +88,14 @@
|
||||
<el-table-column prop="detail" :label="$t('common.detail')" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import api from '../api'
|
||||
@@ -90,11 +103,37 @@ import { fmtBytes } from '../utils/format'
|
||||
import { protocolColor, protocolDisplay } from '../protocols'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const sessions = ref([])
|
||||
const logs = ref([])
|
||||
const alerts = ref([])
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
/** 规范名 online / audit;兼容 ?tab=sessions|connection */
|
||||
function normalizeTab(name) {
|
||||
if (name === 'sessions' || name === 'online') return 'online'
|
||||
if (name === 'audit' || name === 'connection') return 'audit'
|
||||
return 'online'
|
||||
}
|
||||
const activeTab = ref(normalizeTab(route.query.tab))
|
||||
const sessionError = ref('')
|
||||
const auditError = ref('')
|
||||
const auditLoading = ref(false)
|
||||
|
||||
watch(() => route.query.tab, (name) => {
|
||||
const next = normalizeTab(name)
|
||||
if (activeTab.value !== next) activeTab.value = next
|
||||
}, { immediate: true })
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
// 写入规范 query:在线清空;审计固定 tab=audit(别名落地后会改写)
|
||||
const query = tab === 'online' ? {} : { tab: 'audit' }
|
||||
const cur = route.query.tab
|
||||
const needsReplace = normalizeTab(cur) !== tab
|
||||
|| (tab === 'online' && cur != null && cur !== '')
|
||||
|| (tab === 'audit' && cur !== 'audit')
|
||||
if (needsReplace) router.replace({ path: '/sessions', query })
|
||||
})
|
||||
|
||||
function displayUsername(value) {
|
||||
return typeof value === 'string' && value.trim() && value.trim().toUpperCase() !== 'UNDEF'
|
||||
@@ -116,34 +155,56 @@ function eventTagType(e) {
|
||||
return { connect: 'success', disconnect: 'info', auth_fail: 'danger', quota_exceed: 'warning', rate_alert: 'warning' }[e] || 'info'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
async function loadOnline() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [s, l, a] = await Promise.all([
|
||||
const [s, a] = await Promise.all([
|
||||
api.get('/sessions'),
|
||||
api.get('/sessions/log'),
|
||||
api.get('/traffic/alerts'),
|
||||
])
|
||||
sessions.value = s.data || []
|
||||
logs.value = l.data || []
|
||||
alerts.value = a.data || []
|
||||
loadError.value = ''
|
||||
sessionError.value = ''
|
||||
} catch (e) {
|
||||
loadError.value = e.response?.data?.error || t('sessions.loadDataFailed')
|
||||
sessionError.value = e.response?.data?.error || t('sessions.loadDataFailed')
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function loadAudit() {
|
||||
auditLoading.value = true
|
||||
try {
|
||||
const response = await api.get('/sessions/log')
|
||||
logs.value = response.data || []
|
||||
auditError.value = ''
|
||||
} catch (e) {
|
||||
auditError.value = e.response?.data?.error || t('sessions.loadDataFailed')
|
||||
} finally { auditLoading.value = false }
|
||||
}
|
||||
|
||||
function onTabChange(tab) {
|
||||
activeTab.value = tab
|
||||
if (tab === 'audit') loadAudit()
|
||||
else loadOnline()
|
||||
}
|
||||
|
||||
async function disconnect(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(t('sessions.kickConfirm', { username: row.display_name || displayUsername(row.username) }), t('common.confirm'), { type: 'warning' })
|
||||
await api.post(`/sessions/${row.id}/disconnect`)
|
||||
ElMessage.success(t('sessions.kicked'))
|
||||
load()
|
||||
loadOnline()
|
||||
} catch (e) { if (e !== 'cancel') ElMessage.error(t('common.operationFailed')) }
|
||||
}
|
||||
|
||||
let timer
|
||||
onMounted(() => { load(); timer = setInterval(load, 15000) })
|
||||
onMounted(() => {
|
||||
if (activeTab.value === 'audit') loadAudit()
|
||||
else loadOnline()
|
||||
timer = setInterval(() => {
|
||||
if (activeTab.value === 'online') loadOnline()
|
||||
else loadAudit()
|
||||
}, 15000)
|
||||
})
|
||||
onUnmounted(() => clearInterval(timer))
|
||||
</script>
|
||||
|
||||
|
||||
Reference in new issue
Block a user