From dd0acaf623728667b5bdfca68f4ea6c573d5b190 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 5 Jun 2026 18:51:33 -0400 Subject: [PATCH] feat(ansible): host edit page Ansible target link/create/unlink section --- steward/hosts/routes.py | 83 +++++++++++++++++++++++++++++-- steward/templates/hosts/form.html | 47 +++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/steward/hosts/routes.py b/steward/hosts/routes.py index c48e795..56b66d1 100644 --- a/steward/hosts/routes.py +++ b/steward/hosts/routes.py @@ -147,14 +147,38 @@ async def create_host(): @hosts_bp.get("//edit") @require_role(UserRole.operator) async def edit_host(host_id: str): + from steward.models.ansible_inventory import AnsibleTarget + from sqlalchemy.orm import selectinload + async with current_app.db_sessionmaker() as db: result = await db.execute(select(Host).where(Host.id == host_id)) host = result.scalar_one_or_none() - if host is None: - return "Not found", 404 + if host is None: + return "Not found", 404 + + linked_result = await db.execute( + select(AnsibleTarget) + .where(AnsibleTarget.host_id == host_id) + .options(selectinload(AnsibleTarget.groups)) + ) + linked_target = linked_result.scalar_one_or_none() + + # Only show targets not yet linked to any host (plus the currently linked one) + linkable_result = await db.execute( + select(AnsibleTarget) + .where(AnsibleTarget.host_id.is_(None)) + .order_by(AnsibleTarget.name) + ) + linkable_targets = linkable_result.scalars().all() + return await render_template( - "hosts/form.html", host=host, probe_types=list(ProbeType), - ansible_sources=_ansible_source_names()) + "hosts/form.html", + host=host, + probe_types=list(ProbeType), + ansible_sources=_ansible_source_names(), + linked_target=linked_target, + linkable_targets=linkable_targets, + ) @hosts_bp.post("/") @@ -178,6 +202,57 @@ async def update_host(host_id: str): return redirect(url_for("hosts.list_hosts")) +@hosts_bp.post("//ansible-link") +@require_role(UserRole.operator) +async def ansible_link(host_id: str): + """Link, unlink, or create an AnsibleTarget from this host.""" + from steward.models.ansible_inventory import AnsibleTarget + + form = await request.form + action = form.get("action", "") + + async with current_app.db_sessionmaker() as db: + async with db.begin(): + if action == "unlink": + linked_result = await db.execute( + select(AnsibleTarget).where(AnsibleTarget.host_id == host_id) + ) + linked = linked_result.scalar_one_or_none() + if linked: + linked.host_id = None + + elif action == "link": + target_id = form.get("target_id", "").strip() + if target_id: + result = await db.execute( + select(AnsibleTarget).where(AnsibleTarget.id == target_id) + ) + target = result.scalar_one_or_none() + if target: + # Clear any previous link for this host first + old_result = await db.execute( + select(AnsibleTarget).where(AnsibleTarget.host_id == host_id) + ) + old = old_result.scalar_one_or_none() + if old and old.id != target_id: + old.host_id = None + target.host_id = host_id + + elif action == "create": + host_result = await db.execute(select(Host).where(Host.id == host_id)) + host = host_result.scalar_one_or_none() + if host: + new_target = AnsibleTarget( + id=str(uuid.uuid4()), + name=host.name, + address=host.address, + host_id=host_id, + ) + db.add(new_target) + + return redirect(f"/hosts/{host_id}/edit") + + @hosts_bp.post("//run-playbook") @require_role(UserRole.operator) async def run_playbook(host_id: str): diff --git a/steward/templates/hosts/form.html b/steward/templates/hosts/form.html index 3963be2..1ff251f 100644 --- a/steward/templates/hosts/form.html +++ b/steward/templates/hosts/form.html @@ -88,4 +88,51 @@ {% endif %} + +{% if host %} +
+

Ansible Target

+ {% if linked_target %} +
+
+ {{ linked_target.name }} + {{ linked_target.address }} + {% if linked_target.groups %} +
+ {% for grp in linked_target.groups %} + {{ grp.name }} + {% endfor %} +
+ {% endif %} +
+ Edit Target +
+ + +
+
+ {% else %} +

+ No Ansible target linked. Link an existing target or create one from this host. +

+
+ {% if linkable_targets %} +
+ + + +
+ {% endif %} +
+ + +
+
+ {% endif %} +
+{% endif %} {% endblock %}