User, Groups, Logs Update

This commit is contained in:
root
2025-10-29 15:38:25 +00:00
parent 4eea4b23bd
commit 36a6e2b66c
25 changed files with 3623 additions and 1724 deletions
+129 -19
View File
@@ -2,16 +2,27 @@ import { db } from './index.js';
db.exec('PRAGMA foreign_keys = ON;');
const createRedeployLogsTable = `
CREATE TABLE IF NOT EXISTS redeploy_logs (
const createEventLogsTable = `
CREATE TABLE IF NOT EXISTS event_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
stack_id TEXT NOT NULL,
stack_name TEXT NOT NULL,
status TEXT NOT NULL,
category TEXT NOT NULL,
event_type TEXT,
action TEXT,
status TEXT,
severity TEXT,
entity_type TEXT,
entity_id TEXT,
entity_name TEXT,
actor_type TEXT,
actor_id TEXT,
actor_name TEXT,
source TEXT,
context_type TEXT,
context_id TEXT,
context_label TEXT,
message TEXT,
endpoint INTEGER,
redeploy_type TEXT
metadata TEXT
);
`;
@@ -43,6 +54,7 @@ CREATE TABLE IF NOT EXISTS user_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
avatar_color TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
@@ -172,20 +184,112 @@ CREATE TABLE IF NOT EXISTS user_settings (
);
`;
db.exec(createRedeployLogsTable);
const tableExists = (tableName) => {
return Boolean(
db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?")
.get(tableName)
);
};
try {
const columns = db.prepare('PRAGMA table_info(redeploy_logs)').all();
const hasRedeployType = columns.some((column) => column.name === 'redeploy_type');
if (!hasRedeployType) {
db.exec('ALTER TABLE redeploy_logs ADD COLUMN redeploy_type TEXT');
console.log('️ redeploy_type column hinzugefügt');
const serializeMetadata = (payload) => {
if (!payload || typeof payload !== 'object' || !Object.keys(payload).length) {
return null;
}
} catch (err) {
console.error('⚠️ Konnte redeploy_type Spalte nicht prüfen/erstellen:', err.message);
}
try {
return JSON.stringify(payload);
} catch (err) {
console.error('⚠️ Konnte Migrations-Metadaten nicht serialisieren:', err.message);
return null;
}
};
console.log('✅ redeploy_logs table ready');
db.exec(createEventLogsTable);
db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_timestamp ON event_logs (timestamp DESC);');
db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_category ON event_logs (category);');
db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_event_type ON event_logs (event_type);');
db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_entity ON event_logs (entity_type, entity_id);');
db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_context ON event_logs (context_type, context_id);');
console.log('✅ event_logs table ready');
if (tableExists('redeploy_logs')) {
try {
const legacyRows = db.prepare(`
SELECT id, timestamp, stack_id, stack_name, status, message, endpoint, redeploy_type
FROM redeploy_logs
ORDER BY id ASC
`).all();
if (legacyRows.length) {
const insertEventLog = db.prepare(`
INSERT INTO event_logs (
id,
timestamp,
category,
event_type,
action,
status,
entity_type,
entity_id,
entity_name,
context_type,
context_id,
message,
metadata
) VALUES (
@id,
@timestamp,
'stack',
@eventType,
'redeploy',
@status,
'stack',
@entityId,
@entityName,
@contextType,
@contextId,
@message,
@metadata
)
`);
const migrate = db.transaction((rows) => {
rows.forEach((row) => {
const contextType = row.endpoint !== null && row.endpoint !== undefined ? 'endpoint' : null;
const contextId = contextType ? String(row.endpoint) : null;
const metadataPayload = {
origin: 'redeploy_logs'
};
if (row.redeploy_type) {
metadataPayload.redeployType = row.redeploy_type;
}
if (contextId) {
metadataPayload.endpointId = contextId;
}
insertEventLog.run({
id: row.id,
timestamp: row.timestamp,
eventType: row.redeploy_type ?? null,
status: row.status ?? null,
entityId: row.stack_id !== undefined && row.stack_id !== null ? String(row.stack_id) : null,
entityName: row.stack_name ?? null,
contextType,
contextId,
message: row.message ?? null,
metadata: serializeMetadata(metadataPayload)
});
});
});
migrate(legacyRows);
console.log(`${legacyRows.length} Einträge aus redeploy_logs migriert`);
}
db.exec('DROP TABLE redeploy_logs;');
console.log('️ Alte Tabelle redeploy_logs entfernt');
} catch (err) {
console.error('⚠️ Migration von redeploy_logs fehlgeschlagen:', err.message);
}
}
db.exec(createSettingsTable);
console.log('✅ settings table ready');
@@ -198,7 +302,13 @@ try {
const hasAvatarColor = userColumns.some((column) => column.name === 'avatar_color');
if (!hasAvatarColor) {
db.exec('ALTER TABLE users ADD COLUMN avatar_color TEXT');
console.log('️ avatar_color column hinzugefügt');
console.log('️ avatar_color column in Tabelle users hinzugefügt');
}
const userGroupColumns = db.prepare('PRAGMA table_info(user_groups)').all();
const hasAvatarColorGroup = userGroupColumns.some((column) => column.name === 'avatar_color');
if (!hasAvatarColorGroup) {
db.exec('ALTER TABLE user_groups ADD COLUMN avatar_color TEXT');
console.log('️ avatar_color column in Tabelle user_groups hinzugefügt');
}
const emailColumn = userColumns.find((column) => column.name === 'email');
if (emailColumn && emailColumn.notnull === 1) {
-181
View File
@@ -1,181 +0,0 @@
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 redeployTypes = valueToArray(queryParams.redeployTypes ?? queryParams.redeployType);
if (redeployTypes.length) {
const placeholders = redeployTypes.map((_, idx) => `@redeployType${idx}`);
filters.push(`redeploy_type IN (${placeholders.join(', ')})`);
redeployTypes.forEach((entry, idx) => {
params[`redeployType${idx}`] = entry;
});
}
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, redeploy_type)
VALUES (@stackId, @stackName, @status, @message, @endpoint, @redeployType)
`);
export function logRedeployEvent({ stackId, stackName, status, message = null, endpoint = null, redeployType = null }) {
try {
insertRedeployLogStmt.run({
stackId: String(stackId),
stackName: stackName ?? 'Unknown',
status,
message,
endpoint,
redeployType: redeployType ?? null
});
} catch (err) {
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, redeploy_type AS redeployType
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', 'redeploy_type'];
const values = [
row.id,
row.timestamp,
row.stackId,
row.stackName,
row.status,
row.message,
row.endpoint,
row.redeployType
].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 ?? '-'}`,
`Redeploy: ${row.redeployType ?? '-'}`
];
return parts.join(' | ');
});
return {
filename: `redeploy-logs-${timestamp}.txt`,
contentType: 'text/plain; charset=utf-8',
content: lines.join('\n')
};
}