-
+
+
+
+
Leverandør Fordeling
+
-
-
-
-
- Alle systemer kører optimalt.
-
+
+
+
+
-{% endblock %}
\ No newline at end of file
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
diff --git a/app/settings/backend/router.py b/app/settings/backend/router.py
new file mode 100644
index 0000000..f5a8361
--- /dev/null
+++ b/app/settings/backend/router.py
@@ -0,0 +1,239 @@
+"""
+Settings and User Management API Router
+"""
+
+from fastapi import APIRouter, HTTPException
+from typing import List, Optional, Dict
+from pydantic import BaseModel
+from app.core.database import execute_query
+import logging
+
+logger = logging.getLogger(__name__)
+router = APIRouter()
+
+
+# Pydantic Models
+class Setting(BaseModel):
+ id: int
+ key: str
+ value: Optional[str]
+ category: str
+ description: Optional[str]
+ value_type: str
+ is_public: bool
+
+
+class SettingUpdate(BaseModel):
+ value: str
+
+
+class User(BaseModel):
+ id: int
+ username: str
+ email: Optional[str]
+ full_name: Optional[str]
+ is_active: bool
+ last_login: Optional[str]
+ created_at: str
+
+
+class UserCreate(BaseModel):
+ username: str
+ email: str
+ password: str
+ full_name: Optional[str] = None
+
+
+class UserUpdate(BaseModel):
+ email: Optional[str] = None
+ full_name: Optional[str] = None
+ is_active: Optional[bool] = None
+
+
+# Settings Endpoints
+@router.get("/settings", response_model=List[Setting], tags=["Settings"])
+async def get_settings(category: Optional[str] = None):
+ """Get all settings or filter by category"""
+ query = "SELECT * FROM settings"
+ params = []
+
+ if category:
+ query += " WHERE category = %s"
+ params.append(category)
+
+ query += " ORDER BY category, key"
+ result = execute_query(query, tuple(params) if params else None)
+ return result or []
+
+
+@router.get("/settings/{key}", response_model=Setting, tags=["Settings"])
+async def get_setting(key: str):
+ """Get a specific setting by key"""
+ query = "SELECT * FROM settings WHERE key = %s"
+ result = execute_query(query, (key,))
+
+ if not result:
+ raise HTTPException(status_code=404, detail="Setting not found")
+
+ return result[0]
+
+
+@router.put("/settings/{key}", response_model=Setting, tags=["Settings"])
+async def update_setting(key: str, setting: SettingUpdate):
+ """Update a setting value"""
+ query = """
+ UPDATE settings
+ SET value = %s, updated_at = CURRENT_TIMESTAMP
+ WHERE key = %s
+ RETURNING *
+ """
+ result = execute_query(query, (setting.value, key))
+
+ if not result:
+ raise HTTPException(status_code=404, detail="Setting not found")
+
+ logger.info(f"✅ Updated setting: {key}")
+ return result[0]
+
+
+@router.get("/settings/categories/list", tags=["Settings"])
+async def get_setting_categories():
+ """Get list of all setting categories"""
+ query = "SELECT DISTINCT category FROM settings ORDER BY category"
+ result = execute_query(query)
+ return [row['category'] for row in result] if result else []
+
+
+# User Management Endpoints
+@router.get("/users", response_model=List[User], tags=["Users"])
+async def get_users(is_active: Optional[bool] = None):
+ """Get all users"""
+ query = "SELECT user_id as id, username, email, full_name, is_active, last_login, created_at FROM users"
+ params = []
+
+ if is_active is not None:
+ query += " WHERE is_active = %s"
+ params.append(is_active)
+
+ query += " ORDER BY username"
+ result = execute_query(query, tuple(params) if params else None)
+ return result or []
+
+
+@router.get("/users/{user_id}", response_model=User, tags=["Users"])
+async def get_user(user_id: int):
+ """Get user by ID"""
+ query = "SELECT user_id as id, username, email, full_name, is_active, last_login, created_at FROM users WHERE user_id = %s"
+ result = execute_query(query, (user_id,))
+
+ if not result:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ return result[0]
+
+
+@router.post("/users", response_model=User, tags=["Users"])
+async def create_user(user: UserCreate):
+ """Create a new user"""
+ # Check if username exists
+ existing = execute_query("SELECT user_id FROM users WHERE username = %s", (user.username,))
+ if existing:
+ raise HTTPException(status_code=400, detail="Username already exists")
+
+ # Hash password (simple SHA256 for now - should use bcrypt in production)
+ import hashlib
+ password_hash = hashlib.sha256(user.password.encode()).hexdigest()
+
+ query = """
+ INSERT INTO users (username, email, password_hash, full_name, is_active)
+ VALUES (%s, %s, %s, %s, true)
+ RETURNING user_id as id, username, email, full_name, is_active, last_login, created_at
+ """
+ result = execute_query(query, (user.username, user.email, password_hash, user.full_name))
+
+ if not result:
+ raise HTTPException(status_code=500, detail="Failed to create user")
+
+ logger.info(f"✅ Created user: {user.username}")
+ return result[0]
+
+
+@router.put("/users/{user_id}", response_model=User, tags=["Users"])
+async def update_user(user_id: int, user: UserUpdate):
+ """Update user details"""
+ # Check if user exists
+ existing = execute_query("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
+ if not existing:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ # Build update query
+ update_fields = []
+ params = []
+
+ if user.email is not None:
+ update_fields.append("email = %s")
+ params.append(user.email)
+ if user.full_name is not None:
+ update_fields.append("full_name = %s")
+ params.append(user.full_name)
+ if user.is_active is not None:
+ update_fields.append("is_active = %s")
+ params.append(user.is_active)
+
+ if not update_fields:
+ raise HTTPException(status_code=400, detail="No fields to update")
+
+ params.append(user_id)
+ query = f"""
+ UPDATE users
+ SET {', '.join(update_fields)}, updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = %s
+ RETURNING user_id as id, username, email, full_name, is_active, last_login, created_at
+ """
+
+ result = execute_query(query, tuple(params))
+
+ if not result:
+ raise HTTPException(status_code=500, detail="Failed to update user")
+
+ logger.info(f"✅ Updated user: {user_id}")
+ return result[0]
+
+
+@router.delete("/users/{user_id}", tags=["Users"])
+async def deactivate_user(user_id: int):
+ """Deactivate a user (soft delete)"""
+ query = """
+ UPDATE users
+ SET is_active = false, updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = %s
+ RETURNING user_id as id
+ """
+ result = execute_query(query, (user_id,))
+
+ if not result:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ logger.info(f"✅ Deactivated user: {user_id}")
+ return {"message": "User deactivated successfully"}
+
+
+@router.post("/users/{user_id}/reset-password", tags=["Users"])
+async def reset_user_password(user_id: int, new_password: str):
+ """Reset user password"""
+ import hashlib
+ password_hash = hashlib.sha256(new_password.encode()).hexdigest()
+
+ query = """
+ UPDATE users
+ SET password_hash = %s, updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = %s
+ RETURNING user_id as id
+ """
+ result = execute_query(query, (password_hash, user_id))
+
+ if not result:
+ raise HTTPException(status_code=404, detail="User not found")
+
+ logger.info(f"✅ Reset password for user: {user_id}")
+ return {"message": "Password reset successfully"}
diff --git a/app/settings/backend/views.py b/app/settings/backend/views.py
new file mode 100644
index 0000000..a8f396b
--- /dev/null
+++ b/app/settings/backend/views.py
@@ -0,0 +1,19 @@
+"""
+Settings Frontend Views
+"""
+
+from fastapi import APIRouter, Request
+from fastapi.responses import HTMLResponse
+from fastapi.templating import Jinja2Templates
+
+router = APIRouter()
+templates = Jinja2Templates(directory="app")
+
+
+@router.get("/settings", response_class=HTMLResponse, tags=["Frontend"])
+async def settings_page(request: Request):
+ """Render settings page"""
+ return templates.TemplateResponse("settings/frontend/settings.html", {
+ "request": request,
+ "title": "Indstillinger"
+ })
diff --git a/app/settings/frontend/settings.html b/app/settings/frontend/settings.html
new file mode 100644
index 0000000..e2b5a83
--- /dev/null
+++ b/app/settings/frontend/settings.html
@@ -0,0 +1,508 @@
+{% extends "shared/frontend/base.html" %}
+
+{% block title %}Indstillinger - BMC Hub{% endblock %}
+
+{% block extra_css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
Indstillinger
+
System konfiguration og brugerstyring
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Notifikation Indstillinger
+
+
+
+
+
+
+
+
+
Brugerstyring
+
+
+
+
+
+
+ | Bruger |
+ Email |
+ Status |
+ Sidst Login |
+ Oprettet |
+ Handlinger |
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
System Indstillinger
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+
+{% endblock %}
diff --git a/app/shared/frontend/base.html b/app/shared/frontend/base.html
index a5f8ca3..37dc81d 100644
--- a/app/shared/frontend/base.html
+++ b/app/shared/frontend/base.html
@@ -170,7 +170,7 @@