infoscreen-overview in cards
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -73,3 +73,4 @@ dashboard/pages/test.py
|
||||
dashboard/sidebar_test.py
|
||||
dashboard/assets/responsive-sidebar.css
|
||||
certs/
|
||||
sync.ffs_db
|
||||
|
||||
97
dashboard/package-lock.json
generated
97
dashboard/package-lock.json
generated
@@ -13,8 +13,10 @@
|
||||
"@syncfusion/ej2-react-grids": "^29.2.11",
|
||||
"@syncfusion/ej2-react-schedule": "^29.2.10",
|
||||
"cldr-data": "^36.0.4",
|
||||
"lucide-react": "^0.522.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
@@ -23,6 +25,7 @@
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
@@ -1985,6 +1988,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/history": {
|
||||
"version": "4.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@@ -2012,6 +2022,29 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router": {
|
||||
"version": "5.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router-dom": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
@@ -3038,6 +3071,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
|
||||
@@ -5357,6 +5399,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.522.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.522.0.tgz",
|
||||
"integrity": "sha512-jnJbw974yZ7rQHHEFKJOlWAefG3ATSCZHANZxIdx8Rk/16siuwjgA4fBULpXEAWx/RlTs3FzmKW/udWUuO0aRw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -6267,6 +6318,44 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.2.tgz",
|
||||
"integrity": "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.2.tgz",
|
||||
"integrity": "sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -6521,6 +6610,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
"@syncfusion/ej2-react-grids": "^29.2.11",
|
||||
"@syncfusion/ej2-react-schedule": "^29.2.10",
|
||||
"cldr-data": "^36.0.4",
|
||||
"lucide-react": "^0.522.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
@@ -25,6 +27,7 @@
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
|
||||
@@ -1,113 +1,156 @@
|
||||
// import 'react-app-polyfill/ie11'; // optional, falls benötigt
|
||||
import React, { useState } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
|
||||
import logo from './assets/logo.png';
|
||||
import './App.css';
|
||||
import React from 'react';
|
||||
|
||||
// Lucide Icons importieren
|
||||
import {
|
||||
ScheduleComponent,
|
||||
Day,
|
||||
Week,
|
||||
WorkWeek,
|
||||
Month,
|
||||
Agenda,
|
||||
TimelineViews,
|
||||
TimelineMonth,
|
||||
Inject,
|
||||
ViewsDirective,
|
||||
ViewDirective,
|
||||
ResourcesDirective,
|
||||
ResourceDirective,
|
||||
} from '@syncfusion/ej2-react-schedule';
|
||||
import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base';
|
||||
import * as de from 'cldr-data/main/de/ca-gregorian.json';
|
||||
import * as numbers from 'cldr-data/main/de/numbers.json';
|
||||
import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
|
||||
import * as numberingSystems from 'cldr-data/supplemental/numberingSystems.json';
|
||||
LayoutDashboard,
|
||||
Calendar,
|
||||
Boxes,
|
||||
Users,
|
||||
UserSquare,
|
||||
Image,
|
||||
User,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
|
||||
// CLDR-Daten laden
|
||||
loadCldr(
|
||||
(de as unknown as { default: object }).default,
|
||||
(numbers as unknown as { default: object }).default,
|
||||
(timeZoneNames as unknown as { default: object }).default,
|
||||
(numberingSystems as unknown as { default: object }).default
|
||||
);
|
||||
|
||||
// Deutsche Lokalisierung für den Scheduler
|
||||
L10n.load({
|
||||
de: {
|
||||
schedule: {
|
||||
day: 'Tag',
|
||||
week: 'Woche',
|
||||
workWeek: 'Arbeitswoche',
|
||||
month: 'Monat',
|
||||
agenda: 'Agenda',
|
||||
today: 'Heute',
|
||||
noEvents: 'Keine Termine',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Kultur setzen
|
||||
setCulture('de');
|
||||
|
||||
// Ressourcen-Daten
|
||||
const resources = [
|
||||
{ text: 'Raum A', id: 1, color: '#1aaa55' },
|
||||
{ text: 'Raum B', id: 2, color: '#357cd2' },
|
||||
{ text: 'Raum C', id: 3, color: '#7fa900' },
|
||||
const sidebarItems = [
|
||||
{ name: 'Dashboard', path: '/', icon: LayoutDashboard },
|
||||
{ name: 'Termine', path: '/termine', icon: Calendar },
|
||||
{ name: 'Ressourcen', path: '/ressourcen', icon: Boxes },
|
||||
{ name: 'Infoscreens', path: '/Infoscreens', icon: Users },
|
||||
{ name: 'Gruppen', path: '/gruppen', icon: UserSquare },
|
||||
{ name: 'Medien', path: '/medien', icon: Image },
|
||||
{ name: 'Benutzer', path: '/benutzer', icon: User },
|
||||
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings },
|
||||
];
|
||||
|
||||
// Dummy-Termine generieren
|
||||
const now = new Date();
|
||||
const appointments = Array.from({ length: 10 }).map((_, i) => {
|
||||
const dayOffset = Math.floor(i * 1.4); // verteilt auf 14 Tage
|
||||
const start = new Date(now);
|
||||
start.setDate(now.getDate() + dayOffset);
|
||||
start.setHours(9 + (i % 4), 0, 0, 0);
|
||||
const end = new Date(start);
|
||||
end.setHours(start.getHours() + 1);
|
||||
const Layout: React.FC = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return {
|
||||
Id: i + 1,
|
||||
Subject: `Termin ${i + 1}`,
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
ResourceId: (i % 3) + 1,
|
||||
Location: resources[i % 3].text,
|
||||
};
|
||||
});
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<div className="p-8 bg-gray-100 min-h-screen">
|
||||
<h1 className="text-2xl font-bold mb-4">Infoscreen Kalendersteuerung</h1>
|
||||
<ScheduleComponent
|
||||
height="650px"
|
||||
locale="de"
|
||||
currentView="TimelineWeek"
|
||||
eventSettings={{ dataSource: appointments }}
|
||||
group={{ resources: ['Räume'] }}
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-30'}`}
|
||||
style={{
|
||||
backgroundColor: '#e5d8c7',
|
||||
color: '#78591c',
|
||||
fontSize: '1.15rem',
|
||||
fontFamily:
|
||||
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||
}}
|
||||
>
|
||||
<ViewsDirective>
|
||||
<ViewDirective option="Week" />
|
||||
<ViewDirective option="TimelineWeek" />
|
||||
<ViewDirective option="Month" />
|
||||
<ViewDirective option="TimelineMonth" />
|
||||
</ViewsDirective>
|
||||
<ResourcesDirective>
|
||||
<ResourceDirective
|
||||
field="ResourceId"
|
||||
title="Räume"
|
||||
name="Räume"
|
||||
allowMultiple={false}
|
||||
dataSource={resources}
|
||||
textField="text"
|
||||
idField="id"
|
||||
colorField="color"
|
||||
<div
|
||||
className="h-20 flex items-center justify-center border-b"
|
||||
style={{ borderColor: '#d6c3a6' }}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12"
|
||||
style={{ display: collapsed ? 'none' : 'block' }}
|
||||
/>
|
||||
</ResourcesDirective>
|
||||
<Inject services={[Day, Week, WorkWeek, Month, Agenda, TimelineViews, TimelineMonth]} />
|
||||
</ScheduleComponent>
|
||||
</div>
|
||||
<button
|
||||
className="p-2 focus:outline-none hover:bg-[#d6c3a6] transition-colors"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
aria-label={collapsed ? 'Sidebar ausklappen' : 'Sidebar einklappen'}
|
||||
>
|
||||
<span style={{ fontSize: 20 }}>{collapsed ? '▶' : '◀'}</span>
|
||||
</button>
|
||||
<nav className="flex-1 mt-4">
|
||||
{sidebarItems.map(item => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className="flex items-center gap-3 px-6 py-3 transition-colors no-underline"
|
||||
style={{
|
||||
color: '#78591c',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
title={collapsed ? item.name : undefined}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.backgroundColor = '#78591c';
|
||||
e.currentTarget.style.color = '#e5d8c7';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.backgroundColor = '';
|
||||
e.currentTarget.style.color = '#78591c';
|
||||
}}
|
||||
>
|
||||
<Icon size={22} />
|
||||
{!collapsed && item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Header */}
|
||||
<header
|
||||
className="flex items-center px-8 shadow"
|
||||
style={{
|
||||
backgroundColor: '#e5d8c7',
|
||||
color: '#78591c',
|
||||
height: 'calc(48px + 20px)',
|
||||
fontSize: '1.15rem',
|
||||
fontFamily:
|
||||
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12 mr-4"
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
/>
|
||||
<span className="text-2xl font-bold mr-8">Infoscreen-Management</span>
|
||||
<span className="ml-auto" style={{ color: '#78591c' }}>
|
||||
[Organisationsname]
|
||||
</span>
|
||||
</header>
|
||||
<main className="flex-1 p-8 bg-gray-100">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="termine" element={<Appointments />} />
|
||||
<Route path="ressourcen" element={<Ressourcen />} />
|
||||
<Route path="Infoscreens" element={<Infoscreens />} />
|
||||
<Route path="gruppen" element={<Gruppen />} />
|
||||
<Route path="medien" element={<Medien />} />
|
||||
<Route path="benutzer" element={<Benutzer />} />
|
||||
<Route path="einstellungen" element={<Einstellungen />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
// Dummy Components (können in eigene Dateien ausgelagert werden)
|
||||
import Dashboard from './dashboard';
|
||||
import Appointments from './appointments';
|
||||
import Ressourcen from './ressourcen';
|
||||
import Infoscreens from './clients';
|
||||
import Gruppen from './gruppen';
|
||||
import Medien from './medien';
|
||||
import Benutzer from './benutzer';
|
||||
import Einstellungen from './einstellungen';
|
||||
|
||||
17
dashboard/src/apiClients.ts
Normal file
17
dashboard/src/apiClients.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Funktion zum Laden der Clients von der API
|
||||
|
||||
export interface Client {
|
||||
uuid: string;
|
||||
location: string;
|
||||
hardware_hash: string;
|
||||
ip_address: string;
|
||||
last_alive: string | null;
|
||||
}
|
||||
|
||||
export async function fetchClients(): Promise<Client[]> {
|
||||
const response = await fetch('/api/clients');
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Clients');
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
106
dashboard/src/appointments.tsx
Normal file
106
dashboard/src/appointments.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ScheduleComponent,
|
||||
Day,
|
||||
Week,
|
||||
WorkWeek,
|
||||
Month,
|
||||
Agenda,
|
||||
TimelineViews,
|
||||
TimelineMonth,
|
||||
Inject,
|
||||
ViewsDirective,
|
||||
ViewDirective,
|
||||
ResourcesDirective,
|
||||
ResourceDirective,
|
||||
} from '@syncfusion/ej2-react-schedule';
|
||||
import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base';
|
||||
import * as de from 'cldr-data/main/de/ca-gregorian.json';
|
||||
import * as numbers from 'cldr-data/main/de/numbers.json';
|
||||
import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
|
||||
import * as numberingSystems from 'cldr-data/supplemental/numberingSystems.json';
|
||||
|
||||
// CLDR-Daten laden
|
||||
loadCldr(
|
||||
(de as unknown as { default: object }).default,
|
||||
(numbers as unknown as { default: object }).default,
|
||||
(timeZoneNames as unknown as { default: object }).default,
|
||||
(numberingSystems as unknown as { default: object }).default
|
||||
);
|
||||
|
||||
// Deutsche Lokalisierung für den Scheduler
|
||||
L10n.load({
|
||||
de: {
|
||||
schedule: {
|
||||
day: 'Tag',
|
||||
week: 'Woche',
|
||||
workWeek: 'Arbeitswoche',
|
||||
month: 'Monat',
|
||||
agenda: 'Agenda',
|
||||
today: 'Heute',
|
||||
noEvents: 'Keine Termine',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Kultur setzen
|
||||
setCulture('de');
|
||||
|
||||
// Ressourcen-Daten
|
||||
const resources = [
|
||||
{ text: 'Raum A', id: 1, color: '#1aaa55' },
|
||||
{ text: 'Raum B', id: 2, color: '#357cd2' },
|
||||
{ text: 'Raum C', id: 3, color: '#7fa900' },
|
||||
];
|
||||
|
||||
// Dummy-Termine generieren
|
||||
const now = new Date();
|
||||
const appointments = Array.from({ length: 10 }).map((_, i) => {
|
||||
const dayOffset = Math.floor(i * 1.4); // verteilt auf 14 Tage
|
||||
const start = new Date(now);
|
||||
start.setDate(now.getDate() + dayOffset);
|
||||
start.setHours(9 + (i % 4), 0, 0, 0);
|
||||
const end = new Date(start);
|
||||
end.setHours(start.getHours() + 1);
|
||||
|
||||
return {
|
||||
Id: i + 1,
|
||||
Subject: `Termin ${i + 1}`,
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
ResourceId: (i % 3) + 1,
|
||||
Location: resources[i % 3].text,
|
||||
};
|
||||
});
|
||||
|
||||
const Appointments: React.FC = () => (
|
||||
<ScheduleComponent
|
||||
height="650px"
|
||||
locale="de"
|
||||
currentView="TimelineWeek"
|
||||
eventSettings={{ dataSource: appointments }}
|
||||
group={{ resources: ['Räume'] }}
|
||||
>
|
||||
<ViewsDirective>
|
||||
<ViewDirective option="Week" />
|
||||
<ViewDirective option="TimelineWeek" />
|
||||
<ViewDirective option="Month" />
|
||||
<ViewDirective option="TimelineMonth" />
|
||||
</ViewsDirective>
|
||||
<ResourcesDirective>
|
||||
<ResourceDirective
|
||||
field="ResourceId"
|
||||
title="Räume"
|
||||
name="Räume"
|
||||
allowMultiple={false}
|
||||
dataSource={resources}
|
||||
textField="text"
|
||||
idField="id"
|
||||
colorField="color"
|
||||
/>
|
||||
</ResourcesDirective>
|
||||
<Inject services={[Day, Week, WorkWeek, Month, Agenda, TimelineViews, TimelineMonth]} />
|
||||
</ScheduleComponent>
|
||||
);
|
||||
|
||||
export default Appointments;
|
||||
BIN
dashboard/src/assets/TAA_Logo.png
Normal file
BIN
dashboard/src/assets/TAA_Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
dashboard/src/assets/logo.png
Normal file
BIN
dashboard/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
8
dashboard/src/benutzer.tsx
Normal file
8
dashboard/src/benutzer.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Benutzer: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Benutzer</h2>
|
||||
<p>Willkommen im Infoscreen-Management Benutzer.</p>
|
||||
</div>
|
||||
);
|
||||
export default Benutzer;
|
||||
8
dashboard/src/clients.tsx
Normal file
8
dashboard/src/clients.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Infoscreens: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Infoscreens</h2>
|
||||
<p>Willkommen im Infoscreen-Management Infoscreens.</p>
|
||||
</div>
|
||||
);
|
||||
export default Infoscreens;
|
||||
46
dashboard/src/dashboard.tsx
Normal file
46
dashboard/src/dashboard.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchClients } from './apiClients';
|
||||
import type { Client } from './apiClients';
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const [clients, setClients] = useState<Client[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchClients().then(setClients).catch(console.error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header className="mb-8 pb-4 border-b-2 border-[#d6c3a6]">
|
||||
<h2 className="text-3xl font-extrabold mb-2">Dashboard</h2>
|
||||
<p className="text-lg">Willkommen im Infoscreen-Management Dashboard.</p>
|
||||
</header>
|
||||
<h3 className="text-lg font-semibold mt-6 mb-4">Clients</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{clients.map(client => (
|
||||
<div key={client.uuid} className="bg-white rounded shadow p-4 flex flex-col items-center">
|
||||
<h4 className="text-lg font-bold mb-2">{client.location || 'Unbekannter Standort'}</h4>
|
||||
<img
|
||||
src={`/screenshots/${client.uuid}`}
|
||||
alt={`Screenshot ${client.location}`}
|
||||
className="w-full h-48 object-contain bg-gray-100 mb-2"
|
||||
onError={e => (e.currentTarget.style.display = 'none')}
|
||||
/>
|
||||
<div className="text-sm text-gray-700 mb-1">
|
||||
<span className="font-semibold">IP:</span> {client.ip_address}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<span className="font-semibold">Letztes Lebenszeichen:</span>{' '}
|
||||
{client.last_alive ? new Date(client.last_alive).toLocaleString() : '-'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{clients.length === 0 && (
|
||||
<div className="col-span-full text-center text-gray-400">Keine Clients gefunden.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
8
dashboard/src/einstellungen.tsx
Normal file
8
dashboard/src/einstellungen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Einstellungen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Einstellungen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Einstellungen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Einstellungen;
|
||||
8
dashboard/src/gruppen.tsx
Normal file
8
dashboard/src/gruppen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Gruppen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Gruppen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Gruppen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Gruppen;
|
||||
8
dashboard/src/medien.tsx
Normal file
8
dashboard/src/medien.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Medien: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Medien</h2>
|
||||
<p>Willkommen im Infoscreen-Management Medien.</p>
|
||||
</div>
|
||||
);
|
||||
export default Medien;
|
||||
8
dashboard/src/ressourcen.tsx
Normal file
8
dashboard/src/ressourcen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Ressourcen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Ressourcen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Ressourcen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Ressourcen;
|
||||
0
dashboard/src/termine.tsx
Normal file
0
dashboard/src/termine.tsx
Normal file
@@ -1,7 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': 'http://localhost:8000',
|
||||
'/screenshots': 'http://localhost:8000',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
27
nginx.conf
27
nginx.conf
@@ -3,34 +3,21 @@ http {
|
||||
upstream dashboard {
|
||||
server 127.0.0.1:3000;
|
||||
}
|
||||
upstream infoscreen_api {
|
||||
server infoscreen-api:8000;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Optional: HTTP auf HTTPS weiterleiten
|
||||
# return 301 https://$host$request_uri;
|
||||
|
||||
location / {
|
||||
proxy_pass http://dashboard;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /etc/nginx/certs/dev.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/dev.key;
|
||||
|
||||
# Leitet /api/ und /screenshots/ an den API-Server weiter
|
||||
location /api/ {
|
||||
proxy_pass http://infoscreen-api:8000/api/;
|
||||
proxy_pass http://infoscreen_api/api/;
|
||||
}
|
||||
location /screenshots/ {
|
||||
proxy_pass http://infoscreen-api:8000/screenshots/;
|
||||
proxy_pass http://infoscreen_api/screenshots/;
|
||||
}
|
||||
# Alles andere geht ans Frontend
|
||||
location / {
|
||||
proxy_pass http://dashboard;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
Reference in New Issue
Block a user