feat(ansible): default source/playbook dropdowns to the first item
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 46s
CI / integration (push) Successful in 2m18s
CI / publish (push) Successful in 1m0s

The schedule and host run forms opened their Source/Playbook <select>s on a
"— choose … —" placeholder, forcing an extra click. Default them to the first
item and cascade the dependent fields so the initial state is real:

- Remove the placeholder options (schedules source+playbook, host source+
  playbook, and the shared _playbook_options fragment).
- schedules(): pass sel_source (first source, or the edited schedule's) and
  that source's pre-rendered playbooks so the playbook dropdown starts populated
  (create mode too, no longer the all-sources union).
- Cascade on load/change: the source <select> fires playbook-options then
  (hx-on::after-settle) re-triggers the playbook <select>, which loads that
  playbook's variable fields. Host form drives the whole chain from a load
  trigger; schedule create mode loads vars via the playbook <select>'s own load
  trigger, while edit mode keeps its server-rendered prefill untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 23:35:01 -04:00
parent efc11c1837
commit 524fd1f509
4 changed files with 20 additions and 14 deletions
+7 -2
View File
@@ -373,7 +373,6 @@ async def schedules():
{"name": s["name"], "playbooks": src_module.discover_playbooks(s["path"])} {"name": s["name"], "playbooks": src_module.discover_playbooks(s["path"])}
for s in _get_sources() for s in _get_sources()
] ]
all_playbooks = sorted({p for sd in source_data for p in sd["playbooks"]})
rows = [] rows = []
editing = None editing = None
@@ -386,6 +385,12 @@ async def schedules():
if editing_id and s.id == editing_id: if editing_id and s.id == editing_id:
editing = s editing = s
# Default the source dropdown to the first source (or the edited schedule's
# source), and pre-render that source's playbooks so the playbook dropdown
# starts on a real item rather than a placeholder.
sel_source = editing.source_name if editing else (source_data[0]["name"] if source_data else "")
sel_playbooks = next((sd["playbooks"] for sd in source_data if sd["name"] == sel_source), [])
# Edit mode: pre-render the selected playbook's declared variables so their # Edit mode: pre-render the selected playbook's declared variables so their
# saved values show (fresh loads fetch these via HTMX from /playbook-vars). # saved values show (fresh loads fetch these via HTMX from /playbook-vars).
edit_variables: list = [] edit_variables: list = []
@@ -401,7 +406,7 @@ async def schedules():
return await render_template( return await render_template(
"ansible/schedules.html", "ansible/schedules.html",
rows=rows, groups=groups, targets=targets, rows=rows, groups=groups, targets=targets,
source_data=source_data, all_playbooks=all_playbooks, source_data=source_data, sel_source=sel_source, sel_playbooks=sel_playbooks,
editing=editing, interval_presets=INTERVAL_PRESETS, editing=editing, interval_presets=INTERVAL_PRESETS,
edit_variables=edit_variables, edit_description=edit_meta["description"], edit_variables=edit_variables, edit_description=edit_meta["description"],
edit_category=edit_meta["category"], edit_confirm=edit_meta["confirm"], edit_category=edit_meta["category"], edit_confirm=edit_meta["confirm"],
@@ -1,6 +1,7 @@
{# <option> list for a playbook <select>, populated when a source is chosen. {# <option> list for a playbook <select>, populated when a source is chosen.
Returned by /ansible/playbook-options. `selected` pre-selects one (edit). #} Returned by /ansible/playbook-options. `selected` pre-selects one; with none
<option value="">— choose playbook —</option> given the browser selects the first, so the dropdown never sits on a
placeholder. #}
{% for pb in playbooks %} {% for pb in playbooks %}
<option value="{{ pb }}" {% if pb == selected %}selected{% endif %}>{{ pb }}</option> <option value="{{ pb }}" {% if pb == selected %}selected{% endif %}>{{ pb }}</option>
{% endfor %} {% endfor %}
+7 -6
View File
@@ -89,20 +89,21 @@
<label>Source</label> <label>Source</label>
<select name="source_name" required <select name="source_name" required
hx-get="/ansible/playbook-options" hx-trigger="change" hx-get="/ansible/playbook-options" hx-trigger="change"
hx-target="#sch-playbook" hx-swap="innerHTML" hx-include="this"> hx-target="#sch-playbook" hx-swap="innerHTML" hx-include="this"
<option value="">— choose source —</option> hx-on::after-settle="htmx.trigger('#sch-playbook','change')">
{% for sd in source_data %} {% for sd in source_data %}
<option value="{{ sd.name }}" {% if editing and editing.source_name == sd.name %}selected{% endif %}>{{ sd.name }}</option> <option value="{{ sd.name }}" {% if sd.name == sel_source %}selected{% endif %}>{{ sd.name }}</option>
{% endfor %} {% endfor %}
{% if not source_data %}<option value="" disabled>No Ansible sources — add one in Settings</option>{% endif %}
</select> </select>
</div> </div>
<div class="form-group" style="margin-bottom:0;"> <div class="form-group" style="margin-bottom:0;">
<label>Playbook</label> <label>Playbook</label>
<select name="playbook_path" id="sch-playbook" required <select name="playbook_path" id="sch-playbook" required
hx-get="/ansible/playbook-vars?schedule=1" hx-trigger="change" hx-get="/ansible/playbook-vars?schedule=1"
hx-trigger="{% if not editing %}load, {% endif %}change"
hx-target="#sch-playbook-vars" hx-swap="innerHTML" hx-include="closest form"> hx-target="#sch-playbook-vars" hx-swap="innerHTML" hx-include="closest form">
<option value="">— choose playbook —</option> {% for pb in sel_playbooks %}
{% for pb in all_playbooks %}
<option value="{{ pb }}" {% if editing and editing.playbook_path == pb %}selected{% endif %}>{{ pb }}</option> <option value="{{ pb }}" {% if editing and editing.playbook_path == pb %}selected{% endif %}>{{ pb }}</option>
{% endfor %} {% endfor %}
</select> </select>
+3 -4
View File
@@ -161,9 +161,9 @@
<div class="form-group"> <div class="form-group">
<label>Source</label> <label>Source</label>
<select name="source_name" required <select name="source_name" required
hx-get="/ansible/playbook-options" hx-trigger="change" hx-get="/ansible/playbook-options" hx-trigger="load, change"
hx-target="#hp-playbook" hx-swap="innerHTML" hx-include="this"> hx-target="#hp-playbook" hx-swap="innerHTML" hx-include="this"
<option value="">— choose source —</option> hx-on::after-settle="htmx.trigger('#hp-playbook','change')">
{% for s in ansible_sources %}<option value="{{ s }}">{{ s }}</option>{% endfor %} {% for s in ansible_sources %}<option value="{{ s }}">{{ s }}</option>{% endfor %}
</select> </select>
</div> </div>
@@ -172,7 +172,6 @@
<select name="playbook_path" id="hp-playbook" required <select name="playbook_path" id="hp-playbook" required
hx-get="/ansible/playbook-vars" hx-trigger="change" hx-get="/ansible/playbook-vars" hx-trigger="change"
hx-target="#hp-vars" hx-swap="innerHTML" hx-include="closest form"> hx-target="#hp-vars" hx-swap="innerHTML" hx-include="closest form">
<option value="">— choose a source first —</option>
</select> </select>
</div> </div>
<div id="hp-vars"></div> <div id="hp-vars"></div>