107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
|
|
import { MigrateLogEntry } from '@/proxy/setup/models'
|
||
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||
|
|
import { useEffect, useRef, useState } from 'react'
|
||
|
|
import { createRoot } from 'react-dom/client'
|
||
|
|
|
||
|
|
interface DbMigrateLogPanelProps {
|
||
|
|
onClose: () => void
|
||
|
|
}
|
||
|
|
|
||
|
|
function levelColor(level: string): string {
|
||
|
|
switch (level?.toLowerCase()) {
|
||
|
|
case 'error':
|
||
|
|
return 'text-red-400'
|
||
|
|
case 'warn':
|
||
|
|
case 'warning':
|
||
|
|
return 'text-yellow-400'
|
||
|
|
case 'success':
|
||
|
|
return 'text-green-400'
|
||
|
|
default:
|
||
|
|
return 'text-gray-200'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function DbMigrateLogPanel({ onClose }: DbMigrateLogPanelProps) {
|
||
|
|
const [logs, setLogs] = useState<MigrateLogEntry[]>([])
|
||
|
|
const [done, setDone] = useState(false)
|
||
|
|
const bottomRef = useRef<HTMLDivElement>(null)
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handler = (e: CustomEvent<MigrateLogEntry>) => {
|
||
|
|
setLogs((prev) => [...prev, e.detail])
|
||
|
|
}
|
||
|
|
const doneHandler = () => setDone(true)
|
||
|
|
|
||
|
|
window.addEventListener('db-migrate-log', handler as EventListener)
|
||
|
|
window.addEventListener('db-migrate-done', doneHandler)
|
||
|
|
return () => {
|
||
|
|
window.removeEventListener('db-migrate-log', handler as EventListener)
|
||
|
|
window.removeEventListener('db-migrate-done', doneHandler)
|
||
|
|
}
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||
|
|
}, [logs])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60">
|
||
|
|
<div className="flex flex-col w-[700px] max-w-[95vw] h-[520px] max-h-[90vh] rounded-xl shadow-2xl bg-gray-900 border border-gray-700">
|
||
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-700">
|
||
|
|
<span className="text-white font-semibold text-sm">DB Migration Logs</span>
|
||
|
|
{done && (
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="text-gray-400 hover:text-white text-xs px-3 py-1 rounded border border-gray-600 hover:border-gray-400 transition-colors"
|
||
|
|
>
|
||
|
|
Close
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<div className="flex-1 overflow-y-auto p-3 font-mono text-xs leading-relaxed">
|
||
|
|
{logs.map((log, i) => (
|
||
|
|
<div key={i} className={`whitespace-pre-wrap break-words ${levelColor(log.level)}`}>
|
||
|
|
<span className="text-gray-500 mr-2 select-none">[{log.level}]</span>
|
||
|
|
{log.message}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
{!done && (
|
||
|
|
<div className="flex items-center gap-2 text-gray-500 mt-1">
|
||
|
|
<span className="inline-block w-2 h-2 rounded-full bg-blue-400 animate-pulse" />
|
||
|
|
Running...
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<div ref={bottomRef} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
let panelRoot: ReturnType<typeof createRoot> | null = null
|
||
|
|
let panelContainer: HTMLElement | null = null
|
||
|
|
|
||
|
|
export function openDbMigrateLogPanel() {
|
||
|
|
if (panelContainer) return
|
||
|
|
panelContainer = document.createElement('div')
|
||
|
|
document.body.appendChild(panelContainer)
|
||
|
|
panelRoot = createRoot(panelContainer)
|
||
|
|
|
||
|
|
const close = () => {
|
||
|
|
panelRoot?.unmount()
|
||
|
|
panelContainer?.remove()
|
||
|
|
panelRoot = null
|
||
|
|
panelContainer = null
|
||
|
|
}
|
||
|
|
|
||
|
|
panelRoot.render(<DbMigrateLogPanel onClose={close} />)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function dispatchMigrateLog(entry: MigrateLogEntry) {
|
||
|
|
window.dispatchEvent(new CustomEvent('db-migrate-log', { detail: entry }))
|
||
|
|
}
|
||
|
|
|
||
|
|
export function dispatchMigrateDone() {
|
||
|
|
window.dispatchEvent(new CustomEvent('db-migrate-done'))
|
||
|
|
}
|