2026-02-01 19:18:55 +00:00 | | | <!DOCTYPE html>
|
| | | <html lang="en">
|
| | | <head>
|
| | | <meta charset="UTF-8">
|
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| | | <title>Service Worker Inspector - MQL5 Trading Automation</title>
|
| | | <link rel="manifest" href="/manifest.json">
|
| | | <meta name="theme-color" content="#667eea">
|
| | | <style>
|
| | | * {
|
| | | margin: 0;
|
| | | padding: 0;
|
| | | box-sizing: border-box;
|
| | | }
|
| | |
|
| | | body {
|
| | | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| | | min-height: 100vh;
|
| | | padding: 20px;
|
| | | }
|
| | |
|
| | | .container {
|
| | | max-width: 1000px;
|
| | | margin: 0 auto;
|
| | | }
|
| | |
|
| | | .header {
|
| | | background: white;
|
| | | padding: 30px;
|
| | | border-radius: 15px;
|
| | | box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
| | | margin-bottom: 30px;
|
| | | text-align: center;
|
| | | }
|
| | |
|
| | | .header h1 {
|
| | | color: #333;
|
| | | font-size: 2em;
|
| | | margin-bottom: 10px;
|
| | | }
|
| | |
|
| | | .header p {
|
| | | color: #666;
|
| | | font-size: 1em;
|
| | | }
|
| | |
|
| | | .card {
|
| | | background: white;
|
| | | padding: 25px;
|
| | | border-radius: 15px;
|
| | | box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| | | margin-bottom: 20px;
|
| | | }
|
| | |
|
| | | .card h2 {
|
| | | color: #667eea;
|
| | | margin-bottom: 15px;
|
| | | font-size: 1.5em;
|
| | | }
|
| | |
|
| | | .info-grid {
|
| | | display: grid;
|
| | | grid-template-columns: 200px 1fr;
|
| | | gap: 15px;
|
| | | margin-bottom: 15px;
|
| | | }
|
| | |
|
| | | .info-label {
|
| | | color: #666;
|
| | | font-weight: 600;
|
| | | }
|
| | |
|
| | | .info-value {
|
| | | color: #333;
|
| | | word-break: break-all;
|
| | | }
|
| | |
|
| | | .status-badge {
|
| | | display: inline-block;
|
| | | padding: 5px 15px;
|
| | | border-radius: 20px;
|
| | | font-size: 0.9em;
|
| | | font-weight: 600;
|
| | | }
|
| | |
|
| | | .status-active {
|
| | | background: #10b981;
|
| | | color: white;
|
| | | }
|
| | |
|
| | | .status-inactive {
|
| | | background: #ef4444;
|
| | | color: white;
|
| | | }
|
| | |
|
| | | .status-installing {
|
| | | background: #f59e0b;
|
| | | color: white;
|
| | | }
|
| | |
|
| | | .btn {
|
| | | display: inline-block;
|
| | | padding: 12px 24px;
|
| | | background: #667eea;
|
| | | color: white;
|
| | | border: none;
|
| | | border-radius: 8px;
|
| | | font-weight: 600;
|
| | | cursor: pointer;
|
| | | transition: background 0.3s;
|
| | | margin-right: 10px;
|
| | | margin-bottom: 10px;
|
| | | }
|
| | |
|
| | | .btn:hover {
|
| | | background: #5568d3;
|
| | | }
|
| | |
|
| | | .btn-danger {
|
| | | background: #ef4444;
|
| | | }
|
| | |
|
| | | .btn-danger:hover {
|
| | | background: #dc2626;
|
| | | }
|
| | |
|
| | | .log-container {
|
| | | background: #1a1a1a;
|
| | | color: #0f0;
|
| | | padding: 15px;
|
| | | border-radius: 8px;
|
| | | font-family: 'Courier New', monospace;
|
| | | font-size: 0.9em;
|
| | | max-height: 300px;
|
| | | overflow-y: auto;
|
| | | margin-top: 15px;
|
| | | }
|
| | |
|
| | | .log-entry {
|
| | | margin-bottom: 5px;
|
| | | }
|
| | |
|
| | | .log-timestamp {
|
| | | color: #888;
|
| | | }
|
| | |
|
| | | .cache-list {
|
| | | list-style: none;
|
| | | }
|
| | |
|
| | | .cache-item {
|
| | | padding: 10px;
|
| | | border-bottom: 1px solid #eee;
|
| | | display: flex;
|
| | | justify-content: space-between;
|
| | | align-items: center;
|
| | | }
|
| | |
|
| | | .cache-item:last-child {
|
| | | border-bottom: none;
|
| | | }
|
| | |
|
| | | .cache-name {
|
| | | font-weight: 600;
|
| | | color: #333;
|
| | | }
|
| | |
|
| | | .cache-size {
|
| | | color: #667eea;
|
| | | font-weight: 600;
|
| | | }
|
| | |
|
| | | .back-link {
|
| | | display: inline-block;
|
| | | color: white;
|
| | | text-decoration: none;
|
| | | margin-bottom: 20px;
|
| | | padding: 10px 20px;
|
| | | background: rgba(255, 255, 255, 0.2);
|
| | | border-radius: 8px;
|
| | | transition: background 0.3s;
|
| | | }
|
| | |
|
| | | .back-link:hover {
|
| | | background: rgba(255, 255, 255, 0.3);
|
| | | }
|
| | | </style>
|
| | | </head>
|
| | | <body>
|
| | | <div class="container">
|
| | | <a href="/" class="back-link">← Back to Dashboard</a>
|
| | |
|
| | | <div class="header">
|
2026-02-09 11:13:59 +00:00 | | | <h1><span role="img" aria-label="Magnifying Glass">🔍</span> Service Worker Inspector</h1>
|
2026-02-01 19:18:55 +00:00 | | | <p>Monitor and manage your Progressive Web App</p>
|
| | | </div>
|
| | |
|
| | | <div class="card">
|
| | | <h2>Service Worker Status</h2>
|
| | | <div class="info-grid">
|
| | | <div class="info-label">Status:</div>
|
| | | <div class="info-value">
|
| | | <span id="sw-status" class="status-badge status-inactive">Checking...</span>
|
| | | </div>
|
| | |
|
| | | <div class="info-label">State:</div>
|
| | | <div class="info-value" id="sw-state">-</div>
|
| | |
|
| | | <div class="info-label">Scope:</div>
|
| | | <div class="info-value" id="sw-scope">-</div>
|
| | |
|
| | | <div class="info-label">Script URL:</div>
|
| | | <div class="info-value" id="sw-url">-</div>
|
| | |
|
| | | <div class="info-label">Update Available:</div>
|
| | | <div class="info-value" id="sw-update">-</div>
|
| | | </div>
|
| | |
|
| | | <div style="margin-top: 20px;">
|
| | | <button class="btn" onclick="checkUpdate()">Check for Updates</button>
|
| | | <button class="btn" onclick="forceUpdate()">Force Update</button>
|
| | | <button class="btn btn-danger" onclick="unregisterSW()">Unregister</button>
|
| | | </div>
|
| | | </div>
|
| | |
|
| | | <div class="card">
|
| | | <h2>Cache Storage</h2>
|
| | | <div id="cache-info">
|
| | | <p>Loading cache information...</p>
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <button class="btn" onclick="refreshCacheInfo()">Refresh Cache Info</button>
|
| | | <button class="btn btn-danger" onclick="clearAllCaches()">Clear All Caches</button>
|
| | | </div>
|
| | | </div>
|
| | |
|
| | | <div class="card">
|
| | | <h2>Activity Log</h2>
|
| | | <div class="log-container" id="log-container">
|
| | | <div class="log-entry">
|
| | | <span class="log-timestamp">[Starting]</span> Service Worker Inspector initialized
|
| | | </div>
|
| | | </div>
|
| | | <div style="margin-top: 15px;">
|
| | | <button class="btn" onclick="clearLog()">Clear Log</button>
|
| | | </div>
|
| | | </div>
|
| | |
|
| | | <div class="card">
|
| | | <h2>PWA Installation</h2>
|
| | | <div class="info-grid">
|
| | | <div class="info-label">Installable:</div>
|
| | | <div class="info-value" id="pwa-installable">Checking...</div>
|
| | |
|
| | | <div class="info-label">Display Mode:</div>
|
| | | <div class="info-value" id="display-mode">-</div>
|
| | | </div>
|
| | | <div style="margin-top: 20px;">
|
| | | <button class="btn" id="install-btn" style="display: none;" onclick="installPWA()">Install App</button>
|
| | | </div>
|
| | | </div>
|
| | | </div>
|
| | |
|
| | | <script>
|
| | | let deferredPrompt;
|
| | | const logs = [];
|
| | |
|
| | | function log(message) {
|
| | | const timestamp = new Date().toLocaleTimeString();
|
| | | logs.push({ timestamp, message });
|
| | | const logContainer = document.getElementById('log-container');
|
| | | const entry = document.createElement('div');
|
| | | entry.className = 'log-entry';
|
| | | entry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${message}`;
|
| | | logContainer.appendChild(entry);
|
| | | logContainer.scrollTop = logContainer.scrollHeight;
|
| | | }
|
| | |
|
| | | function clearLog() {
|
| | | logs.length = 0;
|
| | | document.getElementById('log-container').innerHTML = '';
|
| | | log('Log cleared');
|
| | | }
|
| | |
|
| | | async function updateServiceWorkerInfo() {
|
| | | if ('serviceWorker' in navigator) {
|
| | | const registration = await navigator.serviceWorker.getRegistration();
|
| | |
|
| | | if (registration) {
|
| | | const statusBadge = document.getElementById('sw-status');
|
| | | statusBadge.textContent = 'Active';
|
| | | statusBadge.className = 'status-badge status-active';
|
| | |
|
| | | const sw = registration.active || registration.installing || registration.waiting;
|
| | | if (sw) {
|
| | | document.getElementById('sw-state').textContent = sw.state;
|
| | | document.getElementById('sw-url').textContent = sw.scriptURL;
|
| | | }
|
| | |
|
| | | document.getElementById('sw-scope').textContent = registration.scope;
|
| | |
|
| | | if (registration.waiting) {
|
| | | document.getElementById('sw-update').textContent = 'Yes - Update available!';
|
| | | log('Service worker update available');
|
| | | } else {
|
| | | document.getElementById('sw-update').textContent = 'No';
|
| | | }
|
| | |
|
| | | log('Service worker information updated');
|
| | | } else {
|
| | | const statusBadge = document.getElementById('sw-status');
|
| | | statusBadge.textContent = 'Not Registered';
|
| | | statusBadge.className = 'status-badge status-inactive';
|
| | | log('No service worker registered');
|
| | | }
|
| | | } else {
|
| | | document.getElementById('sw-status').textContent = 'Not Supported';
|
| | | log('Service workers not supported in this browser');
|
| | | }
|
| | | }
|
| | |
|
| | | async function checkUpdate() {
|
| | | log('Checking for service worker updates...');
|
| | | const registration = await navigator.serviceWorker.getRegistration();
|
| | | if (registration) {
|
| | | await registration.update();
|
| | | log('Update check complete');
|
| | | updateServiceWorkerInfo();
|
| | | }
|
| | | }
|
| | |
|
| | | async function forceUpdate() {
|
| | | log('Forcing service worker update...');
|
| | | const registration = await navigator.serviceWorker.getRegistration();
|
| | | if (registration && registration.waiting) {
|
| | | registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
| | | log('Update forced, reloading page...');
|
| | | setTimeout(() => window.location.reload(), 1000);
|
| | | } else {
|
| | | log('No update available to force');
|
| | | }
|
| | | }
|
| | |
|
| | | async function unregisterSW() {
|
| | | if (confirm('Are you sure you want to unregister the service worker?')) {
|
| | | log('Unregistering service worker...');
|
| | | const registration = await navigator.serviceWorker.getRegistration();
|
| | | if (registration) {
|
| | | await registration.unregister();
|
| | | log('Service worker unregistered');
|
| | | updateServiceWorkerInfo();
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | async function refreshCacheInfo() {
|
| | | log('Refreshing cache information...');
|
| | | const cacheNames = await caches.keys();
|
| | | const cacheInfo = document.getElementById('cache-info');
|
| | |
|
| | | if (cacheNames.length === 0) {
|
| | | cacheInfo.innerHTML = '<p>No caches found</p>';
|
| | | log('No caches found');
|
| | | return;
|
| | | }
|
| | |
|
| | | const cacheStats = await Promise.all(
|
| | | cacheNames.map(async (name) => {
|
| | | const cache = await caches.open(name);
|
| | | const keys = await cache.keys();
|
| | | return { name, size: keys.length };
|
| | | })
|
| | | );
|
| | |
|
| | | cacheInfo.innerHTML = '<ul class="cache-list">' +
|
| | | cacheStats.map(stat =>
|
| | | `<li class="cache-item">
|
| | | <span class="cache-name">${stat.name}</span>
|
| | | <span class="cache-size">${stat.size} items</span>
|
| | | </li>`
|
| | | ).join('') +
|
| | | '</ul>';
|
| | |
|
| | | log(`Found ${cacheNames.length} cache(s)`);
|
| | | }
|
| | |
|
| | | async function clearAllCaches() {
|
| | | if (confirm('Are you sure you want to clear all caches? This will remove offline capabilities until the next page load.')) {
|
| | | log('Clearing all caches...');
|
| | | const cacheNames = await caches.keys();
|
| | | await Promise.all(cacheNames.map(name => caches.delete(name)));
|
| | | log(`Cleared ${cacheNames.length} cache(s)`);
|
| | | refreshCacheInfo();
|
| | | }
|
| | | }
|
| | |
|
| | | function checkDisplayMode() {
|
| | | const displayMode = window.matchMedia('(display-mode: standalone)').matches ?
|
| | | 'Standalone (PWA)' : 'Browser';
|
| | | document.getElementById('display-mode').textContent = displayMode;
|
| | | log(`Display mode: ${displayMode}`);
|
| | | }
|
| | |
|
| | | function installPWA() {
|
| | | if (deferredPrompt) {
|
| | | deferredPrompt.prompt();
|
| | | deferredPrompt.userChoice.then((choiceResult) => {
|
| | | if (choiceResult.outcome === 'accepted') {
|
| | | log('User accepted PWA installation');
|
| | | } else {
|
| | | log('User dismissed PWA installation');
|
| | | }
|
| | | deferredPrompt = null;
|
| | | document.getElementById('install-btn').style.display = 'none';
|
| | | });
|
| | | }
|
| | | }
|
| | |
|
| | | // Listen for install prompt
|
| | | window.addEventListener('beforeinstallprompt', (e) => {
|
| | | e.preventDefault();
|
| | | deferredPrompt = e;
|
| | | document.getElementById('pwa-installable').textContent = 'Yes';
|
| | | document.getElementById('install-btn').style.display = 'inline-block';
|
| | | log('PWA installation prompt available');
|
| | | });
|
| | |
|
| | | // Listen for successful install
|
| | | window.addEventListener('appinstalled', () => {
|
| | | log('PWA successfully installed');
|
| | | document.getElementById('pwa-installable').textContent = 'Installed';
|
| | | document.getElementById('install-btn').style.display = 'none';
|
| | | });
|
| | |
|
| | | // Initialize on page load
|
| | | window.addEventListener('load', () => {
|
| | | log('Page loaded, initializing inspector...');
|
| | | updateServiceWorkerInfo();
|
| | | refreshCacheInfo();
|
| | | checkDisplayMode();
|
| | |
|
| | | if (window.matchMedia('(display-mode: standalone)').matches) {
|
| | | document.getElementById('pwa-installable').textContent = 'Already installed';
|
| | | }
|
| | | });
|
| | |
|
| | | // Listen for service worker updates
|
| | | if ('serviceWorker' in navigator) {
|
| | | navigator.serviceWorker.addEventListener('controllerchange', () => {
|
| | | log('Service worker controller changed');
|
| | | updateServiceWorkerInfo();
|
| | | });
|
| | | }
|
| | | </script>
|
| | | </body>
|
| | | </html>
|