Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
326 lines
15 KiB
HTML
326 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ safe_title }}</title>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
background: #0f0f1a;
|
|
color: #e5e7eb;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.terminal-header {
|
|
background: #1a1a2e;
|
|
padding: 0.5rem 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
border-bottom: 1px solid #374151;
|
|
flex-shrink: 0;
|
|
}
|
|
.terminal-header .host-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
.terminal-header .host-name { font-weight: 600; color: #fff; }
|
|
.terminal-header .host-ip { color: #9ca3af; font-size: 0.875rem; }
|
|
.terminal-header .status-badge {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
.terminal-header .status-badge.online {
|
|
background: rgba(34, 197, 94, 0.2);
|
|
color: #4ade80;
|
|
}
|
|
.terminal-header .session-timer {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: #9ca3af;
|
|
font-size: 0.875rem;
|
|
}
|
|
.terminal-header .session-timer.warning { color: #fbbf24; }
|
|
.terminal-header .session-timer.critical { color: #ef4444; }
|
|
.terminal-header .actions { display: flex; gap: 0.5rem; }
|
|
.terminal-header .btn {
|
|
padding: 0.375rem 0.75rem;
|
|
border-radius: 0.375rem;
|
|
font-size: 0.875rem;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.15s;
|
|
}
|
|
.terminal-header .btn-secondary { background: #374151; color: #e5e7eb; }
|
|
.terminal-header .btn-secondary:hover { background: #4b5563; }
|
|
.terminal-header .btn-secondary.active { background: #4f46e5; color: #fff; }
|
|
.terminal-header .btn-danger { background: #dc2626; color: #fff; }
|
|
.terminal-header .btn-danger:hover { background: #b91c1c; }
|
|
.terminal-container { flex: 1; position: relative; overflow: hidden; }
|
|
.terminal-container iframe { width: 100%; height: 100%; border: none; background: #000; }
|
|
.terminal-loading {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: #0f0f1a;
|
|
}
|
|
.terminal-loading .spinner {
|
|
width: 48px; height: 48px;
|
|
border: 3px solid #374151;
|
|
border-top-color: #3b82f6;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
.terminal-loading .loading-text { margin-top: 1rem; color: #9ca3af; }
|
|
.pwa-hint {
|
|
background: #1e3a5f;
|
|
color: #93c5fd;
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.75rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.pwa-hint button {
|
|
background: none; border: none; color: #60a5fa; cursor: pointer; font-size: 0.75rem;
|
|
}
|
|
body.embed #pwaHint { display: none !important; }
|
|
body.embed .terminal-header { display: none !important; }
|
|
.hidden { display: none !important; }
|
|
/* History Panel */
|
|
.terminal-history-panel {
|
|
position: absolute; top: 0; left: 0; right: 0;
|
|
max-height: 0; overflow: hidden;
|
|
background: #1e1e2e; border-bottom: 1px solid #374151;
|
|
display: flex; flex-direction: column; z-index: 50;
|
|
box-shadow: 0 5px 25px rgba(0,0,0,0.5);
|
|
opacity: 0;
|
|
transition: max-height 0.3s ease, opacity 0.2s ease;
|
|
}
|
|
.terminal-history-panel.open { max-height: 350px; opacity: 1; }
|
|
.terminal-history-header {
|
|
padding: 0.75rem; background: #151520; border-bottom: 1px solid #374151; flex-shrink: 0;
|
|
}
|
|
.terminal-history-search {
|
|
position: relative; display: flex; align-items: center;
|
|
background: #0f0f1a; border: 1px solid #374151;
|
|
border-radius: 0.375rem; padding: 0.5rem 0.75rem; transition: border-color 0.15s;
|
|
}
|
|
.terminal-history-search:focus-within { border-color: #7c3aed; }
|
|
.terminal-history-search i { color: #6b7280; font-size: 0.875rem; margin-right: 0.5rem; }
|
|
.terminal-history-search input {
|
|
flex: 1; background: transparent; border: none; color: #e5e7eb;
|
|
font-size: 0.875rem; outline: none;
|
|
}
|
|
.terminal-history-search input::placeholder { color: #6b7280; }
|
|
.terminal-history-clear-search {
|
|
background: none; border: none; color: #6b7280; cursor: pointer;
|
|
padding: 0.25rem; font-size: 0.75rem; transition: color 0.15s;
|
|
}
|
|
.terminal-history-clear-search:hover { color: #e5e7eb; }
|
|
.terminal-history-filters {
|
|
display: flex; align-items: center; gap: 0.75rem; margin-top: 0.5rem;
|
|
}
|
|
.terminal-history-filter-select {
|
|
background: #0f0f1a; border: 1px solid #374151; border-radius: 0.375rem;
|
|
color: #e5e7eb; padding: 0.375rem 0.5rem; font-size: 0.75rem;
|
|
cursor: pointer; outline: none;
|
|
}
|
|
.terminal-history-filter-select:hover,
|
|
.terminal-history-filter-select:focus { border-color: #7c3aed; }
|
|
.terminal-history-scope {
|
|
display: flex; align-items: center; gap: 0.375rem;
|
|
font-size: 0.75rem; color: #9ca3af; cursor: pointer;
|
|
}
|
|
.terminal-history-scope input {
|
|
width: 0.875rem; height: 0.875rem; accent-color: #7c3aed; cursor: pointer;
|
|
}
|
|
.terminal-history-list {
|
|
flex: 1; overflow-y: auto; padding: 0.5rem;
|
|
scrollbar-width: thin; scrollbar-color: #374151 transparent;
|
|
}
|
|
.terminal-history-list::-webkit-scrollbar { width: 6px; }
|
|
.terminal-history-list::-webkit-scrollbar-track { background: transparent; }
|
|
.terminal-history-list::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
|
|
.terminal-history-item {
|
|
display: flex; align-items: center; padding: 0.5rem 0.75rem;
|
|
border-radius: 0.375rem; cursor: pointer; transition: background 0.15s;
|
|
gap: 0.75rem; border: 1px solid transparent;
|
|
}
|
|
.terminal-history-item:hover { background: rgba(124, 58, 237, 0.1); }
|
|
.terminal-history-item.selected {
|
|
background: rgba(124, 58, 237, 0.2); border-color: rgba(124, 58, 237, 0.4);
|
|
}
|
|
.terminal-history-cmd { flex: 1; min-width: 0; }
|
|
.terminal-history-cmd code {
|
|
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
font-size: 0.8125rem; color: #a5b4fc;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block;
|
|
}
|
|
.terminal-history-cmd code mark {
|
|
background: rgba(251, 191, 36, 0.3); color: #fbbf24;
|
|
padding: 0 0.125rem; border-radius: 2px;
|
|
}
|
|
.terminal-history-meta { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
|
|
.terminal-history-time { font-size: 0.6875rem; color: #6b7280; }
|
|
.terminal-history-count {
|
|
font-size: 0.625rem; color: #7c3aed;
|
|
background: rgba(124, 58, 237, 0.2); padding: 0.125rem 0.375rem;
|
|
border-radius: 9999px; font-weight: 500;
|
|
}
|
|
.terminal-history-actions-inline {
|
|
display: flex; gap: 0.25rem; opacity: 0; transition: opacity 0.15s;
|
|
}
|
|
.terminal-history-item:hover .terminal-history-actions-inline,
|
|
.terminal-history-item.selected .terminal-history-actions-inline { opacity: 1; }
|
|
.terminal-history-action {
|
|
background: rgba(55, 65, 81, 0.5); border: none; color: #9ca3af;
|
|
padding: 0.375rem; border-radius: 0.25rem; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 0.75rem; transition: background 0.15s, color 0.15s, transform 0.1s;
|
|
}
|
|
.terminal-history-action:hover {
|
|
background: rgba(124, 58, 237, 0.3); color: #fff; transform: scale(1.1);
|
|
}
|
|
.terminal-history-action-execute:hover {
|
|
background: rgba(34, 197, 94, 0.3); color: #4ade80;
|
|
}
|
|
.terminal-history-empty {
|
|
display: flex; flex-direction: column; align-items: center;
|
|
justify-content: center; padding: 2rem; color: #6b7280; gap: 0.5rem; text-align: center;
|
|
}
|
|
.terminal-history-empty i { font-size: 2rem; opacity: 0.5; }
|
|
.terminal-history-loading {
|
|
display: flex; align-items: center; justify-content: center;
|
|
padding: 2rem; color: #9ca3af; gap: 0.5rem;
|
|
}
|
|
.terminal-history-footer {
|
|
padding: 0.5rem 1rem; background: #151520;
|
|
border-top: 1px solid #374151; flex-shrink: 0;
|
|
}
|
|
.terminal-history-hint {
|
|
font-size: 0.6875rem; color: #6b7280;
|
|
display: flex; align-items: center; gap: 0.25rem; flex-wrap: wrap;
|
|
}
|
|
.terminal-history-hint kbd {
|
|
background: #374151; color: #e5e7eb; padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem; font-family: inherit; font-size: 0.625rem; border: 1px solid #4b5563;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="{{ 'embed' if embed_mode else '' }}">
|
|
<div class="pwa-hint" id="pwaHint">
|
|
<span>💡 Pour une expérience sans barre d'outils : installez en PWA ou utilisez <code>chrome --app=URL</code></span>
|
|
<button onclick="dismissPwaHint()">✕ Fermer</button>
|
|
</div>
|
|
|
|
<div class="terminal-header">
|
|
<div class="host-info">
|
|
<span class="host-name">{{ safe_host_name }}</span>
|
|
<span class="host-ip">{{ safe_host_ip }}</span>
|
|
<span class="status-badge online">Connecté</span>
|
|
</div>
|
|
<div class="session-timer" id="sessionTimer">
|
|
<i class="fas fa-clock"></i>
|
|
<span id="timerDisplay">{{ timer_display }}</span>
|
|
</div>
|
|
<div class="actions">
|
|
<button class="btn btn-secondary" onclick="toggleHistory()" id="btnHistory" title="Historique (Ctrl+R)">
|
|
<i class="fas fa-history"></i> Historique
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="copySSHCommand()" title="Copier la commande SSH">
|
|
<i class="fas fa-copy"></i> SSH
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="reconnect()" title="Reconnecter">
|
|
<i class="fas fa-redo"></i> Reconnecter
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="goToDashboard()" title="Retour au Dashboard">
|
|
<i class="fas fa-arrow-left"></i> Dashboard
|
|
</button>
|
|
<button class="btn btn-danger" onclick="closeSession()" title="Fermer la session">
|
|
<i class="fas fa-times"></i> Fermer
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="terminal-container">
|
|
<div class="terminal-loading" id="terminalLoading">
|
|
<div style="text-align: center;">
|
|
<div class="spinner"></div>
|
|
<div class="loading-text">Connexion au terminal...</div>
|
|
<div style="margin-top:0.75rem; font-size:0.85rem; color:#9ca3af;">
|
|
session={{ session_id_short }}… · ttyd_port={{ ttyd_port }} · Debug: Ctrl+Shift+D
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<noscript>
|
|
<div style="position:absolute; top:14px; right:14px; z-index:9999; background:rgba(239,68,68,0.18); border:1px solid rgba(239,68,68,0.6); color:#fff; padding:10px 12px; border-radius:10px; font-size:12px; max-width:520px;">
|
|
JavaScript est désactivé: le terminal web ne peut pas se charger.
|
|
</div>
|
|
</noscript>
|
|
{% if debug_panel_html %}
|
|
{{ debug_panel_html | safe }}
|
|
{% endif %}
|
|
<iframe
|
|
id="terminalFrame"
|
|
src="about:blank"
|
|
allow="clipboard-read; clipboard-write; clipboard-write-text"
|
|
></iframe>
|
|
<div class="terminal-history-panel" id="terminalHistoryPanel" style="display: none;">
|
|
<div class="terminal-history-header">
|
|
<div class="terminal-history-search">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text"
|
|
id="terminalHistorySearch"
|
|
placeholder="Rechercher... (Ctrl+R)"
|
|
oninput="searchHistory(this.value)"
|
|
onkeydown="handleHistoryKeydown(event)"
|
|
autocomplete="off">
|
|
<button class="terminal-history-clear-search" onclick="clearHistorySearch()" title="Effacer">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="terminal-history-filters">
|
|
<select id="terminalHistoryTimeFilter" onchange="setHistoryTimeFilter(this.value)" class="terminal-history-filter-select">
|
|
<option value="all">Tout</option>
|
|
<option value="today">Aujourd'hui</option>
|
|
<option value="week">7 jours</option>
|
|
<option value="month">30 jours</option>
|
|
</select>
|
|
<label class="terminal-history-scope">
|
|
<input type="checkbox" id="terminalHistoryAllHosts" onchange="toggleHistoryScope()">
|
|
<span>Tous hôtes</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="terminal-history-list" id="terminalHistoryList">
|
|
<div class="terminal-history-loading">
|
|
<i class="fas fa-spinner fa-spin"></i> Chargement...
|
|
</div>
|
|
</div>
|
|
<div class="terminal-history-footer">
|
|
<span class="terminal-history-hint">
|
|
<kbd>↑</kbd><kbd>↓</kbd> naviguer · <kbd>Enter</kbd> insérer · <kbd>Esc</kbd> fermer
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{ script_block | safe }}
|
|
</body>
|
|
</html>
|