mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-11-05 18:22:21 +01:00
Covers app/templates/dm.html and app/static/js/dm.js: the sidebar, the searchable conversation selector, the composer, message bubbles and their delivery meta, the Contact Info modal, path management, and the repeater list/map pickers. The DM panel's path management and repeater pickers are the code the My Repeaters panel was adapted from, so its strings reuse the repeaters.* keys added in stage 3a rather than duplicating them. Shared chat chrome (filter bar, FAB titles, composer placeholder, status bar) goes to a new chat.* namespace so index.html and app.js can reuse it in stages 7 and 9, and console.status.* is promoted to common.connected/disconnected/ connecting now that two status bars want the same three words. formatRelativeTimeDm() was the last of the four local relative-time implementations; it is gone, folded into formatTimeAgo() from datetime-utils.js. The contact-info row uses the long form to match the contacts page, the map popup the short form to match My Repeaters. Also fixed along the way, both found by grepping this slice's shared strings against the already-translated panels: - repeaters.js reset the map picker label from JS in three places with a hardcoded English string, so the header translated on load and flipped back to English the moment the picker opened (missed in stage 3a). - tHtml() on a plural key always renders the "one" form, because only tn() passes the count to the resolver. That made the repeater picker's shared-prefix tooltip read "3 repeater ma ten prefiks". Fixed in both callers and now an error in scripts/i18n_check.py, which had no way to catch it. Left English on purpose: Flood/Direct/FLOOD and the SNR labels (glossary), and the 'Device path' label written to the DB on import - it is stored data, so translating it would freeze one language into the row. Catalog: 471 -> 526 keys, pl at 100%. Verified against the local container with i18n-diff.js plus a new i18n-stage6-dm.js that drives the panel through each modal, since the diff tool cannot see hidden ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
367 lines
11 KiB
HTML
367 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="{{ lang }}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<title>{{ t('console.title') }} - mc-webui</title>
|
|
|
|
{% include "_head_i18n.html" %}
|
|
|
|
<!-- 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">{{ t('common.connecting') }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Messages Area -->
|
|
<div class="messages-wrap">
|
|
<div class="console-messages" id="consoleMessages">
|
|
{# 'help' is the literal command name — never translated. #}
|
|
<div class="console-message system">{{ t('console.help_hint') }}</div>
|
|
</div>
|
|
<button type="button" class="scroll-bottom-btn" id="scrollBottomBtn"
|
|
title="{{ t('console.scroll_bottom_title') }}" aria-label="{{ t('console.scroll_bottom_aria') }}">
|
|
<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="{{ t('console.history_title') }}" disabled>
|
|
<i class="bi bi-clock-history"></i>
|
|
</button>
|
|
<div class="history-menu" id="historyMenu">
|
|
<div class="history-empty">{{ t('console.history_empty') }}</div>
|
|
</div>
|
|
</div>
|
|
<!-- Clear output transcript -->
|
|
<button type="button" class="btn history-btn" id="clearOutputBtn" title="{{ t('console.clear_title') }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
<input type="text"
|
|
id="commandInput"
|
|
class="form-control console-input"
|
|
placeholder="{{ t('console.input_ph') }}"
|
|
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>
|