mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 09:23:07 +02:00
Preserve table content on API errors with header warning badge
Instead of replacing the entire table with an error alert on network failures, the previous data now stays visible and a warning icon with tooltip appears in the sub-header bar. The warning clears automatically on the next successful poll. Restructured page headers into title row + sub-header row (count, auto-refresh, warning) for better mobile layout.
This commit is contained in:
@@ -8,6 +8,7 @@ import { html, nothing } from 'lit-html';
|
||||
import { render } from 'lit-html';
|
||||
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
import { t } from './i18n.js';
|
||||
import { iconAlert } from './icons.js';
|
||||
|
||||
// Re-export lit-html utilities for page modules
|
||||
export { html, nothing, unsafeHTML };
|
||||
@@ -381,6 +382,17 @@ export function successAlert(message) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a warning badge with tooltip for transient API errors.
|
||||
* @param {string} message - Error message to display as tooltip
|
||||
* @returns {TemplateResult}
|
||||
*/
|
||||
export function warningBadge(message) {
|
||||
return html`<span class="tooltip tooltip-bottom" data-tip="${message}">
|
||||
<span class="badge badge-warning badge-sm">${iconAlert('h-4 w-4')}</span>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render pagination controls.
|
||||
* @param {number} page - Current page (1-based)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { apiGet } from '../api.js';
|
||||
import {
|
||||
html, litRender, nothing, t,
|
||||
getConfig, formatDateTime, formatDateTimeShort, formatRelativeTime,
|
||||
truncateKey, errorAlert,
|
||||
truncateKey, warningBadge,
|
||||
pagination, createFilterHandler, autoSubmit, submitOnEnter, copyToClipboard, renderNodeDisplay,
|
||||
observerIcons, observerDetailRow, toggleObserverDetail, toggleCardObserverDetail
|
||||
} from '../components.js';
|
||||
@@ -24,17 +24,29 @@ export async function render(container, params, router) {
|
||||
const tzBadge = tz && tz !== 'UTC' ? html`<span class="text-sm opacity-60">${tz}</span>` : nothing;
|
||||
const navigate = (url) => router.navigate(url);
|
||||
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
let lastContent = nothing;
|
||||
let lastTotal = null;
|
||||
|
||||
function renderPage(content, { total = null, error = null } = {}) {
|
||||
if (!error) {
|
||||
lastContent = content;
|
||||
lastTotal = total;
|
||||
}
|
||||
const displayContent = error ? lastContent : content;
|
||||
const displayTotal = error ? lastTotal : total;
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold">${t('entities.advertisements')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
</div>
|
||||
${tzBadge}
|
||||
</div>
|
||||
${content}`, container);
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
${displayTotal !== null
|
||||
? html`<span class="badge badge-lg">${t('common.total', { count: displayTotal })}</span>`
|
||||
: nothing}
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${error ? warningBadge(error) : nothing}
|
||||
</div>
|
||||
${displayContent}`, container);
|
||||
}
|
||||
|
||||
// Render page header immediately (old content stays visible until data loads)
|
||||
@@ -223,7 +235,7 @@ ${content}`, container);
|
||||
${paginationBlock}`, { total });
|
||||
|
||||
} catch (e) {
|
||||
renderPage(errorAlert(e.message));
|
||||
renderPage(nothing, { error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
html, litRender, nothing, t,
|
||||
getConfig, formatDateTime, formatDateTimeShort, formatRelativeTime,
|
||||
getChannelLabelsMap, resolveChannelLabel,
|
||||
truncateKey, errorAlert,
|
||||
truncateKey, warningBadge,
|
||||
pagination, timezoneIndicator,
|
||||
createFilterHandler, autoSubmit, submitOnEnter,
|
||||
observerIcons, observerDetailRow, toggleObserverDetail, toggleCardObserverDetail
|
||||
@@ -169,17 +169,29 @@ export async function render(container, params, router) {
|
||||
return deduped;
|
||||
}
|
||||
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
let lastContent = nothing;
|
||||
let lastTotal = null;
|
||||
|
||||
function renderPage(content, { total = null, error = null } = {}) {
|
||||
if (!error) {
|
||||
lastContent = content;
|
||||
lastTotal = total;
|
||||
}
|
||||
const displayContent = error ? lastContent : content;
|
||||
const displayTotal = error ? lastTotal : total;
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold">${t('entities.messages')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
</div>
|
||||
${tzBadge}
|
||||
</div>
|
||||
${content}`, container);
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
${displayTotal !== null
|
||||
? html`<span class="badge badge-lg">${t('common.total', { count: displayTotal })}</span>`
|
||||
: nothing}
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${error ? warningBadge(error) : nothing}
|
||||
</div>
|
||||
${displayContent}`, container);
|
||||
}
|
||||
|
||||
// Render page header immediately (old content stays visible until data loads)
|
||||
@@ -348,7 +360,7 @@ ${content}`, container);
|
||||
${paginationBlock}`, { total });
|
||||
|
||||
} catch (e) {
|
||||
renderPage(errorAlert(e.message));
|
||||
renderPage(nothing, { error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { apiGet } from '../api.js';
|
||||
import {
|
||||
html, litRender, nothing,
|
||||
getConfig, formatDateTime, formatDateTimeShort,
|
||||
truncateKey, errorAlert,
|
||||
truncateKey, warningBadge,
|
||||
pagination, timezoneIndicator,
|
||||
createFilterHandler, autoSubmit, submitOnEnter, copyToClipboard, renderNodeDisplay, t
|
||||
} from '../components.js';
|
||||
@@ -24,17 +24,29 @@ export async function render(container, params, router) {
|
||||
const tzBadge = tz && tz !== 'UTC' ? html`<span class="text-sm opacity-60">${tz}</span>` : nothing;
|
||||
const navigate = (url) => router.navigate(url);
|
||||
|
||||
function renderPage(content, { total = null } = {}) {
|
||||
let lastContent = nothing;
|
||||
let lastTotal = null;
|
||||
|
||||
function renderPage(content, { total = null, error = null } = {}) {
|
||||
if (!error) {
|
||||
lastContent = content;
|
||||
lastTotal = total;
|
||||
}
|
||||
const displayContent = error ? lastContent : content;
|
||||
const displayTotal = error ? lastTotal : total;
|
||||
litRender(html`
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold">${t('entities.nodes')}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${tzBadge}
|
||||
${total !== null ? html`<span class="badge badge-lg">${t('common.total', { count: total })}</span>` : nothing}
|
||||
</div>
|
||||
${tzBadge}
|
||||
</div>
|
||||
${content}`, container);
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
${displayTotal !== null
|
||||
? html`<span class="badge badge-lg">${t('common.total', { count: displayTotal })}</span>`
|
||||
: nothing}
|
||||
<span id="auto-refresh-toggle"></span>
|
||||
${error ? warningBadge(error) : nothing}
|
||||
</div>
|
||||
${displayContent}`, container);
|
||||
}
|
||||
|
||||
// Render page header immediately (old content stays visible until data loads)
|
||||
@@ -195,7 +207,7 @@ ${content}`, container);
|
||||
${paginationBlock}`, { total });
|
||||
|
||||
} catch (e) {
|
||||
renderPage(errorAlert(e.message));
|
||||
renderPage(nothing, { error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user