Files
felhom.eu/website/assets/sizing.js
T
admin d5ef93b13b
gates / gates (push) Successful in 6m42s
Website: the machine estimator (/gepigeny, /en/sizing), live but unlisted; SIZING.md §5 in three verdicts; R-997 filed
A household ticks apps and sees, for an 8, 16 and 32 GB box: fits well / fits, but
tight / does not fit, the totals, and the apps that burst on CPU. One rule in
assets/sizing.js, every number from assets/sizing-data.json (built by
scripts/build_sizing_web.py from the 2026-10-13 N100 measurement; test: 7 cases,
red-proofed). Unlisted: noindex, robots Disallow, not in the sitemap, linked from
nowhere — site_gates.py gate 25 enforces it (UNLISTED twin pairs keep every twin
check). SIZING.md §5 corrected: the 8 GB line contradicted its own x1.5 rule; guest
sizes 4.0/11.4/27.4 GB with the measured 612 MiB MemTotal shortfall. R-993 gains the
fact that the installer accepts a host below any minimum.
2026-10-13 14:31:44 +02:00

184 lines
8.5 KiB
JavaScript

/* sizing.js — the machine estimator (/gepigeny, /en/sizing).
*
* THE RULE lives in estimate() and guestMib() below, and nowhere else on the site. Every number it uses
* comes from /assets/sizing-data.json (built by scripts/build_sizing_web.py from the 2026-10-13 N100
* measurement); this file holds none of its own — scripts/test_build_sizing_web.py checks that.
*
* typical = platform + sum of the selected apps' typical figures
* peak = platform + sum of their peaks (all at peak at once: pessimistic on purpose)
* per box: guest memory by the installer's rule, then
* well peak * headroom <= guest
* tight peak <= guest < peak * headroom
* no guest < peak
*
* The page text is not here: each page carries its own strings (#sizer-strings), so this file is the
* same for both languages.
*/
(function (root) {
'use strict';
function guestMib(data, gb) {
var r = data.guest_rule;
var total = gb * 1024 - r.memtotal_shortfall_mib;
var mem = total - r.reserve_mib;
if (mem > total - r.max_gap_mib) { mem = total - r.max_gap_mib; }
if (mem < r.min_mib) { mem = r.min_mib; }
if (mem > total - r.ceiling_gap_mib) { mem = total - r.ceiling_gap_mib; }
return mem;
}
function estimate(data, slugs) {
var bySlug = {};
data.apps.forEach(function (a) { bySlug[a.slug] = a; });
var typical = data.platform_mb, peak = data.platform_mb, busy = [], unmeasured = [];
slugs.forEach(function (s) {
var a = bySlug[s];
if (!a || a.builtin) { return; }
typical += a.typical_mb;
peak += a.peak_mb;
if (a.cpu_class === 'medium' || a.cpu_class === 'heavy-in-bursts') { busy.push(a); }
if (!a.measured) { unmeasured.push(a); }
});
var boxes = data.boxes_gb.map(function (gb) {
var guest = guestMib(data, gb);
var verdict = peak * data.headroom <= guest ? 'well' : (peak <= guest ? 'tight' : 'no');
return { gb: gb, guest_mb: guest, verdict: verdict };
});
var heavy = busy.filter(function (a) { return a.cpu_class === 'heavy-in-bursts'; });
return { typical_mb: typical, peak_mb: peak, boxes: boxes, busy: busy, heavy: heavy, unmeasured: unmeasured };
}
root.FelhomSizing = { estimate: estimate, guestMib: guestMib };
/* ---------------------------------------------------------------- the page (drawing only) */
if (typeof document === 'undefined') { return; }
function el(tag, cls, txt) {
var e = document.createElement(tag);
if (cls) { e.className = cls; }
if (txt !== undefined) { e.textContent = txt; }
return e;
}
function icon(name) {
var ns = 'http://www.w3.org/2000/svg';
var s = document.createElementNS(ns, 'svg');
s.setAttribute('class', 'ico');
s.setAttribute('aria-hidden', 'true');
var u = document.createElementNS(ns, 'use');
u.setAttribute('href', '/assets/icons.svg?v=1#' + name);
s.appendChild(u);
return s;
}
function start() {
var host = document.getElementById('sizer');
var strEl = document.getElementById('sizer-strings');
if (!host || !strEl) { return; }
var T = JSON.parse(strEl.textContent);
var lang = document.documentElement.lang === 'en' ? 'en' : 'hu';
var gbText = function (mb) {
var v = (mb / 1024).toFixed(1);
return (lang === 'hu' ? v.replace('.', ',') : v) + ' GB';
};
var appsBox = document.getElementById('sizer-apps');
var result = document.getElementById('sizer-result');
fetch(host.getAttribute('data-src')).then(function (r) { return r.json(); }).then(function (data) {
data.categories.forEach(function (c) {
var fs = el('fieldset', 'sizer-group');
fs.appendChild(el('legend', '', lang === 'en' ? c.name_en : c.name_hu));
data.apps.filter(function (a) { return a.category === c.id; }).forEach(function (a) {
var lab = el('label', 'sizer-app');
var cb = el('input');
cb.type = 'checkbox';
cb.value = a.slug;
if (a.builtin) { cb.checked = true; cb.disabled = true; }
lab.appendChild(cb);
var name = el('span', 'sizer-app-name', lang === 'en' ? a.name_en : a.name_hu);
lab.appendChild(name);
var tag = a.builtin ? T.builtin : (lang === 'en' ? a.tagline_en : a.tagline_hu);
if (tag) { lab.appendChild(el('span', 'sizer-app-tag', tag)); }
fs.appendChild(lab);
});
appsBox.appendChild(fs);
});
function selected() {
return Array.prototype.map.call(appsBox.querySelectorAll('input[type=checkbox]:checked:not(:disabled)'),
function (c) { return c.value; });
}
function draw() {
var r = estimate(data, selected());
result.textContent = '';
var totals = el('p', 'sizer-totals');
totals.appendChild(el('span', '', T.typical + ': '));
totals.appendChild(el('strong', 'sizer-num', gbText(r.typical_mb)));
totals.appendChild(el('span', '', ' · ' + T.peak + ': '));
totals.appendChild(el('strong', 'sizer-num', gbText(r.peak_mb)));
result.appendChild(totals);
result.appendChild(el('p', 'sizer-fine', T.platform_note));
var ul = el('ul', 'sizer-boxes');
r.boxes.forEach(function (b) {
var li = el('li', 'sizer-box sizer-' + b.verdict);
var head = el('div', 'sizer-box-head');
head.appendChild(icon(b.verdict === 'well' ? 'i-check' : (b.verdict === 'tight' ? 'i-triangle-alert' : 'i-x')));
head.appendChild(el('strong', 'sizer-box-size', b.gb + ' GB'));
head.appendChild(el('span', 'sizer-verdict', T.verdict[b.verdict]));
li.appendChild(head);
li.appendChild(el('p', 'sizer-fine', T.guest.replace('{x}', gbText(b.guest_mb))));
if (b.verdict === 'tight') { li.appendChild(el('p', 'sizer-why', T.tight_why)); }
ul.appendChild(li);
});
result.appendChild(ul);
if (r.busy.length) {
result.appendChild(el('h3', 'sizer-sub', T.cpu_head));
var cl = el('ul', 'sizer-cpu');
r.busy.forEach(function (a) {
var li = el('li');
li.appendChild(el('strong', '', (lang === 'en' ? a.name_en : a.name_hu) + ': '));
li.appendChild(el('span', '', lang === 'en' ? a.cpu_note_en : a.cpu_note_hu));
cl.appendChild(li);
});
if (r.heavy.length >= 2) {
cl.appendChild(el('li', 'sizer-pair', T.heavy_pair.replace('{names}', r.heavy.map(function (a) {
return lang === 'en' ? a.name_en : a.name_hu;
}).join(T.and))));
}
result.appendChild(cl);
}
if (r.unmeasured.length) {
result.appendChild(el('p', 'sizer-why', T.unmeasured + ': ' + r.unmeasured.map(function (a) {
return lang === 'en' ? a.name_en : a.name_hu;
}).join(', ')));
}
}
appsBox.addEventListener('change', draw);
Array.prototype.forEach.call(document.querySelectorAll('[data-preset]'), function (btn) {
btn.addEventListener('click', function () {
var want = (btn.getAttribute('data-preset') || '').split(',').filter(Boolean);
Array.prototype.forEach.call(appsBox.querySelectorAll('input[type=checkbox]:not(:disabled)'), function (c) {
c.checked = want.indexOf(c.value) !== -1;
});
draw();
});
});
host.hidden = false;
draw();
}).catch(function () {
result.textContent = T.load_error;
host.hidden = false;
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
}
})(this);