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
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>
|