From 50f592b2c0c28bd23cb9f34346d620a8087f9ad4 Mon Sep 17 00:00:00 2001 From: hdpriest Date: Thu, 4 Dec 2025 13:04:26 -0600 Subject: [PATCH] Updates to web viewer: package search now returns all package hits in a single table, prioritizing package name (i.e., search hits), and then the version of package, kernel, kernel version. Kernels now link to their relevant version'd kernel on the kernel view added sorting to table columns and resolved bug in language display --- README.md | 2 +- web/Dockerfile | 10 +- web/static/index.html | 262 +++++++++++++++++++++++++++++++++++------- 3 files changed, 227 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 9b59ce5..61896ba 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ These files should be located in the data directory or mounted as volumes. 1. **Build the Docker image:** ```sh - docker build -t icrn-kernel-webserver:latest -f web/Dockerfile . + docker build -t icrn-kernel-webserver:latest -f web/Dockerfile web/ ``` 2. **Run the container with kernel data:** diff --git a/web/Dockerfile b/web/Dockerfile index 6cc4baa..9e687d1 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -9,16 +9,16 @@ RUN apt-get update && \ WORKDIR /app # Copy requirements and install Python dependencies -COPY web/requirements.txt . +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application files -COPY web/kernel_service.py . -COPY web/nginx.conf /etc/nginx/nginx.conf -COPY web/start.sh /app/start.sh +COPY kernel_service.py . +COPY nginx.conf /etc/nginx/nginx.conf +COPY start.sh /app/start.sh # Copy static files -COPY web/static/ /app/static/ +COPY static/ /app/static/ # Create data directory and fix line endings RUN mkdir -p /app/data && \ diff --git a/web/static/index.html b/web/static/index.html index 876129c..c1f2dfe 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -386,6 +386,11 @@
Search Packages
let filteredPackages = []; let sortColumn = -1; let sortDirection = 1; // 1 for ascending, -1 for descending + let packageSearchRows = []; // Store package search results for sorting + let packageSearchSortColumn = -1; + let packageSearchSortDirection = 1; + let packageSearchTotalMatches = 0; // Store total package matches + let packageSearchQuery = ''; // Store search query const API_BASE = '/api'; // Initialize on page load @@ -768,64 +773,239 @@
Search Packages
No packages found matching "${escapeHtml(data.query)}" `; + packageSearchRows = []; + packageSearchTotalMatches = 0; + packageSearchQuery = data.query || ''; return; } - let html = ` -
- Found ${data.total_matches} package(s) matching "${escapeHtml(data.query)}" -
- `; + // Store total matches and query + packageSearchTotalMatches = data.total_matches || 0; + packageSearchQuery = data.query || ''; + // Flatten the data: create one row per package-kernel combination + packageSearchRows = []; data.packages.forEach(pkg => { - html += ` -
-
-
- ${escapeHtml(pkg.name)} -
- ${pkg.kernel_count} kernel(s) -
-
-
Available in the following kernels:
-
- - - - - - - - - - - `; - if (pkg.kernels && pkg.kernels.length > 0) { pkg.kernels.forEach(kernel => { - html += ` + packageSearchRows.push({ + packageName: pkg.name, + packageVersion: kernel.package_version || 'N/A', + kernelName: kernel.kernel_name || 'N/A', + kernelVersion: kernel.kernel_version || 'N/A', + language: kernel.kernel_language || kernel.language || 'N/A' + }); + }); + } else { + // If no kernels, still show the package + packageSearchRows.push({ + packageName: pkg.name, + packageVersion: 'N/A', + kernelName: 'N/A', + kernelVersion: 'N/A', + language: 'N/A' + }); + } + }); + + // Reset sort state + packageSearchSortColumn = -1; + packageSearchSortDirection = 1; + + // Render the table + renderPackageSearchTable(); + } + + // Render package search results table + function renderPackageSearchTable() { + const resultsDiv = document.getElementById('packageSearchResults'); + + if (packageSearchRows.length === 0) { + resultsDiv.innerHTML = '

No results to display

'; + return; + } + + let html = ` +
+ Found ${packageSearchTotalMatches} package(s) matching "${escapeHtml(packageSearchQuery)}" (${packageSearchRows.length} total result(s)) +
+
+
LanguageKernel NameVersionPackage Version
+ - - - - + + + + + - `; - }); + + + `; + + packageSearchRows.forEach((row, index) => { + // Create clickable kernel name link if kernel info is available + let kernelNameCell; + if (row.kernelName !== 'N/A' && row.kernelVersion !== 'N/A' && row.language !== 'N/A') { + kernelNameCell = `${escapeHtml(row.kernelName)}`; } else { - html += ''; + kernelNameCell = escapeHtml(row.kernelName); } html += ` - -
${escapeHtml(kernel.language || 'N/A')}${escapeHtml(kernel.kernel_name || 'N/A')}${escapeHtml(kernel.kernel_version || 'N/A')}${escapeHtml(kernel.package_version || 'N/A')} + Package Name + + + Package Version + + + Kernel Name + + + Kernel Version + + + Language + +
No kernel information available
-
-
-
+ + ${escapeHtml(row.packageName)} + ${escapeHtml(row.packageVersion)} + ${kernelNameCell} + ${escapeHtml(row.kernelVersion)} + ${escapeHtml(row.language)} + `; }); + html += ` + + + + `; + resultsDiv.innerHTML = html; + + // Update sort icons + for (let i = 0; i < 5; i++) { + const icon = document.getElementById(`packageSortIcon${i}`); + if (icon) { + if (packageSearchSortColumn === i) { + if (packageSearchSortDirection === 1) { + icon.className = 'bi bi-arrow-down sort-icon active'; + } else { + icon.className = 'bi bi-arrow-up sort-icon active'; + } + } else { + icon.className = 'bi bi-arrow-down-up sort-icon'; + } + } + } + + // Attach event listeners to kernel links + resultsDiv.querySelectorAll('.kernel-link').forEach(link => { + link.addEventListener('click', function(e) { + e.preventDefault(); + const language = this.getAttribute('data-language'); + const kernelName = this.getAttribute('data-kernel-name'); + const kernelVersion = this.getAttribute('data-kernel-version'); + navigateToKernel(language, kernelName, kernelVersion); + }); + }); + } + + // Sort package search table + function sortPackageSearchTable(columnIndex) { + // Reset all sort icons + for (let i = 0; i < 5; i++) { + const icon = document.getElementById(`packageSortIcon${i}`); + if (icon) { + icon.className = 'bi bi-arrow-down-up sort-icon'; + } + } + + // If clicking same column, reverse direction + if (packageSearchSortColumn === columnIndex) { + packageSearchSortDirection *= -1; + } else { + packageSearchSortColumn = columnIndex; + packageSearchSortDirection = 1; + } + + // Update sort icon + const icon = document.getElementById(`packageSortIcon${columnIndex}`); + if (icon) { + if (packageSearchSortDirection === 1) { + icon.className = 'bi bi-arrow-down sort-icon active'; + } else { + icon.className = 'bi bi-arrow-up sort-icon active'; + } + } + + // Sort rows + packageSearchRows.sort((a, b) => { + let aVal, bVal; + switch(columnIndex) { + case 0: // Package Name + aVal = a.packageName || ''; + bVal = b.packageName || ''; + break; + case 1: // Package Version + aVal = a.packageVersion || ''; + bVal = b.packageVersion || ''; + break; + case 2: // Kernel Name + aVal = a.kernelName || ''; + bVal = b.kernelName || ''; + break; + case 3: // Kernel Version + aVal = a.kernelVersion || ''; + bVal = b.kernelVersion || ''; + break; + case 4: // Language + aVal = a.language || ''; + bVal = b.language || ''; + break; + } + + if (aVal < bVal) return -1 * packageSearchSortDirection; + if (aVal > bVal) return 1 * packageSearchSortDirection; + return 0; + }); + + // Re-render the table + renderPackageSearchTable(); + } + + // Function to navigate to kernel view + async function navigateToKernel(language, kernelName, version) { + // Switch to kernels view + showView('kernels'); + + // Set the language if needed + const languageSelect = document.getElementById('languageSelect'); + if (languageSelect.value !== language) { + languageSelect.value = language; + currentLanguage = language; + // Load kernels for the new language + await loadKernels(language); + } + + // Find the kernel item in the list to highlight it + const kernelItems = document.querySelectorAll('.kernel-item'); + let kernelElement = null; + + kernelItems.forEach(item => { + const itemKernelName = item.getAttribute('data-kernel-name'); + const itemVersion = item.getAttribute('data-version'); + + if (itemKernelName === kernelName && itemVersion === version) { + kernelElement = item; + } + }); + + // Select the kernel (with or without element for highlighting) + await selectKernel(language, kernelName, version, kernelElement); }