Files
Veeam_dashboard/database.py
smolkik-code ba8a413e85 Initial commit: Veeam multi-instance monitoring dashboard
- FastAPI backend with CRUD for Veeam instances
- Veeam REST API client supporting v9.5-v13 (legacy + OAuth2)
- Multi-version detection and auth fallback
- SQLite caching layer
- Web dashboard with summary cards, instance list, detail tabs
- Docker packaging
2026-06-09 17:10:42 +07:00

134 lines
4.9 KiB
Python

from datetime import datetime
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String, Integer, Boolean, DateTime, Text, select, delete as sa_delete
from config import settings
import json
engine = create_async_engine(settings.db_url, echo=False)
async_session = async_sessionmaker(engine, expire_on_commit=False)
class Base(DeclarativeBase):
pass
class Instance(Base):
__tablename__ = "instances"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
host: Mapped[str] = mapped_column(String(255), nullable=False)
port: Mapped[int] = mapped_column(Integer, nullable=False)
username: Mapped[str] = mapped_column(String(255), nullable=False)
password: Mapped[str] = mapped_column(String(255), nullable=False)
version: Mapped[str] = mapped_column(String(32), default="auto")
notes: Mapped[str] = mapped_column(Text, default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class CacheEntry(Base):
__tablename__ = "cache"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
instance_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
key: Mapped[str] = mapped_column(String(64), nullable=False)
data: Mapped[str] = mapped_column(Text, nullable=False)
fetched_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_instances() -> list[Instance]:
async with async_session() as session:
result = await session.execute(select(Instance).order_by(Instance.name))
return list(result.scalars().all())
async def get_instance(instance_id: int) -> Instance | None:
async with async_session() as session:
result = await session.execute(select(Instance).where(Instance.id == instance_id))
return result.scalar_one_or_none()
async def add_instance(name: str, host: str, port: int, username: str, password: str,
version: str = "auto", notes: str = "") -> Instance:
async with async_session() as session:
inst = Instance(
name=name, host=host, port=port, username=username,
password=password, version=version, notes=notes
)
session.add(inst)
await session.commit()
await session.refresh(inst)
return inst
async def update_instance(instance_id: int, **kwargs) -> Instance | None:
async with async_session() as session:
result = await session.execute(select(Instance).where(Instance.id == instance_id))
inst = result.scalar_one_or_none()
if not inst:
return None
for key, value in kwargs.items():
if hasattr(inst, key):
setattr(inst, key, value)
inst.updated_at = datetime.utcnow()
await session.commit()
await session.refresh(inst)
return inst
async def delete_instance(instance_id: int) -> bool:
async with async_session() as session:
result = await session.execute(select(Instance).where(Instance.id == instance_id))
inst = result.scalar_one_or_none()
if not inst:
return False
await session.delete(inst)
await session.execute(sa_delete(CacheEntry).where(CacheEntry.instance_id == instance_id))
await session.commit()
return True
async def set_cache(instance_id: int, key: str, data: dict):
async with async_session() as session:
result = await session.execute(
select(CacheEntry).where(
CacheEntry.instance_id == instance_id,
CacheEntry.key == key
)
)
entry = result.scalar_one_or_none()
if entry:
entry.data = json.dumps(data)
entry.fetched_at = datetime.utcnow()
else:
entry = CacheEntry(
instance_id=instance_id,
key=key,
data=json.dumps(data)
)
session.add(entry)
await session.commit()
async def get_cache(instance_id: int, key: str) -> dict | None:
async with async_session() as session:
result = await session.execute(
select(CacheEntry).where(
CacheEntry.instance_id == instance_id,
CacheEntry.key == key
)
)
entry = result.scalar_one_or_none()
if entry:
return {"data": json.loads(entry.data), "fetched_at": entry.fetched_at.isoformat()}
return None