feat: документация из Nextcloud, график у сотрудника, автоочистка фото, мониторинг, итог мастера, фильтр по местам; доп. сервисы в docker-compose
- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели) - автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов во внутренней сети Docker; значения из интерфейса важнее - локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API - .env.example, docs/DEPLOY.md (раздел 7а), README
This commit is contained in:
+26
-4
@@ -9,10 +9,10 @@ from fastapi.responses import FileResponse
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import schemas as S, config, logic, push, settings_store as ss
|
||||
from .. import schemas as S, config, logic, photo_cleanup, push, settings_store as ss, sysinfo
|
||||
from ..db import get_db
|
||||
from ..models import Comment, Photo, Order, OrderItem, User, PushSubscription, DeviceToken
|
||||
from ..security import current_user, require_admin, is_manager, is_portal_admin
|
||||
from ..security import current_user, require_admin, require_portal_admin, is_manager, is_portal_admin
|
||||
from .catalog import commit
|
||||
from .orders import load, exec_guard, start, names, can_execute, to_out
|
||||
|
||||
@@ -94,7 +94,7 @@ async def upload_photo(id: int, item_id: int, request: Request, db: Session = De
|
||||
raise HTTPException(415, "Допустимы JPEG, PNG, WebP")
|
||||
ct, ext = kind
|
||||
root = ss.photo_root(db)
|
||||
rel = Path(f"{config.now():%Y/%m}") / (ss.new_name() + ext)
|
||||
rel = Path("keep" if item.photo_keep else "") / f"{config.now():%Y/%m}" / (ss.new_name() + ext) # keep/ — не удаляются очисткой
|
||||
(root / rel).parent.mkdir(parents=True, exist_ok=True)
|
||||
(root / rel).write_bytes(data)
|
||||
db.add(Photo(order_id=o.id, item_id=item.id, path=rel.as_posix(), content_type=ct, size=len(data),
|
||||
@@ -142,8 +142,30 @@ def dir_stats(root: Path) -> dict:
|
||||
return {"files": len(files), "bytes": sum(x.stat().st_size for x in files), "disk_free": free}
|
||||
|
||||
|
||||
def keep_stats(root: Path) -> dict:
|
||||
a, b = dir_stats(root / "keep"), dir_stats(root / "media" / "tasks" / "keep")
|
||||
return {"files": a["files"] + b["files"], "bytes": a["bytes"] + b["bytes"]}
|
||||
|
||||
|
||||
@router.get("/photo-cleanup")
|
||||
def cleanup_preview(db: Session = Depends(get_db), _: User = Depends(require_portal_admin)):
|
||||
"""Сколько фото удалит очистка сейчас (ничего не удаляет)."""
|
||||
return {**photo_cleanup.run(db, dry=True), "last_run": ss.get(db, "photo_cleanup_at") or None}
|
||||
|
||||
|
||||
@router.post("/photo-cleanup")
|
||||
def cleanup_now(db: Session = Depends(get_db), _: User = Depends(require_portal_admin)):
|
||||
return photo_cleanup.run(db)
|
||||
|
||||
|
||||
@router.get("/server/monitor")
|
||||
def monitor(db: Session = Depends(get_db), _: User = Depends(require_portal_admin)):
|
||||
"""Мониторинг сервера: загрузка процессора, память, место на дисках (данные портала и фото)."""
|
||||
return {**sysinfo.snapshot({"Данные портала": Path(config.DATA_DIR), "Фото": ss.photo_root(db)}), "now": config.iso(config.now())}
|
||||
|
||||
|
||||
def settings_payload(db: Session) -> dict:
|
||||
return {**ss.all_editable(db), "photo_stats": dir_stats(ss.photo_root(db)),
|
||||
return {**ss.all_editable(db), "photo_stats": dir_stats(ss.photo_root(db)), "photo_keep_stats": keep_stats(ss.photo_root(db)),
|
||||
"vapid_public": push.public_key(db), "now": config.iso(config.now()),
|
||||
"fcm_enabled": push.fcm_enabled(db), "timezone": str(config.TZ)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user