- introduce Dockerfile for Python environment setup - create FastAPI app with authentication and user management - implement server management features with CRUD operations - add PWA support with service worker and manifest - set up initial templates for UI components 📝 docs(fleetledger): add README for FleetLedger application - describe app features and functionalities - provide security notes and quick start guide 📦 build(fleetledger): configure Docker and docker-compose setup - define Dockerfile for application container - create docker-compose.yml for service orchestration - specify environment variables and volumes for persistence
99 lines
3 KiB
Python
99 lines
3 KiB
Python
from datetime import date, datetime
|
|
from typing import Optional, List
|
|
|
|
from sqlmodel import SQLModel, Field, Relationship
|
|
|
|
|
|
class User(SQLModel, table=True):
|
|
"""Application user model."""
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
|
|
username: str = Field(index=True, unique=True)
|
|
email: Optional[str] = Field(default=None, index=True)
|
|
password_hash: str
|
|
|
|
is_active: bool = Field(default=True)
|
|
is_admin: bool = Field(default=False)
|
|
|
|
servers: List["Server"] = Relationship(back_populates="owner")
|
|
|
|
|
|
class Server(SQLModel, table=True):
|
|
"""Server/VPS entry owned by a user."""
|
|
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
|
|
# Owner
|
|
owner_id: int = Field(foreign_key="user.id")
|
|
owner: Optional[User] = Relationship(back_populates="servers")
|
|
|
|
# General info
|
|
name: str
|
|
hostname: Optional[str] = None
|
|
type: str = "vps" # vps, dedicated, storage, managed, other
|
|
provider: str
|
|
location: Optional[str] = None
|
|
|
|
# Network
|
|
ipv4: Optional[str] = None
|
|
ipv6: Optional[str] = None
|
|
|
|
# Cost / billing
|
|
billing_period: str = "monthly" # monthly, yearly, other
|
|
price: float = 0.0
|
|
currency: str = "EUR"
|
|
contract_start: Optional[date] = None
|
|
contract_end: Optional[date] = None
|
|
tags: Optional[str] = None # e.g. "prod,critical,backup"
|
|
|
|
# Hardware
|
|
cpu_model: Optional[str] = None
|
|
cpu_cores: Optional[int] = None
|
|
ram_mb: Optional[int] = None
|
|
storage_gb: Optional[int] = None
|
|
storage_type: Optional[str] = None # nvme, ssd, hdd, ceph, ...
|
|
|
|
# Access (no private SSH keys, only hints)
|
|
mgmt_url: Optional[str] = None
|
|
mgmt_user: Optional[str] = None
|
|
mgmt_password_encrypted: Optional[str] = None
|
|
ssh_user: Optional[str] = None
|
|
ssh_key_hint: Optional[str] = None # e.g. "id_ed25519_ovh"
|
|
|
|
notes: Optional[str] = None
|
|
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
|
archived: bool = Field(default=False)
|
|
|
|
# ----- Convenience properties for badges / UI -----
|
|
|
|
@property
|
|
def days_until_contract_end(self) -> Optional[int]:
|
|
"""
|
|
Number of days until the contract_end date.
|
|
|
|
Returns:
|
|
int: positive or zero if in the future,
|
|
negative if already past,
|
|
None if no contract_end is set.
|
|
"""
|
|
if not self.contract_end:
|
|
return None
|
|
return (self.contract_end - date.today()).days
|
|
|
|
@property
|
|
def is_expired(self) -> bool:
|
|
"""Return True if the contract_end date lies in the past."""
|
|
return self.contract_end is not None and self.contract_end < date.today()
|
|
|
|
@property
|
|
def is_expiring_soon(self) -> bool:
|
|
"""
|
|
Return True if the contract will end within the next 30 days.
|
|
|
|
This is used for "expiring soon" badges in the UI.
|
|
"""
|
|
days = self.days_until_contract_end
|
|
return days is not None and 0 <= days <= 30
|