User Log and Persistent Faction Information
This commit is contained in:
BIN
routers/__pycache__/activity.cpython-311.pyc
Normal file
BIN
routers/__pycache__/activity.cpython-311.pyc
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.
52
routers/activity.py
Normal file
52
routers/activity.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Activity log endpoints."""
|
||||
from fastapi import APIRouter, Request
|
||||
from pydantic import BaseModel
|
||||
from utils.auth import get_current_user
|
||||
from services.activity_log import activity_logger
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["activity"])
|
||||
|
||||
|
||||
class LogActionRequest(BaseModel):
|
||||
action: str
|
||||
details: str = ""
|
||||
|
||||
|
||||
@router.get("/active_users")
|
||||
async def get_active_users(request: Request):
|
||||
"""Get list of currently active users"""
|
||||
# Update current user's activity
|
||||
try:
|
||||
user_info = get_current_user(request)
|
||||
username = user_info.get("username", "Unknown")
|
||||
await activity_logger.update_user_activity(username)
|
||||
except:
|
||||
pass
|
||||
|
||||
users = await activity_logger.get_active_users(timeout_minutes=30)
|
||||
return {"users": users}
|
||||
|
||||
|
||||
@router.get("/activity_logs")
|
||||
async def get_activity_logs(request: Request, limit: int = 100):
|
||||
"""Get recent activity logs"""
|
||||
# Update current user's activity
|
||||
try:
|
||||
user_info = get_current_user(request)
|
||||
username = user_info.get("username", "Unknown")
|
||||
await activity_logger.update_user_activity(username)
|
||||
except:
|
||||
pass
|
||||
|
||||
logs = await activity_logger.get_logs(limit=limit)
|
||||
return {"logs": logs}
|
||||
|
||||
|
||||
@router.post("/log_action")
|
||||
async def log_action(request: Request, req: LogActionRequest):
|
||||
"""Log a user action"""
|
||||
user_info = get_current_user(request)
|
||||
username = user_info.get("username", "Unknown")
|
||||
|
||||
await activity_logger.log_action(username, req.action, req.details)
|
||||
return {"status": "ok"}
|
||||
@@ -20,7 +20,7 @@ class LoginRequest(BaseModel):
|
||||
|
||||
|
||||
def get_client_ip(request: Request) -> str:
|
||||
"""Get client IP address from request"""
|
||||
#Get client IP address from request
|
||||
# Check X-Forwarded-For header first (for proxy/load balancer)
|
||||
forwarded = request.headers.get("X-Forwarded-For")
|
||||
if forwarded:
|
||||
@@ -29,7 +29,7 @@ def get_client_ip(request: Request) -> str:
|
||||
|
||||
|
||||
def is_locked_out(ip: str) -> bool:
|
||||
"""Check if IP is currently locked out"""
|
||||
#Check if IP is currently locked out
|
||||
if ip not in failed_attempts:
|
||||
return False
|
||||
|
||||
@@ -49,7 +49,7 @@ def is_locked_out(ip: str) -> bool:
|
||||
|
||||
|
||||
def record_failed_attempt(ip: str):
|
||||
"""Record a failed login attempt"""
|
||||
#Record a failed login attempt
|
||||
now = datetime.now()
|
||||
|
||||
if ip not in failed_attempts:
|
||||
@@ -64,13 +64,13 @@ def record_failed_attempt(ip: str):
|
||||
|
||||
|
||||
def clear_failed_attempts(ip: str):
|
||||
"""Clear failed attempts for an IP after successful login"""
|
||||
#Clear failed attempts for an IP after successful login
|
||||
if ip in failed_attempts:
|
||||
del failed_attempts[ip]
|
||||
|
||||
|
||||
def create_jwt_token(username: str) -> str:
|
||||
"""Create a JWT token for the user"""
|
||||
#Create a JWT token for the user
|
||||
expiration = datetime.utcnow() + timedelta(days=7) # Token valid for 7 days
|
||||
payload = {
|
||||
"username": username,
|
||||
@@ -81,7 +81,7 @@ def create_jwt_token(username: str) -> str:
|
||||
|
||||
|
||||
def verify_jwt_token(token: str) -> dict:
|
||||
"""Verify and decode a JWT token"""
|
||||
#Verify and decode a JWT token
|
||||
try:
|
||||
payload = jwt.decode(token, config_module.JWT_SECRET, algorithms=["HS256"])
|
||||
return payload
|
||||
@@ -93,7 +93,7 @@ def verify_jwt_token(token: str) -> dict:
|
||||
|
||||
@router.post("/login")
|
||||
async def login(request: Request, response: Response, req: LoginRequest):
|
||||
"""Login endpoint with rate limiting"""
|
||||
#Login endpoint with rate limiting
|
||||
client_ip = get_client_ip(request)
|
||||
|
||||
# Check if IP is locked out
|
||||
@@ -144,14 +144,14 @@ async def login(request: Request, response: Response, req: LoginRequest):
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(response: Response):
|
||||
"""Logout endpoint"""
|
||||
#Logout endpoint
|
||||
response.delete_cookie("auth_token")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def auth_status(request: Request):
|
||||
"""Check authentication status"""
|
||||
#Check authentication status
|
||||
token = request.cookies.get("auth_token")
|
||||
|
||||
if not token:
|
||||
|
||||
@@ -20,7 +20,7 @@ def set_assignment_manager(manager: BotAssignmentManager):
|
||||
|
||||
@router.get("/bot_status")
|
||||
async def api_bot_status():
|
||||
"""Get current bot status"""
|
||||
#Get current bot status
|
||||
active_count = len(assignment_manager.active_targets) if assignment_manager else 0
|
||||
return {
|
||||
"bot_running": STATE.bot_running,
|
||||
|
||||
@@ -10,7 +10,7 @@ router = APIRouter(prefix="/api", tags=["config"])
|
||||
|
||||
|
||||
def reload_config_from_file():
|
||||
"""Reload config values from JSON into module globals"""
|
||||
#Reload config values from JSON into module globals
|
||||
path = Path("data/config.json")
|
||||
if not path.exists():
|
||||
return
|
||||
@@ -29,7 +29,7 @@ def reload_config_from_file():
|
||||
|
||||
@router.get("/config")
|
||||
async def get_config():
|
||||
"""Get all config values (with sensitive values masked)"""
|
||||
#Get all config values (with sensitive values masked)
|
||||
path = Path("data/config.json")
|
||||
|
||||
# Default config values from config.py
|
||||
@@ -70,7 +70,7 @@ async def get_config():
|
||||
|
||||
@router.post("/config")
|
||||
async def update_config(req: ConfigUpdateRequest):
|
||||
"""Update a single config value"""
|
||||
#Update a single config value
|
||||
path = Path("data/config.json")
|
||||
|
||||
# Valid config keys (from config.py)
|
||||
|
||||
@@ -14,14 +14,14 @@ assignment_manager: Optional[BotAssignmentManager] = None
|
||||
|
||||
|
||||
def set_assignment_manager(manager: BotAssignmentManager):
|
||||
"""Set the global assignment manager reference."""
|
||||
#Set the global assignment manager reference.
|
||||
global assignment_manager
|
||||
assignment_manager = manager
|
||||
|
||||
|
||||
@router.get("/discord_mappings")
|
||||
async def get_discord_mappings():
|
||||
"""Get all Torn ID to Discord ID mappings"""
|
||||
#Get all Torn ID to Discord ID mappings
|
||||
path = Path("data/discord_mapping.json")
|
||||
if not path.exists():
|
||||
return {"mappings": {}}
|
||||
@@ -33,7 +33,7 @@ async def get_discord_mappings():
|
||||
|
||||
@router.post("/discord_mapping")
|
||||
async def add_discord_mapping(req: DiscordMappingRequest):
|
||||
"""Add or update a Torn ID to Discord ID mapping"""
|
||||
#Add or update a Torn ID to Discord ID mapping
|
||||
path = Path("data/discord_mapping.json")
|
||||
|
||||
# Load existing mappings
|
||||
@@ -59,7 +59,7 @@ async def add_discord_mapping(req: DiscordMappingRequest):
|
||||
|
||||
@router.delete("/discord_mapping/{torn_id}")
|
||||
async def remove_discord_mapping(torn_id: int):
|
||||
"""Remove a Discord mapping"""
|
||||
#Remove a Discord mapping
|
||||
path = Path("data/discord_mapping.json")
|
||||
|
||||
if not path.exists():
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
"""Faction data population and status management endpoints."""
|
||||
#Faction data population and status management endpoints.
|
||||
import json
|
||||
from pathlib import Path
|
||||
from fastapi import APIRouter
|
||||
|
||||
from models import FactionRequest
|
||||
from services.server_state import STATE
|
||||
from services.torn_api import populate_friendly, populate_enemy, start_friendly_status_loop, start_enemy_status_loop
|
||||
from services.torn_api import (
|
||||
populate_friendly,
|
||||
populate_enemy,
|
||||
start_friendly_status_loop,
|
||||
start_enemy_status_loop,
|
||||
stop_friendly_status_loop,
|
||||
stop_enemy_status_loop
|
||||
)
|
||||
from utils import load_json_list
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["factions"])
|
||||
@@ -73,3 +80,30 @@ async def api_enemy_status():
|
||||
return {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@router.post("/stop_friendly_status")
|
||||
async def api_stop_friendly_status():
|
||||
await stop_friendly_status_loop()
|
||||
return {"status": "friendly status loop stopped"}
|
||||
|
||||
|
||||
@router.post("/stop_enemy_status")
|
||||
async def api_stop_enemy_status():
|
||||
await stop_enemy_status_loop()
|
||||
return {"status": "enemy status loop stopped"}
|
||||
|
||||
|
||||
@router.get("/dashboard_state")
|
||||
async def get_dashboard_state():
|
||||
"""Get current dashboard state for restoring UI on page load"""
|
||||
return {
|
||||
"friendly_faction_id": STATE.friendly_faction_id,
|
||||
"enemy_faction_id": STATE.enemy_faction_id,
|
||||
"friendly_members": [m.model_dump() for m in STATE.friendly.values()],
|
||||
"enemy_members": [m.model_dump() for m in STATE.enemy.values()],
|
||||
"friendly_status_interval": STATE.friendly_status_interval,
|
||||
"enemy_status_interval": STATE.enemy_status_interval,
|
||||
"friendly_status_running": STATE.friendly_status_running,
|
||||
"enemy_status_running": STATE.enemy_status_running
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
"""Login page"""
|
||||
#Login page
|
||||
# If already authenticated, redirect to dashboard
|
||||
if check_auth(request):
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
@@ -19,7 +19,7 @@ async def login_page(request: Request):
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def dashboard(request: Request):
|
||||
"""Dashboard page - requires authentication"""
|
||||
#Dashboard page - requires authentication
|
||||
if not check_auth(request):
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
print(">>> DASHBOARD ROUTE LOADED")
|
||||
@@ -28,7 +28,15 @@ async def dashboard(request: Request):
|
||||
|
||||
@router.get("/config", response_class=HTMLResponse)
|
||||
async def config_page(request: Request):
|
||||
"""Config page - requires authentication"""
|
||||
#Config page - requires authentication
|
||||
if not check_auth(request):
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
return templates.TemplateResponse("config.html", {"request": request})
|
||||
|
||||
|
||||
@router.get("/users-log", response_class=HTMLResponse)
|
||||
async def users_log_page(request: Request):
|
||||
#Users/Log page - requires authentication
|
||||
if not check_auth(request):
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
return templates.TemplateResponse("users_log.html", {"request": request})
|
||||
|
||||
Reference in New Issue
Block a user