asdasd
This commit is contained in:
@@ -12,6 +12,10 @@
|
|||||||
const tabPanels = document.querySelector('[data-console-tab-panels]');
|
const tabPanels = document.querySelector('[data-console-tab-panels]');
|
||||||
if (!tabBar || !tabPanels) return;
|
if (!tabBar || !tabPanels) return;
|
||||||
|
|
||||||
|
const consoleFab = document.querySelector('[data-console-fab]');
|
||||||
|
const consoleModal = document.querySelector('[data-console-modal]');
|
||||||
|
const consoleClose = document.querySelector('[data-console-close]');
|
||||||
|
|
||||||
let tabCount = 0;
|
let tabCount = 0;
|
||||||
const idleMs = 5 * 60 * 1000;
|
const idleMs = 5 * 60 * 1000;
|
||||||
const idleTimers = new Map();
|
const idleTimers = new Map();
|
||||||
@@ -32,6 +36,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
localStorage.setItem(storageKey, JSON.stringify(tabs));
|
localStorage.setItem(storageKey, JSON.stringify(tabs));
|
||||||
|
if (consoleFab) {
|
||||||
|
consoleFab.classList.toggle('is-visible', tabs.length > 0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activateTab = (id) => {
|
const activateTab = (id) => {
|
||||||
@@ -192,12 +199,38 @@
|
|||||||
openTab(t.label || 'Konsole', t.url, false);
|
openTab(t.label || 'Konsole', t.url, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (consoleFab && tabs.length > 0) {
|
||||||
|
consoleFab.classList.add('is-visible');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
if (!consoleModal) return;
|
||||||
|
consoleModal.classList.add('is-open');
|
||||||
|
consoleModal.setAttribute('aria-hidden', 'false');
|
||||||
|
};
|
||||||
|
const closeModal = () => {
|
||||||
|
if (!consoleModal) return;
|
||||||
|
consoleModal.classList.remove('is-open');
|
||||||
|
consoleModal.setAttribute('aria-hidden', 'true');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (consoleFab) {
|
||||||
|
consoleFab.addEventListener('click', openModal);
|
||||||
|
}
|
||||||
|
if (consoleClose) {
|
||||||
|
consoleClose.addEventListener('click', closeModal);
|
||||||
|
}
|
||||||
|
if (consoleModal) {
|
||||||
|
consoleModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === consoleModal) closeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const queueBody = document.querySelector('[data-queue-body]');
|
const queueBody = document.querySelector('[data-queue-body]');
|
||||||
const countdownEl = document.querySelector('[data-queue-countdown]');
|
const countdownEl = document.querySelector('[data-queue-countdown]');
|
||||||
const refreshBtn = document.querySelector('[data-queue-refresh]');
|
const refreshBtn = document.querySelector('[data-queue-refresh]');
|
||||||
|
|||||||
@@ -53,4 +53,30 @@
|
|||||||
resetForm();
|
resetForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateStatus = (card, status) => {
|
||||||
|
const dot = card.querySelector('[data-host-status]');
|
||||||
|
if (!dot) return;
|
||||||
|
dot.classList.remove('status-ok', 'status-auth', 'status-down');
|
||||||
|
if (status === 'ok') dot.classList.add('status-ok');
|
||||||
|
else if (status === 'down') dot.classList.add('status-down');
|
||||||
|
else dot.classList.add('status-auth');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchStatus = (card) => {
|
||||||
|
const id = card.dataset.hostId;
|
||||||
|
if (!id) return;
|
||||||
|
fetch(`${window.location.pathname}?status_json=1&id=${encodeURIComponent(id)}`, { cache: 'no-store' })
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data && data.ok) {
|
||||||
|
updateStatus(card, data.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.host-card').forEach((card) => {
|
||||||
|
fetchStatus(card);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
z-index: 5;
|
z-index: 30;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
.action-menu-panel form { margin: 0; }
|
.action-menu-panel form { margin: 0; }
|
||||||
|
|||||||
@@ -485,15 +485,6 @@ function sendToActiveConsole(array $host, string $command, bool $strictHostKey):
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card form-card" style="background:var(--panel-2);">
|
|
||||||
<strong>Konsole Tabs</strong>
|
|
||||||
<div class="console-tabs" data-console-tabs>
|
|
||||||
<div class="console-tab-bar" data-console-tab-bar></div>
|
|
||||||
<div class="console-tab-panels" data-console-tab-panels></div>
|
|
||||||
</div>
|
|
||||||
<p class="muted" style="margin-top:.5rem;">Mehrere Konsolen bleiben hier parallel offen.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal" data-queue-modal aria-hidden="true">
|
<div class="modal" data-queue-modal aria-hidden="true">
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -11,6 +11,27 @@ if ($assets) {
|
|||||||
$notice = null;
|
$notice = null;
|
||||||
$error = null;
|
$error = null;
|
||||||
|
|
||||||
|
if (isset($_GET['status_json'])) {
|
||||||
|
require_admin();
|
||||||
|
$id = (int)($_GET['id'] ?? 0);
|
||||||
|
$stmt = $pdo->prepare('SELECT * FROM ' . $table('hosts') . ' WHERE id = :id LIMIT 1');
|
||||||
|
$stmt->execute(['id' => $id]);
|
||||||
|
$host = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$host) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => false, 'error' => 'not_found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$settings = modules()->settings('pi_control');
|
||||||
|
$strictHostKey = !empty($settings['terminal_strict_hostkey']);
|
||||||
|
$reachable = hostReachable((string)($host['host'] ?? ''), (int)($host['port'] ?? 22));
|
||||||
|
$authOk = $reachable ? hostAuthOk($host, $strictHostKey) : false;
|
||||||
|
$status = !$reachable ? 'down' : ($authOk ? 'ok' : 'auth');
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
echo json_encode(['ok' => true, 'status' => $status]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
require_admin();
|
require_admin();
|
||||||
$deleteId = (int)($_POST['delete_id'] ?? 0);
|
$deleteId = (int)($_POST['delete_id'] ?? 0);
|
||||||
@@ -223,12 +244,6 @@ function hostAuthOk(array $host, bool $strictHostKey): bool
|
|||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="host-grid" style="margin-top:.75rem;">
|
<div class="host-grid" style="margin-top:.75rem;">
|
||||||
<?php foreach ($hosts as $h): ?>
|
<?php foreach ($hosts as $h): ?>
|
||||||
<?php
|
|
||||||
$portVal = (int)($h['port'] ?? 22);
|
|
||||||
$reachable = hostReachable((string)($h['host'] ?? ''), $portVal);
|
|
||||||
$authOk = $reachable ? hostAuthOk($h, $strictHostKey) : false;
|
|
||||||
$statusClass = !$reachable ? 'status-down' : ($authOk ? 'status-ok' : 'status-auth');
|
|
||||||
?>
|
|
||||||
<div class="host-card"
|
<div class="host-card"
|
||||||
data-host-id="<?= e((string)$h['id']) ?>"
|
data-host-id="<?= e((string)$h['id']) ?>"
|
||||||
data-name="<?= e((string)($h['name'] ?? '')) ?>"
|
data-name="<?= e((string)($h['name'] ?? '')) ?>"
|
||||||
@@ -244,7 +259,7 @@ function hostAuthOk(array $host, bool $strictHostKey): bool
|
|||||||
<div class="host-card-body">
|
<div class="host-card-body">
|
||||||
<div class="host-card-header">
|
<div class="host-card-header">
|
||||||
<div class="host-card-title">
|
<div class="host-card-title">
|
||||||
<span class="status-dot <?= $statusClass ?>"></span>
|
<span class="status-dot status-auth" data-host-status></span>
|
||||||
<strong><?= e((string)($h['name'] ?? '')) ?></strong>
|
<strong><?= e((string)($h['name'] ?? '')) ?></strong>
|
||||||
</div>
|
</div>
|
||||||
<details class="action-menu">
|
<details class="action-menu">
|
||||||
|
|||||||
@@ -135,6 +135,22 @@ $sidebarItems = $moduleSidebar['items'] ?? [];
|
|||||||
<button class="sidebar-fab" data-sidebar-toggle aria-label="Menü einblenden">»»</button>
|
<button class="sidebar-fab" data-sidebar-toggle aria-label="Menü einblenden">»»</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button class="console-fab" type="button" data-console-fab title="Konsole öffnen">
|
||||||
|
<span>›</span>
|
||||||
|
</button>
|
||||||
|
<div class="console-modal" data-console-modal aria-hidden="true">
|
||||||
|
<div class="console-modal-card">
|
||||||
|
<div class="console-modal-header">
|
||||||
|
<strong>Konsole Tabs</strong>
|
||||||
|
<button class="icon-button" type="button" data-console-close>×</button>
|
||||||
|
</div>
|
||||||
|
<div class="console-tabs" data-console-tabs style="margin-top:12px;">
|
||||||
|
<div class="console-tab-bar" data-console-tab-bar></div>
|
||||||
|
<div class="console-tab-panels" data-console-tab-panels></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL && auth_is_admin()): ?>
|
<?php if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL && auth_is_admin()): ?>
|
||||||
<button class="debug-fab" data-debug-open title="Debug" type="button">🐞</button>
|
<button class="debug-fab" data-debug-open title="Debug" type="button">🐞</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ body {
|
|||||||
}
|
}
|
||||||
.nav-dropdown-menu {
|
.nav-dropdown-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(100% + 6px);
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
@@ -168,7 +168,16 @@ body {
|
|||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 10;
|
z-index: 20;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.nav-dropdown-menu::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 8px;
|
||||||
}
|
}
|
||||||
.nav-dropdown:hover .nav-dropdown-menu,
|
.nav-dropdown:hover .nav-dropdown-menu,
|
||||||
.nav-dropdown:focus-within .nav-dropdown-menu {
|
.nav-dropdown:focus-within .nav-dropdown-menu {
|
||||||
@@ -189,6 +198,54 @@ body {
|
|||||||
grid-template-columns: 260px 1fr;
|
grid-template-columns: 260px 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.console-fab {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 18px;
|
||||||
|
left: 18px;
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #0b0f17;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
.console-fab.is-visible { display: inline-flex; }
|
||||||
|
.console-fab span { font-size: 1.2rem; }
|
||||||
|
|
||||||
|
.console-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(10, 14, 24, 0.55);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 24px;
|
||||||
|
z-index: 45;
|
||||||
|
}
|
||||||
|
.console-modal.is-open { display: flex; }
|
||||||
|
.console-modal-card {
|
||||||
|
width: min(1200px, 96vw);
|
||||||
|
max-height: 92vh;
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.console-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user