add programminfo.tsx and program-info.json for
information and display of program details add simple logout-page
This commit is contained in:
58
dashboard/public/program-info.json
Normal file
58
dashboard/public/program-info.json
Normal 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."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ body {
|
|||||||
/* Layout-Container für Sidebar und Content */
|
/* Layout-Container für Sidebar und Content */
|
||||||
.layout-container {
|
.layout-container {
|
||||||
display: flex;
|
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 */
|
overflow: hidden; /* Verhindert, dass der Scrollbalken den gesamten Container betrifft */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@ body {
|
|||||||
color: var(--sidebar-fg);
|
color: var(--sidebar-fg);
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
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;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
|
||||||
BrowserRouter as Router,
|
|
||||||
Routes,
|
|
||||||
Route,
|
|
||||||
Link,
|
|
||||||
Outlet,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import logo from './assets/logo.png';
|
import logo from './assets/logo.png';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
@@ -21,6 +15,7 @@ import {
|
|||||||
MonitorDotIcon,
|
MonitorDotIcon,
|
||||||
LogOut,
|
LogOut,
|
||||||
Wrench,
|
Wrench,
|
||||||
|
Info,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { ToastProvider } from './components/ToastProvider';
|
import { ToastProvider } from './components/ToastProvider';
|
||||||
|
|
||||||
@@ -34,6 +29,7 @@ const sidebarItems = [
|
|||||||
{ name: 'Medien', path: '/medien', icon: Image },
|
{ name: 'Medien', path: '/medien', icon: Image },
|
||||||
{ name: 'Benutzer', path: '/benutzer', icon: User },
|
{ name: 'Benutzer', path: '/benutzer', icon: User },
|
||||||
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings },
|
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings },
|
||||||
|
{ name: 'Programminfo', path: '/programminfo', icon: Info },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Dummy Components (können in eigene Dateien ausgelagert werden)
|
// Dummy Components (können in eigene Dateien ausgelagert werden)
|
||||||
@@ -46,18 +42,28 @@ import Media from './media';
|
|||||||
import Benutzer from './benutzer';
|
import Benutzer from './benutzer';
|
||||||
import Einstellungen from './einstellungen';
|
import Einstellungen from './einstellungen';
|
||||||
import SetupMode from './SetupMode';
|
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)
|
// ENV aus .env holen (Platzhalter, im echten Projekt über process.env oder API)
|
||||||
// const ENV = import.meta.env.VITE_ENV || 'development';
|
// const ENV = import.meta.env.VITE_ENV || 'development';
|
||||||
|
|
||||||
const Layout: React.FC = () => {
|
const Layout: React.FC = () => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
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 (
|
return (
|
||||||
<div className="layout-container">
|
<div className="layout-container">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside
|
<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
|
<div
|
||||||
className="h-20 flex items-center justify-center border-b"
|
className="h-20 flex items-center justify-center border-b"
|
||||||
@@ -94,20 +100,19 @@ const Layout: React.FC = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
{/* Abmelden-Button immer ganz unten */}
|
{/* Abmelden-Button und Version immer ganz unten */}
|
||||||
<div className="mb-4 mt-auto">
|
<div className="mt-auto mb-2">
|
||||||
<button
|
<Link
|
||||||
|
to="/logout"
|
||||||
className="sidebar-logout flex items-center gap-3 px-6 py-3 w-full transition-colors no-underline"
|
className="sidebar-logout flex items-center gap-3 px-6 py-3 w-full transition-colors no-underline"
|
||||||
title={collapsed ? 'Abmelden' : undefined}
|
title={collapsed ? 'Abmelden' : undefined}
|
||||||
onClick={() => {
|
|
||||||
// Hier ggf. Logout-Logik einfügen
|
|
||||||
window.location.href = '/logout';
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<LogOut size={22} />
|
<LogOut size={22} />
|
||||||
{!collapsed && 'Abmelden'}
|
{!collapsed && 'Abmelden'}
|
||||||
</button>
|
</Link>
|
||||||
|
{!collapsed && version && (
|
||||||
|
<div className="px-6 pt-2 text-xs text-center opacity-70">Version {version}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
@@ -143,9 +148,7 @@ const Layout: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
|
|
||||||
// Automatische Navigation zu /clients bei leerer Beschreibung entfernt
|
// Automatische Navigation zu /clients bei leerer Beschreibung entfernt
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -161,7 +164,9 @@ const App: React.FC = () => {
|
|||||||
<Route path="einstellungen" element={<Einstellungen />} />
|
<Route path="einstellungen" element={<Einstellungen />} />
|
||||||
<Route path="clients" element={<Infoscreens />} />
|
<Route path="clients" element={<Infoscreens />} />
|
||||||
<Route path="setup" element={<SetupMode />} />
|
<Route path="setup" element={<SetupMode />} />
|
||||||
|
<Route path="programminfo" element={<Programminfo />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/logout" element={<Logout />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
12
dashboard/src/logout.tsx
Normal file
12
dashboard/src/logout.tsx
Normal 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;
|
||||||
168
dashboard/src/programminfo.tsx
Normal file
168
dashboard/src/programminfo.tsx
Normal 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;
|
||||||
Reference in New Issue
Block a user