104 lines
3.1 KiB
JavaScript
104 lines
3.1 KiB
JavaScript
import React, { useState, useEffect, useMemo } from 'react';
|
|
import './App.css';
|
|
import { getServices } from './api';
|
|
import ServiceCard from './components/ServiceCard';
|
|
import SearchBox from './components/SearchBox'; // Import SearchBox
|
|
import FilterBox from './components/FilterBox'; // Import FilterBox
|
|
|
|
function App() {
|
|
const [services, setServices] = useState([]);
|
|
const [searchTerm, setSearchTerm] = useState(''); // Add state for search term
|
|
const [selectedLabels, setSelectedLabels] = useState([]);
|
|
|
|
async function fetchServices() {
|
|
const fetchedServices = await getServices();
|
|
setServices(fetchedServices);
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchServices();
|
|
|
|
const ws = new WebSocket('ws://localhost:3001/api/events');
|
|
|
|
ws.onmessage = (event) => {
|
|
const message = JSON.parse(event.data);
|
|
if (message.type === 'container' && (message.action === 'start' || message.action === 'stop')) {
|
|
fetchServices();
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
// Optional: handle reconnection logic here
|
|
};
|
|
|
|
return () => {
|
|
ws.close();
|
|
};
|
|
}, []);
|
|
|
|
// Extract unique labels
|
|
const allLabels = useMemo(() => {
|
|
const labels = new Set();
|
|
services.forEach(service => {
|
|
for (const key in service.labels) {
|
|
labels.add(`${key}:${service.labels[key]}`);
|
|
}
|
|
});
|
|
return Array.from(labels).sort();
|
|
}, [services]);
|
|
|
|
// Handler for toggling labels
|
|
const handleLabelToggle = (labelToToggle) => {
|
|
setSelectedLabels(prevSelectedLabels => {
|
|
if (prevSelectedLabels.includes(labelToToggle)) {
|
|
return prevSelectedLabels.filter(label => label !== labelToToggle);
|
|
} else {
|
|
return [...prevSelectedLabels, labelToToggle];
|
|
}
|
|
});
|
|
};
|
|
|
|
// Filtering logic
|
|
const filteredServices = services.filter(service => {
|
|
const matchesSearchTerm = service.name.toLowerCase().includes(searchTerm.toLowerCase());
|
|
|
|
let matchesFilterTerm = true;
|
|
// Update filtering logic to use selectedLabels
|
|
if (selectedLabels.length > 0) {
|
|
matchesFilterTerm = selectedLabels.every(selectedLabel => {
|
|
const [key, value] = selectedLabel.split(':');
|
|
if (value) { // key:value format
|
|
return service.labels[key] === value;
|
|
} else { // key only format (e.g., "has:env" was replaced by just "env")
|
|
return Object.keys(service.labels).includes(key);
|
|
}
|
|
});
|
|
}
|
|
|
|
return matchesSearchTerm && matchesFilterTerm;
|
|
});
|
|
|
|
return (
|
|
<div className="App">
|
|
<header className="App-header">
|
|
<h1>Docker Service Display</h1>
|
|
<div className="flex space-x-2 w-fit">
|
|
<SearchBox searchTerm={searchTerm} onSearchChange={setSearchTerm} />
|
|
<FilterBox
|
|
availableLabels={allLabels}
|
|
selectedLabels={selectedLabels}
|
|
onLabelToggle={handleLabelToggle}
|
|
/>
|
|
</div>
|
|
</header>
|
|
<main className="service-grid">
|
|
{filteredServices.map(service => (
|
|
<ServiceCard key={service.id} service={service} />
|
|
))}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|