This commit is contained in:
2026-03-07 23:58:40 +01:00
parent 766eab12e7
commit 12c2ce4817
10 changed files with 652 additions and 96 deletions

View File

@@ -0,0 +1,97 @@
(() => {
const form = document.querySelector('[data-command-form]');
const list = document.querySelector('[data-command-list]');
if (!form) return;
const idInput = form.querySelector('input[name="id"]');
const labelInput = form.querySelector('input[name="label"]');
const commandInput = form.querySelector('textarea[name="command"]');
const timeoutInput = form.querySelector('input[name="timeout_sec"]');
const adminInput = form.querySelector('input[name="admin_only"]');
const submitBtn = form.querySelector('[data-command-submit]');
const cancelBtn = form.querySelector('[data-command-cancel]');
const resetForm = () => {
if (idInput) idInput.value = '';
if (labelInput) labelInput.value = '';
if (commandInput) commandInput.value = '';
if (timeoutInput) timeoutInput.value = '';
if (adminInput) adminInput.checked = false;
if (submitBtn) submitBtn.textContent = 'Speichern';
};
document.querySelectorAll('[data-command-edit]').forEach((btn) => {
btn.addEventListener('click', () => {
const item = btn.closest('.command-item');
if (!item) return;
if (idInput) idInput.value = item.dataset.commandId || '';
if (labelInput) labelInput.value = item.dataset.label || '';
if (commandInput) commandInput.value = item.dataset.command || '';
if (timeoutInput) timeoutInput.value = item.dataset.timeout || '';
if (adminInput) adminInput.checked = item.dataset.admin === '1';
if (submitBtn) submitBtn.textContent = 'Aktualisieren';
const details = btn.closest('details');
if (details) details.removeAttribute('open');
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
if (cancelBtn) {
cancelBtn.addEventListener('click', (e) => {
e.preventDefault();
resetForm();
});
}
if (!list) return;
let dragging = null;
list.querySelectorAll('.command-item').forEach((item) => {
item.addEventListener('dragstart', () => {
dragging = item;
item.classList.add('is-dragging');
});
item.addEventListener('dragend', () => {
item.classList.remove('is-dragging');
dragging = null;
saveOrder();
});
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
if (!dragging) return;
const after = getDragAfterElement(list, e.clientY);
if (after == null) {
list.appendChild(dragging);
} else if (after !== dragging) {
list.insertBefore(dragging, after);
}
});
const getDragAfterElement = (container, y) => {
const elements = [...container.querySelectorAll('.command-item:not(.is-dragging)')];
return elements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset, element: child };
}
return closest;
},
{ offset: Number.NEGATIVE_INFINITY, element: null }
).element;
};
const saveOrder = () => {
const order = [...list.querySelectorAll('.command-item')].map((el) => el.dataset.commandId);
fetch(window.location.pathname + '?reorder_json=1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order }),
cache: 'no-store',
}).catch(() => {});
};
})();

View File

@@ -0,0 +1,56 @@
(() => {
const form = document.querySelector('[data-host-form]');
if (!form) return;
const idInput = form.querySelector('input[name="id"]');
const nameInput = form.querySelector('input[name="name"]');
const hostInput = form.querySelector('input[name="host"]');
const portInput = form.querySelector('input[name="port"]');
const userInput = form.querySelector('input[name="username"]');
const authSelect = form.querySelector('select[name="auth_type"]');
const keyInput = form.querySelector('input[name="key_path"]');
const passInput = form.querySelector('input[name="password"]');
const imageInput = form.querySelector('input[name="image_url"]');
const submitBtn = form.querySelector('[data-host-submit]');
const cancelBtn = form.querySelector('[data-host-cancel]');
const resetForm = () => {
if (idInput) idInput.value = '';
if (nameInput) nameInput.value = '';
if (hostInput) hostInput.value = '';
if (portInput) portInput.value = '22';
if (userInput) userInput.value = '';
if (authSelect) authSelect.value = 'key';
if (keyInput) keyInput.value = '';
if (passInput) passInput.value = '';
if (imageInput) imageInput.value = '';
if (submitBtn) submitBtn.textContent = 'Speichern';
};
document.querySelectorAll('[data-host-edit]').forEach((btn) => {
btn.addEventListener('click', () => {
const card = btn.closest('.host-card');
if (!card) return;
if (idInput) idInput.value = card.dataset.hostId || '';
if (nameInput) nameInput.value = card.dataset.name || '';
if (hostInput) hostInput.value = card.dataset.host || '';
if (portInput) portInput.value = card.dataset.port || '22';
if (userInput) userInput.value = card.dataset.username || '';
if (authSelect) authSelect.value = card.dataset.auth || 'key';
if (keyInput) keyInput.value = card.dataset.keyPath || '';
if (passInput) passInput.value = '';
if (imageInput) imageInput.value = card.dataset.imageUrl || '';
if (submitBtn) submitBtn.textContent = 'Aktualisieren';
const details = btn.closest('details');
if (details) details.removeAttribute('open');
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
if (cancelBtn) {
cancelBtn.addEventListener('click', (e) => {
e.preventDefault();
resetForm();
});
}
})();

View File

@@ -73,6 +73,119 @@
background: #0b0f17;
}
.host-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
.host-card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 16px;
overflow: hidden;
display: grid;
grid-template-rows: 120px 1fr;
box-shadow: var(--shadow);
}
.host-card-image {
background: linear-gradient(135deg, #2b3a67 0%, #3b2f5c 45%, #1c2b3f 100%);
background-size: cover;
background-position: center;
position: relative;
}
.host-card-overlay {
position: absolute;
inset: 0;
background: rgba(10, 16, 28, 0.35);
}
.host-card-body {
padding: 12px 14px 14px;
display: grid;
gap: 6px;
}
.host-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.host-card-title {
display: inline-flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 999px;
display: inline-block;
}
.status-ok { background: #31c48d; }
.status-auth { background: #fbbf24; }
.status-down { background: #ef4444; }
.action-menu {
position: relative;
}
.action-menu summary {
list-style: none;
cursor: pointer;
border-radius: 10px;
padding: 2px 6px;
border: 1px solid var(--line);
background: var(--panel-2);
}
.action-menu summary::-webkit-details-marker { display: none; }
.action-menu[open] summary {
background: var(--panel);
}
.action-menu-panel {
position: absolute;
right: 0;
top: calc(100% + 6px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: 12px;
padding: 6px;
min-width: 160px;
display: grid;
gap: 4px;
z-index: 5;
box-shadow: var(--shadow);
}
.action-menu-panel form { margin: 0; }
.command-list {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: 10px;
}
.command-item {
display: grid;
grid-template-columns: 28px 1fr auto;
gap: 10px;
align-items: start;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: var(--panel);
}
.command-item.is-dragging {
opacity: 0.6;
}
.command-drag {
cursor: grab;
color: var(--muted);
font-size: 1.1rem;
padding-top: 4px;
}
.command-body code {
display: inline-block;
word-break: break-word;
}
.queue-button {
display: inline-flex;
align-items: center;