新增删除节点功能

This commit is contained in:
2026-06-19 20:03:25 +08:00
parent 3fbc52c6c1
commit b56cfb7b1e
6 changed files with 117 additions and 1 deletions
+30 -1
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { adminLogout, createNodeBlockingRule, deleteNode, deleteTextMessage, getAdminMe, getHealth, getMapReportViewport, getNodeInfo, getPositions, getTextMessages } from './api'
import { adminLogout, createNodeBlockingRule, deleteNode, deleteTextMessage, getAdminMe, getHealth, getMapReportViewport, getNodeInfo, getPositions, getTextMessages, purgeNode } from './api'
import AdminBlockingManagement from './components/AdminBlockingManagement.vue'
import AdminBot from './components/AdminBot.vue'
import AdminBotDirect from './components/AdminBotDirect.vue'
@@ -75,6 +75,7 @@ type NodeActionPayload = NodeActionRequest & { reason: string }
type PendingDeleteAction =
| { kind: 'delete-message'; message: DeletableTextMessage }
| { kind: 'delete-node'; nodeId: string }
| { kind: 'purge-node'; nodeId: string }
| ({ kind: 'delete-and-block-node' } & NodeActionRequest)
let refreshTimer: number | undefined
let mapBoundsTimer: number | undefined
@@ -202,6 +203,9 @@ const deleteModalTitle = computed(() => {
if (action.kind === 'delete-node') {
return '确认删除节点'
}
if (action.kind === 'purge-node') {
return '确认删除节点'
}
return '确认删除并屏蔽节点'
})
@@ -219,6 +223,9 @@ const deleteModalMessage = computed(() => {
if (action.kind === 'delete-node') {
return '确定要删除这个节点吗?此操作不可撤销。'
}
if (action.kind === 'purge-node') {
return '确定要删除这个节点吗?将同时清理该节点的聊天消息和位置/遥测/路由/路径追踪等数据包记录,此操作不可撤销。'
}
if (!action.message) {
return '确定要删除并屏蔽这个节点吗?请输入屏蔽原因。'
}
@@ -442,6 +449,10 @@ function requestDeleteNode(nodeId: string) {
pendingDeleteAction.value = { kind: 'delete-node', nodeId }
}
function requestPurgeNode(nodeId: string) {
pendingDeleteAction.value = { kind: 'purge-node', nodeId }
}
function requestDeleteAndBlockNode(payload: NodeActionRequest) {
pendingDeleteAction.value = { kind: 'delete-and-block-node', ...payload }
}
@@ -467,6 +478,11 @@ async function confirmDeleteModal(payload: { reason?: string }) {
return
}
if (action.kind === 'purge-node') {
await purgeNodeById(action.nodeId)
return
}
const reason = payload.reason?.trim()
if (!reason) {
return
@@ -544,6 +560,17 @@ async function deleteNodeById(nodeId: string) {
}
}
async function purgeNodeById(nodeId: string) {
try {
await purgeNode(nodeId)
// 该节点的本地缓存包括聊天消息,一起从前端状态里清掉。
messages.value = messages.value.filter((message) => message.from_id !== nodeId)
await removeNodeFromLocalState(nodeId)
} catch (err) {
error.value = err instanceof Error ? err.message : String(err)
}
}
async function deleteAndBlockNode(payload: NodeActionPayload) {
try {
if (payload.message) {
@@ -737,6 +764,7 @@ onBeforeUnmount(() => {
@select-node="selectNode"
@clear-node="clearSelectedNode"
@delete-node="requestDeleteNode"
@purge-node="requestPurgeNode"
@delete-and-block-node="requestDeleteAndBlockNode"
/>
</section>
@@ -753,6 +781,7 @@ onBeforeUnmount(() => {
@select-node="selectNode"
@page-change="loadNodePage"
@delete-node="requestDeleteNode"
@purge-node="requestPurgeNode"
@delete-and-block-node="requestDeleteAndBlockNode"
/>
</template>
+5
View File
@@ -211,6 +211,11 @@ export function deleteNode(nodeId: string): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/nodes/${encodeURIComponent(nodeId)}`)
}
// 「删除节点」:除节点信息和地图上报外,再清理聊天消息以及 position/telemetry/routing/traceroute 等数据包记录。
export function purgeNode(nodeId: string): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/nodes/${encodeURIComponent(nodeId)}/purge`)
}
export function getPositions(limit = 500, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise<ListResponse<PositionRecord>> {
return getJSON<ListResponse<PositionRecord>>(listPath('/api/positions', limit, offset, nodeIdOrOptions))
}
@@ -24,6 +24,7 @@ const emit = defineEmits<{
'select-node': [nodeId: string]
'clear-node': []
'delete-node': [nodeId: string]
'purge-node': [nodeId: string]
'delete-and-block-node': [payload: { nodeId: string; nodeNum: number | null }]
'bounds-change': [payload: MapBoundsChangePayload]
'map-source-change': [sourceId: number]
@@ -146,6 +147,13 @@ function deleteSelectedNode() {
closeNodeMenu()
}
function purgeSelectedNode() {
if (menuNode.value) {
emit('purge-node', menuNode.value.node_id)
}
closeNodeMenu()
}
function deleteAndBlockSelectedNode() {
if (menuNode.value) {
emit('delete-and-block-node', {
@@ -590,6 +598,7 @@ function escapeHTML(value: string): string {
>
<a :href="nodeDetailHref(menuNode.node_id)">节点详细</a>
<button v-if="isAdmin" class="danger" type="button" @click="deleteSelectedNode">删除</button>
<button v-if="isAdmin" class="danger" type="button" @click="purgeSelectedNode">删除节点</button>
<button v-if="isAdmin" class="danger" type="button" @click="deleteAndBlockSelectedNode">删除并屏蔽节点</button>
</div>
</section>
@@ -17,6 +17,7 @@ const emit = defineEmits<{
'select-node': [nodeId: string]
'page-change': [page: number]
'delete-node': [nodeId: string]
'purge-node': [nodeId: string]
'delete-and-block-node': [payload: { nodeId: string; nodeNum: number | null }]
}>()
@@ -53,6 +54,13 @@ function deleteSelectedNode() {
closeNodeMenu()
}
function purgeSelectedNode() {
if (menuNode.value) {
emit('purge-node', menuNode.value.node_id)
}
closeNodeMenu()
}
function deleteAndBlockSelectedNode() {
if (menuNode.value) {
emit('delete-and-block-node', { nodeId: menuNode.value.node_id, nodeNum: menuNode.value.node_num ?? null })
@@ -130,6 +138,7 @@ onBeforeUnmount(() => {
>
<a :href="nodeDetailHref(menuNode.node_id)">节点详细</a>
<button v-if="isAdmin" class="danger" type="button" @click="deleteSelectedNode">删除</button>
<button v-if="isAdmin" class="danger" type="button" @click="purgeSelectedNode">删除节点</button>
<button v-if="isAdmin" class="danger" type="button" @click="deleteAndBlockSelectedNode">删除并屏蔽节点</button>
</div>