频道筛选:text_message 表增加 (channel_id, created_at) 复合索引,主页增加频道筛选输入框,服务端筛选+防抖重载,版本号 backend 1.1.0 / frontend 0.2.0
This commit is contained in:
@@ -439,7 +439,7 @@ type TextMessageRecord struct {
|
|||||||
Text *string `gorm:"column:text"`
|
Text *string `gorm:"column:text"`
|
||||||
PayloadHex *string `gorm:"column:payload_hex"`
|
PayloadHex *string `gorm:"column:payload_hex"`
|
||||||
Topic string `gorm:"column:topic;not null"`
|
Topic string `gorm:"column:topic;not null"`
|
||||||
ChannelID *string `gorm:"column:channel_id"`
|
ChannelID *string `gorm:"column:channel_id;index:idx_text_message_channel_id_created_at,priority:1"`
|
||||||
GatewayID *string `gorm:"column:gateway_id"`
|
GatewayID *string `gorm:"column:gateway_id"`
|
||||||
PacketID *int64 `gorm:"column:packet_id;index:idx_text_message_packet_id"`
|
PacketID *int64 `gorm:"column:packet_id;index:idx_text_message_packet_id"`
|
||||||
PacketTo *string `gorm:"column:packet_to"`
|
PacketTo *string `gorm:"column:packet_to"`
|
||||||
@@ -458,7 +458,7 @@ type TextMessageRecord struct {
|
|||||||
MQTTRemoteHost *string `gorm:"column:mqtt_remote_host"`
|
MQTTRemoteHost *string `gorm:"column:mqtt_remote_host"`
|
||||||
MQTTRemotePort *string `gorm:"column:mqtt_remote_port"`
|
MQTTRemotePort *string `gorm:"column:mqtt_remote_port"`
|
||||||
ContentJSON string `gorm:"column:content_json;not null"`
|
ContentJSON string `gorm:"column:content_json;not null"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;index:idx_text_message_from_num_created_at,priority:2;index:idx_text_message_created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime;index:idx_text_message_from_num_created_at,priority:2;index:idx_text_message_created_at;index:idx_text_message_channel_id_created_at,priority:2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (TextMessageRecord) TableName() string {
|
func (TextMessageRecord) TableName() string {
|
||||||
@@ -707,7 +707,7 @@ func (s *Store) migrate() error {
|
|||||||
model any
|
model any
|
||||||
indexes []string
|
indexes []string
|
||||||
}{
|
}{
|
||||||
{label: "text_message", model: &TextMessageRecord{}, indexes: []string{"idx_text_message_from_num_created_at", "idx_text_message_created_at", "idx_text_message_packet_id"}},
|
{label: "text_message", model: &TextMessageRecord{}, indexes: []string{"idx_text_message_from_num_created_at", "idx_text_message_created_at", "idx_text_message_packet_id", "idx_text_message_channel_id_created_at"}},
|
||||||
{label: "bot_direct_messages", model: &BotDirectMessageRecord{}, indexes: []string{"idx_bot_dm_bot_peer", "idx_bot_dm_bot_created_at"}},
|
{label: "bot_direct_messages", model: &BotDirectMessageRecord{}, indexes: []string{"idx_bot_dm_bot_peer", "idx_bot_dm_bot_created_at"}},
|
||||||
{label: "llm_message_queue", model: &LLMMessageQueueRecord{}, indexes: []string{"idx_llm_queue_bot_created"}},
|
{label: "llm_message_queue", model: &LLMMessageQueueRecord{}, indexes: []string{"idx_llm_queue_bot_created"}},
|
||||||
} {
|
} {
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ func NewRouter(cfg configpkg.WebConfig, consoleLog bool, store *storepkg.Store,
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackendVersion = "1.0.0"
|
const BackendVersion = "1.1.0"
|
||||||
|
|
||||||
var CommitVersion = "dev"
|
var CommitVersion = "dev"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { adminLogout, createNodeBlockingRule, deleteNode, deleteTextMessage, getAdminMe, getHealth, getMapReportViewport, getNodeInfo, getPositions, getTextMessages, purgeNode } from './api'
|
import { adminLogout, createNodeBlockingRule, deleteNode, deleteTextMessage, getAdminMe, getHealth, getMapReportViewport, getNodeInfo, getPositions, getTextMessages, purgeNode } from './api'
|
||||||
import AdminBlockingManagement from './components/AdminBlockingManagement.vue'
|
import AdminBlockingManagement from './components/AdminBlockingManagement.vue'
|
||||||
import AdminBot from './components/AdminBot.vue'
|
import AdminBot from './components/AdminBot.vue'
|
||||||
@@ -69,6 +69,7 @@ const mapReportTotal = ref(0)
|
|||||||
const mapSources = ref<PublicMapTileSource[]>([fallbackMapSource])
|
const mapSources = ref<PublicMapTileSource[]>([fallbackMapSource])
|
||||||
const mapSource = ref<PublicMapTileSource>(fallbackMapSource)
|
const mapSource = ref<PublicMapTileSource>(fallbackMapSource)
|
||||||
const nodeFilter = ref('')
|
const nodeFilter = ref('')
|
||||||
|
const channelFilter = ref('')
|
||||||
const pendingDeleteAction = ref<PendingDeleteAction | null>(null)
|
const pendingDeleteAction = ref<PendingDeleteAction | null>(null)
|
||||||
type DeletableTextMessage = TextMessage & { mergedCount?: number; mergedMessages?: TextMessage[] }
|
type DeletableTextMessage = TextMessage & { mergedCount?: number; mergedMessages?: TextMessage[] }
|
||||||
type NodeActionRequest = { nodeId: string; nodeNum: number | null; message?: DeletableTextMessage }
|
type NodeActionRequest = { nodeId: string; nodeNum: number | null; message?: DeletableTextMessage }
|
||||||
@@ -95,6 +96,7 @@ const nodesById = computed<NodeInfoById>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const normalizedNodeFilter = computed(() => nodeFilter.value.trim().toLowerCase())
|
const normalizedNodeFilter = computed(() => nodeFilter.value.trim().toLowerCase())
|
||||||
|
const normalizedChannelFilter = computed(() => channelFilter.value.trim())
|
||||||
|
|
||||||
function nodeMatchesFilterByInfo(node: NodeInfo | null | undefined, keyword: string): boolean {
|
function nodeMatchesFilterByInfo(node: NodeInfo | null | undefined, keyword: string): boolean {
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
@@ -298,7 +300,7 @@ function clearSelectedNode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadInitialChatMessages() {
|
async function loadInitialChatMessages() {
|
||||||
const response = await getTextMessages(chatPageSize, 0)
|
const response = await getTextMessages(chatPageSize, 0, normalizedChannelFilter.value ? { channelId: normalizedChannelFilter.value } : '')
|
||||||
messages.value = toChronological(response.items)
|
messages.value = toChronological(response.items)
|
||||||
chatHasMore.value = response.items.length === chatPageSize
|
chatHasMore.value = response.items.length === chatPageSize
|
||||||
chatInitialized.value = true
|
chatInitialized.value = true
|
||||||
@@ -311,7 +313,7 @@ async function loadOlderMessages() {
|
|||||||
|
|
||||||
chatLoadingOlder.value = true
|
chatLoadingOlder.value = true
|
||||||
try {
|
try {
|
||||||
const response = await getTextMessages(chatPageSize, messages.value.length)
|
const response = await getTextMessages(chatPageSize, messages.value.length, normalizedChannelFilter.value ? { channelId: normalizedChannelFilter.value } : '')
|
||||||
messages.value = mergeMessages(messages.value, toChronological(response.items))
|
messages.value = mergeMessages(messages.value, toChronological(response.items))
|
||||||
chatHasMore.value = response.items.length === chatPageSize
|
chatHasMore.value = response.items.length === chatPageSize
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -322,10 +324,23 @@ async function loadOlderMessages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function pollLatestMessages() {
|
async function pollLatestMessages() {
|
||||||
const response = await getTextMessages(chatPageSize, 0)
|
const response = await getTextMessages(chatPageSize, 0, normalizedChannelFilter.value ? { channelId: normalizedChannelFilter.value } : '')
|
||||||
messages.value = mergeMessages(messages.value, toChronological(response.items))
|
messages.value = mergeMessages(messages.value, toChronological(response.items))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let channelFilterTimer: number | undefined
|
||||||
|
watch(normalizedChannelFilter, () => {
|
||||||
|
if (channelFilterTimer !== undefined) {
|
||||||
|
window.clearTimeout(channelFilterTimer)
|
||||||
|
}
|
||||||
|
channelFilterTimer = window.setTimeout(() => {
|
||||||
|
messages.value = []
|
||||||
|
chatHasMore.value = true
|
||||||
|
chatInitialized.value = false
|
||||||
|
loadInitialChatMessages()
|
||||||
|
}, 400)
|
||||||
|
})
|
||||||
|
|
||||||
async function loadNodePage(page: number, showLoading = true) {
|
async function loadNodePage(page: number, showLoading = true) {
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
nodePageLoading.value = true
|
nodePageLoading.value = true
|
||||||
@@ -676,6 +691,9 @@ onBeforeUnmount(() => {
|
|||||||
if (mapBoundsTimer !== undefined) {
|
if (mapBoundsTimer !== undefined) {
|
||||||
window.clearTimeout(mapBoundsTimer)
|
window.clearTimeout(mapBoundsTimer)
|
||||||
}
|
}
|
||||||
|
if (channelFilterTimer !== undefined) {
|
||||||
|
window.clearTimeout(channelFilterTimer)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -739,6 +757,20 @@ onBeforeUnmount(() => {
|
|||||||
@click="nodeFilter = ''"
|
@click="nodeFilter = ''"
|
||||||
>清除</button>
|
>清除</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="topbar-filter">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
class="topbar-filter-input"
|
||||||
|
v-model="channelFilter"
|
||||||
|
placeholder="筛选频道"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="normalizedChannelFilter"
|
||||||
|
type="button"
|
||||||
|
class="topbar-filter-clear"
|
||||||
|
@click="channelFilter = ''"
|
||||||
|
>清除</button>
|
||||||
|
</div>
|
||||||
<a class="topbar-link" href="/signed">签到列表</a>
|
<a class="topbar-link" href="/signed">签到列表</a>
|
||||||
<a class="topbar-link" href="/help">使用帮助</a>
|
<a class="topbar-link" href="/help">使用帮助</a>
|
||||||
<a class="topbar-link" href="/admin">管理</a>
|
<a class="topbar-link" href="/admin">管理</a>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export const FRONTEND_VERSION = '0.1.2'
|
export const FRONTEND_VERSION = '0.2.0'
|
||||||
Reference in New Issue
Block a user