Skip to content

Commit 50f592b

Browse files
committed
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
1 parent 400c58b commit 50f592b

3 files changed

Lines changed: 227 additions & 47 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ These files should be located in the data directory or mounted as volumes.
130130

131131
1. **Build the Docker image:**
132132
```sh
133-
docker build -t icrn-kernel-webserver:latest -f web/Dockerfile .
133+
docker build -t icrn-kernel-webserver:latest -f web/Dockerfile web/
134134
```
135135

136136
2. **Run the container with kernel data:**

web/Dockerfile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ RUN apt-get update && \
99
WORKDIR /app
1010

1111
# Copy requirements and install Python dependencies
12-
COPY web/requirements.txt .
12+
COPY requirements.txt .
1313
RUN pip install --no-cache-dir -r requirements.txt
1414

1515
# Copy application files
16-
COPY web/kernel_service.py .
17-
COPY web/nginx.conf /etc/nginx/nginx.conf
18-
COPY web/start.sh /app/start.sh
16+
COPY kernel_service.py .
17+
COPY nginx.conf /etc/nginx/nginx.conf
18+
COPY start.sh /app/start.sh
1919

2020
# Copy static files
21-
COPY web/static/ /app/static/
21+
COPY static/ /app/static/
2222

2323
# Create data directory and fix line endings
2424
RUN mkdir -p /app/data && \

web/static/index.html

Lines changed: 221 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ <h5 class="mb-0">Search Packages</h5>
386386
let filteredPackages = [];
387387
let sortColumn = -1;
388388
let sortDirection = 1; // 1 for ascending, -1 for descending
389+
let packageSearchRows = []; // Store package search results for sorting
390+
let packageSearchSortColumn = -1;
391+
let packageSearchSortDirection = 1;
392+
let packageSearchTotalMatches = 0; // Store total package matches
393+
let packageSearchQuery = ''; // Store search query
389394
const API_BASE = '/api';
390395

391396
// Initialize on page load
@@ -768,64 +773,239 @@ <h5 class="mb-0">Search Packages</h5>
768773
<i class="bi bi-info-circle"></i> No packages found matching "${escapeHtml(data.query)}"
769774
</div>
770775
`;
776+
packageSearchRows = [];
777+
packageSearchTotalMatches = 0;
778+
packageSearchQuery = data.query || '';
771779
return;
772780
}
773781

774-
let html = `
775-
<div class="mb-3">
776-
<strong>Found ${data.total_matches} package(s) matching "${escapeHtml(data.query)}"</strong>
777-
</div>
778-
`;
782+
// Store total matches and query
783+
packageSearchTotalMatches = data.total_matches || 0;
784+
packageSearchQuery = data.query || '';
779785

786+
// Flatten the data: create one row per package-kernel combination
787+
packageSearchRows = [];
780788
data.packages.forEach(pkg => {
781-
html += `
782-
<div class="card mb-3">
783-
<div class="card-header d-flex justify-content-between align-items-center">
784-
<h6 class="mb-0">
785-
<i class="bi bi-box-seam"></i> ${escapeHtml(pkg.name)}
786-
</h6>
787-
<span class="badge bg-secondary">${pkg.kernel_count} kernel(s)</span>
788-
</div>
789-
<div class="card-body">
790-
<h6 class="mb-2">Available in the following kernels:</h6>
791-
<div class="table-responsive">
792-
<table class="table table-sm table-hover">
793-
<thead>
794-
<tr>
795-
<th>Language</th>
796-
<th>Kernel Name</th>
797-
<th>Version</th>
798-
<th>Package Version</th>
799-
</tr>
800-
</thead>
801-
<tbody>
802-
`;
803-
804789
if (pkg.kernels && pkg.kernels.length > 0) {
805790
pkg.kernels.forEach(kernel => {
806-
html += `
791+
packageSearchRows.push({
792+
packageName: pkg.name,
793+
packageVersion: kernel.package_version || 'N/A',
794+
kernelName: kernel.kernel_name || 'N/A',
795+
kernelVersion: kernel.kernel_version || 'N/A',
796+
language: kernel.kernel_language || kernel.language || 'N/A'
797+
});
798+
});
799+
} else {
800+
// If no kernels, still show the package
801+
packageSearchRows.push({
802+
packageName: pkg.name,
803+
packageVersion: 'N/A',
804+
kernelName: 'N/A',
805+
kernelVersion: 'N/A',
806+
language: 'N/A'
807+
});
808+
}
809+
});
810+
811+
// Reset sort state
812+
packageSearchSortColumn = -1;
813+
packageSearchSortDirection = 1;
814+
815+
// Render the table
816+
renderPackageSearchTable();
817+
}
818+
819+
// Render package search results table
820+
function renderPackageSearchTable() {
821+
const resultsDiv = document.getElementById('packageSearchResults');
822+
823+
if (packageSearchRows.length === 0) {
824+
resultsDiv.innerHTML = '<p class="text-muted text-center">No results to display</p>';
825+
return;
826+
}
827+
828+
let html = `
829+
<div class="mb-3">
830+
<strong>Found ${packageSearchTotalMatches} package(s) matching "${escapeHtml(packageSearchQuery)}" (${packageSearchRows.length} total result(s))</strong>
831+
</div>
832+
<div class="table-responsive">
833+
<table class="table table-striped table-hover" id="packageSearchTable">
834+
<thead>
807835
<tr>
808-
<td><span class="badge bg-info">${escapeHtml(kernel.language || 'N/A')}</span></td>
809-
<td>${escapeHtml(kernel.kernel_name || 'N/A')}</td>
810-
<td><span class="badge bg-secondary">${escapeHtml(kernel.kernel_version || 'N/A')}</span></td>
811-
<td>${escapeHtml(kernel.package_version || 'N/A')}</td>
836+
<th class="sortable" onclick="sortPackageSearchTable(0)">
837+
Package Name
838+
<i class="bi bi-arrow-down-up sort-icon" id="packageSortIcon0"></i>
839+
</th>
840+
<th class="sortable" onclick="sortPackageSearchTable(1)">
841+
Package Version
842+
<i class="bi bi-arrow-down-up sort-icon" id="packageSortIcon1"></i>
843+
</th>
844+
<th class="sortable" onclick="sortPackageSearchTable(2)">
845+
Kernel Name
846+
<i class="bi bi-arrow-down-up sort-icon" id="packageSortIcon2"></i>
847+
</th>
848+
<th class="sortable" onclick="sortPackageSearchTable(3)">
849+
Kernel Version
850+
<i class="bi bi-arrow-down-up sort-icon" id="packageSortIcon3"></i>
851+
</th>
852+
<th class="sortable" onclick="sortPackageSearchTable(4)">
853+
Language
854+
<i class="bi bi-arrow-down-up sort-icon" id="packageSortIcon4"></i>
855+
</th>
812856
</tr>
813-
`;
814-
});
857+
</thead>
858+
<tbody>
859+
`;
860+
861+
packageSearchRows.forEach((row, index) => {
862+
// Create clickable kernel name link if kernel info is available
863+
let kernelNameCell;
864+
if (row.kernelName !== 'N/A' && row.kernelVersion !== 'N/A' && row.language !== 'N/A') {
865+
kernelNameCell = `<a href="#" class="kernel-link" data-language="${escapeHtml(row.language)}" data-kernel-name="${escapeHtml(row.kernelName)}" data-kernel-version="${escapeHtml(row.kernelVersion)}" style="color: var(--illinois-blue); text-decoration: none; font-weight: 500; cursor: pointer;">${escapeHtml(row.kernelName)}</a>`;
815866
} else {
816-
html += '<tr><td colspan="4" class="text-muted text-center">No kernel information available</td></tr>';
867+
kernelNameCell = escapeHtml(row.kernelName);
817868
}
818869

819870
html += `
820-
</tbody>
821-
</table>
822-
</div>
823-
</div>
824-
</div>
871+
<tr>
872+
<td>${escapeHtml(row.packageName)}</td>
873+
<td>${escapeHtml(row.packageVersion)}</td>
874+
<td>${kernelNameCell}</td>
875+
<td>${escapeHtml(row.kernelVersion)}</td>
876+
<td><span class="badge bg-info">${escapeHtml(row.language)}</span></td>
877+
</tr>
825878
`;
826879
});
827880

881+
html += `
882+
</tbody>
883+
</table>
884+
</div>
885+
`;
886+
828887
resultsDiv.innerHTML = html;
888+
889+
// Update sort icons
890+
for (let i = 0; i < 5; i++) {
891+
const icon = document.getElementById(`packageSortIcon${i}`);
892+
if (icon) {
893+
if (packageSearchSortColumn === i) {
894+
if (packageSearchSortDirection === 1) {
895+
icon.className = 'bi bi-arrow-down sort-icon active';
896+
} else {
897+
icon.className = 'bi bi-arrow-up sort-icon active';
898+
}
899+
} else {
900+
icon.className = 'bi bi-arrow-down-up sort-icon';
901+
}
902+
}
903+
}
904+
905+
// Attach event listeners to kernel links
906+
resultsDiv.querySelectorAll('.kernel-link').forEach(link => {
907+
link.addEventListener('click', function(e) {
908+
e.preventDefault();
909+
const language = this.getAttribute('data-language');
910+
const kernelName = this.getAttribute('data-kernel-name');
911+
const kernelVersion = this.getAttribute('data-kernel-version');
912+
navigateToKernel(language, kernelName, kernelVersion);
913+
});
914+
});
915+
}
916+
917+
// Sort package search table
918+
function sortPackageSearchTable(columnIndex) {
919+
// Reset all sort icons
920+
for (let i = 0; i < 5; i++) {
921+
const icon = document.getElementById(`packageSortIcon${i}`);
922+
if (icon) {
923+
icon.className = 'bi bi-arrow-down-up sort-icon';
924+
}
925+
}
926+
927+
// If clicking same column, reverse direction
928+
if (packageSearchSortColumn === columnIndex) {
929+
packageSearchSortDirection *= -1;
930+
} else {
931+
packageSearchSortColumn = columnIndex;
932+
packageSearchSortDirection = 1;
933+
}
934+
935+
// Update sort icon
936+
const icon = document.getElementById(`packageSortIcon${columnIndex}`);
937+
if (icon) {
938+
if (packageSearchSortDirection === 1) {
939+
icon.className = 'bi bi-arrow-down sort-icon active';
940+
} else {
941+
icon.className = 'bi bi-arrow-up sort-icon active';
942+
}
943+
}
944+
945+
// Sort rows
946+
packageSearchRows.sort((a, b) => {
947+
let aVal, bVal;
948+
switch(columnIndex) {
949+
case 0: // Package Name
950+
aVal = a.packageName || '';
951+
bVal = b.packageName || '';
952+
break;
953+
case 1: // Package Version
954+
aVal = a.packageVersion || '';
955+
bVal = b.packageVersion || '';
956+
break;
957+
case 2: // Kernel Name
958+
aVal = a.kernelName || '';
959+
bVal = b.kernelName || '';
960+
break;
961+
case 3: // Kernel Version
962+
aVal = a.kernelVersion || '';
963+
bVal = b.kernelVersion || '';
964+
break;
965+
case 4: // Language
966+
aVal = a.language || '';
967+
bVal = b.language || '';
968+
break;
969+
}
970+
971+
if (aVal < bVal) return -1 * packageSearchSortDirection;
972+
if (aVal > bVal) return 1 * packageSearchSortDirection;
973+
return 0;
974+
});
975+
976+
// Re-render the table
977+
renderPackageSearchTable();
978+
}
979+
980+
// Function to navigate to kernel view
981+
async function navigateToKernel(language, kernelName, version) {
982+
// Switch to kernels view
983+
showView('kernels');
984+
985+
// Set the language if needed
986+
const languageSelect = document.getElementById('languageSelect');
987+
if (languageSelect.value !== language) {
988+
languageSelect.value = language;
989+
currentLanguage = language;
990+
// Load kernels for the new language
991+
await loadKernels(language);
992+
}
993+
994+
// Find the kernel item in the list to highlight it
995+
const kernelItems = document.querySelectorAll('.kernel-item');
996+
let kernelElement = null;
997+
998+
kernelItems.forEach(item => {
999+
const itemKernelName = item.getAttribute('data-kernel-name');
1000+
const itemVersion = item.getAttribute('data-version');
1001+
1002+
if (itemKernelName === kernelName && itemVersion === version) {
1003+
kernelElement = item;
1004+
}
1005+
});
1006+
1007+
// Select the kernel (with or without element for highlighting)
1008+
await selectKernel(language, kernelName, version, kernelElement);
8291009
}
8301010
</script>
8311011
</body>

0 commit comments

Comments
 (0)