Test custom.css
This commit is contained in:
@@ -1,3 +1,407 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.title = "KALLILAB CORE";
|
||||
});
|
||||
/* ============================================================================
|
||||
KalliLab — CUSTOM JAVASCRIPT
|
||||
Basiert auf LionCityGaming/homepage custom.js (MIT License)
|
||||
Anpassungen: KalliLab Tab-Namen, keine HA/EPL/Glance iFrames
|
||||
============================================================================ */
|
||||
|
||||
const CONFIG = {
|
||||
STORAGE: {
|
||||
KEY: "lastFocusedTabId",
|
||||
},
|
||||
TIMING: {
|
||||
RETRY_DELAY: 500,
|
||||
STANDARD_REFRESH: 1800000, // 30 Minuten
|
||||
QUICK_REFRESH: 60000, // 1 Minute
|
||||
RETRY_ON_ERROR: 30000,
|
||||
BATCH_DELAY: 100,
|
||||
},
|
||||
SERVICES: {
|
||||
QUICK_REFRESH: [], // keine kritischen Quick-Refresh Widgets
|
||||
},
|
||||
};
|
||||
|
||||
const RELOAD_BUTTON_SELECTORS = [
|
||||
"#revalidate",
|
||||
'[data-testid="revalidate"]',
|
||||
".reload-button",
|
||||
'button[aria-label="Reload"]',
|
||||
'[role="button"][aria-label="Reload"]',
|
||||
];
|
||||
|
||||
// Keine iFrame-Widgets in KalliLab
|
||||
const IFRAME_CONFIG = [];
|
||||
|
||||
// Tab-Mapping für KalliLab Tabs (5 Tabs)
|
||||
const TAB_MAPPING = {
|
||||
"#ueberblick": ["#Überblick-tab", "#Überblick"],
|
||||
"#system": ["#System-tab", "#System"],
|
||||
"#sicherheit": ["#Sicherheit-tab", "#Sicherheit"],
|
||||
"#dienste": ["#Dienste-tab", "#Dienste"],
|
||||
"#backends": ["#Backends-tab", "#Backends"],
|
||||
"": ["#Überblick-tab", "#Überblick"],
|
||||
};
|
||||
|
||||
const state = {
|
||||
lastUpdate: new WeakMap(),
|
||||
currentFocusedTab: null,
|
||||
observers: {
|
||||
reloadButton: null,
|
||||
resize: null,
|
||||
},
|
||||
};
|
||||
|
||||
const domCache = {
|
||||
myTab: null,
|
||||
tabContents: null,
|
||||
activeTabContent: null,
|
||||
|
||||
initCache() {
|
||||
this.myTab = document.getElementById("myTab");
|
||||
this.tabContents = document.querySelectorAll(".tabcontent");
|
||||
this.updateActiveTab();
|
||||
},
|
||||
|
||||
updateActiveTab() {
|
||||
this.activeTabContent = document.querySelector(".tabcontent.active");
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.myTab = null;
|
||||
this.tabContents = null;
|
||||
this.activeTabContent = null;
|
||||
},
|
||||
};
|
||||
|
||||
const storage = {
|
||||
save(tabId) {
|
||||
try {
|
||||
localStorage.setItem(CONFIG.STORAGE.KEY, tabId);
|
||||
} catch (error) {
|
||||
console.warn("Storage save failed:", error);
|
||||
}
|
||||
},
|
||||
|
||||
get() {
|
||||
try {
|
||||
return localStorage.getItem(CONFIG.STORAGE.KEY);
|
||||
} catch (error) {
|
||||
console.warn("Storage retrieval failed:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function executedFunction(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function removeReloadButton() {
|
||||
RELOAD_BUTTON_SELECTORS.forEach((selector) => {
|
||||
document.querySelectorAll(selector).forEach((element) => element.remove());
|
||||
});
|
||||
}
|
||||
|
||||
function setupReloadButtonObserver() {
|
||||
if (state.observers.reloadButton) {
|
||||
return state.observers.reloadButton;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(
|
||||
throttle((mutations) => {
|
||||
const hasAddedNodes = mutations.some((m) => m.addedNodes.length > 0);
|
||||
if (hasAddedNodes) {
|
||||
removeReloadButton();
|
||||
}
|
||||
}, 100),
|
||||
);
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["class", "style"],
|
||||
});
|
||||
|
||||
state.observers.reloadButton = observer;
|
||||
return observer;
|
||||
}
|
||||
|
||||
function updateServiceCard(card, data) {
|
||||
requestAnimationFrame(() => {
|
||||
const titleElement = card.querySelector(".card-title");
|
||||
const statusElement = card.querySelector(".card-status");
|
||||
|
||||
if (titleElement && data.title) {
|
||||
titleElement.textContent = data.title;
|
||||
}
|
||||
|
||||
if (statusElement) {
|
||||
statusElement.textContent = Array.isArray(data)
|
||||
? `${data.length} items`
|
||||
: (data.status ?? (typeof data === "object" ? "Data received" : ""));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateServiceCardError(card, error) {
|
||||
requestAnimationFrame(() => {
|
||||
const statusElement = card.querySelector(".card-status");
|
||||
if (statusElement) {
|
||||
statusElement.textContent = error.message.includes("404")
|
||||
? "Service unavailable"
|
||||
: "Error loading data";
|
||||
statusElement.style.color = "red";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshService(card) {
|
||||
const apiEndpoint = card.dataset.apiEndpoint;
|
||||
if (!apiEndpoint) return;
|
||||
|
||||
const serviceId = card.id || apiEndpoint;
|
||||
const now = Date.now();
|
||||
const lastUpdateTime = state.lastUpdate.get(card) || 0;
|
||||
const isQuickRefreshService = CONFIG.SERVICES.QUICK_REFRESH.includes(serviceId);
|
||||
const minInterval = isQuickRefreshService
|
||||
? CONFIG.TIMING.QUICK_REFRESH
|
||||
: CONFIG.TIMING.STANDARD_REFRESH;
|
||||
|
||||
if (now - lastUpdateTime < minInterval) return;
|
||||
|
||||
try {
|
||||
card.classList.add("updating");
|
||||
|
||||
const response = await fetch(apiEndpoint, {
|
||||
signal: AbortSignal.timeout(10000),
|
||||
}).then((r) => {
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
});
|
||||
|
||||
updateServiceCard(card, response);
|
||||
state.lastUpdate.set(card, now);
|
||||
} catch (error) {
|
||||
console.error(`${serviceId} refresh failed:`, error);
|
||||
updateServiceCardError(card, error);
|
||||
state.lastUpdate.set(card, now - (minInterval - CONFIG.TIMING.RETRY_ON_ERROR));
|
||||
} finally {
|
||||
card.classList.remove("updating");
|
||||
}
|
||||
}
|
||||
|
||||
async function batchUpdateServiceCards(cards) {
|
||||
const batchSize = 3;
|
||||
const batches = [];
|
||||
|
||||
for (let i = 0; i < cards.length; i += batchSize) {
|
||||
batches.push(cards.slice(i, i + batchSize));
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
await Promise.all(batch.map(refreshService));
|
||||
if (batches.length > 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, CONFIG.TIMING.BATCH_DELAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleTabFocusFromURL() {
|
||||
const hash = window.location.hash.toLowerCase();
|
||||
const mapping = TAB_MAPPING[hash] || TAB_MAPPING[""];
|
||||
const [tabSelector, contentSelector] = mapping;
|
||||
|
||||
const tabToFocus = document.querySelector(tabSelector);
|
||||
const contentToShow = document.querySelector(contentSelector);
|
||||
|
||||
if (tabToFocus) {
|
||||
setTabFocus(tabToFocus);
|
||||
storage.save(tabToFocus.id);
|
||||
|
||||
domCache.tabContents.forEach((content) => {
|
||||
content.classList.remove("active");
|
||||
content.style.display = "none";
|
||||
});
|
||||
|
||||
if (contentToShow) {
|
||||
showTabContent(contentToShow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTabFocus(tab) {
|
||||
requestAnimationFrame(() => {
|
||||
if (state.currentFocusedTab) {
|
||||
state.currentFocusedTab.classList.remove("tab-focused");
|
||||
}
|
||||
state.currentFocusedTab = tab;
|
||||
state.currentFocusedTab.classList.add("tab-focused");
|
||||
});
|
||||
}
|
||||
|
||||
function showTabContent(contentElement) {
|
||||
if (!contentElement) return;
|
||||
|
||||
contentElement.classList.add("active");
|
||||
contentElement.style.display = "block";
|
||||
domCache.updateActiveTab();
|
||||
}
|
||||
|
||||
async function preloadAllTabs() {
|
||||
const tabContents = document.querySelectorAll(".tab-pane");
|
||||
const serviceCards = new Set();
|
||||
|
||||
tabContents.forEach((tab) => {
|
||||
tab.querySelectorAll(".service-card").forEach((card) => serviceCards.add(card));
|
||||
});
|
||||
|
||||
await batchUpdateServiceCards(Array.from(serviceCards));
|
||||
}
|
||||
|
||||
function setupPeriodicRefresh() {
|
||||
const debouncedRefresh = debounce(async () => {
|
||||
if (domCache.activeTabContent) {
|
||||
const cards = domCache.activeTabContent.querySelectorAll(
|
||||
".service-card[data-api-endpoint]",
|
||||
);
|
||||
await batchUpdateServiceCards(Array.from(cards));
|
||||
}
|
||||
}, 250);
|
||||
|
||||
setInterval(
|
||||
debouncedRefresh,
|
||||
Math.min(CONFIG.TIMING.QUICK_REFRESH, CONFIG.TIMING.STANDARD_REFRESH),
|
||||
);
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
debouncedRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
domCache.myTab?.addEventListener("click", (event) => {
|
||||
if (event.target.matches('[id$="-tab"]')) {
|
||||
debouncedRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeTabFocus() {
|
||||
// KalliLab Tab-Selektoren
|
||||
const tabs = document.querySelectorAll(
|
||||
"#Überblick-tab, #System-tab, #Sicherheit-tab, #Dienste-tab, #Backends-tab",
|
||||
);
|
||||
|
||||
handleTabFocusFromURL();
|
||||
|
||||
if (!window.location.hash) {
|
||||
const savedTabId = storage.get();
|
||||
const savedTab = savedTabId && document.getElementById(savedTabId);
|
||||
|
||||
if (savedTab) {
|
||||
setTabFocus(savedTab);
|
||||
} else {
|
||||
const activeTab = document.querySelector(".tabcontent.active");
|
||||
const correspondingTab =
|
||||
activeTab && document.querySelector(`[aria-controls="${activeTab.id}"]`);
|
||||
if (correspondingTab) {
|
||||
setTabFocus(correspondingTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabs.forEach((tab) => {
|
||||
const handleTabAction = function () {
|
||||
setTabFocus(this);
|
||||
storage.save(this.id);
|
||||
};
|
||||
|
||||
tab.addEventListener("click", handleTabAction);
|
||||
|
||||
tab.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
this.click();
|
||||
handleTabAction.call(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
if (state.currentFocusedTab) {
|
||||
storage.save(state.currentFocusedTab.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeEverything() {
|
||||
removeReloadButton();
|
||||
setupReloadButtonObserver();
|
||||
|
||||
[100, 500, 1000].forEach((delay) => {
|
||||
setTimeout(removeReloadButton, delay);
|
||||
});
|
||||
|
||||
const hasRequiredElements =
|
||||
document.querySelector("#myTab") && document.querySelector(".service-card");
|
||||
|
||||
if (hasRequiredElements) {
|
||||
domCache.initCache();
|
||||
initializeTabFocus();
|
||||
preloadAllTabs();
|
||||
setupPeriodicRefresh();
|
||||
} else {
|
||||
setTimeout(initializeEverything, CONFIG.TIMING.RETRY_DELAY);
|
||||
}
|
||||
|
||||
window.addEventListener("orientationchange", () => {
|
||||
setTimeout(removeReloadButton, 100);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", removeReloadButton);
|
||||
window.addEventListener("load", initializeEverything);
|
||||
|
||||
if (typeof window.htmlLoaded === "function") {
|
||||
const originalHtmlLoaded = window.htmlLoaded;
|
||||
window.htmlLoaded = () => {
|
||||
originalHtmlLoaded();
|
||||
initializeEverything();
|
||||
};
|
||||
}
|
||||
|
||||
if ("ontouchstart" in window) {
|
||||
window.addEventListener("touchend", () => {
|
||||
setTimeout(removeReloadButton, 100);
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (state.observers.reloadButton) {
|
||||
state.observers.reloadButton.disconnect();
|
||||
state.observers.reloadButton = null;
|
||||
}
|
||||
if (state.observers.resize) {
|
||||
state.observers.resize.disconnect();
|
||||
state.observers.resize = null;
|
||||
}
|
||||
domCache.clear();
|
||||
state.currentFocusedTab = null;
|
||||
}
|
||||
|
||||
window.addEventListener("unload", cleanup);
|
||||
Reference in New Issue
Block a user