Skip to content
2 changes: 1 addition & 1 deletion TankAlarm-112025-Server-BluesOpta/data/client_console.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Client Console</title><style>:root{font-family:"Segoe UI",Arial,sans-serif;color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);transition:background 0.2s ease,color 0.2s ease}body[data-theme="light"]{--bg:#f8fafc;--surface:#ffffff;--text:#1f2933;--muted:#475569;--header-bg:#e2e8f0;--card-border:rgba(15,23,42,0.08);--card-shadow:rgba(15,23,42,0.08);--accent:#2563eb;--accent-strong:#1d4ed8;--accent-contrast:#f8fafc;--chip:#f8fafc;--input-border:#cbd5e1;--danger:#ef4444;--pill-bg:rgba(37,99,235,0.12);--console-bg:#1e1e1e;--console-text:#d4d4d4}body[data-theme="dark"]{--bg:#0f172a;--surface:#1e293b;--text:#e2e8f0;--muted:#94a3b8;--header-bg:#16213d;--card-border:rgba(15,23,42,0.55);--card-shadow:rgba(0,0,0,0.55);--accent:#38bdf8;--accent-strong:#22d3ee;--accent-contrast:#0f172a;--chip:rgba(148,163,184,0.15);--input-border:rgba(148,163,184,0.4);--danger:#f87171;--pill-bg:rgba(56,189,248,0.18);--console-bg:#000000;--console-text:#cccccc}header{background:var(--header-bg);padding:28px 24px;box-shadow:0 20px 45px var(--card-shadow)}header .bar{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;align-items:flex-start}header h1{margin:0;font-size:1.9rem}header p{margin:8px 0 0;color:var(--muted);max-width:640px;line-height:1.4}.header-actions{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.pill{padding:10px 20px;text-decoration:none;font-weight:600;background:var(--pill-bg);color:var(--accent);border:1px solid transparent;transition:transform 0.15s ease}.pill:hover{transform:translateY(-1px)}.icon-button{width:42px;height:42px;border:1px solid var(--card-border);background:var(--surface);color:var(--text);font-size:1.2rem;cursor:pointer;transition:transform 0.15s ease}.icon-button:hover{transform:translateY(-1px)}main{padding:24px;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--surface);border:1px solid var(--card-border);padding:20px;box-shadow:0 25px 55px var(--card-shadow);margin-bottom:24px}.console-output{background:var(--console-bg);color:var(--console-text);font-family:'Consolas','Monaco',monospace;padding:16px;height:400px;overflow-y:auto;border-radius:4px;margin-bottom:16px;font-size:0.9rem}.console-input-area{display:flex;gap:12px}.console-input{flex:1;padding:10px;font-family:'Consolas','Monaco',monospace;border:1px solid var(--input-border);background:var(--bg);color:var(--text)}button{border:none;padding:10px 16px;font-size:0.95rem;font-weight:600;cursor:pointer;background:var(--accent);color:var(--accent-contrast);transition:transform 0.15s ease}button:hover{transform:translateY(-1px)}.log-entry{margin-bottom:4px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:2px}.log-time{color:#569cd6;margin-right:8px}.log-info{color:#4ec9b0}.log-warn{color:#ce9178}.log-err{color:#f44747}</style></head><body data-theme="light"><header><div class="bar"><div><h1>Client Console</h1><p> Direct communication interface for remote client diagnostics and command execution. </p></div><div class="header-actions"><a class="pill secondary" href="/">Dashboard</a><a class="pill" href="/client-console">Client Console</a><a class="pill secondary" href="/config-generator">Config Generator</a><a class="pill secondary" href="/contacts">Contacts</a><a class="pill secondary" href="/serial-monitor">Serial Monitor</a><a class="pill secondary" href="/calibration">Calibration</a><a class="pill secondary" href="/historical">Historical Data</a><a class="pill secondary" href="/server-settings">Server Settings</a></div></div></header><main><div class="card"><div style="margin-bottom:16px;display:flex;gap:12px;align-items:center"><label>Target Client: <select id="clientSelect" style="padding:8px"><option value="all">Broadcast (All Clients)</option><option value="client1">Client-01</option><option value="client2">Client-02</option></select></label><button class="secondary" onclick="clearConsole()">Clear Output</button></div><div class="console-output" id="consoleOutput"><div class="log-entry"><span class="log-time">[10:00:01]</span> <span class="log-info">System initialized. Ready for commands.</span></div></div><div class="console-input-area"><input type="text" class="console-input" id="cmdInput" placeholder="Enter command (e.g. /status, /reset, /config)..."><button onclick="sendCommand()">Send</button></div></div></main><script>const output = document.getElementById('consoleOutput');const input = document.getElementById('cmdInput');function log(msg,type='info'){const time = new Date().toLocaleTimeString();const div = document.createElement('div');div.className = 'log-entry';div.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${msg}</span>`;output.appendChild(div);output.scrollTop = output.scrollHeight;}function sendCommand(){const cmd = input.value.trim();if(!cmd)return;log(`> ${cmd}`,'info');// Mock response simulationsetTimeout(()=>{if(cmd === '/status'){log('Client-01: Online, Battery 12.4V, Signal -65dBm','info');}else if(cmd === '/reset'){log('Client-01: Reset command received. Rebooting...','warn');}else{log(`Unknown command: ${cmd}`,'err');}},500);input.value = '';}function clearConsole(){output.innerHTML = '';}input.addEventListener('keypress',(e)=>{if(e.key === 'Enter')sendCommand();});</script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Client Console</title><style>:root{font-family:"Segoe UI",Arial,sans-serif;color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);transition:background 0.2s ease,color 0.2s ease}body[data-theme="light"]{--bg:#f8fafc;--surface:#ffffff;--text:#1f2933;--muted:#475569;--header-bg:#e2e8f0;--card-border:rgba(15,23,42,0.08);--card-shadow:rgba(15,23,42,0.08);--accent:#2563eb;--accent-strong:#1d4ed8;--accent-contrast:#f8fafc;--chip:#f8fafc;--input-border:#cbd5e1;--danger:#ef4444;--pill-bg:rgba(37,99,235,0.12);--console-bg:#1e1e1e;--console-text:#d4d4d4}body[data-theme="dark"]{--bg:#0f172a;--surface:#1e293b;--text:#e2e8f0;--muted:#94a3b8;--header-bg:#16213d;--card-border:rgba(15,23,42,0.55);--card-shadow:rgba(0,0,0,0.55);--accent:#38bdf8;--accent-strong:#22d3ee;--accent-contrast:#0f172a;--chip:rgba(148,163,184,0.15);--input-border:rgba(148,163,184,0.4);--danger:#f87171;--pill-bg:rgba(56,189,248,0.18);--console-bg:#000000;--console-text:#cccccc}header{background:var(--header-bg);padding:28px 24px;box-shadow:0 20px 45px var(--card-shadow)}header .bar{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;align-items:flex-start}header h1{margin:0;font-size:1.9rem}header p{margin:8px 0 0;color:var(--muted);max-width:640px;line-height:1.4}.header-actions{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.pill{padding:10px 20px;text-decoration:none;font-weight:600;background:var(--pill-bg);color:var(--accent);border:1px solid transparent;transition:transform 0.15s ease}.pill:hover{transform:translateY(-1px)}.icon-button{width:42px;height:42px;border:1px solid var(--card-border);background:var(--surface);color:var(--text);font-size:1.2rem;cursor:pointer;transition:transform 0.15s ease}.icon-button:hover{transform:translateY(-1px)}main{padding:24px;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--surface);border:1px solid var(--card-border);padding:20px;box-shadow:0 25px 55px var(--card-shadow);margin-bottom:24px}h2{margin-top:0;font-size:1.3rem}h3{margin:20px 0 10px;font-size:1.1rem;border-bottom:1px solid var(--card-border);padding-bottom:6px;color:var(--text)}.field{display:flex;flex-direction:column;margin-bottom:12px}.field span{font-size:0.9rem;color:var(--muted);margin-bottom:4px}.field input,.field select{padding:10px 12px;border:1px solid var(--input-border);font-size:0.95rem;background:var(--bg);color:var(--text)}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.config-card{background:var(--chip);border:1px solid var(--card-border);padding:16px;margin-bottom:16px;position:relative}.remove-btn{color:var(--danger);cursor:pointer;font-size:0.9rem;border:none;background:none;padding:0;font-weight:600}.remove-btn:hover{opacity:0.8}.actions{margin-top:24px;display:flex;gap:12px;flex-wrap:wrap}.console-output{background:var(--console-bg);color:var(--console-text);font-family:'Consolas','Monaco',monospace;padding:16px;height:400px;overflow-y:auto;border-radius:4px;margin-bottom:16px;font-size:0.9rem}.console-input-area{display:flex;gap:12px}.console-input{flex:1;padding:10px;font-family:'Consolas','Monaco',monospace;border:1px solid var(--input-border);background:var(--bg);color:var(--text)}button{border:none;padding:10px 16px;font-size:0.95rem;font-weight:600;cursor:pointer;background:var(--accent);color:var(--accent-contrast);transition:transform 0.15s ease}button.secondary{background:transparent;border:1px solid var(--card-border);color:var(--text)}button:hover{transform:translateY(-1px)}.log-entry{margin-bottom:4px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:2px}.log-time{color:#569cd6;margin-right:8px}.log-info{color:#4ec9b0}.log-warn{color:#ce9178}.log-err{color:#f44747}.tooltip-icon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;background:var(--muted);color:var(--surface);font-size:0.7rem;font-weight:700;cursor:help;margin-left:4px;position:relative}.tooltip-icon:hover::after,.tooltip-icon:focus::after{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--text);color:var(--bg);padding:6px 10px;font-size:0.75rem;font-weight:400;max-width:280px;white-space:normal;z-index:100;margin-bottom:4px;box-shadow:0 4px 12px var(--card-shadow)}</style></head><body data-theme="light"><header><div class="bar"><div><h1>Client Console</h1><p> Direct communication interface for remote client diagnostics and command execution. </p></div><div class="header-actions"><a class="pill secondary" href="/">Dashboard</a><a class="pill" href="/client-console">Client Console</a><a class="pill secondary" href="/config-generator">Config Generator</a><a class="pill secondary" href="/contacts">Contacts</a><a class="pill secondary" href="/serial-monitor">Serial Monitor</a><a class="pill secondary" href="/calibration">Calibration</a><a class="pill secondary" href="/historical">Historical Data</a><a class="pill secondary" href="/server-settings">Server Settings</a></div></div></header><main><div class="card"><div style="margin-bottom:16px;display:flex;gap:12px;align-items:center"><label>Target Client: <select id="clientSelect" style="padding:8px" onchange="loadClientConfig()"><option value="">Select Client...</option><option value="client1">Client-01</option><option value="client2">Client-02</option></select></label><button class="secondary" onclick="clearConsole()">Clear Output</button></div><div class="console-output" id="consoleOutput"><div class="log-entry"><span class="log-time">[10:00:01]</span> <span class="log-info">System initialized. Ready for commands.</span></div></div><div class="console-input-area"><input type="text" class="console-input" id="cmdInput" placeholder="Enter command (e.g. /status, /reset, /config)..."><button onclick="sendCommand()">Send</button></div></div><div class="card" id="configEditor" style="display:none;"><h2>Edit Client Configuration</h2><form id="editorForm"><div class="form-grid"><label class="field"><span>Site Name</span><input id="editSiteName" type="text" placeholder="Site Name" required></label><label class="field"><span>Device Label</span><input id="editDeviceLabel" type="text" placeholder="Device Label" required></label><label class="field"><span>Sample Minutes</span><input id="editSampleMinutes" type="number" value="30" min="1" max="1440"></label><label class="field"><span>Level Change Threshold (in)<span class="tooltip-icon" tabindex="0" data-tooltip="Minimum level change in inches required before sending telemetry. Set to 0 to send all readings.">?</span></span><input id="editLevelChangeThreshold" type="number" step="0.1" value="0" placeholder="0 = disabled"></label></div><h3>Power Configuration</h3><div class="form-grid"><label class="field"><span>Power Type<span class="tooltip-icon" tabindex="0" data-tooltip="Select the power configuration for this client installation.">?</span></span><select id="editPowerConfig" onchange="updateEditPowerConfigInfo()"><option value="grid">Grid Powered</option><option value="grid_battery">Grid Powered with 12V Battery Backup</option><option value="solar_pwm">Solar Powered with Basic PWM Charger</option><option value="solar_mppt">Solar Powered with Basic MPPT</option><option value="solar_modbus_mppt">Solar Powered with Modbus MPPT</option></select></label></div><div id="editPowerConfigInfo" style="display:none;background:var(--chip);border:1px solid var(--card-border);padding:12px;margin-bottom:16px;font-size:0.9rem;color:var(--muted);"><strong>Hardware Requirement:</strong> Modbus MPPT requires the Arduino Opta with RS485 expansion module for communication with the MPPT charge controller.</div><h3>Site-Specific Daily Email Recipients</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These email recipients will receive daily reports only for this specific site. This is separate from the unified server-wide daily email list in Server Settings.</p><div id="editDailyEmailContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddDailyEmailBtn" class="secondary">+ Add Email Recipient</button></div><h3>Site-Specific SMS Alarm Contacts</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These contacts will receive SMS alerts only for alarms from this specific site's sensors.</p><div id="editSmsContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddSmsContactBtn" class="secondary">+ Add SMS Contact</button></div><div class="actions"><button type="button" id="saveConfigBtn">Save Configuration</button><button type="button" class="secondary" onclick="cancelEdit()">Cancel</button></div></form></div></main><script>const output = document.getElementById('consoleOutput');const input = document.getElementById('cmdInput');let editDailyEmailIdCounter = 0;let editSmsContactIdCounter = 0;function log(msg,type='info'){const time = new Date().toLocaleTimeString();const div = document.createElement('div');div.className = 'log-entry';div.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${msg}</span>`;output.appendChild(div);output.scrollTop = output.scrollHeight;}function sendCommand(){const cmd = input.value.trim();if(!cmd)return;log(`> ${cmd}`,'info');setTimeout(()=>{if(cmd === '/status'){log('Client-01: Online, Battery 12.4V, Signal -65dBm','info');}else if(cmd === '/reset'){log('Client-01: Reset command received. Rebooting...','warn');}else{log(`Unknown command: ${cmd}`,'err');}},500);input.value = '';}function clearConsole(){output.innerHTML = '';}function loadClientConfig(){const clientId = document.getElementById('clientSelect').value;if(!clientId){document.getElementById('configEditor').style.display = 'none';return;}document.getElementById('configEditor').style.display = 'block';log(`Loading configuration for ${clientId}...`,'info');setTimeout(()=>{document.getElementById('editSiteName').value = 'Example Site';document.getElementById('editDeviceLabel').value = clientId;document.getElementById('editSampleMinutes').value = '30';document.getElementById('editLevelChangeThreshold').value = '0';document.getElementById('editPowerConfig').value = 'grid';updateEditPowerConfigInfo();log(`Configuration loaded for ${clientId}`,'info');},300);}function updateEditPowerConfigInfo(){const powerConfig = document.getElementById('editPowerConfig').value;const infoBox = document.getElementById('editPowerConfigInfo');if(powerConfig === 'solar_modbus_mppt'){infoBox.style.display = 'block';}else{infoBox.style.display = 'none';}}function addEditDailyEmailContact(){const id = editDailyEmailIdCounter++;const container = document.getElementById('editDailyEmailContactsContainer');const card = document.createElement('div');card.className = 'config-card';card.id = `edit-daily-email-${id}`;card.style.padding = '12px';card.style.marginBottom = '8px';card.innerHTML = `<div style="display:flex;gap:8px;align-items:center;"><input type="email" class="edit-daily-email-input" placeholder="user@example.com" style="flex:1;padding:8px 10px;border:1px solid var(--input-border);background:var(--bg);color:var(--text);font-size:0.9rem;"><button type="button" class="remove-btn" onclick="removeEditDailyEmail(${id})" style="padding:6px 10px;font-size:0.85rem;">Remove</button></div>`;container.appendChild(card);}window.removeEditDailyEmail = function(id){const card = document.getElementById(`edit-daily-email-${id}`);if(card){card.remove();}};function addEditSmsContact(){const id = editSmsContactIdCounter++;const container = document.getElementById('editSmsContactsContainer');const card = document.createElement('div');card.className = 'config-card';card.id = `edit-sms-contact-${id}`;card.style.padding = '12px';card.style.marginBottom = '8px';card.innerHTML = `<div style="display:flex;gap:8px;align-items:center;"><input type="text" class="edit-sms-contact-input" placeholder="+15551234567" style="flex:1;padding:8px 10px;border:1px solid var(--input-border);background:var(--bg);color:var(--text);font-size:0.9rem;"><button type="button" class="remove-btn" onclick="removeEditSmsContact(${id})" style="padding:6px 10px;font-size:0.85rem;">Remove</button></div>`;container.appendChild(card);}window.removeEditSmsContact = function(id){const card = document.getElementById(`edit-sms-contact-${id}`);if(card){card.remove();}};function cancelEdit(){document.getElementById('configEditor').style.display = 'none';document.getElementById('clientSelect').value = '';}function saveConfig(){const clientId = document.getElementById('clientSelect').value;if(!clientId){alert('Please select a client');return;}const powerConfigValue = document.getElementById('editPowerConfig').value;const dailyEmailInputs = document.querySelectorAll('.edit-daily-email-input');const dailyEmails = Array.from(dailyEmailInputs).map(input => input.value.trim()).filter(email => email.length > 0);const smsContactInputs = document.querySelectorAll('.edit-sms-contact-input');const smsContacts = Array.from(smsContactInputs).map(input => input.value.trim()).filter(phone => phone.length > 0);const requiresRS485 = powerConfigValue === 'solar_modbus_mppt';const hasBattery = powerConfigValue === 'grid_battery' || powerConfigValue.startsWith('solar');const batteryType = powerConfigValue === 'grid_battery' ? '12v_backup' : (powerConfigValue.startsWith('solar') ? 'solar' : 'none');const config = {site: document.getElementById('editSiteName').value.trim(),deviceLabel: document.getElementById('editDeviceLabel').value.trim(),sampleMinutes: parseInt(document.getElementById('editSampleMinutes').value)||30,levelChangeThreshold: parseFloat(document.getElementById('editLevelChangeThreshold').value)||0,solarPowered: powerConfigValue.startsWith('solar'),powerConfig: powerConfigValue,requiresRS485: requiresRS485,hasBattery: hasBattery,batteryType: batteryType,dailyEmails: dailyEmails,smsContacts: smsContacts};log(`Saving configuration for ${clientId}...`,'info');log(`Config: ${JSON.stringify(config,null,2)}`,'info');setTimeout(()=>{log(`Configuration saved successfully for ${clientId}`,'info');},500);}input.addEventListener('keypress',(e)=>{if(e.key === 'Enter')sendCommand();});document.getElementById('editAddDailyEmailBtn').addEventListener('click',addEditDailyEmailContact);document.getElementById('editAddSmsContactBtn').addEventListener('click',addEditSmsContact);document.getElementById('saveConfigBtn').addEventListener('click',saveConfig);</script></body></html>
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loadClientConfig function uses hardcoded mock data (e.g., "Example Site", clientId as device label) instead of actually loading configuration from a backend API. This means the edit functionality will always show generic placeholder data rather than the actual client configuration, making it impossible to edit real client settings. Consider implementing actual API integration to fetch real client configuration data.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The saveConfig function logs the configuration to the console but doesn't actually send it to a backend API or persist it anywhere. The function simulates saving with a setTimeout but no actual HTTP request is made. This means configuration changes made in the UI will not be saved and will be lost when the page is refreshed or closed.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client console's configuration editor does not populate existing daily email recipients or SMS contacts when loading a client configuration. The containers editDailyEmailContactsContainer and editSmsContactsContainer are not populated in the loadClientConfig function, so even if a client has existing contacts configured, they won't be displayed for editing. Users would need to re-enter all contacts every time they edit a configuration.

Suggested change
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Client Console</title><style>:root{font-family:"Segoe UI",Arial,sans-serif;color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);transition:background 0.2s ease,color 0.2s ease}body[data-theme="light"]{--bg:#f8fafc;--surface:#ffffff;--text:#1f2933;--muted:#475569;--header-bg:#e2e8f0;--card-border:rgba(15,23,42,0.08);--card-shadow:rgba(15,23,42,0.08);--accent:#2563eb;--accent-strong:#1d4ed8;--accent-contrast:#f8fafc;--chip:#f8fafc;--input-border:#cbd5e1;--danger:#ef4444;--pill-bg:rgba(37,99,235,0.12);--console-bg:#1e1e1e;--console-text:#d4d4d4}body[data-theme="dark"]{--bg:#0f172a;--surface:#1e293b;--text:#e2e8f0;--muted:#94a3b8;--header-bg:#16213d;--card-border:rgba(15,23,42,0.55);--card-shadow:rgba(0,0,0,0.55);--accent:#38bdf8;--accent-strong:#22d3ee;--accent-contrast:#0f172a;--chip:rgba(148,163,184,0.15);--input-border:rgba(148,163,184,0.4);--danger:#f87171;--pill-bg:rgba(56,189,248,0.18);--console-bg:#000000;--console-text:#cccccc}header{background:var(--header-bg);padding:28px 24px;box-shadow:0 20px 45px var(--card-shadow)}header .bar{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;align-items:flex-start}header h1{margin:0;font-size:1.9rem}header p{margin:8px 0 0;color:var(--muted);max-width:640px;line-height:1.4}.header-actions{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.pill{padding:10px 20px;text-decoration:none;font-weight:600;background:var(--pill-bg);color:var(--accent);border:1px solid transparent;transition:transform 0.15s ease}.pill:hover{transform:translateY(-1px)}.icon-button{width:42px;height:42px;border:1px solid var(--card-border);background:var(--surface);color:var(--text);font-size:1.2rem;cursor:pointer;transition:transform 0.15s ease}.icon-button:hover{transform:translateY(-1px)}main{padding:24px;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--surface);border:1px solid var(--card-border);padding:20px;box-shadow:0 25px 55px var(--card-shadow);margin-bottom:24px}h2{margin-top:0;font-size:1.3rem}h3{margin:20px 0 10px;font-size:1.1rem;border-bottom:1px solid var(--card-border);padding-bottom:6px;color:var(--text)}.field{display:flex;flex-direction:column;margin-bottom:12px}.field span{font-size:0.9rem;color:var(--muted);margin-bottom:4px}.field input,.field select{padding:10px 12px;border:1px solid var(--input-border);font-size:0.95rem;background:var(--bg);color:var(--text)}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.config-card{background:var(--chip);border:1px solid var(--card-border);padding:16px;margin-bottom:16px;position:relative}.remove-btn{color:var(--danger);cursor:pointer;font-size:0.9rem;border:none;background:none;padding:0;font-weight:600}.remove-btn:hover{opacity:0.8}.actions{margin-top:24px;display:flex;gap:12px;flex-wrap:wrap}.console-output{background:var(--console-bg);color:var(--console-text);font-family:'Consolas','Monaco',monospace;padding:16px;height:400px;overflow-y:auto;border-radius:4px;margin-bottom:16px;font-size:0.9rem}.console-input-area{display:flex;gap:12px}.console-input{flex:1;padding:10px;font-family:'Consolas','Monaco',monospace;border:1px solid var(--input-border);background:var(--bg);color:var(--text)}button{border:none;padding:10px 16px;font-size:0.95rem;font-weight:600;cursor:pointer;background:var(--accent);color:var(--accent-contrast);transition:transform 0.15s ease}button.secondary{background:transparent;border:1px solid var(--card-border);color:var(--text)}button:hover{transform:translateY(-1px)}.log-entry{margin-bottom:4px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:2px}.log-time{color:#569cd6;margin-right:8px}.log-info{color:#4ec9b0}.log-warn{color:#ce9178}.log-err{color:#f44747}.tooltip-icon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;background:var(--muted);color:var(--surface);font-size:0.7rem;font-weight:700;cursor:help;margin-left:4px;position:relative}.tooltip-icon:hover::after,.tooltip-icon:focus::after{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--text);color:var(--bg);padding:6px 10px;font-size:0.75rem;font-weight:400;max-width:280px;white-space:normal;z-index:100;margin-bottom:4px;box-shadow:0 4px 12px var(--card-shadow)}</style></head><body data-theme="light"><header><div class="bar"><div><h1>Client Console</h1><p> Direct communication interface for remote client diagnostics and command execution. </p></div><div class="header-actions"><a class="pill secondary" href="/">Dashboard</a><a class="pill" href="/client-console">Client Console</a><a class="pill secondary" href="/config-generator">Config Generator</a><a class="pill secondary" href="/contacts">Contacts</a><a class="pill secondary" href="/serial-monitor">Serial Monitor</a><a class="pill secondary" href="/calibration">Calibration</a><a class="pill secondary" href="/historical">Historical Data</a><a class="pill secondary" href="/server-settings">Server Settings</a></div></div></header><main><div class="card"><div style="margin-bottom:16px;display:flex;gap:12px;align-items:center"><label>Target Client: <select id="clientSelect" style="padding:8px" onchange="loadClientConfig()"><option value="">Select Client...</option><option value="client1">Client-01</option><option value="client2">Client-02</option></select></label><button class="secondary" onclick="clearConsole()">Clear Output</button></div><div class="console-output" id="consoleOutput"><div class="log-entry"><span class="log-time">[10:00:01]</span> <span class="log-info">System initialized. Ready for commands.</span></div></div><div class="console-input-area"><input type="text" class="console-input" id="cmdInput" placeholder="Enter command (e.g. /status, /reset, /config)..."><button onclick="sendCommand()">Send</button></div></div><div class="card" id="configEditor" style="display:none;"><h2>Edit Client Configuration</h2><form id="editorForm"><div class="form-grid"><label class="field"><span>Site Name</span><input id="editSiteName" type="text" placeholder="Site Name" required></label><label class="field"><span>Device Label</span><input id="editDeviceLabel" type="text" placeholder="Device Label" required></label><label class="field"><span>Sample Minutes</span><input id="editSampleMinutes" type="number" value="30" min="1" max="1440"></label><label class="field"><span>Level Change Threshold (in)<span class="tooltip-icon" tabindex="0" data-tooltip="Minimum level change in inches required before sending telemetry. Set to 0 to send all readings.">?</span></span><input id="editLevelChangeThreshold" type="number" step="0.1" value="0" placeholder="0 = disabled"></label></div><h3>Power Configuration</h3><div class="form-grid"><label class="field"><span>Power Type<span class="tooltip-icon" tabindex="0" data-tooltip="Select the power configuration for this client installation.">?</span></span><select id="editPowerConfig" onchange="updateEditPowerConfigInfo()"><option value="grid">Grid Powered</option><option value="grid_battery">Grid Powered with 12V Battery Backup</option><option value="solar_pwm">Solar Powered with Basic PWM Charger</option><option value="solar_mppt">Solar Powered with Basic MPPT</option><option value="solar_modbus_mppt">Solar Powered with Modbus MPPT</option></select></label></div><div id="editPowerConfigInfo" style="display:none;background:var(--chip);border:1px solid var(--card-border);padding:12px;margin-bottom:16px;font-size:0.9rem;color:var(--muted);"><strong>Hardware Requirement:</strong> Modbus MPPT requires the Arduino Opta with RS485 expansion module for communication with the MPPT charge controller.</div><h3>Site-Specific Daily Email Recipients</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These email recipients will receive daily reports only for this specific site. This is separate from the unified server-wide daily email list in Server Settings.</p><div id="editDailyEmailContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddDailyEmailBtn" class="secondary">+ Add Email Recipient</button></div><h3>Site-Specific SMS Alarm Contacts</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These contacts will receive SMS alerts only for alarms from this specific site's sensors.</p><div id="editSmsContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddSmsContactBtn" class="secondary">+ Add SMS Contact</button></div><div class="actions"><button type="button" id="saveConfigBtn">Save Configuration</button><button type="button" class="secondary" onclick="cancelEdit()">Cancel</button></div></form></div></main><script>const output = document.getElementById('consoleOutput');const input = document.getElementById('cmdInput');let editDailyEmailIdCounter = 0;let editSmsContactIdCounter = 0;function log(msg,type='info'){const time = new Date().toLocaleTimeString();const div = document.createElement('div');div.className = 'log-entry';div.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${msg}</span>`;output.appendChild(div);output.scrollTop = output.scrollHeight;}function sendCommand(){const cmd = input.value.trim();if(!cmd)return;log(`> ${cmd}`,'info');setTimeout(()=>{if(cmd === '/status'){log('Client-01: Online, Battery 12.4V, Signal -65dBm','info');}else if(cmd === '/reset'){log('Client-01: Reset command received. Rebooting...','warn');}else{log(`Unknown command: ${cmd}`,'err');}},500);input.value = '';}function clearConsole(){output.innerHTML = '';}function loadClientConfig(){const clientId = document.getElementById('clientSelect').value;if(!clientId){document.getElementById('configEditor').style.display = 'none';return;}document.getElementById('configEditor').style.display = 'block';log(`Loading configuration for ${clientId}...`,'info');setTimeout(()=>{document.getElementById('editSiteName').value = 'Example Site';document.getElementById('editDeviceLabel').value = clientId;document.getElementById('editSampleMinutes').value = '30';document.getElementById('editLevelChangeThreshold').value = '0';document.getElementById('editPowerConfig').value = 'grid';updateEditPowerConfigInfo();log(`Configuration loaded for ${clientId}`,'info');},300);}function updateEditPowerConfigInfo(){const powerConfig = document.getElementById('editPowerConfig').value;const infoBox = document.getElementById('editPowerConfigInfo');if(powerConfig === 'solar_modbus_mppt'){infoBox.style.display = 'block';}else{infoBox.style.display = 'none';}}function addEditDailyEmailContact(){const id = editDailyEmailIdCounter++;const container = document.getElementById('editDailyEmailContactsContainer');const card = document.createElement('div');card.className = 'config-card';card.id = `edit-daily-email-${id}`;card.style.padding = '12px';card.style.marginBottom = '8px';card.innerHTML = `<div style="display:flex;gap:8px;align-items:center;"><input type="email" class="edit-daily-email-input" placeholder="user@example.com" style="flex:1;padding:8px 10px;border:1px solid var(--input-border);background:var(--bg);color:var(--text);font-size:0.9rem;"><button type="button" class="remove-btn" onclick="removeEditDailyEmail(${id})" style="padding:6px 10px;font-size:0.85rem;">Remove</button></div>`;container.appendChild(card);}window.removeEditDailyEmail = function(id){const card = document.getElementById(`edit-daily-email-${id}`);if(card){card.remove();}};function addEditSmsContact(){const id = editSmsContactIdCounter++;const container = document.getElementById('editSmsContactsContainer');const card = document.createElement('div');card.className = 'config-card';card.id = `edit-sms-contact-${id}`;card.style.padding = '12px';card.style.marginBottom = '8px';card.innerHTML = `<div style="display:flex;gap:8px;align-items:center;"><input type="text" class="edit-sms-contact-input" placeholder="+15551234567" style="flex:1;padding:8px 10px;border:1px solid var(--input-border);background:var(--bg);color:var(--text);font-size:0.9rem;"><button type="button" class="remove-btn" onclick="removeEditSmsContact(${id})" style="padding:6px 10px;font-size:0.85rem;">Remove</button></div>`;container.appendChild(card);}window.removeEditSmsContact = function(id){const card = document.getElementById(`edit-sms-contact-${id}`);if(card){card.remove();}};function cancelEdit(){document.getElementById('configEditor').style.display = 'none';document.getElementById('clientSelect').value = '';}function saveConfig(){const clientId = document.getElementById('clientSelect').value;if(!clientId){alert('Please select a client');return;}const powerConfigValue = document.getElementById('editPowerConfig').value;const dailyEmailInputs = document.querySelectorAll('.edit-daily-email-input');const dailyEmails = Array.from(dailyEmailInputs).map(input => input.value.trim()).filter(email => email.length > 0);const smsContactInputs = document.querySelectorAll('.edit-sms-contact-input');const smsContacts = Array.from(smsContactInputs).map(input => input.value.trim()).filter(phone => phone.length > 0);const requiresRS485 = powerConfigValue === 'solar_modbus_mppt';const hasBattery = powerConfigValue === 'grid_battery' || powerConfigValue.startsWith('solar');const batteryType = powerConfigValue === 'grid_battery' ? '12v_backup' : (powerConfigValue.startsWith('solar') ? 'solar' : 'none');const config = {site: document.getElementById('editSiteName').value.trim(),deviceLabel: document.getElementById('editDeviceLabel').value.trim(),sampleMinutes: parseInt(document.getElementById('editSampleMinutes').value)||30,levelChangeThreshold: parseFloat(document.getElementById('editLevelChangeThreshold').value)||0,solarPowered: powerConfigValue.startsWith('solar'),powerConfig: powerConfigValue,requiresRS485: requiresRS485,hasBattery: hasBattery,batteryType: batteryType,dailyEmails: dailyEmails,smsContacts: smsContacts};log(`Saving configuration for ${clientId}...`,'info');log(`Config: ${JSON.stringify(config,null,2)}`,'info');setTimeout(()=>{log(`Configuration saved successfully for ${clientId}`,'info');},500);}input.addEventListener('keypress',(e)=>{if(e.key === 'Enter')sendCommand();});document.getElementById('editAddDailyEmailBtn').addEventListener('click',addEditDailyEmailContact);document.getElementById('editAddSmsContactBtn').addEventListener('click',addEditSmsContact);document.getElementById('saveConfigBtn').addEventListener('click',saveConfig);</script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Client Console</title><style>:root{font-family:"Segoe UI",Arial,sans-serif;color-scheme:light dark}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);transition:background 0.2s ease,color 0.2s ease}body[data-theme="light"]{--bg:#f8fafc;--surface:#ffffff;--text:#1f2933;--muted:#475569;--header-bg:#e2e8f0;--card-border:rgba(15,23,42,0.08);--card-shadow:rgba(15,23,42,0.08);--accent:#2563eb;--accent-strong:#1d4ed8;--accent-contrast:#f8fafc;--chip:#f8fafc;--input-border:#cbd5e1;--danger:#ef4444;--pill-bg:rgba(37,99,235,0.12);--console-bg:#1e1e1e;--console-text:#d4d4d4}body[data-theme="dark"]{--bg:#0f172a;--surface:#1e293b;--text:#e2e8f0;--muted:#94a3b8;--header-bg:#16213d;--card-border:rgba(15,23,42,0.55);--card-shadow:rgba(0,0,0,0.55);--accent:#38bdf8;--accent-strong:#22d3ee;--accent-contrast:#0f172a;--chip:rgba(148,163,184,0.15);--input-border:rgba(148,163,184,0.4);--danger:#f87171;--pill-bg:rgba(56,189,248,0.18);--console-bg:#000000;--console-text:#cccccc}header{background:var(--header-bg);padding:28px 24px;box-shadow:0 20px 45px var(--card-shadow)}header .bar{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;align-items:flex-start}header h1{margin:0;font-size:1.9rem}header p{margin:8px 0 0;color:var(--muted);max-width:640px;line-height:1.4}.header-actions{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.pill{padding:10px 20px;text-decoration:none;font-weight:600;background:var(--pill-bg);color:var(--accent);border:1px solid transparent;transition:transform 0.15s ease}.pill:hover{transform:translateY(-1px)}.icon-button{width:42px;height:42px;border:1px solid var(--card-border);background:var(--surface);color:var(--text);font-size:1.2rem;cursor:pointer;transition:transform 0.15s ease}.icon-button:hover{transform:translateY(-1px)}main{padding:24px;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--surface);border:1px solid var(--card-border);padding:20px;box-shadow:0 25px 55px var(--card-shadow);margin-bottom:24px}h2{margin-top:0;font-size:1.3rem}h3{margin:20px 0 10px;font-size:1.1rem;border-bottom:1px solid var(--card-border);padding-bottom:6px;color:var(--text)}.field{display:flex;flex-direction:column;margin-bottom:12px}.field span{font-size:0.9rem;color:var(--muted);margin-bottom:4px}.field input,.field select{padding:10px 12px;border:1px solid var(--input-border);font-size:0.95rem;background:var(--bg);color:var(--text)}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.config-card{background:var(--chip);border:1px solid var(--card-border);padding:16px;margin-bottom:16px;position:relative}.remove-btn{color:var(--danger);cursor:pointer;font-size:0.9rem;border:none;background:none;padding:0;font-weight:600}.remove-btn:hover{opacity:0.8}.actions{margin-top:24px;display:flex;gap:12px;flex-wrap:wrap}.console-output{background:var(--console-bg);color:var(--console-text);font-family:'Consolas','Monaco',monospace;padding:16px;height:400px;overflow-y:auto;border-radius:4px;margin-bottom:16px;font-size:0.9rem}.console-input-area{display:flex;gap:12px}.console-input{flex:1;padding:10px;font-family:'Consolas','Monaco',monospace;border:1px solid var(--input-border);background:var(--bg);color:var(--text)}button{border:none;padding:10px 16px;font-size:0.95rem;font-weight:600;cursor:pointer;background:var(--accent);color:var(--accent-contrast);transition:transform 0.15s ease}button.secondary{background:transparent;border:1px solid var(--card-border);color:var(--text)}button:hover{transform:translateY(-1px)}.log-entry{margin-bottom:4px;border-bottom:1px solid rgba(255,255,255,0.1);padding-bottom:2px}.log-time{color:#569cd6;margin-right:8px}.log-info{color:#4ec9b0}.log-warn{color:#ce9178}.log-err{color:#f44747}.tooltip-icon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;background:var(--muted);color:var(--surface);font-size:0.7rem;font-weight:700;cursor:help;margin-left:4px;position:relative}.tooltip-icon:hover::after,.tooltip-icon:focus::after{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--text);color:var(--bg);padding:6px 10px;font-size:0.75rem;font-weight:400;max-width:280px;white-space:normal;z-index:100;margin-bottom:4px;box-shadow:0 4px 12px var(--card-shadow)}</style></head><body data-theme="light"><header><div class="bar"><div><h1>Client Console</h1><p> Direct communication interface for remote client diagnostics and command execution. </p></div><div class="header-actions"><a class="pill secondary" href="/">Dashboard</a><a class="pill" href="/client-console">Client Console</a><a class="pill secondary" href="/config-generator">Config Generator</a><a class="pill secondary" href="/contacts">Contacts</a><a class="pill secondary" href="/serial-monitor">Serial Monitor</a><a class="pill secondary" href="/calibration">Calibration</a><a class="pill secondary" href="/historical">Historical Data</a><a class="pill secondary" href="/server-settings">Server Settings</a></div></div></header><main><div class="card"><div style="margin-bottom:16px;display:flex;gap:12px;align-items:center"><label>Target Client: <select id="clientSelect" style="padding:8px" onchange="loadClientConfig()"><option value="">Select Client...</option><option value="client1">Client-01</option><option value="client2">Client-02</option></select></label><button class="secondary" onclick="clearConsole()">Clear Output</button></div><div class="console-output" id="consoleOutput"><div class="log-entry"><span class="log-time">[10:00:01]</span> <span class="log-info">System initialized. Ready for commands.</span></div></div><div class="console-input-area"><input type="text" class="console-input" id="cmdInput" placeholder="Enter command (e.g. /status, /reset, /config)..."><button onclick="sendCommand()">Send</button></div></div><div class="card" id="configEditor" style="display:none;"><h2>Edit Client Configuration</h2><form id="editorForm"><div class="form-grid"><label class="field"><span>Site Name</span><input id="editSiteName" type="text" placeholder="Site Name" required></label><label class="field"><span>Device Label</span><input id="editDeviceLabel" type="text" placeholder="Device Label" required></label><label class="field"><span>Sample Minutes</span><input id="editSampleMinutes" type="number" value="30" min="1" max="1440"></label><label class="field"><span>Level Change Threshold (in)<span class="tooltip-icon" tabindex="0" data-tooltip="Minimum level change in inches required before sending telemetry. Set to 0 to send all readings.">?</span></span><input id="editLevelChangeThreshold" type="number" step="0.1" value="0" placeholder="0 = disabled"></label></div><h3>Power Configuration</h3><div class="form-grid"><label class="field"><span>Power Type<span class="tooltip-icon" tabindex="0" data-tooltip="Select the power configuration for this client installation.">?</span></span><select id="editPowerConfig" onchange="updateEditPowerConfigInfo()"><option value="grid">Grid Powered</option><option value="grid_battery">Grid Powered with 12V Battery Backup</option><option value="solar_pwm">Solar Powered with Basic PWM Charger</option><option value="solar_mppt">Solar Powered with Basic MPPT</option><option value="solar_modbus_mppt">Solar Powered with Modbus MPPT</option></select></label></div><div id="editPowerConfigInfo" style="display:none;background:var(--chip);border:1px solid var(--card-border);padding:12px;margin-bottom:16px;font-size:0.9rem;color:var(--muted);"><strong>Hardware Requirement:</strong> Modbus MPPT requires the Arduino Opta with RS485 expansion module for communication with the MPPT charge controller.</div><h3>Site-Specific Daily Email Recipients</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These email recipients will receive daily reports only for this specific site. This is separate from the unified server-wide daily email list in Server Settings.</p><div id="editDailyEmailContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddDailyEmailBtn" class="secondary">+ Add Email Recipient</button></div><h3>Site-Specific SMS Alarm Contacts</h3><p style="color: var(--muted); font-size: 0.9rem; margin-bottom: 12px;">These contacts will receive SMS alerts only for alarms from this specific site's sensors.</p><div id="editSmsContactsContainer" style="margin-bottom:16px;"></div><div class="actions" style="margin-bottom:24px;margin-top:0;"><button type="button" id="editAddSmsContactBtn" class="secondary">+ Add SMS Contact</button></div><div class="actions"><button type="button" id="saveConfigBtn">Save Configuration</button><button type="button" class="secondary" onclick="cancelEdit()">Cancel</button></div></form></div></main><script>

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is significant code duplication between config_generator.html and client_console.html for the power configuration dropdown, contact list management (daily emails and SMS), and related helper functions. The functions addEditDailyEmailContact, removeEditDailyEmail, addEditSmsContact, removeEditSmsContact, and updateEditPowerConfigInfo are essentially duplicates of the config generator versions with "Edit" prefixes. Consider extracting this shared logic into a common JavaScript module or utility functions to improve maintainability.

Copilot uses AI. Check for mistakes.

Large diffs are not rendered by default.

Loading