admin firs step
This commit is contained in:
200
frontend/admin/composables/usePolling.ts
Normal file
200
frontend/admin/composables/usePolling.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export interface PollingOptions {
|
||||
interval?: number // milliseconds
|
||||
immediate?: boolean // whether to execute immediately on start
|
||||
maxRetries?: number // maximum number of retries on error
|
||||
retryDelay?: number // delay between retries in milliseconds
|
||||
onError?: (error: Error) => void // error handler
|
||||
}
|
||||
|
||||
export interface PollingState {
|
||||
isPolling: boolean
|
||||
isFetching: boolean
|
||||
error: string | null
|
||||
retryCount: number
|
||||
lastFetchTime: Date | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable for implementing polling/real-time updates
|
||||
*
|
||||
* @param callback - Function to execute on each poll
|
||||
* @param options - Polling configuration options
|
||||
* @returns Polling controls and state
|
||||
*/
|
||||
export const usePolling = <T>(
|
||||
callback: () => Promise<T> | T,
|
||||
options: PollingOptions = {}
|
||||
) => {
|
||||
const {
|
||||
interval = 3000, // 3 seconds default
|
||||
immediate = true,
|
||||
maxRetries = 3,
|
||||
retryDelay = 1000,
|
||||
onError
|
||||
} = options
|
||||
|
||||
// State
|
||||
const state = ref<PollingState>({
|
||||
isPolling: false,
|
||||
isFetching: false,
|
||||
error: null,
|
||||
retryCount: 0,
|
||||
lastFetchTime: null
|
||||
})
|
||||
|
||||
// Polling interval reference
|
||||
let pollInterval: NodeJS.Timeout | null = null
|
||||
let retryTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
// Execute the polling callback
|
||||
const executePoll = async (): Promise<T | null> => {
|
||||
if (state.value.isFetching) {
|
||||
return null // Skip if already fetching
|
||||
}
|
||||
|
||||
state.value.isFetching = true
|
||||
state.value.error = null
|
||||
|
||||
try {
|
||||
const result = await callback()
|
||||
state.value.lastFetchTime = new Date()
|
||||
state.value.retryCount = 0 // Reset retry count on success
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
||||
state.value.error = errorMessage
|
||||
state.value.retryCount++
|
||||
|
||||
// Call error handler if provided
|
||||
if (onError) {
|
||||
onError(error instanceof Error ? error : new Error(errorMessage))
|
||||
}
|
||||
|
||||
// Handle retries
|
||||
if (state.value.retryCount <= maxRetries) {
|
||||
console.warn(`Polling error (retry ${state.value.retryCount}/${maxRetries}):`, errorMessage)
|
||||
|
||||
// Schedule retry
|
||||
if (retryTimeout) {
|
||||
clearTimeout(retryTimeout)
|
||||
}
|
||||
|
||||
retryTimeout = setTimeout(() => {
|
||||
executePoll()
|
||||
}, retryDelay)
|
||||
} else {
|
||||
console.error(`Polling failed after ${maxRetries} retries:`, errorMessage)
|
||||
stopPolling() // Stop polling after max retries
|
||||
}
|
||||
|
||||
return null
|
||||
} finally {
|
||||
state.value.isFetching = false
|
||||
}
|
||||
}
|
||||
|
||||
// Start polling
|
||||
const startPolling = () => {
|
||||
if (state.value.isPolling) {
|
||||
return // Already polling
|
||||
}
|
||||
|
||||
state.value.isPolling = true
|
||||
state.value.error = null
|
||||
|
||||
// Execute immediately if requested
|
||||
if (immediate) {
|
||||
executePoll()
|
||||
}
|
||||
|
||||
// Set up interval
|
||||
pollInterval = setInterval(() => {
|
||||
executePoll()
|
||||
}, interval)
|
||||
}
|
||||
|
||||
// Stop polling
|
||||
const stopPolling = () => {
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval)
|
||||
pollInterval = null
|
||||
}
|
||||
|
||||
if (retryTimeout) {
|
||||
clearTimeout(retryTimeout)
|
||||
retryTimeout = null
|
||||
}
|
||||
|
||||
state.value.isPolling = false
|
||||
state.value.isFetching = false
|
||||
}
|
||||
|
||||
// Toggle polling
|
||||
const togglePolling = () => {
|
||||
if (state.value.isPolling) {
|
||||
stopPolling()
|
||||
} else {
|
||||
startPolling()
|
||||
}
|
||||
}
|
||||
|
||||
// Force immediate execution
|
||||
const forcePoll = async (): Promise<T | null> => {
|
||||
return await executePoll()
|
||||
}
|
||||
|
||||
// Update polling interval
|
||||
const updateInterval = (newInterval: number) => {
|
||||
const wasPolling = state.value.isPolling
|
||||
|
||||
if (wasPolling) {
|
||||
stopPolling()
|
||||
}
|
||||
|
||||
// Update interval in options (for next start)
|
||||
options.interval = newInterval
|
||||
|
||||
if (wasPolling) {
|
||||
startPolling()
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup on unmount
|
||||
onUnmounted(() => {
|
||||
stopPolling()
|
||||
})
|
||||
|
||||
// Auto-start on mount if immediate is true
|
||||
onMounted(() => {
|
||||
if (immediate) {
|
||||
startPolling()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
// State
|
||||
state: state.value,
|
||||
isPolling: state.value.isPolling,
|
||||
isFetching: state.value.isFetching,
|
||||
error: state.value.error,
|
||||
retryCount: state.value.retryCount,
|
||||
lastFetchTime: state.value.lastFetchTime,
|
||||
|
||||
// Controls
|
||||
startPolling,
|
||||
stopPolling,
|
||||
togglePolling,
|
||||
forcePoll,
|
||||
updateInterval,
|
||||
|
||||
// Helper
|
||||
resetError: () => {
|
||||
state.value.error = null
|
||||
state.value.retryCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default usePolling
|
||||
Reference in New Issue
Block a user