mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
819 lines
36 KiB
HTML
819 lines
36 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<title>AfterTouch (SoundTouch Toolkit)</title>
|
|
<link rel="icon" href="/web/img/favicon-braille.svg" type="image/svg+xml"/>
|
|
<link rel="stylesheet" href="/web/css/style.css"/>
|
|
<script src="/web/js/diff.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<h1><img src="/web/img/favicon-braille.svg" alt="AfterTouch Logo" class="logo"/>AfterTouch</h1>
|
|
<p class="subtitle">
|
|
Bose SoundTouch Toolkit
|
|
</p>
|
|
|
|
<div class="tabs">
|
|
<div class="tab-buttons">
|
|
<button class="tab-btn active" onclick="openTab(event, 'tab-overview')">
|
|
Overview
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-settings')">
|
|
1. Settings
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-devices')">
|
|
2. Devices
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-sync')">
|
|
3. Data Sync
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-migration')">
|
|
4. Migration
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-interactions')">
|
|
5. Interactions & Events
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-parity')">
|
|
6. Parity & Mirroring
|
|
</button>
|
|
<button class="tab-btn" onclick="openTab(event, 'tab-account')">
|
|
7. Local Account
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab 0: Overview -->
|
|
<div id="tab-overview" class="tab-content active">
|
|
<h2>Welcome to AfterTouch</h2>
|
|
<p>
|
|
This toolkit helps you keep your Bose SoundTouch speakers
|
|
functional even after the Bose Cloud shutdown in May 2026.
|
|
It emulates the necessary cloud services locally on your
|
|
network.
|
|
</p>
|
|
|
|
<h3>Migration Process at a Glance</h3>
|
|
<div class="info-box prerequisite-box">
|
|
<strong>🔌 Prerequisite: Enable SSH</strong><br/>
|
|
Migration requires SSH access. To enable it:
|
|
<ol class="list-compact">
|
|
<li>
|
|
Create an empty file named
|
|
<code>remote_services</code> on a USB stick.
|
|
</li>
|
|
<li>
|
|
Insert it into the speaker's
|
|
<strong>SERVICE</strong> port and reboot the
|
|
speaker.
|
|
</li>
|
|
</ol>
|
|
<strong>Verify connection:</strong>
|
|
<ul class="list-compact list-indented">
|
|
<li>
|
|
Use the <strong>Migration</strong> tab to select
|
|
your device and verify that
|
|
<em>SSH Connection</em> shows ✅ Success.
|
|
</li>
|
|
<li>
|
|
Or manually:
|
|
<code
|
|
>ssh -oHostKeyAlgorithms=+ssh-rsa
|
|
root@<SPEAKER-IP></code
|
|
>
|
|
(no password).
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<ol class="guide-steps">
|
|
<li>
|
|
<strong>Settings:</strong> Review the
|
|
<strong>Settings</strong> tab. Ensure the "Target
|
|
Domain" and "Proxy Domain" use an IP address or domain
|
|
name that is
|
|
<strong>accessible from your speakers</strong> (usually
|
|
the IP of this server on your local network).
|
|
</li>
|
|
<li>
|
|
<strong>Discovery:</strong> Go to the
|
|
<strong>Devices</strong> tab to find your speakers on
|
|
the network. Ensure your speakers are powered on and
|
|
connected to the same network.
|
|
</li>
|
|
<li>
|
|
<strong>Data Sync:</strong> In the
|
|
<strong>Data Sync</strong> tab, fetch your current
|
|
presets, recents, and sources. This step is critical to
|
|
ensure your local service has all your personalized data
|
|
before you disconnect from the Bose cloud.
|
|
</li>
|
|
<li>
|
|
<strong>Migration:</strong> In the
|
|
<strong>Migration</strong> tab, redirect your speaker to
|
|
this local service. We recommend the
|
|
<strong>XML Configuration</strong> method as it is
|
|
surgical and easily reversible.
|
|
</li>
|
|
<li>
|
|
<strong>Verification:</strong> After migration and
|
|
reboot, your speaker will communicate with this toolkit
|
|
instead of Bose servers.
|
|
</li>
|
|
</ol>
|
|
|
|
<div class="info-box safety-box">
|
|
<strong>⚠️ Safety First:</strong> Before starting any
|
|
migration, please read our
|
|
<a href="https://gesellix.github.io/Bose-SoundTouch/guides/MIGRATION-SAFETY.html" target="_blank">Professional
|
|
Migration & Safety Guide</a>. The toolkit automatically creates backups, but understanding the process
|
|
is key to a smooth transition.
|
|
</div>
|
|
|
|
<h3>Useful Links</h3>
|
|
<ul>
|
|
<li><a href="https://gesellix.github.io/Bose-SoundTouch/guides/SURVIVAL-GUIDE.html" target="_blank">Cloud
|
|
Shutdown Survival Guide</a></li>
|
|
<li><a href="https://gesellix.github.io/Bose-SoundTouch/guides/CLI-REFERENCE.html" target="_blank">CLI
|
|
Reference</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Tab 1: Settings -->
|
|
<div id="tab-settings" class="tab-content">
|
|
<h2>System Settings</h2>
|
|
<div class="info-box safety-box mb-lg">
|
|
<strong>Note:</strong> These URLs must be
|
|
<strong>accessible from your SoundTouch devices</strong>.
|
|
Use the IP address of this server on your local network
|
|
(e.g., <code>http://192.168.1.100:8000</code>) rather than
|
|
<code>localhost</code>.
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="target-domain">Target Domain:</label>
|
|
<input type="text" id="target-domain" placeholder="http://192.168.x.x:8000"/>
|
|
<span class="help-text">Standard services URL</span>
|
|
</div>
|
|
<div class="form-inline form-group">
|
|
<label for="discovery-interval">Discovery Interval:</label>
|
|
<input type="text" id="discovery-interval" placeholder="5m" class="w-100"/>
|
|
<label><input type="checkbox" id="discovery-enabled"/> Enable Automated Discovery</label>
|
|
</div>
|
|
<div class="form-group">
|
|
<strong>DNS Discovery:</strong>
|
|
<div class="mt-sm">
|
|
<label class="mb-sm visible">
|
|
<input type="checkbox" id="dns-enabled"/> Enable
|
|
DNS Discovery Server
|
|
</label>
|
|
<div class="ml-lg mb-sm">
|
|
<div class="form-inline">
|
|
<label for="dns-upstream">Upstream DNS:</label>
|
|
<input
|
|
type="text"
|
|
id="dns-upstream"
|
|
placeholder="Default: system nameservers"
|
|
class="w-200"
|
|
/>
|
|
<span
|
|
class="info-toggle"
|
|
onclick="toggleInfo('dns-upstream-info')"
|
|
>ⓘ</span
|
|
>
|
|
</div>
|
|
<div id="dns-upstream-info" class="info-details">
|
|
Optional: comma-separated list of DNS servers
|
|
(e.g., <code>1.1.1.1, 8.8.8.8</code>).<br/>
|
|
If empty, AfterTouch defaults to the system
|
|
nameservers (e.g. from
|
|
<code>/etc/resolv.conf</code>).<br/>
|
|
<div
|
|
id="dns-current-upstream"
|
|
class="mt-sm fw-bold"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
<div class="ml-lg">
|
|
<div class="form-inline">
|
|
<label for="dns-bind">DNS Bind Address:</label>
|
|
<input
|
|
type="text"
|
|
id="dns-bind"
|
|
placeholder=":53"
|
|
class="w-100"
|
|
/>
|
|
<span class="help-text">
|
|
(e.g., :53 or 0.0.0.0:53.
|
|
<strong>Port 53</strong> is required for actual
|
|
migration)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<strong>Endpoint Mirroring:</strong>
|
|
<div class="mt-sm">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="mirror-enabled"/> Enable
|
|
Background Mirroring to Bose Cloud
|
|
</label>
|
|
<div class="ml-lg mb-sm">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="preferred-source-upstream"/>
|
|
Prefer Upstream Response for Mirrored Endpoints
|
|
</label>
|
|
<label for="mirror-endpoints">Mirror Endpoints (one per line, supports * wildcards):</label>
|
|
<textarea
|
|
id="mirror-endpoints"
|
|
rows="4"
|
|
placeholder="/streaming/account/*/device/*/recent /accounts/*/devices/*/presets/*"
|
|
></textarea>
|
|
|
|
<label for="skip-mirror-endpoints" class="mt-sm visible">Skip Mirror Endpoints (local only, one per line):</label>
|
|
<textarea
|
|
id="skip-mirror-endpoints"
|
|
rows="2"
|
|
placeholder="/oauth/device/*/music/musicprovider/*/token/cs3 /oauth/device/*/music/musicprovider/*/token"
|
|
></textarea>
|
|
<div class="info-box safety-box mt-sm font-size-sm p-sm">
|
|
<strong>Note:</strong> Mirroring sends matching
|
|
requests (including full headers) to the
|
|
official Bose servers for parity comparison. If
|
|
<em>Redact Sensitive Data</em> is enabled in
|
|
Proxy Settings, credentials will be masked in
|
|
<strong>logs and recordings</strong>, but full
|
|
headers are always sent to Bose to ensure
|
|
service compatibility.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<strong>Spotify Integration:</strong>
|
|
<div id="spotify-config-status" class="help-text mt-sm">
|
|
Checking configuration...
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<strong>Proxy Logging:</strong>
|
|
<div class="mt-sm">
|
|
<label class="checkbox-label"><input type="checkbox" id="proxy-redact" onchange="updateProxySettings()"/> Redact Sensitive Headers</label>
|
|
<label class="checkbox-label"><input type="checkbox" id="proxy-log-body" onchange="updateProxySettings()"/> Log Bodies</label>
|
|
<label class="checkbox-label"><input type="checkbox" id="proxy-record" onchange="updateProxySettings()"/> Record Interactions <span class="help-text ml-sm visible">(View in <strong>5. Interactions</strong> tab)</span></label>
|
|
|
|
<div class="ml-lg mt-sm">
|
|
<label for="internal-paths">Internal Paths (skip recording for these patterns):</label>
|
|
<textarea
|
|
id="internal-paths"
|
|
rows="2"
|
|
placeholder="/setup/* /web/*"
|
|
></textarea>
|
|
<span class="help-text">
|
|
Requests matching these patterns will be
|
|
excluded from recording. Use one pattern per
|
|
line.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group mt-lg">
|
|
<button class="btn-primary" onclick="updateSettings()">Save Settings</button>
|
|
<span id="settings-status" class="status"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 2: Devices -->
|
|
<div id="tab-devices" class="tab-content">
|
|
<div class="flex-between">
|
|
<h2>
|
|
Known Devices
|
|
<span id="discovery-indicator" class="help-text hidden ml-lg">🔍 Scanning...</span>
|
|
</h2>
|
|
<div id="spotify-status-header" class="spotify-badge hidden">
|
|
Spotify:
|
|
<span id="spotify-account-name" class="fw-bold">Not Linked</span>
|
|
<button id="link-spotify-btn" class="badge bg-spotify" onclick="linkSpotify()">
|
|
Link Account
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="device-list">Loading devices...</div>
|
|
<div class="form-inline mt-lg">
|
|
<button class="btn-primary" onclick="triggerDiscovery()">Scan Again</button>
|
|
<div class="form-inline">
|
|
<input type="text" id="add-manual-ip" placeholder="Manual IP (e.g. 192.168.1.100)" class="w-250"/>
|
|
<button onclick="addManualDevice()">Add Device</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 3: Data Sync -->
|
|
<div id="tab-sync" class="tab-content">
|
|
<h2>Initial Data Sync</h2>
|
|
<p>
|
|
Before migrating, fetch your presets, recents, and
|
|
configured sources from the device to ensure they are
|
|
available locally.
|
|
</p>
|
|
<div class="device-selection">
|
|
<label for="sync-device-list">Device:</label>
|
|
<select id="sync-device-list">
|
|
<option value="">-- Select a device --</option>
|
|
</select>
|
|
<button class="btn-primary" id="sync-now-btn" onclick="startSync()">Start Sync</button>
|
|
</div>
|
|
<div id="sync-status" class="status"></div>
|
|
<div id="sync-results" class="mt-lg hidden">
|
|
<h3>Sync Results</h3>
|
|
<div id="sync-log" class="console-box"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 4: Migration -->
|
|
<div id="tab-migration" class="tab-content">
|
|
<h2>Device Migration</h2>
|
|
<div class="device-selection">
|
|
<label for="migration-device-list">Device:</label>
|
|
<select
|
|
id="migration-device-list"
|
|
onchange="showSummary(this.value)"
|
|
>
|
|
<option value="">-- Select a device --</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="status" class="status"></div>
|
|
|
|
<div id="migration-fetch-status" class="status"></div>
|
|
|
|
<div id="command-output-box" class="summary-box hidden">
|
|
<h3>Command Output</h3>
|
|
<div id="command-output" class="console-box"></div>
|
|
</div>
|
|
|
|
<div
|
|
id="migration-summary"
|
|
class="summary-box hidden"
|
|
>
|
|
<div id="migration-summary-status" class="status"></div>
|
|
<h3>
|
|
Migration Summary for
|
|
<span id="summary-device-display"></span>
|
|
</h3>
|
|
<p>Migration Status: <span id="migration-status"></span></p>
|
|
<input type="hidden" id="summary-device-id"/>
|
|
<p>SSH Connection: <span id="ssh-status"></span></p>
|
|
<p id="original-config-status" class="hidden">
|
|
Backup: ✅ Found .original config at
|
|
<code
|
|
>/opt/Bose/etc/SoundTouchSdkPrivateCfg.xml.original</code
|
|
>
|
|
<button onclick="toggleOriginalConfig()">
|
|
Show Original Config
|
|
</button>
|
|
</p>
|
|
<p id="no-original-config-status" class="hidden">
|
|
Backup: ❌ Not found
|
|
<button id="backup-config-btn">
|
|
Backup Config Now
|
|
</button>
|
|
</p>
|
|
<p>
|
|
Remote Services Enabled:
|
|
<span id="remote-services-status"></span>
|
|
<span
|
|
id="remote-services-found"
|
|
class="col-remote-addr"
|
|
></span>
|
|
</p>
|
|
<p>
|
|
AfterTouch Local Root CA Trusted:
|
|
<span id="ca-trust-status"></span>
|
|
<button id="trust-ca-btn" class="btn-secondary hidden ml-lg font-size-sm p-sm">
|
|
Trust CA Now
|
|
</button>
|
|
</p>
|
|
|
|
<div id="connection-test" class="test-connection-box">
|
|
<strong>HTTPS Connection Test:</strong>
|
|
<span class="help-text-sm">Verify the device can reach the server over HTTPS.</span>
|
|
<div class="mt-md">
|
|
URL: <code id="test-url"></code>
|
|
</div>
|
|
<div class="mt-md">
|
|
<button id="test-connection-explicit-btn" class="btn-secondary p-sm font-size-sm">
|
|
Test with Explicit CA.crt
|
|
</button>
|
|
<button id="test-connection-trusted-btn" class="btn-secondary p-sm font-size-sm">
|
|
Test with Shared Trust Store
|
|
</button>
|
|
</div>
|
|
<div id="test-result" class="console-box mt-md hidden max-h-200"></div>
|
|
</div>
|
|
|
|
<div id="hosts-redirection-test" class="test-connection-box test-connection-box-warning hidden">
|
|
<strong>Preliminary /etc/hosts Test:</strong>
|
|
<span class="help-text-sm">Verify the device's /etc/hosts mechanism before full migration.</span>
|
|
<div class="mt-md">
|
|
Domain: <code>custom-test-api.bose.fake</code>
|
|
</div>
|
|
<div class="mt-md">
|
|
<button id="test-hosts-btn" class="btn-warning p-sm font-size-sm">
|
|
Test Hosts Redirection
|
|
</button>
|
|
</div>
|
|
<div id="hosts-test-result" class="console-box mt-md hidden max-h-200"></div>
|
|
</div>
|
|
|
|
<div id="dns-redirection-test" class="test-connection-box test-connection-box-success hidden">
|
|
<strong>Preliminary DNS Test:</strong>
|
|
<span class="help-text-sm">Verify the device can resolve domains via the AfterTouch DNS server.</span>
|
|
<div class="mt-md">
|
|
Domain: <code>aftertouch.test</code>
|
|
</div>
|
|
<div class="mt-md">
|
|
<button id="test-dns-btn" class="btn-success p-sm font-size-sm">
|
|
Test DNS Redirection
|
|
</button>
|
|
</div>
|
|
<div id="dns-test-result" class="console-box mt-md hidden max-h-200"></div>
|
|
</div>
|
|
|
|
<div class="summary-box mb-md">
|
|
<label for="migration-method"><strong>Migration Method:</strong></label>
|
|
<select id="migration-method" onchange="toggleMigrationMethod()" class="p-sm">
|
|
<option value="xml">XML Configuration (Recommended - redirects specific services)</option>
|
|
<option value="hosts">/etc/hosts + Root CA (Advanced - global redirection)</option>
|
|
<option value="resolv">/etc/resolv.conf (DHCP-Aware - Redirect via DNS Hook)</option>
|
|
</select>
|
|
<div id="dns-port-warning" class="mt-sm status-error hidden"></div>
|
|
</div>
|
|
|
|
<div id="current-resolv-pane" class="mb-md hidden">
|
|
<span class="config-header">Current /etc/resolv.conf</span>
|
|
<pre id="current-resolv-content"></pre>
|
|
</div>
|
|
|
|
<div id="original-config-pane" class="mb-md hidden">
|
|
<span class="config-header">Original Config (Backup)</span>
|
|
<pre id="original-config-content"></pre>
|
|
</div>
|
|
|
|
<div id="service-options" class="mb-md hidden">
|
|
<h4>Service Implementations</h4>
|
|
<table>
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Original URL</th>
|
|
<th>Implementation</th>
|
|
</tr>
|
|
<tr>
|
|
<td>Marge (Streaming)</td>
|
|
<td id="orig-marge">loading...</td>
|
|
<td>
|
|
<select id="opt-marge" onchange="refreshSummary()">
|
|
<option value="self">AfterTouch (Local Service)</option>
|
|
<option value="upstream">Upstream (Proxy via local service)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Stats</td>
|
|
<td id="orig-stats">loading...</td>
|
|
<td>
|
|
<select id="opt-stats" onchange="refreshSummary()">
|
|
<option value="self">AfterTouch (Local Service)</option>
|
|
<option value="upstream">Upstream (Proxy via local service)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Software Update</td>
|
|
<td id="orig-sw_update">loading...</td>
|
|
<td>
|
|
<select id="opt-sw_update" onchange="refreshSummary()">
|
|
<option value="self">AfterTouch (Local Service)</option>
|
|
<option value="upstream">Upstream (Proxy via local service)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>BMX (Registry)</td>
|
|
<td id="orig-bmx">loading...</td>
|
|
<td>
|
|
<select id="opt-bmx" onchange="refreshSummary()">
|
|
<option value="self">AfterTouch (Local Service)</option>
|
|
<option value="upstream">Upstream (Proxy via local service)</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="diff-container">
|
|
<div id="xml-diff-pane" class="diff-pane">
|
|
<span class="config-header">Current Config (on Speaker)</span>
|
|
<pre id="current-config"></pre>
|
|
</div>
|
|
<div id="planned-xml-pane" class="diff-pane">
|
|
<span class="config-header">Planned Config (AfterTouch)</span>
|
|
<pre id="planned-config"></pre>
|
|
</div>
|
|
<div id="planned-hosts-pane" class="diff-pane hidden">
|
|
<span class="config-header">Planned /etc/hosts Entries</span>
|
|
<pre id="planned-hosts"></pre>
|
|
<div class="help-text-sm mt-md">
|
|
<strong>Note:</strong> This method also injects the AfterTouch Local Root CA into <code>/etc/pki/tls/certs/ca-bundle.crt</code> to enable secure HTTPS communication.
|
|
</div>
|
|
</div>
|
|
<div id="planned-resolv-pane" class="diff-pane hidden">
|
|
<span class="config-header">Planned /etc/resolv.conf Hook</span>
|
|
<pre id="planned-resolv"></pre>
|
|
<div id="resolv-note" class="help-text-sm mt-md">
|
|
<strong>Note:</strong> This method injects a persistent DNS priority hook into the DHCP logic (<code>/etc/udhcpc.d/50default</code>). It preserves your router's search domain and secondary DNS servers. It also injects the Local Root CA.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-md flex-row gap-sm flex-wrap">
|
|
<button id="confirm-migrate-btn" class="btn-success">Confirm Migration</button>
|
|
<button id="revert-migrate-btn" class="btn-warning hidden">Revert to Defaults</button>
|
|
<button id="reboot-speaker-btn" class="btn-secondary">Reboot Speaker</button>
|
|
<button id="ensure-remote-btn" class="btn-primary">Enable Persistent Remote Services</button>
|
|
<button id="remove-remote-btn" class="btn-danger">Remove Persistent Remote Services</button>
|
|
<button onclick="hideStatus('migration-summary')">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 5: Interactions & Events -->
|
|
<div id="tab-interactions" class="tab-content">
|
|
<h2>Recorded Interactions & Device Events</h2>
|
|
<p>
|
|
Analysis of traffic handled by this service (self), proxied
|
|
to Bose (upstream), and internal device events (telemetry).
|
|
</p>
|
|
|
|
<div id="interaction-stats-container" class="summary-box">
|
|
<div class="flex-between mb-md">
|
|
<div class="flex-row">
|
|
<p class="m-0">
|
|
Total Requests:
|
|
<strong id="total-requests">0</strong>
|
|
</p>
|
|
<button onclick="fetchInteractionStats()">Refresh Stats</button>
|
|
<button onclick="showDeviceEvents()">View App/Device Events</button>
|
|
</div>
|
|
<div class="text-right">
|
|
<button onclick="cleanupSessions()" class="btn-danger">Cleanup old sessions</button>
|
|
<span class="help-text-sm">Keeps only the 10 most recent sessions</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex-row items-start">
|
|
<div class="flex-grow border-right pr-md">
|
|
<h3>By Service</h3>
|
|
<ul id="stats-by-service" class="stats-list"></ul>
|
|
</div>
|
|
<div class="flex-2 ml-lg">
|
|
<h3>Sessions</h3>
|
|
<div id="stats-by-session-container" class="scroll-y max-h-200 border-p">
|
|
<ul id="stats-by-session" class="stats-list"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="browse-recordings" class="summary-box mt-md">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">Browse Recordings</h3>
|
|
</div>
|
|
<div class="filter-bar">
|
|
<div class="form-inline">
|
|
<label for="filter-session">Session:</label>
|
|
<select id="filter-session" onchange="fetchInteractions()" class="w-auto">
|
|
<option value="">All Sessions</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-inline">
|
|
<label for="filter-category">Category:</label>
|
|
<select id="filter-category" onchange="fetchInteractions()" class="w-auto">
|
|
<option value="">All Categories</option>
|
|
<option value="self">Self (Emulated)</option>
|
|
<option value="upstream">Upstream (Bose)</option>
|
|
<option value="mirror">Mirror (Bose)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-inline">
|
|
<label for="filter-since">Since:</label>
|
|
<input type="text" id="filter-since" placeholder="YYYY-MM-DD HH:mm:ss" class="w-200" onchange="fetchInteractions()" />
|
|
</div>
|
|
<button onclick="fetchInteractions()" class="btn-primary">Apply Filters</button>
|
|
</div>
|
|
|
|
<div id="interactions-list-container" class="scroll-y max-h-400">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Time</th>
|
|
<th>Method</th>
|
|
<th>Path</th>
|
|
<th>Status</th>
|
|
<th>Category</th>
|
|
<th>Event Details</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="interactions-list">
|
|
<tr>
|
|
<td colspan="8" class="p-lg text-center color-muted">
|
|
No interactions found.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="dns-discoveries" class="summary-box mt-md">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">DNS Discoveries</h3>
|
|
<div class="flex-row">
|
|
<button onclick="downloadDNSDiscoveries()" class="btn-primary">Download JSON</button>
|
|
<button onclick="clearDNSDiscoveries()" class="btn-danger">Clear DNS Logs</button>
|
|
</div>
|
|
</div>
|
|
<p class="help-text-sm">
|
|
Hosts discovered via the AfterTouch DNS server. "Self" means the domain was intercepted and redirected to this service.
|
|
</p>
|
|
<div id="dns-discoveries-list-container" class="scroll-y mt-md max-h-400">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Hostname</th>
|
|
<th>Last Seen</th>
|
|
<th class="text-center">Queries</th>
|
|
<th class="text-center">Bose?</th>
|
|
<th>Category</th>
|
|
<th>Last Client IP</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="dns-discoveries-list">
|
|
<tr>
|
|
<td colspan="6" class="p-lg text-center color-muted">
|
|
No DNS discoveries found.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="interaction-viewer" class="viewer-box hidden">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">
|
|
Recording Viewer:
|
|
<span id="viewer-filename" class="font-size-sm fw-normal"></span>
|
|
</h3>
|
|
<button onclick="hideStatus('interaction-viewer')">Close</button>
|
|
</div>
|
|
<pre id="interaction-content"></pre>
|
|
</div>
|
|
|
|
<!-- Device Events Overlay -->
|
|
<div id="device-events-overlay" class="summary-box mt-md hidden bg-light">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">App & Device Events</h3>
|
|
<div class="flex-row">
|
|
<select id="event-device-selector" onchange="fetchDeviceEvents(this.value)" class="w-auto">
|
|
<option value="">-- Select Device --</option>
|
|
</select>
|
|
<button onclick="hideStatus('device-events-overlay')">Close</button>
|
|
</div>
|
|
</div>
|
|
<div id="events-list-container" class="scroll-y max-h-400">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Type</th>
|
|
<th>Data</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="events-list">
|
|
<tr>
|
|
<td colspan="3" class="p-lg text-center color-muted">
|
|
Select a device to view events.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 6: Parity & Mirroring -->
|
|
<div id="tab-parity" class="tab-content">
|
|
<h2>Parity Analysis</h2>
|
|
<p>
|
|
Detection of discrepancies between AfterTouch local
|
|
responses and official Bose Cloud responses for mirrored
|
|
endpoints.
|
|
</p>
|
|
|
|
<div class="summary-box">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">Parity Mismatches</h3>
|
|
<div class="flex-row">
|
|
<button onclick="fetchParityMismatches()">Refresh Mismatches</button>
|
|
<button onclick="clearParityMismatches()" class="btn-danger">Clear All Records</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="parity-list-container" class="scroll-y max-h-500">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Method</th>
|
|
<th>Path</th>
|
|
<th>Reasons</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="parity-mismatches-list">
|
|
<tr>
|
|
<td colspan="5" class="p-lg text-center color-muted">
|
|
Loading mismatches...
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="parity-diff-view" class="summary-box mt-md hidden">
|
|
<div class="flex-between mb-md">
|
|
<h3 class="m-0">
|
|
Mismatch Detail:
|
|
<span id="diff-path-display"></span>
|
|
</h3>
|
|
<button onclick="hideStatus('parity-diff-view')">Close Detail</button>
|
|
</div>
|
|
|
|
<div class="status-error mb-md visible">
|
|
<strong>Detection Reasons:</strong>
|
|
<ul id="diff-reasons-list" class="list-compact list-indented"></ul>
|
|
</div>
|
|
|
|
<div id="diff-size-warning" class="status-warning mb-md hidden">
|
|
⚠️ <strong>Large Payload:</strong> Rich diff highlighting is disabled to prevent a browser freeze. Showing raw comparison instead.
|
|
<button id="force-rich-diff-btn" class="btn-secondary ml-sm font-size-sm">Show Rich Diff Anyway</button>
|
|
</div>
|
|
|
|
<div class="diff-container-columns mt-md">
|
|
<div class="diff-pane">
|
|
<span class="config-header config-header-local">Local Response (AfterTouch)</span>
|
|
<div id="diff-local-meta" class="help-text-sm mb-md"></div>
|
|
<pre id="diff-local-body"></pre>
|
|
</div>
|
|
<div class="diff-pane">
|
|
<span class="config-header config-header-upstream">Upstream Response (Bose)</span>
|
|
<div id="diff-upstream-meta" class="help-text-sm mb-md"></div>
|
|
<pre id="diff-upstream-body"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 7: Local Account -->
|
|
<div id="tab-account" class="tab-content">
|
|
<div class="flex-between mb-md">
|
|
<h2>Local Account Details</h2>
|
|
<div class="flex-row">
|
|
<label for="account-selector">Account:</label>
|
|
<select id="account-selector" onchange="fetchAccountDetails(this.value)" class="w-auto">
|
|
<option value="default">Default</option>
|
|
</select>
|
|
<button onclick="fetchAccountDetails(document.getElementById('account-selector').value)">Refresh</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="account-info-container" class="summary-box">
|
|
<h3>Account Overview</h3>
|
|
<div id="account-metadata">Loading...</div>
|
|
</div>
|
|
|
|
<div id="account-devices-container" class="mt-md">
|
|
<h3>Connected Devices</h3>
|
|
<div id="account-devices-list" class="help-text">Select an account to view devices.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/web/js/script.js"></script>
|
|
<footer>
|
|
<span id="version-info">AfterTouch</span>
|
|
</footer>
|
|
</body>
|
|
</html>
|