feat (ui): Light/Dark Theme added 02

This commit is contained in:
kodi
2026-03-04 07:29:43 +01:00
parent 1d5bdd5089
commit ebefd2d80c
4 changed files with 227 additions and 19 deletions
+105 -5
View File
@@ -1,4 +1,7 @@
let cmEditor = null;
let filesDirty = false;
let filesSuppressDirtyEvent = false;
let filesTextareaBound = false;
function filesCurrentTheme() {
const t = document.documentElement.getAttribute('data-theme');
@@ -34,6 +37,72 @@ const FILES_ROOT = 'systemd'; // API-root binnen WORKLOADS_DIR
let filesCurrentUiPath = ''; // zonder "systemd/"
let filesCurrentApiPath = ''; // met "systemd/"
function filesModeLabel(uiPath) {
const mode = cmModeForPath(uiPath);
if (mode === 'yaml') return 'YAML';
if (mode === 'application/json') return 'JSON';
if (mode === 'javascript') return 'JavaScript';
return 'Text';
}
function filesCursorLabel() {
if (cmEditor) {
const c = cmEditor.getCursor();
return `Ln ${c.line + 1}, Kol ${c.ch + 1}`;
}
return '';
}
function filesUpdateEditorStatus() {
const el = document.getElementById('filesEditorStatus');
if (!el) return;
if (!filesCurrentUiPath) {
el.textContent = 'Geen bestand geselecteerd';
return;
}
const dirtyTxt = filesDirty ? 'Niet opgeslagen' : 'Opgeslagen';
const parts = [
dirtyTxt,
filesModeLabel(filesCurrentUiPath),
filesCurrentUiPath,
];
const cursor = filesCursorLabel();
if (cursor) parts.push(cursor);
el.textContent = parts.join(' | ');
}
function filesUpdateTreeSelection() {
const treeEl = document.getElementById('filesTree');
if (!treeEl) return;
treeEl.querySelectorAll('.file-entry').forEach(row => {
row.classList.remove('active', 'dirty');
const state = row.querySelector('.file-entry-state');
if (state) state.textContent = '';
});
if (!filesCurrentUiPath) return;
const key = encodeURIComponent(filesCurrentUiPath);
const row = treeEl.querySelector(`.file-entry[data-file="${CSS.escape(key)}"]`);
if (!row) return;
row.classList.add('active');
if (filesDirty) {
row.classList.add('dirty');
const state = row.querySelector('.file-entry-state');
if (state) state.textContent = '●';
}
}
function filesSetDirty(v) {
filesDirty = !!v && !!filesCurrentUiPath;
filesUpdateEditorStatus();
filesUpdateTreeSelection();
}
function cmModeForPath(uiPath) {
const p = (uiPath || '').toLowerCase();
if (p.endsWith('.yaml') || p.endsWith('.yml') || p.endsWith('.kube') || p.endsWith('.container')) return 'yaml';
@@ -58,6 +127,7 @@ function filesSetCurrent(uiPath) {
filesCurrentUiPath = (uiPath || '').trim().replace(/^\/+/, '');
filesCurrentApiPath = filesToApiPath(filesCurrentUiPath);
document.getElementById('filesCurrent').textContent = filesCurrentUiPath || '-';
filesSetDirty(false);
}
async function filesRefresh() {
@@ -72,6 +142,17 @@ async function filesRefresh() {
theme: filesCodeMirrorTheme()
});
cmEditor.setSize('100%', 360);
cmEditor.on('change', () => {
if (filesSuppressDirtyEvent || !filesCurrentUiPath) return;
filesSetDirty(true);
});
cmEditor.on('cursorActivity', filesUpdateEditorStatus);
} else if (taFiles && !filesTextareaBound) {
filesTextareaBound = true;
taFiles.addEventListener('input', () => {
if (!filesCurrentUiPath) return;
filesSetDirty(true);
});
}
}
@@ -179,9 +260,11 @@ async function filesRefresh() {
for (const f of sortedFiles) {
const fullUi = node.uiPath ? `${node.uiPath}/${f}` : f;
const fileKey = encodeURIComponent(fullUi);
out.push(`
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0 4px ${indent + 18}px;border-bottom:1px dashed rgba(36,52,95,.35)">
<span class="mono" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${encodeURIComponent(fullUi)}'))">📄 ${esc(f)}</span>
<div class="file-entry" data-file="${fileKey}" style="padding-left:${indent + 18}px;">
<span class="mono file-entry-name" onclick="filesOpen(decodeURIComponent('${fileKey}'))">📄 ${esc(f)}</span>
<span class="file-entry-state"></span>
</div>
`);
}
@@ -216,9 +299,11 @@ async function filesRefresh() {
<div class="file-folder-files" data-folder-files="${esc(folderKey)}" style="${collapsed ? 'display:none;' : ''}">
${(rootFolder.files || []).slice().sort((a,b)=>a.localeCompare(b)).map(f => {
const fullUi = f;
const fileKey = encodeURIComponent(fullUi);
return `
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 0 4px 18px;border-bottom:1px dashed rgba(36,52,95,.35)">
<span class="mono" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${encodeURIComponent(fullUi)}'))">📄 ${esc(f)}</span>
<div class="file-entry" data-file="${fileKey}" style="padding-left:18px;">
<span class="mono file-entry-name" style="cursor:pointer" onclick="filesOpen(decodeURIComponent('${fileKey}'))">📄 ${esc(f)}</span>
<span class="file-entry-state"></span>
</div>
`;
}).join('')}
@@ -243,6 +328,8 @@ async function filesRefresh() {
const filesBlock = treeEl.querySelector(`[data-folder-files="${CSS.escape(folderKey)}"]`);
if (filesBlock) filesBlock.style.display = isNowCollapsed ? 'none' : '';
};
filesUpdateTreeSelection();
filesUpdateEditorStatus();
}
async function filesOpen(uiPath) {
@@ -253,11 +340,16 @@ async function filesOpen(uiPath) {
const text = res.content || '';
if (cmEditor) {
cmEditor.setOption('mode', cmModeForPath(uiPath));
filesSuppressDirtyEvent = true;
cmEditor.setValue(text);
filesSuppressDirtyEvent = false;
cmEditor.refresh();
cmEditor.setCursor({ line: 0, ch: 0 });
} else {
document.getElementById('filesEditor').value = text;
}
filesSetDirty(false);
filesUpdateEditorStatus();
}
async function filesSave() {
@@ -275,6 +367,7 @@ async function filesSave() {
{ content }
);
filesSetDirty(false);
showModal('Opgeslagen', JSON.stringify(res, null, 2));
await filesRefresh();
}
@@ -290,7 +383,14 @@ async function filesDelete() {
// reset current
filesSetCurrent('');
document.getElementById('filesEditor').value = '';
if (cmEditor) {
filesSuppressDirtyEvent = true;
cmEditor.setValue('');
filesSuppressDirtyEvent = false;
cmEditor.refresh();
} else {
document.getElementById('filesEditor').value = '';
}
await filesRefresh();
}
+5 -5
View File
@@ -28,8 +28,8 @@ function renderImages(images) {
const fullId = img.Id;
const status = containers > 0
? `<span class="badge badge-green">In use</span>`
: `<span class="badge badge-yellow">Unused</span>`;
? `<span class="badge ok">In use</span>`
: `<span class="badge warn">Unused</span>`;
const disabled = containers > 0 ? "disabled" : "";
@@ -39,9 +39,9 @@ function renderImages(images) {
</td>
<td>${repoTag}</td>
<td>${shortId}</td>
<td>${sizeMB} MB</td>
<td>${created}</td>
<td>${containers}</td>
<td class="num">${sizeMB} MB</td>
<td class="muted">${created}</td>
<td class="num">${containers}</td>
<td>${status}</td>
<td>
<button class="btn small bad" onclick="removeSingleImage('${fullId}')" ${disabled}>