dashboard: honor deploy-time API base for live metrics stream (thanks @porkmagus) #96
No files matched your search
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterEach, vi } from 'vitest';
|
||||
|
||||
const originalHead = document.head.innerHTML;
|
||||
const seenUrls: string[] = [];
|
||||
|
||||
class FakeEventSource {
|
||||
url: string;
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
seenUrls.push(url);
|
||||
}
|
||||
addEventListener() {}
|
||||
close() {}
|
||||
}
|
||||
|
||||
async function subscribeWithMeta(meta: string) {
|
||||
document.head.innerHTML = meta;
|
||||
vi.resetModules();
|
||||
vi.stubGlobal('EventSource', FakeEventSource);
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async () => new Response('tok123', { status: 200 })),
|
||||
);
|
||||
const { useLiveMetricsStore } = await import('./liveMetricsStore');
|
||||
useLiveMetricsStore.getState().subscribe(new Set(['cpu' as never]));
|
||||
await new Promise((r) => setTimeout(r, 300));
|
||||
useLiveMetricsStore.getState().unsubscribe();
|
||||
return seenUrls.at(-1) ?? '';
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.head.innerHTML = originalHead;
|
||||
seenUrls.length = 0;
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('live metrics stream URL', () => {
|
||||
it('honors the deploy-time api-base-url instead of the Admin origin', async () => {
|
||||
const url = await subscribeWithMeta('<meta name="api-base-url" content="https://mail.example.org" />');
|
||||
expect(url.startsWith('https://mail.example.org/api/live/metrics?')).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to the Admin origin when no api-base-url is injected', async () => {
|
||||
const url = await subscribeWithMeta('');
|
||||
expect(url.startsWith(`${window.location.origin}/api/live/metrics?`)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -2,11 +2,13 @@
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*
|
||||
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import type { MetricId, Metric } from '../types/metrics';
|
||||
import { apiFetch } from '@/services/api';
|
||||
import { apiFetch, getApiBaseUrl } from '@/services/api';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
type LiveStatus = 'idle' | 'connecting' | 'open' | 'error' | 'closed';
|
||||
@@ -107,9 +109,13 @@ async function openStream(ids: Set<MetricId>) {
|
||||
}
|
||||
|
||||
function getOrigin(): string {
|
||||
const envUrl = import.meta.env.VITE_API_BASE_URL as string | undefined;
|
||||
if (envUrl && envUrl.length > 0) return envUrl.replace(/\/+$/, '');
|
||||
return window.location.origin;
|
||||
// Honor the deploy-time API base (VITE_API_BASE_URL or the api-base-url
|
||||
// meta tag) exactly like every other /api/live caller. Using
|
||||
// window.location.origin here breaks Dashboard live metrics in any split
|
||||
// deployment where the Admin origin is not the server (e.g. Admin in Docker
|
||||
// pointed at the server via API_BASE_URL): the EventSource hits the Admin
|
||||
// nginx instead, which answers with the SPA shell.
|
||||
return getApiBaseUrl();
|
||||
}
|
||||
|
||||
let visibilityTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
Reference in new issue
Block a user