fedipod 1.29.2 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/device-agent.md +9 -2
- package/gateway.md +43 -4
- package/gui.md +2 -2
- package/lib/core/publisher/index.mjs +3 -0
- package/lib/core/wire.mjs +10 -1
- package/lib/device/cli/commands/setup.mjs +6 -2
- package/lib/device/gateway-move.mjs +58 -0
- package/lib/device/migrate.mjs +3 -1
- package/lib/device/setup.mjs +67 -7
- package/lib/gateway/front-core.mjs +34 -14
- package/lib/gateway/gateway-core.mjs +15 -6
- package/lib/gateway/notices.mjs +84 -0
- package/lib/gateway/quiet.mjs +184 -0
- package/package.json +1 -1
- package/run-agent.mjs +5 -0
- package/scripts/stage-site.mjs +6 -3
- package/web/admin/bar.css +44 -10
- package/web/admin/client/index.html +29 -2
- package/web/admin/gateway.js +19 -0
- package/web/admin/index.html +46 -3
- package/web/admin/notices-bar.js +90 -0
- package/web/admin/record.js +1 -0
- package/web/admin/setup/index.html +29 -2
- package/web/admin/upkeep.js +5 -1
- package/web/app/admin-facade.mjs +24 -0
- package/web/app/agent.mjs +48 -0
- package/web/app/boot.mjs +2 -0
- package/web/app/dist/boot.js +3 -1
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +96 -3
- package/web/app/dist/sw.js.map +3 -3
- package/web/front/admin.html +3 -1
- package/web/front/notices.html +69 -0
- package/web/front/notices.js +107 -0
package/web/front/admin.html
CHANGED
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
<body>
|
|
29
29
|
<h1>Accounts on this server</h1>
|
|
30
30
|
<p class="lede">Every name this server answers for: accounts whose identity lives here, and
|
|
31
|
-
accounts that only use it as their gateway. Sign in as the server's admin to see them
|
|
31
|
+
accounts that only use it as their gateway. Sign in as the server's admin to see them.
|
|
32
|
+
<a href="/notices">Notices</a> — what every account sees behind the bell in its bar — are
|
|
33
|
+
written on their own page.</p>
|
|
32
34
|
|
|
33
35
|
<form id="roster-form">
|
|
34
36
|
<p class="field">
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FediPod — notices</title>
|
|
7
|
+
<link rel="icon" href="/icons/gateway.svg" type="image/svg+xml">
|
|
8
|
+
<style>
|
|
9
|
+
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; --danger:#b00020; }
|
|
10
|
+
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; --danger:#ff8a9a; } }
|
|
11
|
+
body { font: 20px/1.6 Arial, Helvetica, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
12
|
+
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
13
|
+
h2 { font-size: 1.25rem; margin: 1.6rem 0 .4rem; }
|
|
14
|
+
p.lede { color: var(--dim); margin-top: 0; }
|
|
15
|
+
label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
|
|
16
|
+
input[type=url], input[type=text], textarea { width: 100%; padding: .5rem .6rem; font: inherit;
|
|
17
|
+
border: 1px solid var(--line); border-radius: 8px; box-sizing: border-box; }
|
|
18
|
+
textarea { min-height: 9rem; resize: vertical; }
|
|
19
|
+
.hint { color: var(--dim); font-size: .95rem; }
|
|
20
|
+
.primary { background: var(--accent); color: #fff; border: none; border-radius: 9px;
|
|
21
|
+
padding: .6rem 1.1rem; font: inherit; cursor: pointer; }
|
|
22
|
+
.primary[disabled] { opacity: .5; cursor: not-allowed; }
|
|
23
|
+
button { font: inherit; cursor: pointer; }
|
|
24
|
+
button.danger { color: var(--danger); background: none; border: 1px solid var(--danger); border-radius: 9px; padding: .4rem .8rem; }
|
|
25
|
+
[hidden] { display: none !important; }
|
|
26
|
+
ul.notices { list-style: none; padding: 0; margin: 1rem 0; }
|
|
27
|
+
ul.notices li { padding: .8rem 0; border-bottom: 1px solid var(--line); }
|
|
28
|
+
ul.notices .title { font-weight: 600; }
|
|
29
|
+
ul.notices .when { color: var(--dim); font-size: .95rem; margin-left: .6rem; }
|
|
30
|
+
ul.notices .body { white-space: pre-wrap; margin: .3rem 0 .6rem; }
|
|
31
|
+
ul.notices .actions button { margin-right: .6rem; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<h1>Notices</h1>
|
|
36
|
+
<p class="lede">What every account on this server sees behind the bell in its bar. Sign in as the
|
|
37
|
+
server's admin to write, change or remove them. <a href="/roster">Back to the roster.</a></p>
|
|
38
|
+
|
|
39
|
+
<form id="notices-form">
|
|
40
|
+
<p class="field">
|
|
41
|
+
<label for="notices-issuer">Your identity provider</label>
|
|
42
|
+
<input type="url" id="notices-issuer" autocomplete="off" value="https://solidcommunity.net">
|
|
43
|
+
</p>
|
|
44
|
+
<p class="actions">
|
|
45
|
+
<button type="submit" id="notices-signin" class="primary">Sign in & manage notices</button>
|
|
46
|
+
</p>
|
|
47
|
+
<p class="hint" id="notices-note" hidden></p>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<section id="notices-editor" hidden>
|
|
51
|
+
<h2 id="editor-title">New notice</h2>
|
|
52
|
+
<form id="editor-form">
|
|
53
|
+
<label for="notice-title">Title</label>
|
|
54
|
+
<input type="text" id="notice-title" maxlength="120" autocomplete="off" required>
|
|
55
|
+
<label for="notice-body">Text</label>
|
|
56
|
+
<textarea id="notice-body" maxlength="4000" required></textarea>
|
|
57
|
+
<p class="hint">Plain text. A blank line starts a new paragraph; a web address becomes a link.</p>
|
|
58
|
+
<p class="actions">
|
|
59
|
+
<button type="submit" id="notice-save" class="primary">Publish</button>
|
|
60
|
+
<button type="button" id="notice-cancel" hidden>Cancel</button>
|
|
61
|
+
</p>
|
|
62
|
+
</form>
|
|
63
|
+
<h2>Published</h2>
|
|
64
|
+
<ul class="notices" id="notices-list"></ul>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
<script type="module" src="/notices.js"></script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// notices.js — the notices page's script: the admin signs in, then writes,
|
|
2
|
+
// changes and removes the notices every account here sees behind the bell.
|
|
3
|
+
// Served by the front at /notices.js, like admin.js for the roster.
|
|
4
|
+
const $ = (id) => document.getElementById(id);
|
|
5
|
+
const note = (text) => { const n = $('notices-note'); n.hidden = !text; n.textContent = text || ''; };
|
|
6
|
+
|
|
7
|
+
let session = null;
|
|
8
|
+
try {
|
|
9
|
+
const { SessionCore } = await import('/solid-oidc-client.js');
|
|
10
|
+
session = new SessionCore({ redirect_uris: [location.origin + '/notices'], client_name: 'FediPod admin' });
|
|
11
|
+
} catch { /* leave null — the "did not load" note fires */ }
|
|
12
|
+
|
|
13
|
+
$('notices-issuer').addEventListener('input', () => {
|
|
14
|
+
$('notices-signin').disabled = !/^https?:\/\/\S+/.test($('notices-issuer').value.trim());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
$('notices-form').addEventListener('submit', (ev) => {
|
|
18
|
+
ev.preventDefault();
|
|
19
|
+
if (!session) { note('the sign-in library did not load — reload and try again'); return; }
|
|
20
|
+
session.login($('notices-issuer').value.trim(), location.origin + '/notices')
|
|
21
|
+
.catch((e) => note('sign-in failed to start: ' + e.message));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
(async () => {
|
|
25
|
+
if (!session) return;
|
|
26
|
+
await session.handleRedirectFromLogin().catch(() => {});
|
|
27
|
+
if (!session.isActive) return;
|
|
28
|
+
const webId = session.webId;
|
|
29
|
+
let editing = null; // the notice being changed, or null for a new one
|
|
30
|
+
|
|
31
|
+
const when = (iso) => { try { return new Date(iso).toLocaleString(); } catch { return iso; } };
|
|
32
|
+
|
|
33
|
+
const send = async (body) => {
|
|
34
|
+
let res;
|
|
35
|
+
try {
|
|
36
|
+
res = await session.authFetch(location.origin + '/api/notices', {
|
|
37
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
38
|
+
});
|
|
39
|
+
} catch (e) { note('the request failed: ' + e.message); return null; }
|
|
40
|
+
const d = await res.json().catch(() => ({}));
|
|
41
|
+
if (res.status === 403) { note('signed in as ' + webId + ', which is not this server’s admin'); return null; }
|
|
42
|
+
if (res.status === 501) { note('this server keeps no notices'); return null; }
|
|
43
|
+
if (res.status >= 400) { note('refused: ' + (d.error || 'HTTP ' + res.status)); return null; }
|
|
44
|
+
return d;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const startEdit = (n) => {
|
|
48
|
+
editing = n;
|
|
49
|
+
$('editor-title').textContent = n ? 'Change this notice' : 'New notice';
|
|
50
|
+
$('notice-title').value = n ? n.title : '';
|
|
51
|
+
$('notice-body').value = n ? n.body : '';
|
|
52
|
+
$('notice-save').textContent = n ? 'Save changes' : 'Publish';
|
|
53
|
+
$('notice-cancel').hidden = !n;
|
|
54
|
+
$('notice-title').focus();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const load = async () => {
|
|
58
|
+
let res;
|
|
59
|
+
try { res = await fetch(location.origin + '/api/notices', { headers: { accept: 'application/json' }, cache: 'no-store' }); }
|
|
60
|
+
catch (e) { note('the notices could not be read: ' + e.message); return; }
|
|
61
|
+
if (res.status === 501) { note('this server keeps no notices'); return; }
|
|
62
|
+
const d = await res.json().catch(() => ({}));
|
|
63
|
+
const ul = $('notices-list');
|
|
64
|
+
ul.textContent = '';
|
|
65
|
+
for (const n of d.notices || []) {
|
|
66
|
+
const li = document.createElement('li');
|
|
67
|
+
const head = document.createElement('div');
|
|
68
|
+
const t = document.createElement('span'); t.className = 'title'; t.textContent = n.title;
|
|
69
|
+
const w = document.createElement('span'); w.className = 'when';
|
|
70
|
+
w.textContent = when(n.at) + (n.updatedAt && n.updatedAt !== n.at ? ' (changed ' + when(n.updatedAt) + ')' : '');
|
|
71
|
+
head.append(t, w);
|
|
72
|
+
const b = document.createElement('p'); b.className = 'body'; b.textContent = n.body;
|
|
73
|
+
const acts = document.createElement('p'); acts.className = 'actions';
|
|
74
|
+
const edit = document.createElement('button'); edit.type = 'button'; edit.textContent = 'Change';
|
|
75
|
+
edit.onclick = () => startEdit(n);
|
|
76
|
+
const rm = document.createElement('button'); rm.type = 'button'; rm.textContent = 'Remove'; rm.className = 'danger';
|
|
77
|
+
rm.onclick = async () => {
|
|
78
|
+
if (!confirm('Remove the notice “' + n.title + '”? Everyone stops seeing it.')) return;
|
|
79
|
+
if (await send({ action: 'delete', id: n.id })) { note('removed “' + n.title + '”'); await load(); }
|
|
80
|
+
};
|
|
81
|
+
acts.append(edit, rm);
|
|
82
|
+
li.append(head, b, acts);
|
|
83
|
+
ul.append(li);
|
|
84
|
+
}
|
|
85
|
+
if (!(d.notices || []).length) { const li = document.createElement('li'); li.textContent = 'No notices yet.'; ul.append(li); }
|
|
86
|
+
$('notices-editor').hidden = false;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
$('editor-form').addEventListener('submit', async (ev) => {
|
|
90
|
+
ev.preventDefault();
|
|
91
|
+
const title = $('notice-title').value.trim();
|
|
92
|
+
const body = $('notice-body').value.trim();
|
|
93
|
+
if (!title || !body) { note('a title and a text are both needed'); return; }
|
|
94
|
+
$('notice-save').disabled = true;
|
|
95
|
+
const r = await send(editing ? { action: 'update', id: editing.id, title, body } : { action: 'create', title, body });
|
|
96
|
+
$('notice-save').disabled = false;
|
|
97
|
+
if (!r) return;
|
|
98
|
+
note(editing ? 'changed “' + title + '”' : 'published “' + title + '” — everyone sees it within a minute');
|
|
99
|
+
startEdit(null);
|
|
100
|
+
await load();
|
|
101
|
+
});
|
|
102
|
+
$('notice-cancel').addEventListener('click', () => startEdit(null));
|
|
103
|
+
|
|
104
|
+
note('signed in as ' + webId);
|
|
105
|
+
$('notices-form').hidden = true;
|
|
106
|
+
await load();
|
|
107
|
+
})();
|