// API-клиент панели. Авторизации в интерфейсе нет (личная панель);
// GHARD_ADMIN_TOKEN на бэкенде опционален — если задан, запросы к API
// нужно подписывать Bearer-токеном вручную (curl/скрипты).
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 ответ */ }
throw new ApiError(response.status, detail)
}
if (response.status === 204) return null
return response.json()
}
export const api = {
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}`)
},
}