mirror of
https://github.com/Sudo-JHare/FHIRFLARE-IG-Toolkit.git
synced 2025-06-14 12:10:03 +00:00
478 lines
20 KiB
HTML
478 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>FHIRPAD - FHIR App Platform - {% block title %}{% endblock %}</title>
|
|
<link href="{{ url_for('static', filename='bootstrap.min.css') }}" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
|
<style>
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background-color: #f8f9fa;
|
|
color: #212529;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
margin: 0;
|
|
}
|
|
main {
|
|
flex-grow: 1;
|
|
}
|
|
h1, h5 {
|
|
font-weight: 600;
|
|
}
|
|
.navbar-light {
|
|
background-color: #ffffff !important;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
.navbar-brand {
|
|
font-weight: bold;
|
|
color: #007bff !important;
|
|
}
|
|
.nav-link {
|
|
color: #333 !important;
|
|
transition: color 0.2s ease;
|
|
}
|
|
.nav-link:hover {
|
|
color: #007bff !important;
|
|
}
|
|
.nav-link.active {
|
|
color: #007bff !important;
|
|
font-weight: bold;
|
|
border-bottom: 2px solid #007bff;
|
|
}
|
|
.dropdown-menu {
|
|
list-style: none !important;
|
|
position: absolute;
|
|
background-color: #ffffff;
|
|
border: none;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
border-radius: 8px;
|
|
padding: 0;
|
|
min-width: 160px;
|
|
z-index: 1000;
|
|
}
|
|
.dropdown-item {
|
|
padding: 0.5rem 1rem;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
.dropdown-item:hover {
|
|
background-color: #e9ecef;
|
|
}
|
|
.dropdown-item.active {
|
|
background-color: #007bff;
|
|
color: white !important;
|
|
}
|
|
.app-card {
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
background-color: #ffffff;
|
|
color: #212529;
|
|
}
|
|
.app-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15) !important;
|
|
}
|
|
.card-img-top {
|
|
background-color: #f8f9fa;
|
|
}
|
|
.card {
|
|
background-color: #ffffff;
|
|
}
|
|
.text-muted {
|
|
color: #6c757d !important;
|
|
}
|
|
.form-check-input:checked {
|
|
background-color: #007bff;
|
|
border-color: #007bff;
|
|
}
|
|
.form-check-label i {
|
|
font-size: 1.2rem;
|
|
margin-left: 0.5rem;
|
|
}
|
|
.navbar-controls {
|
|
gap: 0.5rem;
|
|
padding-right: 0;
|
|
}
|
|
.navbar-search input,
|
|
.view-toggle {
|
|
height: 38px;
|
|
}
|
|
.navbar-search {
|
|
width: 200px;
|
|
}
|
|
.nav-link.login-link {
|
|
line-height: 38px;
|
|
padding: 0 0.5rem;
|
|
}
|
|
footer {
|
|
background-color: #ffffff;
|
|
height: 56px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0.5rem 1rem;
|
|
border-top: 1px solid #e0e0e0;
|
|
}
|
|
.footer-left,
|
|
.footer-right {
|
|
display: inline-flex;
|
|
gap: 0.75rem;
|
|
align-items: center;
|
|
}
|
|
.footer-left a,
|
|
.footer-right a {
|
|
color: #007bff;
|
|
text-decoration: none;
|
|
font-size: 0.85rem;
|
|
}
|
|
.footer-left a:hover,
|
|
.footer-right a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
.initials-avatar {
|
|
display: inline-block;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
background-color: #007bff;
|
|
color: white;
|
|
text-align: center;
|
|
line-height: 30px;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
vertical-align: middle;
|
|
}
|
|
.user-avatar {
|
|
display: inline-block;
|
|
border-radius: 50%;
|
|
vertical-align: middle;
|
|
}
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
html[data-theme="dark"] body {
|
|
background-color: #212529;
|
|
color: #f8f9fa;
|
|
}
|
|
html[data-theme="dark"] .navbar-light {
|
|
background-color: #343a40 !important;
|
|
border-bottom: 1px solid #495057;
|
|
}
|
|
html[data-theme="dark"] .navbar-brand {
|
|
color: #4dabf7 !important;
|
|
}
|
|
html[data-theme="dark"] .nav-link {
|
|
color: #f8f9fa !important;
|
|
}
|
|
html[data-theme="dark"] .nav-link:hover,
|
|
html[data-theme="dark"] .nav-link.active {
|
|
color: #4dabf7 !important;
|
|
border-bottom-color: #4dabf7;
|
|
}
|
|
html[data-theme="dark"] .dropdown-menu {
|
|
background-color: #495057;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
}
|
|
html[data-theme="dark"] .dropdown-item {
|
|
color: #f8f9fa;
|
|
}
|
|
html[data-theme="dark"] .dropdown-item:hover {
|
|
background-color: #6c757d;
|
|
}
|
|
html[data-theme="dark"] .dropdown-item.active {
|
|
background-color: #4dabf7;
|
|
color: white !important;
|
|
}
|
|
html[data-theme="dark"] .app-card {
|
|
background-color: #343a40;
|
|
color: #f8f9fa;
|
|
border: 1px solid #495057;
|
|
}
|
|
html[data-theme="dark"] .card {
|
|
background-color: #343a40;
|
|
border: 1px solid #495057;
|
|
}
|
|
html[data-theme="dark"] .card-img-top {
|
|
background-color: #495057;
|
|
}
|
|
html[data-theme="dark"] .text-muted {
|
|
color: #adb5bd !important;
|
|
}
|
|
html[data-theme="dark"] h1,
|
|
html[data-theme="dark"] h5 {
|
|
color: #f8f9fa;
|
|
}
|
|
html[data-theme="dark"] .form-label {
|
|
color: #f8f9fa;
|
|
}
|
|
html[data-theme="dark"] .form-control {
|
|
background-color: #495057;
|
|
color: #f8f9fa;
|
|
border-color: #6c757d;
|
|
}
|
|
html[data-theme="dark"] .form-control::placeholder {
|
|
color: #adb5bd;
|
|
}
|
|
html[data-theme="dark"] .form-select {
|
|
background-color: #495057;
|
|
color: #f8f9fa;
|
|
border-color: #6c757d;
|
|
}
|
|
html[data-theme="dark"] .alert-danger {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
border-color: #dc3545;
|
|
}
|
|
html[data-theme="dark"] .alert-success {
|
|
background-color: #198754;
|
|
color: white;
|
|
border-color: #198754;
|
|
}
|
|
html[data-theme="dark"] .form-check-input:checked {
|
|
background-color: #4dabf7;
|
|
border-color: #4dabf7;
|
|
}
|
|
html[data-theme="dark"] footer {
|
|
background-color: #343a40;
|
|
border-top: 1px solid #495057;
|
|
}
|
|
html[data-theme="dark"] .footer-left a,
|
|
html[data-theme="dark"] .footer-right a {
|
|
color: #4dabf7;
|
|
}
|
|
@media (max-width: 576px) {
|
|
.navbar-controls {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
width: 100%;
|
|
}
|
|
.navbar-search {
|
|
width: 100%;
|
|
}
|
|
.navbar-search input,
|
|
.view-toggle {
|
|
width: 100%;
|
|
}
|
|
.navbar-controls .nav-link,
|
|
.navbar-controls .form-check,
|
|
.navbar-controls .dropdown {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
footer {
|
|
height: auto;
|
|
padding: 0.5rem;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
.footer-left,
|
|
.footer-right {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 0.5rem;
|
|
}
|
|
.footer-left a,
|
|
.footer-right a {
|
|
font-size: 0.8rem;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav class="navbar navbar-expand-lg navbar-light">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="{{ url_for('gallery.landing') }}">FHIRPAD - FHIR App Platform</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse d-flex justify-content-between" id="navbarNav">
|
|
<ul class="navbar-nav">
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.landing' %}active{% endif %}" href="{{ url_for('gallery.landing') }}">Home</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.gallery' %}active{% endif %}" href="{{ url_for('gallery.gallery') }}">Gallery</a>
|
|
</li>
|
|
{% if current_user.is_authenticated %}
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.register' %}active{% endif %}" href="{{ url_for('gallery.register') }}">Register App</a>
|
|
</li>
|
|
{% if current_user.is_admin %}
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.manage_categories' %}active{% endif %}" href="{{ url_for('gallery.manage_categories') }}">Manage Categories</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.admin_apps' %}active{% endif %}" href="{{ url_for('gallery.admin_apps') }}">Manage Apps</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link {% if request.endpoint == 'gallery.admin_users' %}active{% endif %}" href="{{ url_for('gallery.admin_users') }}">Manage Users</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endif %}
|
|
</ul>
|
|
<div class="navbar-controls d-flex align-items-center ms-auto">
|
|
{% if request.endpoint == 'gallery.gallery' %}
|
|
<form class="navbar-search d-flex" method="GET" action="{{ url_for('gallery.gallery') }}">
|
|
<input class="form-control form-control-sm me-2" type="search" name="search" placeholder="Search apps..." aria-label="Search apps by name, description, or developer" value="{{ request.args.get('search', '') }}">
|
|
<button class="btn btn-outline-primary btn-sm" type="submit"><i class="fas fa-search"></i></button>
|
|
</form>
|
|
<button class="btn btn-outline-secondary btn-sm view-toggle" id="viewToggle" onclick="toggleView()" aria-label="Toggle between grid and list view">
|
|
<i class="fas fa-th"></i> <span id="viewText">Grid View</span>
|
|
</button>
|
|
{% endif %}
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="themeToggle" onchange="toggleTheme()" aria-label="Toggle dark mode" {% if request.cookies.get('theme') == 'dark' %}checked{% endif %}>
|
|
<label class="form-check-label" for="themeToggle">
|
|
<i class="fas fa-sun"></i>
|
|
<i class="fas fa-moon d-none"></i>
|
|
</label>
|
|
</div>
|
|
{% if current_user.is_authenticated and current_user.username %}
|
|
<div class="dropdown">
|
|
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
{% if current_user.email %}
|
|
<img src="https://www.gravatar.com/avatar/{{ current_user.email | md5 }}?s=30&d=identicon" class="user-avatar" alt="User Avatar" onerror="this.style.display='none';this.nextElementSibling.style.display='inline-block';">
|
|
{% endif %}
|
|
<span class="initials-avatar" style="{% if current_user.email %}display: none;{% endif %}">{{ current_user.username[:2] | upper }}</span>
|
|
{{ current_user.username }}
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
|
|
<li><a class="dropdown-item {% if request.endpoint == 'gallery.my_listings' %}active{% endif %}" href="{{ url_for('gallery.my_listings') }}">My Listings</a></li>
|
|
{% if current_user.force_password_change %}
|
|
<li><a class="dropdown-item {% if request.endpoint == 'auth.change_password' %}active{% endif %}" href="{{ url_for('auth.change_password') }}">Change Password</a></li>
|
|
{% endif %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{{ url_for('auth.logout') }}">Logout</a></li>
|
|
</ul>
|
|
</div>
|
|
{% else %}
|
|
<a class="nav-link login-link {% if request.endpoint == 'auth.login' %}active{% endif %}" href="{{ url_for('auth.login') }}">Login</a>
|
|
<a class="nav-link login-link {% if request.endpoint == 'auth.register' %}active{% endif %}" href="{{ url_for('auth.register') }}">Register</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<main class="flex-grow-1">
|
|
<div class="container mt-4">
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</main>
|
|
<footer class="main-footer" role="contentinfo">
|
|
<div class="container-fluid">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="footer-left">
|
|
<a href="https://www.hl7.org/fhir/" target="_blank" rel="noreferrer" aria-label="Visit FHIR website">FHIR</a>
|
|
<a href="/about" aria-label="Learn about FHIRPAD">What is FHIRPAD</a>
|
|
<a href="mailto:support@fhirpad.com" aria-label="Contact FHIRPAD support">Contact Us</a>
|
|
</div>
|
|
<div class="footer-right">
|
|
<a href="/documents/privacy.pdf" aria-label="View Privacy Policy">Privacy</a>
|
|
<a href="/documents/terms.pdf" aria-label="View Terms of Use">Terms of Use</a>
|
|
<a href="/documents/disclaimer.pdf" aria-label="View Disclaimer">Disclaimer</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
<script src="{{ url_for('static', filename='bootstrap.bundle.min.js') }}"></script>
|
|
<script>
|
|
function toggleTheme() {
|
|
const html = document.documentElement;
|
|
const toggle = document.getElementById('themeToggle');
|
|
const sunIcon = document.querySelector('.fa-sun');
|
|
const moonIcon = document.querySelector('.fa-moon');
|
|
if (toggle.checked) {
|
|
html.setAttribute('data-theme', 'dark');
|
|
localStorage.setItem('theme', 'dark');
|
|
sunIcon.classList.add('d-none');
|
|
moonIcon.classList.remove('d-none');
|
|
} else {
|
|
html.removeAttribute('data-theme');
|
|
localStorage.setItem('theme', 'light');
|
|
sunIcon.classList.remove('d-none');
|
|
moonIcon.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function toggleView() {
|
|
const container = document.getElementById('appContainer');
|
|
const table = document.getElementById('appTable');
|
|
const toggle = document.getElementById('viewToggle');
|
|
const viewText = document.getElementById('viewText');
|
|
if (!container || !table) return;
|
|
if (container.classList.contains('d-none')) {
|
|
container.classList.remove('d-none');
|
|
table.classList.add('d-none');
|
|
viewText.textContent = 'Grid View';
|
|
toggle.querySelector('i').className = 'fas fa-th';
|
|
localStorage.setItem('view', 'grid');
|
|
} else {
|
|
container.classList.add('d-none');
|
|
table.classList.remove('d-none');
|
|
viewText.textContent = 'List View';
|
|
toggle.querySelector('i').className = 'fas fa-list';
|
|
localStorage.setItem('view', 'list');
|
|
}
|
|
}
|
|
|
|
function removeFilter(key, value) {
|
|
const url = new URL(window.location);
|
|
const params = new URLSearchParams(url.search);
|
|
if (key === 'search') {
|
|
params.delete('search');
|
|
} else {
|
|
const values = params.getAll(key).filter(v => v !== value.toString());
|
|
params.delete(key);
|
|
values.forEach(v => params.append(key, v));
|
|
}
|
|
window.location.href = `${url.pathname}?${params.toString()}`;
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
const sunIcon = document.querySelector('.fa-sun');
|
|
const moonIcon = document.querySelector('.fa-moon');
|
|
if (savedTheme === 'dark' && themeToggle) {
|
|
document.documentElement.setAttribute('data-theme', 'dark');
|
|
themeToggle.checked = true;
|
|
sunIcon.classList.add('d-none');
|
|
moonIcon.classList.remove('d-none');
|
|
}
|
|
const savedView = localStorage.getItem('view');
|
|
if (savedView === 'list' && document.getElementById('viewToggle')) {
|
|
toggleView();
|
|
}
|
|
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
|
tooltips.forEach(t => new bootstrap.Tooltip(t));
|
|
const currentPath = window.location.pathname;
|
|
document.querySelectorAll('#navbarNav .nav-link').forEach(link => {
|
|
if (link.getAttribute('href') === currentPath) {
|
|
link.classList.add('active');
|
|
} else {
|
|
link.classList.remove('active');
|
|
}
|
|
});
|
|
document.querySelectorAll('#userDropdown + .dropdown-menu .dropdown-item').forEach(link => {
|
|
if (link.getAttribute('href') === currentPath) {
|
|
link.classList.add('active');
|
|
} else {
|
|
link.classList.remove('active');
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |