Add complete project structure and all files
This commit is contained in:
754
backend/app/api/settings.py
Normal file
754
backend/app/api/settings.py
Normal file
@@ -0,0 +1,754 @@
|
||||
"""
|
||||
Settings API endpoints
|
||||
User account settings and panel configuration
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request, Query
|
||||
from fastapi.responses import HTMLResponse, Response
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from typing import Optional
|
||||
|
||||
from ..db import get_db
|
||||
from ..models import User, PanelSettings
|
||||
from ..auth import get_current_user, get_current_user_optional, get_password_hash, verify_password, PermissionChecker
|
||||
from ..logging_service import LoggingService
|
||||
from ..template_helpers import add_i18n_context
|
||||
from ..i18n import I18nService, t
|
||||
from ..services.logo_service import LogoService
|
||||
from ..services.update_service import (
|
||||
get_current_version,
|
||||
check_for_updates,
|
||||
perform_update,
|
||||
get_update_status,
|
||||
reset_update_status
|
||||
)
|
||||
|
||||
|
||||
def get_local_time() -> str:
|
||||
"""Get current local time formatted string"""
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
|
||||
# ==================== Helper Functions ====================
|
||||
|
||||
def get_setting(db: Session, key: str, default: str = None) -> str:
|
||||
"""Get setting value from database"""
|
||||
setting = db.query(PanelSettings).filter(PanelSettings.key == key).first()
|
||||
if setting:
|
||||
return setting.value
|
||||
return default
|
||||
|
||||
|
||||
def set_setting(db: Session, key: str, value: str, description: str = None):
|
||||
"""Set setting value in database"""
|
||||
setting = db.query(PanelSettings).filter(PanelSettings.key == key).first()
|
||||
if setting:
|
||||
setting.value = value
|
||||
if description:
|
||||
setting.description = description
|
||||
else:
|
||||
setting = PanelSettings(key=key, value=value, description=description)
|
||||
db.add(setting)
|
||||
db.commit()
|
||||
|
||||
|
||||
# ==================== Schemas ====================
|
||||
|
||||
class UpdateProfileRequest(BaseModel):
|
||||
full_name: Optional[str] = Field(None, max_length=100)
|
||||
email: Optional[EmailStr] = None
|
||||
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
current_password: str = Field(..., min_length=1)
|
||||
new_password: str = Field(..., min_length=6)
|
||||
confirm_password: str = Field(..., min_length=6)
|
||||
|
||||
|
||||
class PanelSettingsResponse(BaseModel):
|
||||
panel_name: str
|
||||
refresh_interval: int # seconds
|
||||
log_retention_days: int
|
||||
language: str # ru or en
|
||||
|
||||
|
||||
class UpdatePanelSettingsRequest(BaseModel):
|
||||
refresh_interval: Optional[int] = Field(None, ge=1, le=60)
|
||||
log_retention_days: Optional[int] = Field(None, ge=1, le=365)
|
||||
language: Optional[str] = Field(None, pattern='^(ru|en)$')
|
||||
|
||||
|
||||
# ==================== Logo Protection ====================
|
||||
|
||||
@router.get("/api/logo.png")
|
||||
async def get_protected_logo():
|
||||
"""
|
||||
Get protected logo image.
|
||||
Returns the verified original logo or a fallback if tampered.
|
||||
"""
|
||||
logo_bytes = LogoService.get_logo_bytes()
|
||||
return Response(
|
||||
content=logo_bytes,
|
||||
media_type="image/png",
|
||||
headers={
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
"X-Logo-Protected": "true"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ==================== HTML Pages ====================
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def settings_page(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional)
|
||||
):
|
||||
"""Render settings page"""
|
||||
from ..template_helpers import get_language
|
||||
lang = get_language(request)
|
||||
context = {
|
||||
"request": request,
|
||||
"title": t("nav_settings", lang),
|
||||
"page_title": f"⚙️ {t('nav_settings', lang)}",
|
||||
"current_user": current_user
|
||||
}
|
||||
# Add i18n context
|
||||
context = add_i18n_context(request, context)
|
||||
|
||||
return templates.TemplateResponse("settings.html", context)
|
||||
|
||||
|
||||
# ==================== User Account Settings ====================
|
||||
|
||||
@router.get("/api/profile")
|
||||
async def get_profile(
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get current user profile"""
|
||||
return {
|
||||
"id": current_user.id,
|
||||
"username": current_user.username,
|
||||
"email": current_user.email,
|
||||
"full_name": current_user.full_name,
|
||||
"is_admin": current_user.is_admin,
|
||||
"is_active": current_user.is_active,
|
||||
"created_at": current_user.created_at.isoformat() if current_user.created_at else None,
|
||||
"last_login": current_user.last_login.isoformat() if current_user.last_login else None
|
||||
}
|
||||
|
||||
|
||||
@router.put("/api/profile")
|
||||
async def update_profile(
|
||||
data: UpdateProfileRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update user profile"""
|
||||
updated_fields = []
|
||||
|
||||
if data.full_name is not None:
|
||||
current_user.full_name = data.full_name
|
||||
updated_fields.append("full_name")
|
||||
|
||||
if data.email is not None:
|
||||
# Check if email is already taken by another user
|
||||
existing = db.query(User).filter(
|
||||
User.email == data.email,
|
||||
User.id != current_user.id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email уже используется другим пользователем"
|
||||
)
|
||||
current_user.email = data.email
|
||||
updated_fields.append("email")
|
||||
|
||||
if updated_fields:
|
||||
db.commit()
|
||||
db.refresh(current_user)
|
||||
|
||||
# Log profile update
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.AUTH,
|
||||
action="profile_update",
|
||||
message=f"Пользователь {current_user.username} обновил профиль: {', '.join(updated_fields)}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Профиль обновлён",
|
||||
"updated_fields": updated_fields
|
||||
}
|
||||
|
||||
|
||||
@router.post("/api/change-password")
|
||||
async def change_password(
|
||||
data: ChangePasswordRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Change user password"""
|
||||
|
||||
# Verify current password
|
||||
if not verify_password(data.current_password, current_user.hashed_password):
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.WARNING,
|
||||
category=LoggingService.AUTH,
|
||||
action="password_change_failed",
|
||||
message=f"Неудачная попытка смены пароля (неверный текущий пароль)",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id,
|
||||
success=False
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Неверный текущий пароль"
|
||||
)
|
||||
|
||||
# Check new password confirmation
|
||||
if data.new_password != data.confirm_password:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Новые пароли не совпадают"
|
||||
)
|
||||
|
||||
# Update password
|
||||
current_user.hashed_password = get_password_hash(data.new_password)
|
||||
db.commit()
|
||||
|
||||
# Log password change
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.AUTH,
|
||||
action="password_change",
|
||||
message=f"Пользователь {current_user.username} изменил пароль",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id,
|
||||
success=True
|
||||
)
|
||||
|
||||
return {"message": "Пароль успешно изменён"}
|
||||
|
||||
|
||||
# ==================== Panel Settings ====================
|
||||
|
||||
@router.get("/api/panel", response_model=PanelSettingsResponse)
|
||||
async def get_panel_settings(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(PermissionChecker("settings.view"))
|
||||
):
|
||||
"""Get panel settings"""
|
||||
from ..config import settings
|
||||
|
||||
# Get settings from database or use defaults
|
||||
refresh_interval = get_setting(db, "refresh_interval", "5")
|
||||
log_retention_days = get_setting(db, "log_retention_days", "30")
|
||||
language = get_setting(db, "language", "ru")
|
||||
|
||||
return {
|
||||
"panel_name": settings.PANEL_NAME,
|
||||
"refresh_interval": int(refresh_interval),
|
||||
"log_retention_days": int(log_retention_days),
|
||||
"language": language
|
||||
}
|
||||
|
||||
|
||||
@router.put("/api/panel")
|
||||
async def update_panel_settings(
|
||||
data: UpdatePanelSettingsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(PermissionChecker("settings.panel"))
|
||||
):
|
||||
"""Update panel settings (requires settings.panel permission)"""
|
||||
updated_fields = []
|
||||
|
||||
# Save settings to database
|
||||
if data.refresh_interval is not None:
|
||||
set_setting(db, "refresh_interval", str(data.refresh_interval), "Интервал обновления данных (секунды)")
|
||||
updated_fields.append(f"refresh_interval={data.refresh_interval}")
|
||||
|
||||
if data.log_retention_days is not None:
|
||||
set_setting(db, "log_retention_days", str(data.log_retention_days), "Срок хранения логов (дни)")
|
||||
updated_fields.append(f"log_retention_days={data.log_retention_days}")
|
||||
|
||||
if data.language is not None:
|
||||
set_setting(db, "language", data.language, "Язык интерфейса панели")
|
||||
updated_fields.append(f"language={data.language}")
|
||||
|
||||
if updated_fields:
|
||||
# Log settings change
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="panel_settings_update",
|
||||
message=f"Администратор {current_user.username} обновил настройки панели: {', '.join(updated_fields)}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id,
|
||||
details=data.dict(exclude_none=True)
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Настройки панели обновлены",
|
||||
"updated_fields": updated_fields
|
||||
}
|
||||
|
||||
|
||||
@router.post("/api/cleanup-logs")
|
||||
async def cleanup_old_logs(
|
||||
days: int = 30,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Manually cleanup old logs (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут очищать логи"
|
||||
)
|
||||
|
||||
deleted = LoggingService.cleanup_old_logs(db, days=days)
|
||||
|
||||
# Log the cleanup action
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.WARNING,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="logs_cleanup",
|
||||
message=f"Администратор {current_user.username} очистил {deleted} логов старше {days} дней",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id,
|
||||
details={"deleted_count": deleted, "older_than_days": days}
|
||||
)
|
||||
|
||||
return {
|
||||
"message": f"Удалено {deleted} логов старше {days} дней",
|
||||
"deleted": deleted
|
||||
}
|
||||
|
||||
|
||||
# ==================== Translations API ====================
|
||||
|
||||
@router.get("/api/translations/{lang}")
|
||||
async def get_translations(lang: str):
|
||||
"""Get all translations for specific language"""
|
||||
if lang not in ["ru", "en"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Unsupported language. Use 'ru' or 'en'"
|
||||
)
|
||||
|
||||
return I18nService.get_all(lang)
|
||||
|
||||
|
||||
# ==================== Security Settings API ====================
|
||||
|
||||
class SecuritySettingsRequest(BaseModel):
|
||||
single_session_enabled: Optional[bool] = None
|
||||
|
||||
|
||||
@router.get("/api/security-settings")
|
||||
async def get_security_settings(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get security settings (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут просматривать настройки безопасности"
|
||||
)
|
||||
|
||||
from ..services.security_service import SecurityService
|
||||
|
||||
return {
|
||||
"single_session_enabled": SecurityService.get_setting_bool(db, "single_session_enabled", False)
|
||||
}
|
||||
|
||||
|
||||
@router.put("/api/security-settings")
|
||||
async def update_security_settings(
|
||||
data: SecuritySettingsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update security settings (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут изменять настройки безопасности"
|
||||
)
|
||||
|
||||
from ..services.security_service import SecurityService
|
||||
|
||||
updated_fields = []
|
||||
|
||||
if data.single_session_enabled is not None:
|
||||
SecurityService.update_setting(db, "single_session_enabled", str(data.single_session_enabled).lower())
|
||||
updated_fields.append(f"single_session_enabled={data.single_session_enabled}")
|
||||
|
||||
if updated_fields:
|
||||
# Log settings change
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.AUTH,
|
||||
action="security_settings_update",
|
||||
message=f"Администратор {current_user.username} обновил настройки безопасности: {', '.join(updated_fields)}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id,
|
||||
details=data.model_dump(exclude_none=True)
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Настройки безопасности обновлены",
|
||||
"updated_fields": updated_fields
|
||||
}
|
||||
|
||||
|
||||
# ==================== Notification Channel Settings API ====================
|
||||
|
||||
class SMTPSettingsRequest(BaseModel):
|
||||
smtp_host: Optional[str] = Field(None, max_length=255)
|
||||
smtp_port: Optional[int] = Field(None, ge=1, le=65535)
|
||||
smtp_user: Optional[str] = Field(None, max_length=255)
|
||||
smtp_password: Optional[str] = Field(None, max_length=255)
|
||||
smtp_from: Optional[str] = Field(None, max_length=255)
|
||||
smtp_tls: Optional[bool] = None
|
||||
|
||||
|
||||
class TelegramSettingsRequest(BaseModel):
|
||||
telegram_bot_token: Optional[str] = Field(None, max_length=255)
|
||||
|
||||
|
||||
@router.get("/api/notification-channels")
|
||||
async def get_notification_channel_settings(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Get notification channel settings (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут просматривать настройки уведомлений"
|
||||
)
|
||||
|
||||
return {
|
||||
"smtp": {
|
||||
"host": get_setting(db, "smtp_host", ""),
|
||||
"port": int(get_setting(db, "smtp_port", "587") or "587"),
|
||||
"user": get_setting(db, "smtp_user", ""),
|
||||
"password": "***" if get_setting(db, "smtp_password") else "",
|
||||
"from": get_setting(db, "smtp_from", ""),
|
||||
"tls": get_setting(db, "smtp_tls", "true").lower() == "true"
|
||||
},
|
||||
"telegram": {
|
||||
"bot_token": "***" if get_setting(db, "telegram_bot_token") else "",
|
||||
"configured": bool(get_setting(db, "telegram_bot_token"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.put("/api/notification-channels/smtp")
|
||||
async def update_smtp_settings(
|
||||
data: SMTPSettingsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update SMTP settings (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут изменять настройки SMTP"
|
||||
)
|
||||
|
||||
updated_fields = []
|
||||
|
||||
if data.smtp_host is not None:
|
||||
set_setting(db, "smtp_host", data.smtp_host, "SMTP сервер")
|
||||
updated_fields.append("smtp_host")
|
||||
|
||||
if data.smtp_port is not None:
|
||||
set_setting(db, "smtp_port", str(data.smtp_port), "SMTP порт")
|
||||
updated_fields.append("smtp_port")
|
||||
|
||||
if data.smtp_user is not None:
|
||||
set_setting(db, "smtp_user", data.smtp_user, "SMTP пользователь")
|
||||
updated_fields.append("smtp_user")
|
||||
|
||||
if data.smtp_password is not None:
|
||||
set_setting(db, "smtp_password", data.smtp_password, "SMTP пароль")
|
||||
updated_fields.append("smtp_password")
|
||||
|
||||
if data.smtp_from is not None:
|
||||
set_setting(db, "smtp_from", data.smtp_from, "Email отправителя")
|
||||
updated_fields.append("smtp_from")
|
||||
|
||||
if data.smtp_tls is not None:
|
||||
set_setting(db, "smtp_tls", str(data.smtp_tls).lower(), "Использовать TLS")
|
||||
updated_fields.append("smtp_tls")
|
||||
|
||||
if updated_fields:
|
||||
# Reload notification channels
|
||||
from ..services.notification_channels import reload_notification_channels
|
||||
reload_notification_channels()
|
||||
|
||||
# Log settings change
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="smtp_settings_update",
|
||||
message=f"Администратор {current_user.username} обновил настройки SMTP: {', '.join(updated_fields)}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Настройки SMTP обновлены",
|
||||
"updated_fields": updated_fields
|
||||
}
|
||||
|
||||
|
||||
@router.put("/api/notification-channels/telegram")
|
||||
async def update_telegram_settings(
|
||||
data: TelegramSettingsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update Telegram bot settings (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут изменять настройки Telegram"
|
||||
)
|
||||
|
||||
updated_fields = []
|
||||
|
||||
if data.telegram_bot_token is not None:
|
||||
set_setting(db, "telegram_bot_token", data.telegram_bot_token, "Токен Telegram бота")
|
||||
updated_fields.append("telegram_bot_token")
|
||||
|
||||
if updated_fields:
|
||||
# Reload notification channels
|
||||
from ..services.notification_channels import reload_notification_channels
|
||||
reload_notification_channels()
|
||||
|
||||
# Log settings change
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="telegram_settings_update",
|
||||
message=f"Администратор {current_user.username} обновил настройки Telegram",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Настройки Telegram обновлены",
|
||||
"updated_fields": updated_fields
|
||||
}
|
||||
|
||||
|
||||
@router.post("/api/notification-channels/smtp/test")
|
||||
async def test_smtp_connection(
|
||||
test_email: str = Query(..., description="Email для отправки тестового письма"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Test SMTP connection by sending a test email (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут тестировать SMTP"
|
||||
)
|
||||
|
||||
from ..services.notification_channels import get_email_channel
|
||||
|
||||
email_channel = get_email_channel()
|
||||
if not email_channel.is_configured():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="SMTP не настроен. Заполните все необходимые поля."
|
||||
)
|
||||
|
||||
success = await email_channel.send(
|
||||
to_email=test_email,
|
||||
subject="🔧 Тестовое письмо - PVEmanager",
|
||||
body=f"Тестовое письмо от PVEmanager.\n\nВремя: {get_local_time()}\nОтправлено пользователем: {current_user.username}",
|
||||
html_body=f"""
|
||||
<div style="font-family: Arial, sans-serif; padding: 20px; background: #1a1a2e; color: #fff;">
|
||||
<h2 style="color: #667eea;">🔧 Тестовое письмо</h2>
|
||||
<p>SMTP настройки работают корректно!</p>
|
||||
<p>Время: {get_local_time()}</p>
|
||||
<p>Отправлено пользователем: {current_user.username}</p>
|
||||
<hr style="border-color: #333;">
|
||||
<p style="color: #888; font-size: 12px;">PVEmanager</p>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
if success:
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="smtp_test",
|
||||
message=f"Администратор {current_user.username} отправил тестовое письмо на {test_email}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
return {"success": True, "message": f"Тестовое письмо отправлено на {test_email}"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Не удалось отправить тестовое письмо. Проверьте настройки SMTP."
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/notification-channels/telegram/test")
|
||||
async def test_telegram_bot(
|
||||
chat_id: str = Query(..., description="Telegram Chat ID для отправки тестового сообщения"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Test Telegram bot by sending a test message (admin only)"""
|
||||
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Только администраторы могут тестировать Telegram бота"
|
||||
)
|
||||
|
||||
from ..services.notification_channels import get_telegram_channel, get_local_time
|
||||
|
||||
tg_channel = get_telegram_channel()
|
||||
if not tg_channel.is_configured():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Telegram бот не настроен. Укажите токен бота."
|
||||
)
|
||||
|
||||
success = await tg_channel.send(
|
||||
chat_id=chat_id,
|
||||
message=f"🔧 <b>Тестовое сообщение</b>\n\nTelegram бот работает корректно!\n\n🕐 Время: {get_local_time()}\n👤 Отправлено: {current_user.username}\n\n🖥️ <i>PVEmanager</i>",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
if success:
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="telegram_test",
|
||||
message=f"Администратор {current_user.username} отправил тестовое сообщение в Telegram (chat_id: {chat_id})",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
return {"success": True, "message": f"Тестовое сообщение отправлено в Telegram (chat_id: {chat_id})"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Не удалось отправить тестовое сообщение. Проверьте токен бота и chat_id."
|
||||
)
|
||||
|
||||
|
||||
# ==================== System Updates ====================
|
||||
|
||||
@router.get("/api/version")
|
||||
async def get_version():
|
||||
"""Get current panel version"""
|
||||
return {"version": get_current_version()}
|
||||
|
||||
|
||||
@router.get("/api/updates/check")
|
||||
async def check_updates(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(PermissionChecker("settings.manage"))
|
||||
):
|
||||
"""Check for available updates (admin only)"""
|
||||
result = await check_for_updates()
|
||||
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.INFO,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="update_check",
|
||||
message=f"Проверка обновлений: текущая {result['current_version']}, последняя {result.get('latest_version', 'unknown')}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/api/updates/status")
|
||||
async def updates_status():
|
||||
"""Get current update status (no auth required for banner check)"""
|
||||
return get_update_status()
|
||||
|
||||
|
||||
@router.post("/api/updates/perform")
|
||||
async def perform_system_update(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(PermissionChecker("settings.manage"))
|
||||
):
|
||||
"""Perform system update (admin only)"""
|
||||
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.WARNING,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="update_start",
|
||||
message=f"Администратор {current_user.username} запустил обновление системы",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
result = await perform_update()
|
||||
|
||||
if not result["success"]:
|
||||
LoggingService.log(
|
||||
db=db,
|
||||
level=LoggingService.ERROR,
|
||||
category=LoggingService.SYSTEM,
|
||||
action="update_failed",
|
||||
message=f"Ошибка обновления: {result.get('error')}",
|
||||
username=current_user.username,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/api/updates/reset")
|
||||
async def reset_update(
|
||||
current_user: User = Depends(PermissionChecker("settings.manage"))
|
||||
):
|
||||
"""Reset update status (admin only)"""
|
||||
reset_update_status()
|
||||
return {"success": True, "message": "Update status reset"}
|
||||
|
||||
Reference in New Issue
Block a user