v1.0.19
This commit is contained in:
1 parent
4b430340db
commit
9c451aedf9
120 files changed
+7777
-6046
No files matched your search
@@ -70,7 +70,7 @@
|
||||
let successCount = $state(0);
|
||||
let failCount = $state(0);
|
||||
let cancelledCount = $state(0);
|
||||
let abortController: AbortController | null = null;
|
||||
let cancelled = false;
|
||||
|
||||
// Progress calculation
|
||||
const progress = $derived(() => {
|
||||
@@ -88,9 +88,7 @@
|
||||
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
}
|
||||
cancelled = true;
|
||||
});
|
||||
|
||||
async function startOperation() {
|
||||
@@ -106,20 +104,13 @@
|
||||
successCount = 0;
|
||||
failCount = 0;
|
||||
cancelledCount = 0;
|
||||
|
||||
abortController = new AbortController();
|
||||
cancelled = false;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/batch${envId ? `?env=${envId}` : ''}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
operation,
|
||||
entityType,
|
||||
items,
|
||||
options
|
||||
}),
|
||||
signal: abortController.signal
|
||||
body: JSON.stringify({ operation, entityType, items, options })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -127,52 +118,44 @@
|
||||
throw new Error(error.error || 'Request failed');
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error('No response body');
|
||||
}
|
||||
const data = await response.json();
|
||||
const { jobId } = data;
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
// Poll job for progress events
|
||||
let cursor = 0;
|
||||
while (!cancelled) {
|
||||
const jobRes = await fetch(`/api/jobs/${jobId}`);
|
||||
if (!jobRes.ok) break;
|
||||
const job = await jobRes.json();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const event: BatchEvent = JSON.parse(line.slice(6));
|
||||
handleEvent(event);
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
// Process new lines since last poll
|
||||
const newLines = job.lines.slice(cursor);
|
||||
cursor = job.lines.length;
|
||||
for (const line of newLines) {
|
||||
handleEvent(line.data as BatchEvent);
|
||||
}
|
||||
|
||||
if (job.status !== 'running') break;
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.name === 'AbortError') {
|
||||
// User cancelled - mark remaining as cancelled
|
||||
let cancelled = 0;
|
||||
|
||||
if (cancelled) {
|
||||
// Mark remaining items as cancelled
|
||||
let cancelCount = 0;
|
||||
itemStates = itemStates.map(item => {
|
||||
if (item.status === 'pending' || item.status === 'processing') {
|
||||
cancelled++;
|
||||
cancelCount++;
|
||||
return { ...item, status: 'cancelled' as ItemStatus };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
cancelledCount = cancelled;
|
||||
} else {
|
||||
console.error('Batch operation error:', error);
|
||||
cancelledCount = cancelCount;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Batch operation error:', error);
|
||||
} finally {
|
||||
isRunning = false;
|
||||
isComplete = true;
|
||||
abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +178,7 @@
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
}
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { CheckCircle2, XCircle, Loader2, AlertCircle, Terminal, Sun, Moon, Download } from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { appendEnvParam } from '$lib/stores/environment';
|
||||
import { watchJob } from '$lib/utils/sse-fetch';
|
||||
|
||||
interface LayerProgress {
|
||||
id: string;
|
||||
@@ -168,33 +169,10 @@
|
||||
throw new Error('Failed to start pull');
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new Error('No response body');
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim() || !line.startsWith('data: ')) continue;
|
||||
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6));
|
||||
handlePullProgress(data);
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
const { jobId } = await response.json();
|
||||
await watchJob(jobId, (line) => {
|
||||
handlePullProgress(line.data as any);
|
||||
});
|
||||
|
||||
if (status === 'pulling') {
|
||||
duration = Date.now() - startTime;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { tick, onMount } from 'svelte';
|
||||
import { CheckCircle2, XCircle, Loader2, AlertCircle, Terminal, Sun, Moon, Upload } from 'lucide-svelte';
|
||||
import { appendEnvParam } from '$lib/stores/environment';
|
||||
import { watchJob } from '$lib/utils/sse-fetch';
|
||||
|
||||
type PushStatus = 'idle' | 'pushing' | 'complete' | 'error';
|
||||
|
||||
@@ -144,39 +145,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle SSE stream
|
||||
const reader = pushResponse.body?.getReader();
|
||||
if (!reader) {
|
||||
errorMessage = 'No response body';
|
||||
status = 'error';
|
||||
onError?.(errorMessage);
|
||||
return;
|
||||
}
|
||||
const { jobId } = await pushResponse.json();
|
||||
await watchJob(jobId, (line) => {
|
||||
handlePushProgress(line.data as any);
|
||||
});
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6));
|
||||
handlePushProgress(data);
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If stream ended without complete/error status
|
||||
// If job ended without an explicit complete/error event
|
||||
if (status === 'pushing') {
|
||||
status = 'complete';
|
||||
statusMessage = 'Image pushed successfully!';
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Loader2, AlertCircle, Terminal, Sun, Moon, ShieldCheck, ShieldAlert, ShieldX, Shield } from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { appendEnvParam } from '$lib/stores/environment';
|
||||
import { watchJob } from '$lib/utils/sse-fetch';
|
||||
import ScanResultsView from '../../routes/images/ScanResultsView.svelte';
|
||||
|
||||
export interface ScanResult {
|
||||
@@ -148,31 +149,10 @@
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) throw new Error('No response body');
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6));
|
||||
handleScanProgress(data);
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const { jobId } = await response.json();
|
||||
await watchJob(jobId, (line) => {
|
||||
handleScanProgress(line.data as any);
|
||||
});
|
||||
|
||||
// If stream ended without complete status
|
||||
if (status === 'scanning') {
|
||||
|
||||
@@ -57,6 +57,10 @@ class SidebarState {
|
||||
? (this.openMobile = !this.openMobile)
|
||||
: this.setOpen(!this.open);
|
||||
};
|
||||
|
||||
destroy = () => {
|
||||
this.#isMobile.destroy();
|
||||
};
|
||||
}
|
||||
|
||||
const SYMBOL_KEY = "scn-sidebar";
|
||||
|
||||
Reference in new issue
Block a user