基本功能差不多完成

This commit is contained in:
2026-06-04 09:52:57 +08:00
parent c441fed1b3
commit f73d79b7d4
15 changed files with 395 additions and 14 deletions
@@ -62,7 +62,7 @@ onBeforeUnmount(() => {
<div><span>当前连接</span><strong>{{ status.clients_connected }}</strong></div>
<div><span>订阅数</span><strong>{{ status.subscriptions }}</strong></div>
<div><span>转发消息</span><strong>{{ status.messages_sent }}</strong></div>
<div><span>丢弃消息</span><strong>{{ status.messages_dropped }}</strong></div>
<a class="status-card-link" href="/admin/discard_details"><span>丢弃消息</span><strong>{{ status.messages_dropped }}</strong></a>
<div><span>收到包</span><strong>{{ status.packets_received }}</strong></div>
<div><span>发送包</span><strong>{{ status.packets_sent }}</strong></div>
</div>
@@ -0,0 +1,94 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getDiscardDetails } from '../api'
import type { DiscardDetails } from '../types'
const items = ref<DiscardDetails[]>([])
const loading = ref(false)
const error = ref('')
const page = ref(1)
const pageSize = 25
const canPrev = () => page.value > 1
const canNext = () => items.value.length === pageSize
function formatTime(value: string): string {
return new Date(value).toLocaleString()
}
async function refreshItems() {
loading.value = true
error.value = ''
try {
const response = await getDiscardDetails(pageSize, (page.value - 1) * pageSize)
items.value = response.items
} catch (err) {
error.value = err instanceof Error ? err.message : String(err)
} finally {
loading.value = false
}
}
function changePage(nextPage: number) {
page.value = Math.max(1, nextPage)
refreshItems()
}
onMounted(refreshItems)
</script>
<template>
<section class="admin-dashboard">
<div class="panel admin-status-panel">
<div class="panel-header">
<div>
<p class="eyebrow">Discard details</p>
<h2>丢弃数据</h2>
</div>
<button class="admin-button" @click="refreshItems" :disabled="loading">{{ loading ? '刷新中...' : '刷新数据' }}</button>
</div>
<p v-if="error" class="error">{{ error }}</p>
<div class="node-table-wrap">
<table class="node-table">
<thead>
<tr>
<th>时间</th>
<th>Topic</th>
<th>Error</th>
<th>Payload Len</th>
<th>Client ID</th>
<th>Username</th>
<th>Listener</th>
<th>Remote Host</th>
<th>Raw Base64</th>
<th>Content JSON</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id">
<td>{{ formatTime(item.created_at) }}</td>
<td>{{ item.topic || '-' }}</td>
<td>{{ item.error || '-' }}</td>
<td>{{ item.payload_len }}</td>
<td>{{ item.mqtt_client_id || '-' }}</td>
<td>{{ item.mqtt_username || '-' }}</td>
<td>{{ item.mqtt_listener || '-' }}</td>
<td>{{ item.mqtt_remote_host || '-' }}</td>
<td><pre class="discard-raw">{{ item.raw_base64 }}</pre></td>
<td><pre class="discard-json">{{ item.content_json }}</pre></td>
</tr>
</tbody>
</table>
<div v-if="items.length === 0" class="empty">暂无丢弃数据</div>
</div>
<div class="pagination">
<button :disabled="loading || !canPrev()" @click="changePage(page - 1)">上一页</button>
<span> {{ page }} </span>
<span>每页 {{ pageSize }} </span>
<button :disabled="loading || !canNext()" @click="changePage(page + 1)">下一页</button>
</div>
</div>
</section>
</template>
@@ -0,0 +1,39 @@
<template>
<section class="help-page">
<div class="panel">
<div class="panel-header">
<div>
<p class="eyebrow">Help</p>
<h2>如何连接 MQTT</h2>
</div>
</div>
<div class="help-content">
<section>
<h3>连接地址</h3>
<p> Meshtastic 设备或客户端连接到本服务提供的 MQTT broker</p>
<ul>
<li>默认地址<strong>localhost</strong></li>
<li>默认端口<strong>1883</strong></li>
<li>如果部署在其他机器请使用服务器 IP 或域名</li>
</ul>
</section>
<section>
<h3>频道加密要求</h3>
<p>为了让服务能够解析 Meshtastic MQTT payload频道需要满足以下任一条件</p>
<ul>
<li>频道不加密</li>
<li>使用 Meshtastic 默认 PSK<strong>AQ==</strong></li>
</ul>
<p>如果使用自定义加密密钥需要在服务配置中设置对应的 <strong>meshtastic.psk</strong>否则数据可能会被判定为无法解密并丢弃</p>
</section>
<section>
<h3>Topic 说明</h3>
<p>客户端发布到 Meshtastic MQTT topic 服务会先解析 payload解析成功的数据会被转发并写入数据库解析失败的数据会被丢弃并记录到后台的丢弃数据页面</p>
</section>
</div>
</div>
</section>
</template>