mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-28 07:37:56 +09:00
The agent reports a rich capability payload (sudo mode, runtime managers, detected runtimes, allowed paths, systemd JSON support) that the panel was discarding once the WebSocket disconnected. The Overview tab also gave no visibility into what the agent had discovered, which left users guessing why a feature tab was missing or a Docker call returned empty. Backend: - Server model gains seven nullable columns: cached_capabilities, cached_runtimes, cached_runtime_managers, cached_allowed_paths, cached_sudo, cached_systemd_json, capabilities_at. Migration 005 is idempotent (checks existing columns before adding) so it's safe on already-bootstrapped databases. - agent_registry.update_capabilities() now persists a snapshot to the Server row whenever the agent reports state. Wrapped in try/except so a DB hiccup doesn't drop the in-memory update. - /servers/<id> falls back to the cached snapshot when the agent is offline and exposes capabilities_stale + capabilities_at so the UI can badge stale data. Frontend: - New SystemStatusCard renders sudo mode, package manager, runtime managers, detected runtime versions, capability map (as colored chips), and file-access roots. "Stale" badge + relative timestamp + Refresh button (calls agent:recapabilities). - DockerTab now distinguishes "agent online but no docker daemon" from "no containers found", with concrete remediation hints. Load errors surface as a banner instead of being swallowed to console. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
1.6 KiB
Python
54 lines
1.6 KiB
Python
"""Cache capability snapshot per server.
|
|
|
|
Adds JSON columns to ``servers`` so the panel can render the Overview
|
|
tab when the agent is offline. Written by agent_registry.update_
|
|
capabilities every time an agent reports state.
|
|
|
|
Revision ID: 005_server_capabilities_cache
|
|
Revises: 004_deployment_jobs
|
|
Create Date: 2026-05-01
|
|
"""
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = '005_server_capabilities_cache'
|
|
down_revision = '004_deployment_jobs'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
_NEW_COLUMNS = [
|
|
('cached_capabilities', sa.JSON()),
|
|
('cached_runtimes', sa.JSON()),
|
|
('cached_runtime_managers', sa.JSON()),
|
|
('cached_allowed_paths', sa.JSON()),
|
|
('cached_sudo', sa.String(20)),
|
|
('cached_systemd_json', sa.Boolean()),
|
|
('capabilities_at', sa.DateTime()),
|
|
]
|
|
|
|
|
|
def upgrade():
|
|
conn = op.get_bind()
|
|
inspector = sa.inspect(conn)
|
|
if 'servers' not in inspector.get_table_names():
|
|
return
|
|
existing = {c['name'] for c in inspector.get_columns('servers')}
|
|
with op.batch_alter_table('servers') as batch_op:
|
|
for name, type_ in _NEW_COLUMNS:
|
|
if name not in existing:
|
|
batch_op.add_column(sa.Column(name, type_, nullable=True))
|
|
|
|
|
|
def downgrade():
|
|
conn = op.get_bind()
|
|
inspector = sa.inspect(conn)
|
|
if 'servers' not in inspector.get_table_names():
|
|
return
|
|
existing = {c['name'] for c in inspector.get_columns('servers')}
|
|
with op.batch_alter_table('servers') as batch_op:
|
|
for name, _ in _NEW_COLUMNS:
|
|
if name in existing:
|
|
batch_op.drop_column(name)
|