mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-26 21:17:13 +09:00
Probe and report agent feature capabilities (cron, docker, systemd, nginx, etc.) so the panel can gate target pickers without extra round-trips. Key changes: - Add agent/internal/capabilities: probes host for features and platform/distro, returns protocol.CapabilitiesMessage cached at agent startup. - Agent: store probed capabilities, send them on every transport reconnect (sendCapabilities) and include them when using poll transport. - Protocol: introduce TypeCapabilities and CapabilitiesMessage (map[string]bool + platform/distro fields). - Poll transport: accept CapabilitiesMessage in Send(), serialize to raw payload and queue for next /poll; support one-shot capabilities in poll body. - Backend: handle capabilities from socket (on_capabilities) and poll endpoint; persist capabilities in in-memory ConnectedAgent (agent_registry) with helpers get_capabilities/has_capability; include capabilities/platform info in servers API. - New fleet API (GET /api/v1/fleet/targets): list runnable targets (local + connected agents) and optionally filter agents by a requested feature using the advertised capability map. Rationale: make agent feature availability explicit and discoverable to the panel so UI pickers can show only applicable agents without asking agents on every UI interaction.
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""
|
|
Fleet API
|
|
|
|
Cross-server endpoints that don't belong to any single server's resource
|
|
tree — primarily the target-picker support that every agent-aware
|
|
feature page consumes.
|
|
|
|
The "target" abstraction:
|
|
|
|
target = {"kind": "local"}
|
|
| {"kind": "agent", "server_id": "...", "server_name": "..."}
|
|
|
|
Pages render a picker; clicks send `target` in the request body of
|
|
feature CRUD calls; the backend dispatches against `target.kind`. See
|
|
`backend/app/services/fleet_targeting.py` (Phase 1) for the dispatch
|
|
helper.
|
|
|
|
This module is read-only; it does not write any state.
|
|
"""
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
from flask_jwt_extended import jwt_required
|
|
|
|
from app.models.server import Server
|
|
from app.services.agent_registry import agent_registry
|
|
|
|
|
|
fleet_bp = Blueprint('fleet', __name__)
|
|
|
|
|
|
@fleet_bp.route('/targets', methods=['GET'])
|
|
@jwt_required()
|
|
def list_targets():
|
|
"""List runnable targets, optionally filtered by feature.
|
|
|
|
Query params:
|
|
feature — capability name (cron, docker, systemd, …). When
|
|
present, only agents whose advertised capability
|
|
map has feature=true are returned. When absent,
|
|
every connected agent is returned regardless of
|
|
capabilities.
|
|
|
|
The local panel host is always included as the first entry. Even
|
|
if no agent has the requested feature the caller still gets the
|
|
local target back, which matches the behaviour the UI expects:
|
|
"the local target is always available, agents are additive."
|
|
"""
|
|
feature = (request.args.get('feature') or '').strip() or None
|
|
|
|
targets = [{'kind': 'local'}]
|
|
|
|
# Walk the in-memory registry of connected agents. We intentionally
|
|
# do NOT pull from the Server table — capabilities are per-session
|
|
# state, and a server with status=offline can't take a command no
|
|
# matter what it last reported.
|
|
for server_id in agent_registry.get_connected_servers():
|
|
if feature and not agent_registry.has_capability(server_id, feature):
|
|
continue
|
|
|
|
# Look up the display name. We could cache this on
|
|
# ConnectedAgent, but the table is small and the lookup is
|
|
# cheap; not worth the staleness risk.
|
|
server = Server.query.get(server_id)
|
|
if not server:
|
|
# Connected agent without a Server row would be a bug
|
|
# elsewhere; skip rather than 500.
|
|
continue
|
|
|
|
caps = agent_registry.get_capabilities(server_id) or {}
|
|
|
|
targets.append({
|
|
'kind': 'agent',
|
|
'server_id': server.id,
|
|
'server_name': server.name,
|
|
'hostname': server.hostname,
|
|
'capabilities': caps,
|
|
})
|
|
|
|
return jsonify({'targets': targets})
|