Files
Void-Homelab/public/components/service_tile.js
2026-06-08 00:57:48 +10:00

34 lines
1.6 KiB
JavaScript

import { el, safeHref } from '../dom.js';
import { isRemoteHost, pickServiceUrls } from '../views/service_url.js';
// `remote` is injected by the caller (defaults to detecting from the current host) so
// the component stays unit-testable without stubbing window.location.
export function serviceTile(s, remote = isRemoteHost(location.hostname)) {
const { primary, alt, lanOnly } = pickServiceUrls(s, remote);
const img = el('img', { class: 'tile-icon', loading: 'lazy', src: `/api/icons/${s.icon}.png`, alt: s.name });
img.onerror = () => img.replaceWith(el('div', { class: 'tile-icon-fb' }, (s.name[0] || '?').toUpperCase()));
// Root is a div so we can host two sibling <a>s (a stretched primary link + a small
// alt) without nesting anchors (invalid HTML).
const tile = el('div', { class: `tile status-${s.status}${lanOnly ? ' lan-only' : ''}` },
img,
el('div', { class: 'tile-main' },
el('div', { class: 'tile-nm' }, el('span', { class: 'dot' }), s.name),
el('div', { class: 'tile-host' }, s.host || '')),
el('span', { class: 'tile-go' }, 'open ↗'),
// Stretched primary link covers the whole tile (see .tile-link in style.css).
el('a', { class: 'tile-link', href: safeHref(primary), target: '_blank', rel: 'noreferrer',
'aria-label': `Open ${s.name}` }));
if (lanOnly) tile.appendChild(el('span', { class: 'tile-lan', title: 'Not reachable remotely' }, 'LAN-only'));
if (alt) {
tile.appendChild(el('a', {
class: 'tile-alt', href: safeHref(alt), target: '_blank', rel: 'noreferrer',
title: remote ? 'Open via LAN' : 'Open via domain',
}, '⇄'));
}
return tile;
}