- Implemented Docker operations including image building, container management, and resource stats. - Added Gitea API client for repository management and webhook handling. - Introduced monitoring service to collect and store container metrics in InfluxDB. - Created a queue system using BullMQ for managing deployment jobs with real-time log streaming. - Developed Telegram notification service for deployment status updates. - Added Traefik label generation for dynamic reverse proxy configuration. - Implemented WebSocket endpoints for log streaming and terminal access to containers. - Created an updater sidecar for self-updating the AutoDeployer container.
115 lines
4.9 KiB
JavaScript
115 lines
4.9 KiB
JavaScript
// AutoDeployer API Client — vanilla JS (ES module)
|
|
const API_BASE = '/api';
|
|
let accessToken = localStorage.getItem('accessToken') || '';
|
|
|
|
function setToken(token) {
|
|
accessToken = token;
|
|
token ? localStorage.setItem('accessToken', token) : localStorage.removeItem('accessToken');
|
|
}
|
|
function getToken() { return accessToken; }
|
|
|
|
class ApiError extends Error {
|
|
constructor(status, body) { super(body.error || 'API Error'); this.status = status; this.body = body; }
|
|
}
|
|
|
|
async function request(path, opts = {}) {
|
|
const url = `${API_BASE}${path}`;
|
|
const headers = { 'Content-Type': 'application/json', ...opts.headers };
|
|
if (accessToken) headers['Authorization'] = `Bearer ${accessToken}`;
|
|
|
|
let res = await fetch(url, { ...opts, headers, credentials: 'include' });
|
|
|
|
if (res.status === 401 && path !== '/auth/login' && path !== '/auth/refresh') {
|
|
const refreshed = await refreshToken();
|
|
if (refreshed) {
|
|
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
res = await fetch(url, { ...opts, headers, credentials: 'include' });
|
|
} else {
|
|
throw new ApiError(401, { error: 'Session expired' });
|
|
}
|
|
}
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => ({ error: 'Unknown error' }));
|
|
throw new ApiError(res.status, body);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
async function refreshToken() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/auth/refresh`, { method: 'POST', credentials: 'include' });
|
|
if (res.ok) { const d = await res.json(); setToken(d.accessToken); return true; }
|
|
} catch {}
|
|
return false;
|
|
}
|
|
|
|
export const auth = {
|
|
status: () => request('/auth/status'),
|
|
login: (u, p) => request('/auth/login', { method: 'POST', body: JSON.stringify({ username: u, password: p }) }).then(d => { setToken(d.accessToken); return d; }),
|
|
setup: (u, p) => request('/auth/setup', { method: 'POST', body: JSON.stringify({ username: u, password: p }) }).then(d => { setToken(d.accessToken); return d; }),
|
|
logout: () => request('/auth/logout', { method: 'POST' }).then(() => setToken('')).catch(() => setToken('')),
|
|
me: () => request('/auth/me'),
|
|
changePassword: (cur, nw) => request('/auth/password', { method: 'PUT', body: JSON.stringify({ currentPassword: cur, newPassword: nw }) }),
|
|
};
|
|
|
|
export const services = {
|
|
list: () => request('/services'),
|
|
get: (id) => request(`/services/${id}`),
|
|
create: (d) => request('/services', { method: 'POST', body: JSON.stringify(d) }),
|
|
update: (id, d) => request(`/services/${id}`, { method: 'PUT', body: JSON.stringify(d) }),
|
|
delete: (id) => request(`/services/${id}`, { method: 'DELETE' }),
|
|
deploy: (id) => request(`/services/${id}/deploy`, { method: 'POST' }),
|
|
stop: (id) => request(`/services/${id}/stop`, { method: 'POST' }),
|
|
restart: (id) => request(`/services/${id}/restart`, { method: 'POST' }),
|
|
inspect: (id) => request(`/services/${id}/inspect`),
|
|
cleanup: () => request('/services/cleanup', { method: 'POST' }),
|
|
pruneImages: (keep = 2) => request('/services/prune-images', { method: 'POST', body: JSON.stringify({ keep }) }),
|
|
traefikPreview: (id) => request(`/services/${id}/traefik-preview`),
|
|
traefikPreviewBody: (d) => request('/services/traefik-preview', { method: 'POST', body: JSON.stringify(d) }),
|
|
getEnv: (id) => request(`/services/${id}/env`),
|
|
setEnv: (id, vars) => request(`/services/${id}/env`, { method: 'PUT', body: JSON.stringify({ vars }) }),
|
|
};
|
|
|
|
export const deploys = {
|
|
list: () => request('/deploys'),
|
|
get: (id) => request(`/deploys/${id}`),
|
|
};
|
|
|
|
export const networks = {
|
|
list: () => request('/networks'),
|
|
create: (name, driver, internal) => request('/networks', { method: 'POST', body: JSON.stringify({ name, driver, internal }) }),
|
|
remove: (name) => request(`/networks/${name}`, { method: 'DELETE' }),
|
|
};
|
|
|
|
export const monitoring = {
|
|
realtime: () => request('/monitoring/realtime'),
|
|
stats: (name) => request(`/monitoring/${name}/stats`),
|
|
history: (name, range = '-1h', field = 'cpu_percent') => request(`/monitoring/${name}/history?range=${range}&field=${field}`),
|
|
};
|
|
|
|
export const settingsApi = {
|
|
get: () => request('/settings'),
|
|
update: (s) => request('/settings', { method: 'PUT', body: JSON.stringify({ settings: s }) }),
|
|
testGitea: () => request('/settings/test/gitea'),
|
|
testTelegram: () => request('/settings/test/telegram'),
|
|
queueStatus: () => request('/settings/queue'),
|
|
};
|
|
|
|
export const system = {
|
|
version: () => request('/system/version'),
|
|
selfUpdate: () => request('/system/self-update', { method: 'POST' }),
|
|
updateStatus: () => request('/system/update-status'),
|
|
};
|
|
|
|
export function createLogSocket(target) {
|
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return new WebSocket(`${proto}//${location.host}/ws/logs/${target}?token=${accessToken}`);
|
|
}
|
|
|
|
export function createTerminalSocket(containerId) {
|
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
return new WebSocket(`${proto}//${location.host}/ws/terminal/${containerId}?token=${accessToken}`);
|
|
}
|
|
|
|
export { getToken, setToken };
|