- Admin screen: the simple Screen Time setup, the web addresses the app needs, addresses devices were seen reaching, and a Check this device test - Report-only Content-Security-Policy so devices report any other address a page loads (nothing is blocked) - Pictures from other sites pass through the hub (/api/img): podcast search covers, Plex Home pictures, a VBV cover, search answers - docs/v2-roadmap.md: the family-first 2.0 plan, with Family Chat (text only) and no photos or calls Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d5aaad41a
commit
74592c25a5
14 files changed
+224
-7
No files matched your search
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
// ---- iPad and Screen Time (admin): which web addresses the app needs, the ones devices were seen reaching, and a check you run on the device itself.
|
||||
const stOk = u => /^https?:\/\//i.test(u || '') ? '/api/img?u=' + encodeURIComponent(u) : (u || ''); // a picture from another site, passed through the hub so the browser only talks to the hub
|
||||
async function screenTimeSheet() {
|
||||
let d; try { d = await jfetch('/api/screentime'); } catch (e) { return toast(e.message); }
|
||||
const row = (x, extra = '') => `<div class="row"><span class="grow"><b>${esc(x.host)}</b><small style="white-space:normal">${esc(x.why || ((x.what || []).join(', ') + ' · seen ' + x.n + (x.n === 1 ? ' time' : ' times')))}</small></span>${extra}<span class="stres" data-h="${esc(x.host)}"></span><button class="ibtn" data-c="${esc(x.host)}" aria-label="Copy ${esc(x.host)}" title="Copy">${icon('copy')}</button></div>`;
|
||||
const hub = d.required[0].host;
|
||||
const el = openSheet('iPad and Screen Time', 'Let the hub through Screen Time without adding each site one by one', `
|
||||
<p style="margin:0 0 8px"><b>The simple way.</b> On the iPad: <b>Settings → Screen Time → Content & Privacy Restrictions → Content Restrictions → Web Content</b>. Choose <b>Limit Adult Websites</b>, then under <b>Always Allow</b> tap <b>Add Website</b> and enter <b>${esc(hub)}</b>. Nothing else needs adding: the hub carries the music, books, podcasts and pictures itself.</p>
|
||||
<p class="sub" style="white-space:normal">Only if you use <b>Allowed Websites Only</b> do you need to add every address below.</p>
|
||||
<h2>Addresses the app needs</h2><div class="list">${d.required.map(x => row(x)).join('')}</div>
|
||||
${d.seen.length ? `<h2>Others your devices reached</h2><p class="sub" style="white-space:normal">Seen while people used the app (a podcast cover, a weather extension, a site you added). Add only what you want available.</p><div class="list">${d.seen.map(x => row(x)).join('')}</div>` : '<p class="sub" style="white-space:normal">No other addresses have been seen yet. Use the app for a day on the iPad and any others will show up here.</p>'}
|
||||
<h2>Only sometimes</h2><div class="list">${d.optional.map(x => row(x)).join('')}</div>
|
||||
<div class="bar"><button class="btn ghost" id="stcopy" style="flex:1;justify-content:center">Copy the list</button><button class="btn" id="stchk" style="flex:1;justify-content:center">Check this device</button></div>
|
||||
<p class="sub" style="white-space:normal">“Check this device” tries each address from the device you are on, and shows which ones Screen Time (or the network) is blocking. Run it on the iPad.</p>
|
||||
${d.seen.length ? '<div class="bar"><button class="btn ghost" id="stclr">Clear the seen list</button></div>' : ''}`);
|
||||
el.querySelector('.sheet').classList.add('wide');
|
||||
el.querySelectorAll('[data-c]').forEach(b => b.onclick = () => { navigator.clipboard.writeText(b.dataset.c).then(() => toast('Copied ' + b.dataset.c), () => toast(b.dataset.c)); });
|
||||
const all = [...d.required, ...d.seen, ...d.optional].map(x => x.host);
|
||||
el.querySelector('#stcopy').onclick = () => navigator.clipboard.writeText(all.join('\n')).then(() => toast('Copied ' + all.length + ' addresses'), () => toast('Could not copy'));
|
||||
el.querySelector('#stchk').onclick = async e => {
|
||||
const b = e.currentTarget; b.disabled = true; b.textContent = 'Checking…';
|
||||
await Promise.all(all.map(async h => {
|
||||
const mark = el.querySelector(`.stres[data-h="${CSS.escape(h)}"]`); if (!mark) return;
|
||||
try { await fetch(h === location.hostname ? '/manifest.webmanifest' : 'https://' + h + '/', { mode: 'no-cors', cache: 'no-store', signal: AbortSignal.timeout(8000) }); mark.textContent = ' ✓'; mark.style.color = 'var(--accent)'; mark.title = 'Reachable'; }
|
||||
catch { mark.textContent = ' ✗'; mark.style.color = 'var(--danger)'; mark.title = 'Blocked or not reachable from this device'; }
|
||||
}));
|
||||
b.disabled = false; b.textContent = 'Check again';
|
||||
};
|
||||
if (el.querySelector('#stclr')) el.querySelector('#stclr').onclick = async () => { try { await jfetch('/api/screentime', { method: 'DELETE' }); closeSheet(); toast('Cleared'); } catch (x) { toast(x.message); } };
|
||||
}
|
||||
Reference in new issue
Block a user