155 lines
7.0 KiB
HTML
155 lines
7.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{% block title %}{{ server_name }} Dashboard{% endblock %}</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', path='/css/bootstrap.min.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}">
|
|
</head>
|
|
<body class="bg-light">
|
|
<nav class="navbar navbar-dark bg-primary mb-4 py-3"> <!-- Added py-3 for a bit more vertical padding -->
|
|
<div class="container-fluid">
|
|
<!-- Server name on its own "line" (centered, larger, prominent) -->
|
|
<div class="w-100 text-center mb-3">
|
|
<a class="navbar-brand h2 mb-0" href="{{ url_for('dashboard') }}">{{ server_name }}</a>
|
|
</div>
|
|
|
|
<!-- Bottom row: user info and navigation buttons -->
|
|
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between w-100 gap-2">
|
|
<span class="navbar-text text-center text-md-start order-md-1">
|
|
Logged in as: <strong>{{ current_user }}</strong>
|
|
<small class="text-light ms-3">(Close browser to logout)</small>
|
|
</span>
|
|
|
|
<div class="order-md-3">
|
|
<a href="{{ url_for('profile_page') }}" class="btn btn-outline-light btn-sm me-2">Profile</a>
|
|
<a href="/messages" class="btn btn-outline-light btn-sm me-2">Messages</a>
|
|
<a href="/bulletins" class="btn btn-outline-light btn-sm me-2">Bulletins</a>
|
|
<a href="/objects" class="btn btn-outline-light btn-sm me-2">Objects</a>
|
|
<a href="/jobs" class="btn btn-outline-light btn-sm me-2">Jobs</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<script src="{{ url_for('static', path='/js/bootstrap.bundle.min.js') }}"></script>
|
|
{% block scripts %}{% endblock %}
|
|
|
|
<!-- Compose Message Modal -->
|
|
<div class="modal fade" id="composeModal" tabindex="-1" aria-labelledby="composeModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<form id="composeForm">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="composeModalLabel">Compose New Message</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="to_call" class="form-label">To (comma-separated callsigns, or ALL for bulletin)</label>
|
|
<input type="text" class="form-control" id="to_call" name="to_call" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="message_text" class="form-label">Message</label>
|
|
<textarea class="form-control" id="message_text" name="text" rows="8" required></textarea>
|
|
</div>
|
|
<div id="composeStatus" class="alert" role="alert" style="display:none;"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Send Message</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const composeForm = document.getElementById('composeForm');
|
|
if (composeForm) {
|
|
composeForm.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const status = document.getElementById('composeStatus');
|
|
status.style.display = 'none';
|
|
status.className = 'alert';
|
|
|
|
const toInput = document.getElementById('to_call').value.trim(); // keep ID for compatibility
|
|
const text = document.getElementById('message_text').value.trim();
|
|
|
|
if (!toInput || !text) {
|
|
status.className = 'alert alert-danger';
|
|
status.textContent = 'To and Message fields are required.';
|
|
status.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
// Split comma-separated, strip, uppercase, filter empty
|
|
const toList = toInput.split(',')
|
|
.map(c => c.trim().toUpperCase())
|
|
.filter(c => c.length > 0);
|
|
|
|
if (toList.length === 0) {
|
|
status.className = 'alert alert-danger';
|
|
status.textContent = 'At least one valid callsign required.';
|
|
status.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/v1/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
to: toList, // <-- array of callsigns
|
|
text: text // <-- body
|
|
}),
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
let msg = 'Message sent successfully!';
|
|
if (result.warning) {
|
|
msg += ' ' + result.warning;
|
|
}
|
|
status.className = 'alert alert-success';
|
|
status.textContent = msg;
|
|
status.style.display = 'block';
|
|
composeForm.reset();
|
|
setTimeout(() => {
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('composeModal'));
|
|
if (modal) modal.hide();
|
|
}, 1200);
|
|
setTimeout(() => location.reload(), 1700);
|
|
} else {
|
|
const errorData = await response.json();
|
|
let errorMsg = 'Failed to send message';
|
|
if (errorData.detail) {
|
|
if (Array.isArray(errorData.detail)) {
|
|
errorMsg = errorData.detail.map(d => d.msg || d).join('; ');
|
|
} else {
|
|
errorMsg = errorData.detail;
|
|
}
|
|
}
|
|
status.className = 'alert alert-danger';
|
|
status.textContent = errorMsg;
|
|
status.style.display = 'block';
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
status.className = 'alert alert-danger';
|
|
status.textContent = 'Network error';
|
|
status.style.display = 'block';
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |