Compare commits
2 Commits
0afb8cb6e2
...
f69356473b
| Author | SHA1 | Date | |
|---|---|---|---|
| f69356473b | |||
| 38ac13e87c |
27
Dockerfile
27
Dockerfile
@@ -1,27 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# System dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Clean up and install requirements
|
||||
RUN rm -rf /app/*
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt \
|
||||
&& pip install --no-cache-dir wheel setuptools
|
||||
|
||||
# Copy application files
|
||||
COPY website/ ./website/
|
||||
COPY requirements.txt .
|
||||
|
||||
EXPOSE 6000
|
||||
|
||||
# Verify installations
|
||||
RUN pip list
|
||||
|
||||
CMD ["python", "website/app.py"]
|
||||
BIN
archiv_0.1.zip
Normal file
BIN
archiv_0.1.zip
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,16 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "6000:6000"
|
||||
volumes:
|
||||
- ./website:/app/website
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
- FLASK_DEBUG=1
|
||||
command: python website/app.py
|
||||
restart: always
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
Flask
|
||||
Flask-Login
|
||||
Flask-SQLAlchemy
|
||||
Werkzeug
|
||||
SQLAlchemy
|
||||
email_validator
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import requests
|
||||
import socket
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def is_port_in_use(port):
|
||||
"""Check if a port is already in use"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
return s.connect_ex(('127.0.0.1', port)) == 0
|
||||
|
||||
def wait_for_server(url, max_attempts=5, delay=1):
|
||||
"""Wait for the server to start responding"""
|
||||
for i in range(max_attempts):
|
||||
try:
|
||||
response = requests.get(url, timeout=2)
|
||||
if response.status_code == 200:
|
||||
logger.info(f"Server is up and running at {url}")
|
||||
return True
|
||||
except requests.exceptions.RequestException:
|
||||
logger.info(f"Waiting for server to start (attempt {i+1}/{max_attempts})...")
|
||||
time.sleep(delay)
|
||||
return False
|
||||
|
||||
def main():
|
||||
# Get the current directory
|
||||
current_dir = Path(__file__).parent.absolute()
|
||||
website_dir = current_dir / 'website'
|
||||
|
||||
# Check if website directory exists
|
||||
if not website_dir.exists():
|
||||
logger.error(f"Website directory not found: {website_dir}")
|
||||
return False
|
||||
|
||||
# Flask server details
|
||||
host = "127.0.0.1"
|
||||
port = 5000
|
||||
url = f"http://{host}:{port}"
|
||||
|
||||
# Check if the port is already in use
|
||||
if is_port_in_use(port):
|
||||
logger.warning(f"Port {port} is already in use. There might be another server running.")
|
||||
answer = input("Would you like to try to connect to the existing server? (y/n): ")
|
||||
if answer.lower() == 'y':
|
||||
webbrowser.open(url)
|
||||
return True
|
||||
else:
|
||||
logger.info("Please stop the other server and try again.")
|
||||
return False
|
||||
|
||||
# Path to the run.py script
|
||||
run_script = website_dir / 'run.py'
|
||||
|
||||
# Check if run.py exists
|
||||
if not run_script.exists():
|
||||
logger.error(f"Run script not found: {run_script}")
|
||||
return False
|
||||
|
||||
# Start the Flask server in a separate process
|
||||
logger.info(f"Starting Flask server from {run_script}...")
|
||||
|
||||
try:
|
||||
# Use Python executable from the current environment
|
||||
python_exe = sys.executable
|
||||
|
||||
# Start the server as a separate process
|
||||
server_process = subprocess.Popen(
|
||||
[python_exe, str(run_script)],
|
||||
cwd=str(website_dir),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1
|
||||
)
|
||||
|
||||
# Wait for server to start
|
||||
server_started = wait_for_server(url, max_attempts=10, delay=2)
|
||||
|
||||
if server_started:
|
||||
logger.info("Opening web browser...")
|
||||
webbrowser.open(url)
|
||||
|
||||
# Keep the server running and display its output
|
||||
logger.info("Server is running. Press Ctrl+C to stop.")
|
||||
try:
|
||||
for line in server_process.stdout:
|
||||
print(line.strip())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Stopping server...")
|
||||
server_process.terminate()
|
||||
return True
|
||||
else:
|
||||
logger.error("Failed to start the server or server not responding")
|
||||
server_process.terminate()
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting Flask server: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
if not success:
|
||||
print("\nPress Enter to exit...")
|
||||
input()
|
||||
sys.exit(0 if success else 1)
|
||||
72
start.sh
72
start.sh
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Farben für die Ausgabe
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Funktion zum Anzeigen des Hilfetexts
|
||||
show_help() {
|
||||
echo -e "${YELLOW}Verwendung: ./start.sh [Option]${NC}"
|
||||
echo "Optionen:"
|
||||
echo " start - Startet die Container"
|
||||
echo " stop - Stoppt die Container"
|
||||
echo " restart - Neustart der Container"
|
||||
echo " rebuild - Baut die Container neu"
|
||||
echo " clean - Entfernt alle Container und Images"
|
||||
echo " logs - Zeigt die Container-Logs"
|
||||
echo " help - Zeigt diese Hilfe"
|
||||
}
|
||||
|
||||
# Prüfen ob Docker läuft
|
||||
check_docker() {
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo -e "${RED}Error: Docker ist nicht gestartet${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
check_docker
|
||||
echo -e "${GREEN}Starte Container...${NC}"
|
||||
docker-compose up -d
|
||||
echo -e "${GREEN}Container erfolgreich gestartet!${NC}"
|
||||
;;
|
||||
stop)
|
||||
check_docker
|
||||
echo -e "${YELLOW}Stoppe Container...${NC}"
|
||||
docker-compose down
|
||||
echo -e "${GREEN}Container erfolgreich gestoppt!${NC}"
|
||||
;;
|
||||
restart)
|
||||
check_docker
|
||||
echo -e "${YELLOW}Neustart der Container...${NC}"
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
echo -e "${GREEN}Container erfolgreich neugestartet!${NC}"
|
||||
;;
|
||||
rebuild)
|
||||
check_docker
|
||||
echo -e "${YELLOW}Baue Container neu...${NC}"
|
||||
docker-compose down --rmi all
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
echo -e "${GREEN}Container erfolgreich neu gebaut!${NC}"
|
||||
;;
|
||||
clean)
|
||||
check_docker
|
||||
echo -e "${RED}Entferne alle Container und Images...${NC}"
|
||||
docker-compose down --rmi all -v
|
||||
echo -e "${GREEN}Aufräumen abgeschlossen!${NC}"
|
||||
;;
|
||||
logs)
|
||||
check_docker
|
||||
echo -e "${YELLOW}Container-Logs:${NC}"
|
||||
docker-compose logs -f
|
||||
;;
|
||||
help|*)
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
@@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
cd website
|
||||
echo Starting Flask server on http://127.0.0.1:5000
|
||||
python run.py
|
||||
pause
|
||||
@@ -1,88 +0,0 @@
|
||||
const canvas = document.getElementById('netcanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
let w = canvas.width = window.innerWidth;
|
||||
let h = canvas.height = window.innerHeight;
|
||||
|
||||
// Partikel-Definition
|
||||
class Node {
|
||||
constructor() {
|
||||
this.x = Math.random() * w;
|
||||
this.y = Math.random() * h;
|
||||
this.vx = (Math.random() - 0.5) * 0.2;
|
||||
this.vy = (Math.random() - 0.5) * 0.2;
|
||||
}
|
||||
move() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
if (this.x < 0 || this.x > w) this.vx *= -1;
|
||||
if (this.y < 0 || this.y > h) this.vy *= -1;
|
||||
}
|
||||
draw() {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Konfiguration
|
||||
const config = {
|
||||
nodeCount: 30,
|
||||
connectDist: 80,
|
||||
lineAlpha: 0.08,
|
||||
glowColor: 'rgba(152, 165, 177, 0.4)',
|
||||
nodeColor: 'rgba(135, 162, 184, 0.6)'
|
||||
};
|
||||
|
||||
// Erzeuge Nodes
|
||||
const nodes = [];
|
||||
for (let i = 0; i < config.nodeCount; i++) {
|
||||
nodes.push(new Node());
|
||||
}
|
||||
|
||||
// Animation
|
||||
function animate() {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
// Linien zeichnen
|
||||
ctx.strokeStyle = config.glowColor;
|
||||
ctx.lineWidth = 0.8;
|
||||
ctx.shadowBlur = 3;
|
||||
ctx.shadowColor = config.glowColor;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const a = nodes[i];
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const b = nodes[j];
|
||||
const dx = a.x - b.x, dy = a.y - b.y;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
if (dist < config.connectDist) {
|
||||
ctx.globalAlpha = config.lineAlpha * (1 - dist / config.connectDist);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(a.x, a.y);
|
||||
ctx.lineTo(b.x, b.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes zeichnen
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = config.nodeColor;
|
||||
ctx.shadowBlur = 4;
|
||||
ctx.shadowColor = config.nodeColor;
|
||||
nodes.forEach(n => {
|
||||
n.move();
|
||||
n.draw();
|
||||
});
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
animate();
|
||||
|
||||
// Responsiv
|
||||
window.addEventListener('resize', () => {
|
||||
w = canvas.width = window.innerWidth;
|
||||
h = canvas.height = window.innerHeight;
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import requests
|
||||
import time
|
||||
|
||||
def test_flask_server():
|
||||
"""Test if the Flask server is accessible at http://127.0.0.1:5000"""
|
||||
url = "http://127.0.0.1:5000"
|
||||
|
||||
print(f"Testing connection to Flask server at {url}")
|
||||
|
||||
for i in range(3):
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
print(f"SUCCESS! Status code: {response.status_code}")
|
||||
return True
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Attempt {i+1} failed: {e}")
|
||||
if i < 2:
|
||||
print("Waiting 2 seconds and trying again...")
|
||||
time.sleep(2)
|
||||
|
||||
print("Failed to connect to the Flask server after 3 attempts")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_flask_server()
|
||||
Binary file not shown.
Binary file not shown.
280
website/app.py
280
website/app.py
@@ -1,280 +0,0 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
|
||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import json
|
||||
|
||||
db = SQLAlchemy()
|
||||
login_manager = LoginManager()
|
||||
login_manager.login_view = 'login'
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mindmap.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
|
||||
with app.app_context():
|
||||
# Initialize database and create root node
|
||||
db.create_all()
|
||||
try:
|
||||
root = MindMapNode.query.filter_by(parent_id=None).first()
|
||||
if not root:
|
||||
root = MindMapNode(name="Wissenschaft")
|
||||
db.session.add(root)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
print(f"Error initializing database: {str(e)}")
|
||||
|
||||
# Register all routes with the app
|
||||
@login_manager.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(int(id))
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and user.check_password(password):
|
||||
login_user(user)
|
||||
next_page = request.args.get('next')
|
||||
return redirect(next_page or url_for('index'))
|
||||
flash('Ungültiger Benutzername oder Passwort')
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
email = request.form.get('email')
|
||||
password = request.form.get('password')
|
||||
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash('Benutzername existiert bereits')
|
||||
return redirect(url_for('register'))
|
||||
|
||||
if User.query.filter_by(email=email).first():
|
||||
flash('E-Mail ist bereits registriert')
|
||||
return redirect(url_for('register'))
|
||||
|
||||
user = User(username=username, email=email)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
login_user(user)
|
||||
return redirect(url_for('index'))
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/mindmap')
|
||||
def mindmap():
|
||||
try:
|
||||
root_node = MindMapNode.query.filter_by(parent_id=None).first()
|
||||
if not root_node:
|
||||
root_node = MindMapNode(name="Wissenschaft")
|
||||
db.session.add(root_node)
|
||||
db.session.commit()
|
||||
return render_template('mindmap.html')
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error loading mindmap: {str(e)}")
|
||||
return render_template('mindmap.html', error="Fehler beim Laden der Mindmap. Bitte versuchen Sie es erneut.")
|
||||
|
||||
@app.route('/profile')
|
||||
@login_required
|
||||
def profile():
|
||||
thoughts = Thought.query.filter_by(user_id=current_user.id).order_by(Thought.timestamp.desc()).all()
|
||||
return render_template('profile.html', thoughts=thoughts)
|
||||
|
||||
@app.route('/api/mindmap')
|
||||
def get_mindmap():
|
||||
try:
|
||||
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
|
||||
|
||||
def build_tree(node):
|
||||
return {
|
||||
'id': node.id,
|
||||
'name': node.name,
|
||||
'children': [build_tree(child) for child in node.children]
|
||||
}
|
||||
|
||||
result = [build_tree(node) for node in root_nodes]
|
||||
if not result:
|
||||
root = MindMapNode(name="Wissenschaft")
|
||||
db.session.add(root)
|
||||
db.session.commit()
|
||||
result = [build_tree(root)]
|
||||
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error in get_mindmap: {str(e)}")
|
||||
return jsonify({'error': 'Fehler beim Laden der Mindmap'}), 500
|
||||
|
||||
@app.route('/api/thoughts/<int:node_id>', methods=['GET'])
|
||||
def get_thoughts(node_id):
|
||||
node = MindMapNode.query.get_or_404(node_id)
|
||||
thoughts = []
|
||||
|
||||
for thought in node.thoughts:
|
||||
thoughts.append({
|
||||
'id': thought.id,
|
||||
'content': thought.content,
|
||||
'author': thought.author.username,
|
||||
'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'),
|
||||
'comments_count': len(thought.comments),
|
||||
'branch': thought.branch
|
||||
})
|
||||
|
||||
return jsonify(thoughts)
|
||||
|
||||
@app.route('/api/thoughts', methods=['POST'])
|
||||
@login_required
|
||||
def add_thought():
|
||||
data = request.json
|
||||
node_id = data.get('node_id')
|
||||
content = data.get('content')
|
||||
|
||||
if not node_id or not content:
|
||||
return jsonify({'error': 'Fehlende Daten'}), 400
|
||||
|
||||
node = MindMapNode.query.get_or_404(node_id)
|
||||
|
||||
thought = Thought(
|
||||
content=content,
|
||||
branch=node.name,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
db.session.add(thought)
|
||||
node.thoughts.append(thought)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'id': thought.id,
|
||||
'content': thought.content,
|
||||
'author': thought.author.username,
|
||||
'timestamp': thought.timestamp.strftime('%d.%m.%Y, %H:%M'),
|
||||
'branch': thought.branch
|
||||
})
|
||||
|
||||
@app.route('/api/comments/<int:thought_id>', methods=['GET'])
|
||||
def get_comments(thought_id):
|
||||
thought = Thought.query.get_or_404(thought_id)
|
||||
comments = [
|
||||
{
|
||||
'id': comment.id,
|
||||
'content': comment.content,
|
||||
'author': comment.author.username,
|
||||
'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M')
|
||||
}
|
||||
for comment in thought.comments
|
||||
]
|
||||
return jsonify(comments)
|
||||
|
||||
@app.route('/api/comments', methods=['POST'])
|
||||
@login_required
|
||||
def add_comment():
|
||||
data = request.json
|
||||
thought_id = data.get('thought_id')
|
||||
content = data.get('content')
|
||||
|
||||
if not thought_id or not content:
|
||||
return jsonify({'error': 'Fehlende Daten'}), 400
|
||||
|
||||
thought = Thought.query.get_or_404(thought_id)
|
||||
|
||||
comment = Comment(
|
||||
content=content,
|
||||
thought_id=thought_id,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
db.session.add(comment)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'id': comment.id,
|
||||
'content': comment.content,
|
||||
'author': comment.author.username,
|
||||
'timestamp': comment.timestamp.strftime('%d.%m.%Y, %H:%M')
|
||||
})
|
||||
|
||||
@app.route('/admin')
|
||||
@login_required
|
||||
def admin():
|
||||
if not current_user.is_admin:
|
||||
flash('Zugriff verweigert')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
users = User.query.all()
|
||||
nodes = MindMapNode.query.all()
|
||||
thoughts = Thought.query.all()
|
||||
|
||||
return render_template('admin.html', users=users, nodes=nodes, thoughts=thoughts)
|
||||
|
||||
return app
|
||||
|
||||
# Database Models
|
||||
class User(UserMixin, db.Model):
|
||||
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)
|
||||
thoughts = db.relationship('Thought', backref='author', lazy=True)
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
class Thought(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
branch = db.Column(db.String(100), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
|
||||
|
||||
class Comment(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
author = db.relationship('User', backref='comments')
|
||||
|
||||
class MindMapNode(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
parent_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
|
||||
children = db.relationship('MindMapNode', backref=db.backref('parent', remote_side=[id]))
|
||||
thoughts = db.relationship('Thought', secondary='node_thought_association', backref='nodes')
|
||||
|
||||
# Association table for many-to-many relationship between MindMapNode and Thought
|
||||
node_thought_association = db.Table('node_thought_association',
|
||||
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
|
||||
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = create_app()
|
||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||
@@ -1,144 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
# Pfade zu möglichen Datenbankdateien
|
||||
db_paths = [
|
||||
Path("instance/mindmap.db"),
|
||||
Path("mindmap.db"),
|
||||
Path("website/instance/mindmap.db"),
|
||||
Path("website/mindmap.db")
|
||||
]
|
||||
|
||||
# Lösche bestehende Datenbankdateien
|
||||
for db_path in db_paths:
|
||||
if db_path.exists():
|
||||
try:
|
||||
print(f"Lösche Datenbank: {db_path}")
|
||||
os.remove(db_path)
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Löschen von {db_path}: {e}")
|
||||
|
||||
# Stelle sicher, dass das instance-Verzeichnis existiert
|
||||
instance_dir = Path("instance")
|
||||
if not instance_dir.exists():
|
||||
os.makedirs(instance_dir)
|
||||
|
||||
# Importiere Datenbankmodelle und erstelle die Datenbank
|
||||
from app import db, create_app, User, MindMapNode, Thought, Comment
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
print("Erstelle neue Datenbank...")
|
||||
db.drop_all() # Stelle sicher, dass alle Tabellen gelöscht sind
|
||||
db.create_all() # Erstelle Tabellen basierend auf den Modellen
|
||||
|
||||
# Erstelle einen Admin-Benutzer
|
||||
admin = User(username="admin", email="admin@example.com", is_admin=True)
|
||||
admin.set_password("admin123")
|
||||
db.session.add(admin)
|
||||
|
||||
# Erstelle Root-Node
|
||||
root = MindMapNode(name="Wissenschaft")
|
||||
db.session.add(root)
|
||||
|
||||
# Hauptkategorien erstellen
|
||||
naturwissenschaften = MindMapNode(name="Naturwissenschaften", parent=root)
|
||||
geisteswissenschaften = MindMapNode(name="Geisteswissenschaften", parent=root)
|
||||
sozialwissenschaften = MindMapNode(name="Sozialwissenschaften", parent=root)
|
||||
ingenieurwissenschaften = MindMapNode(name="Ingenieurwissenschaften", parent=root)
|
||||
medizin = MindMapNode(name="Medizin", parent=root)
|
||||
informatik = MindMapNode(name="Informatik", parent=root)
|
||||
|
||||
db.session.add_all([naturwissenschaften, geisteswissenschaften, sozialwissenschaften,
|
||||
ingenieurwissenschaften, medizin, informatik])
|
||||
|
||||
# Unterkategorien für Naturwissenschaften
|
||||
physik = MindMapNode(name="Physik", parent=naturwissenschaften)
|
||||
chemie = MindMapNode(name="Chemie", parent=naturwissenschaften)
|
||||
biologie = MindMapNode(name="Biologie", parent=naturwissenschaften)
|
||||
astronomie = MindMapNode(name="Astronomie", parent=naturwissenschaften)
|
||||
geologie = MindMapNode(name="Geologie", parent=naturwissenschaften)
|
||||
|
||||
# Unterkategorien für Physik
|
||||
quantenphysik = MindMapNode(name="Quantenphysik", parent=physik)
|
||||
relativitätstheorie = MindMapNode(name="Relativitätstheorie", parent=physik)
|
||||
thermodynamik = MindMapNode(name="Thermodynamik", parent=physik)
|
||||
|
||||
# Unterkategorien für Geisteswissenschaften
|
||||
philosophie = MindMapNode(name="Philosophie", parent=geisteswissenschaften)
|
||||
geschichte = MindMapNode(name="Geschichte", parent=geisteswissenschaften)
|
||||
linguistik = MindMapNode(name="Linguistik", parent=geisteswissenschaften)
|
||||
literaturwissenschaft = MindMapNode(name="Literaturwissenschaft", parent=geisteswissenschaften)
|
||||
religionswissenschaft = MindMapNode(name="Religionswissenschaft", parent=geisteswissenschaften)
|
||||
|
||||
# Unterkategorien für Sozialwissenschaften
|
||||
soziologie = MindMapNode(name="Soziologie", parent=sozialwissenschaften)
|
||||
psychologie = MindMapNode(name="Psychologie", parent=sozialwissenschaften)
|
||||
politikwissenschaft = MindMapNode(name="Politikwissenschaft", parent=sozialwissenschaften)
|
||||
wirtschaftswissenschaften = MindMapNode(name="Wirtschaftswissenschaften", parent=sozialwissenschaften)
|
||||
|
||||
# Unterkategorien für Ingenieurwissenschaften
|
||||
maschinenbau = MindMapNode(name="Maschinenbau", parent=ingenieurwissenschaften)
|
||||
elektrotechnik = MindMapNode(name="Elektrotechnik", parent=ingenieurwissenschaften)
|
||||
bauingenieurwesen = MindMapNode(name="Bauingenieurwesen", parent=ingenieurwissenschaften)
|
||||
verfahrenstechnik = MindMapNode(name="Verfahrenstechnik", parent=ingenieurwissenschaften)
|
||||
|
||||
# Unterkategorien für Medizin
|
||||
humanmedizin = MindMapNode(name="Humanmedizin", parent=medizin)
|
||||
zahnmedizin = MindMapNode(name="Zahnmedizin", parent=medizin)
|
||||
pharmazie = MindMapNode(name="Pharmazie", parent=medizin)
|
||||
neurologie = MindMapNode(name="Neurologie", parent=medizin)
|
||||
onkologie = MindMapNode(name="Onkologie", parent=medizin)
|
||||
|
||||
# Unterkategorien für Informatik
|
||||
künstliche_intelligenz = MindMapNode(name="Künstliche Intelligenz", parent=informatik)
|
||||
datenbanken = MindMapNode(name="Datenbanken", parent=informatik)
|
||||
softwareentwicklung = MindMapNode(name="Softwareentwicklung", parent=informatik)
|
||||
computergrafik = MindMapNode(name="Computergrafik", parent=informatik)
|
||||
cybersicherheit = MindMapNode(name="Cybersicherheit", parent=informatik)
|
||||
|
||||
# Alle Nodes zur Session hinzufügen
|
||||
all_nodes = [physik, chemie, biologie, astronomie, geologie,
|
||||
quantenphysik, relativitätstheorie, thermodynamik,
|
||||
philosophie, geschichte, linguistik, literaturwissenschaft, religionswissenschaft,
|
||||
soziologie, psychologie, politikwissenschaft, wirtschaftswissenschaften,
|
||||
maschinenbau, elektrotechnik, bauingenieurwesen, verfahrenstechnik,
|
||||
humanmedizin, zahnmedizin, pharmazie, neurologie, onkologie,
|
||||
künstliche_intelligenz, datenbanken, softwareentwicklung, computergrafik, cybersicherheit]
|
||||
|
||||
db.session.add_all(all_nodes)
|
||||
|
||||
# Füge einen Beispiel-Gedanken hinzu
|
||||
thought = Thought(
|
||||
content="Dies ist ein Beispiel-Gedanke zur Wissenschaft allgemein.",
|
||||
branch="Wissenschaft",
|
||||
user_id=1 # Admin-Benutzer
|
||||
)
|
||||
db.session.add(thought)
|
||||
root.thoughts.append(thought)
|
||||
|
||||
# Füge weitere Beispiel-Gedanken hinzu
|
||||
thought_ai = Thought(
|
||||
content="Künstliche Intelligenz transformiert viele Bereiche der Wissenschaft und Gesellschaft.",
|
||||
branch="Künstliche Intelligenz",
|
||||
user_id=1
|
||||
)
|
||||
db.session.add(thought_ai)
|
||||
künstliche_intelligenz.thoughts.append(thought_ai)
|
||||
|
||||
thought_physik = Thought(
|
||||
content="Die Quantenphysik stellt unser Verständnis der Realität grundlegend in Frage.",
|
||||
branch="Quantenphysik",
|
||||
user_id=1
|
||||
)
|
||||
db.session.add(thought_physik)
|
||||
quantenphysik.thoughts.append(thought_physik)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
print("Datenbank wurde erfolgreich initialisiert!")
|
||||
print(f"Admin-Benutzer erstellt: admin/admin123")
|
||||
print(f"Root-Node 'Wissenschaft' erstellt mit mehreren Hauptkategorien und Unterkategorien")
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
flask
|
||||
flask-login
|
||||
flask-wtf
|
||||
email-validator
|
||||
python-dotenv
|
||||
flask-sqlalchemy
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from app import create_app
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting Flask server on http://127.0.0.1:5000")
|
||||
try:
|
||||
# Use threaded=True for better request handling
|
||||
app.run(host="127.0.0.1", port=5000, debug=True, use_reloader=False, threaded=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting Flask server: {e}")
|
||||
sys.exit(1)
|
||||
@@ -1,108 +0,0 @@
|
||||
// Background animation with Three.js
|
||||
let scene, camera, renderer, stars = [];
|
||||
|
||||
function initBackground() {
|
||||
// Setup scene
|
||||
scene = new THREE.Scene();
|
||||
|
||||
// Setup camera
|
||||
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
|
||||
camera.position.z = 100;
|
||||
|
||||
// Setup renderer
|
||||
renderer = new THREE.WebGLRenderer({ alpha: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setClearColor(0x000000, 0); // Transparent background
|
||||
|
||||
// Append renderer to DOM
|
||||
const backgroundContainer = document.getElementById('background-container');
|
||||
if (backgroundContainer) {
|
||||
backgroundContainer.appendChild(renderer.domElement);
|
||||
}
|
||||
|
||||
// Add stars
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: Math.random() * 0.5 + 0.1 });
|
||||
const star = new THREE.Mesh(geometry, material);
|
||||
|
||||
// Random position
|
||||
star.position.x = Math.random() * 600 - 300;
|
||||
star.position.y = Math.random() * 600 - 300;
|
||||
star.position.z = Math.random() * 600 - 300;
|
||||
|
||||
// Store reference to move in animation
|
||||
star.velocity = Math.random() * 0.02 + 0.005;
|
||||
stars.push(star);
|
||||
|
||||
scene.add(star);
|
||||
}
|
||||
|
||||
// Add large glowing particles
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const size = Math.random() * 5 + 2;
|
||||
const geometry = new THREE.SphereGeometry(size, 16, 16);
|
||||
|
||||
// Create a glowing material
|
||||
const color = new THREE.Color();
|
||||
color.setHSL(Math.random(), 0.7, 0.5); // Random hue
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
transparent: true,
|
||||
opacity: 0.2
|
||||
});
|
||||
|
||||
const particle = new THREE.Mesh(geometry, material);
|
||||
|
||||
// Random position but further away
|
||||
particle.position.x = Math.random() * 1000 - 500;
|
||||
particle.position.y = Math.random() * 1000 - 500;
|
||||
particle.position.z = Math.random() * 200 - 400;
|
||||
|
||||
// Store reference to move in animation
|
||||
particle.velocity = Math.random() * 0.01 + 0.002;
|
||||
stars.push(particle);
|
||||
|
||||
scene.add(particle);
|
||||
}
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
// Start animation
|
||||
animate();
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Move stars
|
||||
stars.forEach(star => {
|
||||
star.position.z += star.velocity;
|
||||
|
||||
// Reset position if star moves too close
|
||||
if (star.position.z > 100) {
|
||||
star.position.z = -300;
|
||||
}
|
||||
});
|
||||
|
||||
// Rotate the entire scene slightly for a dreamy effect
|
||||
scene.rotation.y += 0.0003;
|
||||
scene.rotation.x += 0.0001;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
}
|
||||
|
||||
// Initialize background when the DOM is loaded
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initBackground);
|
||||
} else {
|
||||
initBackground();
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
C:\Users\firem\Downloads\background.mp4
|
||||
@@ -1,49 +0,0 @@
|
||||
// Erstelle eine einfache Mindmap-Struktur mit D3.js
|
||||
const data = {
|
||||
name: "Wissenschaftliche Mindmap",
|
||||
children: [
|
||||
{ name: "Forschung", children: [{ name: "Theorie" }, { name: "Experimente" }] },
|
||||
{ name: "Technologie", children: [{ name: "Datenbanken" }, { name: "Cloud Computing" }] }
|
||||
]
|
||||
};
|
||||
|
||||
// D3.js-Setup für die Darstellung der Mindmap
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
const margin = 50;
|
||||
|
||||
const svg = d3.select("#mindmap")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
const root = d3.hierarchy(data);
|
||||
const treeLayout = d3.tree().size([width - margin, height - margin]);
|
||||
treeLayout(root);
|
||||
|
||||
const links = svg.selectAll(".link")
|
||||
.data(root.links())
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("class", "link")
|
||||
.attr("x1", d => d.source.x + margin)
|
||||
.attr("y1", d => d.source.y + margin)
|
||||
.attr("x2", d => d.target.x + margin)
|
||||
.attr("y2", d => d.target.y + margin)
|
||||
.attr("stroke", "#2c3e50");
|
||||
|
||||
const nodes = svg.selectAll(".node")
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "node")
|
||||
.attr("transform", d => `translate(${d.x + margin},${d.y + margin})`);
|
||||
|
||||
nodes.append("circle")
|
||||
.attr("r", 20)
|
||||
.attr("fill", "#3498db");
|
||||
|
||||
nodes.append("text")
|
||||
.attr("dx", 25)
|
||||
.attr("dy", 5)
|
||||
.text(d => d.data.name);
|
||||
@@ -1,27 +0,0 @@
|
||||
/* Grundlegendes Styling für die Seite */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* Styling für den Header */
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
/* Button für die Navigation */
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
0
website/static/three.min.js
vendored
0
website/static/three.min.js
vendored
@@ -1,109 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin | Wissenschaftliche Mindmap{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="glass p-8 mb-8">
|
||||
<h1 class="text-3xl font-bold text-white mb-4">Admin Bereich</h1>
|
||||
<p class="text-white/70">Verwalte Benutzer, Gedanken und die Mindmap-Struktur.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<!-- Users Section -->
|
||||
<div class="dark-glass p-6" x-data="{ tab: 'users' }">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-white">Benutzer</h2>
|
||||
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ users|length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto max-h-[500px]">
|
||||
<table class="w-full text-white/90">
|
||||
<thead class="text-white/60 text-sm uppercase">
|
||||
<tr>
|
||||
<th class="text-left py-3">ID</th>
|
||||
<th class="text-left py-3">Benutzername</th>
|
||||
<th class="text-left py-3">Email</th>
|
||||
<th class="text-left py-3">Rolle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="border-t border-white/10">
|
||||
<td class="py-3">{{ user.id }}</td>
|
||||
<td class="py-3">{{ user.username }}</td>
|
||||
<td class="py-3">{{ user.email }}</td>
|
||||
<td class="py-3">
|
||||
{% if user.is_admin %}
|
||||
<span class="bg-purple-600/70 text-white text-xs px-2 py-1 rounded">Admin</span>
|
||||
{% else %}
|
||||
<span class="bg-blue-600/70 text-white text-xs px-2 py-1 rounded">Benutzer</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mindmap Nodes Section -->
|
||||
<div class="dark-glass p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-white">Mindmap Struktur</h2>
|
||||
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ nodes|length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto max-h-[500px]">
|
||||
<div class="space-y-3">
|
||||
{% for node in nodes %}
|
||||
<div class="glass p-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="font-medium">{{ node.name }}</span>
|
||||
<span class="text-xs text-white/60">ID: {{ node.id }}</span>
|
||||
</div>
|
||||
{% if node.parent %}
|
||||
<p class="text-sm text-white/60 mt-1">Eltern: {{ node.parent.name }}</p>
|
||||
{% else %}
|
||||
<p class="text-sm text-white/60 mt-1">Hauptknoten</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<button class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-4 py-2 rounded-lg transition-all duration-300 text-sm w-full">
|
||||
Neuen Knoten hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thoughts Section -->
|
||||
<div class="dark-glass p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-2xl font-bold text-white">Gedanken</h2>
|
||||
<span class="bg-indigo-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{{ thoughts|length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-y-auto max-h-[500px]">
|
||||
<div class="space-y-3">
|
||||
{% for thought in thoughts %}
|
||||
<div class="glass p-3">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="inline-block px-2 py-0.5 text-xs text-white/70 bg-white/10 rounded-full mb-1">{{ thought.branch }}</span>
|
||||
<span class="text-xs text-white/50">{{ thought.timestamp.strftime('%d.%m.%Y') }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-white mb-1 line-clamp-2">{{ thought.content }}</p>
|
||||
<div class="flex justify-between items-center mt-2 text-xs">
|
||||
<span class="text-white/60">Von: {{ thought.author.username }}</span>
|
||||
<span class="text-white/60">{{ thought.comments|length }} Kommentar(e)</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,126 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Wissenschaftliche Mindmap{% endblock %}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#3b82f6',
|
||||
secondary: '#10b981',
|
||||
accent: '#8b5cf6',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Poppins', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
}
|
||||
|
||||
.dark-glass {
|
||||
background: rgba(17, 24, 39, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background-color: #050b14;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
background-image: linear-gradient(to right, #4f46e5, #8b5cf6);
|
||||
}
|
||||
|
||||
#background-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#background-container canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="antialiased text-gray-100">
|
||||
<!-- Animated background container -->
|
||||
<div id="background-container"></div>
|
||||
|
||||
<div class="min-h-screen">
|
||||
<!-- Navigation -->
|
||||
<nav class="glass px-4 py-3 mx-4 mt-4 flex justify-between items-center">
|
||||
<a href="{{ url_for('index') }}" class="text-white text-xl font-bold">MindMap</a>
|
||||
<div class="flex space-x-4">
|
||||
<a href="{{ url_for('mindmap') }}" class="text-white hover:text-indigo-200 transition-colors">Mindmap</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile') }}" class="text-white hover:text-indigo-200 transition-colors">Profil</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin') }}" class="text-white hover:text-indigo-200 transition-colors">Admin</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('logout') }}" class="text-white hover:text-indigo-200 transition-colors">Abmelden</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('login') }}" class="text-white hover:text-indigo-200 transition-colors">Anmelden</a>
|
||||
<a href="{{ url_for('register') }}" class="text-white hover:text-indigo-200 transition-colors">Registrieren</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Flash messages -->
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="container mx-auto mt-4 px-4">
|
||||
{% for message in messages %}
|
||||
<div class="glass p-4 mb-4 text-white">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="container mx-auto p-4">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="{{ url_for('static', filename='background.js') }}"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,38 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col items-center justify-center min-h-[80vh] text-center">
|
||||
<div class="glass p-8 max-w-2xl w-full">
|
||||
<h1 class="text-4xl font-bold text-white mb-4">Willkommen zur Wissenschafts-Mindmap</h1>
|
||||
<p class="text-xl text-white/80 mb-8">Verknüpfe Wissen in neuronalen Strukturen und teile deine Gedanken mit der Community.</p>
|
||||
|
||||
<div class="flex flex-col space-y-4 sm:flex-row sm:space-y-0 sm:space-x-4 justify-center">
|
||||
<a href="{{ url_for('mindmap') }}" class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 transform hover:scale-105">
|
||||
Zum Netzwerk
|
||||
</a>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<a href="{{ url_for('register') }}" class="glass hover:bg-white/20 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 transform hover:scale-105">
|
||||
Registrieren
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-4xl">
|
||||
<div class="glass p-6 text-white">
|
||||
<h3 class="text-xl font-semibold mb-2">Visualisiere Wissen</h3>
|
||||
<p class="text-white/80">Erkenne Zusammenhänge zwischen verschiedenen Wissensgebieten durch intuitive Mindmaps.</p>
|
||||
</div>
|
||||
|
||||
<div class="glass p-6 text-white">
|
||||
<h3 class="text-xl font-semibold mb-2">Teile Gedanken</h3>
|
||||
<p class="text-white/80">Füge deine eigenen Gedanken zu bestehenden Themen hinzu und bereichere die Community.</p>
|
||||
</div>
|
||||
|
||||
<div class="glass p-6 text-white">
|
||||
<h3 class="text-xl font-semibold mb-2">Interaktive Vernetzung</h3>
|
||||
<p class="text-white/80">Beteilige dich an Diskussionen und sieh wie sich Ideen gemeinsam entwickeln.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Anmelden | Wissenschaftliche Mindmap{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex justify-center items-center min-h-[80vh]">
|
||||
<div class="glass p-8 w-full max-w-md">
|
||||
<h1 class="text-3xl font-bold text-white mb-6 text-center">Anmelden</h1>
|
||||
|
||||
<form method="POST" action="{{ url_for('login') }}" class="space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-white mb-1">Benutzername</label>
|
||||
<input type="text" id="username" name="username" required
|
||||
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-white mb-1">Passwort</label>
|
||||
<input type="password" id="password" name="password" required
|
||||
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300">
|
||||
Anmelden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-white/70">
|
||||
Noch kein Konto?
|
||||
<a href="{{ url_for('register') }}" class="text-indigo-300 hover:text-white font-medium transition-colors">
|
||||
Registrieren
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,708 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Mindmap | Wissenschaftliche Mindmap{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.node {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node circle {
|
||||
fill: rgba(255, 255, 255, 0.2);
|
||||
stroke: white;
|
||||
stroke-width: 1.5px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.node text {
|
||||
font-size: 12px;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.node--selected circle {
|
||||
fill: rgba(139, 92, 246, 0.6);
|
||||
r: 25;
|
||||
stroke: rgba(255, 255, 255, 0.8);
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.link {
|
||||
fill: none;
|
||||
stroke: rgba(255, 255, 255, 0.3);
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.thoughts-panel {
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.thoughts-panel.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.thoughts-container {
|
||||
max-height: calc(100vh - 250px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for the thoughts panel */
|
||||
.thoughts-container::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.thoughts-container::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.thoughts-container::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.thoughts-container::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.tooltip {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/* Node color coding by category */
|
||||
.node-science circle {
|
||||
fill: rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.node-humanities circle {
|
||||
fill: rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.node-technology circle {
|
||||
fill: rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
.node-arts circle {
|
||||
fill: rgba(236, 72, 153, 0.3);
|
||||
}
|
||||
|
||||
/* Add a glow effect on hover */
|
||||
.node:hover circle {
|
||||
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
|
||||
}
|
||||
|
||||
/* Fix for "keine Auswahl" text */
|
||||
#selected-node-title {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col lg:flex-row h-[calc(100vh-100px)]">
|
||||
<!-- Main mindmap visualization area -->
|
||||
<div class="flex-grow relative" id="mindmap-container">
|
||||
<div class="absolute top-4 left-4 z-10">
|
||||
<h1 class="text-2xl font-bold text-white glass px-4 py-2 inline-block">Wissenschaftliche Mindmap</h1>
|
||||
</div>
|
||||
|
||||
<!-- Zoom controls -->
|
||||
<div class="absolute bottom-4 left-4 glass p-2 flex space-x-2 z-10">
|
||||
<button id="zoom-in" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="zoom-out" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M5 10a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<button id="reset-view" class="w-8 h-8 flex items-center justify-center text-white hover:bg-white/20 rounded-full transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="absolute top-4 right-4 glass p-3 z-10">
|
||||
<h3 class="text-sm font-semibold text-white mb-2">Kategorien</h3>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block w-3 h-3 rounded-full bg-indigo-600/60 mr-2"></span>
|
||||
<span class="text-white/90">Naturwissenschaften</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block w-3 h-3 rounded-full bg-green-500/60 mr-2"></span>
|
||||
<span class="text-white/90">Geisteswissenschaften</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block w-3 h-3 rounded-full bg-purple-500/60 mr-2"></span>
|
||||
<span class="text-white/90">Technologie</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block w-3 h-3 rounded-full bg-pink-500/60 mr-2"></span>
|
||||
<span class="text-white/90">Künste</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg id="mindmap" class="w-full h-full"></svg>
|
||||
<div id="tooltip" class="tooltip"></div>
|
||||
</div>
|
||||
|
||||
<!-- Thoughts panel (hidden by default) -->
|
||||
<div id="thoughts-panel" class="thoughts-panel fixed lg:relative right-0 top-0 lg:top-auto h-full lg:h-auto w-full sm:w-96 dark-glass z-20">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-white" id="selected-node-title">Keine Auswahl</h2>
|
||||
<button id="close-panel" class="text-white/70 hover:text-white lg:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="glass p-4 mb-6">
|
||||
<h3 class="text-sm font-medium text-white/80 mb-2">Teile deinen Gedanken</h3>
|
||||
<textarea id="thought-input" rows="3" placeholder="Was denkst du zu diesem Thema?"
|
||||
class="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all resize-none"></textarea>
|
||||
<button id="submit-thought"
|
||||
class="mt-2 w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-medium px-4 py-2 rounded-lg transition-all">
|
||||
Gedanken teilen
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="glass p-4 mb-6 text-center">
|
||||
<p class="text-white/80 mb-2">Melde dich an, um deine Gedanken zu teilen</p>
|
||||
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium">Anmelden</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h3 class="text-lg font-medium text-white mb-3">Community Gedanken</h3>
|
||||
<div id="thoughts-container" class="thoughts-container space-y-4">
|
||||
<div class="text-center py-8 text-white/50">
|
||||
<p>Wähle einen Knoten aus, um Gedanken zu sehen</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comment Modal -->
|
||||
<div id="commentModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
|
||||
<div class="dark-glass p-6 w-full max-w-lg">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white" id="comment-modal-title">Kommentare</h3>
|
||||
<button onclick="closeCommentModal()" class="text-white/70 hover:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="glass p-4 mb-4">
|
||||
<p id="comment-thought-content" class="text-white mb-2"></p>
|
||||
<div class="flex justify-between items-center text-xs text-white/60">
|
||||
<span id="comment-thought-author"></span>
|
||||
<span id="comment-thought-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 max-h-60 overflow-y-auto" id="comments-list">
|
||||
<!-- Comments will be loaded here -->
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="mt-4">
|
||||
<textarea id="comment-input" rows="2" placeholder="Füge einen Kommentar hinzu..."
|
||||
class="w-full px-3 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all resize-none"></textarea>
|
||||
<button id="submit-comment"
|
||||
class="mt-2 w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-medium px-4 py-2 rounded-lg transition-all">
|
||||
Kommentar hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="glass p-4 text-center">
|
||||
<p class="text-white/80 mb-2">Melde dich an, um Kommentare zu hinterlassen</p>
|
||||
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium">Anmelden</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script>
|
||||
// Global variables
|
||||
let selectedNode = null;
|
||||
let currentThoughtId = null;
|
||||
const width = document.getElementById('mindmap-container').clientWidth;
|
||||
const height = document.getElementById('mindmap-container').clientHeight;
|
||||
const nodeCategories = {
|
||||
'Naturwissenschaften': 'node-science',
|
||||
'Geisteswissenschaften': 'node-humanities',
|
||||
'Technologie': 'node-technology',
|
||||
'Künste': 'node-arts'
|
||||
};
|
||||
|
||||
// Initialize D3 visualization
|
||||
const svg = d3.select('#mindmap')
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
const g = svg.append('g');
|
||||
|
||||
// Zoom behavior
|
||||
const zoom = d3.zoom()
|
||||
.scaleExtent([0.3, 3])
|
||||
.on('zoom', (event) => {
|
||||
g.attr('transform', event.transform);
|
||||
});
|
||||
|
||||
svg.call(zoom);
|
||||
|
||||
// Reset to initial view
|
||||
function resetView() {
|
||||
svg.transition().duration(750).call(
|
||||
zoom.transform,
|
||||
d3.zoomIdentity.translate(width / 2, height / 2).scale(0.8)
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize tooltip
|
||||
const tooltip = d3.select('#tooltip');
|
||||
|
||||
// Load mindmap data
|
||||
function loadMindmap() {
|
||||
fetch('/api/mindmap')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
renderMindmap(data[0]); // Assuming first item is the root node
|
||||
resetView();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading mindmap:', error);
|
||||
alert('Fehler beim Laden der Mindmap. Bitte die Seite neu laden.');
|
||||
});
|
||||
}
|
||||
|
||||
// Get node category
|
||||
function getNodeCategory(nodeName, rootCategories) {
|
||||
// Check if the node is one of the root categories
|
||||
if (nodeCategories[nodeName]) {
|
||||
return nodeCategories[nodeName];
|
||||
}
|
||||
|
||||
// Check parent categories for sub-nodes
|
||||
for (const category in rootCategories) {
|
||||
if (rootCategories[category].includes(nodeName)) {
|
||||
return nodeCategories[category];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Process data to track categories
|
||||
function processData(node, rootCategories = {}) {
|
||||
// Initialize categories for root node
|
||||
if (node.name in nodeCategories) {
|
||||
rootCategories[node.name] = [];
|
||||
}
|
||||
|
||||
// Record all children of a category
|
||||
if (node.children) {
|
||||
node.children.forEach(child => {
|
||||
// Add to parent category
|
||||
for (const category in rootCategories) {
|
||||
if (node.name === category || rootCategories[category].includes(node.name)) {
|
||||
rootCategories[category].push(child.name);
|
||||
}
|
||||
}
|
||||
// Process recursively
|
||||
processData(child, rootCategories);
|
||||
});
|
||||
}
|
||||
|
||||
return rootCategories;
|
||||
}
|
||||
|
||||
// Render the mindmap visualization
|
||||
function renderMindmap(data) {
|
||||
// Clear previous content
|
||||
g.selectAll('*').remove();
|
||||
|
||||
// Process data to track categories
|
||||
const rootCategories = processData(data);
|
||||
|
||||
// Create hierarchical layout
|
||||
const root = d3.hierarchy(data);
|
||||
|
||||
// Create radial layout instead of tree for all-direction branches
|
||||
const radius = Math.min(width, height) / 2 - 100;
|
||||
|
||||
const radialLayout = d3.cluster()
|
||||
.size([360, radius]);
|
||||
|
||||
radialLayout(root);
|
||||
|
||||
// Convert to Cartesian coordinates
|
||||
root.descendants().forEach(d => {
|
||||
// Convert from polar to Cartesian coordinates
|
||||
const angle = (d.x - 90) / 180 * Math.PI;
|
||||
d.x = d.y * Math.cos(angle);
|
||||
d.y = d.y * Math.sin(angle);
|
||||
});
|
||||
|
||||
// Create links
|
||||
const links = g.selectAll('.link')
|
||||
.data(root.links())
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('class', 'link')
|
||||
.attr('d', d => {
|
||||
return `M${d.source.x},${d.source.y}
|
||||
C${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2}
|
||||
${(d.source.x + d.target.x) / 2},${(d.source.y + d.target.y) / 2}
|
||||
${d.target.x},${d.target.y}`;
|
||||
});
|
||||
|
||||
// Create nodes
|
||||
const nodes = g.selectAll('.node')
|
||||
.data(root.descendants())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', d => {
|
||||
const categoryClass = getNodeCategory(d.data.name, rootCategories);
|
||||
return `node ${categoryClass} ${d.data.id === selectedNode ? 'node--selected' : ''}`;
|
||||
})
|
||||
.attr('transform', d => `translate(${d.x},${d.y})`)
|
||||
.on('click', (event, d) => selectNode(d.data.id, d.data.name))
|
||||
.on('mouseover', function(event, d) {
|
||||
tooltip.transition()
|
||||
.duration(200)
|
||||
.style('opacity', 0.9);
|
||||
tooltip.html(d.data.name)
|
||||
.style('left', (event.pageX + 10) + 'px')
|
||||
.style('top', (event.pageY - 28) + 'px');
|
||||
})
|
||||
.on('mouseout', function(d) {
|
||||
tooltip.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0);
|
||||
});
|
||||
|
||||
// Add circles to nodes
|
||||
nodes.append('circle')
|
||||
.attr('r', 20)
|
||||
.attr('class', d => (d.depth === 0) ? 'root-node' : ''); // Special class for root node
|
||||
|
||||
// Add text labels
|
||||
nodes.append('text')
|
||||
.attr('dy', 30)
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(d => d.data.name)
|
||||
.each(function(d) {
|
||||
// Wrap text for long labels
|
||||
const text = d3.select(this);
|
||||
const words = d.data.name.split(/\s+/);
|
||||
|
||||
if (words.length > 1) {
|
||||
text.text('');
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const tspan = text.append('tspan')
|
||||
.attr('x', 0)
|
||||
.attr('dy', i === 0 ? 30 : 15)
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(words[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add node count indicator (how many thoughts are associated)
|
||||
nodes.each(function(d) {
|
||||
const node = d3.select(this);
|
||||
|
||||
// Get thought count for this node (an API call would be needed)
|
||||
fetch(`/api/thoughts/${d.data.id}`)
|
||||
.then(response => response.json())
|
||||
.then(thoughts => {
|
||||
if (thoughts.length > 0) {
|
||||
// Add a small indicator
|
||||
node.append('circle')
|
||||
.attr('r', 8)
|
||||
.attr('cx', 20)
|
||||
.attr('cy', -20)
|
||||
.attr('fill', 'rgba(139, 92, 246, 0.9)');
|
||||
|
||||
node.append('text')
|
||||
.attr('x', 20)
|
||||
.attr('y', -16)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', 'white')
|
||||
.attr('font-size', '10px')
|
||||
.text(thoughts.length);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error(`Error loading thoughts for node ${d.data.id}:`, error));
|
||||
});
|
||||
}
|
||||
|
||||
// Handle node selection
|
||||
function selectNode(nodeId, nodeName) {
|
||||
selectedNode = nodeId;
|
||||
|
||||
// Update selected node in visualization
|
||||
g.selectAll('.node').classed('node--selected', d => d.data.id === nodeId);
|
||||
|
||||
// Update panel title
|
||||
document.getElementById('selected-node-title').textContent = nodeName;
|
||||
|
||||
// Load thoughts for this node
|
||||
loadThoughts(nodeId);
|
||||
|
||||
// Open the thoughts panel on mobile
|
||||
document.getElementById('thoughts-panel').classList.add('open');
|
||||
}
|
||||
|
||||
// Load thoughts for a node
|
||||
function loadThoughts(nodeId) {
|
||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||
thoughtsContainer.innerHTML = '<div class="text-center py-4"><div class="inline-block animate-spin rounded-full h-6 w-6 border-t-2 border-white"></div></div>';
|
||||
|
||||
fetch(`/api/thoughts/${nodeId}`)
|
||||
.then(response => response.json())
|
||||
.then(thoughts => {
|
||||
thoughtsContainer.innerHTML = '';
|
||||
|
||||
if (thoughts.length === 0) {
|
||||
thoughtsContainer.innerHTML = '<div class="text-center py-4 text-white/50"><p>Noch keine Gedanken zu diesem Thema</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
thoughts.forEach(thought => {
|
||||
const thoughtEl = document.createElement('div');
|
||||
thoughtEl.className = 'glass p-4 hover:shadow-lg transition-all';
|
||||
thoughtEl.innerHTML = `
|
||||
<p class="text-white mb-2">${thought.content}</p>
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<span class="text-white/70">Von ${thought.author}</span>
|
||||
<span class="text-white/50">${thought.timestamp}</span>
|
||||
</div>
|
||||
<div class="mt-2 text-right">
|
||||
<button class="text-indigo-300 hover:text-white text-sm" onclick="openCommentModal(${thought.id})">
|
||||
${thought.comments_count} Kommentar(e) anzeigen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
thoughtsContainer.appendChild(thoughtEl);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading thoughts:', error);
|
||||
thoughtsContainer.innerHTML = '<div class="text-center py-4 text-red-300"><p>Fehler beim Laden der Gedanken</p></div>';
|
||||
});
|
||||
}
|
||||
|
||||
// Submit a new thought
|
||||
function submitThought() {
|
||||
if (!selectedNode) {
|
||||
alert('Bitte wähle zuerst einen Knoten aus.');
|
||||
return;
|
||||
}
|
||||
|
||||
const thoughtInput = document.getElementById('thought-input');
|
||||
const content = thoughtInput.value.trim();
|
||||
|
||||
if (!content) {
|
||||
alert('Bitte gib einen Gedanken ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/thoughts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
node_id: selectedNode,
|
||||
content: content
|
||||
}),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
thoughtInput.value = '';
|
||||
loadThoughts(selectedNode); // Reload thoughts
|
||||
|
||||
// Refresh node counts in visualization
|
||||
loadMindmap();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding thought:', error);
|
||||
alert('Fehler beim Hinzufügen des Gedankens.');
|
||||
});
|
||||
}
|
||||
|
||||
// Open comment modal
|
||||
function openCommentModal(thoughtId) {
|
||||
currentThoughtId = thoughtId;
|
||||
|
||||
// Get thought details
|
||||
fetch(`/api/thought/${thoughtId}`)
|
||||
.then(response => response.json())
|
||||
.then(thought => {
|
||||
document.getElementById('comment-thought-content').textContent = thought.content;
|
||||
document.getElementById('comment-thought-author').textContent = `Von ${thought.author}`;
|
||||
document.getElementById('comment-thought-time').textContent = thought.timestamp;
|
||||
|
||||
// Get comments
|
||||
return fetch(`/api/comments/${thoughtId}`);
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(comments => {
|
||||
const commentsList = document.getElementById('comments-list');
|
||||
commentsList.innerHTML = '';
|
||||
|
||||
if (comments.length === 0) {
|
||||
commentsList.innerHTML = '<p class="text-center text-white/50 py-3">Keine Kommentare vorhanden</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
comments.forEach(comment => {
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.className = 'glass p-3 mb-2';
|
||||
commentEl.innerHTML = `
|
||||
<p class="text-white text-sm">${comment.content}</p>
|
||||
<div class="flex justify-between items-center mt-1 text-xs">
|
||||
<span class="text-white/70">${comment.author}</span>
|
||||
<span class="text-white/50">${comment.timestamp}</span>
|
||||
</div>
|
||||
`;
|
||||
commentsList.appendChild(commentEl);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading comments:', error));
|
||||
|
||||
document.getElementById('commentModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Close comment modal
|
||||
function closeCommentModal() {
|
||||
document.getElementById('commentModal').classList.add('hidden');
|
||||
currentThoughtId = null;
|
||||
}
|
||||
|
||||
// Submit a new comment
|
||||
function submitComment() {
|
||||
if (!currentThoughtId) return;
|
||||
|
||||
const commentInput = document.getElementById('comment-input');
|
||||
const content = commentInput.value.trim();
|
||||
|
||||
if (!content) {
|
||||
alert('Bitte gib einen Kommentar ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/comments', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
thought_id: currentThoughtId,
|
||||
content: content
|
||||
}),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
commentInput.value = '';
|
||||
|
||||
// Refresh comments
|
||||
fetch(`/api/comments/${currentThoughtId}`)
|
||||
.then(response => response.json())
|
||||
.then(comments => {
|
||||
const commentsList = document.getElementById('comments-list');
|
||||
commentsList.innerHTML = '';
|
||||
|
||||
comments.forEach(comment => {
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.className = 'glass p-3 mb-2';
|
||||
commentEl.innerHTML = `
|
||||
<p class="text-white text-sm">${comment.content}</p>
|
||||
<div class="flex justify-between items-center mt-1 text-xs">
|
||||
<span class="text-white/70">${comment.author}</span>
|
||||
<span class="text-white/50">${comment.timestamp}</span>
|
||||
</div>
|
||||
`;
|
||||
commentsList.appendChild(commentEl);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error refreshing comments:', error));
|
||||
|
||||
// Refresh thoughts since comment count changed
|
||||
if (selectedNode) {
|
||||
loadThoughts(selectedNode);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error adding comment:', error);
|
||||
alert('Fehler beim Hinzufügen des Kommentars.');
|
||||
});
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load mindmap on page load
|
||||
loadMindmap();
|
||||
|
||||
// Submit thought
|
||||
document.getElementById('submit-thought')?.addEventListener('click', submitThought);
|
||||
|
||||
// Submit comment
|
||||
document.getElementById('submit-comment')?.addEventListener('click', submitComment);
|
||||
|
||||
// Close panel on mobile
|
||||
document.getElementById('close-panel')?.addEventListener('click', function() {
|
||||
document.getElementById('thoughts-panel').classList.remove('open');
|
||||
});
|
||||
|
||||
// Zoom controls
|
||||
document.getElementById('zoom-in')?.addEventListener('click', function() {
|
||||
svg.transition().duration(300).call(zoom.scaleBy, 1.3);
|
||||
});
|
||||
|
||||
document.getElementById('zoom-out')?.addEventListener('click', function() {
|
||||
svg.transition().duration(300).call(zoom.scaleBy, 0.7);
|
||||
});
|
||||
|
||||
document.getElementById('reset-view')?.addEventListener('click', resetView);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,131 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Profil | Wissenschaftliche Mindmap{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="glass p-8 mb-8">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-white">Hallo, {{ current_user.username }}</h1>
|
||||
<p class="text-white/70 mt-1">{{ current_user.email }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 md:mt-0">
|
||||
<a href="{{ url_for('mindmap') }}"
|
||||
class="bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300 inline-block">
|
||||
Zur Mindmap
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dark-glass p-8">
|
||||
<h2 class="text-2xl font-bold text-white mb-6">Deine Gedanken</h2>
|
||||
|
||||
{% if thoughts %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for thought in thoughts %}
|
||||
<div class="glass p-6 hover:shadow-lg transition-all" x-data="{ showActions: false }" @mouseenter="showActions = true" @mouseleave="showActions = false">
|
||||
<div class="flex justify-between items-start">
|
||||
<span class="inline-block px-3 py-1 text-xs text-white/70 bg-white/10 rounded-full mb-3">{{ thought.branch }}</span>
|
||||
<span class="text-xs text-white/50">{{ thought.timestamp.strftime('%d.%m.%Y, %H:%M') }}</span>
|
||||
</div>
|
||||
<p class="text-white mb-4 leading-relaxed">{{ thought.content }}</p>
|
||||
|
||||
<div class="flex justify-between items-center" x-show="showActions" x-transition.opacity>
|
||||
<div class="text-xs text-white/70">
|
||||
<span>{{ thought.comments|length }} Kommentar(e)</span>
|
||||
</div>
|
||||
|
||||
<a href="#" onclick="openThoughtDetails('{{ thought.id }}')" class="text-indigo-300 hover:text-white text-sm">Details anzeigen</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-white/70 mb-4">Du hast noch keine Gedanken geteilt.</p>
|
||||
<a href="{{ url_for('mindmap') }}" class="text-indigo-300 hover:text-white">Zur Mindmap gehen und mitmachen</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Thought Detail Modal -->
|
||||
<div id="thoughtModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
|
||||
<div class="dark-glass p-8 w-full max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-2xl font-bold text-white" id="modalThoughtTitle">Gedanke Details</h3>
|
||||
<button onclick="closeThoughtModal()" class="text-white/70 hover:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="modalContent" class="space-y-6">
|
||||
<div class="glass p-4">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<span class="inline-block px-3 py-1 text-xs text-white/70 bg-white/10 rounded-full" id="modalBranch"></span>
|
||||
<span class="text-xs text-white/50" id="modalTimestamp"></span>
|
||||
</div>
|
||||
<p class="text-white" id="modalThoughtContent"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-lg font-medium text-white mb-3">Kommentare</h4>
|
||||
<div id="commentsList" class="space-y-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function openThoughtDetails(thoughtId) {
|
||||
fetch(`/api/thoughts/${thoughtId}`)
|
||||
.then(response => response.json())
|
||||
.then(thought => {
|
||||
document.getElementById('modalThoughtTitle').textContent = `Gedanke von ${thought.author}`;
|
||||
document.getElementById('modalBranch').textContent = thought.branch;
|
||||
document.getElementById('modalTimestamp').textContent = thought.timestamp;
|
||||
document.getElementById('modalThoughtContent').textContent = thought.content;
|
||||
|
||||
// Load comments
|
||||
return fetch(`/api/comments/${thoughtId}`);
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(comments => {
|
||||
const commentsList = document.getElementById('commentsList');
|
||||
commentsList.innerHTML = '';
|
||||
|
||||
if (comments.length === 0) {
|
||||
commentsList.innerHTML = '<p class="text-white/50">Keine Kommentare vorhanden</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
comments.forEach(comment => {
|
||||
const commentEl = document.createElement('div');
|
||||
commentEl.className = 'glass p-3';
|
||||
commentEl.innerHTML = `
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<span class="text-sm font-medium text-white">${comment.author}</span>
|
||||
<span class="text-xs text-white/50">${comment.timestamp}</span>
|
||||
</div>
|
||||
<p class="text-white/90 text-sm">${comment.content}</p>
|
||||
`;
|
||||
commentsList.appendChild(commentEl);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading thought details:', error));
|
||||
|
||||
document.getElementById('thoughtModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeThoughtModal() {
|
||||
document.getElementById('thoughtModal').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,47 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Registrieren | Wissenschaftliche Mindmap{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex justify-center items-center min-h-[80vh]">
|
||||
<div class="glass p-8 w-full max-w-md">
|
||||
<h1 class="text-3xl font-bold text-white mb-6 text-center">Registrieren</h1>
|
||||
|
||||
<form method="POST" action="{{ url_for('register') }}" class="space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-white mb-1">Benutzername</label>
|
||||
<input type="text" id="username" name="username" required
|
||||
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-white mb-1">E-Mail</label>
|
||||
<input type="email" id="email" name="email" required
|
||||
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-white mb-1">Passwort</label>
|
||||
<input type="password" id="password" name="password" required
|
||||
class="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit"
|
||||
class="w-full bg-gradient-to-r from-indigo-500 to-purple-600 hover:from-indigo-600 hover:to-purple-700 text-white font-semibold px-6 py-3 rounded-lg transition-all duration-300">
|
||||
Registrieren
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-white/70">
|
||||
Bereits registriert?
|
||||
<a href="{{ url_for('login') }}" class="text-indigo-300 hover:text-white font-medium transition-colors">
|
||||
Anmelden
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,35 +0,0 @@
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
return """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Seite</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }
|
||||
.container { max-width: 800px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
|
||||
h1 { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Test Seite funktioniert!</h1>
|
||||
<p>Wenn Sie diese Seite sehen können, funktioniert der grundlegende Flask-Server korrekt.</p>
|
||||
<p>Server-Status:</p>
|
||||
<ul>
|
||||
<li>Flask läuft auf Port 5000</li>
|
||||
<li>Keine Datenbankverbindung erforderlich</li>
|
||||
<li>Keine Templates erforderlich</li>
|
||||
</ul>
|
||||
<p>Versuchen Sie, diese URL in verschiedenen Browsern zu öffnen.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
Reference in New Issue
Block a user