This commit is contained in:
@@ -101,8 +101,48 @@
|
|||||||
a.classList.toggle('active', a === active);
|
a.classList.toggle('active', a === active);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Dynamic deep-links: `#devices/<id>` and `#devices/<id>/exec`.
|
||||||
|
// The detail / exec fragments are designed to swap into the
|
||||||
|
// devices index page's `#devices-region` section, so when we
|
||||||
|
// land here from a page refresh we have to chain two ajax calls:
|
||||||
|
// first render the parent page (which provides `#devices-region`),
|
||||||
|
// then swap the fragment into it. htmx.ajax has returned a Promise
|
||||||
|
// since 1.9.4, so the `.then` chain is safe at our pinned 1.9.10.
|
||||||
|
const DEEP_LINK_PATTERNS = [
|
||||||
|
{
|
||||||
|
re: /^#devices\/([^/]+)\/exec$/,
|
||||||
|
parent: '/admin/pages/devices',
|
||||||
|
fragment: id => `/admin/pages/devices/${encodeURIComponent(id)}/exec`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
re: /^#devices\/([^/]+)$/,
|
||||||
|
parent: '/admin/pages/devices',
|
||||||
|
fragment: id => `/admin/pages/devices/${encodeURIComponent(id)}/detail`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
function loadDeepLink(hash) {
|
||||||
|
for (const p of DEEP_LINK_PATTERNS) {
|
||||||
|
const m = hash.match(p.re);
|
||||||
|
if (!m) continue;
|
||||||
|
const id = decodeURIComponent(m[1]);
|
||||||
|
Promise.resolve(
|
||||||
|
htmx.ajax('GET', p.parent, { target: '#main', swap: 'innerHTML' })
|
||||||
|
).then(() =>
|
||||||
|
htmx.ajax('GET', p.fragment(id), {
|
||||||
|
target: '#devices-region',
|
||||||
|
swap: 'innerHTML',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
function loadFromHash() {
|
function loadFromHash() {
|
||||||
const hash = location.hash || '#users';
|
const hash = location.hash || '#users';
|
||||||
|
if (loadDeepLink(hash)) {
|
||||||
|
refreshActive();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const subUrl = SUB_ROUTES[hash];
|
const subUrl = SUB_ROUTES[hash];
|
||||||
if (subUrl) {
|
if (subUrl) {
|
||||||
htmx.ajax('GET', subUrl, { target: '#main', swap: 'innerHTML' });
|
htmx.ajax('GET', subUrl, { target: '#main', swap: 'innerHTML' });
|
||||||
|
|||||||
@@ -1074,12 +1074,14 @@ fn render_device_row(
|
|||||||
</a>
|
</a>
|
||||||
<button class="w-full text-left px-2 py-1 text-xs hover:bg-slate-800 rounded"
|
<button class="w-full text-left px-2 py-1 text-xs hover:bg-slate-800 rounded"
|
||||||
hx-get="/admin/pages/devices/{id}/detail"
|
hx-get="/admin/pages/devices/{id}/detail"
|
||||||
hx-target="#devices-region" hx-swap="innerHTML">
|
hx-target="#devices-region" hx-swap="innerHTML"
|
||||||
|
hx-push-url="#devices/{id}">
|
||||||
{details}
|
{details}
|
||||||
</button>
|
</button>
|
||||||
<button class="w-full text-left px-2 py-1 text-xs hover:bg-slate-800 rounded"
|
<button class="w-full text-left px-2 py-1 text-xs hover:bg-slate-800 rounded"
|
||||||
hx-get="/admin/pages/devices/{id}/exec"
|
hx-get="/admin/pages/devices/{id}/exec"
|
||||||
hx-target="#devices-region" hx-swap="innerHTML">
|
hx-target="#devices-region" hx-swap="innerHTML"
|
||||||
|
hx-push-url="#devices/{id}/exec">
|
||||||
{run_command}
|
{run_command}
|
||||||
</button>
|
</button>
|
||||||
<hr class="border-slate-700 my-1" />
|
<hr class="border-slate-700 my-1" />
|
||||||
@@ -1146,12 +1148,15 @@ pub async fn list_fragment(
|
|||||||
|
|
||||||
/// "Back to devices" — refetches the devices table fragment via HTMX
|
/// "Back to devices" — refetches the devices table fragment via HTMX
|
||||||
/// and swaps it back into `#devices-region`. Used by the detail page.
|
/// and swaps it back into `#devices-region`. Used by the detail page.
|
||||||
|
/// `hx-push-url="#devices"` resets the address bar to the list-level
|
||||||
|
/// hash so a subsequent refresh lands on the list, not the detail.
|
||||||
fn back_button(lang: Lang) -> String {
|
fn back_button(lang: Lang) -> String {
|
||||||
format!(
|
format!(
|
||||||
r##"<button class="text-xs text-sky-300 hover:text-sky-200"
|
r##"<button class="text-xs text-sky-300 hover:text-sky-200"
|
||||||
hx-get="/admin/pages/devices/list-fragment"
|
hx-get="/admin/pages/devices/list-fragment"
|
||||||
hx-target="#devices-region"
|
hx-target="#devices-region"
|
||||||
hx-swap="innerHTML">{label}</button>"##,
|
hx-swap="innerHTML"
|
||||||
|
hx-push-url="#devices">{label}</button>"##,
|
||||||
label = t(lang, "devices.back"),
|
label = t(lang, "devices.back"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,8 @@ async fn render_page(
|
|||||||
<h2 class="text-lg font-semibold">{heading} <code class="font-mono text-sky-300">{id}</code></h2>
|
<h2 class="text-lg font-semibold">{heading} <code class="font-mono text-sky-300">{id}</code></h2>
|
||||||
<button class="text-xs text-sky-300 hover:text-sky-200"
|
<button class="text-xs text-sky-300 hover:text-sky-200"
|
||||||
hx-get="/admin/pages/devices/list-fragment"
|
hx-get="/admin/pages/devices/list-fragment"
|
||||||
hx-target="#devices-region" hx-swap="innerHTML">{back}</button>
|
hx-target="#devices-region" hx-swap="innerHTML"
|
||||||
|
hx-push-url="#devices">{back}</button>
|
||||||
</header>"##,
|
</header>"##,
|
||||||
heading = t(lang, "exec.heading"),
|
heading = t(lang, "exec.heading"),
|
||||||
id = html_escape(peer_id),
|
id = html_escape(peer_id),
|
||||||
|
|||||||
Reference in New Issue
Block a user