Newer
Older
hard-panel / panel / frontend / src / api.js
// API-клиент панели. Доступ к API: браузерная cookie-сессия gnexus-auth
// (если панель закрыта авторизацией) или открытый режим без аутентификации;
// GHARD_ADMIN_TOKEN на бэкенде опционален — если задан, запросы к API можно
// подписывать Bearer-токеном вручную (curl/скрипты/MCP).

export class ApiError extends Error {
  constructor(status, message) {
    super(message)
    this.status = status
  }
}

async function request(path, { method = 'GET', body } = {}) {
  const response = await fetch('/api/v1' + path, {
    method,
    headers: {
      'Content-Type': 'application/json',
    },
    ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
  })
  if (!response.ok) {
    let detail = response.statusText
    try {
      const data = await response.json()
      detail = data.detail || detail
    } catch { /* не-JSON ответ */ }
    // сессия кончилась, пока SPA открыт — сообщаем шлюзу-обёртке в App.vue
    if (response.status === 401) {
      window.dispatchEvent(new CustomEvent('ghard:unauthenticated'))
    }
    throw new ApiError(response.status, detail)
  }
  if (response.status === 204) return null
  return response.json()
}

export const api = {
  // Профиль текущего пользователя / флаг режима авторизации:
  // { auth_enabled: false } (панель открыта) или
  // { auth_enabled: true, user: {...} } | 401 (нужно войти)
  me: () => request('/me'),
  // PATCH /me — только cookie-сессия (у Bearer-админа нет личности для настроек);
  // auth-off → 401 (настраивать нечего). locale: 'en'|'uk'|'ru'|'auto'
  updateMe: (fields) => request('/me', { method: 'PATCH', body: fields }),
  servers: () => request('/servers'),
  server: (id) => request(`/servers/${id}`),
  createServer: (name, hostname = '', interval = 30) =>
    request('/servers', { method: 'POST', body: { name, hostname, interval } }),
  updateServer: (id, fields) =>
    request(`/servers/${id}`, { method: 'PATCH', body: fields }),
  deleteServer: (id) => request(`/servers/${id}`, { method: 'DELETE' }),
  metrics: (id, since, until, limit = 1000) => {
    const params = new URLSearchParams()
    if (since) params.set('since', since.toISOString())
    if (until) params.set('until', until.toISOString())
    params.set('limit', String(limit))
    return request(`/servers/${id}/metrics?${params}`)
  },
  shares: () => request('/shares'),
  createShare: (name, path) =>
    request('/shares', { method: 'POST', body: { name, path } }),
  updateShare: (id, fields) =>
    request(`/shares/${id}`, { method: 'PATCH', body: fields }),
  deleteShare: (id) => request(`/shares/${id}`, { method: 'DELETE' }),
  shareSamples: (id, since, limit = 1000) => {
    const params = new URLSearchParams()
    if (since) params.set('since', since.toISOString())
    params.set('limit', String(limit))
    return request(`/shares/${id}/samples?${params}`)
  },
  services: () => request('/services'),
  createService: (name, url) =>
    request('/services', { method: 'POST', body: { name, url } }),
  updateService: (id, fields) =>
    request(`/services/${id}`, { method: 'PATCH', body: fields }),
  deleteService: (id) => request(`/services/${id}`, { method: 'DELETE' }),
  serviceSamples: (id, since, limit = 1000) => {
    const params = new URLSearchParams()
    if (since) params.set('since', since.toISOString())
    params.set('limit', String(limit))
    return request(`/services/${id}/samples?${params}`)
  },
  serviceIncidents: (id, limit = 50) =>
    request(`/services/${id}/incidents?limit=${limit}`),
  // OAuth не под /api/v1 — служебные пути панели
  logout: async () => {
    return fetch('/auth/logout', { method: 'POST' })
  },
}