From a4685c09b8d713e6bdfb0eefe3a612aa23d08f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Gr=C3=BCssing?= Date: Mon, 1 Jun 2026 21:04:20 +0200 Subject: [PATCH] Add: Feedback-System, GitHub Actions Deploy, Assets-Demo User-Suche --- .github/workflows/README.md | 64 + .github/workflows/deploy.yml | 59 + Assets-Demo.html | 1524 ++++++++++++++++++++ backend/src/routes/feedback.routes.js | 60 + backend/src/server.js | 2 + frontend/src/components/common/Sidebar.jsx | 154 ++ frontend/src/pages/FeedbackPage.jsx | 293 ++++ frontend/src/routes.jsx | 2 + 8 files changed, 2158 insertions(+) create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/deploy.yml create mode 100644 Assets-Demo.html create mode 100644 backend/src/routes/feedback.routes.js create mode 100644 frontend/src/pages/FeedbackPage.jsx diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..708dc73 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,64 @@ +# GitHub Actions — Deploy Workflow + +## Übersicht + +Der Workflow `deploy.yml` läuft automatisch bei jedem Push auf `main`. + +- **Job 1 `build`**: Installiert Dependencies und baut das Frontend — rein zum Kompilierungs-Check. +- **Job 2 `deploy`**: Kopiert den Quellcode per SCP auf den Server und startet Docker neu. **Erfordert manuellen Approval** über das GitHub Environment `production`. + +--- + +## 1. SSH Key als Secret hinterlegen + +### SSH Key generieren (falls noch kein dedizierter Key vorhanden) + +```bash +ssh-keygen -t ed25519 -C "github-actions-itnexus" -f ~/.ssh/github_actions_itnexus +``` + +Den Public Key auf dem Server hinterlegen: + +```bash +cat ~/.ssh/github_actions_itnexus.pub | ssh root@192.168.0.194 "cat >> ~/.ssh/authorized_keys" +``` + +### Secrets in GitHub eintragen + +Gehe zu: **Repository → Settings → Secrets and variables → Actions → New repository secret** + +| Secret Name | Wert | +|---|---| +| `SSH_PRIVATE_KEY` | Inhalt von `~/.ssh/github_actions_itnexus` (Private Key, beginnt mit `-----BEGIN OPENSSH PRIVATE KEY-----`) | +| `SERVER_IP` | `192.168.0.194` | + +--- + +## 2. Production Environment mit Required Reviewer einrichten + +### Environment erstellen + +1. Gehe zu: **Repository → Settings → Environments** +2. Klicke auf **New environment** +3. Name: `production` (exakt so, wie im Workflow hinterlegt) +4. Klicke auf **Configure environment** + +### Required Reviewers setzen + +1. Aktiviere **Required reviewers** +2. Füge `Simon Grüssing` (GitHub-Username) als Reviewer hinzu +3. Optional: **Prevent self-review** aktivieren wenn ein weiterer Reviewer vorhanden ist +4. Speichern mit **Save protection rules** + +--- + +## 3. Approval-Prozess + +1. Push auf `main` → Job `build` startet automatisch und läuft durch. +2. Nach erfolgreichem Build: Job `deploy` wartet auf Approval. +3. GitHub schickt eine **E-Mail-Benachrichtigung** an alle eingetragenen Reviewer. +4. Reviewer klickt in der E-Mail oder direkt im GitHub Actions Tab auf den Workflow-Run. +5. Unter **"This workflow run is waiting for a required review"** → **Review deployments** → Haken bei `production` setzen → **Approve and deploy**. +6. Erst dann startet der Deployment-Job. + +> Tipp: Den Workflow-Status siehst du unter **Repository → Actions**. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0efb03a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,59 @@ +name: IT Nexus Deploy + +on: + push: + branches: + - main + +jobs: + build: + name: Build Frontend + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + working-directory: frontend + run: npm install + + - name: Build frontend + working-directory: frontend + run: npm run build + + deploy: + name: Deploy to Production + runs-on: ubuntu-latest + needs: build + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts + + - name: Deploy frontend/src to server + run: | + scp -r frontend/src root@${{ secrets.SERVER_IP }}:/opt/it-nexus/frontend/src + + - name: Deploy backend/src to server + run: | + scp -r backend/src root@${{ secrets.SERVER_IP }}:/opt/it-nexus/backend/src + + - name: Rebuild and restart Docker containers + run: | + ssh root@${{ secrets.SERVER_IP }} "cd /opt/it-nexus && docker compose build frontend backend && docker compose up -d frontend backend" diff --git a/Assets-Demo.html b/Assets-Demo.html new file mode 100644 index 0000000..e7d7cbf --- /dev/null +++ b/Assets-Demo.html @@ -0,0 +1,1524 @@ + + + + + +Asset-Verwaltung — IT Nexus + + + + + +
+ +
+
Asset-Verwaltung
+
+ + +
+ + +
+
+
+ Assets + 47 +
+ + +
+ + +
+ + + +
+ Alle + Notebook + Monitor + Headset + Drucker + Sonstiges +
+
+ Alle + Verfügbar + Zugewiesen + In Wartung +
+
+ + +
+ 👤 + + +
+ + +
+
+
47
+
Gesamt
+
+
+
31
+
Zugewiesen
+
+
+
12
+
Verfügbar
+
+
+
4
+
In Wartung
+
+
+ + +
+ +
+
💻
+
+
ThinkPad X1 Carbon Gen 10
+
NB-001 · SN: LRF291K
+
+
+ Zugewiesen +
+
+
+ +
+
💻
+
+
Dell Latitude 5520
+
NB-002 · SN: BX92JKL
+
+
+ Verfügbar +
+
+
+ +
+
🖥️
+
+
LG 27" 4K Monitor
+
MON-001 · SN: KJH8821
+
+
+ Zugewiesen +
+
+
+ +
+
💻
+
+
MacBook Pro 14"
+
NB-003 · SN: C02XK2L
+
+
+ Zugewiesen +
+
+
+ +
+
🎧
+
+
Jabra Evolve2 85
+
HD-001 · SN: JAB771
+
+
+ Verfügbar +
+
+
+ +
+
💻
+
+
HP EliteBook 840 G9
+
NB-004 · SN: CZC992
+
+
+ In Wartung +
+
+
+ +
+
🖥️
+
+
Dell UltraSharp U2722
+
MON-002 · SN: DEL4421
+
+
+ Verfügbar +
+
+
+ +
+
💻
+
+
Lenovo ThinkPad T14s
+
NB-005 · SN: PF2KL91
+
+
+ Zugewiesen +
+
+
+ +
+
🖨️
+
+
HP LaserJet Pro MFP
+
DR-001 · SN: VNC7712
+
+
+ Zugewiesen +
+
+
+ +
+
💻
+
+
Dell XPS 15 9530
+
NB-006 · SN: DXP88GL
+
+
+ Verfügbar +
+
+
+ +
+
+ + +
+ + +
+
💻
+
+
ThinkPad X1 Carbon Gen 10
+
NB-001  ·  SN: LRF291K  ·  Lenovo
+
+ Notebook + Zugewiesen + + + Online + +
+
+
+ + + +
+
+ + +
+
+
💰 Buchwert
+
1.247 €
+
Kaufpreis: 1.899 € (01.03.2023)
+
+
+
🔧 Nächste Wartung
+
in 45 Tagen
+
Geplant: 15.07.2026
+
+
+
👤 Zugewiesen an
+
Max Mustermann
+
seit 01.03.2023
+
+
+
🤖 Agent-Sync
+
vor 3 Min
+
v2.0.0 · aktiv
+
+
+ + +
+
Übersicht
+
Wartung
+
Verlauf
+
Agent-Daten
+
Dokumente
+
+ + +
+ +
+ +
+
🖥️ Geräteinformationen
+
+ Typ + Notebook +
+
+ Hersteller + Lenovo +
+
+ Modell + ThinkPad X1 Carbon Gen 10 +
+
+ Seriennummer + LRF291K +
+
+ Betriebssystem + Windows 11 Pro +
+
+ IP-Adresse + + 192.168.0.45 + + +
+
+ Inventarnummer + INV-2023-041 +
+
+ Abteilung + IT +
+
+ Standort + Büro Hauptgebäude +
+
+ + +
+
📊 Finanzdaten & AfA
+
+ Kaufpreis + 1.899,00 € +
+
+ Kaufdatum + 01.03.2023 +
+
+ Nutzungsdauer + 3 Jahre +
+
+ Buchwert aktuell + 1.247,33 € +
+
+ Restwert + 0,00 € +
+
+ Abschreibung/Monat + 52,75 € +
+
+
+ Abgeschrieben + 34,4 % (651,67 €) +
+
+
+
+
+ Kauf: 01.03.2023 + Ende: 28.02.2026 +
+
+
+
+ +
+ +
+
👤 Zuweisung
+
+
MM
+
+
Max Mustermann
+
IT-Administrator
+
+
+
+ E-Mail + m.mustermann@cereda.de +
+
+ Zugewiesen seit + 01.03.2023 +
+
+ + +
+
+ + +
+
🤖 Agent-Status
+ +
+ CPU +
+
+
+ 23% +
+ +
+ RAM +
+
+
+ 8,2 / 16 GB +
+ +
+ Disk +
+
+
+ 234 / 512 GB +
+ +
+ +
+ Letzter Check-in + vor 3 Minuten +
+
+ Agent-Version + v2.0.0 +
+
+ Hostname + DESKTOP-MM01 +
+
+
+
+ + +
+
+
+
📅 Wartungsplan
+
+
+
+
15.07.2026 · Geplant
+
Jährliche Inspektion
+
Hardware-Check, Reinigung, BIOS-Update
+
+
+
+
12.03.2025 · Abgeschlossen
+
Software-Update & Treiber
+
Windows 11 23H2, Lenovo-Treiber aktualisiert
+
+
+
+
05.09.2024 · Abgeschlossen
+
Akku-Diagnose
+
Kapazität: 94% — kein Austausch nötig
+
+
+
+
01.03.2023 · Abgeschlossen
+
Ersteinrichtung & Imaging
+
Windows-Image deployed, Intune-Enrollment
+
+
+
+
+
🔧 Wartung planen
+
+
+
Wartungstyp
+ +
+
+
Datum
+ +
+
+
Notizen
+ +
+ +
+
+
+
+ + +
+
+
🕐 Änderungshistorie
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatumEreignisDetailsBenutzer
01.06.2026 09:14Check-inAgent-Sync erfolgreichSystem
28.05.2026 14:22Wartung geplantJährliche Inspektion (15.07.2026)gruessing
12.03.2025 10:00Wartung abgeschlossenSoftware-Update & Treibergruessing
01.03.2023 08:30ZuweisungZugewiesen an Max Mustermanngruessing
01.03.2023 07:45Asset angelegtErsterfassung NB-001gruessing
+
+
+ + +
+
+
+
💻 System
+
HostnameDESKTOP-MM01
+
OSWindows 11 Pro 23H2
+
Build22631.3810
+
Architekturx64
+
ProzessorIntel Core i7-1265U
+
Kerne10 (12 Threads)
+
RAM gesamt16 GB
+
+
+
🌐 Netzwerk
+
IPv4192.168.0.45
+
IPv6fe80::a1b2:c3d4:e5f6
+
MAC54:E1:AD:F2:9C:3B
+
InterfaceIntel Wi-Fi 6E AX211
+
Gateway192.168.0.254
+
DNS192.168.0.1
+
+
+
🛡️ Sicherheit
+
+ Windows Defender + ✓ Aktiv +
+
+ Signaturen + 1.415.132.0 (aktuell) +
+
+ BitLocker + ✓ Aktiv (C:) +
+
+ Firewall + ✓ Aktiv +
+
+ Pending Reboots + Keine +
+
+
+
📦 Updates
+
+ Status + Aktuell +
+
+ Letztes Update + 28.05.2026 +
+
+ Ausstehend + 0 Updates +
+
+ Agent-Version + v2.0.0 +
+
+ Letzter Check-in + vor 3 Minuten +
+
+
+
+ + +
+
+
📁 Dokumente
+
+
📄
+
+
Kaufbeleg_ThinkPad_X1_2023.pdf
+
PDF · 245 KB · hochgeladen 01.03.2023 von gruessing
+
+
+
+
📋
+
+
Übergabeprotokoll_Mustermann.pdf
+
PDF · 128 KB · hochgeladen 01.03.2023 von gruessing
+
+
+
+
🔧
+
+
Wartungsprotokoll_2025-03.pdf
+
PDF · 89 KB · hochgeladen 12.03.2025 von gruessing
+
+
+
+ +
+
+
+ +
+
+ + + + diff --git a/backend/src/routes/feedback.routes.js b/backend/src/routes/feedback.routes.js new file mode 100644 index 0000000..a93e20d --- /dev/null +++ b/backend/src/routes/feedback.routes.js @@ -0,0 +1,60 @@ +const express = require('express'); +const router = express.Router(); +const { authenticateToken } = require('../middleware/auth'); +const { asyncHandler } = require('../middleware/errorHandler'); + +router.use(authenticateToken); + +// GET /api/feedback — alle Issues von GitHub laden +router.get('/', asyncHandler(async (req, res) => { + const { state = 'open' } = req.query; + const response = await fetch(`https://api.github.com/repos/SimonGCereda/IT-Nexus/issues?state=${state}&labels=feedback&per_page=50`, { + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json' + } + }); + const issues = await response.json(); + res.json({ status: 'success', data: issues }); +})); + +// POST /api/feedback — neues Issue erstellen +router.post('/', asyncHandler(async (req, res) => { + const { title, body, category } = req.body; + const user = req.user; + + const labelMap = { bug: 'bug', feature: 'enhancement', idea: 'question' }; + const labels = ['feedback', labelMap[category] || 'feedback']; + + const issueBody = `**Gemeldet von:** ${user.first_name || ''} ${user.last_name || ''} (@${user.username})\n**Kategorie:** ${category}\n\n---\n\n${body}`; + + const response = await fetch(`https://api.github.com/repos/SimonGCereda/IT-Nexus/issues`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ title, body: issueBody, labels }) + }); + const issue = await response.json(); + res.json({ status: 'success', data: issue }); +})); + +// PATCH /api/feedback/:id — Issue schließen/öffnen (nur Admin) +router.patch('/:id', asyncHandler(async (req, res) => { + const { state } = req.body; + const response = await fetch(`https://api.github.com/repos/SimonGCereda/IT-Nexus/issues/${req.params.id}`, { + method: 'PATCH', + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ state }) + }); + const issue = await response.json(); + res.json({ status: 'success', data: issue }); +})); + +module.exports = router; diff --git a/backend/src/server.js b/backend/src/server.js index 7ff8fd8..b9652e9 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -175,6 +175,8 @@ const securityReportRoutes = require('./routes/securityReport.routes'); app.use('/api/security-reports', securityReportRoutes); const tvRoutes = require('./routes/tv.routes'); app.use('/api/tv', tvRoutes); +const feedbackRoutes = require('./routes/feedback.routes'); +app.use('/api/feedback', feedbackRoutes); // Static file serving for uploads (PDFs) const path = require('path'); diff --git a/frontend/src/components/common/Sidebar.jsx b/frontend/src/components/common/Sidebar.jsx index efd6c71..5e59574 100644 --- a/frontend/src/components/common/Sidebar.jsx +++ b/frontend/src/components/common/Sidebar.jsx @@ -2,6 +2,126 @@ import React, { useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; +/* ── FeedbackModal ───────────────────────────────────────────────── */ +const CATEGORY_OPTIONS = [ + { value: 'bug', label: 'Bug 🐛' }, + { value: 'feature', label: 'Feature-Wunsch ✨' }, + { value: 'idea', label: 'Idee 💡' }, +]; + +function FeedbackModal({ onClose }) { + const [title, setTitle] = useState(''); + const [body, setBody] = useState(''); + const [category, setCategory] = useState('bug'); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(''); + + const handleSubmit = async (e) => { + e.preventDefault(); + if (!title.trim() || !body.trim()) { setError('Bitte Titel und Beschreibung ausfüllen.'); return; } + setLoading(true); + setError(''); + try { + const token = localStorage.getItem('token'); + const API = process.env.REACT_APP_API_URL || ''; + const res = await fetch(`${API}/api/feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, + body: JSON.stringify({ title, body, category }) + }); + const json = await res.json(); + if (json.status === 'success') { setSuccess(true); } + else { setError('Fehler beim Senden.'); } + } catch { + setError('Server nicht erreichbar.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
e.stopPropagation()} + style={{ + background: 'var(--card-bg, #1e1e2e)', + border: '1px solid var(--border-color, rgba(255,255,255,0.1))', + borderRadius: 12, + padding: 28, + width: '100%', + maxWidth: 440, + boxShadow: '0 20px 60px rgba(0,0,0,0.5)' + }} + > +
+

+ Feedback senden +

+ +
+ + {success ? ( +
+
+

Feedback gesendet!

+

Das Issue wurde auf GitHub erstellt.

+ +
+ ) : ( +
+
+ + +
+
+ + setTitle(e.target.value)} + placeholder="Kurze Zusammenfassung…" + style={{ width: '100%', padding: '8px 12px', borderRadius: 8, border: '1px solid var(--border-color, rgba(255,255,255,0.1))', background: 'var(--input-bg, #252535)', color: 'var(--text-primary, #f1f5f9)', fontSize: 13, boxSizing: 'border-box' }} + /> +
+
+ +