612d68f56f
Go + Vue 管理台,SQLite 持久化;支持隧道/域名穿透、域名转发插件链、访客认证、 IP 规则、API Key、Docker 单镜像部署;配置通过 Web 系统设置管理,无需 YAML 文件。 Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.7 KiB
Vue
76 lines
2.7 KiB
Vue
<template>
|
|
<el-dialog v-model="visible" :title="displayTitle" width="720px" destroy-on-close @open="load">
|
|
<el-table :data="items" v-loading="loading" size="small" class="table-scroll--lg">
|
|
<template v-if="type === 'tunnels'">
|
|
<el-table-column prop="name" :label="t('common.name')" />
|
|
<el-table-column prop="listen_port" :label="t('dashboard.listenPort')" width="80" />
|
|
<el-table-column :label="t('common.traffic')">
|
|
<template #default="{ row }">{{ formatBytes((row.inlet_flow || 0) + (row.export_flow || 0)) }}</template>
|
|
</el-table-column>
|
|
</template>
|
|
<template v-else-if="type === 'hosts'">
|
|
<el-table-column prop="host" :label="t('dashboard.host')" />
|
|
<el-table-column prop="target_addr" :label="t('common.target')" />
|
|
<el-table-column :label="t('common.traffic')">
|
|
<template #default="{ row }">{{ formatBytes((row.inlet_flow || 0) + (row.export_flow || 0)) }}</template>
|
|
</el-table-column>
|
|
</template>
|
|
<template v-else>
|
|
<el-table-column prop="ip" :label="t('dashboard.visitorIp')" min-width="120" />
|
|
<el-table-column prop="direct_ip" :label="t('dashboard.directIp')" min-width="120" />
|
|
<el-table-column prop="hit_count" :label="t('dashboard.hitCount')" width="80" />
|
|
<el-table-column prop="resource_type" :label="t('common.resource')" width="80" />
|
|
</template>
|
|
</el-table>
|
|
<div class="card-pager">
|
|
<el-pagination
|
|
v-model:current-page="page"
|
|
v-model:page-size="pageSize"
|
|
:total="total"
|
|
layout="total, prev, pager, next"
|
|
@current-change="load"
|
|
/>
|
|
</div>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, computed } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import api from '@/api'
|
|
import { formatBytes } from '@/utils/format'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps({
|
|
modelValue: Boolean,
|
|
type: { type: String, required: true },
|
|
title: { type: String, default: '' },
|
|
})
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const visible = ref(false)
|
|
const items = ref([])
|
|
const total = ref(0)
|
|
const page = ref(1)
|
|
const pageSize = ref(50)
|
|
const loading = ref(false)
|
|
const displayTitle = computed(() => props.title || t('rank.defaultTitle'))
|
|
|
|
watch(() => props.modelValue, (v) => { visible.value = v })
|
|
watch(visible, (v) => emit('update:modelValue', v))
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const { data } = await api.get('/dashboard/rankings', {
|
|
params: { type: props.type, page: page.value, page_size: pageSize.value },
|
|
})
|
|
items.value = data.items || []
|
|
total.value = data.total || 0
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|