Files
Prism/web/src/views/TrafficLogsView.vue
T
renjue fe8ea784d0
CI / docker (push) Successful in 2m54s
feat: Prism HTTP/SOCKS5 代理网关及管理台
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 15:58:28 +08:00

120 lines
3.8 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import api from '@/api/client'
import PageHeader from '@/components/PageHeader.vue'
import AppCard from '@/components/AppCard.vue'
const { t } = useI18n()
interface TrafficLog {
id: number
host: string
network: string
rule_line: string
outbound_node: string
outbound_chain: string
upload: number
download: number
success: boolean
started_at: string
ended_at: string
}
const items = ref<TrafficLog[]>([])
const total = ref(0)
const loading = ref(false)
const keyword = ref('')
const page = ref(1)
const pageSize = ref(50)
const successCount = computed(() => items.value.filter((i) => i.success).length)
function formatBytes(n: number) {
if (n < 1024) return `${n} B`
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
return `${(n / 1024 / 1024).toFixed(2)} MB`
}
async function load() {
loading.value = true
try {
const { data } = await api.get('/traffic-logs', {
params: {
keyword: keyword.value,
limit: pageSize.value,
offset: (page.value - 1) * pageSize.value,
},
})
items.value = data.items || []
total.value = data.total || 0
} finally {
loading.value = false
}
}
onMounted(load)
</script>
<template>
<div class="page">
<PageHeader :title="t('trafficLogs.title')" :description="t('trafficLogs.desc')">
<template #actions>
<el-button @click="load">{{ t('common.refresh') }}</el-button>
</template>
</PageHeader>
<AppCard>
<div class="card-toolbar">
<el-input
v-model="keyword"
:placeholder="t('trafficLogs.searchPlaceholder')"
clearable
class="toolbar-input--wide"
@keyup.enter="() => { page = 1; load() }"
/>
<el-button type="primary" @click="() => { page = 1; load() }">{{ t('common.search') }}</el-button>
<span v-if="items.length" class="form-hint form-hint--flush">
{{ t('trafficLogs.pageSuccess', { ok: successCount, total: items.length }) }}
</span>
</div>
<el-table :data="items" v-loading="loading" size="small" class="table-scroll--lg">
<el-table-column prop="started_at" :label="t('common.time')" width="168" />
<el-table-column prop="host" :label="t('trafficLogs.target')" min-width="200" show-overflow-tooltip />
<el-table-column prop="rule_line" :label="t('trafficLogs.matchedRule')" min-width="180" show-overflow-tooltip />
<el-table-column prop="outbound_node" :label="t('trafficLogs.outboundNode')" min-width="140" show-overflow-tooltip />
<el-table-column :label="t('trafficLogs.chain')" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
{{ row.outbound_chain || t('common.none') }}
</template>
</el-table-column>
<el-table-column :label="t('trafficLogs.traffic')" width="120">
<template #default="{ row }">
{{ formatBytes(row.upload) }} / {{ formatBytes(row.download) }}
</template>
</el-table-column>
<el-table-column :label="t('trafficLogs.result')" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.success ? 'success' : 'danger'" size="small">
{{ row.success ? t('common.success') : t('common.failed') }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div class="card-pager">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 500]"
:total="total"
layout="total, sizes, prev, pager, next"
@current-change="load"
@size-change="() => { page = 1; load() }"
/>
</div>
</AppCard>
</div>
</template>