Delete/Export/Pageination
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,86 @@
|
||||
import { db } from './index.js';
|
||||
|
||||
const valueToArray = (value) => {
|
||||
if (!value && value !== 0) return [];
|
||||
const base = Array.isArray(value) ? value : [value];
|
||||
return base
|
||||
.flatMap((entry) => String(entry).split(','))
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const singleValue = (value) => {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
};
|
||||
|
||||
export function buildLogFilter(queryParams = {}) {
|
||||
const filters = [];
|
||||
const params = {};
|
||||
|
||||
const ids = valueToArray(queryParams.ids ?? queryParams.id)
|
||||
.map((entry) => Number(entry))
|
||||
.filter((value) => !Number.isNaN(value));
|
||||
if (ids.length) {
|
||||
const placeholders = ids.map((_, idx) => `@id${idx}`);
|
||||
filters.push(`id IN (${placeholders.join(', ')})`);
|
||||
ids.forEach((value, idx) => {
|
||||
params[`id${idx}`] = value;
|
||||
});
|
||||
}
|
||||
|
||||
const stackIds = valueToArray(queryParams.stackIds ?? queryParams.stackId);
|
||||
if (stackIds.length) {
|
||||
const placeholders = stackIds.map((_, idx) => `@stackId${idx}`);
|
||||
filters.push(`stack_id IN (${placeholders.join(', ')})`);
|
||||
stackIds.forEach((stack, idx) => {
|
||||
params[`stackId${idx}`] = stack;
|
||||
});
|
||||
}
|
||||
|
||||
const statuses = valueToArray(queryParams.statuses ?? queryParams.status);
|
||||
if (statuses.length) {
|
||||
const placeholders = statuses.map((_, idx) => `@status${idx}`);
|
||||
filters.push(`status IN (${placeholders.join(', ')})`);
|
||||
statuses.forEach((entry, idx) => {
|
||||
params[`status${idx}`] = entry;
|
||||
});
|
||||
}
|
||||
|
||||
const endpoints = valueToArray(queryParams.endpoints ?? queryParams.endpoint);
|
||||
if (endpoints.length) {
|
||||
const placeholders = endpoints.map((_, idx) => `@endpoint${idx}`);
|
||||
filters.push(`endpoint IN (${placeholders.join(', ')})`);
|
||||
endpoints.forEach((entry, idx) => {
|
||||
const numeric = Number(entry);
|
||||
params[`endpoint${idx}`] = Number.isNaN(numeric) ? entry : numeric;
|
||||
});
|
||||
}
|
||||
|
||||
const messageQuery = singleValue(queryParams.message);
|
||||
if (messageQuery && String(messageQuery).trim()) {
|
||||
filters.push('message LIKE @message');
|
||||
params.message = `%${String(messageQuery).trim()}%`;
|
||||
}
|
||||
|
||||
const from = singleValue(queryParams.from);
|
||||
if (from) {
|
||||
filters.push('timestamp >= @from');
|
||||
params.from = from;
|
||||
}
|
||||
|
||||
const to = singleValue(queryParams.to);
|
||||
if (to) {
|
||||
filters.push('timestamp <= @to');
|
||||
params.to = to;
|
||||
}
|
||||
|
||||
return {
|
||||
whereClause: filters.length ? `WHERE ${filters.join(' AND ')}` : '',
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
const insertRedeployLogStmt = db.prepare(`
|
||||
INSERT INTO redeploy_logs (stack_id, stack_name, status, message, endpoint)
|
||||
VALUES (@stackId, @stackName, @status, @message, @endpoint)
|
||||
@@ -18,3 +99,71 @@ export function logRedeployEvent({ stackId, stackName, status, message = null, e
|
||||
console.error('❌ Fehler beim Speichern des Redeploy-Logs:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteLogById(id) {
|
||||
const stmt = db.prepare('DELETE FROM redeploy_logs WHERE id = ?');
|
||||
const info = stmt.run(id);
|
||||
return info.changes || 0;
|
||||
}
|
||||
|
||||
export function deleteLogsByFilters(queryParams = {}) {
|
||||
const { whereClause, params } = buildLogFilter(queryParams);
|
||||
const stmt = db.prepare(`DELETE FROM redeploy_logs ${whereClause}`);
|
||||
const info = stmt.run(params);
|
||||
return info.changes || 0;
|
||||
}
|
||||
|
||||
export function exportLogsByFilters(queryParams = {}, format = 'txt') {
|
||||
const { whereClause, params } = buildLogFilter(queryParams);
|
||||
const rows = db.prepare(`
|
||||
SELECT id, timestamp, stack_id AS stackId, stack_name AS stackName, status, message, endpoint
|
||||
FROM redeploy_logs
|
||||
${whereClause}
|
||||
ORDER BY datetime(timestamp) DESC
|
||||
`).all(params);
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
|
||||
if (format === 'sql') {
|
||||
const statements = rows.map((row) => {
|
||||
const columns = ['id', 'timestamp', 'stack_id', 'stack_name', 'status', 'message', 'endpoint'];
|
||||
const values = [
|
||||
row.id,
|
||||
row.timestamp,
|
||||
row.stackId,
|
||||
row.stackName,
|
||||
row.status,
|
||||
row.message,
|
||||
row.endpoint
|
||||
].map((value) => {
|
||||
if (value === null || value === undefined) return 'NULL';
|
||||
return `'${String(value).replace(/'/g, "''")}'`;
|
||||
});
|
||||
return `INSERT INTO redeploy_logs (${columns.join(', ')}) VALUES (${values.join(', ')});`;
|
||||
});
|
||||
|
||||
return {
|
||||
filename: `redeploy-logs-${timestamp}.sql`,
|
||||
contentType: 'application/sql; charset=utf-8',
|
||||
content: statements.join('\n')
|
||||
};
|
||||
}
|
||||
|
||||
const lines = rows.map((row) => {
|
||||
const parts = [
|
||||
`[${row.id}]`,
|
||||
row.timestamp,
|
||||
`Stack: ${row.stackName ?? 'Unbekannt'} (ID: ${row.stackId})`,
|
||||
`Status: ${row.status}`,
|
||||
`Endpoint: ${row.endpoint ?? '-'}`,
|
||||
`Nachricht: ${row.message ?? '-'}`
|
||||
];
|
||||
return parts.join(' | ');
|
||||
});
|
||||
|
||||
return {
|
||||
filename: `redeploy-logs-${timestamp}.txt`,
|
||||
contentType: 'text/plain; charset=utf-8',
|
||||
content: lines.join('\n')
|
||||
};
|
||||
}
|
||||
|
||||
+82
-69
@@ -7,7 +7,13 @@ import { Server } from 'socket.io';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { db } from './db/index.js';
|
||||
import { logRedeployEvent } from './db/redeployLogs.js';
|
||||
import {
|
||||
logRedeployEvent,
|
||||
buildLogFilter,
|
||||
deleteLogById,
|
||||
deleteLogsByFilters,
|
||||
exportLogsByFilters
|
||||
} from './db/redeployLogs.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -95,74 +101,15 @@ app.get('/api/stacks', async (req, res) => {
|
||||
|
||||
// Redeploy-Logs abrufen
|
||||
app.get('/api/logs', (req, res) => {
|
||||
const limit = Math.min(parseInt(req.query.limit, 10) || 100, 500);
|
||||
const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0);
|
||||
const perPageParam = req.query.perPage ?? req.query.limit;
|
||||
const perPage = perPageParam === 'all' ? 'all' : Math.min(parseInt(perPageParam, 10) || 50, 500);
|
||||
const page = Math.max(parseInt(req.query.page, 10) || 1, 1);
|
||||
|
||||
const valueToArray = (value) => {
|
||||
if (!value && value !== 0) return [];
|
||||
const base = Array.isArray(value) ? value : [value];
|
||||
return base
|
||||
.flatMap((entry) => String(entry).split(','))
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
const limit = perPage === 'all' ? undefined : perPage;
|
||||
const offset = perPage === 'all' ? 0 : (page - 1) * perPage;
|
||||
|
||||
const singleValue = (value) => {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
};
|
||||
|
||||
const filters = [];
|
||||
const params = { limit, offset };
|
||||
|
||||
const stackIds = valueToArray(req.query.stackIds ?? req.query.stackId);
|
||||
if (stackIds.length) {
|
||||
const placeholders = stackIds.map((_, idx) => `@stackId${idx}`);
|
||||
filters.push(`stack_id IN (${placeholders.join(', ')})`);
|
||||
stackIds.forEach((stack, idx) => {
|
||||
params[`stackId${idx}`] = stack;
|
||||
});
|
||||
}
|
||||
|
||||
const statuses = valueToArray(req.query.statuses ?? req.query.status);
|
||||
if (statuses.length) {
|
||||
const placeholders = statuses.map((_, idx) => `@status${idx}`);
|
||||
filters.push(`status IN (${placeholders.join(', ')})`);
|
||||
statuses.forEach((entry, idx) => {
|
||||
params[`status${idx}`] = entry;
|
||||
});
|
||||
}
|
||||
|
||||
const endpoints = valueToArray(req.query.endpoints ?? req.query.endpoint);
|
||||
if (endpoints.length) {
|
||||
const placeholders = endpoints.map((_, idx) => `@endpoint${idx}`);
|
||||
filters.push(`endpoint IN (${placeholders.join(', ')})`);
|
||||
endpoints.forEach((entry, idx) => {
|
||||
const numeric = Number(entry);
|
||||
params[`endpoint${idx}`] = Number.isNaN(numeric) ? entry : numeric;
|
||||
});
|
||||
}
|
||||
|
||||
const messageQuery = singleValue(req.query.message);
|
||||
if (messageQuery && String(messageQuery).trim()) {
|
||||
filters.push('message LIKE @message');
|
||||
params.message = `%${String(messageQuery).trim()}%`;
|
||||
}
|
||||
|
||||
const from = singleValue(req.query.from);
|
||||
if (from) {
|
||||
filters.push('timestamp >= @from');
|
||||
params.from = from;
|
||||
}
|
||||
|
||||
const to = singleValue(req.query.to);
|
||||
if (to) {
|
||||
filters.push('timestamp <= @to');
|
||||
params.to = to;
|
||||
}
|
||||
|
||||
const whereClause = filters.length ? `WHERE ${filters.join(' AND ')}` : '';
|
||||
const query = `
|
||||
const { whereClause, params } = buildLogFilter(req.query);
|
||||
const baseQuery = `
|
||||
SELECT
|
||||
id,
|
||||
timestamp,
|
||||
@@ -174,13 +121,34 @@ app.get('/api/logs', (req, res) => {
|
||||
FROM redeploy_logs
|
||||
${whereClause}
|
||||
ORDER BY datetime(timestamp) DESC
|
||||
LIMIT @limit OFFSET @offset
|
||||
`;
|
||||
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) as total
|
||||
FROM redeploy_logs
|
||||
${whereClause}
|
||||
`;
|
||||
|
||||
const query = limit !== undefined
|
||||
? `${baseQuery} LIMIT @limit OFFSET @offset`
|
||||
: baseQuery;
|
||||
|
||||
if (limit !== undefined) {
|
||||
params.limit = limit;
|
||||
params.offset = offset;
|
||||
}
|
||||
|
||||
try {
|
||||
const stmt = db.prepare(query);
|
||||
const logs = stmt.all(params);
|
||||
res.json(logs);
|
||||
const total = db.prepare(countQuery).get(params)?.total ?? logs.length;
|
||||
|
||||
res.json({
|
||||
total,
|
||||
items: logs,
|
||||
page,
|
||||
perPage: perPage === 'all' ? 'all' : limit
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler beim Abrufen der Redeploy-Logs:', err.message);
|
||||
if (err.message.includes('no such table')) {
|
||||
@@ -190,6 +158,51 @@ app.get('/api/logs', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/logs/:id', (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
if (Number.isNaN(id)) {
|
||||
return res.status(400).json({ error: 'Ungültige ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
const changes = deleteLogById(id);
|
||||
if (!changes) {
|
||||
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
|
||||
}
|
||||
res.json({ success: true, deleted: changes });
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler beim Löschen des Redeploy-Logs:', err.message);
|
||||
res.status(500).json({ error: 'Fehler beim Löschen des Redeploy-Logs' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/logs', (req, res) => {
|
||||
try {
|
||||
const deleted = deleteLogsByFilters(req.query);
|
||||
res.json({ success: true, deleted });
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler beim Löschen der Redeploy-Logs:', err.message);
|
||||
res.status(500).json({ error: 'Fehler beim Löschen der Redeploy-Logs' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/logs/export', (req, res) => {
|
||||
const format = (req.query.format || 'txt').toLowerCase();
|
||||
if (!['txt', 'sql'].includes(format)) {
|
||||
return res.status(400).json({ error: 'Ungültiges Export-Format' });
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = exportLogsByFilters(req.query, format);
|
||||
res.setHeader('Content-Type', payload.contentType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${payload.filename}"`);
|
||||
res.send(payload.content);
|
||||
} catch (err) {
|
||||
console.error('❌ Fehler beim Export der Redeploy-Logs:', err.message);
|
||||
res.status(500).json({ error: 'Fehler beim Export der Redeploy-Logs' });
|
||||
}
|
||||
});
|
||||
|
||||
// Einzel-Redeploy
|
||||
app.put('/api/stacks/:id/redeploy', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
Reference in New Issue
Block a user