feat(directory): real mesh-gateway count on the Multi-Site modal; docs links

"Theta Gateways: N active gateways" was counting this app's own
unrelated WireGuard roaming-client/exit-node Resources
(metadata.subType === 'wireguard') -- a completely different subsystem
from the gateway-to-gateway mesh the modal is actually about, and it
never queried jump-host's mesh registry at all (so it couldn't show
the local self-entry either, since there was nothing mesh-related
being counted in the first place).

Added utils/jump_client.js (same pattern as utils/proxy_client.js:
reuses jump-host's existing self-service jmp_ API token system rather
than inventing a new credential) to query jump-host's real
GET /api/mesh/gateways. Reports a null count (not misleading 0) when
the integration isn't configured/reachable, surfaced distinctly in the
UI. Also added help links to the published multi-site/mesh docs on the
modal.

Includes docs links + count only -- this session also discovered that
utils/proxy_client.js's PROXY_INTERNAL_URL, and now JUMP_INTERNAL_URL,
were never actually wired into theta-suite's docker-compose.yml, so
both service-to-service integrations were unreachable in every real
deployment despite existing in code (fixed in theta-suite separately).
This commit is contained in:
2026-08-10 22:17:57 -04:00
parent b6a82d58d5
commit 611f1a3318
5 changed files with 179 additions and 6 deletions
+9 -3
View File
@@ -11,6 +11,7 @@ const { projectResources } = require('@simpleworkjs/directory-schema');
const SUPER_ADMIN_GROUP = permission.SUPER_ADMIN_GROUP;
const groups = require('../utils/groups');
const meshReplicate = require('../utils/site_replicate');
const jumpClient = require('../utils/jump_client');
// Make `childCn` a member of `parentCn`, i.e. everyone in the child is
// transitively in the parent. Idempotent and non-fatal: "already a member" is
@@ -954,8 +955,6 @@ async function probeMasterHealth(cfg) {
router.get('/site-status', async (req, res, next) => {
try {
const sites = await Resource.list({ where: { kind: 'site' } });
const allResources = await Resource.list();
const gateResources = allResources.filter(r => r.metadata && r.metadata.subType === 'wireguard');
const cfg = siteConfig.get();
const wanConnected = await probeMasterHealth(cfg);
@@ -970,6 +969,12 @@ router.get('/site-status', async (req, res, next) => {
// fully joined but silently stuck on the one-time snapshot, which was
// otherwise invisible anywhere in the UI.
const registeredSpokesCount = cfg.isMaster ? await SiteSpoke.list().then(l => l.length).catch(() => 0) : 0;
// Real gateway-to-gateway mesh peer count from jump-host's own registry
// (utils/jump_client.js), not this app's unrelated WireGuard
// roaming-client Resources. count is null (not 0) when the query
// couldn't run at all -- the UI distinguishes "0 gateways" from "can't
// tell" instead of showing a misleading zero.
const gateways = await jumpClient.getGatewayCount();
res.json({
status: 'ok',
config: {
@@ -984,7 +989,8 @@ router.get('/site-status', async (req, res, next) => {
},
sitesCount: sites.length,
sites: sites.map(s => ({ id: s.id, name: s.name, slug: s.slug })),
gatewaysCount: gateResources.length
gatewaysCount: gateways.count,
gatewaysNote: gateways.note
});
} catch (err) { next(err); }
});