Fix: chunked storage (12x255=3060 chars), no CFG reference, clean parseCfg/saveCfg
This commit is contained in:
+31
-26
@@ -128,13 +128,14 @@ input[type=time]::-webkit-calendar-picker-indicator{filter:invert(.5)}
|
||||
<div class="modal"><h2>⚙ Settings</h2>
|
||||
<div class="fg"><label>Home Assistant URL</label><input id="set-url" class="fi" type="text"/></div>
|
||||
<div class="fg"><label>Long-Lived Access Token</label><input id="set-tok" class="fi" type="password"/></div>
|
||||
<div class="ibox"><strong>Config not saving?</strong> Go to HA → Developer Tools → Services → call <code>input_text.reload</code>, then hard-refresh this page.</div>
|
||||
<div class="ibox"><strong>Config not saving?</strong> Go to HA → Developer Tools → Actions → call <code>input_text.reload</code>, then hard-refresh this page.</div>
|
||||
<div class="macts"><button class="btn btn-g" onclick="closeModal('settings-modal')">Cancel</button><button class="btn btn-p" onclick="saveSettings()">Save & Reconnect</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const CFG='input_text.parental_control_config';
|
||||
const DHCP='sensor.opnsense_dhcp_leases';
|
||||
const CHUNKS=12, CHUNK_SIZE=250;
|
||||
const chunkId=i=>`input_text.parental_config_${i}`;
|
||||
const COLORS=['#ef5350','#ff7043','#ffa726','#66bb6a','#26c6da','#42a5f5','#7e57c2','#ec407a','#26a69a','#e6c229'];
|
||||
const MAC_RE=/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i;
|
||||
let ws,wsId=1,pending={},retryTimer;
|
||||
@@ -198,28 +199,46 @@ async function onAuth(){
|
||||
catch(e2){console.warn('states failed',e2);}
|
||||
}
|
||||
try{await send({type:'subscribe_events',event_type:'state_changed'});}catch(e){}
|
||||
parseCfg(states[CFG]?.state);
|
||||
parseCfg();
|
||||
updateDHCP();
|
||||
render();
|
||||
}
|
||||
function onStateChange({entity_id,new_state}){
|
||||
states[entity_id]=new_state;
|
||||
if(entity_id===CFG){parseCfg(new_state?.state);schedRender();return;}
|
||||
if(entity_id===chunkId(0)){parseCfg();schedRender();return;}
|
||||
if(entity_id===DHCP){updateDHCP();schedRender();return;}
|
||||
if(entity_id.startsWith('device_tracker.')){schedRender();}
|
||||
}
|
||||
|
||||
function parseCfg(val){
|
||||
try{if(val&&val!=='unknown'&&val!=='unavailable'&&val.trim()[0]==='{'){cfg=JSON.parse(val);if(!cfg.users)cfg.users=[];}}catch(e){}
|
||||
function parseCfg(){
|
||||
let combined='';
|
||||
for(let i=0;i<CHUNKS;i++){
|
||||
const s=states[chunkId(i)];
|
||||
const v=s?.state||'';
|
||||
if(v==='unknown'||v==='unavailable')break;
|
||||
combined+=v;
|
||||
}
|
||||
combined=combined.trim();
|
||||
if(!combined||combined[0]!=='{'){
|
||||
try{combined=localStorage.getItem('pc2_backup')||'';}catch(e){}
|
||||
}
|
||||
if(combined&&combined[0]==='{'){
|
||||
try{cfg=JSON.parse(combined);if(!cfg.users)cfg.users=[];}catch(e){cfg={users:[]};}
|
||||
}
|
||||
}
|
||||
async function saveCfg(){
|
||||
const json=JSON.stringify(cfg);
|
||||
try{localStorage.setItem('pc2_backup',json);}catch(e){}
|
||||
try{await send({type:'call_service',domain:'input_text',service:'set_value',service_data:{entity_id:CFG,value:json}});}
|
||||
catch(e){toast('Save failed — try input_text.reload (see ⚙)','e');}
|
||||
const saves=[];
|
||||
for(let i=0;i<CHUNKS;i++){
|
||||
const chunk=json.slice(i*CHUNK_SIZE,(i+1)*CHUNK_SIZE);
|
||||
saves.push(send({type:'call_service',domain:'input_text',service:'set_value',
|
||||
service_data:{entity_id:chunkId(i),value:chunk}}));
|
||||
}
|
||||
try{await Promise.all(saves);}
|
||||
catch(e){toast('Save failed','e');console.error('saveCfg',e);}
|
||||
}
|
||||
|
||||
/* ── DHCP LEASES ── */
|
||||
function getLeases(){return states[DHCP]?.attributes?.rows||[];}
|
||||
function updateDHCP(){
|
||||
const s=states[DHCP];
|
||||
@@ -239,7 +258,6 @@ function deviceInfo(mac){
|
||||
const online=lease.online==='1'||lease.online===true||lease.state==='active'||lease.state==='online';
|
||||
return{online,ip,label:lease.hostname||lease.descr||mac};
|
||||
}
|
||||
// fallback: GPS device trackers
|
||||
const m=normMac(mac);
|
||||
for(const[eid,s]of Object.entries(states)){
|
||||
if(!eid.startsWith('device_tracker.'))continue;
|
||||
@@ -257,7 +275,6 @@ function discoveredDevices(){
|
||||
if(!mac||assigned.has(mac))continue;
|
||||
out.push({mac,name:r.hostname||r.descr||mac,ip:r.ipaddr||r.address||r.ip||'',online:r.online==='1'||r.online===true||r.state==='active'});
|
||||
}
|
||||
// also GPS trackers
|
||||
for(const[eid,s]of Object.entries(states)){
|
||||
if(!eid.startsWith('device_tracker.'))continue;
|
||||
const a=s.attributes||{};const mac=normMac(a.mac||a.mac_address||a.macaddress||'');
|
||||
@@ -267,7 +284,6 @@ function discoveredDevices(){
|
||||
return out.sort((a,b)=>b.online-a.online);
|
||||
}
|
||||
|
||||
/* ── BLOCKING ── */
|
||||
async function callBlock(ip){await send({type:'call_service',domain:'script',service:'parental_block_ip',service_data:{ip}});}
|
||||
async function callUnblock(ip){await send({type:'call_service',domain:'script',service:'parental_unblock_ip',service_data:{ip}});}
|
||||
async function callApply(){try{await send({type:'call_service',domain:'script',service:'parental_apply_firewall',service_data:{}});}catch(e){}}
|
||||
@@ -292,7 +308,6 @@ async function toggleDevice(userId,mac){
|
||||
catch(e){toast(`Failed to ${block?'block':'unblock'} ${dev.name}`,'e');}
|
||||
}
|
||||
|
||||
/* ── SCHEDULE ── */
|
||||
setInterval(runSchedules,60000);
|
||||
async function runSchedules(){
|
||||
const now=new Date();const hhmm=pad2(now.getHours())+':'+pad2(now.getMinutes());
|
||||
@@ -320,7 +335,6 @@ async function onSchedChange(userId,field,value){
|
||||
await saveCfg();
|
||||
}
|
||||
|
||||
/* ── USERS ── */
|
||||
function uid(){return 'u'+Date.now().toString(36)+Math.random().toString(36).slice(2,6);}
|
||||
function openAddUserModal(){document.getElementById('u-name').value='';buildColorPicker(COLORS[cfg.users.length%COLORS.length]);document.getElementById('add-user-modal').classList.add('open');}
|
||||
function createUser(){
|
||||
@@ -336,14 +350,13 @@ async function deleteUser(userId){
|
||||
cfg.users=cfg.users.filter(u=>u.id!==userId);await saveCfg();render();toast(`${user.name} removed`,'s');
|
||||
}
|
||||
|
||||
/* ── DEVICES ── */
|
||||
function openAddDeviceModal(userId){
|
||||
devTargetUser=userId;
|
||||
document.getElementById('d-name').value='';document.getElementById('d-mac').value='';
|
||||
const disc=discoveredDevices();const list=document.getElementById('disc-list');
|
||||
const note=document.getElementById('dhcp-note');
|
||||
const hasDHCP=states[DHCP]&&states[DHCP].state!=='unavailable'&&states[DHCP].state!=='unknown';
|
||||
document.getElementById('dhcp-note').innerHTML=hasDHCP?'':
|
||||
'<div class="ibox">DHCP sensor not loaded — add <code>opnsense_leases_url</code> to secrets.yaml and restart HA. You can still enter a MAC manually.</div>';
|
||||
note.innerHTML=hasDHCP?'':`<div class="ibox">DHCP sensor not loaded — add <code>opnsense_leases_url</code> to secrets.yaml and restart HA. You can still enter a MAC manually.</div>`;
|
||||
if(!disc.length){list.innerHTML=`<div style="padding:10px;text-align:center;font-size:.8rem;color:var(--muted)">${hasDHCP?'All found devices are assigned':'No DHCP data yet'}</div>`;}
|
||||
else{list.innerHTML=disc.map(d=>`<div class="dlist-item" data-mac="${d.mac}" data-name="${esc(d.name)}" onclick="pickDisc(this)"><span style="width:7px;height:7px;border-radius:50%;background:${d.online?'var(--ok)':'var(--muted)'};flex-shrink:0"></span><div><div class="dl-name">${esc(d.name)}</div><div class="dl-sub">${d.mac}${d.ip?' · '+d.ip:''}</div></div></div>`).join('');}
|
||||
openModal('add-dev-modal');
|
||||
@@ -363,7 +376,6 @@ async function removeDevice(userId,mac){
|
||||
user.devices=user.devices.filter(d=>d.mac!==mac);await saveCfg();render();toast(`${dev.name} removed`,'s');
|
||||
}
|
||||
|
||||
/* ── RENDER ── */
|
||||
function schedRender(){clearTimeout(renderTimer);renderTimer=setTimeout(render,400);}
|
||||
function render(){
|
||||
const grid=document.getElementById('grid');
|
||||
@@ -374,14 +386,7 @@ function renderUser(user){
|
||||
const sched=user.schedule||{enabled:false,weekday:{block_time:'21:00',unblock_time:'07:00'},weekend:{block_time:'22:00',unblock_time:'08:00'}};
|
||||
const open=!!schedOpen[user.id];
|
||||
const devHtml=user.devices.length?user.devices.map(d=>renderDev(user.id,d)).join(''):'<div class="nodev">No devices — click Add Device</div>';
|
||||
return `<div class="card${user.blocked?' blocked':''}">
|
||||
<div class="ch"><span class="cdot" style="background:${user.color}"></span><span class="cname">${esc(user.name)}</span><span class="slbl${user.blocked?' blocked':''}">${user.blocked?'Blocked':'Online'}</span><label class="tw" title="${user.blocked?'Unblock':'Block'} all"><div class="tog"><input type="checkbox" ${user.blocked?'checked':''} onchange="toggleUser('${user.id}')"><div class="tt"></div><div class="th"></div></div></label><button class="delbtn" onclick="deleteUser('${user.id}')" title="Remove">✕</button></div>
|
||||
<div class="cb"><div class="stitle">Devices (${user.devices.length})</div><div class="devlist">${devHtml}</div><button class="btn btn-g btn-sm" onclick="openAddDeviceModal('${user.id}')">+ Add Device</button>
|
||||
<div class="sched"><div class="shed-hd" onclick="toggleSched('${user.id}')"><span class="shed-ttl">🕐 Schedule${sched.enabled?' (active)':''}</span><span class="shed-chev${open?' open':''}">▼</span></div>
|
||||
<div class="shed-body${open?' open':''}"><div class="shed-en"><label class="tw"><div class="tog sm grn"><input type="checkbox" ${sched.enabled?'checked':''} onchange="onSchedChange('${user.id}','enabled',this.checked)"><div class="tt"></div><div class="th"></div></div><span style="font-size:.76rem;color:var(--muted)">Enable schedule blocking</span></label></div>
|
||||
<div class="shed-row"><label>Weekdays</label><input class="tin" type="time" value="${sched.weekday.block_time}" onchange="onSchedChange('${user.id}','weekday.block_time',this.value)"/><span class="tsep">→</span><input class="tin" type="time" value="${sched.weekday.unblock_time}" onchange="onSchedChange('${user.id}','weekday.unblock_time',this.value)"/></div>
|
||||
<div class="shed-row"><label>Weekend</label><input class="tin" type="time" value="${sched.weekend.block_time}" onchange="onSchedChange('${user.id}','weekend.block_time',this.value)"/><span class="tsep">→</span><input class="tin" type="time" value="${sched.weekend.unblock_time}" onchange="onSchedChange('${user.id}','weekend.unblock_time',this.value)"/></div>
|
||||
<p class="shed-hint">Blocked between first and second time. Overnight works (e.g. 21:00 → 07:00).</p></div></div></div></div>`;
|
||||
return `<div class="card${user.blocked?' blocked':''}"><div class="ch"><span class="cdot" style="background:${user.color}"></span><span class="cname">${esc(user.name)}</span><span class="slbl${user.blocked?' blocked':''}">${user.blocked?'Blocked':'Online'}</span><label class="tw" title="${user.blocked?'Unblock':'Block'} all"><div class="tog"><input type="checkbox" ${user.blocked?'checked':''} onchange="toggleUser('${user.id}')"><div class="tt"></div><div class="th"></div></div></label><button class="delbtn" onclick="deleteUser('${user.id}')" title="Remove">✕</button></div><div class="cb"><div class="stitle">Devices (${user.devices.length})</div><div class="devlist">${devHtml}</div><button class="btn btn-g btn-sm" onclick="openAddDeviceModal('${user.id}')">+ Add Device</button><div class="sched"><div class="shed-hd" onclick="toggleSched('${user.id}')"><span class="shed-ttl">🕐 Schedule${sched.enabled?' (active)':''}</span><span class="shed-chev${open?' open':''}">▼</span></div><div class="shed-body${open?' open':''}"><div class="shed-en"><label class="tw"><div class="tog sm grn"><input type="checkbox" ${sched.enabled?'checked':''} onchange="onSchedChange('${user.id}','enabled',this.checked)"><div class="tt"></div><div class="th"></div></div><span style="font-size:.76rem;color:var(--muted)">Enable schedule blocking</span></label></div><div class="shed-row"><label>Weekdays</label><input class="tin" type="time" value="${sched.weekday.block_time}" onchange="onSchedChange('${user.id}','weekday.block_time',this.value)"/><span class="tsep">→</span><input class="tin" type="time" value="${sched.weekday.unblock_time}" onchange="onSchedChange('${user.id}','weekday.unblock_time',this.value)"/></div><div class="shed-row"><label>Weekend</label><input class="tin" type="time" value="${sched.weekend.block_time}" onchange="onSchedChange('${user.id}','weekend.block_time',this.value)"/><span class="tsep">→</span><input class="tin" type="time" value="${sched.weekend.unblock_time}" onchange="onSchedChange('${user.id}','weekend.unblock_time',this.value)"/></div><p class="shed-hint">Blocked between first and second time. Overnight works (e.g. 21:00 → 07:00).</p></div></div></div></div>`;
|
||||
}
|
||||
function renderDev(userId,dev){
|
||||
const info=deviceInfo(dev.mac);const ip=info.ip||dev.last_ip;const sub=[dev.mac,ip].filter(Boolean).join(' · ');
|
||||
|
||||
Reference in New Issue
Block a user