389002fc6f
Adds a breadcrumb trail to nested views for orientation. Mechanism: a
{% block breadcrumb %} slot in base.html (+ styling) and a shared crumbs()
macro in templates/_macros.html; each nested page fills the block with its
trail (root→current, last item is the current page). Pages without the block
render no bar, so top-level nav roots stay clean.
Applied to: Ansible (browse, schedules, playbook editor, run detail) +
inventory (targets/groups + detail), host_agent (fleet, host detail,
settings), hosts form, settings tabs (ansible/auth/notifications/plugins/
reports + plugin detail), dashboard (list, edit), and alerts (rule form,
maintenance + new). Dynamic labels (host/target/run/dashboard names) come
from the page context — no route changes. All 60 templates Jinja-compile.
Task #873.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
1.7 KiB
HTML
46 lines
1.7 KiB
HTML
{% extends "base.html" %}
|
|
{% from "_macros.html" import crumbs %}
|
|
{% block title %}Edit: {{ dashboard.name }} — Steward{% endblock %}
|
|
{% block breadcrumb %}{{ crumbs([("Dashboard", "/"), ("Dashboards", "/dashboards/"), ("Edit " ~ dashboard.name, "")]) }}{% endblock %}
|
|
{% block content %}
|
|
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:1.5rem;">
|
|
<h1 class="page-title" style="margin-bottom:0;">Edit: {{ dashboard.name }}</h1>
|
|
<a href="/d/{{ dashboard.id }}" class="btn btn-ghost btn-sm">Done</a>
|
|
</div>
|
|
{% include "dashboard/_edit_panels.html" %}
|
|
{% endblock %}
|
|
{% block extra_scripts %}
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
|
<script>
|
|
(function () {
|
|
function initSortable() {
|
|
var el = document.getElementById('widget-sort-list');
|
|
if (!el) return;
|
|
var dashId = el.dataset.dashId;
|
|
Sortable.create(el, {
|
|
handle: '.drag-handle',
|
|
animation: 150,
|
|
ghostClass: 'sortable-ghost',
|
|
onEnd: function () {
|
|
var order = Array.from(el.querySelectorAll('[data-widget-id]'))
|
|
.map(function (row) { return parseInt(row.dataset.widgetId, 10); });
|
|
fetch('/d/' + dashId + '/edit/reorder', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ order: order }),
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
// Init on first load and after every HTMX swap (add/remove re-renders the list)
|
|
document.addEventListener('DOMContentLoaded', initSortable);
|
|
document.addEventListener('htmx:afterSwap', initSortable);
|
|
})();
|
|
</script>
|
|
<style>
|
|
.sortable-ghost { opacity: 0.4; }
|
|
.drag-handle:active { cursor: grabbing; }
|
|
</style>
|
|
{% endblock %}
|