07b97bd111
Show git check result next to the button, scroll into view, and test unsaved repo URLs from the form.
694 lines
23 KiB
Python
694 lines
23 KiB
Python
from dataclasses import replace
|
|
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.auth.jwt_auth import require_admin
|
|
from app.database import get_db
|
|
from app.services.email_notify import EmailNotConfiguredError, EmailSendError, send_email_test_message
|
|
from app.services.notification_policy import (
|
|
NotificationPolicyConfig,
|
|
get_effective_notification_policy,
|
|
upsert_notification_policy,
|
|
)
|
|
from app.services.notification_settings import (
|
|
VALID_SEVERITIES,
|
|
EmailConfig,
|
|
TelegramConfig,
|
|
WebhookConfig,
|
|
get_effective_email_config,
|
|
get_effective_telegram_config,
|
|
get_effective_webhook_config,
|
|
upsert_email_channel,
|
|
upsert_telegram_channel,
|
|
upsert_webhook_channel,
|
|
)
|
|
from app.services.telegram_notify import TelegramNotConfiguredError, TelegramSendError, send_telegram_test_message
|
|
from app.services.webhook_notify import (
|
|
WebhookNotConfiguredError,
|
|
WebhookSendError,
|
|
send_webhook_test_message,
|
|
)
|
|
from app.services.event_severity_overrides import (
|
|
SOURCE_DB,
|
|
list_severity_override_items,
|
|
replace_severity_overrides,
|
|
)
|
|
from app.services.ui_settings import (
|
|
get_effective_ui_settings,
|
|
upsert_ui_settings,
|
|
)
|
|
from app.services.linux_admin_settings import (
|
|
get_effective_linux_admin_config,
|
|
upsert_linux_admin_settings,
|
|
)
|
|
from app.services.win_admin_settings import (
|
|
get_effective_win_admin_config,
|
|
upsert_win_admin_settings,
|
|
)
|
|
from app.services.agent_git_release import get_git_release_versions
|
|
from app.services.agent_update_settings import (
|
|
get_effective_agent_update_config,
|
|
upsert_agent_update_settings,
|
|
)
|
|
from app.services.winrm_connect import (
|
|
HostNotWindowsError,
|
|
HostTargetMissingError,
|
|
WinAdminNotConfiguredError,
|
|
resolve_windows_host_target,
|
|
test_winrm_connection,
|
|
)
|
|
|
|
router = APIRouter(prefix="/settings", tags=["settings"])
|
|
|
|
|
|
def _mask_secret(value: str, *, visible_tail: int = 4) -> str | None:
|
|
text = value.strip()
|
|
if not text:
|
|
return None
|
|
if len(text) <= visible_tail:
|
|
return "*" * len(text)
|
|
return f"{'*' * 8}…{text[-visible_tail:]}"
|
|
|
|
|
|
def _mask_url(value: str) -> str | None:
|
|
text = value.strip()
|
|
if not text:
|
|
return None
|
|
if len(text) <= 20:
|
|
return f"{'*' * 6}…{text[-4:]}"
|
|
return f"{text[:16]}…{text[-6:]}"
|
|
|
|
|
|
class TelegramSettingsResponse(BaseModel):
|
|
enabled: bool
|
|
configured: bool
|
|
chat_id_hint: str | None = None
|
|
bot_token_hint: str | None = None
|
|
min_severity: str
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class WebhookSettingsResponse(BaseModel):
|
|
enabled: bool
|
|
configured: bool
|
|
url_hint: str | None = None
|
|
secret_header: str | None = None
|
|
secret_hint: str | None = None
|
|
min_severity: str
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class EmailSettingsResponse(BaseModel):
|
|
enabled: bool
|
|
configured: bool
|
|
smtp_host_hint: str | None = None
|
|
smtp_port: int
|
|
smtp_user: str | None = None
|
|
smtp_password_hint: str | None = None
|
|
mail_from: str | None = None
|
|
mail_to_hint: str | None = None
|
|
smtp_starttls: bool
|
|
smtp_ssl: bool
|
|
min_severity: str
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class NotificationPolicyResponse(BaseModel):
|
|
min_severity: str
|
|
use_telegram: bool
|
|
use_webhook: bool
|
|
use_email: bool
|
|
use_mobile: bool
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class NotificationSettingsResponse(BaseModel):
|
|
policy: NotificationPolicyResponse
|
|
telegram: TelegramSettingsResponse
|
|
webhook: WebhookSettingsResponse
|
|
email: EmailSettingsResponse
|
|
|
|
|
|
class NotificationPolicyUpdate(BaseModel):
|
|
min_severity: str
|
|
use_telegram: bool
|
|
use_webhook: bool
|
|
use_email: bool
|
|
use_mobile: bool = False
|
|
|
|
|
|
class TelegramSettingsUpdate(BaseModel):
|
|
enabled: bool
|
|
bot_token: str | None = None
|
|
chat_id: str | None = None
|
|
|
|
|
|
class WebhookSettingsUpdate(BaseModel):
|
|
enabled: bool
|
|
url: str | None = None
|
|
secret_header: str | None = None
|
|
secret: str | None = None
|
|
|
|
|
|
class EmailSettingsUpdate(BaseModel):
|
|
enabled: bool
|
|
smtp_host: str | None = None
|
|
smtp_port: int | None = None
|
|
smtp_user: str | None = None
|
|
smtp_password: str | None = None
|
|
mail_from: str | None = None
|
|
mail_to: str | None = None
|
|
smtp_starttls: bool | None = None
|
|
smtp_ssl: bool | None = None
|
|
|
|
|
|
class ChannelTestResponse(BaseModel):
|
|
ok: bool = True
|
|
message: str
|
|
|
|
|
|
def _policy_to_response(cfg: NotificationPolicyConfig) -> NotificationPolicyResponse:
|
|
return NotificationPolicyResponse(
|
|
min_severity=cfg.min_severity,
|
|
use_telegram=cfg.use_telegram,
|
|
use_webhook=cfg.use_webhook,
|
|
use_email=cfg.use_email,
|
|
use_mobile=cfg.use_mobile,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
def _telegram_to_response(cfg: TelegramConfig, policy: NotificationPolicyConfig) -> TelegramSettingsResponse:
|
|
return TelegramSettingsResponse(
|
|
enabled=cfg.enabled,
|
|
configured=cfg.configured,
|
|
chat_id_hint=_mask_secret(cfg.chat_id),
|
|
bot_token_hint=_mask_secret(cfg.bot_token),
|
|
min_severity=policy.min_severity,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
def _webhook_to_response(cfg: WebhookConfig, policy: NotificationPolicyConfig) -> WebhookSettingsResponse:
|
|
return WebhookSettingsResponse(
|
|
enabled=cfg.enabled,
|
|
configured=cfg.configured,
|
|
url_hint=_mask_url(cfg.url),
|
|
secret_header=cfg.secret_header or None,
|
|
secret_hint=_mask_secret(cfg.secret),
|
|
min_severity=policy.min_severity,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
def _email_to_response(cfg: EmailConfig, policy: NotificationPolicyConfig) -> EmailSettingsResponse:
|
|
return EmailSettingsResponse(
|
|
enabled=cfg.enabled,
|
|
configured=cfg.configured,
|
|
smtp_host_hint=_mask_url(cfg.smtp_host) if cfg.smtp_host else None,
|
|
smtp_port=cfg.smtp_port,
|
|
smtp_user=cfg.smtp_user or None,
|
|
smtp_password_hint=_mask_secret(cfg.smtp_password),
|
|
mail_from=cfg.mail_from or None,
|
|
mail_to_hint=_mask_secret(cfg.mail_to.replace(";", ",")) if cfg.mail_to else None,
|
|
smtp_starttls=cfg.smtp_starttls,
|
|
smtp_ssl=cfg.smtp_ssl,
|
|
min_severity=policy.min_severity,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
@router.get("/notifications", response_model=NotificationSettingsResponse)
|
|
def get_notification_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> NotificationSettingsResponse:
|
|
policy = get_effective_notification_policy(db)
|
|
return NotificationSettingsResponse(
|
|
policy=_policy_to_response(policy),
|
|
telegram=_telegram_to_response(get_effective_telegram_config(db), policy),
|
|
webhook=_webhook_to_response(get_effective_webhook_config(db), policy),
|
|
email=_email_to_response(get_effective_email_config(db), policy),
|
|
)
|
|
|
|
|
|
@router.put("/notifications/policy", response_model=NotificationPolicyResponse)
|
|
def update_notification_policy(
|
|
body: NotificationPolicyUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> NotificationPolicyResponse:
|
|
if body.min_severity not in VALID_SEVERITIES:
|
|
raise HTTPException(status_code=422, detail=f"min_severity must be one of: {sorted(VALID_SEVERITIES)}")
|
|
if not any((body.use_telegram, body.use_webhook, body.use_email, body.use_mobile)):
|
|
raise HTTPException(status_code=422, detail="Выберите хотя бы один канал")
|
|
try:
|
|
policy = upsert_notification_policy(
|
|
db,
|
|
min_severity=body.min_severity,
|
|
use_telegram=body.use_telegram,
|
|
use_webhook=body.use_webhook,
|
|
use_email=body.use_email,
|
|
use_mobile=body.use_mobile,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
return _policy_to_response(policy)
|
|
|
|
|
|
@router.put("/notifications/telegram", response_model=TelegramSettingsResponse)
|
|
def update_telegram_settings(
|
|
body: TelegramSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> TelegramSettingsResponse:
|
|
try:
|
|
cfg = upsert_telegram_channel(
|
|
db,
|
|
enabled=body.enabled,
|
|
bot_token=body.bot_token,
|
|
chat_id=body.chat_id,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
policy = get_effective_notification_policy(db)
|
|
return _telegram_to_response(cfg, policy)
|
|
|
|
|
|
@router.put("/notifications/webhook", response_model=WebhookSettingsResponse)
|
|
def update_webhook_settings(
|
|
body: WebhookSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> WebhookSettingsResponse:
|
|
try:
|
|
cfg = upsert_webhook_channel(
|
|
db,
|
|
enabled=body.enabled,
|
|
url=body.url,
|
|
secret_header=body.secret_header,
|
|
secret=body.secret,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
policy = get_effective_notification_policy(db)
|
|
return _webhook_to_response(cfg, policy)
|
|
|
|
|
|
@router.put("/notifications/email", response_model=EmailSettingsResponse)
|
|
def update_email_settings(
|
|
body: EmailSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> EmailSettingsResponse:
|
|
try:
|
|
cfg = upsert_email_channel(
|
|
db,
|
|
enabled=body.enabled,
|
|
smtp_host=body.smtp_host,
|
|
smtp_port=body.smtp_port,
|
|
smtp_user=body.smtp_user,
|
|
smtp_password=body.smtp_password,
|
|
mail_from=body.mail_from,
|
|
mail_to=body.mail_to,
|
|
smtp_starttls=body.smtp_starttls,
|
|
smtp_ssl=body.smtp_ssl,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
policy = get_effective_notification_policy(db)
|
|
return _email_to_response(cfg, policy)
|
|
|
|
|
|
@router.post("/notifications/telegram/test", response_model=ChannelTestResponse)
|
|
def test_telegram_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> ChannelTestResponse:
|
|
cfg = get_effective_telegram_config(db)
|
|
try:
|
|
send_telegram_test_message(config=cfg)
|
|
except TelegramNotConfiguredError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
except TelegramSendError as exc:
|
|
code = exc.status_code if exc.status_code and 400 <= exc.status_code < 500 else 502
|
|
raise HTTPException(status_code=code, detail=str(exc)) from exc
|
|
return ChannelTestResponse(message="Тестовое сообщение отправлено в Telegram")
|
|
|
|
|
|
@router.post("/notifications/webhook/test", response_model=ChannelTestResponse)
|
|
def test_webhook_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> ChannelTestResponse:
|
|
cfg = get_effective_webhook_config(db)
|
|
try:
|
|
send_webhook_test_message(config=cfg)
|
|
except WebhookNotConfiguredError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
except WebhookSendError as exc:
|
|
code = exc.status_code if exc.status_code and 400 <= exc.status_code < 500 else 502
|
|
raise HTTPException(status_code=code, detail=str(exc)) from exc
|
|
return ChannelTestResponse(message="Тестовый POST отправлен на webhook URL")
|
|
|
|
|
|
@router.post("/notifications/email/test", response_model=ChannelTestResponse)
|
|
def test_email_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> ChannelTestResponse:
|
|
cfg = get_effective_email_config(db)
|
|
try:
|
|
send_email_test_message(config=cfg)
|
|
except EmailNotConfiguredError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
except EmailSendError as exc:
|
|
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
return ChannelTestResponse(message="Тестовое письмо отправлено по SMTP")
|
|
|
|
|
|
class EventSeverityOverrideRow(BaseModel):
|
|
event_type: str
|
|
default_severity: str
|
|
override_severity: str | None = None
|
|
|
|
|
|
class EventSeverityOverridesResponse(BaseModel):
|
|
items: list[EventSeverityOverrideRow]
|
|
source: str = Field(description="db — overrides хранятся в PostgreSQL")
|
|
|
|
|
|
class EventSeverityOverridesUpdate(BaseModel):
|
|
overrides: dict[str, str | None] = Field(
|
|
description="event_type → severity; null или пустая строка сбрасывает override",
|
|
)
|
|
|
|
|
|
@router.get("/notifications/severity-overrides", response_model=EventSeverityOverridesResponse)
|
|
def get_severity_overrides(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> EventSeverityOverridesResponse:
|
|
items = list_severity_override_items(db)
|
|
return EventSeverityOverridesResponse(
|
|
items=[
|
|
EventSeverityOverrideRow(
|
|
event_type=i.event_type,
|
|
default_severity=i.default_severity,
|
|
override_severity=i.override_severity,
|
|
)
|
|
for i in items
|
|
],
|
|
source=SOURCE_DB,
|
|
)
|
|
|
|
|
|
@router.put("/notifications/severity-overrides", response_model=EventSeverityOverridesResponse)
|
|
def update_severity_overrides(
|
|
body: EventSeverityOverridesUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> EventSeverityOverridesResponse:
|
|
try:
|
|
items = replace_severity_overrides(db, body.overrides)
|
|
db.commit()
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
return EventSeverityOverridesResponse(
|
|
items=[
|
|
EventSeverityOverrideRow(
|
|
event_type=i.event_type,
|
|
default_severity=i.default_severity,
|
|
override_severity=i.override_severity,
|
|
)
|
|
for i in items
|
|
],
|
|
source=SOURCE_DB,
|
|
)
|
|
|
|
|
|
class UiSettingsResponse(BaseModel):
|
|
show_sidebar_system_stats: bool
|
|
source: str = Field(description="db или default")
|
|
|
|
|
|
class UiSettingsUpdate(BaseModel):
|
|
show_sidebar_system_stats: bool
|
|
|
|
|
|
@router.get("/ui", response_model=UiSettingsResponse)
|
|
def get_ui_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> UiSettingsResponse:
|
|
cfg = get_effective_ui_settings(db)
|
|
return UiSettingsResponse(
|
|
show_sidebar_system_stats=cfg.show_sidebar_system_stats,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
@router.put("/ui", response_model=UiSettingsResponse)
|
|
def update_ui_settings(
|
|
body: UiSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> UiSettingsResponse:
|
|
cfg = upsert_ui_settings(db, show_sidebar_system_stats=body.show_sidebar_system_stats)
|
|
return UiSettingsResponse(
|
|
show_sidebar_system_stats=cfg.show_sidebar_system_stats,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
class WinAdminSettingsResponse(BaseModel):
|
|
configured: bool
|
|
user: str | None = None
|
|
password_hint: str | None = None
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class WinAdminSettingsUpdate(BaseModel):
|
|
user: str | None = None
|
|
password: str | None = None
|
|
|
|
|
|
@router.get("/win-admin", response_model=WinAdminSettingsResponse)
|
|
def get_win_admin_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> WinAdminSettingsResponse:
|
|
cfg = get_effective_win_admin_config(db)
|
|
return WinAdminSettingsResponse(
|
|
configured=cfg.configured,
|
|
user=cfg.user or None,
|
|
password_hint=_mask_secret(cfg.password),
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
@router.put("/win-admin", response_model=WinAdminSettingsResponse)
|
|
def update_win_admin_settings(
|
|
body: WinAdminSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> WinAdminSettingsResponse:
|
|
if body.user is not None and not body.user.strip():
|
|
raise HTTPException(status_code=422, detail="user must not be empty")
|
|
cfg = upsert_win_admin_settings(db, user=body.user, password=body.password)
|
|
return WinAdminSettingsResponse(
|
|
configured=cfg.configured,
|
|
user=cfg.user or None,
|
|
password_hint=_mask_secret(cfg.password),
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
class LinuxAdminSettingsResponse(BaseModel):
|
|
configured: bool
|
|
user: str | None = None
|
|
password_hint: str | None = None
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class LinuxAdminSettingsUpdate(BaseModel):
|
|
user: str | None = None
|
|
password: str | None = None
|
|
|
|
|
|
@router.get("/linux-admin", response_model=LinuxAdminSettingsResponse)
|
|
def get_linux_admin_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> LinuxAdminSettingsResponse:
|
|
cfg = get_effective_linux_admin_config(db)
|
|
return LinuxAdminSettingsResponse(
|
|
configured=cfg.configured,
|
|
user=cfg.user or None,
|
|
password_hint=_mask_secret(cfg.password),
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
@router.put("/linux-admin", response_model=LinuxAdminSettingsResponse)
|
|
def update_linux_admin_settings(
|
|
body: LinuxAdminSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> LinuxAdminSettingsResponse:
|
|
if body.user is not None and not body.user.strip():
|
|
raise HTTPException(status_code=422, detail="user must not be empty")
|
|
cfg = upsert_linux_admin_settings(db, user=body.user, password=body.password)
|
|
return LinuxAdminSettingsResponse(
|
|
configured=cfg.configured,
|
|
user=cfg.user or None,
|
|
password_hint=_mask_secret(cfg.password),
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
class AgentUpdateSettingsResponse(BaseModel):
|
|
mode: str
|
|
fallback_enabled: bool
|
|
fallback_after_minutes: int
|
|
recommended_rdp_version: str | None = None
|
|
recommended_ssh_version: str | None = None
|
|
min_rdp_version: str | None = None
|
|
min_ssh_version: str | None = None
|
|
win_agent_update_script: str | None = None
|
|
rdp_git_repo_url: str | None = None
|
|
ssh_git_repo_url: str | None = None
|
|
git_branch: str = "main"
|
|
git_latest_rdp_version: str | None = None
|
|
git_latest_ssh_version: str | None = None
|
|
git_fetched_at: datetime | None = None
|
|
git_errors: dict[str, str] = Field(default_factory=dict)
|
|
source: str = Field(description="env или db")
|
|
|
|
|
|
class AgentUpdateSettingsUpdate(BaseModel):
|
|
mode: str | None = None
|
|
fallback_enabled: bool | None = None
|
|
fallback_after_minutes: int | None = None
|
|
recommended_rdp_version: str | None = None
|
|
recommended_ssh_version: str | None = None
|
|
min_rdp_version: str | None = None
|
|
min_ssh_version: str | None = None
|
|
win_agent_update_script: str | None = None
|
|
rdp_git_repo_url: str | None = None
|
|
ssh_git_repo_url: str | None = None
|
|
git_branch: str | None = None
|
|
|
|
|
|
class AgentGitTestRequest(BaseModel):
|
|
rdp_git_repo_url: str | None = None
|
|
ssh_git_repo_url: str | None = None
|
|
git_branch: str | None = None
|
|
|
|
|
|
class AgentGitTestResponse(BaseModel):
|
|
git_latest_rdp_version: str | None = None
|
|
git_latest_ssh_version: str | None = None
|
|
git_fetched_at: datetime | None = None
|
|
git_errors: dict[str, str] = Field(default_factory=dict)
|
|
from_cache: bool = False
|
|
|
|
|
|
def _agent_update_to_response(cfg, git_release=None) -> AgentUpdateSettingsResponse:
|
|
git_latest_rdp = None
|
|
git_latest_ssh = None
|
|
git_fetched_at = None
|
|
git_errors: dict[str, str] = {}
|
|
if git_release is not None:
|
|
from app.services.agent_update_settings import PRODUCT_RDP, PRODUCT_SSH
|
|
|
|
git_latest_rdp = git_release.versions.get(PRODUCT_RDP) or None
|
|
git_latest_ssh = git_release.versions.get(PRODUCT_SSH) or None
|
|
git_fetched_at = git_release.fetched_at
|
|
git_errors = dict(git_release.errors)
|
|
return AgentUpdateSettingsResponse(
|
|
mode=cfg.mode,
|
|
fallback_enabled=cfg.fallback_enabled,
|
|
fallback_after_minutes=cfg.fallback_after_minutes,
|
|
recommended_rdp_version=cfg.recommended_rdp_version or None,
|
|
recommended_ssh_version=cfg.recommended_ssh_version or None,
|
|
min_rdp_version=cfg.min_rdp_version or None,
|
|
min_ssh_version=cfg.min_ssh_version or None,
|
|
win_agent_update_script=cfg.win_agent_update_script or None,
|
|
rdp_git_repo_url=cfg.rdp_git_repo_url or None,
|
|
ssh_git_repo_url=cfg.ssh_git_repo_url or None,
|
|
git_branch=cfg.git_branch or "main",
|
|
git_latest_rdp_version=git_latest_rdp,
|
|
git_latest_ssh_version=git_latest_ssh,
|
|
git_fetched_at=git_fetched_at,
|
|
git_errors=git_errors,
|
|
source=cfg.source,
|
|
)
|
|
|
|
|
|
@router.get("/agent-updates", response_model=AgentUpdateSettingsResponse)
|
|
def get_agent_update_settings(
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> AgentUpdateSettingsResponse:
|
|
cfg = get_effective_agent_update_config(db)
|
|
git_release = get_git_release_versions(cfg, db=db)
|
|
return _agent_update_to_response(cfg, git_release)
|
|
|
|
|
|
@router.put("/agent-updates", response_model=AgentUpdateSettingsResponse)
|
|
def update_agent_update_settings(
|
|
body: AgentUpdateSettingsUpdate,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> AgentUpdateSettingsResponse:
|
|
try:
|
|
cfg = upsert_agent_update_settings(
|
|
db,
|
|
mode=body.mode,
|
|
fallback_enabled=body.fallback_enabled,
|
|
fallback_after_minutes=body.fallback_after_minutes,
|
|
recommended_rdp_version=body.recommended_rdp_version,
|
|
recommended_ssh_version=body.recommended_ssh_version,
|
|
min_rdp_version=body.min_rdp_version,
|
|
min_ssh_version=body.min_ssh_version,
|
|
win_agent_update_script=body.win_agent_update_script,
|
|
rdp_git_repo_url=body.rdp_git_repo_url,
|
|
ssh_git_repo_url=body.ssh_git_repo_url,
|
|
git_branch=body.git_branch,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
git_release = get_git_release_versions(cfg, db=db, force_refresh=True)
|
|
return _agent_update_to_response(cfg, git_release)
|
|
|
|
|
|
@router.post("/agent-updates/test-git", response_model=AgentGitTestResponse)
|
|
def test_agent_git_release(
|
|
body: AgentGitTestRequest | None = None,
|
|
db: Session = Depends(get_db),
|
|
_user=Depends(require_admin),
|
|
) -> AgentGitTestResponse:
|
|
cfg = get_effective_agent_update_config(db)
|
|
if body is not None:
|
|
cfg = replace(
|
|
cfg,
|
|
rdp_git_repo_url=(body.rdp_git_repo_url or cfg.rdp_git_repo_url).strip(),
|
|
ssh_git_repo_url=(body.ssh_git_repo_url or cfg.ssh_git_repo_url).strip(),
|
|
git_branch=(body.git_branch or cfg.git_branch or "main").strip() or "main",
|
|
)
|
|
git_release = get_git_release_versions(cfg, db=db, force_refresh=True)
|
|
from app.services.agent_update_settings import PRODUCT_RDP, PRODUCT_SSH
|
|
|
|
return AgentGitTestResponse(
|
|
git_latest_rdp_version=git_release.versions.get(PRODUCT_RDP) or None,
|
|
git_latest_ssh_version=git_release.versions.get(PRODUCT_SSH) or None,
|
|
git_fetched_at=git_release.fetched_at,
|
|
git_errors=dict(git_release.errors),
|
|
from_cache=git_release.from_cache,
|
|
)
|