Add service grouping feature to README and implement dynamic rendering in index.html

This commit is contained in:
MayaChat
2025-11-24 00:09:47 -05:00
parent 5ef15cc692
commit 67cb864342
4 changed files with 151 additions and 26 deletions

View File

@@ -16,8 +16,8 @@
</header>
<main>
<section class="grid" id="services-grid">
<!-- Services will be populated dynamically from /services.xml -->
<section id="services-container">
<!-- Service groups will be populated dynamically from /services.xml -->
</section>
<section class="notes">
@@ -35,7 +35,7 @@
<script>
// Fetch services.xml and render the service cards with logos.
(async function(){
const grid = document.getElementById('services-grid');
const container = document.getElementById('services-container');
const searchInput = document.getElementById('search-input');
const host = window.location.hostname;
let allServices = []; // Store all service elements for filtering
@@ -46,9 +46,61 @@
const text = await res.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'application/xml');
const services = Array.from(doc.getElementsByTagName('service'));
if(services.length===0){ grid.innerHTML = '<p class="notes">No services found in services.xml</p>'; return; }
services.forEach(s=>{
// Check if we have groups or just services
const groups = Array.from(doc.getElementsByTagName('group'));
const hasGroups = groups.length > 0;
if(hasGroups){
// Render grouped services
groups.forEach(group => {
const groupName = group.getAttribute('name') || 'Services';
const services = Array.from(group.getElementsByTagName('service'));
if(services.length === 0) return;
// Create group section
const groupSection = document.createElement('section');
groupSection.className = 'service-group';
const groupHeader = document.createElement('h2');
groupHeader.className = 'group-header';
groupHeader.textContent = groupName;
groupSection.appendChild(groupHeader);
const grid = document.createElement('div');
grid.className = 'grid';
services.forEach(s => {
const card = createServiceCard(s, host, allServices);
grid.appendChild(card);
});
groupSection.appendChild(grid);
container.appendChild(groupSection);
});
} else {
// Fallback: render ungrouped services
const services = Array.from(doc.getElementsByTagName('service'));
if(services.length === 0){
container.innerHTML = '<p class="notes">No services found in services.xml</p>';
return;
}
const grid = document.createElement('div');
grid.className = 'grid';
grid.id = 'services-grid';
services.forEach(s => {
const card = createServiceCard(s, host, allServices);
grid.appendChild(card);
});
container.appendChild(grid);
}
// Function to create a service card
function createServiceCard(s, host, allServices) {
const name = s.getAttribute('name') || s.getAttribute('id') || 'unknown';
const proto = s.getAttribute('proto') || 'http';
const port = s.getAttribute('port') || '';
@@ -170,14 +222,18 @@
a.appendChild(infoBtn);
}
grid.appendChild(a);
allServices.push(a);
});
return a;
}
// Search functionality
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase().trim();
let visibleCount = 0;
// Also hide/show group headers
const groupSections = container.querySelectorAll('.service-group');
allServices.forEach(card => {
const serviceName = card.dataset.serviceName;
if(serviceName.includes(searchTerm)){
@@ -187,14 +243,21 @@
card.style.display = 'none';
}
});
// Hide empty groups
groupSections.forEach(section => {
const visibleCards = section.querySelectorAll('.card:not([style*="display: none"])');
section.style.display = visibleCards.length > 0 ? '' : 'none';
});
// Show message if no results
const existingMsg = grid.querySelector('.no-results');
const existingMsg = container.querySelector('.no-results');
if(existingMsg) existingMsg.remove();
if(visibleCount === 0 && searchTerm !== ''){
const msg = document.createElement('p');
msg.className = 'notes no-results';
msg.textContent = `No services found matching "${e.target.value}"`;
grid.appendChild(msg);
container.appendChild(msg);
}
});