ui: tighten portal layout and status polling

This commit is contained in:
Brad Stein 2026-01-03 20:35:59 -03:00
parent 35f7b77c1b
commit d62ac0fd45
4 changed files with 59 additions and 9 deletions

View File

@ -14,7 +14,7 @@
</template>
<script setup>
import { onMounted, ref } from "vue";
import { onMounted, onUnmounted, ref } from "vue";
import TopBar from "./components/TopBar.vue";
import { fallbackHardware, fallbackServices, fallbackNetwork, fallbackMetrics } from "./data/sample.js";
@ -27,12 +27,13 @@ const metricsData = ref(fallbackMetrics());
const statusLoading = ref(true);
const statusFetching = ref(false);
const statusError = ref("");
let pollTimerId = null;
async function refreshLabStatus() {
if (statusFetching.value) return;
statusFetching.value = true;
const controller = new AbortController();
const timeoutId = window.setTimeout(() => controller.abort(), 6000);
const timeoutId = window.setTimeout(() => controller.abort(), 10000);
try {
const resp = await fetch("/api/lab/status", {
headers: { Accept: "application/json" },
@ -52,13 +53,23 @@ async function refreshLabStatus() {
window.clearTimeout(timeoutId);
statusLoading.value = false;
statusFetching.value = false;
scheduleNextPoll();
}
}
onMounted(() => {
refreshLabStatus();
window.setInterval(refreshLabStatus, 30000);
});
onUnmounted(() => {
if (pollTimerId) window.clearTimeout(pollTimerId);
});
function scheduleNextPoll() {
if (pollTimerId) window.clearTimeout(pollTimerId);
const delayMs = labStatus.value ? 30000 : 8000;
pollTimerId = window.setTimeout(refreshLabStatus, delayMs);
}
</script>
<style scoped>

View File

@ -39,6 +39,8 @@ const svgContent = ref("");
const renderKey = ref(props.cardId || `mermaid-${Math.random().toString(36).slice(2)}`);
const isOpen = ref(false);
let initialized = false;
let scheduledHandle = null;
let scheduledKind = "";
const renderDiagram = async () => {
if (!props.diagram) return;
@ -65,10 +67,38 @@ const renderDiagram = async () => {
}
};
onMounted(renderDiagram);
function cancelScheduledRender() {
if (!scheduledHandle) return;
if (scheduledKind === "idle" && window.cancelIdleCallback) {
window.cancelIdleCallback(scheduledHandle);
} else {
window.clearTimeout(scheduledHandle);
}
scheduledHandle = null;
scheduledKind = "";
}
function scheduleRenderDiagram() {
cancelScheduledRender();
if (!props.diagram) return;
const runner = () => {
scheduledHandle = null;
scheduledKind = "";
renderDiagram();
};
if (window.requestIdleCallback) {
scheduledKind = "idle";
scheduledHandle = window.requestIdleCallback(runner, { timeout: 1500 });
} else {
scheduledKind = "timeout";
scheduledHandle = window.setTimeout(runner, 0);
}
}
onMounted(scheduleRenderDiagram);
watch(
() => props.diagram,
() => renderDiagram()
() => scheduleRenderDiagram()
);
const onKeyDown = (event) => {
@ -76,6 +106,7 @@ const onKeyDown = (event) => {
};
const open = () => {
if (!svgContent.value) scheduleRenderDiagram();
isOpen.value = true;
};
@ -94,6 +125,7 @@ watch(isOpen, (value) => {
});
onUnmounted(() => {
cancelScheduledRender();
document.body.style.overflow = "";
window.removeEventListener("keydown", onKeyDown);
});

View File

@ -587,16 +587,18 @@ h1 {
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
align-items: stretch;
}
.account-stack {
display: flex;
flex-direction: column;
gap: 12px;
height: 100%;
}
.account-stack .module {
flex: 1;
flex: 1 1 0;
min-height: 0;
}

View File

@ -181,6 +181,10 @@ const sections = [
.section-grid {
grid-template-columns: 1fr 1fr;
}
.tiles {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.hero {
@ -201,7 +205,8 @@ const sections = [
justify-content: space-between;
gap: 18px;
margin-bottom: 14px;
min-height: 92px;
height: 92px;
overflow: hidden;
}
.group + .group {
@ -226,7 +231,7 @@ const sections = [
.tiles {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
grid-template-columns: 1fr;
gap: 12px;
}
@ -238,7 +243,7 @@ const sections = [
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.02);
transition: border-color 160ms ease, transform 160ms ease;
min-height: 112px;
min-height: 120px;
}
.tile:hover {