homelab_automation/alembic/versions/0014_add_terminal_command_logs_table.py
Bruno Charest 5bc12d0729
Some checks failed
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
Add terminal session management with heartbeat monitoring, idle timeout detection, session reuse logic, and command history panel UI with search and filtering capabilities
2025-12-18 13:49:40 -05:00

95 lines
4.8 KiB
Python

"""Add terminal_command_logs table for command history
Revision ID: 0014
Revises: 0013_add_terminal_sessions_table
Create Date: 2024-12-18
This migration creates the terminal_command_logs table to store
validated terminal commands for history and audit purposes.
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision = '0014'
down_revision = '0013'
branch_labels = None
depends_on = None
def table_exists(table_name: str) -> bool:
"""Check if a table exists in the database."""
bind = op.get_bind()
inspector = inspect(bind)
return table_name in inspector.get_table_names()
def index_exists(index_name: str, table_name: str) -> bool:
"""Check if an index exists on a table."""
bind = op.get_bind()
inspector = inspect(bind)
indexes = inspector.get_indexes(table_name)
return any(idx['name'] == index_name for idx in indexes)
def upgrade() -> None:
# Check if table already exists (idempotent migration)
if table_exists('terminal_command_logs'):
print("Table 'terminal_command_logs' already exists, skipping creation.")
else:
op.create_table(
'terminal_command_logs',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column('host_id', sa.String(), nullable=False),
sa.Column('user_id', sa.String(), nullable=True),
sa.Column('terminal_session_id', sa.String(64), nullable=True),
sa.Column('command', sa.Text(), nullable=False),
sa.Column('command_hash', sa.String(64), nullable=False),
sa.Column('source', sa.String(20), server_default='terminal', nullable=False),
sa.Column('is_blocked', sa.Boolean(), server_default='0', nullable=False),
sa.Column('blocked_reason', sa.String(255), nullable=True),
sa.Column('username', sa.String(100), nullable=True),
sa.Column('host_name', sa.String(100), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.ForeignKeyConstraint(['host_id'], ['hosts.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='SET NULL'),
)
# Create indexes for efficient querying (only if they don't exist)
if not index_exists('ix_terminal_command_logs_created_at', 'terminal_command_logs'):
op.create_index('ix_terminal_command_logs_created_at', 'terminal_command_logs', ['created_at'])
if not index_exists('ix_terminal_command_logs_host_id', 'terminal_command_logs'):
op.create_index('ix_terminal_command_logs_host_id', 'terminal_command_logs', ['host_id'])
if not index_exists('ix_terminal_command_logs_user_id', 'terminal_command_logs'):
op.create_index('ix_terminal_command_logs_user_id', 'terminal_command_logs', ['user_id'])
if not index_exists('ix_terminal_command_logs_terminal_session_id', 'terminal_command_logs'):
op.create_index('ix_terminal_command_logs_terminal_session_id', 'terminal_command_logs', ['terminal_session_id'])
if not index_exists('ix_terminal_command_logs_command_hash', 'terminal_command_logs'):
op.create_index('ix_terminal_command_logs_command_hash', 'terminal_command_logs', ['command_hash'])
# Composite indexes for common query patterns
if not index_exists('ix_terminal_cmd_host_created', 'terminal_command_logs'):
op.create_index('ix_terminal_cmd_host_created', 'terminal_command_logs', ['host_id', 'created_at'])
if not index_exists('ix_terminal_cmd_user_created', 'terminal_command_logs'):
op.create_index('ix_terminal_cmd_user_created', 'terminal_command_logs', ['user_id', 'created_at'])
if not index_exists('ix_terminal_cmd_host_hash', 'terminal_command_logs'):
op.create_index('ix_terminal_cmd_host_hash', 'terminal_command_logs', ['host_id', 'command_hash'])
def downgrade() -> None:
# Drop indexes
op.drop_index('ix_terminal_cmd_host_hash', table_name='terminal_command_logs')
op.drop_index('ix_terminal_cmd_user_created', table_name='terminal_command_logs')
op.drop_index('ix_terminal_cmd_host_created', table_name='terminal_command_logs')
op.drop_index('ix_terminal_command_logs_command_hash', table_name='terminal_command_logs')
op.drop_index('ix_terminal_command_logs_terminal_session_id', table_name='terminal_command_logs')
op.drop_index('ix_terminal_command_logs_user_id', table_name='terminal_command_logs')
op.drop_index('ix_terminal_command_logs_host_id', table_name='terminal_command_logs')
op.drop_index('ix_terminal_command_logs_created_at', table_name='terminal_command_logs')
# Drop table
op.drop_table('terminal_command_logs')