summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/MESHBAY_DESIGN.md6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-settings.js42
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js5
-rw-r--r--packages/meshbay-hub/tests/test_spa_ordering.py4
14 files changed, 60 insertions, 7 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index 9c87372..78a9cb5 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -393,7 +393,11 @@ Four properties, each load-bearing:
attempt is an audit event.
3. **The node's roster is the authority**, not hub membership. A hub that invents
an account, adds it to a group and mints it a token gets
- `not_authorized_for_group`.
+ `not_authorized_for_group`. The Members list says the same: it shows the
+ accounts the node has admitted (the sealed group roster, §11.7 of the protocol).
+ One the hub counts as a member but that has not presented its code yet is
+ shown to the owner alone, as waiting for its code; when the roster cannot be
+ read, the hub's list is shown.
4. **Wrapping happens on every connection.** Nothing is stored per member, so key
rotation propagates by itself and revocation actually takes effect. (Rotating
the key after a revocation is still required — the ex-member holds the current
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
index 29aa7eb..3ffa524 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
@@ -783,6 +783,27 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
useEffect(() => { loadMembers(); }, [loadMembers]);
+ // Who the node has admitted to this group. The hub counts someone a member
+ // once they accept the invitation; the node, once they present their code.
+ // Until then they are not in the group, and are listed as waiting, to the
+ // owner only. Null when the roster cannot be read: the hub's list is shown.
+ const [admitted, setAdmitted] = useState(null);
+ useEffect(() => {
+ const transport = transportRef.current;
+ if (!connected || !transport || !transport.connected) { setAdmitted(null); return; }
+ let cancelled = false;
+ transport.groupRoster({ fresh: true })
+ .then((roster) => { if (!cancelled) setAdmitted(new Set(roster.byAccount.keys())); })
+ .catch(() => { if (!cancelled) setAdmitted(null); });
+ return () => { cancelled = true; };
+ }, [connected, transportRef, members]);
+ const joined = admitted
+ ? members.filter(m => m.user_id === adminId || admitted.has(m.user_id))
+ : members;
+ const awaitingCode = admitted
+ ? members.filter(m => m.user_id !== adminId && !admitted.has(m.user_id))
+ : [];
+
const ownsGroup = Boolean(group && group.is_admin);
const loadHosts = useCallback(() => {
if (!ownsGroup) return;
@@ -1340,7 +1361,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
</${CollapsibleSection}>
`}
- <${CollapsibleSection} title=${`${t('group.tab_members')} (${members.length})`}>
+ <${CollapsibleSection} title=${`${t('group.tab_members')} (${joined.length})`}>
<table class="admin-table">
<thead>
<tr>
@@ -1350,7 +1371,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
</tr>
</thead>
<tbody>
- ${members.map(m => html`
+ ${joined.map(m => html`
<tr key=${m.user_id}>
<td>${m.username}</td>
<td>
@@ -1372,6 +1393,21 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
</td>
</tr>
`)}
+ ${isAdmin && awaitingCode.map(m => html`
+ <tr key=${m.user_id}>
+ <td>${m.username}</td>
+ <td><span class="badge">${t('members.awaiting_code')}</span></td>
+ <td class="admin-actions">
+ <button class="admin-btn danger" disabled=${removing === m.user_id}
+ onClick=${async () => {
+ if (!await ask(t('members.remove_confirm', { user: m.username }))) return;
+ removeMember(m);
+ }}>
+ ${removing === m.user_id ? '...' : t('members.remove')}
+ </button>
+ </td>
+ </tr>
+ `)}
${invited.map(m => html`
<tr key=${m.user_id}>
<td>${m.username}</td>
@@ -1391,7 +1427,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
`)}
</tbody>
</table>
- ${isAdmin && members.length > 1 && html`
+ ${isAdmin && joined.length > 1 && html`
<p class="settings-hint">${t('members.remove_hint')}</p>
`}
</${CollapsibleSection}>
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 16061af..5cc9968 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -1237,6 +1237,7 @@ export default {
'home.accept': "Annehmen",
'home.decline': "Ablehnen",
'members.invited': "eingeladen",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Hosts",
'hosts.hint': "Ihre eigenen Nodes stellen diese Gruppe ohne Rückfrage bereit. Ein anderer Node tut es erst, wenn Sie ihn hier genehmigen; ein Node, der anfragt, steht unten.",
'hosts.none': "Kein anderer Node hat angefragt, diese Gruppe bereitzustellen.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 73c6217..5aa53b7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -1218,6 +1218,7 @@ export default {
'home.accept': "Accept",
'home.decline': "Decline",
'members.invited': "invited",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Hosts",
'hosts.hint': "Your own nodes serve this group without asking. Another node serves it only once you approve it here; a node that asks is listed below.",
'hosts.none': "No other node has asked to host this group.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 7beead3..6558a46 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -1231,6 +1231,7 @@ export default {
'home.accept': "Aceptar",
'home.decline': "Rechazar",
'members.invited': "invitado",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Anfitriones",
'hosts.hint': "Sus propios nodes sirven este grupo sin preguntar. Otro node lo sirve solo cuando usted lo aprueba aquí; un node que lo pide aparece abajo.",
'hosts.none': "Ningún otro node ha pedido alojar este grupo.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 91f9253..5e4c0bf 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -1246,6 +1246,7 @@ export default {
'home.accept': "Accepter",
'home.decline': "Refuser",
'members.invited': "invité",
+ 'members.awaiting_code': "en attente du code",
'hosts.title': "Hôtes",
'hosts.hint': "Vos propres nodes servent ce groupe sans rien demander. Un autre node ne le sert qu'une fois approuvé ici ; un node qui le demande apparaît ci-dessous.",
'hosts.none': "Aucun autre node n'a demandé à héberger ce groupe.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index f558f5f..8772376 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -1245,6 +1245,7 @@ export default {
'home.accept': "Accetta",
'home.decline': "Rifiuta",
'members.invited': "invitato",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Host",
'hosts.hint': "I tuoi node servono questo gruppo senza chiedere. Un altro node lo serve solo dopo che lo approvi qui; un node che lo chiede compare qui sotto.",
'hosts.none': "Nessun altro node ha chiesto di ospitare questo gruppo.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index f03b3e6..fa34470 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -1229,6 +1229,7 @@ export default {
'home.accept': "承諾",
'home.decline': "辞退",
'members.invited': "招待中",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "ホスト",
'hosts.hint': "あなた自身の node は確認なしでこのグループを提供します。他の node は、ここで承認した後にのみ提供します。申請した node は下に表示されます。",
'hosts.none': "このグループのホストを申請した node は他にありません。",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 130213c..fa0841d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -1247,6 +1247,7 @@ export default {
'home.accept': "Accepteren",
'home.decline': "Weigeren",
'members.invited': "uitgenodigd",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Hosts",
'hosts.hint': "Uw eigen nodes bedienen deze groep zonder te vragen. Een andere node doet dat pas nadat u hem hier goedkeurt; een node die erom vraagt, staat hieronder.",
'hosts.none': "Geen andere node heeft gevraagd deze groep te hosten.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index b71094f..9de29ea 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -1273,6 +1273,7 @@ export default {
'home.accept': "Akceptuj",
'home.decline': "Odrzuć",
'members.invited': "zaproszony",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Hosty",
'hosts.hint': "Twoje własne node'y obsługują tę grupę bez pytania. Inny node robi to dopiero po Twojej akceptacji tutaj; node, który o to prosi, jest widoczny poniżej.",
'hosts.none': "Żaden inny node nie prosił o hostowanie tej grupy.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index fa5be5f..908a8db 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -1232,6 +1232,7 @@ export default {
'home.accept': "Aceitar",
'home.decline': "Recusar",
'members.invited': "convidado",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "Hosts",
'hosts.hint': "Seus próprios nodes servem este grupo sem perguntar. Outro node só o serve depois que você o aprova aqui; um node que pede aparece abaixo.",
'hosts.none': "Nenhum outro node pediu para hospedar este grupo.",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index eee91a9..d6fe782 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -1218,6 +1218,7 @@ export default {
'home.accept': "接受",
'home.decline': "拒绝",
'members.invited': "已邀请",
+ 'members.awaiting_code': "waiting for their code",
'hosts.title': "主机",
'hosts.hint': "你自己的 node 无需询问即可提供此群组。其他 node 只有在你于此处批准后才会提供;提出申请的 node 列在下方。",
'hosts.none': "没有其他 node 申请托管此群组。",
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js b/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js
index 8dbb6c0..f079f80 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport-devices.js
@@ -23,7 +23,10 @@ extendTransport(class {
* at first sight, where there is nothing to compare against — that boundary
* is `docs/MESHBAY_DESIGN.md` §3.2's and does not move.
*/
- async groupRoster() {
+ async groupRoster({ fresh = false } = {}) {
+ // `fresh`: read it again rather than keep this connection's copy, for a
+ // page that lists the members and must show who joined since.
+ if (fresh && !this._rosterInFlight) this._roster = null;
if (this._roster) return this._roster;
if (this._rosterInFlight) return this._rosterInFlight;
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py
index 6f28aaa..44f98db 100644
--- a/packages/meshbay-hub/tests/test_spa_ordering.py
+++ b/packages/meshbay-hub/tests/test_spa_ordering.py
@@ -162,7 +162,7 @@ def _component(name: str) -> str:
def test_the_group_settings_panel_renders_what_it_owns():
panel = _component("GroupSettingsPanel")
- assert "members.map(" in panel, "the member list is not rendered"
+ assert "joined.map(" in panel, "the member list is not rendered"
assert "onSubmit=${doInvite}" in panel, "the invite form is not rendered"
assert "onSubmit=${doPair}" in panel, "the pairing form is not rendered"
assert "device.mine_title" in panel, "the devices section is not rendered"
@@ -175,7 +175,7 @@ def test_the_roster_comes_last():
order is for — asked for in those terms.
"""
panel = _component("GroupSettingsPanel")
- listing = panel.index("members.map(")
+ listing = panel.index("joined.map(")
for name, marker in (("the invite form", "onSubmit=${doInvite}"),
("the pairing form", "onSubmit=${doPair}"),
("the devices section", "device.mine_title"),