All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m34s
Invulmenu:
- Vaste stappen (Basis, Verbinden, Instellingen, Toegang, Controleren) in
plaats van één lange lijst. Eén stap per groep werkt niet: de mediane app
heeft één groep, arr-stack eenentwintig. Lege stappen worden overgeslagen.
- Volgende controleert de verplichte velden van die stap; die melding kwam
eerder pas bij het installeren.
- Geavanceerde opties achter één schakelaar die op elke stap zichtbaar is,
plus 'Alles op één pagina' voor het oude gedrag. Beide onthouden.
Geheimen naar .env:
- compose_transform.geheimen_naar_env() vervangt geheime waarden door
${NAAM} en schrijft ze naar .env met rechten 0600. Werkt ook midden in
een database-URL. Poorten en paden blijven leesbaar in compose.
- .serverup.json bevat geen geheimen meer en krijgt ook 0600; daar stonden
ze wereldleesbaar in. Het herconfiguratieformulier leest ze terug uit
.env via een bewaarde veld-naar-sleutel-koppeling.
- docs/beveiliging.md legt uit wat dit niet oplost: docker inspect toont de
waarde nog steeds.
Bewerken:
- Laatste stap toont het gerenderde resultaat met jouw waarden en is
bewerkbaar; compose_override/env_override gaan mee bij het installeren.
Ongeldige YAML wordt geweigerd voordat er een map bestaat.
- De bestaande editor heeft tabbladen (compose/.env) en biedt herstarten na
opslaan. De .env-PUT weigert een regel zonder '=' en logt in audit.
Inloggegevens:
- /api/stacks/<n>/credentials plus paneel op de stackkaart: adres,
gebruikersnaam, wachtwoord achter een oog-knop, kopieerknop. Alleen voor
beheerders, elk bekijken komt in het auditlog.
- Nieuw 'credential'-kenmerk in template.json met naamherkenning als
terugval, zodat de bestaande 96 sjablonen meteen werken.
Herstel van een regressie uit v0.7.60: acht inlogwachtwoorden hadden hun
genereerknop verloren toen die aan 'generate' werd gehangen. Terug, behalve
waar een verzonnen waarde fout is (WireGuard-sleutel, externe tokens).
De menulogica wordt getest door de echte component uit index.html in Node uit
te voeren; slaat zichzelf over waar node ontbreekt. 2295 tests groen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
96 lines
5.7 KiB
JavaScript
96 lines
5.7 KiB
JavaScript
// Draait de echte Alpine-component uit index.html en toetst de logica van
|
|
// het invulmenu. Wordt aangeroepen door tests/test_ui_logica.py; los draaien
|
|
// kan met: node tests/js/menu.test.js
|
|
const fs = require('fs');
|
|
const html = fs.readFileSync(__dirname + '/../../server-up/templates/index.html', 'utf8');
|
|
// Alleen de component-functie uitsnijden en uitvoeren.
|
|
const start = html.indexOf('function app() {');
|
|
const eind = html.indexOf('</script>', start);
|
|
const bron = html.slice(start, eind);
|
|
global.localStorage = { getItem: () => null, setItem: () => {} };
|
|
global.location = { hash: '', search: '' };
|
|
global.window = { matchMedia: () => ({matches:false, addEventListener(){}}),
|
|
addEventListener(){}, innerWidth: 1200 };
|
|
global.document = { documentElement: {classList:{add(){},remove(){}}},
|
|
addEventListener(){}, querySelector: () => null };
|
|
global.navigator = { clipboard: { writeText: async () => {} }, language: 'nl' };
|
|
global.crypto = require('crypto').webcrypto;
|
|
eval(bron);
|
|
const c = app();
|
|
c.t = (k, v) => k; // vertaling niet nodig voor de logica
|
|
c.bpFieldVisible = () => true;
|
|
c.rpc = async () => ({ok: true, preview_files: {}, env_preview: ''});
|
|
c.toast = () => {};
|
|
|
|
function veld(naam, stap, extra = {}) {
|
|
return Object.assign({name: naam, title: naam, step: stap, type: 'str',
|
|
group: 'Algemeen', required: false, advanced: false}, extra);
|
|
}
|
|
|
|
let fouten = 0;
|
|
const check = (naam, waar) => { if (!waar) { fouten++; console.log(' ✖ ' + naam); }
|
|
else console.log(' ✓ ' + naam); };
|
|
|
|
// ── Eenvoudige app: alleen basisvelden ──────────────────────────────────────
|
|
c.install = {format:'boilerplate', loading:false, fields:[
|
|
veld('service_name','basis'), veld('port','basis'), veld('data_dir','basis'),
|
|
], values:{}, step:0};
|
|
c.onePage = false; c.showAdvanced = false;
|
|
let s = c.wizardSteps().map(x => x.id);
|
|
check('eenvoudige app: basis, verbinden, controleren',
|
|
JSON.stringify(s) === '["basis","verbinden","controleren"]');
|
|
|
|
// ── App met geheimen ────────────────────────────────────────────────────────
|
|
c.install.fields.push(veld('db_password','geheimen',{required:true, secret:true}));
|
|
s = c.wizardSteps().map(x => x.id);
|
|
check('geheimenstap verschijnt', s.includes('geheimen'));
|
|
|
|
// ── Lege stap wordt overgeslagen ────────────────────────────────────────────
|
|
check('geen lege instellingenstap', !s.includes('instellingen'));
|
|
|
|
// ── Geavanceerde velden ─────────────────────────────────────────────────────
|
|
c.install.fields.push(veld('retention','instellingen',{advanced:true}));
|
|
check('geavanceerd veld verborgen', c.stepFields('instellingen').length === 0);
|
|
check('stap zonder zichtbare velden verdwijnt',
|
|
!c.wizardSteps().map(x=>x.id).includes('instellingen'));
|
|
c.showAdvanced = true;
|
|
check('geavanceerd veld zichtbaar na aanzetten', c.stepFields('instellingen').length === 1);
|
|
check('stap komt terug', c.wizardSteps().map(x=>x.id).includes('instellingen'));
|
|
check('teller klopt', c.advancedCount() === 1);
|
|
|
|
// ── Verplicht veld blokkeert Volgende ───────────────────────────────────────
|
|
c.install.step = c.wizardSteps().findIndex(x => x.id === 'geheimen');
|
|
check('leeg verplicht veld wordt gemeld', c.stepMissing().length === 1);
|
|
c.install.values.db_password = 'ietsGeheims';
|
|
check('ingevuld: niets meer te melden', c.stepMissing().length === 0);
|
|
|
|
// ── Groepsschakelaar uit: veld telt niet mee ────────────────────────────────
|
|
c.install.fields.push(veld('opt_pass','geheimen',
|
|
{required:true, secret:true, group:'Extra', group_toggle:'gebruik_extra'}));
|
|
c.install.values.gebruik_extra = false;
|
|
check('uitgeschakelde groep blokkeert niet', c.stepMissing().length === 0);
|
|
c.install.values.gebruik_extra = true;
|
|
check('ingeschakelde groep blokkeert wel', c.stepMissing().length === 1);
|
|
|
|
// ── Navigatie ───────────────────────────────────────────────────────────────
|
|
c.install.values.opt_pass = 'x';
|
|
c.install.step = 0;
|
|
check('niet op de laatste stap', !c.opLaatsteStap());
|
|
c.install.step = c.wizardSteps().length - 1;
|
|
check('wel op de laatste stap', c.opLaatsteStap());
|
|
check('laatste stap is controleren',
|
|
c.wizardSteps()[c.install.step].id === 'controleren');
|
|
|
|
// ── Alles op één pagina ─────────────────────────────────────────────────────
|
|
c.onePage = true;
|
|
check('wizard uit bij één pagina', !c.wizardActief());
|
|
|
|
// ── Groepskoppen ────────────────────────────────────────────────────────────
|
|
c.onePage = false;
|
|
c.install.fields = [veld('a','basis'), veld('b','basis')];
|
|
check('één groep: geen kop', c.stepGroups('basis')[0].showHeader === false);
|
|
c.install.fields.push(veld('c','basis',{group:'Tweede'}));
|
|
check('twee groepen: wel koppen', c.stepGroups('basis').every(g => g.showHeader));
|
|
|
|
console.log(fouten === 0 ? '\nAlle menucontroles geslaagd' : `\n${fouten} FOUTEN`);
|
|
process.exit(fouten ? 1 : 0);
|