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
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,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",
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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}>"
|
||||
Reference in New Issue
Block a user