Spaces:
Sleeping
Sleeping
anoderb commited on
Commit Β·
287a59e
1
Parent(s): cd4b768
Add manual log refresh and clear log buttons; ensured level filtering works
Browse files- backend/main.py +6 -0
- frontend/app/dashboard/forecast/page.tsx +25 -5
- frontend/lib/api.ts +1 -0
backend/main.py
CHANGED
|
@@ -215,6 +215,12 @@ def get_logs(limit: int = Query(200, le=500), level: Optional[str] = None):
|
|
| 215 |
"""Get structured logs with optional level filter."""
|
| 216 |
return memory_handler.get_logs(limit=limit, level=level)
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
# ββ Komoditas βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 219 |
@app.get("/komoditas", dependencies=[Depends(verify_token)])
|
| 220 |
def list_komoditas():
|
|
|
|
| 215 |
"""Get structured logs with optional level filter."""
|
| 216 |
return memory_handler.get_logs(limit=limit, level=level)
|
| 217 |
|
| 218 |
+
@app.post("/clear-logs", dependencies=[Depends(verify_token)])
|
| 219 |
+
def clear_logs():
|
| 220 |
+
"""Clear all in-memory logs."""
|
| 221 |
+
memory_handler.clear_logs()
|
| 222 |
+
return {"success": True, "message": "Log berhasil dihapus."}
|
| 223 |
+
|
| 224 |
# ββ Komoditas βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 225 |
@app.get("/komoditas", dependencies=[Depends(verify_token)])
|
| 226 |
def list_komoditas():
|
frontend/app/dashboard/forecast/page.tsx
CHANGED
|
@@ -124,6 +124,14 @@ export default function ForecastPage() {
|
|
| 124 |
} catch (e: any) { alert(`β ${e.message}`) }
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
const anyRunning = status?.any_forecast_running || runAllLoading || Object.keys(runningIds).length > 0
|
| 128 |
|
| 129 |
return (
|
|
@@ -300,14 +308,26 @@ export default function ForecastPage() {
|
|
| 300 |
|
| 301 |
{/* RIGHT: Terminal Logs */}
|
| 302 |
<div className="glass-panel rounded-2xl p-6 border border-white/5 flex flex-col h-[500px] lg:h-[calc(100vh-180px)] lg:sticky lg:top-24">
|
| 303 |
-
<div className="flex
|
| 304 |
-
<
|
| 305 |
-
<
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
<div className="flex bg-slate-900/80 rounded-lg p-0.5 border border-white/5">
|
| 308 |
{['ALL', 'INFO', 'WARNING', 'ERROR'].map(lvl => (
|
| 309 |
<button key={lvl} onClick={() => setLogLevelFilter(lvl)}
|
| 310 |
-
className={`px-2 py-1 rounded-md text-[10px] font-bold transition ${logLevelFilter === lvl ? 'bg-slate-700 text-white' : 'text-slate-500 hover:text-slate-300'}`}>
|
| 311 |
{lvl}
|
| 312 |
</button>
|
| 313 |
))}
|
|
|
|
| 124 |
} catch (e: any) { alert(`β ${e.message}`) }
|
| 125 |
}
|
| 126 |
|
| 127 |
+
async function handleClearLogs() {
|
| 128 |
+
if (!confirm('Hapus semua log di server?')) return
|
| 129 |
+
try {
|
| 130 |
+
await api.clearLogs()
|
| 131 |
+
setLogs([])
|
| 132 |
+
} catch (e: any) { alert(`β ${e.message}`) }
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
const anyRunning = status?.any_forecast_running || runAllLoading || Object.keys(runningIds).length > 0
|
| 136 |
|
| 137 |
return (
|
|
|
|
| 308 |
|
| 309 |
{/* RIGHT: Terminal Logs */}
|
| 310 |
<div className="glass-panel rounded-2xl p-6 border border-white/5 flex flex-col h-[500px] lg:h-[calc(100vh-180px)] lg:sticky lg:top-24">
|
| 311 |
+
<div className="flex flex-col gap-3 pb-4 mb-4 border-b border-white/5">
|
| 312 |
+
<div className="flex items-center justify-between">
|
| 313 |
+
<h2 className="text-sm font-bold text-slate-100 flex items-center gap-2">
|
| 314 |
+
<Terminal className="w-4 h-4 text-indigo-400" /> Log Real-time
|
| 315 |
+
</h2>
|
| 316 |
+
<div className="flex gap-1.5">
|
| 317 |
+
<button onClick={() => fetchData()} title="Refresh Log"
|
| 318 |
+
className="p-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg transition">
|
| 319 |
+
<RefreshCw className="w-3.5 h-3.5" />
|
| 320 |
+
</button>
|
| 321 |
+
<button onClick={handleClearLogs} title="Hapus Semua Log"
|
| 322 |
+
className="p-1.5 bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 rounded-lg transition border border-rose-500/10">
|
| 323 |
+
<Trash2 className="w-3.5 h-3.5" />
|
| 324 |
+
</button>
|
| 325 |
+
</div>
|
| 326 |
+
</div>
|
| 327 |
<div className="flex bg-slate-900/80 rounded-lg p-0.5 border border-white/5">
|
| 328 |
{['ALL', 'INFO', 'WARNING', 'ERROR'].map(lvl => (
|
| 329 |
<button key={lvl} onClick={() => setLogLevelFilter(lvl)}
|
| 330 |
+
className={`flex-1 px-2 py-1.5 rounded-md text-[10px] font-bold transition ${logLevelFilter === lvl ? 'bg-slate-700 text-white' : 'text-slate-500 hover:text-slate-300'}`}>
|
| 331 |
{lvl}
|
| 332 |
</button>
|
| 333 |
))}
|
frontend/lib/api.ts
CHANGED
|
@@ -38,6 +38,7 @@ export const api = {
|
|
| 38 |
// Status & Logs (realtime)
|
| 39 |
getStatus: () => req('/status'),
|
| 40 |
getLogs: (limit = 200, level?: string) => req(`/logs?limit=${limit}${level && level !== 'ALL' ? `&level=${level}` : ''}`),
|
|
|
|
| 41 |
|
| 42 |
// Scheduler (multiple schedules)
|
| 43 |
getSchedules: () => req('/schedules'),
|
|
|
|
| 38 |
// Status & Logs (realtime)
|
| 39 |
getStatus: () => req('/status'),
|
| 40 |
getLogs: (limit = 200, level?: string) => req(`/logs?limit=${limit}${level && level !== 'ALL' ? `&level=${level}` : ''}`),
|
| 41 |
+
clearLogs: () => req('/clear-logs', { method: 'POST' }),
|
| 42 |
|
| 43 |
// Scheduler (multiple schedules)
|
| 44 |
getSchedules: () => req('/schedules'),
|