Add timeout handling and loading state management in API calls

This commit is contained in:
Andy Oknen 2025-07-31 04:51:55 +00:00
parent 1f75299312
commit fcb5efd753
4 changed files with 76 additions and 2 deletions

View file

@ -15,12 +15,18 @@ class YggdrasilAPI {
*/ */
async callAdmin(command, args = {}) { async callAdmin(command, args = {}) {
const url = command ? `${this.baseURL}/${command}` : this.baseURL; const url = command ? `${this.baseURL}/${command}` : this.baseURL;
// Create AbortController for timeout functionality
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
const options = { const options = {
method: Object.keys(args).length > 0 ? 'POST' : 'GET', method: Object.keys(args).length > 0 ? 'POST' : 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
credentials: 'same-origin' // Include session cookies credentials: 'same-origin', // Include session cookies
signal: controller.signal
}; };
if (Object.keys(args).length > 0) { if (Object.keys(args).length > 0) {
@ -29,6 +35,7 @@ class YggdrasilAPI {
try { try {
const response = await fetch(url, options); const response = await fetch(url, options);
clearTimeout(timeoutId); // Clear timeout on successful response
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`); throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@ -42,6 +49,13 @@ class YggdrasilAPI {
return data.response || data.commands; return data.response || data.commands;
} catch (error) { } catch (error) {
clearTimeout(timeoutId); // Clear timeout on error
if (error.name === 'AbortError') {
console.error(`API call timeout for ${command}:`, error);
throw new Error('Request timeout - service may be unavailable');
}
console.error(`API call failed for ${command}:`, error); console.error(`API call failed for ${command}:`, error);
throw error; throw error;
} }

View file

@ -7,12 +7,22 @@
window.nodeInfo = null; window.nodeInfo = null;
window.peersData = null; window.peersData = null;
let isLoading = false; let isLoading = false;
let isLoadingNodeInfo = false;
let isLoadingPeers = false;
/** /**
* Load and display node information * Load and display node information
*/ */
async function loadNodeInfo() { async function loadNodeInfo() {
if (isLoadingNodeInfo) {
console.log('Node info request already in progress, skipping...');
return window.nodeInfo;
}
try { try {
isLoadingNodeInfo = true;
const info = await window.yggAPI.getSelf(); const info = await window.yggAPI.getSelf();
window.nodeInfo = info; window.nodeInfo = info;
updateNodeInfoDisplay(info); updateNodeInfoDisplay(info);
@ -21,6 +31,8 @@ async function loadNodeInfo() {
console.error('Failed to load node info:', error); console.error('Failed to load node info:', error);
showError('Failed to load node information: ' + error.message); showError('Failed to load node information: ' + error.message);
throw error; throw error;
} finally {
isLoadingNodeInfo = false;
} }
} }
@ -28,7 +40,13 @@ async function loadNodeInfo() {
* Load and display peers information * Load and display peers information
*/ */
async function loadPeers() { async function loadPeers() {
if (isLoadingPeers) {
console.log('Peers request already in progress, skipping...');
return window.peersData;
}
try { try {
isLoadingPeers = true;
const data = await window.yggAPI.getPeers(); const data = await window.yggAPI.getPeers();
window.peersData = data; window.peersData = data;
updatePeersDisplay(data); updatePeersDisplay(data);
@ -37,6 +55,8 @@ async function loadPeers() {
console.error('Failed to load peers:', error); console.error('Failed to load peers:', error);
showError('Failed to load peers information: ' + error.message); showError('Failed to load peers information: ' + error.message);
throw error; throw error;
} finally {
isLoadingPeers = false;
} }
} }
@ -100,6 +120,8 @@ function updatePeersDisplay(data) {
window.updateNodeInfoDisplay = updateNodeInfoDisplay; window.updateNodeInfoDisplay = updateNodeInfoDisplay;
window.updatePeersDisplay = updatePeersDisplay; window.updatePeersDisplay = updatePeersDisplay;
// Expose copy functions to window for access from HTML onclick handlers // Expose copy functions to window for access from HTML onclick handlers
window.copyNodeKey = copyNodeKey; window.copyNodeKey = copyNodeKey;
window.copyNodeAddress = copyNodeAddress; window.copyNodeAddress = copyNodeAddress;
@ -359,18 +381,23 @@ function copyPeerKey(key) {
} }
} }
/** /**
* Auto-refresh data * Auto-refresh data
*/ */
function startAutoRefresh() { function startAutoRefresh() {
// Refresh every 30 seconds // Refresh every 30 seconds
setInterval(async () => { setInterval(async () => {
if (!isLoading) { // Only proceed if individual requests are not already in progress
if (!isLoadingNodeInfo && !isLoadingPeers) {
try { try {
await Promise.all([loadNodeInfo(), loadPeers()]); await Promise.all([loadNodeInfo(), loadPeers()]);
} catch (error) { } catch (error) {
console.error('Auto-refresh failed:', error); console.error('Auto-refresh failed:', error);
} }
} else {
console.log('Skipping auto-refresh - requests already in progress');
} }
}, 30000); }, 30000);
} }

View file

@ -14,6 +14,11 @@
</head> </head>
<body> <body>
<!-- Decorative progress bar -->
<div class="progress-timer-container">
<div class="progress-timer-bar"></div>
</div>
<div class="container"> <div class="container">
<header> <header>
<div class="header-content"> <div class="header-content">

View file

@ -114,6 +114,16 @@
--shadow-heavy: rgba(0, 0, 0, 0.5); --shadow-heavy: rgba(0, 0, 0, 0.5);
} }
/* Dark theme progress bar adjustments */
[data-theme="dark"] .progress-timer-container {
background: rgba(0, 0, 0, 0.4);
}
[data-theme="dark"] .progress-timer-bar {
background: linear-gradient(90deg, #3498db, #27ae60);
box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -128,6 +138,24 @@ body {
overflow: hidden; /* Prevent body scroll */ overflow: hidden; /* Prevent body scroll */
} }
/* Decorative progress bar */
.progress-timer-container {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.2);
z-index: 1000;
}
.progress-timer-bar {
height: 100%;
background: linear-gradient(90deg, #3498db, #2ecc71);
width: 100%;
box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);
}
.container { .container {
display: grid; display: grid;
grid-template-columns: 250px 1fr; grid-template-columns: 250px 1fr;