Add full upload history export and keep complete history

This commit is contained in:
Administrator 2026-03-28 19:48:28 +01:00
parent 29ab989cbe
commit c197a004c8
6 changed files with 158 additions and 10 deletions

View File

@ -86,8 +86,6 @@ const DEFAULTS = {
history: []
};
const MAX_HISTORY = 100;
class ConfigStore {
constructor(app) {
const dir = app && app.isPackaged
@ -253,9 +251,6 @@ class ConfigStore {
return this._enqueueWrite(() => {
const config = this.load();
config.history.push(entry);
if (config.history.length > MAX_HISTORY) {
config.history = config.history.slice(-MAX_HISTORY);
}
return this._atomicWrite(JSON.stringify(config, null, 2));
});
}

128
main.js
View File

@ -123,6 +123,97 @@ function appendUploadLog(hoster, link, fileName) {
} catch {}
}
function flattenHistoryForExport(history) {
const rows = [];
const list = Array.isArray(history) ? history : [];
for (const batch of list) {
const batchId = batch && batch.id ? String(batch.id) : '';
const rawTs = batch && batch.timestamp ? String(batch.timestamp) : '';
const parsedTs = rawTs ? new Date(rawTs) : null;
const batchTimestamp = parsedTs && !Number.isNaN(parsedTs.getTime())
? parsedTs.toISOString()
: rawTs;
const files = Array.isArray(batch && batch.files) ? batch.files : [];
for (const file of files) {
const fileName = file && file.name ? String(file.name) : '';
const filePath = file && file.path ? String(file.path) : '';
const fileSize = Number.isFinite(Number(file && file.size)) ? Number(file.size) : '';
const results = Array.isArray(file && file.results) ? file.results : [];
if (results.length === 0) {
rows.push({
batchId,
batchTimestamp,
fileName,
filePath,
fileSize,
hoster: '',
status: '',
link: '',
error: ''
});
continue;
}
for (const result of results) {
const link = result && (result.download_url || result.embed_url || result.file_code)
? String(result.download_url || result.embed_url || result.file_code)
: '';
rows.push({
batchId,
batchTimestamp,
fileName,
filePath,
fileSize,
hoster: result && result.hoster ? String(result.hoster) : '',
status: result && result.status ? String(result.status) : '',
link,
error: result && (result.error || result.message) ? String(result.error || result.message) : ''
});
}
}
}
return rows;
}
function toCsvCell(value) {
const text = value === null || value === undefined ? '' : String(value);
if (!/[",\r\n]/.test(text)) return text;
return `"${text.replace(/"/g, '""')}"`;
}
function buildHistoryCsv(rows) {
const header = [
'Batch ID',
'Batch Timestamp',
'File Name',
'File Path',
'File Size Bytes',
'Hoster',
'Status',
'Link',
'Error'
];
const lines = [header.map(toCsvCell).join(',')];
for (const row of rows) {
lines.push([
row.batchId,
row.batchTimestamp,
row.fileName,
row.filePath,
row.fileSize,
row.hoster,
row.status,
row.link,
row.error
].map(toCsvCell).join(','));
}
return `${lines.join('\n')}\n`;
}
// --- Multi-account helpers ---
function hosterAccountHasCreds(name, account) {
if (!account) return false;
@ -571,6 +662,43 @@ ipcMain.handle('get-history', () => {
return configStore.loadHistory();
});
ipcMain.handle('export-history', async (_event, format) => {
const normalizedFormat = String(format || 'csv').toLowerCase() === 'json' ? 'json' : 'csv';
const history = configStore.loadHistory();
const rows = flattenHistoryForExport(history);
const datePrefix = new Date().toISOString().slice(0, 10);
const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, {
title: 'Upload-Verlauf exportieren',
defaultPath: `upload-history-${datePrefix}.${normalizedFormat}`,
filters: normalizedFormat === 'json'
? [{ name: 'JSON-Datei', extensions: ['json'] }]
: [{ name: 'CSV-Datei', extensions: ['csv'] }]
});
if (canceled || !filePath) return { ok: false, canceled: true };
if (normalizedFormat === 'json') {
const payload = {
exportedAt: new Date().toISOString(),
totalBatches: Array.isArray(history) ? history.length : 0,
totalRows: rows.length,
history
};
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf-8');
} else {
fs.writeFileSync(filePath, buildHistoryCsv(rows), 'utf-8');
}
return {
ok: true,
path: filePath,
format: normalizedFormat,
totalBatches: Array.isArray(history) ? history.length : 0,
totalRows: rows.length
};
});
ipcMain.handle('run-health-check', async (_event, payload) => {
const config = configStore.load();
const hosters = payload && Array.isArray(payload.hosters) ? payload.hosters : [];

View File

@ -6,6 +6,7 @@ contextBridge.exposeInMainWorld('api', {
saveConfig: (config) => ipcRenderer.invoke('save-config', config),
getHistory: () => ipcRenderer.invoke('get-history'),
clearHistory: () => ipcRenderer.invoke('clear-history'),
exportHistory: (format) => ipcRenderer.invoke('export-history', format),
// Hoster settings
getHosterSettings: () => ipcRenderer.invoke('get-hoster-settings'),

View File

@ -2934,6 +2934,26 @@ async function loadHistory() {
renderHistoryTable(container);
}
async function exportHistory() {
const history = await window.api.getHistory();
if (!history || history.length === 0) {
alert('Kein Verlauf zum Exportieren vorhanden.');
return;
}
const asCsv = confirm('Verlauf als CSV exportieren?\n\nOK = CSV\nAbbrechen = JSON');
const format = asCsv ? 'csv' : 'json';
const result = await window.api.exportHistory(format);
if (!result || result.canceled) return;
if (!result.ok) {
alert(result.error || 'Export fehlgeschlagen.');
return;
}
showCopyToast(`Verlauf exportiert (${result.totalRows || 0} Zeilen)`);
}
function sortRecentFiles(data) {
const sorted = data.slice();
const { key, direction } = recentSortState;
@ -3193,6 +3213,7 @@ function setupListeners() {
await window.api.clearHistory();
loadHistory();
});
document.getElementById('exportHistoryBtn').addEventListener('click', exportHistory);
// Auto health check toggle
const autoToggle = document.getElementById('autoHealthCheckToggle');

View File

@ -227,7 +227,10 @@
<div class="history-container">
<div class="history-header">
<h2>Upload-Verlauf</h2>
<button class="btn btn-secondary" id="clearHistoryBtn">Verlauf löschen</button>
<div style="display:flex; gap:8px">
<button class="btn btn-secondary" id="exportHistoryBtn">Verlauf exportieren</button>
<button class="btn btn-secondary" id="clearHistoryBtn">Verlauf löschen</button>
</div>
</div>
<div id="historyContainer"></div>
</div>

View File

@ -93,14 +93,14 @@ describe('ConfigStore', () => {
assert.equal(config.hosterSettings['doodstream.com'].retries, 10); // preserved
});
it('appendHistory adds entries and caps at 100', async () => {
it('appendHistory keeps complete history without truncation', async () => {
for (let i = 0; i < 105; i++) {
await store.appendHistory({ id: `batch-${i}`, timestamp: new Date().toISOString(), files: [] });
}
const history = store.loadHistory();
assert.equal(history.length, 100);
assert.equal(history[0].id, 'batch-5'); // first 5 dropped
assert.equal(history[99].id, 'batch-104');
assert.equal(history.length, 105);
assert.equal(history[0].id, 'batch-0');
assert.equal(history[104].id, 'batch-104');
});
it('clearHistory empties the array', async () => {