From ff8a4671f62842b619f7adfe7b710279bdf11189 Mon Sep 17 00:00:00 2001 From: martadams89 Date: Sat, 26 Apr 2025 10:32:59 +0100 Subject: [PATCH 01/19] Fix resource protocols for reverse proxy compatibility in status page --- templates/status_page.html | 55 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/templates/status_page.html b/templates/status_page.html index bed764c..28efbd2 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -28,7 +28,42 @@ const currentProtocol = window.location.protocol; const currentHost = window.location.host; console.log("Page loading with protocol:", currentProtocol); - + + function fixResources() { + console.log("Fixing resource protocols for reverse proxy compatibility"); + + document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) { + const href = link.getAttribute('href'); + if (href && href.startsWith('http:') && window.location.protocol === 'https:') { + const newHref = href.replace('http:', 'https:'); + console.log(`Fixing stylesheet: ${href} → ${newHref}`); + link.setAttribute('href', newHref); + } + }); + + document.querySelectorAll('script[src]').forEach(function(script) { + const src = script.getAttribute('src'); + if (src && src.startsWith('http:') && window.location.protocol === 'https:') { + const newSrc = src.replace('http:', 'https:'); + console.log(`Fixing script: ${src} → ${newSrc}`); + script.setAttribute('src', newSrc); + } + }); + + document.querySelectorAll('link[rel="preconnect"]').forEach(function(link) { + const href = link.getAttribute('href'); + if (href && href.startsWith('http:') && window.location.protocol === 'https:') { + const newHref = href.replace('http:', 'https:'); + console.log(`Fixing preconnect: ${href} → ${newHref}`); + link.setAttribute('href', newHref); + } + }); + } + + fixResources(); + + document.addEventListener('DOMContentLoaded', fixResources); + const origFetch = window.fetch; window.fetch = function(url, options) { if (url && typeof url === 'string' && url.startsWith('http')) { @@ -44,10 +79,26 @@ } return origFetch.call(this, url, options); }; - + const baseTag = document.createElement('base'); baseTag.href = currentProtocol + '//' + currentHost + '/'; document.head.appendChild(baseTag); + + const preconnectDomains = ['cdn.tailwindcss.com', 'rsms.me']; + preconnectDomains.forEach(domain => { + const existingPreconnect = Array.from(document.querySelectorAll('link[rel="preconnect"]')) + .find(link => link.href.includes(domain)); + + if (existingPreconnect) { + const href = existingPreconnect.getAttribute('href'); + if (href && href.startsWith('http') && + new URL(href).protocol !== currentProtocol) { + const newHref = currentProtocol + '//' + new URL(href).host; + console.log(`Updating preconnect: ${href} → ${newHref}`); + existingPreconnect.setAttribute('href', newHref); + } + } + }); })(); From 4c7b9450d7b3237ffff9f7386e3275385998f5e7 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 17:24:39 +0200 Subject: [PATCH 02/19] new feature for showing all tunnels --- app.py | 47 +++++++++++++++++++- templates/status_page.html | 89 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 169cfa4..b1bbf55 100644 --- a/app.py +++ b/app.py @@ -2084,6 +2084,45 @@ def update_cloudflare_config(): # If we reached here, either there were no changes or the update was successful return True +def get_all_account_cloudflare_tunnels(): + """Retrieves a list of all Cloudflare Tunnels for the configured account.""" + if not CF_ACCOUNT_ID: + logging.warning("CF_ACCOUNT_ID is not configured. Cannot list all Cloudflare tunnels from the account.") + return [] + if not CF_API_TOKEN: + logging.error("Cloudflare API token not configured. Cannot list all account tunnels.") + return [] + + endpoint = f"/accounts/{CF_ACCOUNT_ID}/cfd_tunnel" + params = {"is_deleted": "false", "status": "healthy,degraded,down,inactive"} + + logging.info(f"Attempting to list all Cloudflare tunnels for account ID {CF_ACCOUNT_ID}") + try: + + response_data = cf_api_request("GET", endpoint, params=params) + tunnels = response_data.get("result", []) + + if isinstance(tunnels, list): + logging.info(f"Successfully retrieved {len(tunnels)} Cloudflare tunnels from the account.") + + tunnels.sort(key=lambda t: t.get("name", "").lower()) + return tunnels + else: + logging.error(f"Unexpected data format for account tunnels list: {type(tunnels)}. Expected a list. Response: {response_data}") + return [] + except requests.exceptions.RequestException as e: + + logging.error(f"API error listing all Cloudflare tunnels for the account: {e}") + + if hasattr(e, 'response') and e.response is not None: + if e.response.status_code == 403: + logging.error("Permission denied (403) listing account tunnels. Ensure API token has 'Account:Cloudflare Tunnel:Read' permission for the account.") + elif e.response.status_code == 400 and "account_id is invalid" in e.response.text.lower(): # Example check + logging.error("Invalid Account ID (400) provided for listing account tunnels. Please check CF_ACCOUNT_ID environment variable.") + return [] + except Exception as e: + logging.error(f"Unexpected error listing all Cloudflare tunnels for the account: {e}", exc_info=True) + return [] app = Flask(__name__) app.secret_key = os.urandom(24) @@ -2195,7 +2234,7 @@ def status_page(): docker_available = docker_client is not None external_cloudflared = USE_EXTERNAL_CLOUDFLARED external_tunnel_id = EXTERNAL_TUNNEL_ID - + all_account_tunnels_list = get_all_account_cloudflare_tunnels() return render_template('status_page.html', tunnel_state=template_tunnel_state, agent_state=template_agent_state, @@ -2205,7 +2244,11 @@ def status_page(): docker_available=docker_available, external_cloudflared=external_cloudflared, external_tunnel_id=external_tunnel_id, - rules=rules_for_template) + rules=rules_for_template, + all_account_tunnels=all_account_tunnels_list, + CF_ACCOUNT_ID_CONFIGURED=bool(CF_ACCOUNT_ID), # Pass boolean flag + ACCOUNT_ID_FOR_DISPLAY=CF_ACCOUNT_ID if CF_ACCOUNT_ID else "Not Configured" # Pass actual ID or placeholder + ) @app.route('/ping') def ping(): diff --git a/templates/status_page.html b/templates/status_page.html index 28efbd2..28a1982 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -378,6 +378,95 @@ +
+
+

+ All Cloudflare Tunnels on Account +

+ + {% if CF_ACCOUNT_ID_CONFIGURED %} +

+ Displaying tunnels for Cloudflare Account ID: {{ ACCOUNT_ID_FOR_DISPLAY | e }}. +
+ This list shows all tunnels found on the account, not just the one managed by this DockFlare instance. +

+ {% if all_account_tunnels is defined and all_account_tunnels %} +
+ + + + + + + + + + + + + {% for tunnel in all_account_tunnels %} + + + + + + + + + {% endfor %} + +
Tunnel NameTunnel IDStatusCreated AtConnectionsVersion
+ {{ tunnel.name | e }} + + {{ tunnel.id | e }} + + + {{ tunnel.status | capitalize | e }} + + + {% if tunnel.created_at %} + {{ tunnel.created_at.split('T')[0] | e }} + {% else %} + N/A + {% endif %} + + {% if tunnel.connections and tunnel.connections is iterable and not tunnel.connections is string %} + {% for conn in tunnel.connections %} + {{ conn.colo_name | e }} + {% endfor %} + {% if tunnel.connections | length > 0 %} + ({{ tunnel.connections | length }} total) + {% else %} + (0 total) + {% endif %} + {% elif tunnel.connections %} + {{ tunnel.connections | e }} + {% else %} + None + {% endif %} + + {{ tunnel.version | e or 'N/A' }} +
+
+ {% elif all_account_tunnels is defined %} +

No Cloudflare Tunnels found for Account ID: {{ ACCOUNT_ID_FOR_DISPLAY | e }}.

+

This could also mean an error occurred fetching them (check DockFlare logs). Ensure your CF_API_TOKEN has 'Account:Cloudflare Tunnel:Read' permission for the account level.

+ {% else %} +

Could not retrieve tunnel information (all_account_tunnels variable not available in template).

+ {% endif %} + {% else %} +

+ The CF_ACCOUNT_ID environment variable is not configured. This section cannot be displayed. +

+ {% endif %} +
+
+

From 4b81f6345d8e6f6efc69e2d44fecab48c758f6c5 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 17:40:29 +0200 Subject: [PATCH 03/19] changed function --- app.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/app.py b/app.py index b1bbf55..3e7eeba 100644 --- a/app.py +++ b/app.py @@ -2084,41 +2084,40 @@ def update_cloudflare_config(): # If we reached here, either there were no changes or the update was successful return True + def get_all_account_cloudflare_tunnels(): - """Retrieves a list of all Cloudflare Tunnels for the configured account.""" - if not CF_ACCOUNT_ID: + if not CF_ACCOUNT_ID: logging.warning("CF_ACCOUNT_ID is not configured. Cannot list all Cloudflare tunnels from the account.") return [] - if not CF_API_TOKEN: + if not CF_API_TOKEN: logging.error("Cloudflare API token not configured. Cannot list all account tunnels.") return [] endpoint = f"/accounts/{CF_ACCOUNT_ID}/cfd_tunnel" - params = {"is_deleted": "false", "status": "healthy,degraded,down,inactive"} + params = { + "is_deleted": "false", + "status": ["healthy", "degraded", "down", "inactive"] + } - logging.info(f"Attempting to list all Cloudflare tunnels for account ID {CF_ACCOUNT_ID}") + logging.info(f"Attempting to list all Cloudflare tunnels for account ID {CF_ACCOUNT_ID} with params: {params}") try: - response_data = cf_api_request("GET", endpoint, params=params) tunnels = response_data.get("result", []) if isinstance(tunnels, list): logging.info(f"Successfully retrieved {len(tunnels)} Cloudflare tunnels from the account.") - tunnels.sort(key=lambda t: t.get("name", "").lower()) return tunnels else: logging.error(f"Unexpected data format for account tunnels list: {type(tunnels)}. Expected a list. Response: {response_data}") return [] except requests.exceptions.RequestException as e: - logging.error(f"API error listing all Cloudflare tunnels for the account: {e}") - if hasattr(e, 'response') and e.response is not None: if e.response.status_code == 403: logging.error("Permission denied (403) listing account tunnels. Ensure API token has 'Account:Cloudflare Tunnel:Read' permission for the account.") - elif e.response.status_code == 400 and "account_id is invalid" in e.response.text.lower(): # Example check - logging.error("Invalid Account ID (400) provided for listing account tunnels. Please check CF_ACCOUNT_ID environment variable.") + elif e.response.status_code == 400: + logging.error(f"Bad Request (400) listing account tunnels. API Response: {e.response.text}") return [] except Exception as e: logging.error(f"Unexpected error listing all Cloudflare tunnels for the account: {e}", exc_info=True) From 7bc850712cf54cb438b676377b9e00b63956c1d6 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 17:44:36 +0200 Subject: [PATCH 04/19] api update --- app.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 3e7eeba..320b5c3 100644 --- a/app.py +++ b/app.py @@ -2095,8 +2095,7 @@ def get_all_account_cloudflare_tunnels(): endpoint = f"/accounts/{CF_ACCOUNT_ID}/cfd_tunnel" params = { - "is_deleted": "false", - "status": ["healthy", "degraded", "down", "inactive"] + "is_deleted": "false" } logging.info(f"Attempting to list all Cloudflare tunnels for account ID {CF_ACCOUNT_ID} with params: {params}") @@ -2105,9 +2104,16 @@ def get_all_account_cloudflare_tunnels(): tunnels = response_data.get("result", []) if isinstance(tunnels, list): - logging.info(f"Successfully retrieved {len(tunnels)} Cloudflare tunnels from the account.") - tunnels.sort(key=lambda t: t.get("name", "").lower()) - return tunnels + logging.info(f"Successfully retrieved {len(tunnels)} Cloudflare tunnels from the account (any status).") + + desired_statuses = {"healthy", "degraded", "down", "inactive"} + filtered_tunnels = [ + tunnel for tunnel in tunnels if tunnel.get("status") in desired_statuses + ] + + logging.info(f"Returning {len(filtered_tunnels)} tunnels after client-side status check for relevant statuses.") + filtered_tunnels.sort(key=lambda t: t.get("name", "").lower()) + return filtered_tunnels else: logging.error(f"Unexpected data format for account tunnels list: {type(tunnels)}. Expected a list. Response: {response_data}") return [] From ff011e5a83b1c967d8d23d3d88e028fc786226d1 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 18:28:09 +0200 Subject: [PATCH 05/19] new functions --- app.py | 71 ++++++++++++++++++++++++ templates/status_page.html | 107 ++++++++++++++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 320b5c3..282fd5c 100644 --- a/app.py +++ b/app.py @@ -53,6 +53,10 @@ EXTERNAL_TUNNEL_ID = os.getenv('EXTERNAL_TUNNEL_ID') # Network scanning configuration SCAN_ALL_NETWORKS = os.getenv('SCAN_ALL_NETWORKS', 'false').lower() in ['true', '1', 't', 'yes'] +# Tunnel DNS Scanning to show DNS entrys for other +# for function get_dns_records_for_tunnel +TUNNEL_DNS_SCAN_ZONE_NAMES_STR = os.getenv('TUNNEL_DNS_SCAN_ZONE_NAMES', '') +TUNNEL_DNS_SCAN_ZONE_NAMES = [name.strip() for name in TUNNEL_DNS_SCAN_ZONE_NAMES_STR.split(',') if name.strip()] # Settings that are only required when NOT using external cloudflared TUNNEL_NAME = os.getenv("TUNNEL_NAME", "dockflared-tunnel") @@ -2129,6 +2133,38 @@ def get_all_account_cloudflare_tunnels(): logging.error(f"Unexpected error listing all Cloudflare tunnels for the account: {e}", exc_info=True) return [] +def get_dns_records_for_tunnel(zone_id, tunnel_id): + """Fetches CNAME records in a specific zone pointing to a specific tunnel.""" + if not zone_id or not tunnel_id: + logging.warning("get_dns_records_for_tunnel: Missing zone_id or tunnel_id.") + return [] + + expected_cname_content = f"{tunnel_id}.cfargotunnel.com" + + endpoint = f"/zones/{zone_id}/dns_records" + params = { + "type": "CNAME", + "content": expected_cname_content, + "per_page": 100 # Get up to 100 records, handle pagination if more are expected + } + logging.info(f"Fetching DNS records for tunnel {tunnel_id} in zone {zone_id} with content {expected_cname_content}") + + try: + response_data = cf_api_request("GET", endpoint, params=params) # Your existing cf_api_request + dns_records = response_data.get("result", []) + if isinstance(dns_records, list): + + return [{"name": record.get("name"), "id": record.get("id"), "zone_id": zone_id} for record in dns_records if record.get("name")] + else: + logging.error(f"Unexpected data format for DNS records list in zone {zone_id}: {type(dns_records)}") + return [] + except requests.exceptions.RequestException as e: + logging.error(f"API error fetching DNS records for tunnel {tunnel_id} in zone {zone_id}: {e}") + return [] + except Exception as e: + logging.error(f"Unexpected error fetching DNS records for tunnel {tunnel_id} in zone {zone_id}: {e}", exc_info=True) + return [] + app = Flask(__name__) app.secret_key = os.urandom(24) app.config['PREFERRED_URL_SCHEME'] = 'https' # Default for url_for, but client-side JS will fix @@ -2188,6 +2224,41 @@ def get_display_token(token): return "Not available" return f"{token[:5]}...{token[-5:]}" if len(token) > 10 else "Token retrieved (short)" +@app.route('/tunnel-dns-records/') +def tunnel_dns_records(tunnel_id): + if not tunnel_id: + return jsonify({"error": "Tunnel ID is required"}), 400 + + all_found_dns_records = [] + + zone_ids_to_scan = set() + + if CF_ZONE_ID: # Your global CF_ZONE_ID + zone_ids_to_scan.add(CF_ZONE_ID) + + for zone_name in TUNNEL_DNS_SCAN_ZONE_NAMES: # The list loaded from env var + resolved_zone_id = get_zone_id_from_name(zone_name) # Your existing function + if resolved_zone_id: + zone_ids_to_scan.add(resolved_zone_id) + else: + logging.warning(f"Could not resolve Zone ID for configured scan name: {zone_name}") + + if not zone_ids_to_scan: + logging.warning(f"No Zone IDs configured or resolved for DNS scan for tunnel {tunnel_id}.") + + return jsonify({"dns_records": [], "message": "No zones configured for DNS scan."}) + + for zone_id in zone_ids_to_scan: + records_in_zone = get_dns_records_for_tunnel(zone_id, tunnel_id) + if records_in_zone: # records_in_zone is a list of dicts + + all_found_dns_records.extend(records_in_zone) + + all_found_dns_records.sort(key=lambda r: r.get("name", "").lower()) + + logging.info(f"Found {len(all_found_dns_records)} DNS records for tunnel {tunnel_id} across {len(zone_ids_to_scan)} zones.") + return jsonify({"dns_records": all_found_dns_records}) + @app.context_processor def inject_protocol(): """Inject protocol info into all templates with more reliable detection.""" diff --git a/templates/status_page.html b/templates/status_page.html index 28a1982..afb3400 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -138,7 +138,7 @@
- Beta Build + early build
@@ -318,6 +318,13 @@ Container Delete Scheduled (UTC) Actions + +/- + Tunnel Name + Tunnel ID + Status + Created At + Connections + Version @@ -406,6 +413,26 @@ {% for tunnel in all_account_tunnels %} + + + + + +
+

Click the '+' button to load DNS records for this tunnel.

+
+ + + + {{ tunnel.name | e }} + {{ tunnel.name | e }} @@ -625,7 +652,85 @@ .catch(error => console.error("Server ping failed:", error)); }, 30000); } + document.querySelectorAll('.tunnel-dns-toggle').forEach(button => { + button.addEventListener('click', async function() { + const tunnelId = this.dataset.tunnelId; + // The targetRow is the one *after* the button's row, which is for DNS records + const tunnelDetailsRow = this.closest('tr'); + const dnsRecordsDisplayRow = tunnelDetailsRow.nextElementSibling; + const targetDivId = this.getAttribute('aria-controls'); // e.g., "dns-records-TUNNEL_ID" + const targetDiv = document.getElementById(targetDivId); + const isExpanded = this.getAttribute('aria-expanded') === 'true'; + const expandIcon = this.querySelector('.expand-icon'); + const collapseIcon = this.querySelector('.collapse-icon'); + + if (!dnsRecordsDisplayRow || !targetDiv) { + console.error("Could not find target row or div for DNS records for tunnel:", tunnelId); + return; + } + + if (isExpanded) { + dnsRecordsDisplayRow.classList.add('hidden'); + this.setAttribute('aria-expanded', 'false'); + if (expandIcon) expandIcon.classList.remove('hidden'); + if (collapseIcon) collapseIcon.classList.add('hidden'); + if (expandIcon) expandIcon.style.transform = 'rotate(0deg)'; // Reset rotation + } else { + this.setAttribute('aria-expanded', 'true'); + if (expandIcon) expandIcon.classList.add('hidden'); + if (collapseIcon) collapseIcon.classList.remove('hidden'); + if (expandIcon) expandIcon.style.transform = 'rotate(45deg)'; // Visual cue + + // Only fetch if data hasn't been loaded or if a previous load resulted in an error + if (targetDiv.dataset.loaded !== 'true' || targetDiv.dataset.loaded === 'error') { + targetDiv.innerHTML = '

Loading DNS records...

'; + dnsRecordsDisplayRow.classList.remove('hidden'); // Show before loading + + try { + const currentProtocol = window.location.protocol; + const currentHost = window.location.host; + const fetchUrl = `${currentProtocol}//${currentHost}/tunnel-dns-records/${tunnelId}?t=${Date.now()}`; // Added cache buster + + console.log(`Fetching DNS for tunnel ${tunnelId} from ${fetchUrl}`); + const response = await fetch(fetchUrl); + + if (!response.ok) { + let errorDetail = `HTTP error ${response.status}`; + try { + const errorData = await response.json(); + errorDetail = errorData.error || errorData.message || errorDetail; + } catch (e) { /* Failed to parse JSON error */ } + throw new Error(errorDetail); + } + const data = await response.json(); + + if (data.dns_records && data.dns_records.length > 0) { + let dnsHtml = '
    '; // Using list-none for custom styling if needed + data.dns_records.forEach(record => { + const recordUrl = `https://${record.name}`; // Assuming HTTPS + dnsHtml += `
  • ${record.name}
  • `; + }); + dnsHtml += '
'; + targetDiv.innerHTML = dnsHtml; + targetDiv.dataset.loaded = 'true'; + } else if (data.message) { // For messages like "No zones configured" + targetDiv.innerHTML = `

${data.message}

`; + targetDiv.dataset.loaded = 'info'; // Or 'true' if you don't want to reload + } else { + targetDiv.innerHTML = '

No CNAME DNS records found pointing to this tunnel in the configured zones.

'; + targetDiv.dataset.loaded = 'true'; + } + } catch (error) { + console.error('Error fetching DNS records for tunnel ' + tunnelId + ':', error); + targetDiv.innerHTML = `

Error loading DNS records: ${error.message}

`; + targetDiv.dataset.loaded = 'error'; // Allows retry on next click + } + } + dnsRecordsDisplayRow.classList.remove('hidden'); // Ensure it's visible after loading or if already loaded + } + }); +}); function updateReconciliationStatus() { const currentProtocol = window.location.protocol; const currentHost = window.location.host; From 7d3fa1c2e37fb9728ae4dd1734f0e2875b627b44 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 19:04:37 +0200 Subject: [PATCH 06/19] status_page updates --- templates/status_page copy.html | 883 ++++++++++++++++++++++++++++++++ templates/status_page.html | 566 +++----------------- 2 files changed, 947 insertions(+), 502 deletions(-) create mode 100644 templates/status_page copy.html diff --git a/templates/status_page copy.html b/templates/status_page copy.html new file mode 100644 index 0000000..afb3400 --- /dev/null +++ b/templates/status_page copy.html @@ -0,0 +1,883 @@ + + + + + + + + Dockflare - Status + + + + + + + + + + + + + + + +
+
+ early build +
+
+ +
+
+ Dockflare Logo Banner +
+
+ + + +
+ + {% if initialization and initialization.in_progress %} +
+
+
+ + + + +
+
+

Initialization in progress...

+

You can view logs below while waiting. The web interface will update automatically when ready.

+
+
+
+ {% endif %} + +
+
+ +
+
+

+ Tunnel & Agent Status +

+ +
+

Cloudflare API & Tunnel Setup

+ {% set status_box_class = 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' %} + {% if tunnel_state.get('error') and not external_cloudflared %} + {% set status_box_class = 'border-red-500 bg-red-50 dark:bg-red-900/20' %} + {% elif (tunnel_state.get('id') and tunnel_state.get('token')) or (external_cloudflared and tunnel_state.get('id')) %} + {% set status_box_class = 'border-green-500 bg-green-50 dark:bg-green-900/20' %} + {% endif %} +
+

API Status: {{ tunnel_state.status_message }}

+ {% if tunnel_state.get('error') %} +
+ Error Details: +
{{ tunnel_state.error }}
+
+ {% endif %} +
+
+ +
+

Tunnel Details

+
+

Desired Name: {{ tunnel_state.name }}

+

Tunnel ID: {{ tunnel_state.id if tunnel_state.id else 'N/A' }}

+ {% if not external_cloudflared %} +
+ Tunnel Token: +
{{ display_token }}
+
+ {% endif %} +
+
+ +
+

Agent Control

+ {% if agent_state.last_action_status %} + {% set action_box_class = 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' %} + {% if 'Error:' in agent_state.last_action_status %} + {% set action_box_class = 'border-red-500 bg-red-50 dark:bg-red-900/20' %} + {% elif 'Warning:' in agent_state.last_action_status %} + {% set action_box_class = 'border-amber-500 bg-amber-50 dark:bg-amber-900/20' %} + {% endif %} +
+

Last Action: {{ agent_state.last_action_status }}

+
+ {% endif %} + +
+ {% if external_cloudflared %} +
+
+ External Mode: + + + Using External Cloudflared + +
+
+ External ID: + + {{ external_tunnel_id }} + +
+
+

External cloudflared mode enabled. DockFlare manages tunnel configuration and DNS records, but not the agent container.

+
+
+ {% else %} +
+
+ Agent Status: + {% set indicator_class = 'bg-gray-400' %} + {% if agent_state.container_status == 'running' %} {% set indicator_class = 'bg-green-500 animate-pulse-fast' %} + {% elif agent_state.container_status in ['exited', 'dead', 'not_found'] %} {% set indicator_class = 'bg-red-500' %} + {% elif agent_state.container_status == 'docker_unavailable' %} {% set indicator_class = 'bg-gray-500' %} + {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} + {% set indicator_class = 'bg-amber-500' %} + {% endif %} + + + {% set status_text_class = 'text-gray-600 dark:text-gray-400' %} + {% if agent_state.container_status=='running' %} {% set status_text_class = 'text-green-600 dark:text-green-400' %} + {% elif agent_state.container_status in ['exited', 'dead', 'not_found', 'docker_unavailable'] %} {% set status_text_class = 'text-red-600 dark:text-red-400' %} + {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} + {% set status_text_class = 'text-amber-600 dark:text-amber-400' %} + {% endif %} + + {{ agent_state.container_status.replace('_',' ') if agent_state.container_status else 'Unknown' }} + +
+
+ Agent Name: + + {{ cloudflared_container_name }} + +
+
+ +
+ {% set base_button_style = 'w-full sm:w-auto inline-flex items-center justify-center px-5 py-2 rounded-md text-white font-semibold shadow-md transition-all duration-200 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none' %} + {% set start_button_style = 'bg-green-600 hover:bg-green-700 focus:ring-green-500 disabled:bg-green-300 dark:disabled:bg-green-800' %} + {% set stop_button_style = 'bg-red-600 hover:bg-red-700 focus:ring-red-500 disabled:bg-red-300 dark:disabled:bg-red-800' %} + {% set is_startable = tunnel_state.get('id') and tunnel_state.get('token') and docker_available %} + {% set is_stoppable = docker_available %} + + {% if agent_state.container_status=='running' %} +
+ +
+ {% else %} +
+ +
+ {% endif %} +
+ {% endif %} +
+
+
+
+ +
+
+

+ Managed Ingress Rules +

+ + {% if rules %} +
+ + + + + + + + + + + + + + + + + + + + {% for hostname, details in rules.items()|sort %} + + + + + + + + + {% endfor %} + +
HostnameService TargetStatusContainerDelete Scheduled (UTC)Actions+/-Tunnel NameTunnel IDStatusCreated AtConnectionsVersion
+ + {{ hostname }} + + {% if hostname.startswith('*.') %} + + wildcard + + {% endif %} + + {{ details.service }} + + {% set status_text_class = 'text-orange-600 dark:text-orange-400' if 'pending' in details.status else 'text-green-600 dark:text-green-400' %} + {% set status_bg_class = 'bg-orange-100 dark:bg-orange-900/30' if 'pending' in details.status else 'bg-green-100 dark:bg-green-900/30' %} + + {{ details.status.replace('_',' ') }} + + + + {{ details.container_id[:12] if details.container_id else 'N/A' }} + + + {% if details.status=='pending_deletion' and details.delete_at %} +
+ {{ details.delete_at.strftime('%H:%M %d-%b-%Y') }} + (calculating...) +
+ {% else %} + N/A + {% endif %} +
+
+ +
+
+
+ {% else %} +

No ingress rules are currently being managed.

+ {% endif %} +
+
+ +
+
+

+ All Cloudflare Tunnels on Account +

+ + {% if CF_ACCOUNT_ID_CONFIGURED %} +

+ Displaying tunnels for Cloudflare Account ID: {{ ACCOUNT_ID_FOR_DISPLAY | e }}. +
+ This list shows all tunnels found on the account, not just the one managed by this DockFlare instance. +

+ {% if all_account_tunnels is defined and all_account_tunnels %} +
+ + + + + + + + + + + + + {% for tunnel in all_account_tunnels %} + + + + + + + + + + + + + + {% endfor %} + +
Tunnel NameTunnel IDStatusCreated AtConnectionsVersion
+ +
+ {{ tunnel.name | e }} + + {{ tunnel.name | e }} + + {{ tunnel.id | e }} + + + {{ tunnel.status | capitalize | e }} + + + {% if tunnel.created_at %} + {{ tunnel.created_at.split('T')[0] | e }} + {% else %} + N/A + {% endif %} + + {% if tunnel.connections and tunnel.connections is iterable and not tunnel.connections is string %} + {% for conn in tunnel.connections %} + {{ conn.colo_name | e }} + {% endfor %} + {% if tunnel.connections | length > 0 %} + ({{ tunnel.connections | length }} total) + {% else %} + (0 total) + {% endif %} + {% elif tunnel.connections %} + {{ tunnel.connections | e }} + {% else %} + None + {% endif %} + + {{ tunnel.version | e or 'N/A' }} +
+
+ {% elif all_account_tunnels is defined %} +

No Cloudflare Tunnels found for Account ID: {{ ACCOUNT_ID_FOR_DISPLAY | e }}.

+

This could also mean an error occurred fetching them (check DockFlare logs). Ensure your CF_API_TOKEN has 'Account:Cloudflare Tunnel:Read' permission for the account level.

+ {% else %} +

Could not retrieve tunnel information (all_account_tunnels variable not available in template).

+ {% endif %} + {% else %} +

+ The CF_ACCOUNT_ID environment variable is not configured. This section cannot be displayed. +

+ {% endif %} +
+
+ +
+
+

+ Real-time Activity Logs +

+
Connecting to log stream...
+
+
+ +
+ + + + + + + \ No newline at end of file diff --git a/templates/status_page.html b/templates/status_page.html index afb3400..2377269 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -27,43 +27,28 @@ (function() { const currentProtocol = window.location.protocol; const currentHost = window.location.host; - console.log("Page loading with protocol:", currentProtocol); - function fixResources() { - console.log("Fixing resource protocols for reverse proxy compatibility"); - document.querySelectorAll('link[rel="stylesheet"]').forEach(function(link) { const href = link.getAttribute('href'); if (href && href.startsWith('http:') && window.location.protocol === 'https:') { - const newHref = href.replace('http:', 'https:'); - console.log(`Fixing stylesheet: ${href} → ${newHref}`); - link.setAttribute('href', newHref); + link.setAttribute('href', href.replace('http:', 'https:')); } }); - document.querySelectorAll('script[src]').forEach(function(script) { const src = script.getAttribute('src'); if (src && src.startsWith('http:') && window.location.protocol === 'https:') { - const newSrc = src.replace('http:', 'https:'); - console.log(`Fixing script: ${src} → ${newSrc}`); - script.setAttribute('src', newSrc); + script.setAttribute('src', src.replace('http:', 'https:')); } }); - document.querySelectorAll('link[rel="preconnect"]').forEach(function(link) { const href = link.getAttribute('href'); if (href && href.startsWith('http:') && window.location.protocol === 'https:') { - const newHref = href.replace('http:', 'https:'); - console.log(`Fixing preconnect: ${href} → ${newHref}`); - link.setAttribute('href', newHref); + link.setAttribute('href', href.replace('http:', 'https:')); } }); } - fixResources(); - document.addEventListener('DOMContentLoaded', fixResources); - const origFetch = window.fetch; window.fetch = function(url, options) { if (url && typeof url === 'string' && url.startsWith('http')) { @@ -73,29 +58,21 @@ urlObj.protocol = currentProtocol; return origFetch.call(this, urlObj.toString(), options); } - } catch (e) { - console.error('Error processing URL:', e); - } + } catch (e) { console.error('Error processing URL:', e); } } return origFetch.call(this, url, options); }; - const baseTag = document.createElement('base'); baseTag.href = currentProtocol + '//' + currentHost + '/'; document.head.appendChild(baseTag); - const preconnectDomains = ['cdn.tailwindcss.com', 'rsms.me']; preconnectDomains.forEach(domain => { const existingPreconnect = Array.from(document.querySelectorAll('link[rel="preconnect"]')) .find(link => link.href.includes(domain)); - if (existingPreconnect) { const href = existingPreconnect.getAttribute('href'); - if (href && href.startsWith('http') && - new URL(href).protocol !== currentProtocol) { - const newHref = currentProtocol + '//' + new URL(href).host; - console.log(`Updating preconnect: ${href} → ${newHref}`); - existingPreconnect.setAttribute('href', newHref); + if (href && href.startsWith('http') && new URL(href).protocol !== currentProtocol) { + existingPreconnect.setAttribute('href', currentProtocol + '//' + new URL(href).host); } } }); @@ -104,19 +81,12 @@ - - - - + + .connection-badge { display: inline-block; padding: 0.2em 0.4em; font-size: 85%; font-weight: 600; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.25rem; background-color: #6366f1; color: white; margin-right: 4px; margin-bottom: 4px; } + html.dark .connection-badge { background-color: #818cf8; } + .warning-text { color: #ef4444; font-weight: bold; } + html.dark .warning-text { color: #f87171; } + @@ -179,7 +153,6 @@

Tunnel & Agent Status

-

Cloudflare API & Tunnel Setup

{% set status_box_class = 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' %} @@ -198,7 +171,6 @@ {% endif %}

-

Tunnel Details

@@ -212,7 +184,6 @@ {% endif %}
-

Agent Control

{% if agent_state.last_action_status %} @@ -226,22 +197,17 @@

Last Action: {{ agent_state.last_action_status }}

{% endif %} -
{% if external_cloudflared %}
External Mode: - - Using External Cloudflared - + Using External Cloudflared
External ID: - - {{ external_tunnel_id }} - + {{ external_tunnel_id }}

External cloudflared mode enabled. DockFlare manages tunnel configuration and DNS records, but not the agent container.

@@ -255,36 +221,27 @@ {% if agent_state.container_status == 'running' %} {% set indicator_class = 'bg-green-500 animate-pulse-fast' %} {% elif agent_state.container_status in ['exited', 'dead', 'not_found'] %} {% set indicator_class = 'bg-red-500' %} {% elif agent_state.container_status == 'docker_unavailable' %} {% set indicator_class = 'bg-gray-500' %} - {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} - {% set indicator_class = 'bg-amber-500' %} + {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} {% set indicator_class = 'bg-amber-500' %} {% endif %} - {% set status_text_class = 'text-gray-600 dark:text-gray-400' %} {% if agent_state.container_status=='running' %} {% set status_text_class = 'text-green-600 dark:text-green-400' %} {% elif agent_state.container_status in ['exited', 'dead', 'not_found', 'docker_unavailable'] %} {% set status_text_class = 'text-red-600 dark:text-red-400' %} - {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} - {% set status_text_class = 'text-amber-600 dark:text-amber-400' %} + {% elif agent_state.container_status in ['created', 'paused', 'restarting'] %} {% set status_text_class = 'text-amber-600 dark:text-amber-400' %} {% endif %} - - {{ agent_state.container_status.replace('_',' ') if agent_state.container_status else 'Unknown' }} - + {{ agent_state.container_status.replace('_',' ') if agent_state.container_status else 'Unknown' }}
Agent Name: - - {{ cloudflared_container_name }} - + {{ cloudflared_container_name }}
-
{% set base_button_style = 'w-full sm:w-auto inline-flex items-center justify-center px-5 py-2 rounded-md text-white font-semibold shadow-md transition-all duration-200 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none' %} {% set start_button_style = 'bg-green-600 hover:bg-green-700 focus:ring-green-500 disabled:bg-green-300 dark:disabled:bg-green-800' %} {% set stop_button_style = 'bg-red-600 hover:bg-red-700 focus:ring-red-500 disabled:bg-red-300 dark:disabled:bg-red-800' %} {% set is_startable = tunnel_state.get('id') and tunnel_state.get('token') and docker_available %} {% set is_stoppable = docker_available %} - {% if agent_state.container_status=='running' %}
@@ -306,7 +263,6 @@

Managed Ingress Rules

- {% if rules %}
@@ -318,13 +274,6 @@ - - - - - - - @@ -335,43 +284,27 @@ {{ hostname }} {% if hostname.startswith('*.') %} - - wildcard - + wildcard {% endif %} - + - + @@ -390,7 +323,6 @@

All Cloudflare Tunnels on Account

- {% if CF_ACCOUNT_ID_CONFIGURED %}

Displaying tunnels for Cloudflare Account ID: {{ ACCOUNT_ID_FOR_DISPLAY | e }}. @@ -398,10 +330,11 @@ This list shows all tunnels found on the account, not just the one managed by this DockFlare instance.

{% if all_account_tunnels is defined and all_account_tunnels %} -
+
Container Delete Scheduled (UTC) Actions+/-Tunnel NameTunnel IDStatusCreated AtConnectionsVersion
- {{ details.service }} - {{ details.service }} {% set status_text_class = 'text-orange-600 dark:text-orange-400' if 'pending' in details.status else 'text-green-600 dark:text-green-400' %} {% set status_bg_class = 'bg-orange-100 dark:bg-orange-900/30' if 'pending' in details.status else 'bg-green-100 dark:bg-green-900/30' %} - - {{ details.status.replace('_',' ') }} - - - - {{ details.container_id[:12] if details.container_id else 'N/A' }} - + {{ details.status.replace('_',' ') }} {{ details.container_id[:12] if details.container_id else 'N/A' }} {% if details.status=='pending_deletion' and details.delete_at %}
{{ details.delete_at.strftime('%H:%M %d-%b-%Y') }} (calculating...)
- {% else %} - N/A - {% endif %} + {% else %} N/A {% endif %}
- +
+ @@ -423,57 +356,33 @@ - - - - - - + + - + + + {% endfor %} @@ -487,9 +396,7 @@

Could not retrieve tunnel information (all_account_tunnels variable not available in template).

{% endif %} {% else %} -

- The CF_ACCOUNT_ID environment variable is not configured. This section cannot be displayed. -

+

The CF_ACCOUNT_ID environment variable is not configured. This section cannot be displayed.

{% endif %} @@ -502,382 +409,37 @@
Connecting to log stream...
- - \ No newline at end of file From 461ee6d373dc59c9357d0db92ed24bae9cd835eb Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 19:17:56 +0200 Subject: [PATCH 07/19] bugfixing --- templates/status_page.html | 90 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/templates/status_page.html b/templates/status_page.html index 2377269..5c32a2d 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -334,7 +334,7 @@
+/- Tunnel Name Tunnel ID Status
- {{ tunnel.name | e }} - - {{ tunnel.name | e }} - - {{ tunnel.id | e }} - {{ tunnel.name | e }}{{ tunnel.id | e }} + {% else %} bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 {% endif %}"> {{ tunnel.status | capitalize | e }} - {% if tunnel.created_at %} - {{ tunnel.created_at.split('T')[0] | e }} - {% else %} - N/A - {% endif %} + {% if tunnel.created_at %}{{ tunnel.created_at.split('T')[0] | e }}{% else %}N/A{% endif %} {% if tunnel.connections and tunnel.connections is iterable and not tunnel.connections is string %} - {% for conn in tunnel.connections %} - {{ conn.colo_name | e }} - {% endfor %} - {% if tunnel.connections | length > 0 %} - ({{ tunnel.connections | length }} total) - {% else %} - (0 total) - {% endif %} - {% elif tunnel.connections %} - {{ tunnel.connections | e }} - {% else %} - None - {% endif %} + {% for conn in tunnel.connections %}{{ conn.colo_name | e }}{% endfor %} + {% if tunnel.connections | length > 0 %}({{ tunnel.connections | length }} total){% else %}(0 total){% endif %} + {% elif tunnel.connections %}{{ tunnel.connections | e }}{% else %}None{% endif %} - {{ tunnel.version | e or 'N/A' }} + {{ tunnel.version | e or 'N/A' }}
- + @@ -429,6 +429,7 @@ function updateCountdowns() {const countdownElements = document.querySelectorAll('.countdown-timer'); if (!countdownElements.length) return;const now = new Date();countdownElements.forEach(span => {const td = span.closest('td[data-delete-at]'); if (!td) return;const deleteAtISO = td.dataset.deleteAt; if (!deleteAtISO) return;try {const targetDate = new Date(deleteAtISO); if (isNaN(targetDate.getTime())) throw new Error("Invalid date parsed");const diff = targetDate - now; span.textContent = `(${formatTimeDifference(diff)})`;if (diff < 0) span.classList.add('text-red-500'); else span.classList.remove('text-red-500');} catch (e) { span.textContent = "(invalid date)"; }});} function startServerPing() {if (pingInterval) clearInterval(pingInterval);pingInterval = setInterval(() => {const currentProtocol = window.location.protocol; const currentHost = window.location.host;const pingUrl = `${currentProtocol}//${currentHost}/ping?t=${new Date().getTime()}`;fetch(pingUrl).then(response => response.ok ? response.json() : Promise.reject(`Ping failed: ${response.status}`)).then(data => {}).catch(error => {});}, 30000);} function updateReconciliationStatus() {const currentProtocol = window.location.protocol;const currentHost = window.location.host;const statusUrl = `${currentProtocol}//${currentHost}/reconciliation-status?t=${new Date().getTime()}`;fetch(statusUrl).then(response => response.json()).then(data => {const statusElement = document.getElementById('reconciliation-status');const messageElement = document.getElementById('reconciliation-status-message');if (!statusElement || !messageElement) {return;}if (data.status) {messageElement.textContent = data.status;messageElement.style.display = data.in_progress ? 'block' : 'none';}if (data.in_progress) {statusElement.innerHTML = `

Reconciliation: ${data.progress}%

Processing ${data.processed_items} of ${data.total_items} items...

`;} else if (statusElement.innerHTML.includes('Reconciliation')) {statusElement.innerHTML = `

Reconciliation complete

`;setTimeout(() => {if (statusElement.innerHTML.includes('Reconciliation complete')) {statusElement.innerHTML = '';if (messageElement) {messageElement.style.display = 'none';}}}, 5000);}}).catch(err => {});} + document.addEventListener('DOMContentLoaded', function() { const currentProtocol = window.location.protocol;const currentHost = window.location.host; document.querySelectorAll('form.protocol-aware-form').forEach(form => {if (form.getAttribute('action')) {let actionUrl = form.getAttribute('action');if (actionUrl.startsWith('/')) {actionUrl = `${currentProtocol}//${currentHost}${actionUrl}`; form.setAttribute('action', actionUrl);} else if (actionUrl.startsWith('http')) {try {const parsedUrl = new URL(actionUrl);if (parsedUrl.host === currentHost && parsedUrl.protocol !== currentProtocol) {parsedUrl.protocol = currentProtocol; form.setAttribute('action', parsedUrl.toString());}} catch (e) {}}}}); @@ -437,8 +438,91 @@ const themeToggleButton = document.getElementById('theme-toggle');const htmlElement = document.documentElement; if (themeToggleButton) {const themeToggleIcon = themeToggleButton.querySelector('span');const setTheme = (theme) => {if (theme !== 'light' && theme !== 'dark') { return; }localStorage.setItem('theme', theme);if (theme === 'dark') {htmlElement.classList.add('dark'); if (themeToggleIcon) themeToggleIcon.textContent = '☀️';} else {htmlElement.classList.remove('dark'); if (themeToggleIcon) themeToggleIcon.textContent = '🌓';}};const toggleTheme = () => {const isCurrentlyDark = htmlElement.classList.contains('dark');const currentTheme = isCurrentlyDark ? 'dark' : 'light'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; setTheme(newTheme);};themeToggleButton.addEventListener('click', toggleTheme);const initTheme = () => {const savedTheme = localStorage.getItem('theme'); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;if (savedTheme === 'dark' || savedTheme === 'light') setTheme(savedTheme);else setTheme(prefersDark ? 'dark' : 'light');};initTheme();} updateReconciliationStatus();setInterval(updateReconciliationStatus, 2000); - document.querySelectorAll('.tunnel-dns-toggle').forEach(button => {button.addEventListener('click', async function() {const tunnelId = this.dataset.tunnelId;const tunnelDetailsRow = this.closest('tr'); const dnsRecordsDisplayRow = tunnelDetailsRow.nextElementSibling; const targetDivId = this.getAttribute('aria-controls'); const targetDiv = document.getElementById(targetDivId); const isExpanded = this.getAttribute('aria-expanded') === 'true';const expandIcon = this.querySelector('.expand-icon');const collapseIcon = this.querySelector('.collapse-icon');if (!dnsRecordsDisplayRow || !dnsRecordsDisplayRow.classList.contains('dns-records-row')) {console.error(`Could not find the dedicated DNS records row for tunnel ${tunnelId}.`); return;}if (!targetDiv) {console.error(`Could not find targetDiv with ID ${targetDivId} for tunnel ${tunnelId}`);return;}if (isExpanded) {dnsRecordsDisplayRow.classList.add('hidden');this.setAttribute('aria-expanded', 'false');if (expandIcon) expandIcon.classList.remove('hidden');if (collapseIcon) collapseIcon.classList.add('hidden');} else {this.setAttribute('aria-expanded', 'true');if (expandIcon) expandIcon.classList.add('hidden');if (collapseIcon) collapseIcon.classList.remove('hidden');if (targetDiv.dataset.loaded !== 'true' || targetDiv.dataset.loaded === 'error') {targetDiv.innerHTML = '

Loading DNS records...

';dnsRecordsDisplayRow.classList.remove('hidden'); try {const currentProtocol = window.location.protocol;const currentHost = window.location.host;const fetchUrl = `${currentProtocol}//${currentHost}/tunnel-dns-records/${encodeURIComponent(tunnelId)}?t=${Date.now()}`;const response = await fetch(fetchUrl);if (!response.ok) {let errorDetail = `HTTP error ${response.status}`;try {const errorData = await response.json();errorDetail = errorData.error || errorData.message || errorDetail;} catch (e) {}throw new Error(errorDetail);}const data = await response.json();const currentTargetDiv = document.getElementById(`dns-records-${tunnelId}`);if (!currentTargetDiv) {console.error("PANIC: Target DIV disappeared or was wrong during fetch for tunnel " + tunnelId);return;}if (data.dns_records && data.dns_records.length > 0) {let dnsHtml = '
    ';data.dns_records.forEach(record => {const recordUrl = `https://${record.name}`;dnsHtml += `
  • ${record.name}
  • `;});dnsHtml += '
';currentTargetDiv.innerHTML = dnsHtml;currentTargetDiv.dataset.loaded = 'true';} else if (data.message) {currentTargetDiv.innerHTML = `

${data.message}

`;currentTargetDiv.dataset.loaded = 'info';} else {currentTargetDiv.innerHTML = '

No CNAME DNS records found pointing to this tunnel in the configured zones.

';currentTargetDiv.dataset.loaded = 'true';}} catch (error) {const errorTargetDiv = document.getElementById(`dns-records-${tunnelId}`);if (errorTargetDiv) {errorTargetDiv.innerHTML = `

Error loading DNS records: ${error.message}

`;errorTargetDiv.dataset.loaded = 'error';}}}dnsRecordsDisplayRow.classList.remove('hidden');}}}); - }); + + // --- START: New JavaScript for Tunnel DNS Toggle --- + document.querySelectorAll('.tunnel-dns-toggle').forEach(button => { + button.addEventListener('click', async function() { + const tunnelId = this.dataset.tunnelId; + const tunnelDetailsRow = this.closest('tr'); + const dnsRecordsDisplayRow = tunnelDetailsRow.nextElementSibling; + const targetDivId = this.getAttribute('aria-controls'); + const targetDiv = document.getElementById(targetDivId); + const isExpanded = this.getAttribute('aria-expanded') === 'true'; + const expandIcon = this.querySelector('.expand-icon'); + const collapseIcon = this.querySelector('.collapse-icon'); + + if (!dnsRecordsDisplayRow || !dnsRecordsDisplayRow.classList.contains('dns-records-row')) { + console.error(`Could not find the dedicated DNS records row for tunnel ${tunnelId}. Button was in:`, tunnelDetailsRow, "Next sibling is:", dnsRecordsDisplayRow); + return; + } + if (!targetDiv) { + console.error(`Could not find targetDiv with ID ${targetDivId} for tunnel ${tunnelId}`); + return; + } + + if (isExpanded) { + dnsRecordsDisplayRow.classList.add('hidden'); + this.setAttribute('aria-expanded', 'false'); + if (expandIcon) expandIcon.classList.remove('hidden'); + if (collapseIcon) collapseIcon.classList.add('hidden'); + } else { + this.setAttribute('aria-expanded', 'true'); + if (expandIcon) expandIcon.classList.add('hidden'); + if (collapseIcon) collapseIcon.classList.remove('hidden'); + + if (targetDiv.dataset.loaded !== 'true' || targetDiv.dataset.loaded === 'error') { + targetDiv.innerHTML = '

Loading DNS records...

'; + dnsRecordsDisplayRow.classList.remove('hidden'); + + try { + const currentProtocol = window.location.protocol; + const currentHost = window.location.host; + const fetchUrl = `${currentProtocol}//${currentHost}/tunnel-dns-records/${encodeURIComponent(tunnelId)}?t=${Date.now()}`; + + const response = await fetch(fetchUrl); + if (!response.ok) { + let errorDetail = `HTTP error ${response.status}`; + try { const errorData = await response.json(); errorDetail = errorData.error || errorData.message || errorDetail; } catch (e) {} + throw new Error(errorDetail); + } + const data = await response.json(); + const currentTargetDiv = document.getElementById(`dns-records-${tunnelId}`); + if (!currentTargetDiv) { + console.error("PANIC: Target DIV disappeared or was wrong during fetch for tunnel " + tunnelId); + return; + } + + if (data.dns_records && data.dns_records.length > 0) { + let dnsHtml = '
    '; + data.dns_records.forEach(record => { + const recordUrl = `https://${record.name}`; + dnsHtml += `
  • ${record.name}
  • `; + }); + dnsHtml += '
'; + currentTargetDiv.innerHTML = dnsHtml; + currentTargetDiv.dataset.loaded = 'true'; + } else if (data.message) { + currentTargetDiv.innerHTML = `

${data.message}

`; + currentTargetDiv.dataset.loaded = 'info'; + } else { + currentTargetDiv.innerHTML = '

No CNAME DNS records found pointing to this tunnel in the configured zones.

'; + currentTargetDiv.dataset.loaded = 'true'; + } + } catch (error) { + const errorTargetDiv = document.getElementById(`dns-records-${tunnelId}`); + if (errorTargetDiv) { + errorTargetDiv.innerHTML = `

Error loading DNS records: ${error.message}

`; + errorTargetDiv.dataset.loaded = 'error'; + } + } + } + dnsRecordsDisplayRow.classList.remove('hidden'); + } + }); + }); + // --- END: New JavaScript for Tunnel DNS Toggle --- + + }); startServerPing(); From f6c7c3298bfb0e824ec449575e0adf13c44a1133 Mon Sep 17 00:00:00 2001 From: ChrispyBacon-dev Date: Sat, 10 May 2025 19:33:02 +0200 Subject: [PATCH 08/19] minor change removing version in table --- templates/status_page copy.html | 3 --- templates/status_page.html | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/templates/status_page copy.html b/templates/status_page copy.html index afb3400..573f890 100644 --- a/templates/status_page copy.html +++ b/templates/status_page copy.html @@ -472,9 +472,6 @@ None {% endif %} - {% endfor %} diff --git a/templates/status_page.html b/templates/status_page.html index 5c32a2d..69a3cf6 100644 --- a/templates/status_page.html +++ b/templates/status_page.html @@ -340,7 +340,6 @@ - @@ -379,7 +378,7 @@ - -
+/-+/- Tunnel Name Tunnel ID Status - {{ tunnel.version | e or 'N/A' }} -
Status Created At ConnectionsVersion
{{ tunnel.version | e or 'N/A' }}