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 { Ref } from 'vue'
import { computed } from 'vue'
export function usePerformanceMetrics(nginxInfo: Ref<NginxPerformanceInfo | undefined>) {
// Format numbers to a more readable form

View file

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

View file

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

View file

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

View file

@ -1,20 +1,16 @@
<script setup lang="tsx">
import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
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 StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
import { input, select } from '@/components/StdDesign/StdDataEntry'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { Tag } from 'ant-design-vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const isScanning = ref(false)
const { isScanning } = useIndexStatus()
const stdCurdRef = ref()
const sse = ref<SSE>()
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>
<template>

View file

@ -2,13 +2,12 @@
import type { EnvGroup } from '@/api/env_group'
import type { Site } from '@/api/site'
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 site from '@/api/site'
import EnvGroupTabs from '@/components/EnvGroupTabs/EnvGroupTabs.vue'
import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { ConfigStatus } from '@/constants'
import InspectConfig from '@/views/config/InspectConfig.vue'
import columns from '@/views/site/site_list/columns'
@ -20,15 +19,15 @@ const route = useRoute()
const router = useRouter()
const table = ref()
const inspect_config = ref()
const inspectConfig = ref()
const envGroupId = ref(Number.parseInt(route.query.env_group_id as string) || 0)
const envGroups = ref([]) as Ref<EnvGroup[]>
const isScanning = ref(false)
const sse = ref<SSE>()
const { isScanning } = useIndexStatus()
watch(route, () => {
inspect_config.value?.test()
inspectConfig.value?.test()
})
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) {
site.destroy(site_name).then(() => {
table.value.get_list()
message.success($gettext('Delete site: %{site_name}', { site_name }))
inspect_config.value?.test()
inspectConfig.value?.test()
})
}
@ -133,7 +88,7 @@ function handleBatchUpdated() {
</template>
</div>
</template>
<InspectConfig ref="inspect_config" />
<InspectConfig ref="inspectConfig" />
<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 { actualValueRender, datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
import { input, selector } from '@/components/StdDesign/StdDataEntry'
import { useIndexStatus } from '@/composables/useIndexStatus'
import { ConfigStatus } from '@/constants'
import InspectConfig from '@/views/config/InspectConfig.vue'
import envGroupColumns from '@/views/environments/group/columns'
import StreamDuplicate from '@/views/stream/components/StreamDuplicate.vue'
import { CheckCircleOutlined, LoadingOutlined } from '@ant-design/icons-vue'
import { Badge, message } from 'ant-design-vue'
const columns: Column[] = [{
@ -169,12 +171,22 @@ function handleBatchUpdated() {
table.value?.get_list()
table.value?.resetSelection()
}
const { isScanning } = useIndexStatus()
</script>
<template>
<ACard :title="$gettext('Manage Streams')">
<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>
<InspectConfig ref="inspect_config" />