feat(index-status): add composable for monitoring cache index status using SSE

This commit is contained in:
Jacky 2025-04-12 00:21:48 +00:00
parent d815e292db
commit 2d3a5e2a16
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
8 changed files with 75 additions and 107 deletions

View file

@ -0,0 +1,49 @@
import cacheIndex from '@/api/cache_index'
import { SSE } from 'sse.js'
import { useSSE } from './useSSE'
/**
* Composable for monitoring cache index status
* Provides a way to track indexing/scanning status through SSE
*/
export interface IndexStatus {
isScanning: Ref<boolean>
}
/**
* Setup SSE connection to monitor indexing status
*/
export function setupIndexStatus() {
const { connect, disconnect, sseInstance } = useSSE()
const isScanning = ref(false)
disconnect()
const sse = cacheIndex.index_status()
if (sse instanceof SSE) {
connect({
url: '', // Not needed as we already have the SSE instance
token: '', // Not needed as we already have the SSE instance
onMessage: data => {
isScanning.value = data.scanning
},
onError: () => {
// Reconnection is handled by useSSE
},
})
// Manually assign the SSE instance since we're using a pre-created one
sseInstance.value = sse
}
provide('indexStatus', {
isScanning,
})
}
export function useIndexStatus(): IndexStatus {
return inject<IndexStatus>('indexStatus')!
}

View file

@ -1,6 +1,4 @@
import type { NginxPerformanceInfo } from '@/api/ngx' import type { NginxPerformanceInfo } from '@/api/ngx'
import type { Ref } from 'vue'
import { computed } from 'vue'
export function usePerformanceMetrics(nginxInfo: Ref<NginxPerformanceInfo | undefined>) { export function usePerformanceMetrics(nginxInfo: Ref<NginxPerformanceInfo | undefined>) {
// Format numbers to a more readable form // Format numbers to a more readable form

View file

@ -1,6 +1,5 @@
import type { SSEvent } from 'sse.js' import type { SSEvent } from 'sse.js'
import { SSE } from 'sse.js' import { SSE } from 'sse.js'
import { onUnmounted, shallowRef } from 'vue'
export interface SSEOptions { export interface SSEOptions {
url: string url: string

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import settings from '@/api/settings' import settings from '@/api/settings'
import PageHeader from '@/components/PageHeader/PageHeader.vue' import PageHeader from '@/components/PageHeader/PageHeader.vue'
import { setupIndexStatus } from '@/composables/useIndexStatus'
import { useSettingsStore } from '@/pinia' import { useSettingsStore } from '@/pinia'
import _ from 'lodash' import _ from 'lodash'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
@ -41,6 +42,8 @@ settings.get_server_name().then(r => {
server_name.value = r.name server_name.value = r.name
}) })
setupIndexStatus()
const breadList = ref([]) const breadList = ref([])
provide('breadList', breadList) provide('breadList', breadList)

View file

@ -88,7 +88,7 @@ function handleCancel() {
</AButton> </AButton>
<AModal <AModal
v-model:visible="modalVisible" v-model:open="modalVisible"
:title="$gettext('Delete Certificate')" :title="$gettext('Delete Certificate')"
:confirm-loading="confirmLoading" :confirm-loading="confirmLoading"
:ok-button-props="{ :ok-button-props="{

View file

@ -1,20 +1,16 @@
<script setup lang="tsx"> <script setup lang="tsx">
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer' import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import type { Column } from '@/components/StdDesign/types' import type { Column } from '@/components/StdDesign/types'
import type { SSE, SSEvent } from 'sse.js'
import cacheIndex from '@/api/cache_index'
import nginxLog from '@/api/nginx_log' import nginxLog from '@/api/nginx_log'
import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue' import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
import { input, select } from '@/components/StdDesign/StdDataEntry' import { input, select } from '@/components/StdDesign/StdDataEntry'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue' import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { Tag } from 'ant-design-vue' import { Tag } from 'ant-design-vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const isScanning = ref(false) const { isScanning } = useIndexStatus()
const stdCurdRef = ref() const stdCurdRef = ref()
const sse = ref<SSE>()
const columns: Column[] = [ const columns: Column[] = [
{ {
@ -63,50 +59,6 @@ function viewLog(record: { type: string, path: string }) {
}, },
}) })
} }
// Connect to SSE endpoint and setup handlers
async function setupSSE() {
if (sse.value) {
sse.value.close()
}
sse.value = cacheIndex.index_status()
// Handle incoming messages
if (sse.value) {
sse.value.onmessage = (e: SSEvent) => {
try {
if (!e.data)
return
const data = JSON.parse(e.data)
isScanning.value = data.scanning
stdCurdRef.value.get_list()
}
catch (error) {
console.error('Error parsing SSE message:', error)
}
}
sse.value.onerror = () => {
// Reconnect on error
setTimeout(() => {
setupSSE()
}, 5000)
}
}
}
onMounted(() => {
setupSSE()
})
onUnmounted(() => {
if (sse.value) {
sse.value.close()
}
})
</script> </script>
<template> <template>

View file

@ -2,13 +2,12 @@
import type { EnvGroup } from '@/api/env_group' import type { EnvGroup } from '@/api/env_group'
import type { Site } from '@/api/site' import type { Site } from '@/api/site'
import type { Column } from '@/components/StdDesign/types' import type { Column } from '@/components/StdDesign/types'
import type { SSE, SSEvent } from 'sse.js'
import cacheIndex from '@/api/cache_index'
import env_group from '@/api/env_group' import env_group from '@/api/env_group'
import site from '@/api/site' import site from '@/api/site'
import EnvGroupTabs from '@/components/EnvGroupTabs/EnvGroupTabs.vue' import EnvGroupTabs from '@/components/EnvGroupTabs/EnvGroupTabs.vue'
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue' import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue' import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { ConfigStatus } from '@/constants' import { ConfigStatus } from '@/constants'
import InspectConfig from '@/views/config/InspectConfig.vue' import InspectConfig from '@/views/config/InspectConfig.vue'
import columns from '@/views/site/site_list/columns' import columns from '@/views/site/site_list/columns'
@ -20,15 +19,15 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const table = ref() const table = ref()
const inspect_config = ref() const inspectConfig = ref()
const envGroupId = ref(Number.parseInt(route.query.env_group_id as string) || 0) const envGroupId = ref(Number.parseInt(route.query.env_group_id as string) || 0)
const envGroups = ref([]) as Ref<EnvGroup[]> const envGroups = ref([]) as Ref<EnvGroup[]>
const isScanning = ref(false)
const sse = ref<SSE>() const { isScanning } = useIndexStatus()
watch(route, () => { watch(route, () => {
inspect_config.value?.test() inspectConfig.value?.test()
}) })
onMounted(async () => { onMounted(async () => {
@ -48,55 +47,11 @@ onMounted(async () => {
} }
}) })
onMounted(() => {
setupSSE()
})
// Connect to SSE endpoint and setup handlers
async function setupSSE() {
if (sse.value) {
sse.value.close()
}
sse.value = cacheIndex.index_status()
// Handle incoming messages
if (sse.value) {
sse.value.onmessage = (e: SSEvent) => {
try {
if (!e.data)
return
const data = JSON.parse(e.data)
isScanning.value = data.scanning
table.value.get_list()
}
catch (error) {
console.error('Error parsing SSE message:', error)
}
}
sse.value.onerror = () => {
// Reconnect on error
setTimeout(() => {
setupSSE()
}, 5000)
}
}
}
onUnmounted(() => {
if (sse.value) {
sse.value.close()
}
})
function destroy(site_name: string) { function destroy(site_name: string) {
site.destroy(site_name).then(() => { site.destroy(site_name).then(() => {
table.value.get_list() table.value.get_list()
message.success($gettext('Delete site: %{site_name}', { site_name })) message.success($gettext('Delete site: %{site_name}', { site_name }))
inspect_config.value?.test() inspectConfig.value?.test()
}) })
} }
@ -133,7 +88,7 @@ function handleBatchUpdated() {
</template> </template>
</div> </div>
</template> </template>
<InspectConfig ref="inspect_config" /> <InspectConfig ref="inspectConfig" />
<EnvGroupTabs v-model:active-key="envGroupId" :env-groups="envGroups" /> <EnvGroupTabs v-model:active-key="envGroupId" :env-groups="envGroups" />

View file

@ -10,10 +10,12 @@ import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue' import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import { actualValueRender, datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer' import { actualValueRender, datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, selector } from '@/components/StdDesign/StdDataEntry' import { input, selector } from '@/components/StdDesign/StdDataEntry'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { ConfigStatus } from '@/constants' import { ConfigStatus } from '@/constants'
import InspectConfig from '@/views/config/InspectConfig.vue' import InspectConfig from '@/views/config/InspectConfig.vue'
import envGroupColumns from '@/views/environments/group/columns' import envGroupColumns from '@/views/environments/group/columns'
import StreamDuplicate from '@/views/stream/components/StreamDuplicate.vue' import StreamDuplicate from '@/views/stream/components/StreamDuplicate.vue'
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { Badge, message } from 'ant-design-vue' import { Badge, message } from 'ant-design-vue'
const columns: Column[] = [{ const columns: Column[] = [{
@ -169,12 +171,22 @@ function handleBatchUpdated() {
table.value?.get_list() table.value?.get_list()
table.value?.resetSelection() table.value?.resetSelection()
} }
const { isScanning } = useIndexStatus()
</script> </script>
<template> <template>
<ACard :title="$gettext('Manage Streams')"> <ACard :title="$gettext('Manage Streams')">
<template #extra> <template #extra>
<a @click="add">{{ $gettext('Add') }}</a> <div class="flex items-center cursor-default">
<a class="mr-4" @click="add">{{ $gettext('Add') }}</a>
<template v-if="isScanning">
<LoadingOutlined class="mr-2" spin />{{ $gettext('Indexing...') }}
</template>
<template v-else>
<CheckCircleOutlined class="mr-2" />{{ $gettext('Indexed') }}
</template>
</div>
</template> </template>
<InspectConfig ref="inspect_config" /> <InspectConfig ref="inspect_config" />