add programminfo.tsx and program-info.json for

information and display of program details
add simple logout-page
This commit is contained in:
2025-08-30 16:00:59 +00:00
parent 270bad5980
commit 2ca5f0060e
5 changed files with 263 additions and 21 deletions

View File

@@ -0,0 +1,58 @@
{
"appName": "Infoscreen-Management",
"version": "2025.1.0-alpha.3",
"copyright": "© 2025 Third-Age-Applications",
"supportContact": "support@third-age-applications.com",
"description": "Eine zentrale Verwaltungsoberfläche für digitale Informationsbildschirme.",
"techStack": {
"frontend": "React, Vite, TypeScript, Tailwind CSS",
"backend": "Python (Flask), SQLAlchemy",
"database": "MariaDB",
"realtime": "Mosquitto (MQTT)",
"containerization": "Docker"
},
"openSourceComponents": {
"frontend": [
{ "name": "React", "license": "MIT" },
{ "name": "Vite", "license": "MIT" },
{ "name": "Tailwind CSS", "license": "MIT" },
{ "name": "Lucide Icons", "license": "ISC" },
{ "name": "Syncfusion UI Components", "license": "Kommerziell / Community" }
],
"backend": [
{ "name": "Flask", "license": "BSD" },
{ "name": "SQLAlchemy", "license": "MIT" },
{ "name": "Paho-MQTT", "license": "EPL/EDL" },
{ "name": "Alembic", "license": "MIT" }
]
},
"buildInfo": {
"buildDate": "2025-08-30T12:00:00Z",
"commitId": "a1b2c3d4e5f6"
},
"changelog": [
{
"version": "2025.1.0-alpha.3",
"date": "2025-08-30",
"changes": [
"NEU: Programminfo-Seite mit dynamischen Daten, Build-Infos und Changelog.",
"NEU: Logout-Funktionalität implementiert.",
"FIX: Breite der Sidebar im eingeklappten Zustand korrigiert."
]
},
{
"version": "2025.1.0-alpha.2",
"date": "2025-08-29",
"changes": [
"INFO: Analyse und Anzeige der verwendeten Open-Source-Bibliotheken."
]
},
{
"version": "2025.1.0-alpha.1",
"date": "2025-08-28",
"changes": [
"Initiales Setup des Projekts und der Grundstruktur."
]
}
]
}

View File

@@ -28,7 +28,7 @@ body {
/* Layout-Container für Sidebar und Content */
.layout-container {
display: flex;
min-height: 100vh;
height: 100vh; /* Feste Höhe auf die des Viewports setzen */
overflow: hidden; /* Verhindert, dass der Scrollbalken den gesamten Container betrifft */
}
@@ -38,7 +38,6 @@ body {
color: var(--sidebar-fg);
font-size: 1.15rem;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
width: 240px; /* Feste Breite für die Sidebar */
flex-shrink: 0;
overflow: hidden;
}

View File

@@ -1,11 +1,5 @@
import React, { useState } from 'react';
import {
BrowserRouter as Router,
Routes,
Route,
Link,
Outlet,
} from 'react-router-dom';
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
import logo from './assets/logo.png';
import './App.css';
@@ -21,6 +15,7 @@ import {
MonitorDotIcon,
LogOut,
Wrench,
Info,
} from 'lucide-react';
import { ToastProvider } from './components/ToastProvider';
@@ -34,6 +29,7 @@ const sidebarItems = [
{ name: 'Medien', path: '/medien', icon: Image },
{ name: 'Benutzer', path: '/benutzer', icon: User },
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings },
{ name: 'Programminfo', path: '/programminfo', icon: Info },
];
// Dummy Components (können in eigene Dateien ausgelagert werden)
@@ -46,18 +42,28 @@ import Media from './media';
import Benutzer from './benutzer';
import Einstellungen from './einstellungen';
import SetupMode from './SetupMode';
import Programminfo from './programminfo';
import Logout from './logout';
// ENV aus .env holen (Platzhalter, im echten Projekt über process.env oder API)
// const ENV = import.meta.env.VITE_ENV || 'development';
const Layout: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const [version, setVersion] = useState('');
React.useEffect(() => {
fetch('/program-info.json')
.then(res => res.json())
.then(data => setVersion(data.version))
.catch(err => console.error('Failed to load version info:', err));
}, []);
return (
<div className="layout-container">
{/* Sidebar */}
<aside
className={`sidebar-theme flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-30'}`}
className={`sidebar-theme flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-64'}`}
>
<div
className="h-20 flex items-center justify-center border-b"
@@ -94,20 +100,19 @@ const Layout: React.FC = () => {
);
})}
</nav>
{/* Abmelden-Button immer ganz unten */}
<div className="mb-4 mt-auto">
<button
{/* Abmelden-Button und Version immer ganz unten */}
<div className="mt-auto mb-2">
<Link
to="/logout"
className="sidebar-logout flex items-center gap-3 px-6 py-3 w-full transition-colors no-underline"
title={collapsed ? 'Abmelden' : undefined}
onClick={() => {
// Hier ggf. Logout-Logik einfügen
window.location.href = '/logout';
}}
type="button"
>
<LogOut size={22} />
{!collapsed && 'Abmelden'}
</button>
</Link>
{!collapsed && version && (
<div className="px-6 pt-2 text-xs text-center opacity-70">Version {version}</div>
)}
</div>
</aside>
{/* Main Content */}
@@ -143,9 +148,7 @@ const Layout: React.FC = () => {
);
};
const App: React.FC = () => {
// Automatische Navigation zu /clients bei leerer Beschreibung entfernt
return (
@@ -161,7 +164,9 @@ const App: React.FC = () => {
<Route path="einstellungen" element={<Einstellungen />} />
<Route path="clients" element={<Infoscreens />} />
<Route path="setup" element={<SetupMode />} />
<Route path="programminfo" element={<Programminfo />} />
</Route>
<Route path="/logout" element={<Logout />} />
</Routes>
</ToastProvider>
);

12
dashboard/src/logout.tsx Normal file
View File

@@ -0,0 +1,12 @@
import React from 'react';
const Logout: React.FC = () => (
<div className="flex items-center justify-center h-screen">
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">Abmeldung</h2>
<p>Sie haben sich erfolgreich abgemeldet.</p>
</div>
</div>
);
export default Logout;

View File

@@ -0,0 +1,168 @@
import React, { useState, useEffect } from 'react';
interface ProgramInfo {
appName: string;
version: string;
copyright: string;
supportContact: string;
description: string;
techStack: {
[key: string]: string;
};
openSourceComponents: {
frontend: { name: string; license: string }[];
backend: { name: string; license: string }[];
};
buildInfo: {
buildDate: string;
commitId: string;
};
changelog: {
version: string;
date: string;
changes: string[];
}[];
}
const Programminfo: React.FC = () => {
const [info, setInfo] = useState<ProgramInfo | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch('/program-info.json')
.then(response => {
if (!response.ok) {
throw new Error('Netzwerk-Antwort war nicht ok');
}
return response.json();
})
.then(data => setInfo(data))
.catch(error => {
console.error('Fehler beim Laden der Programminformationen:', error);
setError('Informationen konnten nicht geladen werden.');
});
}, []);
if (error) {
return (
<div>
<h2 className="text-xl font-bold mb-4 text-red-600">Fehler</h2>
<p>{error}</p>
</div>
);
}
if (!info) {
return (
<div>
<h2 className="text-xl font-bold mb-4">Programminfo</h2>
<p>Lade Informationen...</p>
</div>
);
}
return (
<div className="space-y-8">
<div>
<h2 className="text-2xl font-bold mb-2">{info.appName}</h2>
<p className="text-gray-600">{info.description}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Allgemeine Infos & Build */}
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-xl font-semibold mb-4 border-b pb-2">Allgemein</h3>
<div className="space-y-3">
<p>
<strong>Version:</strong> {info.version}
</p>
<p>
<strong>Copyright:</strong> {info.copyright}
</p>
<p>
<strong>Support:</strong>{' '}
<a href={`mailto:${info.supportContact}`} className="text-blue-600 hover:underline">
{info.supportContact}
</a>
</p>
<hr className="my-4" />
<h4 className="font-semibold">Build-Informationen</h4>
<p>
<strong>Build-Datum:</strong>{' '}
{new Date(info.buildInfo.buildDate).toLocaleString('de-DE')}
</p>
<p>
<strong>Commit-ID:</strong>{' '}
<span className="font-mono text-sm bg-gray-100 p-1 rounded">
{info.buildInfo.commitId}
</span>
</p>
</div>
</div>
{/* Technischer Stack */}
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-xl font-semibold mb-4 border-b pb-2">Technologie-Stack</h3>
<ul className="list-disc list-inside space-y-2">
{Object.entries(info.techStack).map(([key, value]) => (
<li key={key}>
<span className="font-semibold capitalize">{key}:</span> {value}
</li>
))}
</ul>
</div>
</div>
{/* Changelog */}
<div>
<h3 className="text-xl font-semibold mb-4">Änderungsprotokoll (Changelog)</h3>
<div className="space-y-6">
{info.changelog.map(log => (
<div key={log.version} className="bg-white p-6 rounded-lg shadow">
<h4 className="font-bold text-lg mb-2">
Version {log.version}{' '}
<span className="text-sm font-normal text-gray-500">
- {new Date(log.date).toLocaleDateString('de-DE')}
</span>
</h4>
<ul className="list-disc list-inside space-y-1 text-gray-700">
{log.changes.map((change, index) => (
<li key={index}>{change}</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Open Source Komponenten */}
<div>
<h3 className="text-xl font-semibold mb-4">Verwendete Open-Source-Komponenten</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="bg-white p-6 rounded-lg shadow">
<h4 className="font-bold mb-3">Frontend</h4>
<ul className="list-disc list-inside space-y-1">
{info.openSourceComponents.frontend.map(item => (
<li key={item.name}>
{item.name} ({item.license}-Lizenz)
</li>
))}
</ul>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h4 className="font-bold mb-3">Backend</h4>
<ul className="list-disc list-inside space-y-1">
{info.openSourceComponents.backend.map(item => (
<li key={item.name}>
{item.name} ({item.license}-Lizenz)
</li>
))}
</ul>
</div>
</div>
</div>
</div>
);
};
export default Programminfo;