mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-06-11 09:14:52 +02:00
a571d5388d
- Stop persisting "Disconnected" / "Failed to connect" — these are session-local events; saving them made every reopen begin with a stale red error. - Scroll to the bottom after restoring transcript so reopens land at the latest entry instead of the top. - Add a floating chat-style jump-to-latest button that appears whenever the user scrolls more than ~80px above the bottom and disappears once they're back at the latest entry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
363 lines
10 KiB
HTML
363 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<title>Console - mc-webui</title>
|
|
|
|
<!-- Favicon -->
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/favicon-32x32.png') }}">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='images/favicon-16x16.png') }}">
|
|
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
|
|
|
<!-- Bootstrap 5 CSS (local) -->
|
|
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
|
|
<!-- Bootstrap Icons (local) -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}">
|
|
<!-- Custom CSS -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
|
|
<style>
|
|
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
background-color: #1a1a2e;
|
|
}
|
|
|
|
.console-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
}
|
|
|
|
.console-header {
|
|
background-color: #16213e;
|
|
border-bottom: 1px solid #0f3460;
|
|
padding: 0.75rem 1rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.device-name {
|
|
color: #4ecdc4;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.messages-wrap {
|
|
flex: 1;
|
|
min-height: 0;
|
|
position: relative;
|
|
display: flex;
|
|
}
|
|
|
|
.console-messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
background-color: #1a1a2e;
|
|
min-height: 0;
|
|
}
|
|
|
|
.scroll-bottom-btn {
|
|
position: absolute;
|
|
right: 1rem;
|
|
bottom: 1rem;
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
border: 1px solid #0f3460;
|
|
background-color: #16213e;
|
|
color: #4ecdc4;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
|
|
cursor: pointer;
|
|
z-index: 5;
|
|
}
|
|
|
|
.scroll-bottom-btn.show {
|
|
display: flex;
|
|
}
|
|
|
|
.scroll-bottom-btn:hover {
|
|
background-color: #1a1a4e;
|
|
color: #00ff88;
|
|
}
|
|
|
|
.console-message {
|
|
margin-bottom: 1rem;
|
|
font-family: 'Courier New', Consolas, monospace;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.console-message.command {
|
|
color: #00ff88;
|
|
}
|
|
|
|
.console-message.command::before {
|
|
content: '> ';
|
|
color: #888;
|
|
}
|
|
|
|
.console-message.response {
|
|
color: #e0e0e0;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
background-color: #16213e;
|
|
padding: 0.5rem;
|
|
border-radius: 0.25rem;
|
|
border-left: 3px solid #0f3460;
|
|
}
|
|
|
|
.console-message.error {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.console-message.system {
|
|
color: #4ecdc4;
|
|
font-style: italic;
|
|
}
|
|
|
|
.console-message.historic {
|
|
opacity: 0.55;
|
|
}
|
|
|
|
.history-divider {
|
|
border: none;
|
|
border-top: 1px dashed #0f3460;
|
|
margin: 0.5rem 0 1rem 0;
|
|
}
|
|
|
|
.console-input-area {
|
|
background-color: #16213e;
|
|
border-top: 1px solid #0f3460;
|
|
padding: 0.75rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.console-input {
|
|
background-color: #0f3460;
|
|
border: 1px solid #1a1a2e;
|
|
color: #00ff88;
|
|
font-family: 'Courier New', Consolas, monospace;
|
|
}
|
|
|
|
.console-input:focus {
|
|
background-color: #0f3460;
|
|
border-color: #00ff88;
|
|
color: #00ff88;
|
|
box-shadow: 0 0 0 0.2rem rgba(0, 255, 136, 0.25);
|
|
}
|
|
|
|
.console-input::placeholder {
|
|
color: #666;
|
|
}
|
|
|
|
.console-input:disabled {
|
|
background-color: #0a1628;
|
|
color: #444;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
|
|
.status-dot.connected {
|
|
background-color: #00ff88;
|
|
}
|
|
|
|
.status-dot.disconnected {
|
|
background-color: #ff6b6b;
|
|
}
|
|
|
|
.status-dot.connecting {
|
|
background-color: #ffd93d;
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
/* Loading spinner for pending commands */
|
|
.console-message.pending::after {
|
|
content: '';
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border: 2px solid #4ecdc4;
|
|
border-top-color: transparent;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-left: 0.5rem;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* History dropdown */
|
|
.history-dropdown {
|
|
position: relative;
|
|
}
|
|
|
|
.history-btn {
|
|
background-color: #0f3460;
|
|
border: 1px solid #1a1a2e;
|
|
color: #4ecdc4;
|
|
}
|
|
|
|
.history-btn:hover, .history-btn:focus {
|
|
background-color: #1a1a4e;
|
|
border-color: #4ecdc4;
|
|
color: #4ecdc4;
|
|
}
|
|
|
|
.history-btn:disabled {
|
|
background-color: #0a1628;
|
|
color: #444;
|
|
}
|
|
|
|
.history-menu {
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
min-width: 250px;
|
|
max-width: 100%;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
background-color: #16213e;
|
|
border: 1px solid #0f3460;
|
|
border-radius: 0.375rem;
|
|
margin-bottom: 0.25rem;
|
|
display: none;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.history-menu.show {
|
|
display: block;
|
|
}
|
|
|
|
.history-item {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 0.5rem 0.75rem;
|
|
color: #e0e0e0;
|
|
text-decoration: none;
|
|
font-family: 'Courier New', Consolas, monospace;
|
|
font-size: 0.85rem;
|
|
border: none;
|
|
background: none;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.history-item:hover {
|
|
background-color: #0f3460;
|
|
color: #00ff88;
|
|
}
|
|
|
|
.history-empty {
|
|
padding: 0.75rem;
|
|
color: #666;
|
|
text-align: center;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Mobile adjustments */
|
|
@media (max-width: 576px) {
|
|
.console-header {
|
|
padding: 0.5rem 0.75rem;
|
|
}
|
|
|
|
.console-header h6 {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.console-messages {
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.console-message {
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.console-input-area {
|
|
padding: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="console-container">
|
|
<!-- Header with status indicator -->
|
|
<div class="console-header d-flex justify-content-between align-items-center">
|
|
<small class="device-name">{{ device_name }}</small>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<span class="status-dot" id="statusDot"></span>
|
|
<small class="text-muted" id="statusText">Connecting...</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Messages Area -->
|
|
<div class="messages-wrap">
|
|
<div class="console-messages" id="consoleMessages">
|
|
<div class="console-message system">Type 'help' for available commands.</div>
|
|
</div>
|
|
<button type="button" class="scroll-bottom-btn" id="scrollBottomBtn" title="Scroll to latest" aria-label="Scroll to latest">
|
|
<i class="bi bi-arrow-down"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Input Area -->
|
|
<div class="console-input-area">
|
|
<form id="consoleForm" class="d-flex gap-2">
|
|
<!-- History dropdown -->
|
|
<div class="history-dropdown">
|
|
<button type="button" class="btn history-btn" id="historyBtn" title="Command history" disabled>
|
|
<i class="bi bi-clock-history"></i>
|
|
</button>
|
|
<div class="history-menu" id="historyMenu">
|
|
<div class="history-empty">No commands in history</div>
|
|
</div>
|
|
</div>
|
|
<!-- Clear output transcript -->
|
|
<button type="button" class="btn history-btn" id="clearOutputBtn" title="Clear output history">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
<input type="text"
|
|
id="commandInput"
|
|
class="form-control console-input"
|
|
placeholder="Enter command..."
|
|
autocomplete="off"
|
|
autocapitalize="off"
|
|
spellcheck="false"
|
|
disabled>
|
|
<button type="submit" class="btn btn-success" id="sendBtn" disabled>
|
|
<i class="bi bi-send"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Socket.IO client -->
|
|
<script src="{{ url_for('static', filename='vendor/socket.io/socket.io.min.js') }}"></script>
|
|
<!-- Bootstrap JS Bundle (local) -->
|
|
<script src="{{ url_for('static', filename='vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
|
|
<!-- Console JS -->
|
|
<script src="{{ url_for('static', filename='js/console.js') }}"></script>
|
|
</body>
|
|
</html>
|