feat: unified api.js auth + Pydantic models + JWT security hardening

- All Alpine.js pages now use shared api.js for JWT auto-refresh (9 pages)
- Dashboard uses /servers/stats endpoint instead of fetching all servers
- Replaced all raw dict params with Pydantic models (agent, settings, presets)
- Added AgentHeartbeat, AgentExec, SettingUpdatePayload, DbCredentialCreate schemas
- JWT decode now requires exp+sub claims; better error logging
- ServerService.push_to_servers delegates to SyncService.batch_push
- SyncService handles None audit_repo/retry_repo gracefully
- Branding: web/app.js + web/style.css MultiSync→Nexus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-05-21 23:38:01 +08:00
parent a584792603
commit 7caa880ebd
19 changed files with 148 additions and 171 deletions
+23 -23
View File
@@ -88,29 +88,29 @@ class ServerService:
source_path: str,
sync_mode: str = "incremental",
operator: str = "admin",
batch_size: int = 50,
concurrency: int = 10,
) -> Dict[int, SyncLog]:
"""Push files to multiple servers in batches"""
results = {}
servers = []
for id in server_ids:
server = await self.server_repo.get_by_id(id)
if server:
servers.append(server)
"""Push files to multiple servers — delegates to SyncService.batch_push.
# TODO: Implement batch push with asyncio.Semaphore(10)
# batch_size=50, interval=30s
for server in servers:
target_path = server.target_path or f"/www/wwwroot/"
log = SyncLog(
server_id=server.id,
source_path=source_path,
target_path=target_path,
trigger_type="batch",
operator=operator,
status="pending",
sync_mode=sync_mode,
)
log = await self.sync_log_repo.create(log)
results[server.id] = log
Note: API routes should use SyncService directly via Depends(get_sync_service).
This method exists for internal callers (e.g. schedule_runner) that already
have a ServerService instance.
"""
from server.application.services.sync_service import SyncService
return results
# Build a SyncService sharing our existing repos + the extra ones it needs
sync_service = SyncService(
server_repo=self.server_repo,
sync_log_repo=self.sync_log_repo,
audit_repo=None, # audit handled by caller if needed
retry_repo=None, # retry handled by caller if needed
)
return await sync_service.batch_push(
server_ids=server_ids,
source_path=source_path,
sync_mode=sync_mode,
operator=operator,
batch_size=batch_size,
concurrency=concurrency,
)