[Website] update data/page.tsx (#1969)

This commit is contained in:
Michel Roegl-Brunner 2025-02-03 10:38:45 +01:00 committed by GitHub
parent fa4ab5c8a3
commit 00f58d71d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,33 +1,9 @@
"use client"; "use client";
import ApplicationChart from "@/components/ApplicationChart"; import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button"; import DatePicker from 'react-datepicker';
import { Calendar } from "@/components/ui/calendar"; import 'react-datepicker/dist/react-datepicker.css';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import ApplicationChart from "../../components/ApplicationChart";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { format } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react";
import React, { useCallback, useEffect, useState } from "react";
interface DataModel { interface DataModel {
id: number; id: number;
@ -64,41 +40,23 @@ const DataFetcher: React.FC = () => {
const [reloadInterval, setReloadInterval] = useState<NodeJS.Timeout | null>(null); const [reloadInterval, setReloadInterval] = useState<NodeJS.Timeout | null>(null);
const fetchData = useCallback(async () => { useEffect(() => {
try { const fetchData = async () => {
const response = await fetch("https://api.htl-braunau.at/data/json"); try {
if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`); const response = await fetch("https://api.htl-braunau.at/data/json");
const result: DataModel[] = await response.json(); if (!response.ok) throw new Error("Failed to fetch data: ${response.statusText}");
setData(result); const result: DataModel[] = await response.json();
setLoading(false); setData(result);
} catch (err) { } catch (err) {
setError((err as Error).message); setError((err as Error).message);
setLoading(false); } finally {
} setLoading(false);
}
};
fetchData();
}, []); }, []);
useEffect(() => {
fetchData();
const storedInterval = localStorage.getItem('reloadInterval');
if (storedInterval) {
setIntervalTime(Number(storedInterval));
}
}, [fetchData]);
useEffect(() => {
let intervalId: NodeJS.Timeout | null = null;
if (interval > 0) {
intervalId = setInterval(fetchData, Math.max(interval, 10) * 1000);
localStorage.setItem('reloadInterval', interval.toString());
} else {
localStorage.removeItem('reloadInterval');
}
return () => {
if (intervalId) clearInterval(intervalId);
};
}, [interval, fetchData]);
const filteredData = data.filter(item => { const filteredData = data.filter(item => {
const matchesSearchQuery = Object.values(item).some(value => const matchesSearchQuery = Object.values(item).some(value =>
@ -153,194 +111,198 @@ const DataFetcher: React.FC = () => {
return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`; return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`;
}; };
const handleItemsPerPageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setItemsPerPage(Number(event.target.value));
setCurrentPage(1);
};
const paginatedData = sortedData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage); const paginatedData = sortedData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
const statusCounts = data.reduce((acc, item) => { useEffect(() => {
const status = item.status; const storedInterval = localStorage.getItem('reloadInterval');
acc[status] = (acc[status] || 0) + 1; if (storedInterval) {
return acc; setIntervalTime(Number(storedInterval));
}, {} as Record<string, number>); }
}, []);
if (loading) return <div className="flex justify-center items-center h-screen">Loading...</div>;
if (error) return <div className="flex justify-center items-center h-screen text-red-500">Error: {error}</div>; useEffect(() => {
if (interval <= 10) {
const newInterval = setInterval(() => {
window.location.reload();
}, 10000);
return () => clearInterval(newInterval);
} else {
const newInterval = setInterval(() => {
window.location.reload();
}, interval * 1000);
}
}, [interval]);
useEffect(() => {
if (interval > 0) {
localStorage.setItem('reloadInterval', interval.toString());
} else {
localStorage.removeItem('reloadInterval');
}
}, [interval]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
var installingCounts: number = 0;
var failedCounts: number = 0;
var doneCounts: number = 0
var unknownCounts: number = 0;
data.forEach((item) => {
if (item.status === "installing") {
installingCounts += 1;
} else if (item.status === "failed") {
failedCounts += 1;
}
else if (item.status === "done") {
doneCounts += 1;
}
else {
unknownCounts += 1;
}
});
return ( return (
<div className="container mx-auto p-6 pt-20 space-y-6"> <div className="p-6 mt-20">
<h1 className="text-3xl font-bold text-center">Created LXCs</h1> <h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
<div className="mb-4 flex space-x-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> <div>
<Card> <input
<CardHeader className="pb-2"> type="text"
<CardTitle className="text-sm font-medium">Search</CardTitle> placeholder="Search..."
</CardHeader> value={searchQuery}
<CardContent> onChange={e => setSearchQuery(e.target.value)}
<Input className="p-2 border"
placeholder="Search..." />
value={searchQuery} <label className="text-sm text-gray-600 mt-1 block">Search by keyword</label>
onChange={e => setSearchQuery(e.target.value)} </div>
/> <div>
</CardContent> <DatePicker
</Card> selected={startDate}
onChange={date => setStartDate(date)}
<Card> selectsStart
<CardHeader className="pb-2"> startDate={startDate}
<CardTitle className="text-sm font-medium">Start Date</CardTitle> endDate={endDate}
</CardHeader> placeholderText="Start date"
<CardContent> className="p-2 border"
<Popover> />
<PopoverTrigger asChild> <label className="text-sm text-gray-600 mt-1 block">Set a start date</label>
<Button variant="outline" className="w-full justify-start text-left font-normal">
<CalendarIcon className="mr-2 h-4 w-4" />
{startDate ? format(startDate, "PPP") : "Pick a date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={startDate || undefined}
onSelect={(date: Date | undefined) => setStartDate(date || null)}
initialFocus
/>
</PopoverContent>
</Popover>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">End Date</CardTitle>
</CardHeader>
<CardContent>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-start text-left font-normal">
<CalendarIcon className="mr-2 h-4 w-4" />
{endDate ? format(endDate, "PPP") : "Pick a date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={endDate || undefined}
onSelect={(date: Date | undefined) => setEndDate(date || null)}
initialFocus
/>
</PopoverContent>
</Popover>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium">Reload Interval</CardTitle>
</CardHeader>
<CardContent>
<Input
type="number"
value={interval}
onChange={e => setIntervalTime(Number(e.target.value))}
placeholder="Interval (seconds)"
/>
</CardContent>
</Card>
</div>
<ApplicationChart data={filteredData} />
<div className="flex justify-between items-center">
<p className="text-lg font-medium">{filteredData.length} results found</p>
<div className="flex gap-2 items-center">
<span>🔄 Installing: {statusCounts.installing || 0}</span>
<span> Completed: {statusCounts.done || 0}</span>
<span> Failed: {statusCounts.failed || 0}</span>
<span> Unknown: {statusCounts.unknown || 0}</span>
</div> </div>
<Select value={itemsPerPage.toString()} onValueChange={(value) => setItemsPerPage(Number(value))}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Items per page" />
</SelectTrigger>
<SelectContent>
{[25, 50, 100, 200].map(value => (
<SelectItem key={value} value={value.toString()}>
{value} items
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="rounded-md border"> <div>
<Table> <DatePicker
<TableHeader> selected={endDate}
<TableRow> onChange={date => setEndDate(date)}
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('status')}>Status</TableHead> selectsEnd
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('nsapp')}>Application</TableHead> startDate={startDate}
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_type')}>OS</TableHead> endDate={endDate}
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_version')}>OS Version</TableHead> placeholderText="End date"
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('disk_size')}>Disk Size</TableHead> className="p-2 border"
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('core_count')}>Core Count</TableHead> />
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ram_size')}>RAM Size</TableHead> <label className="text-sm text-gray-600 mt-1 block">Set a end date</label>
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('hn')}>Hostname</TableHead> </div>
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ssh')}>SSH</TableHead>
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('verbose')}>Verb</TableHead> <div className="mb-4 flex space-x-4">
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('tags')}>Tags</TableHead> <div>
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('method')}>Method</TableHead> <input
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('pve_version')}>PVE Version</TableHead> type="number"
<TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('created_at')}>Created At</TableHead> value={interval}
</TableRow> onChange={e => setIntervalTime(Number(e.target.value))}
</TableHeader> className="p-2 border"
<TableBody> placeholder="Interval (seconds)"
{paginatedData.map((item, index) => ( />
<TableRow key={index}> <label className="text-sm text-gray-600 mt-1 block">Set reload interval (0 for no reload)</label>
<TableCell className="px-4 py-2 border-b">{item.status === "done" ? ( </div>
"✔️" </div>
) : item.status === "failed" ? (
"❌"
) : item.status === "installing" ? (
"🔄"
) : (
item.status
)}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.nsapp}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.os_type}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.os_version}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.disk_size}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.core_count}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.ram_size}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.hn}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.ssh}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.verbose}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.tags.replace(/;/g, ' ')}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.method}</TableCell>
<TableCell className="px-4 py-2 border-b">{item.pve_version}</TableCell>
<TableCell className="px-4 py-2 border-b">{formatDate(item.created_at)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div> </div>
<ApplicationChart data={filteredData} />
<div className="flex items-center justify-center space-x-2"> <div className="mb-4 flex justify-between items-center">
<Button <p className="text-lg font-bold">{filteredData.length} results found</p>
variant="outline" <p className="text-lg font">Status Legend: 🔄 installing {installingCounts} | completetd {doneCounts} | failed {failedCounts} | unknown {unknownCounts}</p>
<select value={itemsPerPage} onChange={handleItemsPerPageChange} className="p-2 border">
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
<option value={200}>200</option>
</select>
</div>
<div className="overflow-x-auto">
<div className="overflow-y-auto lg:overflow-y-visible">
<table className="min-w-full table-auto border-collapse">
<thead>
<tr>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('status')}>Status</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('nsapp')}>Application</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_type')}>OS</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_version')}>OS Version</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('disk_size')}>Disk Size</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('core_count')}>Core Count</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ram_size')}>RAM Size</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('method')}>Method</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('pve_version')}>PVE Version</th>
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('created_at')}>Created At</th>
</tr>
</thead>
<tbody>
{paginatedData.map((item, index) => (
<tr key={index}>
<td className="px-4 py-2 border-b">
{item.status === "done" ? (
"✔️"
) : item.status === "failed" ? (
"❌"
) : item.status === "installing" ? (
"🔄"
) : (
item.status
)}
</td>
<td className="px-4 py-2 border-b">{item.nsapp}</td>
<td className="px-4 py-2 border-b">{item.os_type}</td>
<td className="px-4 py-2 border-b">{item.os_version}</td>
<td className="px-4 py-2 border-b">{item.disk_size}</td>
<td className="px-4 py-2 border-b">{item.core_count}</td>
<td className="px-4 py-2 border-b">{item.ram_size}</td>
<td className="px-4 py-2 border-b">{item.method}</td>
<td className="px-4 py-2 border-b">{item.pve_version}</td>
<td className="px-4 py-2 border-b">{formatDate(item.created_at)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="mt-4 flex justify-between items-center">
<button
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1} disabled={currentPage === 1}
className="p-2 border"
> >
Previous Previous
</Button> </button>
<span className="text-sm"> <span>Page {currentPage}</span>
Page {currentPage} of {Math.ceil(sortedData.length / itemsPerPage)} <button
</span>
<Button
variant="outline"
onClick={() => setCurrentPage(prev => (prev * itemsPerPage < sortedData.length ? prev + 1 : prev))} onClick={() => setCurrentPage(prev => (prev * itemsPerPage < sortedData.length ? prev + 1 : prev))}
disabled={currentPage * itemsPerPage >= sortedData.length} disabled={currentPage * itemsPerPage >= sortedData.length}
className="p-2 border"
> >
Next Next
</Button> </button>
</div> </div>
</div> </div>
); );
}; };
export default DataFetcher; export default DataFetcher;