feat: complete program platform experience
This commit is contained in:
1 parent
e50b8b480b
commit
383c066d86
9 files changed
+419
-67
No files matched your search
@@ -1,5 +1,9 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from config import settings
|
||||
from routes.auth import require_auth
|
||||
@@ -13,6 +17,17 @@ class BrandUpdate(BaseModel):
|
||||
brand_logo_url: Optional[str] = None
|
||||
|
||||
|
||||
class TranscodeQuality(BaseModel):
|
||||
label: str = Field(min_length=1, max_length=32)
|
||||
width: int = Field(ge=160, le=7680)
|
||||
height: int = Field(ge=90, le=4320)
|
||||
bitrate: str = Field(pattern=r"^[1-9][0-9]*(?:k|M)$")
|
||||
|
||||
|
||||
class TranscodeProfilesUpdate(BaseModel):
|
||||
profiles: dict[str, list[TranscodeQuality]]
|
||||
|
||||
|
||||
@router.get("/brand")
|
||||
async def get_brand():
|
||||
return {
|
||||
@@ -31,3 +46,81 @@ async def update_brand(body: BrandUpdate, _: bool = Depends(require_auth)):
|
||||
if body.brand_logo_url is not None:
|
||||
settings.brand_logo_url = body.brand_logo_url
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/transcode-profiles")
|
||||
async def get_transcode_profiles(_: bool = Depends(require_auth)):
|
||||
return settings.transcode_profiles
|
||||
|
||||
|
||||
@router.put("/transcode-profiles")
|
||||
async def update_transcode_profiles(
|
||||
body: TranscodeProfilesUpdate,
|
||||
_: bool = Depends(require_auth),
|
||||
):
|
||||
if not body.profiles:
|
||||
raise HTTPException(status_code=422, detail="至少需要一个转码配置")
|
||||
|
||||
normalized: dict[str, list[dict]] = {}
|
||||
for raw_name, qualities in body.profiles.items():
|
||||
name = raw_name.strip()
|
||||
if not name or len(name) > 64:
|
||||
raise HTTPException(status_code=422, detail="转码配置名称无效")
|
||||
if not qualities:
|
||||
raise HTTPException(status_code=422, detail=f"转码配置 {name} 至少需要一个清晰度")
|
||||
labels = [quality.label.strip() for quality in qualities]
|
||||
if any(not label for label in labels) or len(labels) != len(set(labels)):
|
||||
raise HTTPException(status_code=422, detail=f"转码配置 {name} 的清晰度名称无效或重复")
|
||||
normalized[name] = [
|
||||
{
|
||||
"label": quality.label.strip(),
|
||||
"width": quality.width,
|
||||
"height": quality.height,
|
||||
"bitrate": quality.bitrate,
|
||||
}
|
||||
for quality in sorted(qualities, key=lambda item: item.height, reverse=True)
|
||||
]
|
||||
|
||||
settings.transcode_profiles = normalized
|
||||
settings.qualities = next(iter(normalized.values()))
|
||||
return {"ok": True, "profiles": normalized}
|
||||
|
||||
|
||||
@router.get("/system-metrics")
|
||||
async def get_system_metrics(_: bool = Depends(require_auth)):
|
||||
load_1m, load_5m, load_15m = os.getloadavg()
|
||||
memory_total = 0
|
||||
memory_available = 0
|
||||
meminfo = Path("/proc/meminfo")
|
||||
if meminfo.exists():
|
||||
values = {}
|
||||
for line in meminfo.read_text(encoding="ascii").splitlines():
|
||||
key, _, value = line.partition(":")
|
||||
if value:
|
||||
values[key] = int(value.strip().split()[0]) * 1024
|
||||
memory_total = values.get("MemTotal", 0)
|
||||
memory_available = values.get("MemAvailable", 0)
|
||||
|
||||
metrics_path = Path(settings.replay_media_dir).resolve()
|
||||
disk_path = metrics_path if metrics_path.exists() else Path.cwd()
|
||||
disk = shutil.disk_usage(disk_path)
|
||||
cpu_count = os.cpu_count() or 1
|
||||
return {
|
||||
"cpu": {
|
||||
"count": cpu_count,
|
||||
"load_1m": load_1m,
|
||||
"load_5m": load_5m,
|
||||
"load_15m": load_15m,
|
||||
"load_percent": round(min(100, load_1m / cpu_count * 100), 1),
|
||||
},
|
||||
"memory": {
|
||||
"total": memory_total,
|
||||
"available": memory_available,
|
||||
"used_percent": round((1 - memory_available / memory_total) * 100, 1) if memory_total else 0,
|
||||
},
|
||||
"disk": {
|
||||
"total": disk.total,
|
||||
"free": disk.free,
|
||||
"used_percent": round(disk.used / disk.total * 100, 1) if disk.total else 0,
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user