Files
meshtastic_mqtt_server/meshmap_frontend/src/api.ts
T

540 lines
22 KiB
TypeScript

import type {
AdminLoginLogsResponse,
AdminLoginResponse,
AdminManagedUserResponse,
AdminMqttStatus,
AdminRuntimeSettingsPayload,
AdminRuntimeSettingsResponse,
AdminUsersResponse,
BotMessage,
BotMessageMutationResponse,
BotNode,
BotNodeMutationResponse,
BotNodePayload,
BotSendMessagePayload,
BlockingRuleResponse,
DiscardDetails,
ForbiddenWordBlockingRule,
ForbiddenWordBlockingRulePayload,
HealthStatus,
HelpContentResponse,
HelpPreviewResponse,
IPBlockingRule,
IPBlockingRulePayload,
ListResponse,
MapBoundsQuery,
MapReport,
MapTileSource,
MapTileSourcePayload,
MapTileSourceResponse,
MapViewportResponse,
MQTTForwarder,
MQTTForwarderPayload,
MQTTForwardMutationResponse,
MQTTForwardStatusResponse,
MQTTForwardTopic,
MQTTForwardTopicPayload,
NodeBlockingRule,
NodeBlockingRulePayload,
NodeInfo,
PositionRecord,
PublicMapTileSourceResponse,
PublicMapTileSourcesResponse,
SignDayCount,
SignRecord,
SignRecordPayload,
TelemetryRecord,
TextMessage,
BotDirectMessage,
BotDirectConversationsResponse,
LLMMessage,
LLMMessageStatus,
LLMProvider,
LLMProviderPayload,
LLMProviderResponse,
LLMPlatformRouterPayload,
LLMPlatformRouterResponse,
LLMPrimaryConfigPayload,
LLMPrimaryConfigResponse,
} from './types'
async function requestJSON<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(path, { credentials: 'same-origin', ...init })
if (!response.ok) {
let message = `${response.status} ${response.statusText}`
try {
const data = (await response.json()) as { error?: string }
if (data.error) {
message = data.error
}
} catch {
// Keep the HTTP status message when the response is not JSON.
}
throw new Error(message)
}
return response.json() as Promise<T>
}
type ListQueryOptions = {
nodeId?: string
channelId?: string
since?: string
until?: string
}
function listPath(path: string, limit: number, offset: number, nodeIdOrOptions: string | ListQueryOptions = ''): string {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) })
const options = typeof nodeIdOrOptions === 'string' ? { nodeId: nodeIdOrOptions } : nodeIdOrOptions
if (options.nodeId) {
params.set('node_id', options.nodeId)
}
if (options.channelId) {
params.set('channel_id', options.channelId)
}
if (options.since) {
params.set('since', options.since)
}
if (options.until) {
params.set('until', options.until)
}
return `${path}?${params.toString()}`
}
function getJSON<T>(path: string): Promise<T> {
return requestJSON<T>(path)
}
function postJSON<T>(path: string, body?: unknown): Promise<T> {
return requestJSON<T>(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: body == null ? undefined : JSON.stringify(body),
})
}
function putJSON<T>(path: string, body?: unknown): Promise<T> {
return requestJSON<T>(path, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: body == null ? undefined : JSON.stringify(body),
})
}
function deleteJSON<T>(path: string): Promise<T> {
return requestJSON<T>(path, { method: 'DELETE' })
}
export function getHealth(): Promise<HealthStatus> {
return getJSON<HealthStatus>('/api/health')
}
export function getHelpContent(): Promise<HelpContentResponse> {
return getJSON<HelpContentResponse>('/api/help')
}
export function getNodeInfo(limit = 500, offset = 0): Promise<ListResponse<NodeInfo>> {
return getJSON<ListResponse<NodeInfo>>(listPath('/api/nodeinfo', limit, offset))
}
export function getNodeInfoById(nodeId: string): Promise<NodeInfo> {
return getJSON<NodeInfo>(`/api/nodeinfo/${encodeURIComponent(nodeId)}`)
}
export function getMapReports(limit = 500, offset = 0, bounds?: MapBoundsQuery): Promise<ListResponse<MapReport>> {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) })
if (bounds) {
params.set('min_lat', String(bounds.min_lat))
params.set('max_lat', String(bounds.max_lat))
params.set('min_lng', String(bounds.min_lng))
params.set('max_lng', String(bounds.max_lng))
}
return getJSON<ListResponse<MapReport>>(`/api/map-reports?${params.toString()}`)
}
export function getMapReportById(nodeId: string): Promise<MapReport> {
return getJSON<MapReport>(`/api/map-reports/${encodeURIComponent(nodeId)}`)
}
export function getMapReportViewport(bounds: MapBoundsQuery, zoom: number, limit = 1000): Promise<MapViewportResponse> {
const params = new URLSearchParams({
min_lat: String(bounds.min_lat),
max_lat: String(bounds.max_lat),
min_lng: String(bounds.min_lng),
max_lng: String(bounds.max_lng),
zoom: String(zoom),
limit: String(limit),
})
return getJSON<MapViewportResponse>(`/api/map-reports/viewport?${params.toString()}`)
}
export function getDefaultMapSource(): Promise<PublicMapTileSourceResponse> {
return getJSON<PublicMapTileSourceResponse>('/api/map-source/default')
}
export function getEnabledMapSources(): Promise<PublicMapTileSourcesResponse> {
return getJSON<PublicMapTileSourcesResponse>('/api/map-source/enabled')
}
export function getTextMessages(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise<ListResponse<TextMessage>> {
return getJSON<ListResponse<TextMessage>>(listPath('/api/text-messages', limit, offset, nodeIdOrOptions))
}
export function getSignRecords(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise<ListResponse<SignRecord>> {
return getJSON<ListResponse<SignRecord>>(listPath('/api/signs', limit, offset, nodeIdOrOptions))
}
export function getSignDailyCounts(nodeIdOrOptions: string | ListQueryOptions = ''): Promise<ListResponse<SignDayCount>> {
return getJSON<ListResponse<SignDayCount>>(listPath('/api/signs/daily', 500, 0, nodeIdOrOptions))
}
export function getAdminSignRecords(limit = 100, offset = 0, nodeIdOrOptions: string | ListQueryOptions = ''): Promise<ListResponse<SignRecord>> {
return getJSON<ListResponse<SignRecord>>(listPath('/api/admin/signs', limit, offset, nodeIdOrOptions))
}
export function createAdminSignRecord(payload: SignRecordPayload): Promise<{ item: SignRecord }> {
return postJSON<{ item: SignRecord }>('/api/admin/signs', payload)
}
export function updateAdminSignRecord(id: number, payload: SignRecordPayload): Promise<{ item: SignRecord }> {
return putJSON<{ item: SignRecord }>(`/api/admin/signs/${id}`, payload)
}
export function deleteAdminSignRecord(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/signs/${id}`)
}
export function deleteTextMessage(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/text-messages/${id}`)
}
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))
}
export function getDiscardDetails(limit = 100, offset = 0): Promise<ListResponse<DiscardDetails>> {
return getJSON<ListResponse<DiscardDetails>>(listPath('/api/discard-details', limit, offset))
}
export function getTelemetry(limit = 500, offset = 0, nodeId = ''): Promise<ListResponse<TelemetryRecord>> {
return getJSON<ListResponse<TelemetryRecord>>(listPath('/api/telemetry', limit, offset, nodeId))
}
export function adminLogin(username: string, password: string): Promise<AdminLoginResponse> {
return postJSON<AdminLoginResponse>('/api/admin/login', { username, password })
}
export function adminLogout(): Promise<{ status: string }> {
return postJSON<{ status: string }>('/api/admin/logout')
}
export function getAdminMe(): Promise<AdminLoginResponse> {
return getJSON<AdminLoginResponse>('/api/admin/me')
}
export function getAdminMqttStatus(): Promise<AdminMqttStatus> {
return getJSON<AdminMqttStatus>('/api/admin/mqtt/status')
}
// 一键断开 MQTT 客户端并把它的远端 IP 加入屏蔽表。
export function disconnectAndBlockMqttClient(
clientId: string,
payload: { reason?: string } = {},
): Promise<{
status: string
client_id: string
ip_value: string
disconnected: boolean
ip_rule_created: boolean
ip_rule_id?: number
}> {
return postJSON(`/api/admin/mqtt/clients/${encodeURIComponent(clientId)}/disconnect-and-block`, payload)
}
export function getAdminRuntimeSettings(): Promise<AdminRuntimeSettingsResponse> {
return getJSON<AdminRuntimeSettingsResponse>('/api/admin/runtime-settings')
}
export function updateAdminRuntimeSettings(payload: AdminRuntimeSettingsPayload): Promise<AdminRuntimeSettingsResponse> {
return putJSON<AdminRuntimeSettingsResponse>('/api/admin/runtime-settings', payload)
}
export function getAdminHelpContent(): Promise<HelpContentResponse> {
return getJSON<HelpContentResponse>('/api/admin/help')
}
export function saveAdminHelpContent(markdown: string): Promise<HelpContentResponse> {
return postJSON<HelpContentResponse>('/api/admin/help', { markdown })
}
export function previewAdminHelpContent(markdown: string): Promise<HelpPreviewResponse> {
return postJSON<HelpPreviewResponse>('/api/admin/help/preview', { markdown })
}
export function getAdminUsers(): Promise<AdminUsersResponse> {
return getJSON<AdminUsersResponse>('/api/admin/users')
}
export function createAdminUser(username: string, password: string): Promise<AdminManagedUserResponse> {
return postJSON<AdminManagedUserResponse>('/api/admin/users', { username, password })
}
export function updateAdminUserPassword(id: number, password: string): Promise<AdminManagedUserResponse> {
return putJSON<AdminManagedUserResponse>(`/api/admin/users/${id}/password`, { password })
}
export function getAdminLoginLogs(limit = 100, offset = 0): Promise<AdminLoginLogsResponse> {
return getJSON<AdminLoginLogsResponse>(`/api/admin/log/login?limit=${limit}&offset=${offset}`)
}
export function getAdminMapSources(limit = 100, offset = 0): Promise<ListResponse<MapTileSource>> {
return getJSON<ListResponse<MapTileSource>>(listPath('/api/admin/map-source', limit, offset))
}
export function createAdminMapSource(payload: MapTileSourcePayload): Promise<MapTileSourceResponse> {
return postJSON<MapTileSourceResponse>('/api/admin/map-source', payload)
}
export function updateAdminMapSource(id: number, payload: MapTileSourcePayload): Promise<MapTileSourceResponse> {
return putJSON<MapTileSourceResponse>(`/api/admin/map-source/${id}`, payload)
}
export function deleteAdminMapSource(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/map-source/${id}`)
}
export function setDefaultAdminMapSource(id: number): Promise<MapTileSourceResponse> {
return postJSON<MapTileSourceResponse>(`/api/admin/map-source/${id}/default`)
}
export function getNodeBlockingRules(limit = 100, offset = 0): Promise<ListResponse<NodeBlockingRule>> {
return getJSON<ListResponse<NodeBlockingRule>>(listPath('/api/admin/blocking/nodes', limit, offset))
}
export function createNodeBlockingRule(payload: NodeBlockingRulePayload): Promise<BlockingRuleResponse<NodeBlockingRule>> {
return postJSON<BlockingRuleResponse<NodeBlockingRule>>('/api/admin/blocking/nodes', payload)
}
export function updateNodeBlockingRule(id: number, payload: NodeBlockingRulePayload): Promise<BlockingRuleResponse<NodeBlockingRule>> {
return putJSON<BlockingRuleResponse<NodeBlockingRule>>(`/api/admin/blocking/nodes/${id}`, payload)
}
export function deleteNodeBlockingRule(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/blocking/nodes/${id}`)
}
export function getIPBlockingRules(limit = 100, offset = 0): Promise<ListResponse<IPBlockingRule>> {
return getJSON<ListResponse<IPBlockingRule>>(listPath('/api/admin/blocking/ips', limit, offset))
}
export function createIPBlockingRule(payload: IPBlockingRulePayload): Promise<BlockingRuleResponse<IPBlockingRule>> {
return postJSON<BlockingRuleResponse<IPBlockingRule>>('/api/admin/blocking/ips', payload)
}
export function updateIPBlockingRule(id: number, payload: IPBlockingRulePayload): Promise<BlockingRuleResponse<IPBlockingRule>> {
return putJSON<BlockingRuleResponse<IPBlockingRule>>(`/api/admin/blocking/ips/${id}`, payload)
}
export function deleteIPBlockingRule(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/blocking/ips/${id}`)
}
export function getForbiddenWordBlockingRules(limit = 100, offset = 0): Promise<ListResponse<ForbiddenWordBlockingRule>> {
return getJSON<ListResponse<ForbiddenWordBlockingRule>>(listPath('/api/admin/blocking/words', limit, offset))
}
export function createForbiddenWordBlockingRule(payload: ForbiddenWordBlockingRulePayload): Promise<BlockingRuleResponse<ForbiddenWordBlockingRule>> {
return postJSON<BlockingRuleResponse<ForbiddenWordBlockingRule>>('/api/admin/blocking/words', payload)
}
export function updateForbiddenWordBlockingRule(id: number, payload: ForbiddenWordBlockingRulePayload): Promise<BlockingRuleResponse<ForbiddenWordBlockingRule>> {
return putJSON<BlockingRuleResponse<ForbiddenWordBlockingRule>>(`/api/admin/blocking/words/${id}`, payload)
}
export function deleteForbiddenWordBlockingRule(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/blocking/words/${id}`)
}
export function getMQTTForwarders(limit = 100, offset = 0): Promise<ListResponse<MQTTForwarder>> {
return getJSON<ListResponse<MQTTForwarder>>(listPath('/api/admin/mqtt-forward/forwarders', limit, offset))
}
export function createMQTTForwarder(payload: MQTTForwarderPayload): Promise<MQTTForwardMutationResponse<MQTTForwarder>> {
return postJSON<MQTTForwardMutationResponse<MQTTForwarder>>('/api/admin/mqtt-forward/forwarders', payload)
}
export function updateMQTTForwarder(id: number, payload: MQTTForwarderPayload): Promise<MQTTForwardMutationResponse<MQTTForwarder>> {
return putJSON<MQTTForwardMutationResponse<MQTTForwarder>>(`/api/admin/mqtt-forward/forwarders/${id}`, payload)
}
export function deleteMQTTForwarder(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/mqtt-forward/forwarders/${id}`)
}
export function restartMQTTForwarder(id: number): Promise<{ status: string }> {
return postJSON<{ status: string }>(`/api/admin/mqtt-forward/forwarders/${id}/restart`)
}
export function getMQTTForwardTopics(forwarderId: number, limit = 100, offset = 0): Promise<ListResponse<MQTTForwardTopic>> {
return getJSON<ListResponse<MQTTForwardTopic>>(listPath(`/api/admin/mqtt-forward/forwarders/${forwarderId}/topics`, limit, offset))
}
export function createMQTTForwardTopic(forwarderId: number, payload: MQTTForwardTopicPayload): Promise<MQTTForwardMutationResponse<MQTTForwardTopic>> {
return postJSON<MQTTForwardMutationResponse<MQTTForwardTopic>>(`/api/admin/mqtt-forward/forwarders/${forwarderId}/topics`, payload)
}
export function updateMQTTForwardTopic(id: number, payload: MQTTForwardTopicPayload): Promise<MQTTForwardMutationResponse<MQTTForwardTopic>> {
return putJSON<MQTTForwardMutationResponse<MQTTForwardTopic>>(`/api/admin/mqtt-forward/topics/${id}`, payload)
}
export function deleteMQTTForwardTopic(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/mqtt-forward/topics/${id}`)
}
export function getMQTTForwardStatus(): Promise<MQTTForwardStatusResponse> {
return getJSON<MQTTForwardStatusResponse>('/api/admin/mqtt-forward/status')
}
export function getBotNodes(limit = 100, offset = 0): Promise<ListResponse<BotNode>> {
return getJSON<ListResponse<BotNode>>(listPath('/api/admin/bot/nodes', limit, offset))
}
export function createBotNode(payload: BotNodePayload): Promise<BotNodeMutationResponse> {
return postJSON<BotNodeMutationResponse>('/api/admin/bot/nodes', payload)
}
export function updateBotNode(id: number, payload: BotNodePayload): Promise<BotNodeMutationResponse> {
return putJSON<BotNodeMutationResponse>(`/api/admin/bot/nodes/${id}`, payload)
}
export function deleteBotNode(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/bot/nodes/${id}`)
}
export function broadcastBotNodeInfo(id: number): Promise<BotNodeMutationResponse> {
return postJSON<BotNodeMutationResponse>(`/api/admin/bot/nodes/${id}/nodeinfo`)
}
export function regenerateBotNodeKeys(id: number): Promise<BotNodeMutationResponse> {
return postJSON<BotNodeMutationResponse>(`/api/admin/bot/nodes/${id}/keys/regenerate`)
}
export function getBotMessages(botId = 0, limit = 100, offset = 0): Promise<ListResponse<BotMessage>> {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) })
if (botId > 0) {
params.set('bot_id', String(botId))
}
return getJSON<ListResponse<BotMessage>>(`/api/admin/bot/messages?${params.toString()}`)
}
export function getBotDirectMessages(botId: number, targetNodeNum: number, limit = 100, offset = 0, direction = ''): Promise<ListResponse<BotDirectMessage>> {
const params = new URLSearchParams({ bot_id: String(botId), target_node_num: String(targetNodeNum), limit: String(limit), offset: String(offset) })
if (direction) {
params.set('direction', direction)
}
return getJSON<ListResponse<BotDirectMessage>>(`/api/admin/bot/direct-messages?${params.toString()}`)
}
export function getBotConversations(botId: number, limit = 100, offset = 0): Promise<BotDirectConversationsResponse> {
const params = new URLSearchParams({ bot_id: String(botId), limit: String(limit), offset: String(offset) })
return getJSON<BotDirectConversationsResponse>(`/api/admin/bot/conversations?${params.toString()}`)
}
export function markBotDirectMessagesRead(botId: number, peerNodeNum: number): Promise<{ updated: number }> {
return postJSON<{ updated: number }>('/api/admin/bot/direct-messages/read', { bot_id: botId, peer_node_num: peerNodeNum })
}
// 静默使用未导出类型,避免 TS6133(未使用的导入)。
export type { BotDirectConversation } from './types'
export function sendBotMessage(payload: BotSendMessagePayload): Promise<BotMessageMutationResponse> {
return postJSON<BotMessageMutationResponse>('/api/admin/bot/messages', payload)
}
// LLM 消息队列 API
export function getLLMMessages(limit = 100, offset = 0, botId?: number, includeDeleted = false): Promise<ListResponse<LLMMessage>> {
const params = new URLSearchParams({ limit: String(limit), offset: String(offset) })
if (botId !== undefined && botId > 0) {
params.set('bot_id', String(botId))
}
if (includeDeleted) {
params.set('include_deleted', 'true')
}
return getJSON<ListResponse<LLMMessage>>(`/api/admin/llm/messages?${params.toString()}`)
}
export function getLLMMessage(id: number): Promise<{ item: LLMMessage }> {
return getJSON<{ item: LLMMessage }>(`/api/admin/llm/messages/${id}`)
}
export function updateLLMMessageStatus(id: number, status: LLMMessageStatus, error?: string): Promise<{ status: string }> {
return putJSON<{ status: string }>(`/api/admin/llm/messages/${id}/status`, { status, error })
}
export function deleteLLMMessage(id: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/llm/messages/${id}`)
}
export function deleteLLMMessagesByBot(botId: number): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/llm/bots/${botId}/messages`)
}
export function cleanupDeletedLLMMessages(days = 7): Promise<{ status: string; deleted_count: number }> {
return postJSON<{ status: string; deleted_count: number }>('/api/admin/llm/messages/cleanup', { days })
}
// LLM Provider API
export function getLLMProviders(includeInactive = false): Promise<ListResponse<LLMProvider>> {
const params = new URLSearchParams()
if (includeInactive) {
params.set('include_inactive', 'true')
}
const query = params.toString() ? `?${params.toString()}` : ''
return getJSON<ListResponse<LLMProvider>>(`/api/admin/llm/providers${query}`)
}
export function getLLMProvider(name: string): Promise<LLMProviderResponse> {
return getJSON<LLMProviderResponse>(`/api/admin/llm/providers/${encodeURIComponent(name)}`)
}
export function createLLMProvider(payload: LLMProviderPayload): Promise<LLMProviderResponse> {
return postJSON<LLMProviderResponse>('/api/admin/llm/providers', payload)
}
export function updateLLMProvider(name: string, payload: Partial<LLMProviderPayload>): Promise<LLMProviderResponse> {
return putJSON<LLMProviderResponse>(`/api/admin/llm/providers/${encodeURIComponent(name)}`, payload)
}
export function deleteLLMProvider(name: string): Promise<{ status: string }> {
return deleteJSON<{ status: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`)
}
// LLM Tool Router API
export function getLLMToolRouter(): Promise<LLMPlatformRouterResponse> {
return getJSON<LLMPlatformRouterResponse>('/api/admin/llm/tool-router')
}
export function updateLLMToolRouter(payload: Partial<LLMPlatformRouterPayload>): Promise<LLMPlatformRouterResponse> {
return putJSON<LLMPlatformRouterResponse>('/api/admin/llm/tool-router', payload)
}
// LLM Primary Config API - 主 AI 回复配置
export function getLLMPrimaryConfig(): Promise<LLMPrimaryConfigResponse> {
return getJSON<LLMPrimaryConfigResponse>('/api/admin/llm/primary-config')
}
export function updateLLMPrimaryConfig(payload: Partial<LLMPrimaryConfigPayload>): Promise<LLMPrimaryConfigResponse> {
return putJSON<LLMPrimaryConfigResponse>('/api/admin/llm/primary-config', payload)
}
// 静默使用未导出类型,避免 TS6133(未使用的导入)。
export type { LLMMessage, LLMMessageStatus, LLMProvider, LLMPlatformRouter, LLMPrimaryConfig } from './types'