Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Apps with a container check no longer show an unhealthy badge while Docker
container health checks are off.

- An app's health check can be turned off while Docker container health checks
are off.

- A widget's settings now open from that widget's own saved config.

- A tile with only a fixed label set now shows that badge.
Expand Down
22 changes: 13 additions & 9 deletions api/src/routes/health.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ const demoData = require('../demo-data');
const log = require('../log');
const SOCKET_PROXY_URL_DEFAULT = process.env.SOCKET_PROXY_URL || '';

async function fetchContainerHealth() {
const cfg = loadConfig();
const socketUrl = cfg.settings?.server?.socketProxyUrl || SOCKET_PROXY_URL_DEFAULT;
if (!socketUrl) return Object.create(null);
function socketProxyUrl(cfg) {
return cfg.settings?.server?.socketProxyUrl || SOCKET_PROXY_URL_DEFAULT;
}

async function fetchContainerHealth(socketUrl) {
try {
const r = await fetchUnchecked(`${socketUrl}/containers/json?all=true`);
if (!Array.isArray(r.data)) return Object.create(null);
Expand Down Expand Up @@ -133,15 +134,18 @@ on('GET', '/api/health', async (req, res) => {
const cfg = loadConfig();
return json(res, 200, demoData.demoHealth(cfg.items));
}
const containers = await fetchContainerHealth();
const cfg = loadConfig(),
result = Object.create(null);
const cfg = loadConfig();
const socketUrl = socketProxyUrl(cfg);
/* Without a proxy address every container lookup misses, which would report
every container-backed app as unhealthy. */
const containers = socketUrl ? await fetchContainerHealth(socketUrl) : Object.create(null);
const result = Object.create(null);
await Promise.allSettled(
cfg.items
.filter(i => i.type === 'app' && (i.container || i.ping || i.monitoring?.healthcheck?.enabled))
.map(async item => {
const mon = item.monitoring?.healthcheck || {};
const cName = mon.container || item.container || '';
const cName = socketUrl ? mon.container || item.container || '' : '';
const ping = mon.pingUrl || item.ping || '';
/* Build the entry up rather than replace it. An item can have both a
container and a ping, and the tile needs the detail from each. */
Expand Down Expand Up @@ -170,7 +174,7 @@ on('GET', '/api/health', async (req, res) => {
detail.pingStatus = r.status;
detail.pingError = r.error;
}
result[item.id] = { unhealthy, ...detail };
if (cName || ping) result[item.id] = { unhealthy, ...detail };
}),
);
json(res, 200, result);
Expand Down
21 changes: 19 additions & 2 deletions api/test/health-route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require('node:path');

const { tmpDir } = require('../test-support/tmp');
process.env.CONFIG_PATH = path.join(tmpDir('health'), 'apps.json');
delete process.env.SOCKET_PROXY_URL;

const { test, before, after } = require('node:test');
const assert = require('node:assert/strict');
Expand Down Expand Up @@ -62,10 +63,10 @@ function health() {
});
}

function configure(item) {
function configure(item, socketProxyUrl = socketBase) {
saveConfig({
items: [Object.assign({ id: 'a1', type: 'app', name: 'App' }, item)],
settings: { server: { socketProxyUrl: socketBase } },
settings: { server: { socketProxyUrl } },
});
}

Expand Down Expand Up @@ -157,3 +158,19 @@ test('a container actually named __proto__ is matched, not discarded', async ()
assert.equal(r.unhealthy, true);
assert.equal(r.state, 'exited');
});

test('a container check is skipped while no socket proxy is configured', async () => {
containers = [];
configure({ container: 'myapp', monitoring: { healthcheck: { enabled: true, container: 'myapp' } } }, '');
assert.equal((await health()).a1, undefined);
});

test('a ping check still runs while no socket proxy is configured', async () => {
containers = [];
targetStatus = 200;
configure({ container: 'myapp', ping: `${targetBase}/` }, '');
const r = (await health()).a1;
assert.equal(r.unhealthy, false);
assert.equal(r.pingStatus, 200);
assert.equal(r.state, undefined);
});
2 changes: 1 addition & 1 deletion ui/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,6 @@ <h1 class="sec-title" id="ev-title">General</h1>
</div></div>
</div>

<script type="module" src="/js/admin.js?v=22c45dff"></script>
<script type="module" src="/js/admin.js?v=79e9c341"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion ui/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock voll ({max}/{max}). Entferne zuerst eine App.",
"badge": "Badge",
"healthCheck": "Zustandsprüfung",
"healthGlobalOff": "Aktiviere Docker-Container-Zustandsprüfungen unter Allgemein und richte sie ein, um dies zu verwenden.",
"healthGlobalOff": "Container-Prüfungen brauchen Docker-Container-Zustandsprüfungen unter Allgemein. URL-Prüfungen funktionieren auch ohne.",
"type": "Typ",
"container": "Container",
"ping": "Ping",
Expand Down
2 changes: 1 addition & 1 deletion ui/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock full ({max}/{max}). Remove an app first.",
"badge": "Badge",
"healthCheck": "Health Check",
"healthGlobalOff": "Turn on Docker Container Health Checks in General, then configure it, to use this.",
"healthGlobalOff": "Container checks need Docker Container Health Checks turned on in General. URL checks work without it.",
"type": "Type",
"container": "Container",
"ping": "Ping",
Expand Down
2 changes: 1 addition & 1 deletion ui/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock lleno ({max}/{max}). Elimina una aplicación primero.",
"badge": "Insignia",
"healthCheck": "Comprobación de estado",
"healthGlobalOff": "Activa las Comprobaciones de estado de contenedores Docker en General y configúralas para usar esto.",
"healthGlobalOff": "Las comprobaciones de contenedor requieren las Comprobaciones de estado de contenedores Docker en General. Las comprobaciones de URL funcionan sin ellas.",
"type": "Tipo",
"container": "Contenedor",
"ping": "Ping",
Expand Down
2 changes: 1 addition & 1 deletion ui/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock پر است ({max}/{max}). ابتدا یک برنامه را حذف کنید.",
"badge": "نشان",
"healthCheck": "بررسی سلامت",
"healthGlobalOff": "برای استفاده از این، بررسی سلامت کانتینرهای داکر را در عمومی روشن و پیکربندی کنید.",
"healthGlobalOff": "بررسی کانتینر به روشن‌بودن بررسی سلامت کانتینرهای داکر در عمومی نیاز دارد. بررسی نشانی اینترنتی بدون آن هم کار می‌کند.",
"type": "نوع",
"container": "کانتینر",
"ping": "پینگ",
Expand Down
2 changes: 1 addition & 1 deletion ui/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock plein ({max}/{max}). Retirez d'abord une application.",
"badge": "Badge",
"healthCheck": "Vérification de l'état",
"healthGlobalOff": "Activez les Vérifications de l'état des conteneurs Docker dans Général, puis configurez-les, pour utiliser ceci.",
"healthGlobalOff": "Les vérifications de conteneur exigent les Vérifications de l'état des conteneurs Docker dans Général. Les vérifications d'URL fonctionnent sans.",
"type": "Type",
"container": "Conteneur",
"ping": "Ping",
Expand Down
2 changes: 1 addition & 1 deletion ui/i18n/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"dockFull": "Dock 已满 ({max}/{max})。请先移除一个应用。",
"badge": "徽章",
"healthCheck": "健康检查",
"healthGlobalOff": "请先在通用中开启 Docker 容器健康检查并完成配置后再使用。",
"healthGlobalOff": "容器检查需要在通用中开启 Docker 容器健康检查。网址检查无需开启即可使用。",
"type": "类型",
"container": "容器",
"ping": "Ping",
Expand Down
5 changes: 2 additions & 3 deletions ui/js/admin-app-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function buildAppForm(body, item) {
<p class="grp-hdr">${t('app.badge')}</p>
<div class="grp">
<div class="row"><span class="rl">${t('app.healthCheck')}</span>${tog('hc-en', hc.enabled)}</div>
<div id="hc-sub" ${hc.enabled && globalHealthOn ? '' : 'hidden'}>
<div id="hc-sub" ${hc.enabled ? '' : 'hidden'}>
<div class="row"><span class="rl">${t('app.type')}</span><div class="segr">
<label class="segr-opt"><input type="radio" name="hc-type" id="hc-type-con" ${isPing ? '' : 'checked'}><span class="segr-dot"></span><span>${t('app.container')}</span></label>
<label class="segr-opt"><input type="radio" name="hc-type" id="hc-type-ping" ${isPing ? 'checked' : ''}><span class="segr-dot"></span><span>${t('app.ping')}</span></label>
Expand Down Expand Up @@ -238,13 +238,12 @@ export function buildAppForm(body, item) {

setTogDisabled(el('f-dock'), dockBlocked, 'dock-full-tip');
const hcEn = el('hc-en');
setTogDisabled(hcEn, !globalHealthOn, 'hc-off-tip');
const showHide = (id, on) => {
const node = el(id);
if (node) node.hidden = !on;
};
hcEn?.addEventListener('change', e => {
if (globalHealthOn) showHide('hc-sub', tgt(e).checked);
showHide('hc-sub', tgt(e).checked);
});
document.querySelectorAll('input[name="hc-type"]').forEach(r =>
r.addEventListener('change', () => {
Expand Down
2 changes: 1 addition & 1 deletion ui/js/admin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildAppForm, buildFolderForm, serializeKvRows } from '/js/admin-app-form.js?v=f8fda76d';
import { buildAppForm, buildFolderForm, serializeKvRows } from '/js/admin-app-form.js?v=848f1e7d';
import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=76be763e';
import { applyDrop, canJoinFolder } from '/js/admin-drag-logic.js?v=8fcf583a';
import { reorderItems, resolveAdminSection } from '/js/admin-logic.js?v=f3f87abf';
Expand Down