mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
Split Contact Management into 3 dedicated pages for improved mobile usability: - /contacts/manage - Settings & navigation hub (manual approval + cleanup) - /contacts/pending - Full-screen pending contacts view - /contacts/existing - Full-screen existing contacts with search/filter/sort New Features: - Advanced sorting: Name (A-Z/Z-A) & Last advert (newest/oldest) - URL-based sort state (?sort=name&order=asc) - Activity indicators: 🟢 active, 🟡 recent, 🔴 inactive - Changed terminology: "Last seen" → "Last advert" (more accurate) - Cleanup tool moved from Settings modal to Contact Management page Technical Changes: - Created contacts_base.html standalone template - Split contacts.html into 3 specialized templates - Refactored contacts.js for multi-page support with page detection - Added 2 new Flask routes: /contacts/pending, /contacts/existing - Removed cleanup section from base.html Settings modal Mobile-first design: Each page has full-screen space with touch-friendly navigation and back buttons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
276 lines
14 KiB
HTML
276 lines
14 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>{% block title %}mc-webui{% endblock %}</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 -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<!-- Bootstrap Icons -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css">
|
|
|
|
<!-- Custom CSS -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
<!-- Navbar -->
|
|
<nav class="navbar navbar-dark bg-primary">
|
|
<div class="container-fluid">
|
|
<span class="navbar-brand mb-0 h1">
|
|
<i class="bi bi-broadcast"></i> mc-webui
|
|
{% if device_name %}
|
|
<small class="text-white-50 d-none d-sm-inline">- {{ device_name }}</small>
|
|
{% endif %}
|
|
</span>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div id="notificationBell" class="btn btn-outline-light btn-sm position-relative" style="cursor: default;" title="Unread messages">
|
|
<i class="bi bi-bell"></i>
|
|
</div>
|
|
<select id="channelSelector" class="form-select form-select-sm" style="width: auto; min-width: 100px;" title="Select channel">
|
|
<option value="0">Public</option>
|
|
<!-- Channels loaded dynamically via JavaScript -->
|
|
</select>
|
|
<button class="btn btn-outline-light btn-sm" data-bs-toggle="offcanvas" data-bs-target="#mainMenu" title="Menu">
|
|
<i class="bi bi-list" style="font-size: 1.25rem;"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Offcanvas Menu -->
|
|
<div class="offcanvas offcanvas-end" tabindex="-1" id="mainMenu">
|
|
<div class="offcanvas-header">
|
|
<h5 class="offcanvas-title"><i class="bi bi-menu-button-wide"></i> Menu</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<div class="list-group list-group-flush">
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" id="refreshBtn">
|
|
<i class="bi bi-arrow-clockwise" style="font-size: 1.5rem;"></i>
|
|
<span>Refresh Messages</span>
|
|
</button>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#channelsModal" data-bs-dismiss="offcanvas">
|
|
<i class="bi bi-broadcast-pin" style="font-size: 1.5rem;"></i>
|
|
<span>Manage Channels</span>
|
|
</button>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" onclick="window.location.href='/dm';">
|
|
<i class="bi bi-envelope" style="font-size: 1.5rem;"></i>
|
|
<div class="d-flex flex-grow-1 justify-content-between align-items-center">
|
|
<span>Direct Messages</span>
|
|
<span id="dmMenuBadge" class="badge bg-success rounded-pill" style="display: none;">0</span>
|
|
</div>
|
|
</button>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" onclick="window.location.href='/contacts/manage';">
|
|
<i class="bi bi-person-check" style="font-size: 1.5rem;"></i>
|
|
<span>Contact Management</span>
|
|
</button>
|
|
<div class="list-group-item">
|
|
<div class="d-flex align-items-center gap-3 mb-2">
|
|
<i class="bi bi-calendar3" style="font-size: 1.5rem;"></i>
|
|
<label for="dateSelector" class="form-label mb-0">Message History</label>
|
|
</div>
|
|
<select id="dateSelector" class="form-select" title="Select date">
|
|
<option value="">Today (Live)</option>
|
|
<!-- Archive dates loaded dynamically via JavaScript -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Network Commands Section -->
|
|
<div class="list-group-item py-2 mt-2">
|
|
<small class="text-muted fw-bold text-uppercase">Network Commands</small>
|
|
</div>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" id="advertBtn" title="Send single advertisement (recommended for normal operation)">
|
|
<i class="bi bi-megaphone" style="font-size: 1.5rem;"></i>
|
|
<div>
|
|
<span>Send Advert</span>
|
|
<small class="d-block text-muted">Announce presence (normal)</small>
|
|
</div>
|
|
</button>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3 text-warning" id="floodadvBtn" title="Flood advertisement - use sparingly! High airtime usage.">
|
|
<i class="bi bi-broadcast" style="font-size: 1.5rem;"></i>
|
|
<div>
|
|
<span>Flood Advert</span>
|
|
<small class="d-block text-muted">Network recovery only!</small>
|
|
</div>
|
|
</button>
|
|
|
|
<div class="list-group-item py-2 mt-2">
|
|
<small class="text-muted fw-bold text-uppercase">Configuration</small>
|
|
</div>
|
|
<button class="list-group-item list-group-item-action d-flex align-items-center gap-3" data-bs-toggle="modal" data-bs-target="#settingsModal" data-bs-dismiss="offcanvas">
|
|
<i class="bi bi-gear" style="font-size: 1.5rem;"></i>
|
|
<span>Settings</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<main>
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<!-- Channels Management Modal -->
|
|
<div class="modal fade" id="channelsModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="bi bi-broadcast-pin"></i> Manage Channels</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Channel List -->
|
|
<h6>Your Channels</h6>
|
|
<div id="channelsList" class="list-group mb-3">
|
|
<div class="text-center text-muted py-3">
|
|
<div class="spinner-border spinner-border-sm"></div> Loading...
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="btn-group w-100 mb-3" role="group">
|
|
<button type="button" class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#addChannelForm">
|
|
<i class="bi bi-plus-circle"></i> Add New Channel
|
|
</button>
|
|
<button type="button" class="btn btn-success" data-bs-toggle="collapse" data-bs-target="#joinChannelForm">
|
|
<i class="bi bi-box-arrow-in-right"></i> Join Existing
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Add Channel Form (collapsed) -->
|
|
<div class="collapse" id="addChannelForm">
|
|
<div class="card card-body mb-3">
|
|
<h6>Create New Channel</h6>
|
|
<form id="createChannelForm">
|
|
<div class="mb-2">
|
|
<label for="newChannelName" class="form-label">Channel Name</label>
|
|
<input type="text" class="form-control" id="newChannelName"
|
|
placeholder="e.g., Malopolska"
|
|
pattern="[a-zA-Z0-9_\-]+"
|
|
required>
|
|
<small class="text-muted">Only letters, numbers, _ and -</small>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-plus-circle"></i> Create & Auto-generate Key
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Join Channel Form (collapsed) -->
|
|
<div class="collapse" id="joinChannelForm">
|
|
<div class="card card-body mb-3">
|
|
<h6>Join Existing Channel</h6>
|
|
<form id="joinChannelFormSubmit">
|
|
<div class="mb-2">
|
|
<label for="joinChannelName" class="form-label">Channel Name</label>
|
|
<input type="text" class="form-control" id="joinChannelName" required>
|
|
</div>
|
|
<div class="mb-2">
|
|
<label for="joinChannelKey" class="form-label">Channel Key (32 hex chars) <small class="text-muted">- optional for channels starting with #</small></label>
|
|
<input type="text" class="form-control" id="joinChannelKey"
|
|
placeholder="485af7e164459d280d8818d9c99fb30d (leave empty for # channels)"
|
|
pattern="[a-fA-F0-9]{32}"
|
|
maxlength="32">
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-success">
|
|
<i class="bi bi-box-arrow-in-right"></i> Join Channel
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="scanQRBtn">
|
|
<i class="bi bi-qr-code-scan"></i> Scan QR Code
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Share Channel Modal -->
|
|
<div class="modal fade" id="shareChannelModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="bi bi-share"></i> Share Channel</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<h6 id="shareChannelName"></h6>
|
|
|
|
<!-- QR Code -->
|
|
<div class="text-center mb-3">
|
|
<img id="shareChannelQR" src="" alt="QR Code" class="img-fluid" style="max-width: 300px;">
|
|
</div>
|
|
|
|
<!-- Channel Details -->
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">Channel Key:</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control font-monospace" id="shareChannelKey" readonly>
|
|
<button class="btn btn-outline-secondary" type="button" onclick="copyChannelKey()">
|
|
<i class="bi bi-clipboard"></i> Copy
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info small">
|
|
<i class="bi bi-info-circle"></i>
|
|
Share this QR code or key with others to let them join this channel.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modal -->
|
|
<div class="modal fade" id="settingsModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="bi bi-gear"></i> Settings</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<h6>Device Information</h6>
|
|
<div id="deviceInfo" class="small text-muted">
|
|
Loading...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast container for notifications -->
|
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
<div id="notificationToast" class="toast" role="alert">
|
|
<div class="toast-header">
|
|
<strong class="me-auto">mc-webui</strong>
|
|
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
<div class="toast-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bootstrap 5 JS Bundle -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- Custom JS -->
|
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
|
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|