Add comprehensive SSH terminal drawer feature with embedded and popout modes, integrate playbook lint results API with local cache fallback, and enhance host management UI with terminal access buttons
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:
2025-12-17 23:59:17 -05:00
parent 05087aa380
commit 493668f746
110 changed files with 22054 additions and 24 deletions
+6
View File
@@ -13,9 +13,14 @@ from .docker_container import DockerContainer
from .docker_image import DockerImage
from .docker_volume import DockerVolume
from .docker_alert import DockerAlert
from .terminal_session import TerminalSession
__all__ = [
"Base",
"get_db",
"init_db",
"async_session_maker",
"engine",
"Host",
"BootstrapStatus",
"Task",
@@ -31,4 +36,5 @@ __all__ = [
"DockerImage",
"DockerVolume",
"DockerAlert",
"TerminalSession",
]
+1
View File
@@ -124,6 +124,7 @@ async def init_db() -> None:
docker_image,
docker_volume,
docker_alert,
playbook_lint,
) # noqa: F401
async with engine.begin() as conn:
+81
View File
@@ -0,0 +1,81 @@
"""
Model for storing ansible-lint results per playbook.
"""
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy import Column, DateTime, Integer, String, Text, JSON
from sqlalchemy.orm import Mapped, mapped_column
from .database import Base
class PlaybookLintResult(Base):
"""Stores the latest ansible-lint result for each playbook."""
__tablename__ = "playbook_lint_results"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
# Playbook filename (unique constraint)
filename: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
# Quality score (0-100)
quality_score: Mapped[int] = mapped_column(Integer, nullable=False, default=100)
# Summary counts
total_issues: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
errors_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
warnings_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Execution time in milliseconds
execution_time_ms: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Full issues list as JSON
issues_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Raw output from ansible-lint (for copy to clipboard)
raw_output: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc)
)
def __repr__(self) -> str:
return f"<PlaybookLintResult(filename={self.filename}, score={self.quality_score})>"
def to_dict(self) -> dict:
"""Convert to dictionary for API response."""
import json
issues = []
if self.issues_json:
try:
issues = json.loads(self.issues_json)
except (json.JSONDecodeError, TypeError):
issues = []
return {
"id": self.id,
"filename": self.filename,
"quality_score": self.quality_score,
"summary": {
"total": self.total_issues,
"errors": self.errors_count,
"warnings": self.warnings_count,
},
"issues": issues,
"execution_time_ms": self.execution_time_ms,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
+53
View File
@@ -0,0 +1,53 @@
"""
Model for terminal sessions - stores SSH terminal session metadata.
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from sqlalchemy import DateTime, Integer, String, text
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
from .database import Base
class TerminalSession(Base):
"""
Represents an active or recent SSH terminal session.
Sessions are created when a user opens a terminal to a host,
and cleaned up after expiration or manual closure.
"""
__tablename__ = "terminal_sessions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
host_id: Mapped[str] = mapped_column(String, nullable=False, index=True)
host_name: Mapped[str] = mapped_column(String, nullable=False)
host_ip: Mapped[str] = mapped_column(String, nullable=False)
user_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
username: Mapped[Optional[str]] = mapped_column(String, nullable=True)
# Token hash for session authentication (never store plain token)
token_hash: Mapped[str] = mapped_column(String(128), nullable=False)
# ttyd process management
ttyd_port: Mapped[int] = mapped_column(Integer, nullable=False)
ttyd_pid: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
# Session mode: 'embedded' or 'popout'
mode: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'embedded'"))
# Session status: 'active', 'closed', 'expired', 'error'
status: Mapped[str] = mapped_column(String(20), nullable=False, server_default=text("'active'"))
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
def __repr__(self) -> str:
return f"<TerminalSession id={self.id} host={self.host_name} status={self.status}>"