155 lines
4.7 KiB
TypeScript
155 lines
4.7 KiB
TypeScript
|
|
import { ref, computed, watch } from 'vue'
|
||
|
|
|
||
|
|
export interface CallHistoryItem {
|
||
|
|
id: string
|
||
|
|
componentCode: string
|
||
|
|
componentName: string
|
||
|
|
timestamp: number
|
||
|
|
inputs: unknown
|
||
|
|
error?: string
|
||
|
|
duration: number
|
||
|
|
statusCode?: number
|
||
|
|
}
|
||
|
|
|
||
|
|
const HISTORY_STORAGE_KEY = 's8n_call_history'
|
||
|
|
const MAX_HISTORY_ITEMS = 50
|
||
|
|
const KEEP_REQUEST_INFO_KEY = 's8n_keep_request_info'
|
||
|
|
|
||
|
|
export function useCallHistory(currentComponentCode?: () => string | undefined) {
|
||
|
|
const callHistory = ref<CallHistoryItem[]>([])
|
||
|
|
const keepRequestInfo = ref(false)
|
||
|
|
const callHistoryFilter = ref<'all' | 'current'>('all')
|
||
|
|
|
||
|
|
const loadKeepRequestInfo = () => {
|
||
|
|
const stored = localStorage.getItem(KEEP_REQUEST_INFO_KEY)
|
||
|
|
if (stored) {
|
||
|
|
try {
|
||
|
|
keepRequestInfo.value = JSON.parse(stored)
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Failed to parse keep request info', e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const saveKeepRequestInfo = () => {
|
||
|
|
localStorage.setItem(KEEP_REQUEST_INFO_KEY, JSON.stringify(keepRequestInfo.value))
|
||
|
|
}
|
||
|
|
|
||
|
|
const loadCallHistory = () => {
|
||
|
|
const stored = localStorage.getItem(HISTORY_STORAGE_KEY)
|
||
|
|
if (stored) {
|
||
|
|
try {
|
||
|
|
callHistory.value = JSON.parse(stored)
|
||
|
|
} catch (e) {
|
||
|
|
console.error('Failed to parse call history', e)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const saveCallHistory = () => {
|
||
|
|
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(callHistory.value))
|
||
|
|
}
|
||
|
|
|
||
|
|
const addCallHistory = (item: Omit<CallHistoryItem, 'id'>) => {
|
||
|
|
// Deep clone inputs to prevent reference sharing
|
||
|
|
const clonedItem = {
|
||
|
|
...item,
|
||
|
|
inputs: item.inputs !== undefined ? JSON.parse(JSON.stringify(item.inputs)) : item.inputs,
|
||
|
|
}
|
||
|
|
|
||
|
|
// If keepRequestInfo is false, strip sensitive or large input data
|
||
|
|
const processedItem = { ...clonedItem }
|
||
|
|
if (!keepRequestInfo.value) {
|
||
|
|
// Keep only essential metadata about inputs, not the full data
|
||
|
|
if (processedItem.inputs && typeof processedItem.inputs === 'object') {
|
||
|
|
const inputs = processedItem.inputs as Record<string, unknown>
|
||
|
|
// Create a minimal representation showing input structure without values
|
||
|
|
const minimalInputs: Record<string, unknown> = {}
|
||
|
|
for (const key in inputs) {
|
||
|
|
if (Object.prototype.hasOwnProperty.call(inputs, key)) {
|
||
|
|
const value = inputs[key]
|
||
|
|
if (value === null || value === undefined) {
|
||
|
|
minimalInputs[key] = value
|
||
|
|
} else if (Array.isArray(value)) {
|
||
|
|
minimalInputs[key] = `[Array of ${value.length} items]`
|
||
|
|
} else if (typeof value === 'object') {
|
||
|
|
minimalInputs[key] = `{Object with ${Object.keys(value).length} keys}`
|
||
|
|
} else if (typeof value === 'string' && value.length > 50) {
|
||
|
|
minimalInputs[key] = `${value.substring(0, 47)}...`
|
||
|
|
} else {
|
||
|
|
minimalInputs[key] = value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
processedItem.inputs = minimalInputs
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const newItem = {
|
||
|
|
...processedItem,
|
||
|
|
id: Date.now().toString() + Math.random().toString(36).substring(2),
|
||
|
|
}
|
||
|
|
callHistory.value.unshift(newItem)
|
||
|
|
if (callHistory.value.length > MAX_HISTORY_ITEMS) {
|
||
|
|
callHistory.value = callHistory.value.slice(0, MAX_HISTORY_ITEMS)
|
||
|
|
}
|
||
|
|
saveCallHistory()
|
||
|
|
}
|
||
|
|
|
||
|
|
const clearCallHistory = () => {
|
||
|
|
callHistory.value = []
|
||
|
|
saveCallHistory()
|
||
|
|
}
|
||
|
|
|
||
|
|
const getStatusColor = (statusCode: number): string => {
|
||
|
|
if (statusCode >= 200 && statusCode < 300) return 'positive'
|
||
|
|
if (statusCode >= 300 && statusCode < 400) return 'info'
|
||
|
|
if (statusCode >= 400 && statusCode < 500) return 'warning'
|
||
|
|
if (statusCode >= 500) return 'negative'
|
||
|
|
return 'grey'
|
||
|
|
}
|
||
|
|
|
||
|
|
const truncateError = (error: string): string => {
|
||
|
|
if (error.length <= 50) return error
|
||
|
|
return error.substring(0, 47) + '...'
|
||
|
|
}
|
||
|
|
|
||
|
|
const selectHistoryItem = (item: CallHistoryItem) => {
|
||
|
|
// For now, just log the selection
|
||
|
|
console.log('Selected history item:', item)
|
||
|
|
// Could implement functionality to load the component and inputs
|
||
|
|
}
|
||
|
|
|
||
|
|
const filteredCallHistory = computed(() => {
|
||
|
|
const history = callHistory.value
|
||
|
|
if (callHistoryFilter.value === 'all') {
|
||
|
|
return history
|
||
|
|
}
|
||
|
|
// filter by current component
|
||
|
|
const currentCode = currentComponentCode ? currentComponentCode() : undefined
|
||
|
|
if (!currentCode) {
|
||
|
|
return history
|
||
|
|
}
|
||
|
|
return history.filter((item) => item.componentCode === currentCode)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Watch keepRequestInfo to save changes
|
||
|
|
watch(keepRequestInfo, () => {
|
||
|
|
saveKeepRequestInfo()
|
||
|
|
})
|
||
|
|
|
||
|
|
return {
|
||
|
|
callHistory,
|
||
|
|
keepRequestInfo,
|
||
|
|
callHistoryFilter,
|
||
|
|
loadKeepRequestInfo,
|
||
|
|
loadCallHistory,
|
||
|
|
addCallHistory,
|
||
|
|
clearCallHistory,
|
||
|
|
getStatusColor,
|
||
|
|
truncateError,
|
||
|
|
selectHistoryItem,
|
||
|
|
filteredCallHistory,
|
||
|
|
}
|
||
|
|
}
|