v3.1
This commit is contained in:
parent
63c93fa7e4
commit
b2d2b8b2f3
11 changed files with 258 additions and 171 deletions
|
|
@ -1,13 +1,13 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ServiceCategory, ServiceStatus } from '@/lib/data';
|
import { ServiceStatus } from '@/src/types/service';
|
||||||
|
|
||||||
interface FilterBarProps {
|
interface FilterBarProps {
|
||||||
categories: ServiceCategory[];
|
categories: string[];
|
||||||
statuses: ServiceStatus[];
|
statuses: ServiceStatus[];
|
||||||
selectedCategories: ServiceCategory[];
|
selectedCategories: string[];
|
||||||
selectedStatuses: ServiceStatus[];
|
selectedStatuses: ServiceStatus[];
|
||||||
onCategoryChange: (category: ServiceCategory) => void;
|
onCategoryChange: (category: string) => void;
|
||||||
onStatusChange: (status: ServiceStatus) => void;
|
onStatusChange: (status: ServiceStatus) => void;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Service } from '@/lib/data';
|
import { Service, ServiceCheckResult } from '@/src/types/service';
|
||||||
|
|
||||||
interface ServiceCardProps {
|
interface ServiceCardProps {
|
||||||
service: Service;
|
service: Service;
|
||||||
|
result?: ServiceCheckResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_CONFIG = {
|
const STATUS_CONFIG = {
|
||||||
|
|
@ -25,14 +26,22 @@ const STATUS_CONFIG = {
|
||||||
dotColor: 'bg-red-500',
|
dotColor: 'bg-red-500',
|
||||||
label: 'Offline',
|
label: 'Offline',
|
||||||
},
|
},
|
||||||
|
unknown: {
|
||||||
|
bgColor: 'bg-slate-50 dark:bg-slate-950',
|
||||||
|
textColor: 'text-slate-700 dark:text-slate-300',
|
||||||
|
dotColor: 'bg-slate-500',
|
||||||
|
label: 'Unbekannt',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceCard - Zeigt einen einzelnen Service mit Status, Icon und Öffnen-Button
|
* ServiceCard - Zeigt einen einzelnen Service mit Status, Icon und Öffnen-Button
|
||||||
* @param service - Das Service-Objekt mit Name, Beschreibung, Status, etc.
|
* @param service - Das Service-Objekt mit Name, Beschreibung, etc.
|
||||||
|
* @param result - Optionale Status-Check-Ergebnisse
|
||||||
*/
|
*/
|
||||||
export function ServiceCard({ service }: ServiceCardProps) {
|
export function ServiceCard({ service, result }: ServiceCardProps) {
|
||||||
const config = STATUS_CONFIG[service.status];
|
const status = result?.status || 'unknown';
|
||||||
|
const config = STATUS_CONFIG[status];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
|
@ -43,15 +52,9 @@ export function ServiceCard({ service }: ServiceCardProps) {
|
||||||
>
|
>
|
||||||
<div className="h-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden hover:border-slate-300 dark:hover:border-slate-600">
|
<div className="h-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden hover:border-slate-300 dark:hover:border-slate-600">
|
||||||
<div className="p-6 flex flex-col gap-4 h-full">
|
<div className="p-6 flex flex-col gap-4 h-full">
|
||||||
{/* Header mit Icon, Name, Kategorie und Status */}
|
{/* Header mit Name, Kategorie und Status */}
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1 flex items-center gap-3">
|
<div className="flex-1">
|
||||||
{service.icon && (
|
|
||||||
<span className="text-2xl flex-shrink-0" aria-hidden="true">
|
|
||||||
{service.icon}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors truncate">
|
<h3 className="text-lg font-semibold text-slate-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors truncate">
|
||||||
{service.name}
|
{service.name}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -59,14 +62,13 @@ export function ServiceCard({ service }: ServiceCardProps) {
|
||||||
{service.category}
|
{service.category}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status Badge */}
|
{/* Status Badge */}
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 px-3 py-1 rounded-full whitespace-nowrap flex-shrink-0 ${config.bgColor}`}
|
className={`flex items-center gap-2 px-3 py-1 rounded-full whitespace-nowrap flex-shrink-0 ${config.bgColor}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`w-2 h-2 rounded-full ${config.dotColor} animate-pulse`}
|
className={`w-2 h-2 rounded-full ${config.dotColor} ${status !== 'unknown' ? 'animate-pulse' : ''}`}
|
||||||
/>
|
/>
|
||||||
<span className={`text-xs font-medium ${config.textColor}`}>
|
<span className={`text-xs font-medium ${config.textColor}`}>
|
||||||
{config.label}
|
{config.label}
|
||||||
|
|
@ -74,6 +76,18 @@ export function ServiceCard({ service }: ServiceCardProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Status Details */}
|
||||||
|
{result && (
|
||||||
|
<div className="text-xs text-slate-500 dark:text-slate-400 space-y-1">
|
||||||
|
{result.httpStatus && (
|
||||||
|
<div>HTTP: {result.httpStatus}</div>
|
||||||
|
)}
|
||||||
|
{result.responseTimeMs !== null && (
|
||||||
|
<div>Zeit: {result.responseTimeMs}ms</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Beschreibung */}
|
{/* Beschreibung */}
|
||||||
<p className="text-sm text-slate-600 dark:text-slate-400 flex-grow line-clamp-2">
|
<p className="text-sm text-slate-600 dark:text-slate-400 flex-grow line-clamp-2">
|
||||||
{service.description}
|
{service.description}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Service } from '@/lib/data';
|
import { Service } from '@/src/types/service';
|
||||||
import { ServiceCard } from './ServiceCard';
|
import { ServiceCard } from './ServiceCard';
|
||||||
import { EmptyState } from './EmptyState';
|
import { EmptyState } from './EmptyState';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useState, useMemo } from 'react';
|
|
||||||
import { Service, ServiceCategory, ServiceStatus } from '@/lib/data';
|
|
||||||
import { FilterBar } from './FilterBar';
|
|
||||||
import { ServiceGrid } from './ServiceGrid';
|
|
||||||
|
|
||||||
interface ServiceSectionProps {
|
|
||||||
services: Service[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServiceSection - Verwaltet Filterlogik und rendert FilterBar + ServiceGrid
|
|
||||||
* @param services - Array aller verfügbaren Services
|
|
||||||
*/
|
|
||||||
export function ServiceSection({ services }: ServiceSectionProps) {
|
|
||||||
const [selectedCategories, setSelectedCategories] = useState<
|
|
||||||
ServiceCategory[]
|
|
||||||
>([]);
|
|
||||||
const [selectedStatuses, setSelectedStatuses] = useState<ServiceStatus[]>([]);
|
|
||||||
|
|
||||||
// Extrahiere eindeutige Kategorien und Status aus Services
|
|
||||||
const categories = useMemo(
|
|
||||||
() => Array.from(new Set(services.map((s) => s.category))).sort(),
|
|
||||||
[services]
|
|
||||||
);
|
|
||||||
|
|
||||||
const statuses = useMemo(
|
|
||||||
() =>
|
|
||||||
(['online', 'warning', 'offline'] as ServiceStatus[]).filter((status) =>
|
|
||||||
services.some((s) => s.status === status)
|
|
||||||
),
|
|
||||||
[services]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filtere Services basierend auf ausgewählten Filtern
|
|
||||||
const filteredServices = useMemo(() => {
|
|
||||||
return services.filter((service) => {
|
|
||||||
const categoryMatch =
|
|
||||||
selectedCategories.length === 0 ||
|
|
||||||
selectedCategories.includes(service.category);
|
|
||||||
|
|
||||||
const statusMatch =
|
|
||||||
selectedStatuses.length === 0 ||
|
|
||||||
selectedStatuses.includes(service.status);
|
|
||||||
|
|
||||||
return categoryMatch && statusMatch;
|
|
||||||
});
|
|
||||||
}, [services, selectedCategories, selectedStatuses]);
|
|
||||||
|
|
||||||
const handleCategoryChange = (category: ServiceCategory) => {
|
|
||||||
setSelectedCategories((prev) =>
|
|
||||||
prev.includes(category)
|
|
||||||
? prev.filter((c) => c !== category)
|
|
||||||
: [...prev, category]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleStatusChange = (status: ServiceStatus) => {
|
|
||||||
setSelectedStatuses((prev) =>
|
|
||||||
prev.includes(status) ? prev.filter((s) => s !== status) : [...prev, status]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
|
||||||
setSelectedCategories([]);
|
|
||||||
setSelectedStatuses([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FilterBar
|
|
||||||
categories={categories}
|
|
||||||
statuses={statuses}
|
|
||||||
selectedCategories={selectedCategories}
|
|
||||||
selectedStatuses={selectedStatuses}
|
|
||||||
onCategoryChange={handleCategoryChange}
|
|
||||||
onStatusChange={handleStatusChange}
|
|
||||||
onReset={handleReset}
|
|
||||||
/>
|
|
||||||
<ServiceGrid services={filteredServices} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
105
app/components/dashboard-client.tsx
Normal file
105
app/components/dashboard-client.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { ServiceCheckResult } from '@/src/types/service';
|
||||||
|
import { services } from '@/src/data/services';
|
||||||
|
import { ServiceCard } from './ServiceCard';
|
||||||
|
|
||||||
|
export function DashboardClient() {
|
||||||
|
const [results, setResults] = useState<ServiceCheckResult[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const fetchStatus = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
const response = await fetch('/api/status');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch status');
|
||||||
|
}
|
||||||
|
const data: ServiceCheckResult[] = await response.json();
|
||||||
|
setResults(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStatus();
|
||||||
|
|
||||||
|
// Auto-refresh every 30 seconds
|
||||||
|
const interval = setInterval(fetchStatus, 30000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getResultForService = (serviceId: string): ServiceCheckResult | undefined => {
|
||||||
|
return results.find(result => result.id === serviceId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-50 dark:bg-slate-900">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-gradient-to-r from-slate-900 to-slate-800 border-b border-slate-700 shadow-lg">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold text-white tracking-tight">
|
||||||
|
Homelab Dashboard
|
||||||
|
</h1>
|
||||||
|
<div className="text-sm text-slate-400">
|
||||||
|
{loading ? (
|
||||||
|
'Lade Status...'
|
||||||
|
) : (
|
||||||
|
'Automatische Aktualisierung: 30s'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-400 text-sm">
|
||||||
|
Live-Status aller verwalteten Dienste
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
{error && (
|
||||||
|
<div className="mb-8 p-4 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-lg">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 text-red-600 dark:text-red-400"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="text-red-800 dark:text-red-200">
|
||||||
|
Fehler beim Laden der Status-Daten: {error}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Services Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
{services.map((service) => (
|
||||||
|
<ServiceCard
|
||||||
|
key={service.id}
|
||||||
|
service={service}
|
||||||
|
result={getResultForService(service.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,6 @@
|
||||||
export { Header } from './Header';
|
export { Header } from './Header';
|
||||||
export { ServiceCard } from './ServiceCard';
|
export { ServiceCard } from './ServiceCard';
|
||||||
export { ServiceGrid } from './ServiceGrid';
|
export { ServiceGrid } from './ServiceGrid';
|
||||||
export { ServiceSection } from './ServiceSection';
|
|
||||||
export { FilterBar } from './FilterBar';
|
export { FilterBar } from './FilterBar';
|
||||||
export { EmptyState } from './EmptyState';
|
export { EmptyState } from './EmptyState';
|
||||||
|
export { DashboardClient } from './dashboard-client';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type { Metadata } from 'next';
|
|
||||||
import { Metadata as NextMetadata } from 'next';
|
import { Metadata as NextMetadata } from 'next';
|
||||||
|
|
||||||
export const metadata: NextMetadata = {
|
export const metadata: NextMetadata = {
|
||||||
|
|
|
||||||
15
app/page.tsx
15
app/page.tsx
|
|
@ -1,21 +1,12 @@
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Header, ServiceSection } from './components';
|
import { DashboardClient } from './components/dashboard-client';
|
||||||
import { services } from '@/lib/data';
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Homelab Dashboard',
|
title: 'Homelab Dashboard',
|
||||||
description: 'Verwaltungs-Dashboard für dein Homelab',
|
description: 'Live-Monitoring-Dashboard für dein Homelab',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return <DashboardClient />;
|
||||||
<div className="min-h-screen bg-slate-50 dark:bg-slate-900">
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
||||||
<ServiceSection services={services} />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
src/app/api/status/route.ts
Normal file
67
src/app/api/status/route.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { services } from '@/src/data/services';
|
||||||
|
import { ServiceCheckResult } from '@/src/types/service';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
async function checkService(service: typeof services[0]): Promise<ServiceCheckResult> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
||||||
|
|
||||||
|
const response = await fetch(service.monitorUrl, {
|
||||||
|
method: 'HEAD', // Use HEAD to minimize data transfer
|
||||||
|
cache: 'no-store',
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
let status: ServiceCheckResult['status'];
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
status = responseTime <= 2000 ? 'online' : 'warning';
|
||||||
|
} else if (response.status >= 400 && response.status < 500) {
|
||||||
|
status = 'warning';
|
||||||
|
} else {
|
||||||
|
status = 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: service.id,
|
||||||
|
status,
|
||||||
|
responseTimeMs: responseTime,
|
||||||
|
httpStatus: response.status,
|
||||||
|
checkedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: service.id,
|
||||||
|
status: 'offline',
|
||||||
|
responseTimeMs: responseTime,
|
||||||
|
httpStatus: null,
|
||||||
|
checkedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const results = await Promise.all(
|
||||||
|
services.map(service => checkService(service))
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(results);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking services:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to check services' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,4 @@
|
||||||
export type ServiceCategory = 'Infrastruktur' | 'Smart Home' | 'Medien' | 'Dokumente';
|
import { Service } from '@/src/types/service';
|
||||||
export type ServiceStatus = 'online' | 'warning' | 'offline';
|
|
||||||
|
|
||||||
export interface Service {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
category: ServiceCategory;
|
|
||||||
status: ServiceStatus;
|
|
||||||
url: string;
|
|
||||||
icon?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const services: Service[] = [
|
export const services: Service[] = [
|
||||||
// Infrastruktur
|
// Infrastruktur
|
||||||
|
|
@ -17,37 +6,33 @@ export const services: Service[] = [
|
||||||
id: 'unraid',
|
id: 'unraid',
|
||||||
name: 'Unraid',
|
name: 'Unraid',
|
||||||
description: 'NAS und Server Management Platform',
|
description: 'NAS und Server Management Platform',
|
||||||
category: 'Infrastruktur',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost',
|
url: 'http://localhost',
|
||||||
icon: '🖥️',
|
monitorUrl: 'http://localhost',
|
||||||
|
category: 'Infrastruktur',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'unifi',
|
id: 'unifi',
|
||||||
name: 'UniFi',
|
name: 'UniFi',
|
||||||
description: 'Netzwerk-Management und WLAN-Controller',
|
description: 'Netzwerk-Management und WLAN-Controller',
|
||||||
category: 'Infrastruktur',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:8443',
|
url: 'http://localhost:8443',
|
||||||
icon: '🌐',
|
monitorUrl: 'http://localhost:8443',
|
||||||
|
category: 'Infrastruktur',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'checkmk',
|
id: 'checkmk',
|
||||||
name: 'Checkmk',
|
name: 'Checkmk',
|
||||||
description: 'Monitoring und Alerting System',
|
description: 'Monitoring und Alerting System',
|
||||||
category: 'Infrastruktur',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost/checkmk',
|
url: 'http://localhost/checkmk',
|
||||||
icon: '📊',
|
monitorUrl: 'http://localhost/checkmk',
|
||||||
|
category: 'Infrastruktur',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'nginx-proxy',
|
id: 'nginx-proxy',
|
||||||
name: 'Nginx Proxy Manager',
|
name: 'Nginx Proxy Manager',
|
||||||
description: 'Reverse Proxy und SSL-Verwaltung',
|
description: 'Reverse Proxy und SSL-Verwaltung',
|
||||||
category: 'Infrastruktur',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:81',
|
url: 'http://localhost:81',
|
||||||
icon: '⚙️',
|
monitorUrl: 'http://localhost:81',
|
||||||
|
category: 'Infrastruktur',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Smart Home
|
// Smart Home
|
||||||
|
|
@ -55,10 +40,9 @@ export const services: Service[] = [
|
||||||
id: 'home-assistant',
|
id: 'home-assistant',
|
||||||
name: 'Home Assistant',
|
name: 'Home Assistant',
|
||||||
description: 'Smart Home Automation und Integration',
|
description: 'Smart Home Automation und Integration',
|
||||||
category: 'Smart Home',
|
|
||||||
status: 'offline',
|
|
||||||
url: 'http://localhost:8123',
|
url: 'http://localhost:8123',
|
||||||
icon: '🏠',
|
monitorUrl: 'http://localhost:8123',
|
||||||
|
category: 'Smart Home',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Medien
|
// Medien
|
||||||
|
|
@ -66,46 +50,41 @@ export const services: Service[] = [
|
||||||
id: 'plex',
|
id: 'plex',
|
||||||
name: 'Plex',
|
name: 'Plex',
|
||||||
description: 'Medienserver und Streaming-Dienst',
|
description: 'Medienserver und Streaming-Dienst',
|
||||||
category: 'Medien',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:32400',
|
url: 'http://localhost:32400',
|
||||||
icon: '🎬',
|
monitorUrl: 'http://localhost:32400',
|
||||||
|
category: 'Medien',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'jellyfin',
|
id: 'jellyfin',
|
||||||
name: 'Jellyfin',
|
name: 'Jellyfin',
|
||||||
description: 'Open-Source Medienserver',
|
description: 'Open-Source Medienserver',
|
||||||
category: 'Medien',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:8096',
|
url: 'http://localhost:8096',
|
||||||
icon: '📺',
|
monitorUrl: 'http://localhost:8096',
|
||||||
|
category: 'Medien',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'radarr',
|
id: 'radarr',
|
||||||
name: 'Radarr',
|
name: 'Radarr',
|
||||||
description: 'Automatisches Filmmanagement',
|
description: 'Automatisches Filmmanagement',
|
||||||
category: 'Medien',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:7878',
|
url: 'http://localhost:7878',
|
||||||
icon: '🎥',
|
monitorUrl: 'http://localhost:7878',
|
||||||
|
category: 'Medien',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sonarr',
|
id: 'sonarr',
|
||||||
name: 'Sonarr',
|
name: 'Sonarr',
|
||||||
description: 'Automatisches Serienmanagement',
|
description: 'Automatisches Serienmanagement',
|
||||||
category: 'Medien',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:8989',
|
url: 'http://localhost:8989',
|
||||||
icon: '📺',
|
monitorUrl: 'http://localhost:8989',
|
||||||
|
category: 'Medien',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'prowlarr',
|
id: 'prowlarr',
|
||||||
name: 'Prowlarr',
|
name: 'Prowlarr',
|
||||||
description: 'Indexer und Proxy Manager',
|
description: 'Indexer und Proxy Manager',
|
||||||
category: 'Medien',
|
|
||||||
status: 'warning',
|
|
||||||
url: 'http://localhost:9696',
|
url: 'http://localhost:9696',
|
||||||
icon: '🔍',
|
monitorUrl: 'http://localhost:9696',
|
||||||
|
category: 'Medien',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Dokumente
|
// Dokumente
|
||||||
|
|
@ -113,18 +92,16 @@ export const services: Service[] = [
|
||||||
id: 'seafile',
|
id: 'seafile',
|
||||||
name: 'Seafile',
|
name: 'Seafile',
|
||||||
description: 'Dateifreigabe und Cloud Storage',
|
description: 'Dateifreigabe und Cloud Storage',
|
||||||
category: 'Dokumente',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:8000',
|
url: 'http://localhost:8000',
|
||||||
icon: '☁️',
|
monitorUrl: 'http://localhost:8000',
|
||||||
|
category: 'Dokumente',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'forgejo',
|
id: 'forgejo',
|
||||||
name: 'Forgejo',
|
name: 'Forgejo',
|
||||||
description: 'Self-hosted Git und Code Repository',
|
description: 'Self-hosted Git und Code Repository',
|
||||||
category: 'Dokumente',
|
|
||||||
status: 'online',
|
|
||||||
url: 'http://localhost:3000',
|
url: 'http://localhost:3000',
|
||||||
icon: '💾',
|
monitorUrl: 'http://localhost:3000',
|
||||||
|
category: 'Dokumente',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
18
src/types/service.ts
Normal file
18
src/types/service.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
export type ServiceStatus = 'online' | 'warning' | 'offline' | 'unknown';
|
||||||
|
|
||||||
|
export interface Service {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
monitorUrl: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceCheckResult {
|
||||||
|
id: string;
|
||||||
|
status: ServiceStatus;
|
||||||
|
responseTimeMs: number | null;
|
||||||
|
httpStatus: number | null;
|
||||||
|
checkedAt: string;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue