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: []
|
history: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_HISTORY = 100;
|
|
||||||
|
|
||||||
class ConfigStore {
|
class ConfigStore {
|
||||||
constructor(app) {
|
constructor(app) {
|
||||||
const dir = app && app.isPackaged
|
const dir = app && app.isPackaged
|
||||||
@ -253,9 +251,6 @@ class ConfigStore {
|
|||||||
return this._enqueueWrite(() => {
|
return this._enqueueWrite(() => {
|
||||||
const config = this.load();
|
const config = this.load();
|
||||||
config.history.push(entry);
|
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));
|
return this._atomicWrite(JSON.stringify(config, null, 2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
128
main.js
128
main.js
@ -123,6 +123,97 @@ function appendUploadLog(hoster, link, fileName) {
|
|||||||
} catch {}
|
} 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 ---
|
// --- Multi-account helpers ---
|
||||||
function hosterAccountHasCreds(name, account) {
|
function hosterAccountHasCreds(name, account) {
|
||||||
if (!account) return false;
|
if (!account) return false;
|
||||||
@ -571,6 +662,43 @@ ipcMain.handle('get-history', () => {
|
|||||||
return configStore.loadHistory();
|
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) => {
|
ipcMain.handle('run-health-check', async (_event, payload) => {
|
||||||
const config = configStore.load();
|
const config = configStore.load();
|
||||||
const hosters = payload && Array.isArray(payload.hosters) ? payload.hosters : [];
|
const hosters = payload && Array.isArray(payload.hosters) ? payload.hosters : [];
|
||||||
|
|||||||
@ -6,6 +6,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
saveConfig: (config) => ipcRenderer.invoke('save-config', config),
|
saveConfig: (config) => ipcRenderer.invoke('save-config', config),
|
||||||
getHistory: () => ipcRenderer.invoke('get-history'),
|
getHistory: () => ipcRenderer.invoke('get-history'),
|
||||||
clearHistory: () => ipcRenderer.invoke('clear-history'),
|
clearHistory: () => ipcRenderer.invoke('clear-history'),
|
||||||
|
exportHistory: (format) => ipcRenderer.invoke('export-history', format),
|
||||||
|
|
||||||
// Hoster settings
|
// Hoster settings
|
||||||
getHosterSettings: () => ipcRenderer.invoke('get-hoster-settings'),
|
getHosterSettings: () => ipcRenderer.invoke('get-hoster-settings'),
|
||||||
|
|||||||
@ -2934,6 +2934,26 @@ async function loadHistory() {
|
|||||||
renderHistoryTable(container);
|
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) {
|
function sortRecentFiles(data) {
|
||||||
const sorted = data.slice();
|
const sorted = data.slice();
|
||||||
const { key, direction } = recentSortState;
|
const { key, direction } = recentSortState;
|
||||||
@ -3193,6 +3213,7 @@ function setupListeners() {
|
|||||||
await window.api.clearHistory();
|
await window.api.clearHistory();
|
||||||
loadHistory();
|
loadHistory();
|
||||||
});
|
});
|
||||||
|
document.getElementById('exportHistoryBtn').addEventListener('click', exportHistory);
|
||||||
|
|
||||||
// Auto health check toggle
|
// Auto health check toggle
|
||||||
const autoToggle = document.getElementById('autoHealthCheckToggle');
|
const autoToggle = document.getElementById('autoHealthCheckToggle');
|
||||||
|
|||||||
@ -227,8 +227,11 @@
|
|||||||
<div class="history-container">
|
<div class="history-container">
|
||||||
<div class="history-header">
|
<div class="history-header">
|
||||||
<h2>Upload-Verlauf</h2>
|
<h2>Upload-Verlauf</h2>
|
||||||
|
<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>
|
<button class="btn btn-secondary" id="clearHistoryBtn">Verlauf löschen</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="historyContainer"></div>
|
<div id="historyContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -93,14 +93,14 @@ describe('ConfigStore', () => {
|
|||||||
assert.equal(config.hosterSettings['doodstream.com'].retries, 10); // preserved
|
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++) {
|
for (let i = 0; i < 105; i++) {
|
||||||
await store.appendHistory({ id: `batch-${i}`, timestamp: new Date().toISOString(), files: [] });
|
await store.appendHistory({ id: `batch-${i}`, timestamp: new Date().toISOString(), files: [] });
|
||||||
}
|
}
|
||||||
const history = store.loadHistory();
|
const history = store.loadHistory();
|
||||||
assert.equal(history.length, 100);
|
assert.equal(history.length, 105);
|
||||||
assert.equal(history[0].id, 'batch-5'); // first 5 dropped
|
assert.equal(history[0].id, 'batch-0');
|
||||||
assert.equal(history[99].id, 'batch-104');
|
assert.equal(history[104].id, 'batch-104');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clearHistory empties the array', async () => {
|
it('clearHistory empties the array', async () => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user