ui: tighten portal layout and status polling
This commit is contained in:
parent
35f7b77c1b
commit
d62ac0fd45
@ -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>
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user