Files
sure/app/components/DS/tabs_controller.js
T
Juan José Mata fd6f4ff078 Add live AI checks to system health (#3155)
* Add live AI checks to system health

Give super admins a dedicated AI status view with bounded liveness probes for LLMs, vector stores, pgvector, and embedding endpoints. Record sanitized failures in both the system debug log and Rails logger, and document the recommended local configuration.\n\nCloses #3145

* Fix AI health CI checks

* Address AI health review feedback

* Correct Ollama model preload guidance

* Distinguish OpenAI-compatible providers

* Make Ollama startup readiness explicit

* Recognize Cloudflare AI endpoints
2026-08-24 22:41:08 +02:00

110 lines
3.2 KiB
JavaScript

import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="tabs--components"
export default class extends Controller {
static classes = ["navBtnActive", "navBtnInactive"];
static targets = ["panel", "navBtn"];
static values = {
sessionKey: String,
urlParamKey: String,
navigateOnChange: Boolean,
};
show(e) {
const btn = e.target.closest("button");
const selectedTabId = btn.dataset.id;
this.navBtnTargets.forEach((navBtn) => {
const isSelected = navBtn.dataset.id === selectedTabId;
if (isSelected) {
navBtn.classList.add(...this.navBtnActiveClasses);
navBtn.classList.remove(...this.navBtnInactiveClasses);
} else {
navBtn.classList.add(...this.navBtnInactiveClasses);
navBtn.classList.remove(...this.navBtnActiveClasses);
}
// Roving tabindex per WAI-ARIA APG: only the active tab is in
// the tab order. ArrowLeft/Right (see handleKeydown) moves focus
// across the tablist; Tab moves past the widget.
navBtn.setAttribute("aria-selected", isSelected.toString());
navBtn.setAttribute("tabindex", isSelected ? "0" : "-1");
});
this.panelTargets.forEach((panel) => {
if (panel.dataset.id === selectedTabId) {
panel.classList.remove("hidden");
} else {
panel.classList.add("hidden");
}
});
if (this.urlParamKeyValue) {
const url = new URL(window.location.href);
url.searchParams.set(this.urlParamKeyValue, selectedTabId);
if (this.navigateOnChangeValue) {
window.location.assign(url.toString());
return;
}
window.history.replaceState({}, "", url);
}
// Update URL with the selected tab
if (this.sessionKeyValue) {
this.#updateSessionPreference(selectedTabId);
}
}
// WAI-ARIA APG "Tabs with Manual Activation" — arrow keys move
// focus, Enter/Space activates. Prevents accidental tab swap when
// tabbing through, which is important here because some tab
// contents trigger Turbo fetches.
handleKeydown(e) {
const navBtns = this.navBtnTargets;
const currentIndex = navBtns.indexOf(e.target);
if (currentIndex === -1) return;
let nextIndex = null;
switch (e.key) {
case "ArrowRight":
nextIndex = (currentIndex + 1) % navBtns.length;
break;
case "ArrowLeft":
nextIndex = (currentIndex - 1 + navBtns.length) % navBtns.length;
break;
case "Home":
nextIndex = 0;
break;
case "End":
nextIndex = navBtns.length - 1;
break;
case "Enter":
case " ":
e.preventDefault();
this.show(e);
return;
default:
return;
}
e.preventDefault();
navBtns[nextIndex].focus();
}
#updateSessionPreference(selectedTabId) {
fetch("/current_session", {
method: "PUT",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-CSRF-Token": document.querySelector('[name="csrf-token"]').content,
Accept: "application/json",
},
body: new URLSearchParams({
"current_session[tab_key]": this.sessionKeyValue,
"current_session[tab_value]": selectedTabId,
}).toString(),
});
}
}