Firefox ESR, Office aus eigenem Pfad, Warnung bei ungespeicherten Blaupausen

- Katalog: Mozilla Firefox ESR (DE)
- Office-Modus "Offline aus eigenem Pfad" (Netzlaufwerk/Ordner) mit
  Pruefung auf Office\Data und SourcePath in der XML-Vorschau
- Seitenleiste: Speichern-Button + Hinweis bei ungespeicherten Aenderungen;
  Rueckfrage vor Laden einer anderen Blaupause, Beenden und Schliessen

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-21 16:48:33 +02:00
co-authored by Claude Opus 5.5
parent 46a969ee90
commit 59da23d986
7 changed files with 56 additions and 11 deletions
+35 -8
View File
@@ -37,7 +37,7 @@ const DEFAULTS = () => ({
exclude: ['Groove', 'Lync', 'Bing'], extraProducts: [], pidKey: '', company: '',
updates: true, removeMsi: true, migrateArch: true, sharedComputer: false, disableVba: true, ooxmlDefault: true, showUi: false
},
officeMode: 'online',
officeMode: 'online', officeSource: '',
shortcuts: [],
system: { computerName: '', timeZone: '', showExtensions: true, disableConsumer: true },
security: { defender: true, firewall: true, smartScreen: true, uac: false, disableSmb1: true, disableAutorun: true, lsaProtection: false, telemetryMin: false, rdp: 'keep', localAdminName: '', localAdminPassword: '' },
@@ -61,7 +61,22 @@ function deepMerge(base, add) {
}
const getPath = p => p.split('.').reduce((o, k) => o?.[k], S);
function setPath(p, v) { const ks = p.split('.'); const last = ks.pop(); ks.reduce((o, k) => o[k] ??= {}, S)[last] = v; }
function persist() { try { localStorage.setItem('kdsState', JSON.stringify(S)); } catch { } renderSummary(); }
function persist() { try { localStorage.setItem('kdsState', JSON.stringify(S)); } catch { } renderSummary(); updateDirty(); }
// ---- Ungespeicherte Änderungen erkennen (Vergleich mit dem zuletzt gespeicherten/geladenen Stand)
const snap = o => JSON.stringify(o);
let savedSnap = null;
try { savedSnap = localStorage.getItem('kdsSaved'); } catch { }
if (savedSnap == null) savedSnap = snap(S);
const isDirty = () => snap(S) !== savedSnap;
function markSaved() { savedSnap = snap(S); try { localStorage.setItem('kdsSaved', savedSnap); } catch { } updateDirty(); }
function updateDirty() {
const d = isDirty();
$('#dirtyWarn')?.classList.toggle('hidden', !d);
$('#btnSideSave')?.classList.toggle('pulse', d);
}
const confirmDiscard = () => !isDirty() || confirm('Es gibt ungespeicherte Änderungen an der aktuellen Blaupause. Trotzdem fortfahren und Änderungen verwerfen?');
window.addEventListener('beforeunload', e => { if (isDirty()) { e.preventDefault(); e.returnValue = ''; } });
// generische Formularbindung: data-bind="pfad" und data-bind-list="pfad" (Checkbox-Gruppen)
function bindForms() {
@@ -87,12 +102,14 @@ function fillForms() {
$$('input', box).forEach(i => i.checked = v.includes(i.value));
});
$$('#steps input').forEach(i => i.checked = arr(S.steps).includes(i.value));
$('#officeSourceRow').classList.toggle('hidden', S.officeMode !== 'custom');
$('#profileName').textContent = 'Blaupause: ' + ([S.customer, S.name].filter(Boolean).join(' / ') || '(ungespeichert)');
$$('.pinonly').forEach(e => e.classList.toggle('hidden', !S.bitlocker.usePin));
}
function onChange(path) {
if (path.startsWith('office')) { if (path === 'office.product') { renderOfficeChannels(); renderOfficeExtras(); } updateOfficeXml(); }
if (path === 'bitlocker.usePin') fillForms();
if (path === 'officeMode' || path === 'officeSource') { fillForms(); updateOfficeXml(); }
if (path === 'bloatware.recommended') renderBloat();
}
function loadState(obj) { S = deepMerge(DEFAULTS(), obj); persist(); fillForms(); renderAll(); }
@@ -288,12 +305,13 @@ $('#officeExtras').onchange = () => { S.office.extraProducts = $$('#officeExtras
let xmlTimer;
function updateOfficeXml() {
clearTimeout(xmlTimer);
xmlTimer = setTimeout(() => guard(async () => { $('#officeXml').textContent = await api('POST', '/api/office/xml', S.office, true); }), 250);
xmlTimer = setTimeout(() => guard(async () => { $('#officeXml').textContent = await api('POST', '/api/office/xml', { ...S.office, sourcePath: S.officeMode === 'custom' ? S.officeSource : '' }, true); }), 250);
}
$('#btnXmlDownload').onclick = () => download('configuration.xml', $('#officeXml').textContent, 'application/xml');
$('#btnXmlCopy').onclick = () => navigator.clipboard.writeText($('#officeXml').textContent).then(() => toast('XML kopiert'));
$('#btnOffice').onclick = () => guard(async () => {
jobStarted((await api('POST', '/api/office/install', { office: S.office, mode: S.officeMode, name: S.name })).job);
if (S.officeMode === 'custom' && !S.officeSource) return toast('Pfad zur Office-Quelle angeben', true);
jobStarted((await api('POST', '/api/office/install', { office: S.office, mode: S.officeMode, sourcePath: S.officeSource, name: S.name })).job);
});
async function loadOfficeBuilds() {
const b = arr(await api('GET', '/api/office/configs').catch(() => []));
@@ -670,8 +688,9 @@ function renderQuickSelect() {
}
$('#bpFilter').oninput = renderBlueprints;
async function loadBlueprint(id) {
if (!confirmDiscard()) { renderQuickSelect(); return; }
const data = await api('GET', '/api/blueprints/' + encodeURIComponent(id));
loadState({ ...data, id });
loadState({ ...data, id }); markSaved();
renderBlueprints(); renderQuickSelect();
$('#bpCustomer').value = S.customer || ''; $('#profileSaveName').value = S.name || ''; $('#bpDescription').value = S.description || '';
toast(`Blaupause "${[S.customer, S.name].filter(Boolean).join(' / ')}" geladen`);
@@ -700,6 +719,7 @@ async function saveBlueprint(asNew) {
await api('DELETE', '/api/blueprints/' + encodeURIComponent(S.id));
Object.assign(S, { id, name, customer, description: $('#bpDescription').value.trim() }); persist();
await api('POST', '/api/blueprints/' + encodeURIComponent(id), JSON.stringify(S, null, 2));
markSaved();
toast(`Blaupause "${name}" gespeichert`); fillForms(); loadProfiles();
}
$('#btnProfileSave').onclick = () => guard(() => saveBlueprint(false));
@@ -709,7 +729,14 @@ $('#importFile').onchange = async e => {
const f = e.target.files[0]; e.target.value = '';
if (f) guard(async () => { const d = JSON.parse(await f.text()); d.id = ''; loadState(d); loadProfiles(); toast('Importiert – zum Übernehmen „Speichern“ klicken'); });
};
$('#btnReset').onclick = () => { if (confirm('Alle Einstellungen auf Standard zurücksetzen?')) loadState(DEFAULTS()); };
$('#btnReset').onclick = () => { if (confirm('Alle Einstellungen auf Standard zurücksetzen?')) { loadState(DEFAULTS()); markSaved(); } };
// Speichern-Button in der Seitenleiste
$('#btnSideSave').onclick = () => guard(async () => {
if (!S.name) { showTab('profiles'); $('#profileSaveName').focus(); return toast('Bitte Kunde und Namen für die neue Blaupause angeben', true); }
if (!blueprints.length) await loadProfiles();
$('#bpCustomer').value = S.customer || ''; $('#profileSaveName').value = S.name; $('#bpDescription').value = S.description || '';
await saveBlueprint(false);
});
function download(name, text, type) {
const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([text], { type })); a.download = name; a.click();
@@ -759,13 +786,13 @@ async function waitJob(id) {
}
}
$('#btnShutdown').onclick = () => { if (confirm('Agent beenden? Laufende Aufgaben werden abgebrochen.')) api('POST', '/api/shutdown', {}).finally(() => { document.body.innerHTML = '<main><h1>Agent beendet.</h1><p>Das Fenster kann geschlossen werden.</p></main>'; }); };
$('#btnShutdown').onclick = () => { if (confirmDiscard() && confirm('Agent beenden? Laufende Aufgaben werden abgebrochen.')) api('POST', '/api/shutdown', {}).finally(() => { document.body.innerHTML = '<main><h1>Agent beendet.</h1><p>Das Fenster kann geschlossen werden.</p></main>'; }); };
// ---------------------------------------------------------------- Start
function renderAll() { renderOffice(); renderSelectedApps(); renderPlanned(); renderWifi(); renderSummary(); }
(async () => {
if (!TOKEN) { document.body.innerHTML = '<main><h1>Kein Zugriffstoken</h1><p>Bitte die vollständige URL (mit ?token=…) aus dem Agent-Fenster öffnen.</p></main>'; return; }
bindForms(); renderAll(); fillForms(); scTypeUi();
bindForms(); renderAll(); fillForms(); scTypeUi(); updateDirty();
await guard(loadStatus);
await guard(loadCatalog);
await guard(async () => {