You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

627 lines
20 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hades - {% block title %}{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--hades-bg-primary: linear-gradient(135deg, #1f2937 0%, #374151 50%, #4b5563 100%);
--hades-bg-overlay: rgba(255, 255, 255, 0.98);
--hades-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--hades-shadow-dark: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -2px rgb(0 0 0 / 0.2);
--hades-navbar-bg: rgba(31, 41, 55, 0.95);
--hades-text-light: #f9fafb;
--hades-text-muted: #d1d5db;
}
.Text-Area {
white-space: pre-wrap;
}
.Border-Right-Dotted {
border-right-style: dotted;
border-right: thick black;
}
html, body {
background: var(--hades-bg-primary);
background-attachment: fixed;
height: 1%;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-weight: 400;
color: var(--hades-text-light);
}
/* Add Hades background image - positioned behind everything */
body::after {
content: '';
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 80vh;
background-image: url('/static/hades.png');
background-repeat: no-repeat;
background-position: left bottom;
background-size: auto 80vh;
opacity: 0.5;
z-index: -1;
pointer-events: none;
}
/* Main content area styling */
main {
min-height: calc(100vh - 76px);
}
/* Enhanced navbar styling for dark theme */
.navbar {
backdrop-filter: blur(10px);
background: var(--hades-navbar-bg) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: var(--hades-shadow-dark);
}
.navbar-brand {
font-weight: 600;
font-size: 1.5rem;
color: #dc2626 !important;
}
.navbar-nav .nav-link {
color: var(--hades-text-light) !important;
font-weight: 500;
transition: color 0.2s ease;
}
.navbar-nav .nav-link:hover {
color: #dc2626 !important;
}
.navbar-toggler {
border-color: rgba(255, 255, 255, 0.2);
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='m4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
/* Dropdown menu dark theme */
.dropdown-menu {
background-color: #374151;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: var(--hades-shadow-dark);
}
.dropdown-item {
color: var(--hades-text-light);
transition: all 0.2s ease;
}
.dropdown-item:hover,
.dropdown-item:focus {
background-color: #4b5563;
color: var(--hades-text-light);
}
.dropdown-divider {
border-color: rgba(255, 255, 255, 0.1);
}
/* Card improvements for dark theme */
.card {
backdrop-filter: blur(10px);
background: var(--hades-bg-overlay);
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: var(--hades-shadow-dark);
color: #1f2937;
}
.card-header {
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
/* Table improvements */
.table {
background: var(--hades-bg-overlay);
color: #1f2937;
}
.table-light {
background: rgba(248, 249, 250, 0.98) !important;
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.03);
}
/* Badge improvements */
.badge {
font-weight: 500;
letter-spacing: 0.025em;
}
/* Button improvements */
.btn {
font-weight: 500;
border-radius: 0.5rem;
transition: all 0.2s ease-in-out;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--hades-shadow);
}
/* Search form improvements for dark theme */
.navbar .form-control {
border-radius: 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.9);
color: #1f2937;
}
.navbar .form-control::placeholder {
color: #6b7280;
}
.navbar .form-control:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
background: rgba(255, 255, 255, 0.95);
}
.navbar .btn-outline-primary {
border-color: #3b82f6;
color: #3b82f6;
}
.navbar .btn-outline-primary:hover {
background-color: #3b82f6;
border-color: #3b82f6;
color: white;
}
/* Alert styling for dark theme */
.alert {
border-radius: 0.5rem;
box-shadow: var(--hades-shadow);
}
/* Modal improvements for dark theme */
.modal-content {
box-shadow: var(--hades-shadow-dark);
}
/* Responsive typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
letter-spacing: -0.025em;
}
/* Loading animation for better UX */
.loading {
opacity: 0.7;
pointer-events: none;
}
/* Better focus states for accessibility - FIXED */
.btn:focus,
.btn:focus-visible {
outline: 2px solid rgba(59, 130, 246, 0.5);
outline-offset: 2px;
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
.btn-primary:focus,
.btn-primary:focus-visible {
outline: 2px solid rgba(59, 130, 246, 0.7);
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
.btn-outline-primary:focus,
.btn-outline-primary:focus-visible {
outline: 2px solid rgba(59, 130, 246, 0.7);
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
}
.btn-success:focus,
.btn-success:focus-visible,
.btn-outline-success:focus,
.btn-outline-success:focus-visible {
outline: 2px solid rgba(34, 197, 94, 0.7);
box-shadow: 0 0 0 0.2rem rgba(34, 197, 94, 0.25);
}
.btn-warning:focus,
.btn-warning:focus-visible,
.btn-outline-warning:focus,
.btn-outline-warning:focus-visible {
outline: 2px solid rgba(245, 158, 11, 0.7);
box-shadow: 0 0 0 0.2rem rgba(245, 158, 11, 0.25);
}
.btn-danger:focus,
.btn-danger:focus-visible,
.btn-outline-danger:focus,
.btn-outline-danger:focus-visible {
outline: 2px solid rgba(239, 68, 68, 0.7);
box-shadow: 0 0 0 0.2rem rgba(239, 68, 68, 0.25);
}
.btn-secondary:focus,
.btn-secondary:focus-visible,
.btn-outline-secondary:focus,
.btn-outline-secondary:focus-visible {
outline: 2px solid rgba(107, 114, 128, 0.7);
box-shadow: 0 0 0 0.2rem rgba(107, 114, 128, 0.25);
}
.btn-info:focus,
.btn-info:focus-visible,
.btn-outline-info:focus,
.btn-outline-info:focus-visible {
outline: 2px solid rgba(6, 182, 212, 0.7);
box-shadow: 0 0 0 0.2rem rgba(6, 182, 212, 0.25);
}
.form-control:focus,
.nav-link:focus {
outline: 2px solid rgba(59, 130, 246, 0.7);
outline-offset: 2px;
}
/* Custom scrollbar for webkit browsers - dark theme */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.4);
}
/* Toast container styling for dark theme */
.toast {
background-color: #374151;
color: var(--hades-text-light);
}
.toast-header {
background-color: #4b5563;
color: var(--hades-text-light);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
/* Ensure text in cards remains dark and readable */
.card .text-muted {
color: #6b7280 !important;
}
.card .text-dark {
color: #1f2937 !important;
}
/* Input group button styling */
.input-group .btn-outline-primary {
border-color: #3b82f6;
color: #3b82f6;
}
.input-group .btn-outline-primary:hover {
background-color: #3b82f6;
border-color: #3b82f6;
color: white;
}
/* Responsive adjustments for Hades background */
@media (max-width: 768px) {
body::after {
background-size: 50vh auto;
opacity: 0.3;
}
}
@media (max-width: 576px) {
body::after {
background-size: 40vh auto;
opacity: 0.2;
}
}
.tooltip-custom {
cursor: help;
border-bottom: 1px dotted #6c757d;
position: relative;
}
/* JavaScript-powered tooltip positioning */
.tooltip-js {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
z-index: 10000;
max-width: 250px;
text-align: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
}
.tooltip-js.show {
opacity: 1;
}
.tooltip-js::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: rgba(0, 0, 0, 0.9);
}
/* Ensure cards don't interfere with tooltips */
.card {
position: relative;
z-index: 1;
}
.card:hover {
z-index: 2;
}
{% block styles %}
{% endblock %}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg sticky-top">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('main.index') }}">
<i class="bi bi-router me-2"></i>Hades
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarScroll" aria-controls="navbarScroll" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarScroll">
<ul class="navbar-nav me-auto my-2 my-lg-0">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.index') }}">
<i class="bi bi-house me-1"></i>Home
</a>
</li>
</ul>
<!-- Search Form with Tooltip -->
<form class="d-flex me-3" role="search" method="POST" action="/search/results">
<div class="input-group">
<input class="form-control tooltip-custom"
type="search"
placeholder="Search..."
aria-label="Search"
name="to_search"
data-tooltip="Can search for INTs, IVCs, ONT Serials, Beacon Serials/MACs">
<button class="btn btn-outline-primary" type="submit">
<i class="bi bi-search"></i>
</button>
</div>
</form>
<!-- Profile Dropdown -->
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle me-1"></i>Profile
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{{ url_for('auth.changepassword') }}">
<i class="bi bi-key me-2"></i>Change Password
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{{ url_for('auth.logout') }}">
<i class="bi bi-box-arrow-right me-2"></i>Logout
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main>
{% block content %}
{% endblock %}
</main>
<!-- Toast container for notifications -->
<div class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index: 1200;">
<!-- Toasts will be dynamically added here -->
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script>
// Enhanced UX improvements
document.addEventListener('DOMContentLoaded', function() {
// Add loading states to forms
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function() {
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.innerHTML = '<i class="bi bi-arrow-clockwise spin me-1"></i>Loading...';
submitBtn.disabled = true;
}
});
});
// Auto-hide alerts after 5 seconds
const alerts = document.querySelectorAll('.alert-dismissible');
alerts.forEach(alert => {
setTimeout(() => {
const closeBtn = alert.querySelector('.btn-close');
if (closeBtn) closeBtn.click();
}, 5000);
});
// Add spinning animation for loading states
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spin { animation: spin 1s linear infinite; }
`;
document.head.appendChild(style);
});
</script>
<!-- Enhanced tooltip JavaScript with search-specific functionality -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Enhanced tooltip positioning
const tooltipElements = document.querySelectorAll('.tooltip-custom');
let activeTooltip = null;
tooltipElements.forEach(element => {
element.addEventListener('mouseenter', function(e) {
// Remove any existing tooltip
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
// Create tooltip element
const tooltip = document.createElement('div');
tooltip.className = 'tooltip-js';
tooltip.textContent = this.getAttribute('data-tooltip');
document.body.appendChild(tooltip);
// Get positions including scroll offset
const rect = this.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
// Force tooltip to render to get dimensions
tooltip.style.visibility = 'hidden';
tooltip.style.display = 'block';
const tooltipRect = tooltip.getBoundingClientRect();
tooltip.style.visibility = '';
tooltip.style.display = '';
// Calculate position relative to document
let left = rect.left + scrollLeft + (rect.width / 2) - (tooltipRect.width / 2);
let top = rect.top + scrollTop - tooltipRect.height - 8;
// Adjust if tooltip goes off-screen horizontally
if (left < 10) {
left = 10;
}
if (left + tooltipRect.width > window.innerWidth - 10) {
left = window.innerWidth - tooltipRect.width - 10;
}
// Adjust if tooltip goes off-screen vertically (show below instead)
if (top < scrollTop + 10) {
top = rect.bottom + scrollTop + 8;
// Update arrow direction for bottom positioning
tooltip.innerHTML = this.getAttribute('data-tooltip');
tooltip.style.setProperty('--arrow-direction', 'up');
// Add upward arrow
const arrow = document.createElement('div');
arrow.style.cssText = `
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.9);
`;
tooltip.appendChild(arrow);
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
// Show tooltip with smooth transition
setTimeout(() => tooltip.classList.add('show'), 10);
activeTooltip = tooltip;
});
element.addEventListener('mouseleave', function() {
if (activeTooltip) {
activeTooltip.classList.remove('show');
setTimeout(() => {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}, 200); // Match the CSS transition duration
}
});
// Also hide tooltip when input gets focus (user starts typing)
if (element.tagName.toLowerCase() === 'input') {
element.addEventListener('focus', function() {
if (activeTooltip) {
activeTooltip.classList.remove('show');
setTimeout(() => {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}, 200);
}
});
}
});
// Clean up tooltips when scrolling or clicking
window.addEventListener('scroll', function() {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
});
document.addEventListener('click', function() {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
});
});
</script>
{% block scripts %}
{% endblock %}
</body>
</html>