-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance client config UI with power dropdown, site-specific contact lists, dynamic relay selection, and client console editor #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
68e6fe0
673cf05
ea991fe
e2366f6
ade1e27
6a6656f
c7a5ef7
2426cec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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> | ||||||
|
||||||
| <!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
AI
Jan 16, 2026
There was a problem hiding this comment.
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.
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
loadClientConfigfunction 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.