feat: Implement container customization and add Portainer installation/removal playbooks.
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
This commit is contained in:
@@ -13,6 +13,7 @@ from .docker_container import DockerContainer
|
||||
from .docker_image import DockerImage
|
||||
from .docker_volume import DockerVolume
|
||||
from .docker_alert import DockerAlert
|
||||
from .container_customization import ContainerCustomization
|
||||
from .favorite_group import FavoriteGroup
|
||||
from .favorite_container import FavoriteContainer
|
||||
from .terminal_session import TerminalSession
|
||||
@@ -39,6 +40,7 @@ __all__ = [
|
||||
"DockerImage",
|
||||
"DockerVolume",
|
||||
"DockerAlert",
|
||||
"ContainerCustomization",
|
||||
"FavoriteGroup",
|
||||
"FavoriteContainer",
|
||||
"TerminalSession",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from .database import Base
|
||||
|
||||
|
||||
class ContainerCustomization(Base):
|
||||
__tablename__ = "container_customizations"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True)
|
||||
|
||||
host_id: Mapped[str] = mapped_column(String, ForeignKey("hosts.id", ondelete="CASCADE"), nullable=False)
|
||||
container_id: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
|
||||
icon_key: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
icon_color: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
|
||||
bg_color: Mapped[Optional[str]] = mapped_column(String(20), nullable=True)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "host_id", "container_id", name="uq_container_customizations_user_host_container"),
|
||||
{"sqlite_autoincrement": True},
|
||||
)
|
||||
@@ -3,11 +3,14 @@ Uses SQLAlchemy 2.x async engine with SQLite + aiosqlite driver.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import AsyncGenerator
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from sqlalchemy import event, MetaData
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import declarative_base
|
||||
@@ -129,5 +132,42 @@ async def init_db() -> None:
|
||||
favorite_container,
|
||||
) # noqa: F401
|
||||
|
||||
def _to_sync_database_url(db_url: str) -> str:
|
||||
return db_url.replace("sqlite+aiosqlite:", "sqlite:")
|
||||
|
||||
def _run_alembic_upgrade() -> None:
|
||||
# Try multiple locations for alembic.ini (dev vs Docker)
|
||||
alembic_ini_paths = [
|
||||
ROOT_DIR / "alembic.ini", # Dev: /path/to/project/alembic.ini
|
||||
Path("/alembic.ini"), # Docker: /alembic.ini
|
||||
Path("/app/alembic.ini"), # Docker alternative
|
||||
]
|
||||
|
||||
alembic_ini = None
|
||||
for path in alembic_ini_paths:
|
||||
if path.exists():
|
||||
alembic_ini = path
|
||||
print(f"[DB] Found alembic.ini at: {alembic_ini}")
|
||||
break
|
||||
|
||||
if not alembic_ini:
|
||||
print(f"[DB] alembic.ini not found in any of: {[str(p) for p in alembic_ini_paths]}")
|
||||
return
|
||||
|
||||
try:
|
||||
cfg = Config(str(alembic_ini))
|
||||
cfg.set_main_option("sqlalchemy.url", _to_sync_database_url(DATABASE_URL))
|
||||
print(f"[DB] Running Alembic upgrade to head...")
|
||||
command.upgrade(cfg, "head")
|
||||
print(f"[DB] Alembic upgrade completed successfully")
|
||||
except Exception as e:
|
||||
print(f"[DB] Alembic upgrade failed: {e}")
|
||||
raise
|
||||
|
||||
try:
|
||||
await asyncio.to_thread(_run_alembic_upgrade)
|
||||
except Exception as e:
|
||||
print(f"[DB] Exception during Alembic migration: {e}")
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
Reference in New Issue
Block a user