Files
crypto_trading/frontend/src/api.js
T
matitos ac92dd5ac4 Observability: Prometheus metrics, log panel, runbook
- GET /api/metrics renders Prometheus text (equity, P&L, per-strategy
  equity/pnl/allocation/halted, info) with no client library.
- In-memory ring log handler (app/logs.py) exposed at GET /api/logs and a
  dashboard "Server logs" panel; Runtime mirrors bot events into the logger.
- docs/runbook.md covers deploys, the kill switch, resetting paper state and
  going live. Adds tests for metrics rendering and the log ring.
2026-09-22 17:10:38 +00:00

111 lines
4.1 KiB
JavaScript

import axios from 'axios'
const http = axios.create({ baseURL: '/api', timeout: 10000 })
export const getVersion = () => http.get('/version').then((r) => r.data)
export const getStatus = () => http.get('/status').then((r) => r.data)
export const getMultiStatus = () => http.get('/bot/status').then((r) => r.data)
export const getLiveCheck = () => http.get('/live-check').then((r) => r.data)
export const startMany = (payload) => http.post('/bot/start-many', payload).then((r) => r.data)
export const getStrategies = () => http.get('/strategies').then((r) => r.data)
export const getPositions = () => http.get('/positions').then((r) => r.data)
export const getOrders = () => http.get('/orders').then((r) => r.data)
export const getEvents = () => http.get('/events').then((r) => r.data)
export const getEquity = () => http.get('/equity').then((r) => r.data)
export const getLogs = (limit = 100) =>
http.get('/logs', { params: { limit } }).then((r) => r.data)
export const startBot = (payload) => http.post('/bot/start', payload).then((r) => r.data)
export const stopBot = () => http.post('/bot/stop').then((r) => r.data)
export const killBot = (reason = 'manual kill switch') =>
http.post('/bot/kill', { reason }).then((r) => r.data)
export const closePosition = (id, reason = 'manual') =>
http.post(`/positions/${id}/close`, { reason }).then((r) => r.data)
export const placeOrder = (payload) => http.post('/orders', payload).then((r) => r.data)
export const runBacktest = (payload) =>
http.post('/backtest', payload, { timeout: 60000 }).then((r) => r.data)
export const runSweep = (payload) =>
http.post('/sweep', payload, { timeout: 120000 }).then((r) => r.data)
export const getPresets = () => http.get('/presets').then((r) => r.data)
export const createPreset = (payload) => http.post('/presets', payload).then((r) => r.data)
export const deletePreset = (id) => http.delete(`/presets/${id}`).then((r) => r.data)
export function openSocket({ onMessage, onOpen, onClose, onError }) {
const proto = window.location.protocol === 'https:' ? 'wss' : 'ws'
const url = `${proto}://${window.location.host}/api/ws`
let socket
let retryTimer
let closed = false
let attempt = 0
const schedule = () => {
if (closed) return
// Retry at 1s, then back off up to 30s so a persistently failing socket
// does not hammer the server while a brief blip recovers immediately.
const delay = Math.min(1000 * 2 ** attempt, 30000)
attempt += 1
retryTimer = setTimeout(connect, delay)
}
const connect = () => {
if (closed) return
let opened = false
let reported = false
console.debug('[ws] connecting to', url)
socket = new WebSocket(url)
// Report at most one failure per attempt (Safari often fires error *and*
// close for the same bad handshake).
const reportFailure = (code, detail) => {
if (reported) return
reported = true
onError?.(code, detail)
}
// Some mobile browsers leave a blocked upgrade hanging without firing
// onclose/onerror. Treat a slow handshake as a failure so the caller can
// fall back to polling instead of waiting forever.
const openTimeout = setTimeout(() => {
if (!opened) {
reportFailure(0, 'handshake timed out')
socket.close()
}
}, 5000)
socket.onopen = () => {
opened = true
clearTimeout(openTimeout)
attempt = 0
console.debug('[ws] open')
onOpen?.()
}
socket.onmessage = (event) => onMessage?.(JSON.parse(event.data))
socket.onclose = (event) => {
clearTimeout(openTimeout)
console.debug('[ws] close', event.code, event.reason)
onClose?.()
if (!opened) {
// 1008 = policy violation (token rejected); 1006 = abnormal close.
reportFailure(event.code, event.reason)
}
schedule()
}
socket.onerror = (event) => {
clearTimeout(openTimeout)
if (!opened) reportFailure(0, event?.message || 'connection error')
socket.close()
}
}
connect()
return {
close() {
closed = true
clearTimeout(retryTimer)
socket?.close()
},
}
}