Restore the v2.1.19 application baseline and retain only the focused import preflight summary with duplicate, unavailable, destination, job, and size-limit visibility.
This commit is contained in:
+1
-117
@@ -39,116 +39,6 @@
|
||||
return out;
|
||||
}
|
||||
|
||||
function summarizeHosterHealth(history, options = {}) {
|
||||
const out = {};
|
||||
const hosters = options.hosters && typeof options.hosters === 'object' ? options.hosters : {};
|
||||
const accountStatuses = options.accountStatuses && typeof options.accountStatuses === 'object' ? options.accountStatuses : {};
|
||||
const failedKeys = new Set(options.sessionFailedKeys instanceof Set
|
||||
? options.sessionFailedKeys
|
||||
: (Array.isArray(options.sessionFailedKeys) ? options.sessionFailedKeys : []));
|
||||
const nowCandidate = options.now instanceof Date
|
||||
? options.now.getTime()
|
||||
: (Number.isFinite(options.now) ? Number(options.now) : Date.parse(options.now));
|
||||
const nowMs = Number.isFinite(nowCandidate) ? nowCandidate : Date.now();
|
||||
const recentCutoff = nowMs - 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const ensure = (name) => out[name] || (out[name] = {
|
||||
sampleSize: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
successRate: null,
|
||||
effectiveBytes: 0,
|
||||
effectiveDurationSec: 0,
|
||||
effectiveBytesPerSecond: null,
|
||||
lastSuccessAt: null,
|
||||
failuresLast7Days: 0,
|
||||
configuredAccounts: 0,
|
||||
accountProblems: 0,
|
||||
uncheckedAccounts: 0,
|
||||
checkingAccounts: 0
|
||||
});
|
||||
|
||||
const hasCredentials = (account) => {
|
||||
if (!account || typeof account !== 'object' || !account.id) return false;
|
||||
if (account.authType === 'api') return Boolean(String(account.apiKey || '').trim());
|
||||
if (account.authType === 'login') return Boolean(String(account.username || '').trim() && String(account.password || '').trim());
|
||||
return Boolean(String(account.apiKey || '').trim() || (String(account.username || '').trim() && String(account.password || '').trim()));
|
||||
};
|
||||
|
||||
for (const [name, accountsValue] of Object.entries(hosters)) {
|
||||
const bucket = ensure(name);
|
||||
const accounts = Array.isArray(accountsValue) ? accountsValue : [];
|
||||
bucket.configuredAccounts = accounts.length;
|
||||
for (const account of accounts) {
|
||||
if (account?.enabled === false) continue;
|
||||
const status = accountStatuses[account?.id]?.status || 'unchecked';
|
||||
const unavailable = !hasCredentials(account);
|
||||
const problem = unavailable || failedKeys.has(`${name}:${account?.id || ''}`) || ['error', 'warn', 'otp_required'].includes(status);
|
||||
if (problem) bucket.accountProblems++;
|
||||
if (!unavailable && status === 'unchecked') bucket.uncheckedAccounts++;
|
||||
if (!unavailable && status === 'checking') bucket.checkingAccounts++;
|
||||
}
|
||||
}
|
||||
|
||||
const validBatches = (Array.isArray(history) ? history : [])
|
||||
.map((batch, index) => {
|
||||
const timestampMs = batch?.timestamp ? Date.parse(batch.timestamp) : NaN;
|
||||
return { batch, index, timestampMs };
|
||||
})
|
||||
.filter(({ timestampMs }) => Number.isFinite(timestampMs) && timestampMs <= nowMs);
|
||||
const batches = [...validBatches]
|
||||
.sort((a, b) => b.timestampMs - a.timestampMs || b.index - a.index)
|
||||
.slice(0, 50);
|
||||
|
||||
for (const { batch, timestampMs } of batches) {
|
||||
if (!batch || !Array.isArray(batch.files)) continue;
|
||||
for (const file of batch.files) {
|
||||
if (!file || !Array.isArray(file.results)) continue;
|
||||
const fileSize = Number(file.size);
|
||||
for (const result of file.results) {
|
||||
if (!result?.hoster) continue;
|
||||
const bucket = ensure(result.hoster);
|
||||
bucket.sampleSize++;
|
||||
if (result.status === 'done') {
|
||||
bucket.successful++;
|
||||
const previous = bucket.lastSuccessAt ? Date.parse(bucket.lastSuccessAt) : -Infinity;
|
||||
if (timestampMs > previous) bucket.lastSuccessAt = new Date(timestampMs).toISOString();
|
||||
const durationSec = Number(result.durationSec);
|
||||
if (Number.isFinite(fileSize) && fileSize > 0 && Number.isFinite(durationSec) && durationSec > 0) {
|
||||
bucket.effectiveBytes += fileSize;
|
||||
bucket.effectiveDurationSec += durationSec;
|
||||
}
|
||||
} else if (result.status === 'skipped') {
|
||||
bucket.skipped++;
|
||||
} else {
|
||||
bucket.failed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const { batch, timestampMs } of validBatches) {
|
||||
if (timestampMs < recentCutoff || !Array.isArray(batch?.files)) continue;
|
||||
for (const file of batch.files) {
|
||||
if (!Array.isArray(file?.results)) continue;
|
||||
for (const result of file.results) {
|
||||
if (!result?.hoster || result.status === 'done' || result.status === 'skipped') continue;
|
||||
ensure(result.hoster).failuresLast7Days++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const bucket of Object.values(out)) {
|
||||
const attempted = bucket.successful + bucket.failed;
|
||||
bucket.successRate = attempted > 0 ? bucket.successful / attempted : null;
|
||||
bucket.effectiveBytesPerSecond = bucket.effectiveDurationSec > 0
|
||||
? bucket.effectiveBytes / bucket.effectiveDurationSec
|
||||
: null;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function classifyErrorCategory(err) {
|
||||
if (!err || typeof err !== 'string') return 'unknown';
|
||||
const s = err.toLowerCase();
|
||||
@@ -196,10 +86,8 @@
|
||||
};
|
||||
const existingJobIds = new Set();
|
||||
const filesByName = new Map();
|
||||
const filesByKey = new Map();
|
||||
for (const file of merged.files) {
|
||||
filesByName.set(String(file.name || file.fileName || ''), file);
|
||||
if (file.fileKey) filesByKey.set(String(file.fileKey), file);
|
||||
for (const result of file.results) {
|
||||
if (result?.jobId) existingJobIds.add(result.jobId);
|
||||
}
|
||||
@@ -208,14 +96,11 @@
|
||||
for (const skipped of Array.isArray(skippedJobs) ? skippedJobs : []) {
|
||||
if (!skipped || (skipped.jobId && existingJobIds.has(skipped.jobId))) continue;
|
||||
const fileName = String(skipped.fileName || skipped.file || '').split(/[\\/]/).pop() || '';
|
||||
const fileKey = String(skipped.fileKey || '');
|
||||
let file = fileKey ? filesByKey.get(fileKey) : filesByName.get(fileName);
|
||||
let file = filesByName.get(fileName);
|
||||
if (!file) {
|
||||
file = { name: fileName, size: Number(skipped.size) || 0, results: [] };
|
||||
if (fileKey) file.fileKey = fileKey;
|
||||
merged.files.push(file);
|
||||
filesByName.set(fileName, file);
|
||||
if (fileKey) filesByKey.set(fileKey, file);
|
||||
}
|
||||
file.results.push({
|
||||
jobId: skipped.jobId || null,
|
||||
@@ -285,7 +170,6 @@
|
||||
|
||||
const api = {
|
||||
summarizePerHoster,
|
||||
summarizeHosterHealth,
|
||||
classifyErrorCategory,
|
||||
summarizeBatchErrors,
|
||||
mergeSkippedIntoSummary,
|
||||
|
||||
Reference in New Issue
Block a user