Add Proxmox API extensions, WebSocket monitoring, and email notifications
Co-authored-by: Ospab <189454929+Ospab@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import http from 'http';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
import authRoutes from './modules/auth/auth.routes';
|
||||
import ticketRoutes from './modules/ticket/ticket.routes';
|
||||
import checkRoutes from './modules/check/check.routes';
|
||||
@@ -8,10 +10,21 @@ import proxmoxRoutes from '../proxmox/proxmox.routes';
|
||||
import tariffRoutes from './modules/tariff';
|
||||
import osRoutes from './modules/os';
|
||||
import serverRoutes from './modules/server';
|
||||
import { MonitoringService } from './modules/server/monitoring.service';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Настройка Socket.IO с CORS
|
||||
const io = new SocketIOServer(server, {
|
||||
cors: {
|
||||
origin: ['http://localhost:3000', 'http://localhost:5173'],
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true
|
||||
}
|
||||
});
|
||||
|
||||
// ИСПРАВЛЕНО: более точная настройка CORS
|
||||
app.use(cors({
|
||||
@@ -65,7 +78,13 @@ app.use('/api/server', serverRoutes);
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
// Инициализация сервиса мониторинга
|
||||
const monitoringService = new MonitoringService(io);
|
||||
monitoringService.startMonitoring();
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`🚀 Сервер запущен на порту ${PORT}`);
|
||||
console.log(`📊 База данных: ${process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА'}`);
|
||||
console.log(`🔌 WebSocket сервер запущен`);
|
||||
console.log(`📡 Мониторинг серверов активен`);
|
||||
});
|
||||
133
ospabhost/backend/src/modules/notification/email.service.ts
Normal file
133
ospabhost/backend/src/modules/notification/email.service.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Конфигурация email транспорта
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||
port: Number(process.env.SMTP_PORT) || 587,
|
||||
secure: false, // true для 465, false для других портов
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASS
|
||||
}
|
||||
});
|
||||
|
||||
export interface EmailNotification {
|
||||
to: string;
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
}
|
||||
|
||||
// Отправка email уведомления
|
||||
export async function sendEmail(notification: EmailNotification) {
|
||||
try {
|
||||
// Проверяем наличие конфигурации SMTP
|
||||
if (!process.env.SMTP_USER || !process.env.SMTP_PASS) {
|
||||
console.log('SMTP not configured, skipping email notification');
|
||||
return { status: 'skipped', message: 'SMTP not configured' };
|
||||
}
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: `"Ospab Host" <${process.env.SMTP_USER}>`,
|
||||
...notification
|
||||
});
|
||||
|
||||
console.log('Email sent: %s', info.messageId);
|
||||
return { status: 'success', messageId: info.messageId };
|
||||
} catch (error: any) {
|
||||
console.error('Error sending email:', error);
|
||||
return { status: 'error', message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Отправка уведомления о высокой нагрузке
|
||||
export async function sendResourceAlertEmail(userId: number, serverId: number, alertType: string, value: string) {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) return { status: 'error', message: 'User not found' };
|
||||
|
||||
const subject = `Предупреждение: Высокая нагрузка на сервер #${serverId}`;
|
||||
const html = `
|
||||
<h2>Предупреждение о ресурсах сервера</h2>
|
||||
<p>Здравствуйте, ${user.username}!</p>
|
||||
<p>Обнаружено превышение лимитов ресурсов на вашем сервере #${serverId}:</p>
|
||||
<ul>
|
||||
<li><strong>Тип:</strong> ${alertType}</li>
|
||||
<li><strong>Значение:</strong> ${value}</li>
|
||||
</ul>
|
||||
<p>Рекомендуем проверить сервер и при необходимости увеличить его ресурсы.</p>
|
||||
<p>С уважением,<br>Команда Ospab Host</p>
|
||||
`;
|
||||
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
subject,
|
||||
html
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error sending resource alert email:', error);
|
||||
return { status: 'error', message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Отправка уведомления о создании сервера
|
||||
export async function sendServerCreatedEmail(userId: number, serverId: number, serverDetails: any) {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) return { status: 'error', message: 'User not found' };
|
||||
|
||||
const subject = `Ваш сервер #${serverId} успешно создан`;
|
||||
const html = `
|
||||
<h2>Сервер успешно создан!</h2>
|
||||
<p>Здравствуйте, ${user.username}!</p>
|
||||
<p>Ваш новый сервер был успешно создан:</p>
|
||||
<ul>
|
||||
<li><strong>ID сервера:</strong> ${serverId}</li>
|
||||
<li><strong>Тариф:</strong> ${serverDetails.tariff}</li>
|
||||
<li><strong>ОС:</strong> ${serverDetails.os}</li>
|
||||
<li><strong>IP адрес:</strong> ${serverDetails.ip || 'Получение...'}</li>
|
||||
</ul>
|
||||
<p>Вы можете управлять сервером через панель управления.</p>
|
||||
<p>С уважением,<br>Команда Ospab Host</p>
|
||||
`;
|
||||
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
subject,
|
||||
html
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error sending server created email:', error);
|
||||
return { status: 'error', message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Отправка уведомления о приближении срока оплаты
|
||||
export async function sendPaymentReminderEmail(userId: number, serverId: number, daysLeft: number) {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) return { status: 'error', message: 'User not found' };
|
||||
|
||||
const subject = `Напоминание: Оплата за сервер #${serverId}`;
|
||||
const html = `
|
||||
<h2>Напоминание об оплате</h2>
|
||||
<p>Здравствуйте, ${user.username}!</p>
|
||||
<p>До окончания срока действия вашего тарифа для сервера #${serverId} осталось ${daysLeft} дней.</p>
|
||||
<p>Пожалуйста, пополните баланс, чтобы избежать прерывания обслуживания.</p>
|
||||
<p>Ваш текущий баланс: ${user.balance}₽</p>
|
||||
<p>С уважением,<br>Команда Ospab Host</p>
|
||||
`;
|
||||
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
subject,
|
||||
html
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error sending payment reminder email:', error);
|
||||
return { status: 'error', message: error.message };
|
||||
}
|
||||
}
|
||||
191
ospabhost/backend/src/modules/server/monitoring.service.ts
Normal file
191
ospabhost/backend/src/modules/server/monitoring.service.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getContainerStats } from './proxmoxApi';
|
||||
import { sendResourceAlertEmail } from '../notification/email.service';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export class MonitoringService {
|
||||
private io: SocketIOServer;
|
||||
private monitoringInterval: NodeJS.Timeout | null = null;
|
||||
private readonly MONITORING_INTERVAL = 30000; // 30 секунд
|
||||
|
||||
constructor(io: SocketIOServer) {
|
||||
this.io = io;
|
||||
this.setupSocketHandlers();
|
||||
}
|
||||
|
||||
private setupSocketHandlers() {
|
||||
this.io.on('connection', (socket) => {
|
||||
console.log(`Client connected: ${socket.id}`);
|
||||
|
||||
// Подписка на обновления конкретного сервера
|
||||
socket.on('subscribe-server', async (serverId: number) => {
|
||||
console.log(`Client ${socket.id} subscribed to server ${serverId}`);
|
||||
socket.join(`server-${serverId}`);
|
||||
|
||||
// Отправляем начальную статистику
|
||||
try {
|
||||
const server = await prisma.server.findUnique({ where: { id: serverId } });
|
||||
if (server && server.proxmoxId) {
|
||||
const stats = await getContainerStats(server.proxmoxId);
|
||||
socket.emit('server-stats', { serverId, stats });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching initial stats for server ${serverId}:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
// Отписка от обновлений сервера
|
||||
socket.on('unsubscribe-server', (serverId: number) => {
|
||||
console.log(`Client ${socket.id} unsubscribed from server ${serverId}`);
|
||||
socket.leave(`server-${serverId}`);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`Client disconnected: ${socket.id}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Запуск периодического мониторинга
|
||||
public startMonitoring() {
|
||||
if (this.monitoringInterval) {
|
||||
console.log('Monitoring already running');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting server monitoring service...');
|
||||
this.monitoringInterval = setInterval(async () => {
|
||||
await this.checkAllServers();
|
||||
}, this.MONITORING_INTERVAL);
|
||||
|
||||
// Первая проверка сразу
|
||||
this.checkAllServers();
|
||||
}
|
||||
|
||||
// Остановка мониторинга
|
||||
public stopMonitoring() {
|
||||
if (this.monitoringInterval) {
|
||||
clearInterval(this.monitoringInterval);
|
||||
this.monitoringInterval = null;
|
||||
console.log('Monitoring service stopped');
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка всех активных серверов
|
||||
private async checkAllServers() {
|
||||
try {
|
||||
const servers = await prisma.server.findMany({
|
||||
where: {
|
||||
status: {
|
||||
in: ['running', 'stopped', 'creating']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const server of servers) {
|
||||
if (server.proxmoxId) {
|
||||
try {
|
||||
const stats = await getContainerStats(server.proxmoxId);
|
||||
|
||||
if (stats.status === 'success' && stats.data) {
|
||||
// Обновляем статус и метрики в БД
|
||||
await prisma.server.update({
|
||||
where: { id: server.id },
|
||||
data: {
|
||||
status: stats.data.status,
|
||||
cpuUsage: stats.data.cpu || 0,
|
||||
memoryUsage: stats.data.memory?.usage || 0,
|
||||
diskUsage: stats.data.disk?.usage || 0,
|
||||
networkIn: stats.data.network?.in || 0,
|
||||
networkOut: stats.data.network?.out || 0,
|
||||
lastPing: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Отправляем обновления подписанным клиентам
|
||||
this.io.to(`server-${server.id}`).emit('server-stats', {
|
||||
serverId: server.id,
|
||||
stats
|
||||
});
|
||||
|
||||
// Проверяем превышение лимитов и отправляем алерты
|
||||
await this.checkResourceLimits(server, stats.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error monitoring server ${server.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in checkAllServers:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка превышения лимитов ресурсов
|
||||
private async checkResourceLimits(server: any, stats: any) {
|
||||
const alerts = [];
|
||||
|
||||
// CPU превышает 90%
|
||||
if (stats.cpu && stats.cpu > 0.9) {
|
||||
alerts.push({
|
||||
type: 'cpu',
|
||||
message: `CPU usage is at ${(stats.cpu * 100).toFixed(1)}%`,
|
||||
level: 'warning'
|
||||
});
|
||||
|
||||
// Отправляем email уведомление
|
||||
await sendResourceAlertEmail(
|
||||
server.userId,
|
||||
server.id,
|
||||
'CPU',
|
||||
`${(stats.cpu * 100).toFixed(1)}%`
|
||||
);
|
||||
}
|
||||
|
||||
// Memory превышает 90%
|
||||
if (stats.memory?.usage && stats.memory.usage > 90) {
|
||||
alerts.push({
|
||||
type: 'memory',
|
||||
message: `Memory usage is at ${stats.memory.usage.toFixed(1)}%`,
|
||||
level: 'warning'
|
||||
});
|
||||
|
||||
// Отправляем email уведомление
|
||||
await sendResourceAlertEmail(
|
||||
server.userId,
|
||||
server.id,
|
||||
'Memory',
|
||||
`${stats.memory.usage.toFixed(1)}%`
|
||||
);
|
||||
}
|
||||
|
||||
// Disk превышает 90%
|
||||
if (stats.disk?.usage && stats.disk.usage > 90) {
|
||||
alerts.push({
|
||||
type: 'disk',
|
||||
message: `Disk usage is at ${stats.disk.usage.toFixed(1)}%`,
|
||||
level: 'warning'
|
||||
});
|
||||
|
||||
// Отправляем email уведомление
|
||||
await sendResourceAlertEmail(
|
||||
server.userId,
|
||||
server.id,
|
||||
'Disk',
|
||||
`${stats.disk.usage.toFixed(1)}%`
|
||||
);
|
||||
}
|
||||
|
||||
// Отправляем алерты, если есть
|
||||
if (alerts.length > 0) {
|
||||
this.io.to(`server-${server.id}`).emit('server-alerts', {
|
||||
serverId: server.id,
|
||||
alerts
|
||||
});
|
||||
|
||||
console.log(`Alerts for server ${server.id}:`, alerts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,6 +342,133 @@ export async function getConsoleURL(vmid: number): Promise<{ status: string; url
|
||||
}
|
||||
}
|
||||
|
||||
// Изменение конфигурации контейнера (CPU, RAM, Disk)
|
||||
export async function resizeContainer(vmid: number, config: { cores?: number; memory?: number; rootfs?: string }) {
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${vmid}/config`,
|
||||
config,
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
data: response.data?.data
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка изменения конфигурации:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Создание снэпшота
|
||||
export async function createSnapshot(vmid: number, snapname: string, description?: string) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${vmid}/snapshot`,
|
||||
{
|
||||
snapname,
|
||||
description: description || `Snapshot ${snapname}`
|
||||
},
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
taskId: response.data?.data,
|
||||
snapname
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка создания снэпшота:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Получение списка снэпшотов
|
||||
export async function listSnapshots(vmid: number) {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${vmid}/snapshot`,
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
data: response.data?.data || []
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка получения списка снэпшотов:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Восстановление из снэпшота
|
||||
export async function rollbackSnapshot(vmid: number, snapname: string) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${vmid}/snapshot/${snapname}/rollback`,
|
||||
{},
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
taskId: response.data?.data
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка восстановления снэпшота:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление снэпшота
|
||||
export async function deleteSnapshot(vmid: number, snapname: string) {
|
||||
try {
|
||||
const response = await axios.delete(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc/${vmid}/snapshot/${snapname}`,
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
taskId: response.data?.data
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка удаления снэпшота:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Получение списка всех контейнеров
|
||||
export async function listContainers() {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${PROXMOX_API_URL}/nodes/${PROXMOX_NODE}/lxc`,
|
||||
{ headers: getProxmoxHeaders() }
|
||||
);
|
||||
return {
|
||||
status: 'success',
|
||||
data: response.data?.data || []
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('Ошибка получения списка контейнеров:', error);
|
||||
return {
|
||||
status: 'error',
|
||||
message: error.response?.data?.errors || error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка соединения с Proxmox
|
||||
export async function checkProxmoxConnection() {
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,12 @@ import {
|
||||
controlContainer,
|
||||
getContainerStats,
|
||||
changeRootPassword as proxmoxChangeRootPassword,
|
||||
deleteContainer
|
||||
deleteContainer,
|
||||
resizeContainer,
|
||||
createSnapshot,
|
||||
listSnapshots,
|
||||
rollbackSnapshot,
|
||||
deleteSnapshot
|
||||
} from './proxmoxApi';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
@@ -212,3 +217,88 @@ export async function changeRootPassword(req: Request, res: Response) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка смены пароля' });
|
||||
}
|
||||
}
|
||||
|
||||
// Изменить конфигурацию сервера
|
||||
export async function resizeServer(req: Request, res: Response) {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const { cores, memory, disk } = req.body;
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
|
||||
const config: any = {};
|
||||
if (cores) config.cores = Number(cores);
|
||||
if (memory) config.memory = Number(memory);
|
||||
if (disk) config.rootfs = `local:${Number(disk)}`;
|
||||
|
||||
const result = await resizeContainer(server.proxmoxId, config);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка изменения конфигурации' });
|
||||
}
|
||||
}
|
||||
|
||||
// Создать снэпшот
|
||||
export async function createServerSnapshot(req: Request, res: Response) {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const { snapname, description } = req.body;
|
||||
if (!snapname) return res.status(400).json({ error: 'Не указано имя снэпшота' });
|
||||
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
|
||||
const result = await createSnapshot(server.proxmoxId, snapname, description);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка создания снэпшота' });
|
||||
}
|
||||
}
|
||||
|
||||
// Получить список снэпшотов
|
||||
export async function getServerSnapshots(req: Request, res: Response) {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
|
||||
const result = await listSnapshots(server.proxmoxId);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка получения снэпшотов' });
|
||||
}
|
||||
}
|
||||
|
||||
// Восстановить из снэпшота
|
||||
export async function rollbackServerSnapshot(req: Request, res: Response) {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const { snapname } = req.body;
|
||||
if (!snapname) return res.status(400).json({ error: 'Не указано имя снэпшота' });
|
||||
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
|
||||
const result = await rollbackSnapshot(server.proxmoxId, snapname);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка восстановления снэпшота' });
|
||||
}
|
||||
}
|
||||
|
||||
// Удалить снэпшот
|
||||
export async function deleteServerSnapshot(req: Request, res: Response) {
|
||||
try {
|
||||
const id = Number(req.params.id);
|
||||
const { snapname } = req.body;
|
||||
if (!snapname) return res.status(400).json({ error: 'Не указано имя снэпшота' });
|
||||
|
||||
const server = await prisma.server.findUnique({ where: { id } });
|
||||
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
|
||||
|
||||
const result = await deleteSnapshot(server.proxmoxId, snapname);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
res.status(500).json({ error: error?.message || 'Ошибка удаления снэпшота' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
restartServer,
|
||||
getServerStatus,
|
||||
deleteServer,
|
||||
changeRootPassword
|
||||
changeRootPassword,
|
||||
resizeServer,
|
||||
createServerSnapshot,
|
||||
getServerSnapshots,
|
||||
rollbackServerSnapshot,
|
||||
deleteServerSnapshot
|
||||
} from './server.controller';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
@@ -72,4 +77,11 @@ router.post('/:id/restart', restartServer);
|
||||
router.delete('/:id', deleteServer);
|
||||
router.post('/:id/password', changeRootPassword);
|
||||
|
||||
// Новые маршруты для управления конфигурацией и снэпшотами
|
||||
router.put('/:id/resize', resizeServer);
|
||||
router.post('/:id/snapshots', createServerSnapshot);
|
||||
router.get('/:id/snapshots', getServerSnapshots);
|
||||
router.post('/:id/snapshots/rollback', rollbackServerSnapshot);
|
||||
router.delete('/:id/snapshots', deleteServerSnapshot);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user