Add full upload history export and keep complete history
This commit is contained in:
parent
29ab989cbe
commit
c197a004c8
@ -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
128
main.js
@ -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 : [];
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user