Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,64 @@
});
}

function scanAllImages() {
console.log("Scan All Images button clicked.");
const scanAllButton = document.getElementById('scan-all-button');
if (!scanAllButton) {
console.error("'scan-all-button' not found in the DOM!");
return;
}

let alpineButtonData = Alpine.$data(scanAllButton);
if (alpineButtonData) {
alpineButtonData.scanningAll = true;
} else {
console.warn("Could not retrieve Alpine data for 'scan-all-button'. Text update might fail. Ensure button has x-data.");
// As a fallback, you might try to set text directly, but this isn't reactive:
// scanAllButton.textContent = 'Scanning...';
}

const rows = document.querySelectorAll('tbody tr[data-image-id]');
let imagesQueuedByThisAction = 0;

rows.forEach(rowElement => {
const rowAlpineData = Alpine.$data(rowElement);
const imageIdFromDataset = rowElement.dataset.imageId; // Correct way to get imageId

if (rowAlpineData && imageIdFromDataset && rowAlpineData.hasImageId) {
const isAlreadyInGlobalQueue = scanQueue.some(item => item.imageId === imageIdFromDataset);
const isRowAlreadyProcessing = rowAlpineData.scanState === 'scanning' ||
rowAlpineData.scanState === 'queued' ||
rowAlpineData.scanState === 'linked';

// console.log(`Row ${rowAlpineData.rowLoopIndex}: imageId=${imageIdFromDataset}, scanState=${rowAlpineData.scanState}, isAlreadyInGlobalQueue=${isAlreadyInGlobalQueue}, isRowAlreadyProcessing=${isRowAlreadyProcessing}`);

if (!isAlreadyInGlobalQueue && !isRowAlreadyProcessing) {
console.log(`Scan All: Adding to queue - Image ID: ${imageIdFromDataset}, Row Index: ${rowAlpineData.rowLoopIndex}`);
addToScanQueue(imageIdFromDataset, rowAlpineData.rowLoopIndex); // imageIdFromDataset is now correct
imagesQueuedByThisAction++;
} else {
// console.log(`Scan All: Skipping image ${imageIdFromDataset} (row ${rowAlpineData.rowLoopIndex}), already processing or in queue.`);
}
} else {
let logMsg = `Scan All: Skipping row (ID: ${rowElement.id}). Reason: `;
if (!rowAlpineData) logMsg += "Missing Alpine data. ";
if (!imageIdFromDataset) logMsg += "Missing imageId in dataset. ";
if (rowAlpineData && !rowAlpineData.hasImageId) logMsg += "Alpine data 'hasImageId' is false. ";
console.log(logMsg);
}
});

if (imagesQueuedByThisAction === 0) {
console.log("Scan All: No new images were added to the scan queue. Resetting button state if applicable.");
if (alpineButtonData && alpineButtonData.scanningAll) {
alpineButtonData.scanningAll = false;
}
}
// Note: The 'scanningAll' state will also be reset to false by processScanQueue/addToScanQueue
// when the main queue is empty and no scans are in progress.
}

function addToScanQueue(imageId, clickedRowLoopIndex) {
if (!imageId) return;

Expand Down Expand Up @@ -108,6 +166,19 @@
scanQueue.push({ imageId: imageId, primaryRowLoopIndex: clickedRowLoopIndex });
syncImageRowsState(imageId, clickedRowLoopIndex, 'queued');
processScanQueue();

// Check if the main scan queue is empty and no scan is in progress, then reset scanAll button
// This is a simple way to reset the button. A more robust way would involve tracking scans initiated by "Scan All"
if (scanQueue.length === 0 && !isScanInProgress) {
const scanAllButton = document.getElementById('scan-all-button');
if (scanAllButton) {
let alpineButtonData = Alpine.$data(scanAllButton);
if (alpineButtonData && alpineButtonData.scanningAll) {
alpineButtonData.scanningAll = false;
console.log("Scan All button reset as queue is empty and no scan in progress.");
}
}
}
}

function generateRsdIconSvg(type, status, detail = '') {
Expand Down Expand Up @@ -245,13 +316,37 @@

isScanInProgress = false;
processScanQueue();

// Also reset scanAll button if an error occurs and the queue becomes empty
if (scanQueue.length === 0 && !isScanInProgress) {
const scanAllButton = document.getElementById('scan-all-button');
if (scanAllButton) {
let alpineButtonData = Alpine.$data(scanAllButton);
if (alpineButtonData && alpineButtonData.scanningAll) {
alpineButtonData.scanningAll = false;
console.log("Scan All button reset due to error and empty queue.");
}
}
}
})
.catch(error => {
console.error('Error scanning image:', imageIdToScan, error);
alert('Error scanning image ' + imageIdToScan + ': ' + error.message);
syncImageRowsState(imageIdToScan, primaryRowIdx, 'idle');
isScanInProgress = false;
processScanQueue();

// Also reset scanAll button if an error occurs and the queue becomes empty
if (scanQueue.length === 0 && !isScanInProgress) {
const scanAllButton = document.getElementById('scan-all-button');
if (scanAllButton) {
let alpineButtonData = Alpine.$data(scanAllButton);
if (alpineButtonData && alpineButtonData.scanningAll) {
alpineButtonData.scanningAll = false;
console.log("Scan All button reset due to error and empty queue.");
}
}
}
});
}
</script>
Expand Down
28 changes: 25 additions & 3 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,30 @@

{% block content %}
<div class="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6 mb-6">
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-100 mb-4">Running Docker Containers</h1>
<div class="overflow-x-auto">
<div class="flex justify-between items-center mb-4">
<h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-100">Running Docker Containers</h1>
<button
id="scan-all-button"
@click="scanAllImages()"
class="bg-green-500 hover:bg-green-700 dark:hover:bg-green-600 text-white font-bold py-2 px-4 rounded text-sm flex items-center justify-center"
x-data="{ scanningAll: false }"
:disabled="scanningAll"
:class="{ 'cursor-not-allowed opacity-50': scanningAll }">
<template x-if="scanningAll">
<div class="flex items-center">
<svg class="spinner h-4 w-4 animate-spin text-white mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Scanning All...</span>
</div>
</template>
<template x-if="!scanningAll">
<span>Scan All</span>
</template>
</button>
</div>
<div class="overflow-x-hidden">
<table class="min-w-full table-auto">
<thead class="bg-gray-200 dark:bg-gray-700">
<tr>
Expand Down Expand Up @@ -35,7 +57,7 @@ <h1 class="text-2xl font-semibold text-gray-800 dark:text-gray-100 mb-4">Running
@click="if (isScanned && detailsUrl && scanState !== 'scanning' && scanState !== 'queued' && scanState !== 'linked') window.location.href = detailsUrl"
:class="{ 'hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer': isScanned && scanState !== 'scanning' && scanState !== 'queued' && scanState !== 'linked', 'hover:bg-gray-50 dark:hover:bg-gray-700': !isScanned }">
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 align-middle">{{ container.name }}</td>
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 align-middle">{{ container.image_name }}</td>
<td class="px-3 py-2 text-sm text-gray-900 dark:text-gray-200 align-middle break-all max-w-lg">{{ container.image_name }}</td>
<td class="hidden md:table-cell px-3 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 align-middle">{{ container.image_id }}</td>
<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-200 align-middle">{{ container.status }}</td>
<td class="hidden md:table-cell px-2 py-2 text-sm text-gray-500 dark:text-gray-400 align-middle">{{ container.created_at.strftime('%Y-%m-%d %H:%M') if container.created_at else 'N/A' }}</td>
Expand Down