Compare commits
37 Commits
6cf9b2a627
...
tills-bran
| Author | SHA1 | Date | |
|---|---|---|---|
| a7bb9563b3 | |||
| 4e0c470663 | |||
| b5300f74bd | |||
| 6a53e621ca | |||
| a59ce652af | |||
| 27cfc95081 | |||
| c513666391 | |||
| ae30dbce57 | |||
| 817ddd98e9 | |||
| bfce2fc7b7 | |||
| efbcd567ee | |||
| a873765d08 | |||
| efbcadb95a | |||
| da3ccaffe9 | |||
| f4e04573bd | |||
| aa253f3871 | |||
| cfd6a25b21 | |||
| d307763007 | |||
| d7e6912e08 | |||
| ffe96074f4 | |||
| 49ccf3908a | |||
| 9514645904 | |||
| 63f45abb3e | |||
| 7d74b5a7bf | |||
| 55f2553780 | |||
| 0852ea070b | |||
| 7a0533ac09 | |||
| 65c44ab371 | |||
| 5399169b11 | |||
| 05f6f149ad | |||
| 12ed413c04 | |||
| ccf5a1d678 | |||
| eb23d638e6 | |||
| 750dba2d6f | |||
| 6aa3780a96 | |||
| 8890a62026 | |||
| 4f6aea8e20 |
15
.env
15
.env
@@ -1,13 +1,2 @@
|
||||
# MindMap Umgebungsvariablen
|
||||
# Kopiere diese Datei zu .env und passe die Werte an
|
||||
|
||||
# Flask
|
||||
SECRET_KEY=dein-geheimer-schluessel-hier
|
||||
|
||||
# OpenAI API
|
||||
OPENAI_API_KEY=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
|
||||
|
||||
# Datenbank
|
||||
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden
|
||||
# Der Pfad wird relativ zum Projektverzeichnis angegeben
|
||||
# SQLALCHEMY_DATABASE_URI=sqlite:////absoluter/pfad/zu/database/systades.db OPENAI_API_KEY=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
|
||||
SECRET_KEY=eed9298856dc9363cd32778265780d6904ba24e6a6b815a2cc382bcdd767ea7b
|
||||
OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier
|
||||
|
||||
8
.vscode/jsconfig.json
vendored
Normal file
8
.vscode/jsconfig.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"esnext"
|
||||
]
|
||||
}
|
||||
}
|
||||
68
.vscode/main.js
vendored
Normal file
68
.vscode/main.js
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/// <reference types="vscode" />
|
||||
// @ts-check
|
||||
// API: https://code.visualstudio.com/api/references/vscode-api
|
||||
// @ts-ignore
|
||||
const vscode = require('vscode');
|
||||
* @typedef {import('vscode').ExtensionContext} ExtensionContext
|
||||
* @typedef {import('vscode').commands} commands
|
||||
* @typedef {import('vscode').window} window
|
||||
* @typedef {import('vscode').TextEditor} TextEditor
|
||||
* @typedef {import('vscode').TextDocument} TextDocument
|
||||
*/
|
||||
|
||||
/**
|
||||
* Aktiviert die Erweiterung und registriert den Auto-Resume-Befehl
|
||||
* @param {vscode.ExtensionContext} context - Der Erweiterungskontext
|
||||
*/
|
||||
function activate(context) {
|
||||
const disposable = vscode.commands.registerCommand('extension.autoResume', () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) return;
|
||||
|
||||
const document = editor.document;
|
||||
const text = document.getText();
|
||||
|
||||
// Track last click time to avoid multiple clicks
|
||||
let lastClickTime = 0;
|
||||
|
||||
// Main function that looks for and clicks the resume link
|
||||
function clickResumeLink() {
|
||||
// Prevent clicking too frequently (3 second cooldown)
|
||||
const now = Date.now();
|
||||
if (now - lastClickTime < 3000) return;
|
||||
|
||||
// Check if text contains rate limit text
|
||||
if (text.includes('stop the agent after 25 tool calls') ||
|
||||
text.includes('Note: we default stop')) {
|
||||
|
||||
// Find the resume link position
|
||||
const resumePos = text.indexOf('resume the conversation');
|
||||
if (resumePos !== -1) {
|
||||
vscode.window.showInformationMessage('Auto-resuming conversation...');
|
||||
lastClickTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Führe periodisch aus
|
||||
const interval = global.setInterval(clickResumeLink, 1000);
|
||||
|
||||
// Speichere das Intervall in den Subscriptions
|
||||
context.subscriptions.push({
|
||||
dispose: () => global.clearInterval(interval)
|
||||
});
|
||||
// Führe die Funktion sofort aus
|
||||
clickResumeLink();
|
||||
});
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
activate,
|
||||
deactivate
|
||||
}
|
||||
@@ -16,7 +16,82 @@
|
||||
- Vergessen der Mindmap-Datenstruktur gemäß der Roadmap
|
||||
|
||||
# Häufige Fehler und Lösungen
|
||||
D
|
||||
|
||||
## Datenbankfehler
|
||||
|
||||
### Fehler: "no such column: user.password"
|
||||
|
||||
**Fehlerbeschreibung:**
|
||||
```
|
||||
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: user.password
|
||||
[SQL: SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password AS user_password, user.created_at AS user_created_at, user.is_active AS user_is_active, user.role AS user_role
|
||||
FROM user
|
||||
WHERE user.id = ?]
|
||||
```
|
||||
|
||||
**Ursache:**
|
||||
Die Spalte `password` fehlt in der Tabelle `user` der SQLite-Datenbank. Dies kann durch eine unvollständige Datenbankinitialisierung oder ein fehlerhaftes Schema-Update verursacht worden sein.
|
||||
|
||||
**Lösung:**
|
||||
|
||||
1. **Datenbank reparieren mit dem Fix-Skript**
|
||||
|
||||
```bash
|
||||
python fix_user_table.py
|
||||
```
|
||||
|
||||
Dieses Skript:
|
||||
- Prüft, ob die Tabelle `user` existiert und erstellt sie, falls nicht
|
||||
- Prüft, ob die Spalte `password` existiert und fügt sie hinzu, falls nicht
|
||||
|
||||
2. **Standardbenutzer erstellen**
|
||||
|
||||
```bash
|
||||
python create_default_users.py
|
||||
```
|
||||
|
||||
Dieses Skript:
|
||||
- Erstellt Standardbenutzer (admin, user), falls keine vorhanden sind
|
||||
- Setzt Passwörter mit korrektem Hashing
|
||||
|
||||
3. **Datenbank testen**
|
||||
|
||||
```bash
|
||||
python test_app.py
|
||||
```
|
||||
|
||||
Dieses Skript überprüft:
|
||||
- Ob die Datenbank existiert
|
||||
- Ob die Tabelle `user` korrekt konfiguriert ist
|
||||
- Ob Benutzer vorhanden sind
|
||||
|
||||
## Häufige Ursachen für Datenbankfehler
|
||||
|
||||
1. **Inkonsistente Datenbankschemas**
|
||||
- Unterschiede zwischen dem SQLAlchemy-Modell und der tatsächlichen Datenbankstruktur
|
||||
- Fehlende Spalten, die in den Modellen definiert sind
|
||||
|
||||
2. **Falsche Datenbankinitialisierung**
|
||||
- Die Datenbank wurde nicht korrekt initialisiert
|
||||
- Fehler bei der Migration oder dem Schema-Update
|
||||
|
||||
3. **Datenbankdatei-Korrumpierung**
|
||||
- Die SQLite-Datenbankdatei wurde beschädigt
|
||||
- Lösung: Sicherung wiederherstellen oder Datenbank neu erstellen
|
||||
|
||||
## Vorbeugende Maßnahmen
|
||||
|
||||
1. **Regelmäßige Backups**
|
||||
- Tägliche Sicherung der Datenbankdatei
|
||||
|
||||
2. **Schema-Validierung**
|
||||
- Regelmäßige Überprüfung der Datenbankstruktur
|
||||
- Automatisierte Tests für Datenbankschema
|
||||
|
||||
3. **Datenbankmigration**
|
||||
- Verwenden Sie Flask-Migrate für strukturierte Datenbank-Updates
|
||||
- Dokumentieren Sie alle Schemaänderungen
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
### Problem: Externe Ressourcen werden nicht geladen
|
||||
@@ -79,15 +154,6 @@ D
|
||||
|
||||
3. Stellen Sie sicher, dass die Datei `static/css/tailwind.min.css` existiert und aktuell ist.
|
||||
|
||||
## Datenbank-Fehler
|
||||
|
||||
### Problem: Datenbank existiert nicht
|
||||
**Fehler:** SQLite-Datenbank kann nicht geöffnet werden.
|
||||
|
||||
**Lösung:**
|
||||
1. Datenbank initialisieren: `python TOOLS.py db:rebuild`
|
||||
2. Sicherstellen, dass das Datenbankverzeichnis existiert und Schreibrechte hat
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
### Problem: Login funktioniert nicht
|
||||
|
||||
51
README.md
51
README.md
@@ -9,6 +9,7 @@ Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen
|
||||
- Tailwind CSS (via CDN) für moderne UI
|
||||
- SVG-Bibliotheken für Visualisierungen (D3.js)
|
||||
- JavaScript/Alpine.js für interaktive Komponenten
|
||||
- WebGL für animierte Hintergrundeffekte
|
||||
- **Datenbank**: SQLite mit SQLAlchemy
|
||||
- **KI-Integration**: OpenAI API für intelligente Assistenz
|
||||
|
||||
@@ -61,16 +62,20 @@ Für detaillierte Hilfe: `python TOOLS.py -h`
|
||||
- [x] Favicon erstellen
|
||||
- [x] Setup-Skript für einfache Installation
|
||||
|
||||
### Phase 2: Design-Überarbeitung 🔄
|
||||
### Phase 2: Design-Überarbeitung ✅
|
||||
- [x] Implementierung des Dark Mode
|
||||
- [x] Erstellung eines modernen, minimalistischen UI mit Tech-Ästhetik
|
||||
- [x] Responsive Design für alle Geräte
|
||||
- [ ] Gestaltung der Landing Page mit großer Typografie
|
||||
- [x] Gestaltung der Landing Page mit großer Typografie
|
||||
- [x] Animierter Neurales Netzwerk-Hintergrund mit WebGL
|
||||
|
||||
### Phase 3: Mindmap-Funktionalitäten 🔄
|
||||
- [x] Verbesserte Visualisierung mit SVG und D3.js
|
||||
- [x] Implementierung der Mouseover-Funktion
|
||||
- [x] Entwicklung der Suchfunktion für Knoten
|
||||
- [x] Clustertopologie für neuronale Netzwerkdarstellung
|
||||
- [x] Fehlerbehandlung für robuste Datenverarbeitung und Knotenverweise
|
||||
- [x] Verbesserte Verbindungserkennung zwischen Knoten
|
||||
- [ ] Tagging-System für Inhalte
|
||||
- [ ] Quellenmanagement und -verlinkung
|
||||
- [ ] Upload-Funktionalität an Knotenpunkten
|
||||
@@ -115,8 +120,8 @@ Für detaillierte Hilfe: `python TOOLS.py -h`
|
||||
|
||||
## Aktueller Status
|
||||
- **Phase 1**: ✅ Abgeschlossen
|
||||
- **Phase 2**: 🔄 In Bearbeitung (75% abgeschlossen)
|
||||
- **Phase 3**: 🔄 In Bearbeitung (50% abgeschlossen)
|
||||
- **Phase 2**: ✅ Abgeschlossen
|
||||
- **Phase 3**: 🔄 In Bearbeitung (75% abgeschlossen)
|
||||
|
||||
## Aktuelle Fortschritte
|
||||
- Grundlegende UI modernisiert mit Tailwind CSS und Dark Mode
|
||||
@@ -124,12 +129,41 @@ Für detaillierte Hilfe: `python TOOLS.py -h`
|
||||
- Setup-Prozess vereinfacht mit einem Shell-Skript
|
||||
- Mindmap-Visualisierung komplett überarbeitet mit D3.js für eine interaktivere Erfahrung
|
||||
- Responsive Design für optimale Darstellung auf allen Geräten
|
||||
- Animierter neuronaler Netzwerk-Hintergrund mit WebGL implementiert
|
||||
- Verbesserte neuronale Cluster-Darstellung in der MindMap-Ansicht
|
||||
- Behebung von kritischen Fehlern in der Knotenvisualisierung und Verbindungserkennung
|
||||
- Robustere Datenverarbeitung für Mindmap-Knoten implementiert
|
||||
- Fehlerbehandlung für verschiedene API-Datenformate verbessert
|
||||
|
||||
## Neuronaler Netzwerk-Hintergrund
|
||||
|
||||
Ein wesentliches neues Feature ist der animierte Hintergrund, der ein neuronales Netzwerk simuliert:
|
||||
|
||||
- **WebGL-basierte Rendering-Engine** für hohe Performance
|
||||
- **Dynamische Knoten und Verbindungen** mit realistischem Bewegungsverhalten
|
||||
- **Neuronenfeuer-Simulation** mit Signalweiterleitung zwischen Knoten
|
||||
- **Clustertopologie** für realistisches Erscheinungsbild
|
||||
- **Anpassbare Farbgebung und Animationsparameter**
|
||||
- **Flüssige Animationen** mit über 100 Knotenpunkten
|
||||
|
||||
Die Animation ist vollständig responsiv und passt sich automatisch an verschiedene Bildschirmgrößen an, ohne die Browser-Performance zu beeinträchtigen.
|
||||
|
||||
## Mindmap-Verbesserungen
|
||||
|
||||
Die Mindmap-Darstellung wurde grundlegend überarbeitet:
|
||||
|
||||
- **D3.js Force-Directed Graph** für intuitive Knotenpositionierung
|
||||
- **Verbesserte Fehlerbehandlung** für robustere Datenverarbeitung
|
||||
- **Neuronale Cluster-Gruppierung** von thematisch zusammengehörigen Inhalten
|
||||
- **Glasmorphismus-Effekte** für moderne visuelle Darstellung
|
||||
- **Verbesserte Hover- und Selektionseffekte**
|
||||
- **Flüssige Animationen** bei Knotenauswahl und -fokussierung
|
||||
|
||||
## Nächste Schritte
|
||||
- Fertigstellung der Landing Page
|
||||
- Erstellung der "Wer sind wir?"-Seite
|
||||
- Implementierung des Tagging-Systems für Gedanken
|
||||
- Fertigstellung des Tagging-Systems für Gedanken
|
||||
- Verbesserung der Gedankenansicht im Mindmap-Bereich
|
||||
- Implementierung von Quellenmanagement
|
||||
- Überarbeitung der Startseite mit neuen Features
|
||||
|
||||
## Content Security Policy (CSP)
|
||||
|
||||
@@ -147,6 +181,7 @@ Die Anwendung nutzt eine Mischung aus lokalen Ressourcen und CDNs:
|
||||
- Alpine.js
|
||||
- Font Awesome
|
||||
- Google Fonts (Inter und JetBrains Mono)
|
||||
- WebGL-Animation (neural-network-background.js)
|
||||
|
||||
### CSP-Nonces
|
||||
|
||||
@@ -158,4 +193,4 @@ Die Anwendung verwendet Nonces für Inline-Skripte. In den Templates wird `{{ cs
|
||||
</script>
|
||||
```
|
||||
|
||||
*Zuletzt aktualisiert: 06.06.2024*
|
||||
*Zuletzt aktualisiert: 15.06.2024*
|
||||
87
ROADMAP.md
87
ROADMAP.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorientierten Mindmap-Funktionalität für das Systades-Projekt.
|
||||
|
||||
## Phase 1: Grundlegendes Datenmodell und Backend (Abgeschlossen)
|
||||
## Phase 1: Grundlegendes Datenmodell und Backend (Abgeschlossen ✅)
|
||||
|
||||
- [x] Entwurf des Datenbankschemas für benutzerorientierte Mindmaps
|
||||
- [x] Implementierung der Modelle in models.py
|
||||
@@ -10,31 +10,54 @@ Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorien
|
||||
- [x] Integration mit der bestehenden Benutzerauthentifizierung
|
||||
- [x] Seed-Daten für die Entwicklung und Tests
|
||||
|
||||
## Phase 2: Dynamische Mindmap-Visualisierung (Aktuell)
|
||||
## Phase 2: Dynamische Mindmap-Visualisierung (Abgeschlossen ✅)
|
||||
|
||||
- [ ] Anpassung des Frontend-Codes zur Verwendung der DB-Daten anstelle des SVG
|
||||
- [ ] Implementierung von AJAX-Anfragen zum Laden der Mindmap-Daten
|
||||
- [ ] Dynamisches Rendering der Knoten, Verbindungen und Labels
|
||||
- [ ] Drag-and-Drop-Funktionalität für die Bewegung von Knoten
|
||||
- [ ] Zoom- und Pan-Funktionalität mit Persistenz der Ansicht
|
||||
- [x] Anpassung des Frontend-Codes zur Verwendung der DB-Daten anstelle des SVG
|
||||
- [x] Implementierung von AJAX-Anfragen zum Laden der Mindmap-Daten
|
||||
- [x] Dynamisches Rendering der Knoten, Verbindungen und Labels
|
||||
- [x] Drag-and-Drop-Funktionalität für die Bewegung von Knoten
|
||||
- [x] Zoom- und Pan-Funktionalität mit Persistenz der Ansicht
|
||||
- [x] Verbesserte Fehlerbehandlung in der Knotenvisualisierung
|
||||
- [x] Robustere Verbindungserkennung zwischen Knoten
|
||||
- [x] Implementierung von Glasmorphismus-Effekten für moderneres UI
|
||||
|
||||
## Phase 3: Benutzerdefinierte Mindmaps
|
||||
## Phase 3: Visuelles Design und UX (Abgeschlossen ✅)
|
||||
|
||||
- [ ] UI für das Erstellen, Bearbeiten und Löschen eigener Mindmaps
|
||||
- [x] Implementierung des Dark Mode
|
||||
- [x] Entwicklung eines modernen, minimalistischen UI
|
||||
- [x] Animierter neuronaler Netzwerk-Hintergrund mit WebGL
|
||||
- [x] Responsive Design für alle Geräte
|
||||
- [x] Verbesserte Hover- und Selektionseffekte
|
||||
- [x] Clustertopologie für neuronale Netzwerkdarstellung
|
||||
- [x] Animierte Neuronenfeuer-Simulation mit Signalweiterleitung
|
||||
|
||||
## Phase 4: Benutzerdefinierte Mindmaps (Aktuell 🔄)
|
||||
|
||||
- [x] UI für das Betrachten bestehender Mindmaps
|
||||
- [ ] UI für das Erstellen und Bearbeiten eigener Mindmaps
|
||||
- [ ] Funktion zum Hinzufügen/Entfernen von Knoten aus der öffentlichen Mindmap
|
||||
- [ ] Speichern der Knotenpositionen und Ansichtseinstellungen
|
||||
- [ ] Benutzerspezifische Visualisierungseinstellungen
|
||||
- [ ] Dashboard mit Übersicht aller Mindmaps des Benutzers
|
||||
|
||||
## Phase 4: Notizen und Annotationen
|
||||
## Phase 5: Notizen und Annotationen
|
||||
|
||||
- [x] Anzeige von Gedanken zu Mindmap-Knoten
|
||||
- [ ] UI für das Hinzufügen privater Notizen zu Knoten
|
||||
- [ ] Visuelle Anzeige von Notizen in der Mindmap
|
||||
- [ ] Texteditor mit Markdown-Unterstützung für Notizen
|
||||
- [ ] Kategorisierung und Farbkodierung von Notizen
|
||||
- [ ] Suchfunktion für Notizen
|
||||
|
||||
## Phase 5: Integrationen und Erweiterungen
|
||||
## Phase 6: Tagging und Quellenmanagement
|
||||
|
||||
- [ ] Tagging-System für Inhalte implementieren
|
||||
- [ ] Verknüpfen von Quellen mit Mindmap-Knoten
|
||||
- [ ] Upload-Funktionalität für Dateien und Medien
|
||||
- [ ] Verwaltung von Zitaten und Referenzen
|
||||
- [ ] Visuelles Feedback für Tags und Quellen in der Mindmap
|
||||
|
||||
## Phase 7: Integrationen und Erweiterungen
|
||||
|
||||
- [ ] Import/Export-Funktionalität für Mindmaps (JSON, PNG)
|
||||
- [ ] Teilen von Mindmaps (öffentlich/privat/mit bestimmten Benutzern)
|
||||
@@ -42,7 +65,7 @@ Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorien
|
||||
- [ ] Verknüpfung mit externen Ressourcen (Links, Dateien)
|
||||
- [ ] Versionierung von Mindmaps
|
||||
|
||||
## Phase 6: KI-Integration und Analyse
|
||||
## Phase 8: KI-Integration und Analyse
|
||||
|
||||
- [ ] KI-gestützte Vorschläge für Verbindungen zwischen Knoten
|
||||
- [ ] Automatische Kategorisierung von Inhalten
|
||||
@@ -50,7 +73,7 @@ Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorien
|
||||
- [ ] Mindmap-Statistiken und Analysen
|
||||
- [ ] KI-basierte Zusammenfassung von Teilbereichen der Mindmap
|
||||
|
||||
## Phase 7: Optimierung und Skalierung
|
||||
## Phase 9: Optimierung und Skalierung
|
||||
|
||||
- [ ] Performance-Optimierung für große Mindmaps
|
||||
- [ ] Verbesserung der Benutzerfreundlichkeit basierend auf Feedback
|
||||
@@ -99,6 +122,7 @@ Das Datenbankschema umfasst folgende Hauptentitäten:
|
||||
### Frontend-Technologien
|
||||
|
||||
- D3.js für die Visualisierung der Mindmap
|
||||
- WebGL für den neuronalen Netzwerk-Hintergrund
|
||||
- AJAX für dynamisches Laden von Daten
|
||||
- Interaktive Bedienelemente mit JavaScript
|
||||
- Responsive Design mit Tailwind CSS
|
||||
@@ -114,16 +138,35 @@ Die implementierten API-Endpunkte umfassen:
|
||||
- `/api/mindmap/<id>/update_node_position` - Aktualisierung von Knotenpositionen
|
||||
- `/api/mindmap/<id>/notes` - Verwaltung von Notizen
|
||||
- `/api/nodes/<id>/thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten
|
||||
- `/api/get_dark_mode` - Abrufen der Dark Mode Einstellung
|
||||
|
||||
## Aktuelle Änderungen
|
||||
## Neuronaler Netzwerk-Hintergrund
|
||||
|
||||
Der neue WebGL-basierte Hintergrund bietet:
|
||||
|
||||
- WebGL-basierte Rendering-Engine für optimale Performance
|
||||
- Dynamische Knoten und Verbindungen mit realistischem Verhalten
|
||||
- Clustering von neuronalen Knoten für natürlicheres Erscheinungsbild
|
||||
- Simulation von neuronaler Aktivität und Signalweiterleitung
|
||||
- Anpassbare visuelle Parameter (Helligkeit, Dichte, Geschwindigkeit)
|
||||
- Vollständig responsives Design für alle Bildschirmgrößen
|
||||
|
||||
## Aktuelle Verbesserungen
|
||||
- Tailwind CSS wurde auf CDN-Version aktualisiert (06.06.2024)
|
||||
- Content Security Policy (CSP) für Tailwind CSS CDN konfiguriert
|
||||
- Content Security Policy (CSP) für Tailwind CSS CDN und WebGL konfiguriert
|
||||
- Behebung kritischer Fehler in der Mindmap-Knotenvisualisierung (15.06.2024)
|
||||
- Verbesserte Verbindungserkennung zwischen Knoten implementiert
|
||||
- Robuste Fehlerbehandlung für verschiedene API-Datenformate
|
||||
|
||||
## Zukünftige Aufgaben
|
||||
- Überprüfung der Kompatibilität der Tailwind CSS CDN-Version mit allen UI-Komponenten
|
||||
- Optimierung der Ladezeiten für mobile Geräte
|
||||
- Überarbeitung der Dark Mode Funktionalität mit neuer Tailwind Version
|
||||
## Zukünftige Aufgaben (Q3 2024)
|
||||
- Implementierung des Tagging-Systems für Gedanken
|
||||
- Quellenmanagement für Mindmap-Knoten
|
||||
- Erweiterte Benutzerprofilfunktionen
|
||||
- Verbesserung der mobilen Benutzererfahrung
|
||||
- Integration von Exportfunktionen für Mindmaps
|
||||
|
||||
## Langfristige Ziele
|
||||
- Migration zu einer statisch kompilierten Tailwind CSS Version für Produktivumgebungen
|
||||
- Implementierung von Tailwind Plugins für erweiterte UI-Funktionen
|
||||
*Zuletzt aktualisiert: 15.06.2024*
|
||||
|
||||
## [Entfernt] CORS-Unterstützung (flask-cors)
|
||||
- Die flask-cors-Bibliothek und alle zugehörigen Initialisierungen wurden entfernt.
|
||||
- CORS wird nicht mehr unterstützt oder benötigt.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
115
app.py
Executable file → Normal file
115
app.py
Executable file → Normal file
@@ -17,12 +17,14 @@ import secrets
|
||||
from sqlalchemy.sql import func
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
from flask_socketio import SocketIO, emit
|
||||
from flask_migrate import Migrate
|
||||
|
||||
# Modelle importieren
|
||||
from models import (
|
||||
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
|
||||
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
|
||||
node_thought_association, user_thought_bookmark
|
||||
node_thought_association, user_thought_bookmark, node_relationship
|
||||
)
|
||||
|
||||
# Lade .env-Datei
|
||||
@@ -39,6 +41,8 @@ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
||||
app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(), 'uploads'))
|
||||
app.config['WTF_CSRF_ENABLED'] = False
|
||||
|
||||
# OpenAI API-Konfiguration
|
||||
api_key = os.environ.get("OPENAI_API_KEY")
|
||||
@@ -88,6 +92,11 @@ login_manager.login_view = 'login'
|
||||
# Erst nach der App-Initialisierung die DB-Check-Funktionen importieren
|
||||
from utils.db_check import check_db_connection, initialize_db_if_needed
|
||||
|
||||
# SocketIO initialisieren
|
||||
socketio = SocketIO(app)
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
def create_default_categories():
|
||||
"""Erstellt die Standardkategorien für die Mindmap"""
|
||||
# Hauptkategorien
|
||||
@@ -509,6 +518,10 @@ def datenschutz():
|
||||
def agb():
|
||||
return render_template('agb.html')
|
||||
|
||||
@app.route('/ueber-uns/')
|
||||
def ueber_uns():
|
||||
return render_template('ueber_uns.html')
|
||||
|
||||
# Benutzer-Mindmap-Funktionalität
|
||||
@app.route('/my-mindmap/<int:mindmap_id>')
|
||||
@login_required
|
||||
@@ -903,45 +916,30 @@ def delete_note(note_id):
|
||||
@app.route('/api/mindmap')
|
||||
def get_mindmap():
|
||||
"""API-Endpunkt zur Bereitstellung der Mindmap-Daten in hierarchischer Form."""
|
||||
# Alle root-Nodes (ohne parent) abrufen
|
||||
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
|
||||
|
||||
if not root_nodes:
|
||||
# Wenn keine Nodes existieren, rufen wir initialize_database direkt auf
|
||||
# anstatt create_sample_mindmap zu verwenden
|
||||
with app.app_context():
|
||||
initialize_database()
|
||||
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
|
||||
|
||||
# Ergebnisse in hierarchischer Struktur zurückgeben
|
||||
# Root-Knoten: Knoten ohne Eltern
|
||||
root_nodes = MindMapNode.query.\
|
||||
outerjoin(node_relationship, MindMapNode.id == node_relationship.c.child_id).\
|
||||
filter(node_relationship.c.parent_id == None).all()
|
||||
|
||||
result = []
|
||||
|
||||
for node in root_nodes:
|
||||
node_data = build_node_tree(node)
|
||||
result.append(node_data)
|
||||
|
||||
return jsonify({"nodes": result})
|
||||
|
||||
def build_node_tree(node):
|
||||
"""Erzeugt eine hierarchische Darstellung eines Knotens inkl. seiner Kindknoten."""
|
||||
# Gedankenzähler abrufen von der many-to-many Beziehung
|
||||
thought_count = len(node.thoughts)
|
||||
|
||||
# Daten für aktuellen Knoten
|
||||
node_data = {
|
||||
"id": node.id,
|
||||
"name": node.name,
|
||||
"description": f"Knoten mit {thought_count} Gedanken",
|
||||
"description": node.description or "",
|
||||
"thought_count": thought_count,
|
||||
"children": []
|
||||
}
|
||||
|
||||
# Rekursiv Kinder hinzufügen
|
||||
child_nodes = MindMapNode.query.filter_by(parent_id=node.id).all()
|
||||
for child_node in child_nodes:
|
||||
child_data = build_node_tree(child_node)
|
||||
for child in node.children:
|
||||
child_data = build_node_tree(child)
|
||||
node_data["children"].append(child_data)
|
||||
|
||||
return node_data
|
||||
|
||||
@app.route('/api/nodes/<int:node_id>/thoughts')
|
||||
@@ -1227,8 +1225,17 @@ def chat_with_assistant():
|
||||
|
||||
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
|
||||
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
|
||||
"Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. "
|
||||
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
|
||||
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
|
||||
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
|
||||
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
|
||||
"Wichtige Funktionen sind:\n"
|
||||
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
|
||||
"- Kategorisierung und thematische Organisation\n"
|
||||
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
|
||||
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
|
||||
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
|
||||
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
|
||||
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
|
||||
"Antworte informativ, sachlich und gut strukturiert auf Deutsch.")
|
||||
|
||||
# Formatiere Nachrichten für OpenAI API
|
||||
@@ -1242,6 +1249,7 @@ def chat_with_assistant():
|
||||
# Alte Implementierung für direktes Prompt
|
||||
prompt = data.get('prompt', '')
|
||||
context = data.get('context', '')
|
||||
selected_items = data.get('selected_items', []) # Ausgewählte Elemente aus der Datenbank
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
@@ -1250,13 +1258,39 @@ def chat_with_assistant():
|
||||
|
||||
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
|
||||
system_message = (
|
||||
"Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. "
|
||||
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
|
||||
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
|
||||
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
|
||||
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
|
||||
"Wichtige Funktionen sind:\n"
|
||||
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
|
||||
"- Kategorisierung und thematische Organisation\n"
|
||||
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
|
||||
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
|
||||
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
|
||||
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
|
||||
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
|
||||
"Antworte informativ, sachlich und gut strukturiert auf Deutsch."
|
||||
)
|
||||
|
||||
if context:
|
||||
system_message += f"\n\nKontext: {context}"
|
||||
|
||||
if selected_items:
|
||||
system_message += "\n\nAusgewählte Elemente aus der Datenbank:\n"
|
||||
for item in selected_items:
|
||||
if 'type' in item and 'data' in item:
|
||||
if item['type'] == 'thought':
|
||||
system_message += f"- Gedanke: {item['data'].get('title', 'Unbekannter Titel')}\n"
|
||||
system_message += f" Zusammenfassung: {item['data'].get('abstract', 'Keine Zusammenfassung')}\n"
|
||||
system_message += f" Keywords: {item['data'].get('keywords', 'Keine Keywords')}\n"
|
||||
elif item['type'] == 'category':
|
||||
system_message += f"- Kategorie: {item['data'].get('name', 'Unbekannte Kategorie')}\n"
|
||||
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
|
||||
system_message += f" Unterkategorien: {item['data'].get('subcategories', 'Keine Unterkategorien')}\n"
|
||||
elif item['type'] == 'mindmap':
|
||||
system_message += f"- Mindmap: {item['data'].get('name', 'Unbekannte Mindmap')}\n"
|
||||
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
|
||||
system_message += f" Knoten: {item['data'].get('nodes', 'Keine Knoten')}\n"
|
||||
|
||||
api_messages = [
|
||||
{"role": "system", "content": system_message},
|
||||
@@ -1291,7 +1325,7 @@ def chat_with_assistant():
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=api_messages,
|
||||
max_tokens=600, # Erhöht für längere, detailliertere Antworten
|
||||
max_tokens=1000, # Erhöht für ausführlichere Antworten und detaillierte Führungen
|
||||
temperature=0.7,
|
||||
timeout=20 # 20 Sekunden Timeout
|
||||
)
|
||||
@@ -1432,7 +1466,7 @@ if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
# Make sure tables exist
|
||||
db.create_all()
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
socketio.run(app, debug=True, host='0.0.0.0')
|
||||
|
||||
@app.route('/api/refresh-mindmap')
|
||||
def refresh_mindmap():
|
||||
@@ -1479,4 +1513,23 @@ def refresh_mindmap():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Datenbankverbindung konnte nicht hergestellt werden'
|
||||
}), 500
|
||||
}), 500
|
||||
|
||||
|
||||
# Route zur Mindmap HTML-Seite
|
||||
@app.route('/mindmap')
|
||||
def mindmap_page():
|
||||
return render_template('mindmap.html')
|
||||
|
||||
# Fehlerbehandlung
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
return jsonify({'error': 'Nicht gefunden'}), 404
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request(e):
|
||||
return jsonify({'error': 'Fehlerhafte Anfrage'}), 400
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error(e):
|
||||
return jsonify({'error': 'Serverfehler'}), 500
|
||||
34
check_schema.py
Normal file
34
check_schema.py
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
|
||||
# Verbindung zur Datenbank herstellen
|
||||
conn = sqlite3.connect('systades.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Liste aller Tabellen abrufen
|
||||
print("Alle Tabellen in der Datenbank:")
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
for table in tables:
|
||||
print(f"- {table[0]}")
|
||||
|
||||
# Schema der Datenbank abrufen
|
||||
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table';")
|
||||
schemas = cursor.fetchall()
|
||||
|
||||
# Schematische Informationen ausgeben
|
||||
print("\nDatenbankschema:")
|
||||
for schema in schemas:
|
||||
print("\n" + str(schema[0]))
|
||||
|
||||
# Schema der User-Tabelle genauer untersuchen, falls vorhanden
|
||||
if ('user',) in tables:
|
||||
print("\n\nBenutzer-Tabellenschema:")
|
||||
cursor.execute("PRAGMA table_info(user);")
|
||||
user_columns = cursor.fetchall()
|
||||
for column in user_columns:
|
||||
print(f"Column: {column[1]}, Type: {column[2]}, NOT NULL: {column[3]}, Default: {column[4]}, Primary Key: {column[5]}")
|
||||
|
||||
conn.close()
|
||||
72
create_default_users.py
Normal file
72
create_default_users.py
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
from werkzeug.security import generate_password_hash
|
||||
from datetime import datetime
|
||||
|
||||
# Prüfen, ob die Datenbank existiert
|
||||
db_path = 'systades.db'
|
||||
if not os.path.exists(db_path):
|
||||
print(f"Datenbank {db_path} existiert nicht.")
|
||||
exit(1)
|
||||
|
||||
# Verbindung zur Datenbank herstellen
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Überprüfen, ob bereits Benutzer vorhanden sind
|
||||
cursor.execute("SELECT COUNT(*) FROM user;")
|
||||
user_count = cursor.fetchone()[0]
|
||||
|
||||
if user_count == 0:
|
||||
print("Keine Benutzer in der Datenbank gefunden. Erstelle Standardbenutzer...")
|
||||
|
||||
# Standardbenutzer definieren
|
||||
default_users = [
|
||||
{
|
||||
'username': 'admin',
|
||||
'email': 'admin@example.com',
|
||||
'password': generate_password_hash('admin'),
|
||||
'created_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'is_active': 1,
|
||||
'role': 'admin'
|
||||
},
|
||||
{
|
||||
'username': 'user',
|
||||
'email': 'user@example.com',
|
||||
'password': generate_password_hash('user'),
|
||||
'created_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'is_active': 1,
|
||||
'role': 'user'
|
||||
}
|
||||
]
|
||||
|
||||
# Benutzer einfügen
|
||||
for user in default_users:
|
||||
cursor.execute("""
|
||||
INSERT INTO user (username, email, password, created_at, is_active, role)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
""", (
|
||||
user['username'],
|
||||
user['email'],
|
||||
user['password'],
|
||||
user['created_at'],
|
||||
user['is_active'],
|
||||
user['role']
|
||||
))
|
||||
conn.commit()
|
||||
print(f"{len(default_users)} Standardbenutzer wurden erstellt.")
|
||||
else:
|
||||
print(f"Es sind bereits {user_count} Benutzer in der Datenbank vorhanden.")
|
||||
|
||||
# Überprüfen der eingefügten Benutzer
|
||||
print("\nBenutzer in der Datenbank:")
|
||||
cursor.execute("SELECT id, username, email, role FROM user;")
|
||||
users = cursor.fetchall()
|
||||
for user in users:
|
||||
print(f"ID: {user[0]}, Benutzername: {user[1]}, E-Mail: {user[2]}, Rolle: {user[3]}")
|
||||
|
||||
conn.close()
|
||||
print("\nBenutzeraktualisierung abgeschlossen.")
|
||||
BIN
database/__pycache__/models.cpython-313.pyc
Normal file
BIN
database/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
70
fix_user_table.py
Normal file
70
fix_user_table.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
# Prüfen, ob die Datenbank existiert
|
||||
db_path = 'systades.db'
|
||||
if not os.path.exists(db_path):
|
||||
print(f"Datenbank {db_path} existiert nicht.")
|
||||
exit(1)
|
||||
|
||||
# Verbindung zur Datenbank herstellen
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Prüfen, ob die User-Tabelle existiert
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user';")
|
||||
if not cursor.fetchone():
|
||||
print("Die Tabelle 'user' existiert nicht. Erstelle neue Tabelle...")
|
||||
cursor.execute('''
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username VARCHAR(80) NOT NULL UNIQUE,
|
||||
email VARCHAR(120) NOT NULL UNIQUE,
|
||||
password VARCHAR(512) NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
role VARCHAR(20) DEFAULT 'user'
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
print("Die Tabelle 'user' wurde erfolgreich erstellt.")
|
||||
else:
|
||||
# Überprüfen, ob die Spalte 'password' existiert
|
||||
cursor.execute("PRAGMA table_info(user);")
|
||||
columns = cursor.fetchall()
|
||||
column_names = [column[1] for column in columns]
|
||||
|
||||
if 'password' not in column_names:
|
||||
print("Die Spalte 'password' fehlt in der Tabelle 'user'. Füge Spalte hinzu...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN password VARCHAR(512);")
|
||||
conn.commit()
|
||||
print("Die Spalte 'password' wurde erfolgreich hinzugefügt.")
|
||||
else:
|
||||
print("Die Spalte 'password' existiert bereits in der Tabelle 'user'.")
|
||||
|
||||
# Überprüfen der aktualisierten Spaltenstruktur
|
||||
print("\nAktualisierte Tabellenspalten der 'user'-Tabelle:")
|
||||
cursor.execute("PRAGMA table_info(user);")
|
||||
updated_columns = cursor.fetchall()
|
||||
for column in updated_columns:
|
||||
print(f"Column: {column[1]}, Type: {column[2]}, NOT NULL: {column[3]}, Default: {column[4]}, Primary Key: {column[5]}")
|
||||
|
||||
# Datenbanktabellen anzeigen
|
||||
print("\nAlle Tabellen in der Datenbank:")
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||
tables = cursor.fetchall()
|
||||
for table in tables:
|
||||
print(f"- {table[0]}")
|
||||
|
||||
# Schemaüberprüfung der user-Tabelle
|
||||
print("\nSchema der 'user'-Tabelle:")
|
||||
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='user';")
|
||||
schema = cursor.fetchone()
|
||||
if schema:
|
||||
print(schema[0])
|
||||
|
||||
conn.close()
|
||||
print("\nDatenbankaktualisierung abgeschlossen.")
|
||||
470
init_db.py
Executable file → Normal file
470
init_db.py
Executable file → Normal file
@@ -5,252 +5,240 @@ from app import app, initialize_database, db_path
|
||||
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
|
||||
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
|
||||
import os
|
||||
import sqlite3
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from datetime import datetime
|
||||
|
||||
def init_database():
|
||||
"""Initialisiert die Datenbank mit Beispieldaten."""
|
||||
with app.app_context():
|
||||
# Datenbank löschen und neu erstellen
|
||||
if os.path.exists(db_path):
|
||||
os.remove(db_path)
|
||||
|
||||
# Stellen Sie sicher, dass das Verzeichnis existiert
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
db.create_all()
|
||||
|
||||
# Admin-Benutzer erstellen
|
||||
admin = User(username='admin', email='admin@example.com', is_admin=True)
|
||||
admin.set_password('admin')
|
||||
db.session.add(admin)
|
||||
|
||||
# Beispiel-Benutzer erstellen
|
||||
user = User(username='user', email='user@example.com')
|
||||
user.set_password('user')
|
||||
db.session.add(user)
|
||||
|
||||
# Commit, um IDs zu generieren
|
||||
db.session.commit()
|
||||
|
||||
# Wissenschaftliche Kategorien erstellen
|
||||
science = Category(name='Wissenschaft', description='Wissenschaftliche Erkenntnisse',
|
||||
color_code='#4CAF50', icon='flask')
|
||||
db.session.add(science)
|
||||
|
||||
philosophy = Category(name='Philosophie', description='Philosophische Theorien und Gedanken',
|
||||
color_code='#9C27B0', icon='lightbulb')
|
||||
db.session.add(philosophy)
|
||||
|
||||
technology = Category(name='Technologie', description='Technologische Entwicklungen',
|
||||
color_code='#FF9800', icon='microchip')
|
||||
db.session.add(technology)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Wissenschaftliche Unterkategorien
|
||||
physics = Category(name='Physik', description='Studium der Materie und Energie',
|
||||
color_code='#81C784', icon='atom', parent_id=science.id)
|
||||
biology = Category(name='Biologie', description='Studium lebender Organismen',
|
||||
color_code='#66BB6A', icon='leaf', parent_id=science.id)
|
||||
chemistry = Category(name='Chemie', description='Studium der Stoffe und ihrer Reaktionen',
|
||||
color_code='#A5D6A7', icon='vial', parent_id=science.id)
|
||||
|
||||
db.session.add_all([physics, biology, chemistry])
|
||||
|
||||
# Technologie-Unterkategorien
|
||||
informatics = Category(name='Informatik', description='Studium der Informationsverarbeitung',
|
||||
color_code='#FFB74D', icon='laptop-code', parent_id=technology.id)
|
||||
ai = Category(name='Künstliche Intelligenz', description='Entwicklung intelligenter Systeme',
|
||||
color_code='#FFA726', icon='robot', parent_id=technology.id)
|
||||
|
||||
db.session.add_all([informatics, ai])
|
||||
|
||||
# Philosophie-Unterkategorien
|
||||
ethics = Category(name='Ethik', description='Moralphilosophie und Wertesysteme',
|
||||
color_code='#BA68C8', icon='balance-scale', parent_id=philosophy.id)
|
||||
logic = Category(name='Logik', description='Studie der gültigen Schlussfolgerungen',
|
||||
color_code='#AB47BC', icon='project-diagram', parent_id=philosophy.id)
|
||||
|
||||
db.session.add_all([ethics, logic])
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Knoten für die öffentliche Mindmap erstellen
|
||||
nodes = {
|
||||
'quantenmechanik': MindMapNode(
|
||||
name='Quantenmechanik',
|
||||
description='Physikalische Theorie zur Beschreibung der Materie auf atomarer Ebene',
|
||||
color_code='#81C784',
|
||||
category_id=physics.id,
|
||||
created_by_id=admin.id
|
||||
),
|
||||
'relativitaetstheorie': MindMapNode(
|
||||
name='Relativitätstheorie',
|
||||
description='Einsteins Theorien zur Raumzeit und Gravitation',
|
||||
color_code='#81C784',
|
||||
category_id=physics.id,
|
||||
created_by_id=admin.id
|
||||
),
|
||||
'genetik': MindMapNode(
|
||||
name='Genetik',
|
||||
description='Wissenschaft der Gene und Vererbung',
|
||||
color_code='#66BB6A',
|
||||
category_id=biology.id,
|
||||
created_by_id=admin.id
|
||||
),
|
||||
'machine_learning': MindMapNode(
|
||||
name='Machine Learning',
|
||||
description='Algorithmen, die aus Daten lernen können',
|
||||
color_code='#FFA726',
|
||||
category_id=ai.id,
|
||||
created_by_id=admin.id
|
||||
),
|
||||
'ki_ethik': MindMapNode(
|
||||
name='KI-Ethik',
|
||||
description='Moralische Implikationen künstlicher Intelligenz',
|
||||
color_code='#BA68C8',
|
||||
category_id=ethics.id,
|
||||
created_by_id=user.id
|
||||
)
|
||||
}
|
||||
|
||||
for node in nodes.values():
|
||||
db.session.add(node)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Verknüpfungen zwischen Knoten herstellen (Hierarchie)
|
||||
nodes['machine_learning'].parents.append(nodes['ki_ethik'])
|
||||
db.session.commit()
|
||||
|
||||
# Gedanken erstellen
|
||||
thoughts = [
|
||||
{
|
||||
'title': 'Künstliche Intelligenz und Bewusstsein',
|
||||
'content': 'Die Frage nach maschinellem Bewusstsein ist fundamental für die KI-Ethik. Aktuelle KI-Systeme haben kein Bewusstsein, aber fortschrittliche KI könnte in Zukunft Eigenschaften entwickeln, die diesem nahekommen.',
|
||||
'abstract': 'Eine Untersuchung der philosophischen Implikationen von KI-Bewusstsein.',
|
||||
'keywords': 'KI, Bewusstsein, Ethik, Philosophie',
|
||||
'branch': 'Philosophie',
|
||||
'color_code': '#BA68C8',
|
||||
'source_type': 'Markdown',
|
||||
'user_id': user.id,
|
||||
'node': nodes['ki_ethik']
|
||||
},
|
||||
{
|
||||
'title': 'Quantenmechanik und Realität',
|
||||
'content': 'Die Kopenhagener Deutung und ihre Auswirkungen auf unser Verständnis der Realität. Quantenmechanik stellt grundlegende Annahmen über Determinismus und Lokalität in Frage.',
|
||||
'abstract': 'Eine Analyse verschiedener Interpretationen der Quantenmechanik.',
|
||||
'keywords': 'Quantenmechanik, Physik, Realität',
|
||||
'branch': 'Physik',
|
||||
'color_code': '#81C784',
|
||||
'source_type': 'PDF',
|
||||
'user_id': admin.id,
|
||||
'node': nodes['quantenmechanik']
|
||||
},
|
||||
{
|
||||
'title': 'Deep Learning Fortschritte',
|
||||
'content': 'Die neuesten Fortschritte im Deep Learning haben zu beeindruckenden Ergebnissen in Bereichen wie Computer Vision, Natural Language Processing und Reinforcement Learning geführt.',
|
||||
'abstract': 'Überblick über aktuelle Deep Learning-Techniken und ihre Anwendungen.',
|
||||
'keywords': 'Deep Learning, Neural Networks, AI',
|
||||
'branch': 'Technologie',
|
||||
'color_code': '#FFA726',
|
||||
'source_type': 'Webpage',
|
||||
'user_id': admin.id,
|
||||
'node': nodes['machine_learning']
|
||||
}
|
||||
]
|
||||
|
||||
thought_objects = []
|
||||
for t_data in thoughts:
|
||||
node = t_data.pop('node')
|
||||
thought = Thought(**t_data)
|
||||
node.thoughts.append(thought)
|
||||
thought_objects.append(thought)
|
||||
db.session.add(thought)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Beziehungen zwischen Gedanken
|
||||
relation = ThoughtRelation(
|
||||
source_id=thought_objects[0].id,
|
||||
target_id=thought_objects[2].id,
|
||||
relation_type=RelationType.INSPIRES,
|
||||
created_by_id=user.id
|
||||
)
|
||||
db.session.add(relation)
|
||||
|
||||
# Bewertungen erstellen
|
||||
rating1 = ThoughtRating(
|
||||
thought_id=thought_objects[0].id,
|
||||
user_id=admin.id,
|
||||
relevance_score=5
|
||||
)
|
||||
rating2 = ThoughtRating(
|
||||
thought_id=thought_objects[2].id,
|
||||
user_id=user.id,
|
||||
relevance_score=4
|
||||
)
|
||||
db.session.add_all([rating1, rating2])
|
||||
|
||||
# Kommentare erstellen
|
||||
for thought in thought_objects:
|
||||
comment = Comment(
|
||||
content=f'Interessante Perspektive zu {thought.title}!',
|
||||
thought_id=thought.id,
|
||||
user_id=admin.id if thought.user_id != admin.id else user.id
|
||||
)
|
||||
db.session.add(comment)
|
||||
|
||||
# Benutzer-Mindmaps erstellen
|
||||
user_mindmap = UserMindmap(
|
||||
name='Meine KI-Forschung',
|
||||
description='Meine persönliche Sammlung zu KI und Ethik',
|
||||
user_id=user.id
|
||||
)
|
||||
db.session.add(user_mindmap)
|
||||
db.session.commit()
|
||||
|
||||
# Knoten zur Benutzer-Mindmap hinzufügen
|
||||
user_mindmap_nodes = [
|
||||
UserMindmapNode(
|
||||
user_mindmap_id=user_mindmap.id,
|
||||
node_id=nodes['machine_learning'].id,
|
||||
x_position=200,
|
||||
y_position=300
|
||||
),
|
||||
UserMindmapNode(
|
||||
user_mindmap_id=user_mindmap.id,
|
||||
node_id=nodes['ki_ethik'].id,
|
||||
x_position=500,
|
||||
y_position=200
|
||||
)
|
||||
]
|
||||
db.session.add_all(user_mindmap_nodes)
|
||||
|
||||
# Private Notizen
|
||||
note = MindmapNote(
|
||||
user_id=user.id,
|
||||
mindmap_id=user_mindmap.id,
|
||||
node_id=nodes['ki_ethik'].id,
|
||||
content="Recherchiere mehr über aktuelle ethische Richtlinien für KI-Entwicklung!",
|
||||
color_code="#FFF59D"
|
||||
)
|
||||
db.session.add(note)
|
||||
|
||||
# Gedanken zu Bookmarks hinzufügen
|
||||
user.bookmarked_thoughts.append(thought_objects[0])
|
||||
admin.bookmarked_thoughts.append(thought_objects[1])
|
||||
|
||||
# Finaler Commit
|
||||
db.session.commit()
|
||||
|
||||
print("Datenbank wurde erfolgreich initialisiert!")
|
||||
# Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///systades.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
db.init_app(app)
|
||||
|
||||
def init_db():
|
||||
"""Alias für Kompatibilität mit älteren Scripts."""
|
||||
init_database()
|
||||
with app.app_context():
|
||||
print("Initialisiere Datenbank...")
|
||||
|
||||
# Tabellen erstellen
|
||||
db.create_all()
|
||||
print("Tabellen wurden erstellt.")
|
||||
|
||||
# Standardbenutzer erstellen, falls keine vorhanden sind
|
||||
if User.query.count() == 0:
|
||||
print("Erstelle Standardbenutzer...")
|
||||
create_default_users()
|
||||
|
||||
# Standardkategorien erstellen, falls keine vorhanden sind
|
||||
if Category.query.count() == 0:
|
||||
print("Erstelle Standardkategorien...")
|
||||
create_default_categories()
|
||||
|
||||
# Beispiel-Mindmap erstellen, falls keine Knoten vorhanden sind
|
||||
if MindMapNode.query.count() == 0:
|
||||
print("Erstelle Beispiel-Mindmap...")
|
||||
create_sample_mindmap()
|
||||
|
||||
print("Datenbankinitialisierung abgeschlossen.")
|
||||
|
||||
def create_default_users():
|
||||
"""Erstellt Standardbenutzer für die Anwendung"""
|
||||
users = [
|
||||
{
|
||||
'username': 'admin',
|
||||
'email': 'admin@example.com',
|
||||
'password': 'admin',
|
||||
'role': 'admin'
|
||||
},
|
||||
{
|
||||
'username': 'user',
|
||||
'email': 'user@example.com',
|
||||
'password': 'user',
|
||||
'role': 'user'
|
||||
}
|
||||
]
|
||||
|
||||
for user_data in users:
|
||||
password = user_data.pop('password')
|
||||
user = User(**user_data)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
|
||||
db.session.commit()
|
||||
print(f"{len(users)} Benutzer wurden erstellt.")
|
||||
|
||||
def create_default_categories():
|
||||
"""Erstellt die Standardkategorien für die Mindmap"""
|
||||
categories = [
|
||||
{
|
||||
'name': 'Konzept',
|
||||
'description': 'Abstrakte Ideen und theoretische Konzepte',
|
||||
'color_code': '#6366f1',
|
||||
'icon': 'lightbulb'
|
||||
},
|
||||
{
|
||||
'name': 'Technologie',
|
||||
'description': 'Hardware, Software, Tools und Plattformen',
|
||||
'color_code': '#10b981',
|
||||
'icon': 'cpu'
|
||||
},
|
||||
{
|
||||
'name': 'Prozess',
|
||||
'description': 'Workflows, Methodologien und Vorgehensweisen',
|
||||
'color_code': '#f59e0b',
|
||||
'icon': 'git-branch'
|
||||
},
|
||||
{
|
||||
'name': 'Person',
|
||||
'description': 'Personen, Teams und Organisationen',
|
||||
'color_code': '#ec4899',
|
||||
'icon': 'user'
|
||||
},
|
||||
{
|
||||
'name': 'Dokument',
|
||||
'description': 'Dokumentationen, Referenzen und Ressourcen',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'file-text'
|
||||
}
|
||||
]
|
||||
|
||||
for cat_data in categories:
|
||||
category = Category(**cat_data)
|
||||
db.session.add(category)
|
||||
|
||||
db.session.commit()
|
||||
print(f"{len(categories)} Kategorien wurden erstellt.")
|
||||
|
||||
def create_sample_mindmap():
|
||||
"""Erstellt eine Beispiel-Mindmap mit Knoten und Beziehungen"""
|
||||
|
||||
# Kategorien für die Zuordnung
|
||||
categories = Category.query.all()
|
||||
category_map = {cat.name: cat for cat in categories}
|
||||
|
||||
# Beispielknoten erstellen
|
||||
nodes = [
|
||||
{
|
||||
'name': 'Wissensmanagement',
|
||||
'description': 'Systematische Erfassung, Speicherung und Nutzung von Wissen in Organisationen.',
|
||||
'color_code': '#6366f1',
|
||||
'icon': 'database',
|
||||
'category': category_map.get('Konzept'),
|
||||
'x': 0,
|
||||
'y': 0
|
||||
},
|
||||
{
|
||||
'name': 'Mind-Mapping',
|
||||
'description': 'Technik zur visuellen Darstellung von Informationen und Zusammenhängen.',
|
||||
'color_code': '#10b981',
|
||||
'icon': 'git-branch',
|
||||
'category': category_map.get('Prozess'),
|
||||
'x': 200,
|
||||
'y': -150
|
||||
},
|
||||
{
|
||||
'name': 'Cytoscape.js',
|
||||
'description': 'JavaScript-Bibliothek für die Visualisierung und Manipulation von Graphen.',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'code',
|
||||
'category': category_map.get('Technologie'),
|
||||
'x': 350,
|
||||
'y': -50
|
||||
},
|
||||
{
|
||||
'name': 'Socket.IO',
|
||||
'description': 'Bibliothek für Echtzeit-Kommunikation zwischen Client und Server.',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'zap',
|
||||
'category': category_map.get('Technologie'),
|
||||
'x': 350,
|
||||
'y': 100
|
||||
},
|
||||
{
|
||||
'name': 'Kollaboration',
|
||||
'description': 'Zusammenarbeit mehrerer Benutzer an gemeinsamen Inhalten.',
|
||||
'color_code': '#f59e0b',
|
||||
'icon': 'users',
|
||||
'category': category_map.get('Prozess'),
|
||||
'x': 200,
|
||||
'y': 150
|
||||
},
|
||||
{
|
||||
'name': 'SQLite',
|
||||
'description': 'Leichtgewichtige relationale Datenbank, die ohne Server-Prozess auskommt.',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'database',
|
||||
'category': category_map.get('Technologie'),
|
||||
'x': 0,
|
||||
'y': 200
|
||||
},
|
||||
{
|
||||
'name': 'Flask',
|
||||
'description': 'Leichtgewichtiges Python-Webframework für die Entwicklung von Webanwendungen.',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'server',
|
||||
'category': category_map.get('Technologie'),
|
||||
'x': -200,
|
||||
'y': 150
|
||||
},
|
||||
{
|
||||
'name': 'REST API',
|
||||
'description': 'Architekturstil für verteilte Systeme, insbesondere Webanwendungen.',
|
||||
'color_code': '#10b981',
|
||||
'icon': 'link',
|
||||
'category': category_map.get('Konzept'),
|
||||
'x': -200,
|
||||
'y': -150
|
||||
},
|
||||
{
|
||||
'name': 'Dokumentation',
|
||||
'description': 'Strukturierte Erfassung und Beschreibung von Informationen und Prozessen.',
|
||||
'color_code': '#ec4899',
|
||||
'icon': 'file-text',
|
||||
'category': category_map.get('Dokument'),
|
||||
'x': -350,
|
||||
'y': 0
|
||||
}
|
||||
]
|
||||
|
||||
# Knoten in die Datenbank einfügen
|
||||
node_objects = {}
|
||||
for node_data in nodes:
|
||||
category = node_data.pop('category', None)
|
||||
x = node_data.pop('x', 0)
|
||||
y = node_data.pop('y', 0)
|
||||
node = MindMapNode(**node_data)
|
||||
if category:
|
||||
node.category_id = category.id
|
||||
db.session.add(node)
|
||||
db.session.flush() # Generiert IDs für neue Objekte
|
||||
node_objects[node.name] = node
|
||||
|
||||
# Beziehungen erstellen
|
||||
relationships = [
|
||||
('Wissensmanagement', 'Mind-Mapping'),
|
||||
('Wissensmanagement', 'Kollaboration'),
|
||||
('Wissensmanagement', 'Dokumentation'),
|
||||
('Mind-Mapping', 'Cytoscape.js'),
|
||||
('Kollaboration', 'Socket.IO'),
|
||||
('Wissensmanagement', 'SQLite'),
|
||||
('SQLite', 'Flask'),
|
||||
('Flask', 'REST API'),
|
||||
('REST API', 'Socket.IO'),
|
||||
('REST API', 'Dokumentation')
|
||||
]
|
||||
|
||||
for parent_name, child_name in relationships:
|
||||
parent = node_objects.get(parent_name)
|
||||
child = node_objects.get(child_name)
|
||||
if parent and child:
|
||||
parent.children.append(child)
|
||||
|
||||
db.session.commit()
|
||||
print(f"{len(nodes)} Knoten und {len(relationships)} Beziehungen wurden erstellt.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_database()
|
||||
init_db()
|
||||
print("Datenbank wurde erfolgreich initialisiert!")
|
||||
print("Sie können die Anwendung jetzt mit 'python app.py' starten")
|
||||
print("Anmelden mit:")
|
||||
|
||||
1
migrations/README
Normal file
1
migrations/README
Normal file
@@ -0,0 +1 @@
|
||||
Single-database configuration for Flask.
|
||||
BIN
migrations/__pycache__/env.cpython-311.pyc
Normal file
BIN
migrations/__pycache__/env.cpython-311.pyc
Normal file
Binary file not shown.
50
migrations/alembic.ini
Normal file
50
migrations/alembic.ini
Normal file
@@ -0,0 +1,50 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic,flask_migrate
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[logger_flask_migrate]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = flask_migrate
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
113
migrations/env.py
Normal file
113
migrations/env.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import logging
|
||||
from logging.config import fileConfig
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from alembic import context
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
logger = logging.getLogger('alembic.env')
|
||||
|
||||
|
||||
def get_engine():
|
||||
try:
|
||||
# this works with Flask-SQLAlchemy<3 and Alchemical
|
||||
return current_app.extensions['migrate'].db.get_engine()
|
||||
except (TypeError, AttributeError):
|
||||
# this works with Flask-SQLAlchemy>=3
|
||||
return current_app.extensions['migrate'].db.engine
|
||||
|
||||
|
||||
def get_engine_url():
|
||||
try:
|
||||
return get_engine().url.render_as_string(hide_password=False).replace(
|
||||
'%', '%%')
|
||||
except AttributeError:
|
||||
return str(get_engine().url).replace('%', '%%')
|
||||
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
config.set_main_option('sqlalchemy.url', get_engine_url())
|
||||
target_db = current_app.extensions['migrate'].db
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def get_metadata():
|
||||
if hasattr(target_db, 'metadatas'):
|
||||
return target_db.metadatas[None]
|
||||
return target_db.metadata
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url, target_metadata=get_metadata(), literal_binds=True
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online():
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
|
||||
# this callback is used to prevent an auto-migration from being generated
|
||||
# when there are no changes to the schema
|
||||
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
|
||||
def process_revision_directives(context, revision, directives):
|
||||
if getattr(config.cmd_opts, 'autogenerate', False):
|
||||
script = directives[0]
|
||||
if script.upgrade_ops.is_empty():
|
||||
directives[:] = []
|
||||
logger.info('No changes in schema detected.')
|
||||
|
||||
conf_args = current_app.extensions['migrate'].configure_args
|
||||
if conf_args.get("process_revision_directives") is None:
|
||||
conf_args["process_revision_directives"] = process_revision_directives
|
||||
|
||||
connectable = get_engine()
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=get_metadata(),
|
||||
**conf_args
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
24
migrations/script.py.mako
Normal file
24
migrations/script.py.mako
Normal file
@@ -0,0 +1,24 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = ${repr(up_revision)}
|
||||
down_revision = ${repr(down_revision)}
|
||||
branch_labels = ${repr(branch_labels)}
|
||||
depends_on = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade():
|
||||
${downgrades if downgrades else "pass"}
|
||||
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
"""Add password column to user
|
||||
|
||||
Revision ID: d4406f5b12f7
|
||||
Revises:
|
||||
Create Date: 2025-04-28 21:26:37.430823
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd4406f5b12f7'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('password', sa.String(length=512), nullable=False, server_default="changeme"))
|
||||
batch_op.add_column(sa.Column('is_active', sa.Boolean(), nullable=True))
|
||||
batch_op.add_column(sa.Column('role', sa.String(length=20), nullable=True))
|
||||
batch_op.drop_column('last_login')
|
||||
batch_op.drop_column('bio')
|
||||
batch_op.drop_column('password_hash')
|
||||
batch_op.drop_column('is_admin')
|
||||
batch_op.drop_column('avatar')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('avatar', sa.VARCHAR(length=200), nullable=True))
|
||||
batch_op.add_column(sa.Column('is_admin', sa.BOOLEAN(), nullable=True))
|
||||
batch_op.add_column(sa.Column('password_hash', sa.VARCHAR(length=128), nullable=True))
|
||||
batch_op.add_column(sa.Column('bio', sa.TEXT(), nullable=True))
|
||||
batch_op.add_column(sa.Column('last_login', sa.DATETIME(), nullable=True))
|
||||
batch_op.drop_column('role')
|
||||
batch_op.drop_column('is_active')
|
||||
batch_op.drop_column('password')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
112
models.py
Executable file → Normal file
112
models.py
Executable file → Normal file
@@ -6,6 +6,8 @@ from flask_login import UserMixin
|
||||
from datetime import datetime
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from enum import Enum
|
||||
import uuid as uuid_pkg
|
||||
import os
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
@@ -43,30 +45,28 @@ user_thought_bookmark = db.Table('user_thought_bookmark',
|
||||
db.Column('created_at', db.DateTime, default=datetime.utcnow)
|
||||
)
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
class User(db.Model, UserMixin):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128))
|
||||
is_admin = db.Column(db.Boolean, default=False)
|
||||
password = db.Column(db.String(512), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
last_login = db.Column(db.DateTime)
|
||||
avatar = db.Column(db.String(200))
|
||||
bio = db.Column(db.Text)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
role = db.Column(db.String(20), default="user") # 'user', 'admin', 'moderator'
|
||||
|
||||
# Beziehungen
|
||||
thoughts = db.relationship('Thought', backref='author', lazy=True)
|
||||
comments = db.relationship('Comment', backref='author', lazy=True)
|
||||
user_mindmaps = db.relationship('UserMindmap', backref='user', lazy=True)
|
||||
mindmap_notes = db.relationship('MindmapNote', backref='user', lazy=True)
|
||||
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
|
||||
backref=db.backref('bookmarked_by', lazy='dynamic'))
|
||||
# Relationships
|
||||
threads = db.relationship('Thread', backref='creator', lazy=True)
|
||||
messages = db.relationship('Message', backref='author', lazy=True)
|
||||
projects = db.relationship('Project', backref='owner', lazy=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
self.password = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
return check_password_hash(self.password, password)
|
||||
|
||||
class Category(db.Model):
|
||||
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
|
||||
@@ -81,6 +81,9 @@ class Category(db.Model):
|
||||
children = db.relationship('Category', backref=db.backref('parent', remote_side=[id]))
|
||||
nodes = db.relationship('MindMapNode', backref='category', lazy=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Category {self.name}>'
|
||||
|
||||
class MindMapNode(db.Model):
|
||||
"""Öffentliche Mindmap-Knoten, die für alle Benutzer sichtbar sind"""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@@ -92,7 +95,9 @@ class MindMapNode(db.Model):
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
||||
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
|
||||
|
||||
|
||||
__table_args__ = {'extend_existing': True}
|
||||
|
||||
# Beziehungen für Baumstruktur (mehrere Eltern möglich)
|
||||
parents = db.relationship(
|
||||
'MindMapNode',
|
||||
@@ -111,6 +116,20 @@ class MindMapNode(db.Model):
|
||||
# Beziehung zum Ersteller
|
||||
created_by = db.relationship('User', backref='created_nodes')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<MindMapNode {self.name}>'
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'color_code': self.color_code,
|
||||
'icon': self.icon,
|
||||
'category_id': self.category_id,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None
|
||||
}
|
||||
|
||||
class UserMindmap(db.Model):
|
||||
"""Benutzerspezifische Mindmap, die vom Benutzer personalisierbar ist"""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@@ -227,4 +246,63 @@ class Comment(db.Model):
|
||||
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Thread model
|
||||
class Thread(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
# Relationships
|
||||
messages = db.relationship('Message', backref='thread', lazy=True, cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Thread {self.title}>'
|
||||
|
||||
# Message model
|
||||
class Message(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
thread_id = db.Column(db.Integer, db.ForeignKey('thread.id'), nullable=False)
|
||||
role = db.Column(db.String(20), default="user") # 'user', 'assistant', 'system'
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Message {self.id} by {self.user_id}>'
|
||||
|
||||
# Project model
|
||||
class Project(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(150), nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
|
||||
|
||||
# Relationships
|
||||
documents = db.relationship('Document', backref='project', lazy=True, cascade="all, delete-orphan")
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Project {self.title}>'
|
||||
|
||||
# Document model
|
||||
class Document(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(150), nullable=False)
|
||||
content = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False)
|
||||
filename = db.Column(db.String(150), nullable=True)
|
||||
file_path = db.Column(db.String(300), nullable=True)
|
||||
file_type = db.Column(db.String(50), nullable=True)
|
||||
file_size = db.Column(db.Integer, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Document {self.title}>'
|
||||
@@ -5,10 +5,13 @@ email-validator
|
||||
python-dotenv
|
||||
werkzeug==2.2.3
|
||||
flask-sqlalchemy==3.0.5
|
||||
openai==1.15.0
|
||||
openai
|
||||
requests==2.31.0
|
||||
flask-cors==4.0.0
|
||||
gunicorn==21.2.0
|
||||
#pillow==10.0.1
|
||||
pytest==7.4.0
|
||||
pytest-flask==1.2.0
|
||||
pytest-flask==1.2.0
|
||||
Flask-Migrate
|
||||
flask-socketio==5.3.6
|
||||
python-engineio==4.8.2
|
||||
python-socketio==5.11.1
|
||||
@@ -35,6 +35,21 @@
|
||||
--transition-fast: 150ms ease-in-out;
|
||||
--transition-normal: 300ms ease-in-out;
|
||||
--transition-slow: 500ms ease-in-out;
|
||||
|
||||
/* Light mode optimierte Farben */
|
||||
--light-bg: #f9fafb;
|
||||
--light-text: #1e293b;
|
||||
--light-heading: #0f172a;
|
||||
--light-primary: #3b82f6;
|
||||
--light-primary-hover: #4f46e5;
|
||||
--light-secondary: #6b7280;
|
||||
--light-border: #e5e7eb;
|
||||
--light-card-bg: rgba(255, 255, 255, 0.92);
|
||||
--light-navbar-bg: rgba(255, 255, 255, 0.92);
|
||||
--light-input-bg: #ffffff;
|
||||
--light-input-border: #d1d5db;
|
||||
--light-input-focus: #3b82f6;
|
||||
--light-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
@@ -60,9 +75,9 @@ html.dark body {
|
||||
}
|
||||
|
||||
/* Light Mode */
|
||||
body {
|
||||
background-color: var(--bg-primary-light);
|
||||
color: var(--text-primary-light);
|
||||
body:not(.dark) {
|
||||
background-color: var(--light-bg);
|
||||
color: var(--light-text);
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
@@ -418,4 +433,94 @@ html.dark .mystical-dot {
|
||||
|
||||
html.dark :focus-visible {
|
||||
outline-color: var(--accent-primary-dark);
|
||||
}
|
||||
|
||||
/* Light Mode Überschriften */
|
||||
body:not(.dark) h1,
|
||||
body:not(.dark) h2,
|
||||
body:not(.dark) h3,
|
||||
body:not(.dark) h4,
|
||||
body:not(.dark) h5,
|
||||
body:not(.dark) h6 {
|
||||
color: var(--light-heading);
|
||||
}
|
||||
|
||||
/* Light Mode Links */
|
||||
body:not(.dark) a {
|
||||
color: var(--light-primary);
|
||||
}
|
||||
|
||||
body:not(.dark) a:hover {
|
||||
color: var(--light-primary-hover);
|
||||
}
|
||||
|
||||
/* Light Mode Buttons */
|
||||
body:not(.dark) .btn,
|
||||
body:not(.dark) button:not(.toggle) {
|
||||
background-color: var(--light-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: var(--light-shadow);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn:hover,
|
||||
body:not(.dark) button:not(.toggle):hover {
|
||||
background-color: var(--light-primary-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Light Mode Cards und Panels */
|
||||
body:not(.dark) .card,
|
||||
body:not(.dark) .panel {
|
||||
background-color: var(--light-card-bg);
|
||||
border: 1px solid var(--light-border);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--light-shadow);
|
||||
}
|
||||
|
||||
/* Light Mode Tabelle */
|
||||
body:not(.dark) table {
|
||||
background-color: var(--light-card-bg);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
body:not(.dark) th {
|
||||
background-color: var(--light-bg);
|
||||
color: var(--light-heading);
|
||||
border-bottom: 1px solid var(--light-border);
|
||||
}
|
||||
|
||||
body:not(.dark) td {
|
||||
border-bottom: 1px solid var(--light-border);
|
||||
}
|
||||
|
||||
/* Light Mode Inputs */
|
||||
body:not(.dark) input,
|
||||
body:not(.dark) textarea,
|
||||
body:not(.dark) select {
|
||||
background-color: var(--light-input-bg);
|
||||
border: 1px solid var(--light-input-border);
|
||||
color: var(--light-text);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
body:not(.dark) input:focus,
|
||||
body:not(.dark) textarea:focus,
|
||||
body:not(.dark) select:focus {
|
||||
border-color: var(--light-input-focus);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Navbar im Light Mode verbessern */
|
||||
body:not(.dark) nav,
|
||||
body:not(.dark) .navbar {
|
||||
background-color: var(--light-navbar-bg);
|
||||
box-shadow: var(--light-shadow);
|
||||
border-bottom: 1px solid var(--light-border);
|
||||
}
|
||||
@@ -33,15 +33,74 @@ html.dark, html {
|
||||
backdrop-filter: blur(5px) !important;
|
||||
}
|
||||
|
||||
/* Dark Mode - Navbar */
|
||||
body.dark .glass-navbar-dark {
|
||||
background-color: rgba(10, 14, 25, 0.7) !important;
|
||||
}
|
||||
|
||||
/* Light Mode - Verbesserter Navbar */
|
||||
body .glass-navbar-light {
|
||||
background-color: rgba(255, 255, 255, 0.7) !important;
|
||||
background-color: rgba(255, 255, 255, 0.92) !important;
|
||||
backdrop-filter: blur(10px) !important;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||
border-bottom: 1px solid rgba(220, 220, 220, 0.5) !important;
|
||||
}
|
||||
|
||||
/* Make sure footer has proper transparency */
|
||||
footer {
|
||||
/* Light Mode - Verbesserte Lesbarkeit für Navbar-Elemente */
|
||||
body:not(.dark) .navbar-link,
|
||||
body:not(.dark) .navbar-item {
|
||||
color: #1e3a8a !important; /* Dunkles Blau für bessere Lesbarkeit */
|
||||
}
|
||||
|
||||
body:not(.dark) .navbar-link:hover,
|
||||
body:not(.dark) .navbar-item:hover {
|
||||
color: #4f46e5 !important; /* Helles Lila beim Hover */
|
||||
background-color: rgba(240, 245, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
/* Light Mode - Buttons verbessert */
|
||||
body:not(.dark) .btn,
|
||||
body:not(.dark) button {
|
||||
background-color: #3b82f6 !important; /* Klares Blau statt Grau */
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn:hover,
|
||||
body:not(.dark) button:hover {
|
||||
background-color: #4f46e5 !important; /* Lila beim Hover */
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12) !important;
|
||||
}
|
||||
|
||||
/* Verbesserte Karten im Light Mode */
|
||||
body:not(.dark) .card,
|
||||
body:not(.dark) .panel {
|
||||
background-color: rgba(255, 255, 255, 0.92) !important;
|
||||
border: 1px solid rgba(220, 220, 220, 0.8) !important;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
/* Verbesserte Lesbarkeit für Text im Light Mode */
|
||||
body:not(.dark) {
|
||||
color: #1e293b !important; /* Dunkles Blau-Grau statt Schwarz */
|
||||
}
|
||||
|
||||
body:not(.dark) h1,
|
||||
body:not(.dark) h2,
|
||||
body:not(.dark) h3,
|
||||
body:not(.dark) h4,
|
||||
body:not(.dark) h5,
|
||||
body:not(.dark) h6 {
|
||||
color: #0f172a !important; /* Fast schwarz für Überschriften */
|
||||
}
|
||||
|
||||
/* Make sure footer has proper transparency and styling */
|
||||
body.dark footer {
|
||||
background-color: rgba(10, 14, 25, 0.7) !important;
|
||||
}
|
||||
|
||||
body:not(.dark) footer {
|
||||
background-color: rgba(249, 250, 251, 0.92) !important;
|
||||
border-top: 1px solid rgba(220, 220, 220, 0.8) !important;
|
||||
}
|
||||
@@ -1441,4 +1441,204 @@ html, body {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Light Mode Optimierungen für wichtige UI-Komponenten */
|
||||
|
||||
/* Buttons im Light Mode */
|
||||
.btn-primary:not(.dark-mode .btn-primary) {
|
||||
background-color: var(--light-primary, #3b82f6);
|
||||
color: white;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-primary:not(.dark-mode .btn-primary):hover {
|
||||
background-color: var(--light-primary-hover, #4f46e5);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-secondary:not(.dark-mode .btn-secondary) {
|
||||
background-color: #f3f4f6;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-secondary:not(.dark-mode .btn-secondary):hover {
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Navbar im Light Mode */
|
||||
.navbar:not(.dark-mode .navbar),
|
||||
.nav:not(.dark-mode .nav) {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.navbar:not(.dark-mode .navbar) .nav-link,
|
||||
.nav:not(.dark-mode .nav) .nav-link {
|
||||
color: #1e3a8a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.navbar:not(.dark-mode .navbar) .nav-link:hover,
|
||||
.nav:not(.dark-mode .nav) .nav-link:hover {
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
.navbar:not(.dark-mode .navbar) .navbar-brand,
|
||||
.nav:not(.dark-mode .nav) .navbar-brand {
|
||||
color: #0f172a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Dropdown Menüs im Light Mode */
|
||||
.dropdown-menu:not(.dark-mode .dropdown-menu) {
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05), 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.dropdown-item:not(.dark-mode .dropdown-item) {
|
||||
color: #1e293b;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.dropdown-item:not(.dark-mode .dropdown-item):hover {
|
||||
background-color: #f1f5f9;
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
/* Karten im Light Mode */
|
||||
.card:not(.dark-mode .card) {
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.03), 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header:not(.dark-mode .card-header) {
|
||||
background-color: #f8fafc;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.card-footer:not(.dark-mode .card-footer) {
|
||||
background-color: #f8fafc;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Formulare im Light Mode */
|
||||
.form-control:not(.dark-mode .form-control) {
|
||||
background-color: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.form-control:not(.dark-mode .form-control):focus {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
|
||||
}
|
||||
|
||||
/* Tabs im Light Mode */
|
||||
.nav-tabs:not(.dark-mode .nav-tabs) {
|
||||
border-bottom-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link {
|
||||
color: #64748b;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link:hover {
|
||||
border-color: #e5e7eb #e5e7eb #e5e7eb;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link.active {
|
||||
color: #0f172a;
|
||||
background-color: white;
|
||||
border-color: #e5e7eb #e5e7eb white;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Alerts im Light Mode */
|
||||
.alert:not(.dark-mode .alert) {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.alert-primary:not(.dark-mode .alert-primary) {
|
||||
background-color: #eff6ff;
|
||||
border-color: #bfdbfe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.alert-success:not(.dark-mode .alert-success) {
|
||||
background-color: #f0fdf4;
|
||||
border-color: #bbf7d0;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.alert-warning:not(.dark-mode .alert-warning) {
|
||||
background-color: #fffbeb;
|
||||
border-color: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.alert-danger:not(.dark-mode .alert-danger) {
|
||||
background-color: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/* Badges im Light Mode */
|
||||
.badge:not(.dark-mode .badge) {
|
||||
font-weight: 500;
|
||||
padding: 0.25em 0.6em;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.badge-primary:not(.dark-mode .badge-primary) {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-secondary:not(.dark-mode .badge-secondary) {
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
/* Tabellen im Light Mode */
|
||||
table:not(.dark-mode table) {
|
||||
background-color: white;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table:not(.dark-mode table) th {
|
||||
background-color: #f8fafc;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table:not(.dark-mode table) td {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 0.75rem;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
table:not(.dark-mode table) tr:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
@@ -7,15 +7,6 @@
|
||||
* Verwaltet die globale Anwendungslogik
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialisiere die Anwendung
|
||||
MindMap.init();
|
||||
|
||||
// Wende Dunkel-/Hellmodus an
|
||||
const isDarkMode = localStorage.getItem('darkMode') === 'dark';
|
||||
document.documentElement.classList.toggle('dark', isDarkMode);
|
||||
});
|
||||
|
||||
/**
|
||||
* Hauptobjekt der MindMap-Anwendung
|
||||
*/
|
||||
@@ -24,7 +15,7 @@ const MindMap = {
|
||||
initialized: false,
|
||||
darkMode: document.documentElement.classList.contains('dark'),
|
||||
pageInitializers: {},
|
||||
currentPage: document.body.dataset.page,
|
||||
currentPage: null,
|
||||
|
||||
/**
|
||||
* Initialisiert die MindMap-Anwendung
|
||||
@@ -32,6 +23,9 @@ const MindMap = {
|
||||
init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
// Setze currentPage erst jetzt, wenn DOM garantiert geladen ist
|
||||
this.currentPage = document.body && document.body.dataset ? document.body.dataset.page : null;
|
||||
|
||||
console.log('MindMap-Anwendung wird initialisiert...');
|
||||
|
||||
// Initialisiere den ChatGPT-Assistenten
|
||||
@@ -229,6 +223,13 @@ const MindMap = {
|
||||
});
|
||||
}
|
||||
};
|
||||
window.MindMap = MindMap;
|
||||
|
||||
// Globale Export für andere Module
|
||||
window.MindMap = MindMap;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialisiere die Anwendung
|
||||
MindMap.init();
|
||||
|
||||
// Wende Dunkel-/Hellmodus an
|
||||
const isDarkMode = localStorage.getItem('darkMode') === 'dark';
|
||||
document.documentElement.classList.toggle('dark', isDarkMode);
|
||||
});
|
||||
234
static/js/mindmap.html
Normal file
234
static/js/mindmap.html
Normal file
@@ -0,0 +1,234 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Interaktive Mindmap</title>
|
||||
|
||||
<!-- Cytoscape.js -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
|
||||
<!-- Socket.IO -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
|
||||
|
||||
<!-- Feather Icons (optional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f9fafb;
|
||||
color: #111827;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #1f2937;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
#cy {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category-filter:not(.active) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.category-filter:hover:not(.active) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Kontextmenü Styling */
|
||||
#context-menu {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#context-menu .menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#context-menu .menu-item:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>Interaktive Mindmap</h1>
|
||||
<div class="search-container">
|
||||
<input type="text" id="search-mindmap" class="search-input" placeholder="Suchen...">
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="toolbar">
|
||||
<button id="addNode" class="btn">
|
||||
<i data-feather="plus-circle"></i>
|
||||
Knoten hinzufügen
|
||||
</button>
|
||||
<button id="addEdge" class="btn">
|
||||
<i data-feather="git-branch"></i>
|
||||
Verbindung erstellen
|
||||
</button>
|
||||
<button id="editNode" class="btn btn-secondary">
|
||||
<i data-feather="edit-2"></i>
|
||||
Knoten bearbeiten
|
||||
</button>
|
||||
<button id="deleteNode" class="btn btn-danger">
|
||||
<i data-feather="trash-2"></i>
|
||||
Knoten löschen
|
||||
</button>
|
||||
<button id="deleteEdge" class="btn btn-danger">
|
||||
<i data-feather="scissors"></i>
|
||||
Verbindung löschen
|
||||
</button>
|
||||
<button id="reLayout" class="btn btn-secondary">
|
||||
<i data-feather="refresh-cw"></i>
|
||||
Layout neu anordnen
|
||||
</button>
|
||||
<button id="exportMindmap" class="btn btn-secondary">
|
||||
<i data-feather="download"></i>
|
||||
Exportieren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="category-filters" class="category-filters">
|
||||
<!-- Wird dynamisch befüllt -->
|
||||
</div>
|
||||
|
||||
<div id="cy"></div>
|
||||
|
||||
<footer class="footer">
|
||||
Mindmap-Anwendung © 2023
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Unsere Mindmap JS -->
|
||||
<script src="../js/mindmap.js"></script>
|
||||
|
||||
<!-- Icons initialisieren -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof feather !== 'undefined') {
|
||||
feather.replace();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
749
static/js/mindmap.js
Normal file
749
static/js/mindmap.js
Normal file
@@ -0,0 +1,749 @@
|
||||
/**
|
||||
* Mindmap.js - Interaktive Mind-Map Implementierung
|
||||
* - Cytoscape.js für Graph-Rendering
|
||||
* - Fetch API für REST-Zugriffe
|
||||
* - Socket.IO für Echtzeit-Synchronisation
|
||||
*/
|
||||
|
||||
(async () => {
|
||||
/* 1. Initialisierung und Grundkonfiguration */
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'label': 'data(name)',
|
||||
'text-valign': 'center',
|
||||
'color': '#fff',
|
||||
'background-color': 'data(color)',
|
||||
'width': 45,
|
||||
'height': 45,
|
||||
'font-size': 11,
|
||||
'text-outline-width': 1,
|
||||
'text-outline-color': '#000',
|
||||
'text-outline-opacity': 0.5,
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': 80
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node[icon]',
|
||||
style: {
|
||||
'background-image': function(ele) {
|
||||
return `static/img/icons/${ele.data('icon')}.svg`;
|
||||
},
|
||||
'background-width': '60%',
|
||||
'background-height': '60%',
|
||||
'background-position-x': '50%',
|
||||
'background-position-y': '40%',
|
||||
'text-margin-y': 10
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 2,
|
||||
'line-color': '#888',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'target-arrow-color': '#888'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: ':selected',
|
||||
style: {
|
||||
'border-width': 3,
|
||||
'border-color': '#f8f32b'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}
|
||||
});
|
||||
|
||||
/* 2. Hilfs-Funktionen für API-Zugriffe */
|
||||
const get = async endpoint => {
|
||||
try {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return []; // Leeres Array zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim Abrufen von ${endpoint}:`, error);
|
||||
return []; // Leeres Array zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
const post = async (endpoint, body) => {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return {}; // Leeres Objekt zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim POST zu ${endpoint}:`, error);
|
||||
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
const del = async endpoint => {
|
||||
try {
|
||||
const response = await fetch(endpoint, { method: 'DELETE' });
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return {}; // Leeres Objekt zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim DELETE zu ${endpoint}:`, error);
|
||||
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
/* 3. Kategorien laden für Style-Informationen */
|
||||
let categories = await get('/api/categories');
|
||||
|
||||
/* 4. Daten laden und Rendering */
|
||||
const loadMindmap = async () => {
|
||||
try {
|
||||
// Nodes und Beziehungen parallel laden
|
||||
const [nodes, relationships] = await Promise.all([
|
||||
get('/api/mind_map_nodes'),
|
||||
get('/api/node_relationships')
|
||||
]);
|
||||
|
||||
// Graph leeren (für Reload-Fälle)
|
||||
cy.elements().remove();
|
||||
|
||||
// Überprüfen, ob nodes ein Array ist, wenn nicht, setze es auf ein leeres Array
|
||||
const nodesArray = Array.isArray(nodes) ? nodes : [];
|
||||
|
||||
// Knoten zum Graph hinzufügen
|
||||
cy.add(
|
||||
nodesArray.map(node => {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
return {
|
||||
data: {
|
||||
id: node.id.toString(),
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
},
|
||||
position: node.x && node.y ? { x: node.x, y: node.y } : undefined
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Überprüfen, ob relationships ein Array ist, wenn nicht, setze es auf ein leeres Array
|
||||
const relationshipsArray = Array.isArray(relationships) ? relationships : [];
|
||||
|
||||
// Kanten zum Graph hinzufügen
|
||||
cy.add(
|
||||
relationshipsArray.map(rel => ({
|
||||
data: {
|
||||
id: `${rel.parent_id}_${rel.child_id}`,
|
||||
source: rel.parent_id.toString(),
|
||||
target: rel.child_id.toString()
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
// Wenn keine Knoten geladen wurden, Fallback-Knoten erstellen
|
||||
if (nodesArray.length === 0) {
|
||||
// Mindestens einen Standardknoten hinzufügen
|
||||
cy.add({
|
||||
data: {
|
||||
id: 'fallback-1',
|
||||
name: 'Mindmap',
|
||||
description: 'Erstellen Sie hier Ihre eigene Mindmap',
|
||||
color: '#3b82f6',
|
||||
icon: 'help-circle'
|
||||
},
|
||||
position: { x: 300, y: 200 }
|
||||
});
|
||||
|
||||
// Erfolgsmeldung anzeigen
|
||||
console.log('Mindmap erfolgreich initialisiert mit Fallback-Knoten');
|
||||
|
||||
// Info-Meldung für Benutzer anzeigen
|
||||
const infoBox = document.createElement('div');
|
||||
infoBox.classList.add('info-message');
|
||||
infoBox.style.position = 'absolute';
|
||||
infoBox.style.top = '50%';
|
||||
infoBox.style.left = '50%';
|
||||
infoBox.style.transform = 'translate(-50%, -50%)';
|
||||
infoBox.style.padding = '15px 20px';
|
||||
infoBox.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
|
||||
infoBox.style.color = 'white';
|
||||
infoBox.style.borderRadius = '8px';
|
||||
infoBox.style.zIndex = '5';
|
||||
infoBox.style.maxWidth = '80%';
|
||||
infoBox.style.textAlign = 'center';
|
||||
infoBox.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
||||
infoBox.innerHTML = 'Mindmap erfolgreich initialisiert.<br>Verwenden Sie die Werkzeugleiste, um Knoten hinzuzufügen.';
|
||||
|
||||
document.getElementById('cy').appendChild(infoBox);
|
||||
|
||||
// Meldung nach 5 Sekunden ausblenden
|
||||
setTimeout(() => {
|
||||
infoBox.style.opacity = '0';
|
||||
infoBox.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => {
|
||||
if (infoBox.parentNode) {
|
||||
infoBox.parentNode.removeChild(infoBox);
|
||||
}
|
||||
}, 500);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Layout anwenden wenn keine Positionsdaten vorhanden
|
||||
const nodesWithoutPosition = cy.nodes().filter(node =>
|
||||
!node.position() || (node.position().x === 0 && node.position().y === 0)
|
||||
);
|
||||
|
||||
if (nodesWithoutPosition.length > 0) {
|
||||
cy.layout({
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}).run();
|
||||
}
|
||||
|
||||
// Tooltip-Funktionalität
|
||||
cy.nodes().unbind('mouseover').bind('mouseover', (event) => {
|
||||
const node = event.target;
|
||||
const description = node.data('description');
|
||||
|
||||
if (description) {
|
||||
const tooltip = document.getElementById('node-tooltip') ||
|
||||
document.createElement('div');
|
||||
|
||||
if (!tooltip.id) {
|
||||
tooltip.id = 'node-tooltip';
|
||||
tooltip.style.position = 'absolute';
|
||||
tooltip.style.backgroundColor = '#333';
|
||||
tooltip.style.color = '#fff';
|
||||
tooltip.style.padding = '8px';
|
||||
tooltip.style.borderRadius = '4px';
|
||||
tooltip.style.maxWidth = '250px';
|
||||
tooltip.style.zIndex = 10;
|
||||
tooltip.style.pointerEvents = 'none';
|
||||
tooltip.style.transition = 'opacity 0.2s';
|
||||
tooltip.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
|
||||
document.body.appendChild(tooltip);
|
||||
}
|
||||
|
||||
const renderedPosition = node.renderedPosition();
|
||||
const containerRect = cy.container().getBoundingClientRect();
|
||||
|
||||
tooltip.innerHTML = description;
|
||||
tooltip.style.left = (containerRect.left + renderedPosition.x + 25) + 'px';
|
||||
tooltip.style.top = (containerRect.top + renderedPosition.y - 15) + 'px';
|
||||
tooltip.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
|
||||
cy.nodes().unbind('mouseout').bind('mouseout', () => {
|
||||
const tooltip = document.getElementById('node-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.style.opacity = '0';
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Mindmap:', error);
|
||||
alert('Die Mindmap konnte nicht geladen werden. Bitte prüfen Sie die Konsole für Details.');
|
||||
}
|
||||
};
|
||||
|
||||
// Initial laden
|
||||
await loadMindmap();
|
||||
|
||||
/* 5. Socket.IO für Echtzeit-Synchronisation */
|
||||
const socket = io();
|
||||
|
||||
socket.on('node_added', async (node) => {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
cy.add({
|
||||
data: {
|
||||
id: node.id.toString(),
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
}
|
||||
});
|
||||
|
||||
// Layout neu anwenden, wenn nötig
|
||||
if (!node.x || !node.y) {
|
||||
cy.layout({ name: 'breadthfirst', directed: true, padding: 30 }).run();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('node_updated', (node) => {
|
||||
const cyNode = cy.$id(node.id.toString());
|
||||
if (cyNode.length > 0) {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
cyNode.data({
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
});
|
||||
|
||||
if (node.x && node.y) {
|
||||
cyNode.position({ x: node.x, y: node.y });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('node_deleted', (nodeId) => {
|
||||
const cyNode = cy.$id(nodeId.toString());
|
||||
if (cyNode.length > 0) {
|
||||
cy.remove(cyNode);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('relationship_added', (rel) => {
|
||||
cy.add({
|
||||
data: {
|
||||
id: `${rel.parent_id}_${rel.child_id}`,
|
||||
source: rel.parent_id.toString(),
|
||||
target: rel.child_id.toString()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('relationship_deleted', (rel) => {
|
||||
const edgeId = `${rel.parent_id}_${rel.child_id}`;
|
||||
const cyEdge = cy.$id(edgeId);
|
||||
if (cyEdge.length > 0) {
|
||||
cy.remove(cyEdge);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('category_updated', async () => {
|
||||
// Kategorien neu laden
|
||||
categories = await get('/api/categories');
|
||||
// Nodes aktualisieren, die diese Kategorie verwenden
|
||||
cy.nodes().forEach(node => {
|
||||
const categoryId = node.data('category_id');
|
||||
if (categoryId) {
|
||||
const category = categories.find(c => c.id === categoryId);
|
||||
if (category) {
|
||||
node.data('color', node.data('color_code') || category.color_code);
|
||||
node.data('icon', node.data('icon') || category.icon);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* 6. UI-Interaktionen */
|
||||
// Knoten hinzufügen
|
||||
const btnAddNode = document.getElementById('addNode');
|
||||
if (btnAddNode) {
|
||||
btnAddNode.addEventListener('click', async () => {
|
||||
const name = prompt('Knotenname eingeben:');
|
||||
if (!name) return;
|
||||
|
||||
const description = prompt('Beschreibung (optional):');
|
||||
|
||||
// Kategorie auswählen
|
||||
let categoryId = null;
|
||||
if (categories.length > 0) {
|
||||
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
|
||||
const categoryChoice = prompt(
|
||||
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
|
||||
'0'
|
||||
);
|
||||
|
||||
if (categoryChoice !== null) {
|
||||
const index = parseInt(categoryChoice, 10);
|
||||
if (!isNaN(index) && index >= 0 && index < categories.length) {
|
||||
categoryId = categories[index].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Knoten erstellen
|
||||
await post('/api/mind_map_node', {
|
||||
name,
|
||||
description,
|
||||
category_id: categoryId
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Verbindung hinzufügen
|
||||
const btnAddEdge = document.getElementById('addEdge');
|
||||
if (btnAddEdge) {
|
||||
btnAddEdge.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 2) {
|
||||
alert('Bitte genau zwei Knoten auswählen (Parent → Child)');
|
||||
return;
|
||||
}
|
||||
|
||||
const [parent, child] = sel.map(node => node.id());
|
||||
await post('/api/node_relationship', {
|
||||
parent_id: parent,
|
||||
child_id: child
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Knoten bearbeiten
|
||||
const btnEditNode = document.getElementById('editNode');
|
||||
if (btnEditNode) {
|
||||
btnEditNode.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau einen Knoten auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
const node = sel[0];
|
||||
const nodeData = node.data();
|
||||
|
||||
const name = prompt('Knotenname:', nodeData.name);
|
||||
if (!name) return;
|
||||
|
||||
const description = prompt('Beschreibung:', nodeData.description || '');
|
||||
|
||||
// Kategorie auswählen
|
||||
let categoryId = nodeData.category_id;
|
||||
if (categories.length > 0) {
|
||||
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
|
||||
const categoryChoice = prompt(
|
||||
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
|
||||
categories.findIndex(c => c.id === categoryId).toString()
|
||||
);
|
||||
|
||||
if (categoryChoice !== null) {
|
||||
const index = parseInt(categoryChoice, 10);
|
||||
if (!isNaN(index) && index >= 0 && index < categories.length) {
|
||||
categoryId = categories[index].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Knoten aktualisieren
|
||||
await post(`/api/mind_map_node/${nodeData.id}`, {
|
||||
name,
|
||||
description,
|
||||
category_id: categoryId
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Knoten löschen
|
||||
const btnDeleteNode = document.getElementById('deleteNode');
|
||||
if (btnDeleteNode) {
|
||||
btnDeleteNode.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau einen Knoten auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
|
||||
const nodeId = sel[0].id();
|
||||
await del(`/api/mind_map_node/${nodeId}`);
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Verbindung löschen
|
||||
const btnDeleteEdge = document.getElementById('deleteEdge');
|
||||
if (btnDeleteEdge) {
|
||||
btnDeleteEdge.addEventListener('click', async () => {
|
||||
const sel = cy.$('edge:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau eine Verbindung auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Sind Sie sicher, dass Sie diese Verbindung löschen möchten?')) {
|
||||
const edge = sel[0];
|
||||
const parentId = edge.source().id();
|
||||
const childId = edge.target().id();
|
||||
|
||||
await del(`/api/node_relationship/${parentId}/${childId}`);
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Layout aktualisieren
|
||||
const btnReLayout = document.getElementById('reLayout');
|
||||
if (btnReLayout) {
|
||||
btnReLayout.addEventListener('click', () => {
|
||||
cy.layout({
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}).run();
|
||||
});
|
||||
}
|
||||
|
||||
/* 7. Position speichern bei Drag & Drop */
|
||||
cy.on('dragfree', 'node', async (e) => {
|
||||
const node = e.target;
|
||||
const position = node.position();
|
||||
|
||||
await post(`/api/mind_map_node/${node.id()}/position`, {
|
||||
x: Math.round(position.x),
|
||||
y: Math.round(position.y)
|
||||
});
|
||||
|
||||
// Andere Benutzer erhalten die Position über den node_updated Event
|
||||
});
|
||||
|
||||
/* 8. Kontextmenü (optional) */
|
||||
const setupContextMenu = () => {
|
||||
cy.on('cxttap', 'node', function(e) {
|
||||
const node = e.target;
|
||||
const nodeData = node.data();
|
||||
|
||||
// Position des Kontextmenüs berechnen
|
||||
const renderedPosition = node.renderedPosition();
|
||||
const containerRect = cy.container().getBoundingClientRect();
|
||||
const menuX = containerRect.left + renderedPosition.x;
|
||||
const menuY = containerRect.top + renderedPosition.y;
|
||||
|
||||
// Kontextmenü erstellen oder aktualisieren
|
||||
let contextMenu = document.getElementById('context-menu');
|
||||
if (!contextMenu) {
|
||||
contextMenu = document.createElement('div');
|
||||
contextMenu.id = 'context-menu';
|
||||
contextMenu.style.position = 'absolute';
|
||||
contextMenu.style.backgroundColor = '#fff';
|
||||
contextMenu.style.border = '1px solid #ccc';
|
||||
contextMenu.style.borderRadius = '4px';
|
||||
contextMenu.style.padding = '5px 0';
|
||||
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
|
||||
contextMenu.style.zIndex = 1000;
|
||||
document.body.appendChild(contextMenu);
|
||||
}
|
||||
|
||||
// Menüinhalte
|
||||
contextMenu.innerHTML = `
|
||||
<div class="menu-item" data-action="edit">Knoten bearbeiten</div>
|
||||
<div class="menu-item" data-action="connect">Verbindung erstellen</div>
|
||||
<div class="menu-item" data-action="delete">Knoten löschen</div>
|
||||
`;
|
||||
|
||||
// Styling für Menüpunkte
|
||||
const menuItems = contextMenu.querySelectorAll('.menu-item');
|
||||
menuItems.forEach(item => {
|
||||
item.style.padding = '8px 20px';
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.fontSize = '14px';
|
||||
|
||||
item.addEventListener('mouseover', function() {
|
||||
this.style.backgroundColor = '#f0f0f0';
|
||||
});
|
||||
|
||||
item.addEventListener('mouseout', function() {
|
||||
this.style.backgroundColor = 'transparent';
|
||||
});
|
||||
|
||||
// Event-Handler
|
||||
item.addEventListener('click', async function() {
|
||||
const action = this.getAttribute('data-action');
|
||||
|
||||
switch(action) {
|
||||
case 'edit':
|
||||
// Knoten bearbeiten (gleiche Logik wie beim Edit-Button)
|
||||
const name = prompt('Knotenname:', nodeData.name);
|
||||
if (name) {
|
||||
const description = prompt('Beschreibung:', nodeData.description || '');
|
||||
await post(`/api/mind_map_node/${nodeData.id}`, { name, description });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
// Modus zum Verbinden aktivieren
|
||||
cy.nodes().unselect();
|
||||
node.select();
|
||||
alert('Wählen Sie nun einen zweiten Knoten aus, um eine Verbindung zu erstellen');
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
|
||||
await del(`/api/mind_map_node/${nodeData.id}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Menü schließen
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Menü positionieren und anzeigen
|
||||
contextMenu.style.left = menuX + 'px';
|
||||
contextMenu.style.top = menuY + 'px';
|
||||
contextMenu.style.display = 'block';
|
||||
|
||||
// Event-Listener zum Schließen des Menüs
|
||||
const closeMenu = function() {
|
||||
if (contextMenu) {
|
||||
contextMenu.style.display = 'none';
|
||||
}
|
||||
document.removeEventListener('click', closeMenu);
|
||||
};
|
||||
|
||||
// Verzögerung, um den aktuellen Click nicht zu erfassen
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeMenu);
|
||||
}, 0);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
// Kontextmenü aktivieren (optional)
|
||||
// setupContextMenu();
|
||||
|
||||
/* 9. Export-Funktion (optional) */
|
||||
const btnExport = document.getElementById('exportMindmap');
|
||||
if (btnExport) {
|
||||
btnExport.addEventListener('click', () => {
|
||||
const elements = cy.json().elements;
|
||||
const exportData = {
|
||||
nodes: elements.nodes.map(n => ({
|
||||
id: n.data.id,
|
||||
name: n.data.name,
|
||||
description: n.data.description,
|
||||
category_id: n.data.category_id,
|
||||
x: Math.round(n.position?.x || 0),
|
||||
y: Math.round(n.position?.y || 0)
|
||||
})),
|
||||
relationships: elements.edges.map(e => ({
|
||||
parent_id: e.data.source,
|
||||
child_id: e.data.target
|
||||
}))
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'mindmap_export.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
/* 10. Filter-Funktion nach Kategorien (optional) */
|
||||
const setupCategoryFilters = () => {
|
||||
const filterContainer = document.getElementById('category-filters');
|
||||
if (!filterContainer || !categories.length) return;
|
||||
|
||||
filterContainer.innerHTML = '';
|
||||
|
||||
// "Alle anzeigen" Option
|
||||
const allBtn = document.createElement('button');
|
||||
allBtn.innerText = 'Alle Kategorien';
|
||||
allBtn.className = 'category-filter active';
|
||||
allBtn.onclick = () => {
|
||||
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
|
||||
allBtn.classList.add('active');
|
||||
cy.nodes().removeClass('filtered').show();
|
||||
cy.edges().show();
|
||||
};
|
||||
filterContainer.appendChild(allBtn);
|
||||
|
||||
// Filter-Button pro Kategorie
|
||||
categories.forEach(category => {
|
||||
const btn = document.createElement('button');
|
||||
btn.innerText = category.name;
|
||||
btn.className = 'category-filter';
|
||||
btn.style.backgroundColor = category.color_code;
|
||||
btn.style.color = '#fff';
|
||||
btn.onclick = () => {
|
||||
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const matchingNodes = cy.nodes().filter(node => node.data('category_id') === category.id);
|
||||
cy.nodes().addClass('filtered').hide();
|
||||
matchingNodes.removeClass('filtered').show();
|
||||
|
||||
// Verbindungen zu/von diesen Knoten anzeigen
|
||||
cy.edges().hide();
|
||||
matchingNodes.connectedEdges().show();
|
||||
};
|
||||
filterContainer.appendChild(btn);
|
||||
});
|
||||
};
|
||||
|
||||
// Filter-Funktionalität aktivieren (optional)
|
||||
// setupCategoryFilters();
|
||||
|
||||
/* 11. Suchfunktion (optional) */
|
||||
const searchInput = document.getElementById('search-mindmap');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
if (!searchTerm) {
|
||||
cy.nodes().removeClass('search-hidden').show();
|
||||
cy.edges().show();
|
||||
return;
|
||||
}
|
||||
|
||||
cy.nodes().forEach(node => {
|
||||
const name = node.data('name').toLowerCase();
|
||||
const description = (node.data('description') || '').toLowerCase();
|
||||
|
||||
if (name.includes(searchTerm) || description.includes(searchTerm)) {
|
||||
node.removeClass('search-hidden').show();
|
||||
node.connectedEdges().show();
|
||||
} else {
|
||||
node.addClass('search-hidden').hide();
|
||||
// Kanten nur verstecken, wenn beide verbundenen Knoten versteckt sind
|
||||
node.connectedEdges().forEach(edge => {
|
||||
const otherNode = edge.source().id() === node.id() ? edge.target() : edge.source();
|
||||
if (otherNode.hasClass('search-hidden')) {
|
||||
edge.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Mindmap erfolgreich initialisiert');
|
||||
})();
|
||||
@@ -14,9 +14,18 @@ if (window.MindMap) {
|
||||
window.MindMap.pageInitializers.mindmap = initMindmapPage;
|
||||
}
|
||||
|
||||
// Initialisiere die Mindmap-Seite nur, wenn alle Abhängigkeiten vorhanden sind
|
||||
if (window.MindMap && typeof MindMapVisualization !== 'undefined') {
|
||||
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
|
||||
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
|
||||
window.MindMap.pageInitializers.mindmap = initMindmapPage;
|
||||
initMindmapPage();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prüfe, ob wir auf der Mindmap-Seite sind und initialisiere
|
||||
if (document.body.dataset.page === 'mindmap') {
|
||||
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
|
||||
initMindmapPage();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -818,6 +818,26 @@ class MindMapVisualization {
|
||||
this.updateNodeAppearance(d.id, isBookmarked);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle direkt verbundenen Knoten eines Knotens zurück
|
||||
* @param {Object} node - Der Knoten, für den die Verbindungen gesucht werden
|
||||
* @returns {Array} Array der verbundenen Knotenobjekte
|
||||
*/
|
||||
getConnectedNodes(node) {
|
||||
if (!node || !this.links || !this.nodes) return [];
|
||||
const nodeId = node.id;
|
||||
const connectedIds = new Set();
|
||||
this.links.forEach(link => {
|
||||
if (link.source === nodeId || (link.source && link.source.id === nodeId)) {
|
||||
connectedIds.add(link.target.id ? link.target.id : link.target);
|
||||
}
|
||||
if (link.target === nodeId || (link.target && link.target.id === nodeId)) {
|
||||
connectedIds.add(link.source.id ? link.source.id : link.source);
|
||||
}
|
||||
});
|
||||
return this.nodes.filter(n => connectedIds.has(n.id));
|
||||
}
|
||||
}
|
||||
|
||||
// Exportiere die Klasse für die Verwendung in anderen Modulen
|
||||
|
||||
@@ -477,6 +477,9 @@ class MindMapVisualization {
|
||||
this.nodes = processed.nodes;
|
||||
this.links = processed.links;
|
||||
|
||||
// Verbindungszählungen aktualisieren
|
||||
this.updateConnectionCounts();
|
||||
|
||||
// Visualisierung aktualisieren
|
||||
this.updateVisualization();
|
||||
|
||||
@@ -492,6 +495,9 @@ class MindMapVisualization {
|
||||
this.nodes = this.defaultNodes;
|
||||
this.links = this.defaultLinks;
|
||||
|
||||
// Verbindungszählungen auch für Fallback-Daten aktualisieren
|
||||
this.updateConnectionCounts();
|
||||
|
||||
// Fehler anzeigen
|
||||
this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
|
||||
this.showFlash('Fehler beim Laden der Mindmap-Daten. Standarddaten werden angezeigt.', 'error');
|
||||
@@ -936,7 +942,7 @@ class MindMapVisualization {
|
||||
// Highlights für verbundene Nodes und Links hinzufügen
|
||||
if (this.g) {
|
||||
// Verbundene Nodes identifizieren
|
||||
const connectedNodes = this.getConnectedNodes(d);
|
||||
const connectedNodes = this.getConnectedNodesById(d.id);
|
||||
const connectedNodeIds = connectedNodes.map(node => node.id);
|
||||
|
||||
// Alle Nodes etwas transparenter machen
|
||||
@@ -1035,7 +1041,7 @@ class MindMapVisualization {
|
||||
}
|
||||
// Falls ein Node ausgewählt ist, den Highlight-Status für diesen beibehalten
|
||||
else if (this.selectedNode && this.g) {
|
||||
const connectedNodes = this.getConnectedNodes(this.selectedNode);
|
||||
const connectedNodes = this.getConnectedNodesById(this.selectedNode.id);
|
||||
const connectedNodeIds = connectedNodes.map(node => node.id);
|
||||
|
||||
// Alle Nodes auf den richtigen Highlight-Status setzen
|
||||
@@ -1101,23 +1107,87 @@ class MindMapVisualization {
|
||||
|
||||
// Findet alle verbundenen Knoten zu einem gegebenen Knoten
|
||||
getConnectedNodes(node) {
|
||||
if (!this.links || !this.nodes) return [];
|
||||
if (!this.links || !this.nodes || !node) return [];
|
||||
|
||||
// Sicherstellen, dass der Knoten eine ID hat
|
||||
const nodeId = node.id || node;
|
||||
|
||||
return this.nodes.filter(n =>
|
||||
this.links.some(link =>
|
||||
(link.source.id === node.id && link.target.id === n.id) ||
|
||||
(link.target.id === node.id && link.source.id === n.id)
|
||||
)
|
||||
this.links.some(link => {
|
||||
const sourceId = link.source?.id || link.source;
|
||||
const targetId = link.target?.id || link.target;
|
||||
return (sourceId === nodeId && targetId === n.id) ||
|
||||
(targetId === nodeId && sourceId === n.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Prüft, ob zwei Knoten verbunden sind
|
||||
isConnected(a, b) {
|
||||
return this.links.some(link =>
|
||||
(link.source.id === a.id && link.target.id === b.id) ||
|
||||
(link.target.id === a.id && link.source.id === b.id)
|
||||
if (!this.links || !a || !b) return false;
|
||||
|
||||
// Sicherstellen, dass die Knoten IDs haben
|
||||
const aId = a.id || a;
|
||||
const bId = b.id || b;
|
||||
|
||||
return this.links.some(link => {
|
||||
const sourceId = link.source?.id || link.source;
|
||||
const targetId = link.target?.id || link.target;
|
||||
return (sourceId === aId && targetId === bId) ||
|
||||
(targetId === aId && sourceId === bId);
|
||||
});
|
||||
}
|
||||
|
||||
// Überprüft, ob ein Link zwischen zwei Knoten existiert
|
||||
hasLink(source, target) {
|
||||
if (!this.links || !source || !target) return false;
|
||||
|
||||
// Sicherstellen, dass die Knoten IDs haben
|
||||
const sourceId = source.id || source;
|
||||
const targetId = target.id || target;
|
||||
|
||||
return this.links.some(link => {
|
||||
const linkSourceId = link.source?.id || link.source;
|
||||
const linkTargetId = link.target?.id || link.target;
|
||||
return (linkSourceId === sourceId && linkTargetId === targetId) ||
|
||||
(linkTargetId === sourceId && linkSourceId === targetId);
|
||||
});
|
||||
}
|
||||
|
||||
// Sicherere Methode zum Abrufen verbundener Knoten, die Prüfungen enthält
|
||||
getConnectedNodesById(nodeId) {
|
||||
if (!this.links || !this.nodes || !nodeId) return [];
|
||||
|
||||
return this.nodes.filter(n =>
|
||||
this.links.some(link => {
|
||||
const sourceId = link.source.id || link.source;
|
||||
const targetId = link.target.id || link.target;
|
||||
return (sourceId === nodeId && targetId === n.id) ||
|
||||
(targetId === nodeId && sourceId === n.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Aktualisiert die Verbindungszählungen für alle Knoten
|
||||
updateConnectionCounts() {
|
||||
if (!this.nodes || !this.links) return;
|
||||
|
||||
// Für jeden Knoten die Anzahl der Verbindungen berechnen
|
||||
this.nodes.forEach(node => {
|
||||
// Sichere Methode, um verbundene Knoten zu zählen
|
||||
const connectedNodes = this.nodes.filter(n =>
|
||||
n.id !== node.id && this.links.some(link => {
|
||||
const sourceId = link.source.id || link.source;
|
||||
const targetId = link.target.id || link.target;
|
||||
return (sourceId === node.id && targetId === n.id) ||
|
||||
(targetId === node.id && sourceId === n.id);
|
||||
})
|
||||
);
|
||||
|
||||
// Speichere die Anzahl als Eigenschaft des Knotens
|
||||
node.connectionCount = connectedNodes.length;
|
||||
});
|
||||
}
|
||||
|
||||
// Klick-Handler für Knoten
|
||||
nodeClicked(event, d) {
|
||||
|
||||
1109
static/neural-network-background-full.js
Normal file
1109
static/neural-network-background-full.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,22 @@ body {
|
||||
|
||||
body.dark {
|
||||
color: var(--dark-text-primary);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Ensure proper contrast in both modes */
|
||||
body:not(.dark) {
|
||||
--text-primary: var(--light-text-primary);
|
||||
--text-secondary: var(--light-text-secondary);
|
||||
--bg-primary: var(--light-bg-primary);
|
||||
--bg-secondary: var(--light-bg-secondary);
|
||||
}
|
||||
|
||||
body.dark {
|
||||
--text-primary: var(--dark-text-primary);
|
||||
--text-secondary: var(--dark-text-secondary);
|
||||
--bg-primary: var(--dark-bg-primary);
|
||||
--bg-secondary: var(--dark-bg-secondary);
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
<meta name="keywords" content="systades, wissen, visualisierung, lernen, gedanken, theorie">
|
||||
<meta name="author" content="Systades-Team">
|
||||
|
||||
<!-- Tailwind CSS - Beide Optionen verfügbar -->
|
||||
<!-- Tailwind CSS - CDN für Entwicklung und Produktion (laut Vorgabe) -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Alternative lokale Version, falls die CDN-Version blockiert wird -->
|
||||
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
|
||||
<script>
|
||||
tailwind = window.tailwind || {};
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
@@ -112,82 +112,44 @@
|
||||
{% block extra_css %}{% endblock %}
|
||||
|
||||
<!-- Custom dark mode styles -->
|
||||
<!-- ► ► Farb‑Token strikt getrennt ◄ ◄ -->
|
||||
<style>
|
||||
/* Dark mystical theme */
|
||||
.dark {
|
||||
--bg-primary: #0a0e19;
|
||||
--bg-secondary: #111827;
|
||||
--text-primary: #f9fafb;
|
||||
--text-secondary: #e5e7eb;
|
||||
--accent-primary: #6d28d9;
|
||||
--accent-secondary: #8b5cf6;
|
||||
--glow-effect: 0 0 15px rgba(124, 58, 237, 0.5);
|
||||
}
|
||||
|
||||
/* Light theme with mystical tones */
|
||||
/* Light‑Mode */
|
||||
:root {
|
||||
--bg-primary: #f8fafc;
|
||||
--bg-secondary: #f1f5f9;
|
||||
--text-primary: #1e293b;
|
||||
--text-secondary: #475569;
|
||||
--accent-primary: #7c3aed;
|
||||
--accent-secondary: #8b5cf6;
|
||||
--glow-effect: 0 0 15px rgba(139, 92, 246, 0.3);
|
||||
--bg-primary:#f4f6fa;
|
||||
--bg-secondary:#e9ecf3;
|
||||
--text-primary:#232837;
|
||||
--text-secondary:#475569;
|
||||
--accent-primary:#7c3aed;
|
||||
--accent-secondary:#8b5cf6;
|
||||
--glow-effect:0 0 8px rgba(139,92,246,.08);
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
/* Dark‑Mode */
|
||||
.dark {
|
||||
--bg-primary:#181c24;
|
||||
--bg-secondary:#232837;
|
||||
--text-primary:#f9fafb;
|
||||
--text-secondary:#e5e7eb;
|
||||
--accent-primary:#6d28d9;
|
||||
--accent-secondary:#8b5cf6;
|
||||
--glow-effect:0 0 8px rgba(124,58,237,.15);
|
||||
}
|
||||
|
||||
/* Mystical glowing effects */
|
||||
.mystical-glow {
|
||||
text-shadow: var(--glow-effect);
|
||||
|
||||
body {
|
||||
@apply min-h-screen bg-[color:var(--bg-primary)] text-[color:var(--text-primary)] transition-colors duration-300;
|
||||
}
|
||||
|
||||
|
||||
/* Utilities */
|
||||
.mystical-glow { text-shadow: var(--glow-effect); }
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));
|
||||
-webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:none;
|
||||
}
|
||||
|
||||
/* Glass morphism effects */
|
||||
.glass-morphism {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dark .glass-navbar-dark {
|
||||
background-color: rgba(10, 14, 25, 0.8);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.glass-navbar-light {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Alpine.js x-cloak für ausgeblendete Elemente */
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
/* Grundlegende Klassen, um sicherzustellen, dass Tailwind geladen wird */
|
||||
.nav-link {
|
||||
@apply text-gray-300 hover:text-white transition-colors duration-200;
|
||||
}
|
||||
|
||||
.nav-link-active {
|
||||
@apply text-white font-medium;
|
||||
}
|
||||
|
||||
.nav-link-light {
|
||||
@apply text-gray-600 hover:text-gray-900 transition-colors duration-200;
|
||||
}
|
||||
|
||||
.nav-link-light-active {
|
||||
@apply text-gray-900 font-medium;
|
||||
}
|
||||
</style>
|
||||
.glass-morphism { backdrop-filter: blur(10px); }
|
||||
.glass-navbar { @apply glass-morphism border backdrop-blur-xl; }
|
||||
.light .glass-navbar { background-color:rgba(255,255,255,.8); border-color:rgba(0,0,0,.05); }
|
||||
.dark .glass-navbar { background-color:rgba(10,14,25,.8); border-color:rgba(255,255,255,.05); }
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
|
||||
darkMode: true,
|
||||
@@ -544,6 +506,10 @@
|
||||
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||
Impressum
|
||||
</a>
|
||||
<a href="{{ url_for('ueber_uns') }}" class="text-sm transition-all duration-200"
|
||||
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||
Über uns
|
||||
</a>
|
||||
<a href="{{ url_for('datenschutz') }}" class="text-sm transition-all duration-200"
|
||||
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||
Datenschutz
|
||||
@@ -611,5 +577,38 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Dark/Light-Mode persistent und robust -->
|
||||
<script>
|
||||
(function() {
|
||||
function applyMode(mode) {
|
||||
if (mode === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('colorMode', 'dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('colorMode', 'light');
|
||||
}
|
||||
}
|
||||
// Beim Laden: Präferenz aus localStorage oder System übernehmen
|
||||
const stored = localStorage.getItem('colorMode');
|
||||
if (stored === 'dark' || stored === 'light') {
|
||||
applyMode(stored);
|
||||
} else {
|
||||
// Systempräferenz als Fallback
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
applyMode(prefersDark ? 'dark' : 'light');
|
||||
}
|
||||
// Umschalter für alle Mode-Toggles
|
||||
window.toggleColorMode = function() {
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
applyMode(isDark ? 'light' : 'dark');
|
||||
};
|
||||
// Optional: globales Event für andere Skripte
|
||||
window.addEventListener('storage', function(e) {
|
||||
if (e.key === 'colorMode') applyMode(e.newValue);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,56 +8,56 @@
|
||||
<h1 class="text-3xl font-bold mb-6 gradient-text">Impressum</h1>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Angaben gemäß § 5 TMG</h2>
|
||||
<p class="mb-4">MindMap GmbH<br>
|
||||
Musterstraße 123<br>
|
||||
12345 Musterstadt<br>
|
||||
Deutschland</p>
|
||||
|
||||
<h2 class="text-xl font-bold mb-4">Angaben gemäß § 5 TMG und § 55 RStV</h2>
|
||||
<p class="mb-4">
|
||||
<strong>Vertreten durch:</strong><br>
|
||||
Max Mustermann, Geschäftsführer
|
||||
Diese Website wird privat betrieben von:<br>
|
||||
Marwin Medczinski<br>
|
||||
Fasanenstraße 30<br>
|
||||
16761 Hennigsdorf<br>
|
||||
Deutschland
|
||||
</p>
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
<p class="mb-4">
|
||||
<strong>Kontakt:</strong><br>
|
||||
Telefon: +49 (0) 123 456789<br>
|
||||
E-Mail: info@mindmap-example.com
|
||||
</p>
|
||||
|
||||
<p class="mb-4">
|
||||
<strong>Registereintrag:</strong><br>
|
||||
Eintragung im Handelsregister.<br>
|
||||
Registergericht: Amtsgericht Musterstadt<br>
|
||||
Registernummer: HRB 12345
|
||||
</p>
|
||||
|
||||
<p class="mb-4">
|
||||
<strong>Umsatzsteuer-ID:</strong><br>
|
||||
Umsatzsteuer-Identifikationsnummer gemäß §27 a Umsatzsteuergesetz:<br>
|
||||
DE 123456789
|
||||
Telefon: +49 (0) 173 8041824<br>
|
||||
E-Mail: medczinski.marwin@gmx.de
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Redaktionell verantwortlich</h2>
|
||||
<p>
|
||||
Max Mustermann<br>
|
||||
Musterstraße 123<br>
|
||||
12345 Musterstadt
|
||||
<h2 class="text-xl font-bold mb-4">Inhaltlich Verantwortlicher gemäß § 55 Abs. 2 RStV</h2>
|
||||
<p class="mb-4">
|
||||
Marwin Medczinski<br>
|
||||
Fasanenstraße 30<br>
|
||||
16761 Hennigsdorf
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Hinweis zur Streitbeilegung</h2>
|
||||
<p class="mb-4">Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: <a href="https://ec.europa.eu/consumers/odr/" target="_blank" class="text-purple-600 hover:text-purple-800">https://ec.europa.eu/consumers/odr/</a></p>
|
||||
<p class="mb-4">Ich bin nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Haftungsausschluss</h2>
|
||||
|
||||
<h3 class="text-lg font-bold mb-2">Haftung für Inhalte</h3>
|
||||
<p class="mb-4">Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.</p>
|
||||
<p class="mb-4">Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.</p>
|
||||
<p class="mb-4">Die Inhalte dieser Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte kann ich jedoch keine Gewähr übernehmen. Als Diensteanbieter bin ich gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG bin ich als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.</p>
|
||||
<p class="mb-4">Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werde ich diese Inhalte umgehend entfernen.</p>
|
||||
|
||||
<h3 class="text-lg font-bold mb-2">Haftung für Links</h3>
|
||||
<p class="mb-4">Unser Angebot enthält Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.</p>
|
||||
<p>Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.</p>
|
||||
<p class="mb-4">Diese Website enthält Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen Einfluss habe. Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.</p>
|
||||
<p class="mb-4">Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen.</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Urheberrecht</h2>
|
||||
<p class="mb-4">Die durch mich erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen meiner schriftlichen Zustimmung. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.</p>
|
||||
<p>Soweit die Inhalte auf dieser Seite nicht von mir erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitte ich um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Inhalte umgehend entfernen.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -626,7 +626,7 @@
|
||||
<div class="text-center py-12">
|
||||
<i class="fas fa-lightbulb text-5xl text-gray-400 mb-4"></i>
|
||||
<p class="text-gray-500">Noch keine Gedanken erstellt</p>
|
||||
<a href="{{ url_for('create_thought') }}" class="mt-4 inline-block px-4 py-2 bg-purple-600 text-white rounded-lg">Ersten Gedanken erstellen</a>
|
||||
<a href="{{ url_for('get_thought') }}" class="mt-4 inline-block px-4 py-2 bg-purple-600 text-white rounded-lg">Ersten Gedanken erstellen</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
79
templates/ueber_uns.html
Normal file
79
templates/ueber_uns.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Über uns{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="card p-6 md:p-8">
|
||||
<h1 class="text-3xl font-bold mb-6 gradient-text">Über uns</h1>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Unsere Vision</h2>
|
||||
<p class="mb-4">
|
||||
Systades ist ein innovatives Projekt, das darauf abzielt, das Teilen und Vernetzen von Wissen und Gedanken zu revolutionieren. Unsere Plattform ermöglicht es Nutzern, ihre Ideen in interaktiven Mindmaps zu organisieren und mit anderen zu teilen, wodurch ein kollaboratives Netzwerk des Wissens entsteht.
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
Wir glauben daran, dass Wissen am wertvollsten ist, wenn es geteilt und vernetzt wird. Durch die Verbindung verschiedener Perspektiven und Denkansätze entstehen neue Erkenntnisse und Innovationen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Das Team</h2>
|
||||
<p class="mb-4">
|
||||
Till Tomczak und Marwin Medczinski arbeiten gemeinsam daran, Systades kontinuierlich zu verbessern und weiterzuentwickeln.
|
||||
</p>
|
||||
|
||||
<!-- Platz für Team-Mitglieder -->
|
||||
<div class="team-members space-y-6">
|
||||
<!-- Beispiel für ein Team-Mitglied (kann als Vorlage verwendet werden) -->
|
||||
<!--
|
||||
<div class="team-member p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<h3 class="text-lg font-bold mb-2">[Name]</h3>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-2">[Position/Rolle]</p>
|
||||
<p class="text-sm">[Kurze Beschreibung oder Verantwortlichkeiten]</p>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Unsere Mission</h2>
|
||||
<p class="mb-4">
|
||||
Wir setzen uns dafür ein, eine Plattform zu schaffen, die:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 mb-4">
|
||||
<li>Intuitive Werkzeuge für die Organisation und Visualisierung von Wissen bereitstellt</li>
|
||||
<li>Die Zusammenarbeit und den Austausch zwischen Nutzern fördert</li>
|
||||
<li>Kreativität und innovative Denkansätze unterstützt</li>
|
||||
<li>Einen sicheren und respektvollen Raum für intellektuellen Austausch bietet</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="mb-8">
|
||||
<h2 class="text-xl font-bold mb-4">Technologie & Innovation</h2>
|
||||
<p class="mb-4">
|
||||
Systades nutzt modernste Technologien und innovative Ansätze, um eine optimale Nutzererfahrung zu gewährleisten. Unsere Plattform wird kontinuierlich weiterentwickelt, um neue Funktionen und Verbesserungen zu integrieren.
|
||||
</p>
|
||||
<p>
|
||||
Wir legen besonderen Wert auf:
|
||||
</p>
|
||||
<ul class="list-disc list-inside space-y-2 mt-2">
|
||||
<li>Intuitive Benutzeroberfläche</li>
|
||||
<li>Hohe Performance und Zuverlässigkeit</li>
|
||||
<li>Datensicherheit und Privatsphäre</li>
|
||||
<li>Barrierefreiheit und Inklusivität</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 class="text-xl font-bold mb-4">Kontakt & Feedback</h2>
|
||||
<p class="mb-4">
|
||||
Wir freuen uns über Ihr Feedback und Ihre Ideen zur Verbesserung von Systades. Gemeinsam können wir die Plattform weiter optimieren und an die Bedürfnisse unserer Nutzer anpassen.
|
||||
</p>
|
||||
<p>
|
||||
Kontaktieren Sie uns gerne für Fragen, Anregungen oder Kooperationsmöglichkeiten.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
60
test_app.py
Normal file
60
test_app.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
|
||||
print("Systades Anwendungstest")
|
||||
print("=======================")
|
||||
|
||||
# Prüfen, ob die Datenbank existiert
|
||||
db_path = 'systades.db'
|
||||
if not os.path.exists(db_path):
|
||||
print(f"Datenbank {db_path} existiert nicht.")
|
||||
exit(1)
|
||||
|
||||
# Datenbankprüfung
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Prüfen, ob die User-Tabelle existiert und Benutzer enthält
|
||||
cursor.execute("SELECT COUNT(*) FROM user;")
|
||||
user_count = cursor.fetchone()[0]
|
||||
print(f"Anzahl der Benutzer in der Datenbank: {user_count}")
|
||||
|
||||
if user_count == 0:
|
||||
print("WARNUNG: Keine Benutzer in der Datenbank gefunden!")
|
||||
print("Bitte führen Sie das Skript 'create_default_users.py' aus, um Standardbenutzer zu erstellen.")
|
||||
conn.close()
|
||||
exit(1)
|
||||
|
||||
# Tabellenschema prüfen
|
||||
cursor.execute("PRAGMA table_info(user);")
|
||||
columns = cursor.fetchall()
|
||||
column_names = [column[1] for column in columns]
|
||||
print(f"Spalten in der User-Tabelle: {', '.join(column_names)}")
|
||||
|
||||
# Überprüfen, ob die password-Spalte existiert
|
||||
if 'password' not in column_names:
|
||||
print("FEHLER: Die Spalte 'password' fehlt in der Tabelle 'user'!")
|
||||
print("Bitte führen Sie das Skript 'fix_user_table.py' aus, um die Datenbank zu reparieren.")
|
||||
conn.close()
|
||||
exit(1)
|
||||
|
||||
# Benutzer für Testlogin abrufen
|
||||
cursor.execute("SELECT username, email FROM user LIMIT 1;")
|
||||
test_user = cursor.fetchone()
|
||||
if test_user:
|
||||
print(f"Testbenutzer für Login: {test_user[0]} (E-Mail: {test_user[1]})")
|
||||
else:
|
||||
print("FEHLER: Konnte keinen Testbenutzer abrufen.")
|
||||
|
||||
conn.close()
|
||||
|
||||
print("\nDie Datenbank scheint korrekt konfiguriert zu sein.")
|
||||
print("Sie können nun die Anwendung starten und sich mit den folgenden Zugangsdaten anmelden:")
|
||||
print(" Admin: username=admin, password=admin")
|
||||
print(" User: username=user, password=user")
|
||||
print("\nTest abgeschlossen.")
|
||||
0
utils/__init__.py
Executable file → Normal file
0
utils/__init__.py
Executable file → Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
utils/db_fix.py
Executable file → Normal file
0
utils/db_fix.py
Executable file → Normal file
0
utils/db_rebuild.py
Executable file → Normal file
0
utils/db_rebuild.py
Executable file → Normal file
0
utils/db_test.py
Executable file → Normal file
0
utils/db_test.py
Executable file → Normal file
0
utils/download_resources.py
Executable file → Normal file
0
utils/download_resources.py
Executable file → Normal file
0
utils/server.py
Executable file → Normal file
0
utils/server.py
Executable file → Normal file
0
utils/user_manager.py
Executable file → Normal file
0
utils/user_manager.py
Executable file → Normal file
247
venv/bin/Activate.ps1
Normal file
247
venv/bin/Activate.ps1
Normal file
@@ -0,0 +1,247 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
69
venv/bin/activate
Normal file
69
venv/bin/activate
Normal file
@@ -0,0 +1,69 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/home/core/dev/website/venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="(venv) ${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT="(venv) "
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
26
venv/bin/activate.csh
Normal file
26
venv/bin/activate.csh
Normal file
@@ -0,0 +1,26 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/home/core/dev/website/venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "(venv) $prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT "(venv) "
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
69
venv/bin/activate.fish
Normal file
69
venv/bin/activate.fish
Normal file
@@ -0,0 +1,69 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/); you cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/home/core/dev/website/venv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT "(venv) "
|
||||
end
|
||||
8
venv/bin/distro
Normal file
8
venv/bin/distro
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from distro.distro import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/dotenv
Normal file
8
venv/bin/dotenv
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from dotenv.__main__ import cli
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(cli())
|
||||
8
venv/bin/email_validator
Normal file
8
venv/bin/email_validator
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from email_validator.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/flask
Normal file
8
venv/bin/flask
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from flask.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/gunicorn
Normal file
8
venv/bin/gunicorn
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from gunicorn.app.wsgiapp import run
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(run())
|
||||
8
venv/bin/httpx
Normal file
8
venv/bin/httpx
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from httpx import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/normalizer
Normal file
8
venv/bin/normalizer
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from charset_normalizer import cli
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(cli.cli_detect())
|
||||
8
venv/bin/openai
Normal file
8
venv/bin/openai
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from openai.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pip
Normal file
8
venv/bin/pip
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pip3
Normal file
8
venv/bin/pip3
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pip3.11
Normal file
8
venv/bin/pip3.11
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/py.test
Normal file
8
venv/bin/py.test
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pytest import console_main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(console_main())
|
||||
8
venv/bin/pytest
Normal file
8
venv/bin/pytest
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pytest import console_main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(console_main())
|
||||
BIN
venv/bin/python
Normal file
BIN
venv/bin/python
Normal file
Binary file not shown.
BIN
venv/bin/python3
Normal file
BIN
venv/bin/python3
Normal file
Binary file not shown.
BIN
venv/bin/python3.11
Normal file
BIN
venv/bin/python3.11
Normal file
Binary file not shown.
8
venv/bin/tqdm
Normal file
8
venv/bin/tqdm
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/home/core/dev/website/venv/bin/python3.11
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from tqdm.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
164
venv/include/site/python3.11/greenlet/greenlet.h
Normal file
164
venv/include/site/python3.11/greenlet/greenlet.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||
|
||||
/* Greenlet object interface */
|
||||
|
||||
#ifndef Py_GREENLETOBJECT_H
|
||||
#define Py_GREENLETOBJECT_H
|
||||
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is deprecated and undocumented. It does not change. */
|
||||
#define GREENLET_VERSION "1.0.0"
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
#define implementation_ptr_t void*
|
||||
#endif
|
||||
|
||||
typedef struct _greenlet {
|
||||
PyObject_HEAD
|
||||
PyObject* weakreflist;
|
||||
PyObject* dict;
|
||||
implementation_ptr_t pimpl;
|
||||
} PyGreenlet;
|
||||
|
||||
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||
|
||||
|
||||
/* C API functions */
|
||||
|
||||
/* Total number of symbols that are exported */
|
||||
#define PyGreenlet_API_pointers 12
|
||||
|
||||
#define PyGreenlet_Type_NUM 0
|
||||
#define PyExc_GreenletError_NUM 1
|
||||
#define PyExc_GreenletExit_NUM 2
|
||||
|
||||
#define PyGreenlet_New_NUM 3
|
||||
#define PyGreenlet_GetCurrent_NUM 4
|
||||
#define PyGreenlet_Throw_NUM 5
|
||||
#define PyGreenlet_Switch_NUM 6
|
||||
#define PyGreenlet_SetParent_NUM 7
|
||||
|
||||
#define PyGreenlet_MAIN_NUM 8
|
||||
#define PyGreenlet_STARTED_NUM 9
|
||||
#define PyGreenlet_ACTIVE_NUM 10
|
||||
#define PyGreenlet_GET_PARENT_NUM 11
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
/* This section is used by modules that uses the greenlet C API */
|
||||
static void** _PyGreenlet_API = NULL;
|
||||
|
||||
# define PyGreenlet_Type \
|
||||
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||
|
||||
# define PyExc_GreenletError \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||
|
||||
# define PyExc_GreenletExit \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_New(PyObject *args)
|
||||
*
|
||||
* greenlet.greenlet(run, parent=None)
|
||||
*/
|
||||
# define PyGreenlet_New \
|
||||
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetCurrent(void)
|
||||
*
|
||||
* greenlet.getcurrent()
|
||||
*/
|
||||
# define PyGreenlet_GetCurrent \
|
||||
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Throw(
|
||||
* PyGreenlet *greenlet,
|
||||
* PyObject *typ,
|
||||
* PyObject *val,
|
||||
* PyObject *tb)
|
||||
*
|
||||
* g.throw(...)
|
||||
*/
|
||||
# define PyGreenlet_Throw \
|
||||
(*(PyObject * (*)(PyGreenlet * self, \
|
||||
PyObject * typ, \
|
||||
PyObject * val, \
|
||||
PyObject * tb)) \
|
||||
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||
*
|
||||
* g.switch(*args, **kwargs)
|
||||
*/
|
||||
# define PyGreenlet_Switch \
|
||||
(*(PyObject * \
|
||||
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||
*
|
||||
* g.parent = new_parent
|
||||
*/
|
||||
# define PyGreenlet_SetParent \
|
||||
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||
*
|
||||
* return greenlet.parent;
|
||||
*
|
||||
* This could return NULL even if there is no exception active.
|
||||
* If it does not return NULL, you are responsible for decrementing the
|
||||
* reference count.
|
||||
*/
|
||||
# define PyGreenlet_GetParent \
|
||||
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||
|
||||
/*
|
||||
* deprecated, undocumented alias.
|
||||
*/
|
||||
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||
|
||||
# define PyGreenlet_MAIN \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||
|
||||
# define PyGreenlet_STARTED \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||
|
||||
# define PyGreenlet_ACTIVE \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||
|
||||
|
||||
|
||||
|
||||
/* Macro that imports greenlet and initializes C API */
|
||||
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||
keep the older definition to be sure older code that might have a copy of
|
||||
the header still works. */
|
||||
# define PyGreenlet_Import() \
|
||||
{ \
|
||||
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||
}
|
||||
|
||||
#endif /* GREENLET_MODULE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* !Py_GREENLETOBJECT_H */
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,28 @@
|
||||
Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
123
venv/lib/python3.11/site-packages/Flask-2.2.5.dist-info/METADATA
Normal file
123
venv/lib/python3.11/site-packages/Flask-2.2.5.dist-info/METADATA
Normal file
@@ -0,0 +1,123 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Flask
|
||||
Version: 2.2.5
|
||||
Summary: A simple framework for building complex web applications.
|
||||
Home-page: https://palletsprojects.com/p/flask
|
||||
Author: Armin Ronacher
|
||||
Author-email: armin.ronacher@active-4.com
|
||||
Maintainer: Pallets
|
||||
Maintainer-email: contact@palletsprojects.com
|
||||
License: BSD-3-Clause
|
||||
Project-URL: Donate, https://palletsprojects.com/donate
|
||||
Project-URL: Documentation, https://flask.palletsprojects.com/
|
||||
Project-URL: Changes, https://flask.palletsprojects.com/changes/
|
||||
Project-URL: Source Code, https://github.com/pallets/flask/
|
||||
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
|
||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
||||
Project-URL: Chat, https://discord.gg/pallets
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Flask
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.rst
|
||||
Requires-Dist: Werkzeug (>=2.2.2)
|
||||
Requires-Dist: Jinja2 (>=3.0)
|
||||
Requires-Dist: itsdangerous (>=2.0)
|
||||
Requires-Dist: click (>=8.0)
|
||||
Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10"
|
||||
Provides-Extra: async
|
||||
Requires-Dist: asgiref (>=3.2) ; extra == 'async'
|
||||
Provides-Extra: dotenv
|
||||
Requires-Dist: python-dotenv ; extra == 'dotenv'
|
||||
|
||||
Flask
|
||||
=====
|
||||
|
||||
Flask is a lightweight `WSGI`_ web application framework. It is designed
|
||||
to make getting started quick and easy, with the ability to scale up to
|
||||
complex applications. It began as a simple wrapper around `Werkzeug`_
|
||||
and `Jinja`_ and has become one of the most popular Python web
|
||||
application frameworks.
|
||||
|
||||
Flask offers suggestions, but doesn't enforce any dependencies or
|
||||
project layout. It is up to the developer to choose the tools and
|
||||
libraries they want to use. There are many extensions provided by the
|
||||
community that make adding new functionality easy.
|
||||
|
||||
.. _WSGI: https://wsgi.readthedocs.io/
|
||||
.. _Werkzeug: https://werkzeug.palletsprojects.com/
|
||||
.. _Jinja: https://jinja.palletsprojects.com/
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Install and update using `pip`_:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install -U Flask
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||
|
||||
|
||||
A Simple Example
|
||||
----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# save this as app.py
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "Hello, World!"
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
For guidance on setting up a development environment and how to make a
|
||||
contribution to Flask, see the `contributing guidelines`_.
|
||||
|
||||
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
|
||||
|
||||
|
||||
Donate
|
||||
------
|
||||
|
||||
The Pallets organization develops and supports Flask and the libraries
|
||||
it uses. In order to grow the community of contributors and users, and
|
||||
allow the maintainers to devote more time to the projects, `please
|
||||
donate today`_.
|
||||
|
||||
.. _please donate today: https://palletsprojects.com/donate
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
- Documentation: https://flask.palletsprojects.com/
|
||||
- Changes: https://flask.palletsprojects.com/changes/
|
||||
- PyPI Releases: https://pypi.org/project/Flask/
|
||||
- Source Code: https://github.com/pallets/flask/
|
||||
- Issue Tracker: https://github.com/pallets/flask/issues/
|
||||
- Website: https://palletsprojects.com/p/flask/
|
||||
- Twitter: https://twitter.com/PalletsTeam
|
||||
- Chat: https://discord.gg/pallets
|
||||
@@ -0,0 +1,54 @@
|
||||
../../../bin/flask,sha256=6fWTGy7Nkz970qr1oQpRe3U74s3GtrbIej78CF8-MSI,234
|
||||
Flask-2.2.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Flask-2.2.5.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
||||
Flask-2.2.5.dist-info/METADATA,sha256=rZTjr5v4M7HB-zC-w2Y0ZU96OYSGBb-Hm15jlLJhs3g,3889
|
||||
Flask-2.2.5.dist-info/RECORD,,
|
||||
Flask-2.2.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
Flask-2.2.5.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
||||
Flask-2.2.5.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41
|
||||
Flask-2.2.5.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
|
||||
flask/__init__.py,sha256=GJgAILDWhW_DQljuoJ4pk9zBUy70zPPu-VZ6kLyiVI4,2890
|
||||
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
||||
flask/__pycache__/__init__.cpython-311.pyc,,
|
||||
flask/__pycache__/__main__.cpython-311.pyc,,
|
||||
flask/__pycache__/app.cpython-311.pyc,,
|
||||
flask/__pycache__/blueprints.cpython-311.pyc,,
|
||||
flask/__pycache__/cli.cpython-311.pyc,,
|
||||
flask/__pycache__/config.cpython-311.pyc,,
|
||||
flask/__pycache__/ctx.cpython-311.pyc,,
|
||||
flask/__pycache__/debughelpers.cpython-311.pyc,,
|
||||
flask/__pycache__/globals.cpython-311.pyc,,
|
||||
flask/__pycache__/helpers.cpython-311.pyc,,
|
||||
flask/__pycache__/logging.cpython-311.pyc,,
|
||||
flask/__pycache__/scaffold.cpython-311.pyc,,
|
||||
flask/__pycache__/sessions.cpython-311.pyc,,
|
||||
flask/__pycache__/signals.cpython-311.pyc,,
|
||||
flask/__pycache__/templating.cpython-311.pyc,,
|
||||
flask/__pycache__/testing.cpython-311.pyc,,
|
||||
flask/__pycache__/typing.cpython-311.pyc,,
|
||||
flask/__pycache__/views.cpython-311.pyc,,
|
||||
flask/__pycache__/wrappers.cpython-311.pyc,,
|
||||
flask/app.py,sha256=ue4tEeDnr3m-eSEwz7OJ1_wafSYl3fl6eo-NLFgNNJQ,99141
|
||||
flask/blueprints.py,sha256=fenhKP_Sh5eU6qtWeHacg1GVeun4pQzK2vq8sNDd1hY,27266
|
||||
flask/cli.py,sha256=pLmnWObe_G4_ZAFQdh7kgwqPMxRXm4oUhaUSBpJMeq4,33532
|
||||
flask/config.py,sha256=Ubo_juzSYsAKqD2vD3vm6mjsPo3EOJDdSEzYq8lKTJI,12585
|
||||
flask/ctx.py,sha256=bGEQQuF2_cHqZ3ZNMeMeEG8HOLJkDlL88u2BBxCrRao,14829
|
||||
flask/debughelpers.py,sha256=_RvAL3TW5lqMJeCVWtTU6rSDJC7jnRaBL6OEkVmooyU,5511
|
||||
flask/globals.py,sha256=EX0XdX73BTWdVF0UHDSNet2ER3kI6sKveo3_o5IOs98,3187
|
||||
flask/helpers.py,sha256=XTHRgLlyxeEzR988q63-4OY8RswTscR-5exFxK10CLU,25280
|
||||
flask/json/__init__.py,sha256=TOwldHT3_kFaXHlORKi9yCWt7dbPNB0ovdHHQWlSRzY,11175
|
||||
flask/json/__pycache__/__init__.cpython-311.pyc,,
|
||||
flask/json/__pycache__/provider.cpython-311.pyc,,
|
||||
flask/json/__pycache__/tag.cpython-311.pyc,,
|
||||
flask/json/provider.py,sha256=jXCNypf11PN4ngQjEt6LnSdCWQ1yHIAkNLHlXQlCB-A,10674
|
||||
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
|
||||
flask/logging.py,sha256=WYng0bLTRS_CJrocGcCLJpibHf1lygHE_pg-KoUIQ4w,2293
|
||||
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
flask/scaffold.py,sha256=EKx-Tr5BXLzeKKvq3ZAi_2oUQVZuC4OJSJTocyDXsSo,35958
|
||||
flask/sessions.py,sha256=adWCRnJYETJcjjhlcvUgZR5S0DMqKQctS0nzkY9g9Us,15927
|
||||
flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136
|
||||
flask/templating.py,sha256=1P4OzvSnA2fsJTYgQT3G4owVKsuOz8XddCiR6jMHGJ0,7419
|
||||
flask/testing.py,sha256=JtHRQY7mIH39SM4S51svAr8e7Xk87dqMb30Z6Dyv9TA,10706
|
||||
flask/typing.py,sha256=KgxegTF9v9WvuongeF8LooIvpZPauzGrq9ZXf3gBlYc,2969
|
||||
flask/views.py,sha256=LulttWL4owVFlgwrJi8GCNM4inC3xbs2IBlY31bdCS4,6765
|
||||
flask/wrappers.py,sha256=el3tn1LgSUV0eNGgYMjKICT5I7qGJgbpIhvci4nrwQ8,5702
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.40.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
flask = flask.cli:main
|
||||
@@ -0,0 +1 @@
|
||||
flask
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright (C) 2016 Cory Dolphin, Olin College
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,147 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Flask-Cors
|
||||
Version: 4.0.0
|
||||
Summary: A Flask extension adding a decorator for CORS support
|
||||
Home-page: https://github.com/corydolphin/flask-cors
|
||||
Author: Cory Dolphin
|
||||
Author-email: corydolphin@gmail.com
|
||||
License: MIT
|
||||
Platform: any
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
License-File: LICENSE
|
||||
Requires-Dist: Flask (>=0.9)
|
||||
|
||||
Flask-CORS
|
||||
==========
|
||||
|
||||
|Build Status| |Latest Version| |Supported Python versions|
|
||||
|License|
|
||||
|
||||
A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.
|
||||
|
||||
This package has a simple philosophy: when you want to enable CORS, you wish to enable it for all use cases on a domain.
|
||||
This means no mucking around with different allowed headers, methods, etc.
|
||||
|
||||
By default, submission of cookies across domains is disabled due to the security implications.
|
||||
Please see the documentation for how to enable credential'ed requests, and please make sure you add some sort of `CSRF <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`__ protection before doing so!
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install the extension with using pip, or easy\_install.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ pip install -U flask-cors
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This package exposes a Flask extension which by default enables CORS support on all routes, for all origins and methods.
|
||||
It allows parameterization of all CORS headers on a per-resource level.
|
||||
The package also contains a decorator, for those who prefer this approach.
|
||||
|
||||
Simple Usage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
In the simplest case, initialize the Flask-Cors extension with default arguments in order to allow CORS for all domains on all routes.
|
||||
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
|
||||
|
||||
.. code:: python
|
||||
|
||||
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
@app.route("/")
|
||||
def helloWorld():
|
||||
return "Hello, cross-origin-world!"
|
||||
|
||||
Resource specific CORS
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Alternatively, you can specify CORS options on a resource and origin level of granularity by passing a dictionary as the `resources` option, mapping paths to a set of options.
|
||||
See the full list of options in the `documentation <https://flask-cors.corydolphin.com/en/latest/api.html#extension>`__.
|
||||
|
||||
.. code:: python
|
||||
|
||||
app = Flask(__name__)
|
||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
@app.route("/api/v1/users")
|
||||
def list_users():
|
||||
return "user example"
|
||||
|
||||
Route specific CORS via decorator
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This extension also exposes a simple decorator to decorate flask routes with.
|
||||
Simply add ``@cross_origin()`` below a call to Flask's ``@app.route(..)`` to allow CORS on a given route.
|
||||
See the full list of options in the `decorator documentation <https://flask-cors.corydolphin.com/en/latest/api.html#decorator>`__.
|
||||
|
||||
.. code:: python
|
||||
|
||||
@app.route("/")
|
||||
@cross_origin()
|
||||
def helloWorld():
|
||||
return "Hello, cross-origin-world!"
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
For a full list of options, please see the full `documentation <https://flask-cors.corydolphin.com/en/latest/api.html>`__
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
If things aren't working as you expect, enable logging to help understand what is going on under the hood, and why.
|
||||
|
||||
.. code:: python
|
||||
|
||||
logging.getLogger('flask_cors').level = logging.DEBUG
|
||||
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
A simple set of tests is included in ``test/``.
|
||||
To run, install nose, and simply invoke ``nosetests`` or ``python setup.py test`` to exercise the tests.
|
||||
|
||||
If nosetests does not work for you, due to it no longer working with newer python versions.
|
||||
You can use pytest to run the tests instead.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Questions, comments or improvements?
|
||||
Please create an issue on `Github <https://github.com/corydolphin/flask-cors>`__, tweet at `@corydolphin <https://twitter.com/corydolphin>`__ or send me an email.
|
||||
I do my best to include every contribution proposed in any way that I can.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
This Flask extension is based upon the `Decorator for the HTTP Access Control <https://web.archive.org/web/20190128010149/http://flask.pocoo.org/snippets/56/>`__ written by Armin Ronacher.
|
||||
|
||||
.. |Build Status| image:: https://api.travis-ci.org/corydolphin/flask-cors.svg?branch=master
|
||||
:target: https://travis-ci.org/corydolphin/flask-cors
|
||||
.. |Latest Version| image:: https://img.shields.io/pypi/v/Flask-Cors.svg
|
||||
:target: https://pypi.python.org/pypi/Flask-Cors/
|
||||
.. |Supported Python versions| image:: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
|
||||
:target: https://img.shields.io/pypi/pyversions/Flask-Cors.svg
|
||||
.. |License| image:: http://img.shields.io/:license-mit-blue.svg
|
||||
:target: https://pypi.python.org/pypi/Flask-Cors/
|
||||
@@ -0,0 +1,17 @@
|
||||
Flask_Cors-4.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Flask_Cors-4.0.0.dist-info/LICENSE,sha256=bhob3FSDTB4HQMvOXV9vLK4chG_Sp_SCsRZJWU-vvV0,1069
|
||||
Flask_Cors-4.0.0.dist-info/METADATA,sha256=iien2vLs6EIqceJgaNEJ6FPinwfjzFWSNl7XOkuyc10,5419
|
||||
Flask_Cors-4.0.0.dist-info/RECORD,,
|
||||
Flask_Cors-4.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
Flask_Cors-4.0.0.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110
|
||||
Flask_Cors-4.0.0.dist-info/top_level.txt,sha256=aWye_0QNZPp_QtPF4ZluLHqnyVLT9CPJsfiGhwqkWuo,11
|
||||
flask_cors/__init__.py,sha256=wZDCvPTHspA2g1VV7KyKN7R-uCdBnirTlsCzgPDcQtI,792
|
||||
flask_cors/__pycache__/__init__.cpython-311.pyc,,
|
||||
flask_cors/__pycache__/core.cpython-311.pyc,,
|
||||
flask_cors/__pycache__/decorator.cpython-311.pyc,,
|
||||
flask_cors/__pycache__/extension.cpython-311.pyc,,
|
||||
flask_cors/__pycache__/version.cpython-311.pyc,,
|
||||
flask_cors/core.py,sha256=e1u_o5SOcS_gMWGjcQrkyk91uPICnzZ3AXZvy5jQ_FE,14063
|
||||
flask_cors/decorator.py,sha256=BeJsyX1wYhVKWN04FAhb6z8YqffiRr7wKqwzHPap4bw,5009
|
||||
flask_cors/extension.py,sha256=nP4Zq_BhgDVWwPdIl_f-uucNxD38pXUo-dkL-voXc58,7832
|
||||
flask_cors/version.py,sha256=61rJjfThnbRdElpSP2tm31hPmFnHJmcwoPhtqA0Bi_Q,22
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user