Files
basicswap/basicswap/templates/header.html
gerlofvanek b2df4ea80d GUI v3.2.0
2025-02-27 17:27:50 +01:00

918 lines
39 KiB
HTML

<!DOCTYPE html>
{% from 'style.html' import change_password_svg, notifications_network_offer_svg,
notifications_bid_accepted_svg, notifications_unknow_event_svg,
notifications_new_bid_on_offer_svg, notifications_close_svg, swap_in_progress_mobile_svg,
wallet_svg, page_back_svg, order_book_svg, new_offer_svg, settings_svg, asettings_svg,
cog_svg, rpc_svg, debug_svg, explorer_svg, tor_svg, smsg_svg, outputs_svg, automation_svg,
shutdown_svg, notifications_svg, debug_nerd_svg, wallet_locked_svg, mobile_menu_svg,
wallet_unlocked_svg, tor_purple_svg, sun_svg, moon_svg, swap_in_progress_svg,
swap_in_progress_green_svg, available_bids_svg, your_offers_svg, bids_received_svg,
bids_sent_svg, header_arrow_down_svg, love_svg %}
<html lang="en">
<head>
<meta charset="UTF-8">
{% if refresh %}
<meta http-equiv="refresh" content="{{ refresh }}">
{% endif %}
<!-- Scripts -->
<script src="/static/js/libs/chart.js"></script>
<script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="/static/js/main.js"></script>
<script src="/static/js/tabs.js"></script>
<script src="/static/js/dropdown.js"></script>
<script src="/static/js/libs/popper.js"></script>
<script src="/static/js/libs/tippy.js"></script>
<script src="/static/js/tooltips.js"></script>
<!-- Styles -->
<link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet" />
<link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
<link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
<title>(BSX) BasicSwap - v{{ version }}</title>
<!-- Initialize tooltips -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const tooltipManager = TooltipManager.initialize();
tooltipManager.initializeTooltips();
});
</script>
<!-- Dark mode initialization -->
<script>
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
(!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (!localStorage.getItem('color-theme')) {
localStorage.setItem('color-theme', 'dark');
}
document.documentElement.classList.toggle('dark', isDarkMode);
</script>
<!-- Shutdown modal functionality -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const shutdownButtons = document.querySelectorAll('.shutdown-button');
const shutdownModal = document.getElementById('shutdownModal');
const closeModalButton = document.getElementById('closeShutdownModal');
const confirmShutdownButton = document.getElementById('confirmShutdown');
const shutdownWarning = document.getElementById('shutdownWarning');
function updateShutdownButtons() {
const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
shutdownButtons.forEach(button => {
if (activeSwaps > 0) {
button.classList.add('shutdown-disabled');
button.setAttribute('data-disabled', 'true');
button.setAttribute('title', 'Caution: Swaps in progress');
} else {
button.classList.remove('shutdown-disabled');
button.removeAttribute('data-disabled');
button.removeAttribute('title');
}
});
}
function showShutdownModal() {
const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0');
if (activeSwaps > 0) {
shutdownWarning.classList.remove('hidden');
confirmShutdownButton.textContent = 'Yes, Shut Down Anyway';
} else {
shutdownWarning.classList.add('hidden');
confirmShutdownButton.textContent = 'Yes, Shut Down';
}
shutdownModal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function hideShutdownModal() {
shutdownModal.classList.add('hidden');
document.body.style.overflow = '';
}
shutdownButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
showShutdownModal();
});
});
closeModalButton.addEventListener('click', hideShutdownModal);
confirmShutdownButton.addEventListener('click', function() {
const shutdownToken = document.querySelector('.shutdown-button')
.getAttribute('href').split('/').pop();
window.location.href = '/shutdown/' + shutdownToken;
});
shutdownModal.addEventListener('click', function(e) {
if (e.target === this) {
hideShutdownModal();
}
});
updateShutdownButtons();
});
</script>
</head>
<body class="dark:bg-gray-700">
<!-- Shutdown Modal -->
<div id="shutdownModal" tabindex="-1" class="hidden fixed inset-0 z-50 overflow-y-auto overflow-x-hidden">
<div class="fixed inset-0 bg-black bg-opacity-60 transition-opacity"></div>
<div class="flex items-center justify-center min-h-screen p-4 relative z-10">
<div class="bg-white dark:bg-gray-500 rounded-lg shadow-xl max-w-md w-full">
<div class="p-6 text-center">
<svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200"
aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<h3 class="mb-5 text-lg font-normal text-gray-700 dark:text-gray-300">
Are you sure you want to shut down?
</h3>
<p id="shutdownWarning" class="mb-5 text-sm text-red-500 font-bold hidden">
Warning: Swaps are in progress. Please wait for swaps to complete before shutting down.
</p>
<p class="mb-5 text-sm text-gray-500 dark:text-gray-300">
This action will shut down the application. Are you sure you want to proceed?
</p>
<button id="confirmShutdown" type="button" class="text-white bg-red-600 hover:bg-red-800
focus:ring-0 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium
rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2">
Yes, Shut Down
</button>
<button id="closeShutdownModal" type="button" class="text-gray-500 bg-white hover:bg-gray-100
focus:ring-0 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm
font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300
dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Main Navigation -->
<section>
<nav class="relative bg-gray-700">
<div class="p-6 container flex flex-wrap items-center justify-between items-center mx-auto">
<!-- Logo -->
<a class="flex-shrink-0 mr-12 text-2xl text-white font-semibold" href="/">
<img class="h-10" src="/static/images/logos/basicswap-logo.svg" alt="" width="auto">
</a>
<!-- Desktop Navigation -->
<ul class="hidden xl:flex">
<!-- Wallets -->
<li>
<a class="flex mr-10 items-center py-3 text-gray-50 hover:text-gray-100 text-sm"
href="/wallets">
{{ wallet_svg | safe }}
<span>Wallets</span>
</a>
</li>
<!-- Network Order Book -->
<li>
<a class="flex mr-10 items-center py-2.5 text-gray-50 hover:text-gray-100 text-sm"
href="/offers">
{{ order_book_svg | safe }}
<span>Network Order Book</span>
<span id="network-offers-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-3 mr-2 px-2.5 py-1 font-small text-white bg-blue-500 rounded-full">
{{ summary.num_network_offers }}
</span>
</a>
</li>
<!-- Place New Offer -->
<li>
<a class="flex rounded-full flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500
hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border
border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" href="/newoffer">
{{ new_offer_svg | safe }}
<span>Place new Offer</span>
</a>
</li>
</ul>
<!-- Settings Dropdown -->
<ul class="hidden xl:flex lg:justify-end lg:items-center lg:space-x-6 ml-auto">
<div id="dropdownNavbarLink" data-dropdown-toggle="dropdownNavbar" class="flex justify-between
items-center py-2 pr-4 pl-3 w-full text-gray-50 text-sm md:border-0 md:p-0 md:w-auto
text-gray-50 hover:text-gray-100">
{{ settings_svg | safe }}
Settings & Tools
{{ header_arrow_down_svg| safe }}
</div>
</ul>
<!-- Settings Menu -->
<div id="dropdownNavbar" class="hidden z-50 w-50 font-normal bg-white shadow divide-y
divide-gray-100 dark:bg-gray-500 dark:divide-gray-400 dark:text-white">
<ul class="py-0 text-sm text-gray-700" aria-labelledby="dropdownLargeButton">
<!-- Settings Menu Items -->
<li>
<a href="/settings" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Settings</span>
{{ cog_svg | safe }}
Settings
</a>
</li>
<li>
<a href="/changepassword" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Change/Set Password</span>
{{ change_password_svg | safe }}
Change/Set Password
</a>
</li>
{% if debug_mode == true %}
<li>
<a href="/rpc" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">RPC</span>
{{ rpc_svg | safe }}
RPC Console
</a>
</li>
<li>
<a href="/debug" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Debug</span>
{{ debug_svg | safe }}
Debug
</a>
</li>
<li>
<a href="/explorers" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Explorers</span>
{{ explorer_svg | safe }}
Explorers
</a>
</li>
{% endif %}
{% if use_tor_proxy == true %}
<li>
<a href="/tor" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Tor</span>
{{ tor_svg | safe }}
Tor
</a>
</li>
{% endif %}
<li>
<a href="/smsgaddresses" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">SMSG Addresses</span>
{{ smsg_svg | safe }}
SMSG Addresses
</a>
</li>
<li>
<a href="/watched" class="flex items-center block py-4 px-4 hover:bg-gray-100 dark:hover:bg-gray-700
dark:text-white">
<span class="sr-only">Watch Outputs</span>
{{ outputs_svg | safe }}
<span>Watch Outputs</span>
<span id="watched-outputs-counter" class="inline-flex justify-center items-center text-xs font-semibold
ml-3 mr-2 px-2.5 py-1 text-white {% if summary.num_watched_outputs > 0 %}bg-blue-500
{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_watched_outputs }}
</span>
</a>
</li>
{% if debug_mode == true %}
<li>
<a href="/automation" class="flex items-center block py-4 px-4 hover:bg-gray-100
dark:hover:bg-gray-700 dark:text-white">
<span class="sr-only">Automation Strategies</span>
{{ automation_svg | safe }}
Automation Strategies
</a>
</li>
{% endif %}
</ul>
<div class="text-sm text-gray-700">
<a href="/shutdown/{{ shutdown_token }}" class="shutdown-button flex items-center block py-4
px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200
dark:hover:text-white" data-active-swaps="{{ summary.num_swapping }}">
{{ shutdown_svg | safe }}
<span>Shutdown</span>
</a>
</div>
</div>
<!-- Status Icons -->
<div class="flex mr-2 items-center text-gray-50 hover:text-gray-100 text-sm ml-5">
<div class="flex-shrink-0 w-px h-10 bg-gray-400 dark:bg-gray-400 ml-4 mr-5"></div>
<!-- Debug Mode Icon -->
{% if debug_mode == true %}
<ul class="xl:flex">
<li>
<div data-tooltip-target="tooltip-DEV" class="ml-5 flex items-center text-gray-50
hover:text-gray-100 text-sm">
{{ debug_nerd_svg | safe }}
</div>
<div id="tooltip-DEV" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3
text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
<p><b>Debug mode:</b> Active</p>
{% if debug_ui_mode == true %}
<p><b>Debug UI mode:</b> Active</p>
{% endif %}
</div>
</li>
</ul>
{% endif %}
<!-- Wallet Status -->
{% if encrypted == true %}
<ul class="xl:flex">
<li>
{% if locked == true %}
<div data-tooltip-target="tooltip-locked-wallets" class="ml-5 flex items-center text-gray-50
hover:text-gray-100 text-sm">
{{ wallet_locked_svg | safe }}
</div>
<div id="tooltip-locked-wallets" role="tooltip" class="inline-block absolute invisible z-10
py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
<p><b>Wallets:</b> Locked</p>
</div>
{% else %}
<a href='/lock'>
<div data-tooltip-target="tooltip-unlocked-wallets" class="ml-5 flex items-center
text-gray-50 hover:text-gray-100 text-sm">
{{ wallet_unlocked_svg | safe }}
</div>
<div id="tooltip-unlocked-wallets" role="tooltip" class="inline-block absolute invisible
z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
<p><b>Wallets:</b> Unlocked</p>
</div>
</a>
{% endif %}
</li>
</ul>
{% endif %}
<!-- Tor Status -->
{% if use_tor_proxy == true %}
<ul class="xl:flex ml-5">
<li>
<a href="/tor">
<div data-tooltip-target="tooltip-tor" class="flex items-center text-gray-50
hover:text-gray-100 text-sm">
{{ tor_purple_svg | safe }}
</div>
<div id="tooltip-tor" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3
text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
<b>Tor mode:</b> Active
{% if tor_established == true %}
<br><b>Tor:</b> Connected
{% endif %}
</div>
</a>
</li>
</ul>
{% endif %}
<!-- Theme Toggle -->
<button data-tooltip-target="tooltip-darkmode" id="theme-toggle" type="button"
class="text-gray-500 dark:text-gray-400 focus:outline-none rounded-lg text-sm ml-5">
{{ sun_svg | safe }}
{{ moon_svg | safe }}
<div id="tooltip-darkmode" role="tooltip" class="inline-block absolute invisible z-10 py-2
px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
Dark mode
</div>
</button>
</div>
<!-- Mobile Menu Button -->
<div class="ml-auto flex xl:hidden">
<button class="navbar-burger flex items-center rounded focus:outline-none">
{{ mobile_menu_svg | safe }}
</button>
</div>
</div>
<!-- Secondary Navigation Bar -->
<div class="hidden xl:block py-5 px-6 bg-coolGray-100 border-gray-100 dark:border-gray-500
dark:bg-body border-b dark:border-b-2">
<div class="flex items-center justify-center container mx-auto">
<ul class="flex items-center space-x-8">
<!-- Your Offers -->
<li>
<a data-tooltip-target="tooltip-your-offers" class="flex items-center text-sm text-gray-400
hover:text-gray-600 dark:text-gray-100 dark:hover:text-gray-100" href="/sentoffers">
{{ your_offers_svg | safe }}
<span>Your Offers</span>
<span id="offers-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-3 px-2.5 py-1 text-white {% if summary.num_sent_active_offers > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_sent_active_offers }}
</span>
</a>
<div id="tooltip-your-offers" role="tooltip" class="inline-block absolute invisible z-10
py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0
transition-opacity duration-300 tooltip">
<p><b>Total offers:</b> {{ summary.num_sent_offers }}</p>
<p><b>Active offers:</b> {{ summary.num_sent_active_offers }}</p>
</div>
</li>
<li>
<span class="text-gray-300">|</span>
</li>
<!-- Bid Requests -->
<li>
<a class="flex items-center text-sm text-gray-400 hover:text-gray-600 dark:text-gray-100
dark:hover:text-gray-100" href="/availablebids">
{{ available_bids_svg | safe }}
<span>Bid Requests</span>
<span id="bid-requests-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-3 px-2.5 py-1 text-white {% if summary.num_available_bids > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_available_bids }}
</span>
</a>
</li>
<li>
<span class="text-gray-300">|</span>
</li>
<!-- Bids -->
<li>
<a href="/bids" data-tooltip-target="tooltip-bids" class="flex items-center text-sm text-gray-400
hover:text-gray-600 dark:text-gray-100 dark:hover:text-gray-100">
<span class="inline-block mr-2">{{ bids_sent_svg | safe }}</span>
<span>Bids</span>
<span class="flex items-center ml-2">
<!-- Outgoing bids counter arrow -->
<span id="sent-bids-counter" class="inline-flex items-center text-xs font-semibold px-2.5 py-1
text-white {% if summary.num_sent_active_bids > 0 %}bg-blue-500{% else %}bg-gray-400{% endif %}
rounded-full">
<svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 19V5L19 12L5 19Z" fill="currentColor" transform="rotate(-90 12 12)"/>
</svg>
{{ summary.num_sent_active_bids }}
</span>
<!-- Incoming bids counter arrow -->
<span id="recv-bids-counter" class="inline-flex items-center text-xs font-semibold ml-2 px-2.5
py-1 text-white {% if summary.num_recv_active_bids > 0 %}bg-blue-500{% else %}bg-gray-400
{% endif %} rounded-full">
<svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 19V5L19 12L5 19Z" fill="currentColor" transform="rotate(90 12 12)"/>
</svg>
{{ summary.num_recv_active_bids }}
</span>
</span>
</a>
<div id="tooltip-bids" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm
font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300
tooltip">
<p><b>Sent bids:</b> {{ summary.num_sent_bids }} ({{ summary.num_sent_active_bids }} active)</p>
<p><b>Received bids:</b> {{ summary.num_recv_bids }} ({{ summary.num_recv_active_bids }} active)</p>
</div>
</li>
<li>
<span class="text-gray-300">|</span>
</li>
<!-- Swaps in Progress -->
<li>
<a class="flex items-center text-sm text-gray-400 hover:text-gray-600 dark:text-gray-100
dark:hover:text-gray-100" href="/active">
<div id="swapContainer" class="inline-flex center-spin mr-2"
{% if summary.num_swapping != 0 %}style="animation: spin 2s linear infinite;"{% endif %}>
{% if summary.num_swapping != 0 %}
{{ swap_in_progress_green_svg | safe }}
{% else %}
{{ swap_in_progress_svg | safe }}
{% endif %}
</div>
<span>Swaps in Progress</span>
<span id="swaps-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-3 px-2.5 py-1 text-white {% if summary.num_swapping > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_swapping }}
</span>
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Mobile Navigation Menu -->
<div class="hidden navbar-menu fixed top-0 left-0 bottom-0 w-3/4 lg:w-80 sm:max-w-xs z-50">
<div class="navbar-backdrop fixed inset-0 bg-gray-700 dark:bg-gray-600 opacity-10"></div>
<nav class="relative flex flex-col pt-6 pb-8 h-full w-full bg-gray-700 dark:bg-gray-600 overflow-y-auto">
<div class="flex w-full items-center px-6 pb-6 mb-6 lg:border-b border-gray-700">
<a class="text-xl text-white font-semibold" href="/">
<img class="h-8" src="/static/images/logos/basicswap-logo.svg" alt="" width="auto">
</a>
</div>
<div class="px-4 pb-6">
<!-- Main Navigation -->
<ul class="mb-8 text-sm font-medium">
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/offers">
{{ order_book_svg | safe }}
<span>Network Order Book</span>
<span id="network-offers-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-auto px-2.5 py-1 text-white {% if summary.num_network_offers and
summary.num_network_offers > 0 %}bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_network_offers }}
</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/newoffer">
{{ new_offer_svg | safe }}
<span>Place New Offer</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/wallets">
{{ wallet_svg | safe }}
<span>Wallets</span>
</a>
</li>
</ul>
<!-- Trading Section -->
<h3 class="mb-2 text-xs uppercase text-gray-300 font-medium">Trading</h3>
<ul class="mb-8 text-sm font-medium">
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/sentoffers">
{{ your_offers_svg | safe }}
<span>Your Offers</span>
<span id="offers-counter" class="inline-flex justify-center items-center text-xs font-semibold
ml-auto px-2.5 py-1 text-white {% if summary.num_sent_active_offers and
summary.num_sent_active_offers > 0 %}bg-blue-500{% else %}bg-gray-400{% endif %}
rounded-full">
{{ summary.num_sent_active_offers }}
</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/availablebids">
{{ available_bids_svg | safe }}
<span>Bid Requests</span>
<span id="bid-requests-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-auto px-2.5 py-1 text-white {% if summary.num_available_bids and
summary.num_available_bids > 0 %}bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_available_bids }}
</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/bids">
{{ bids_received_svg | safe }}
<span>Bids</span>
<div class="flex ml-auto">
<span id="sent-bids-counter" class="inline-flex items-center text-xs font-semibold px-2.5
py-1 text-white {% if summary.num_sent_active_bids and summary.num_sent_active_bids > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full mr-2">
<svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 19V5L19 12L5 19Z" fill="currentColor" transform="rotate(-90 12 12)"/>
</svg>
{{ summary.num_sent_active_bids }}
</span>
<span id="recv-bids-counter" class="inline-flex items-center text-xs font-semibold px-2.5
py-1 text-white {% if summary.num_recv_active_bids and summary.num_recv_active_bids > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
<svg class="w-3 h-3 mr-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 19V5L19 12L5 19Z" fill="currentColor" transform="rotate(90 12 12)"/>
</svg>
{{ summary.num_recv_active_bids }}
</span>
</div>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/active">
<div id="swapContainer" class="inline-flex center-spin mr-2"
{% if summary.num_swapping and summary.num_swapping != 0 %}
style="animation: spin 2s linear infinite;"{% endif %}>
{% if summary.num_swapping and summary.num_swapping != 0 %}
{{ swap_in_progress_green_svg | safe }}
{% else %}
{{ swap_in_progress_svg | safe }}
{% endif %}
</div>
<span>Swaps in Progress</span>
<span id="swaps-counter" class="inline-flex justify-center items-center text-xs font-semibold
ml-auto px-2.5 py-1 text-white {% if summary.num_swapping and summary.num_swapping > 0 %}
bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_swapping }}
</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/watched">
{{ outputs_svg | safe }}
<span>Watch Outputs</span>
<span id="watched-outputs-counter" class="inline-flex justify-center items-center text-xs
font-semibold ml-auto px-2.5 py-1 text-white {% if summary.num_watched_outputs and
summary.num_watched_outputs > 0 %}bg-blue-500{% else %}bg-gray-400{% endif %} rounded-full">
{{ summary.num_watched_outputs }}
</span>
</a>
</li>
</ul>
<!-- Settings Section -->
<h3 class="mb-2 text-xs uppercase text-gray-300 font-medium">Settings & Tools</h3>
<ul class="text-sm font-medium">
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/settings">
{{ settings_svg | safe }}
<span>Settings</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/changepassword">
{{ change_password_svg | safe }}
<span>Change/Set Password</span>
</a>
</li>
{% if debug_mode == true %}
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/rpc">
{{ rpc_svg | safe }}
<span>RPC Console</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/debug">
{{ debug_svg | safe }}
<span>Debug</span>
</a>
</li>
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/explorers">
{{ explorer_svg | safe }}
<span>Explorers</span>
</a>
</li>
{% endif %}
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/smsgaddresses">
{{ smsg_svg | safe }}
<span>SMSG Addresses</span>
</a>
</li>
{% if debug_mode == true %}
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded"
href="/automation">
{{ automation_svg | safe }}
<span>Automation Strategies</span>
</a>
</li>
{% endif %}
{% if use_tor_proxy == true %}
<li>
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/tor">
{{ tor_svg | safe }}
<span>Tor</span>
</a>
</li>
{% endif %}
</ul>
<!-- Shutdown Button -->
<div class="pt-8">
<a href="/shutdown/{{ shutdown_token }}" class="shutdown-button flex items-center pl-3 py-3 pr-4
text-gray-50 hover:bg-gray-900 rounded" data-active-swaps="{{ summary.num_swapping }}">
{{ shutdown_svg | safe }}
<span>Shutdown</span>
</a>
</div>
</div>
</nav>
</div>
</div>
</section>
<!-- WebSocket -->
{% if ws_port %}
<script>
(function() {
window.notificationConfig = {
showNewOffers: false,
showNewBids: true,
showBidAccepted: true
};
function ensureToastContainer() {
let container = document.getElementById('ul_updates');
if (!container) {
const floating_div = document.createElement('div');
floating_div.classList.add('floatright');
container = document.createElement('ul');
container.setAttribute('id', 'ul_updates');
floating_div.appendChild(container);
document.body.appendChild(floating_div);
}
return container;
}
function createToast(title, type = 'success') {
const messages = ensureToastContainer();
const message = document.createElement('li');
message.innerHTML = `
<div id="hide">
<div id="toast-${type}" class="flex items-center p-4 mb-4 w-full max-w-xs text-gray-500
bg-white rounded-lg shadow" role="alert">
<div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10
bg-blue-500 rounded-lg">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="18" width="18"
viewBox="0 0 24 24">
<g fill="#ffffff">
<path d="M8.5,20a1.5,1.5,0,0,1-1.061-.439L.379,12.5,2.5,10.379l6,6,13-13L23.621,
5.5,9.561,19.561A1.5,1.5,0,0,1,8.5,20Z"></path>
</g>
</svg>
</div>
<div class="uppercase w-40 ml-3 text-sm font-semibold text-gray-900">${title}</div>
<button type="button" onclick="closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5
bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-0 focus:outline-none
focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8">
<span class="sr-only">Close</span>
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1
1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293
4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</button>
</div>
</div>
`;
messages.appendChild(message);
}
function updateElement(elementId, value, options = {}) {
const element = document.getElementById(elementId);
if (!element) return false;
const safeValue = (value !== undefined && value !== null)
? value
: (element.dataset.lastValue || 0);
element.dataset.lastValue = safeValue;
if (elementId === 'sent-bids-counter' || elementId === 'recv-bids-counter') {
const svg = element.querySelector('svg');
element.textContent = safeValue;
if (svg) {
element.insertBefore(svg, element.firstChild);
}
} else {
element.textContent = safeValue;
}
if (['offers-counter', 'bid-requests-counter', 'sent-bids-counter',
'recv-bids-counter', 'swaps-counter', 'network-offers-counter',
'watched-outputs-counter'].includes(elementId)) {
element.classList.remove('bg-blue-500', 'bg-gray-400');
element.classList.add(safeValue > 0 ? 'bg-blue-500' : 'bg-gray-400');
}
if (elementId === 'swaps-counter') {
const swapContainer = document.getElementById('swapContainer');
if (swapContainer) {
const isSwapping = safeValue > 0;
if (isSwapping) {
swapContainer.innerHTML = `{{ swap_in_progress_green_svg | safe }}`;
swapContainer.style.animation = 'spin 2s linear infinite';
} else {
swapContainer.innerHTML = `{{ swap_in_progress_svg | safe }}`;
swapContainer.style.animation = 'none';
}
}
}
return true;
}
function fetchSummaryData() {
fetch('/json')
.then(response => response.json())
.then(data => {
updateElement('network-offers-counter', data.num_network_offers);
updateElement('offers-counter', data.num_sent_active_offers);
updateElement('sent-bids-counter', data.num_sent_active_bids);
updateElement('recv-bids-counter', data.num_recv_active_bids);
updateElement('bid-requests-counter', data.num_available_bids);
updateElement('swaps-counter', data.num_swapping);
updateElement('watched-outputs-counter', data.num_watched_outputs);
})
.catch(error => console.error('Summary data fetch error:', error));
}
function initWebSocket() {
const wsUrl = "ws://" + window.location.hostname + ":{{ ws_port }}";
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('🟢 WebSocket connection established for Dynamic Counters');
fetchSummaryData();
setInterval(fetchSummaryData, 30000); // Refresh every 30 seconds
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.event) {
let toastTitle;
let shouldShowToast = false;
switch (data.event) {
case 'new_offer':
toastTitle = `New network <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
shouldShowToast = window.notificationConfig.showNewOffers;
break;
case 'new_bid':
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>New bid</a> on
<a class="underline" href=/offer/${data.offer_id}>offer</a>`;
shouldShowToast = window.notificationConfig.showNewBids;
break;
case 'bid_accepted':
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>Bid</a> accepted`;
shouldShowToast = window.notificationConfig.showBidAccepted;
break;
}
if (toastTitle && shouldShowToast) {
createToast(toastTitle);
}
}
fetchSummaryData();
} catch (error) {
console.error('WebSocket message processing error:', error);
}
};
ws.onerror = (error) => {
console.error('WebSocket Error:', error);
};
ws.onclose = (event) => {
console.log('WebSocket connection closed', event);
setTimeout(initWebSocket, 5000);
};
}
window.closeAlert = function(event) {
let element = event.target;
while (element.nodeName !== "BUTTON") {
element = element.parentNode;
}
element.parentNode.parentNode.removeChild(element.parentNode);
};
function init() {
initWebSocket();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
{% endif %}