diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/group-settings.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/group-settings.js | 78 |
1 files changed, 78 insertions, 0 deletions
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 fba8a0e..048f831 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js @@ -410,7 +410,14 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, onAppDirectories, onRefreshIndex, onPaired, onLeft }) { const [members, setMembers] = useState([]); + // Asked and not answered — the owner is the only one told (the hub leaves + // the field out for anyone else). + const [invited, setInvited] = useState([]); const [adminId, setAdminId] = useState(''); + // Nodes other than the owner's that asked to serve this group, and what the + // owner answered. Only the owner reads it. + const [hosts, setHosts] = useState([]); + const [hostBusy, setHostBusy] = useState(''); const [loading, setLoading] = useState(true); const [inviteUser, setInviteUser] = useState(''); const [inviting, setInviting] = useState(false); @@ -802,6 +809,7 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, hubFetch(`/v1/groups/${groupId}/members`, { token }) .then(data => { setMembers(data.members || []); + setInvited(data.invited || []); setAdminId(data.admin_id || ''); }) .catch(() => {}) @@ -810,6 +818,30 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, useEffect(() => { loadMembers(); }, [loadMembers]); + const ownsGroup = Boolean(group && group.is_admin); + const loadHosts = useCallback(() => { + if (!ownsGroup) return; + hubFetch(`/v1/groups/${groupId}/hosts`, { token }) + .then(data => setHosts(data.hosts || [])) + .catch(() => setHosts([])); + }, [groupId, token, ownsGroup]); + + useEffect(() => { loadHosts(); }, [loadHosts]); + + const decideHost = useCallback(async (host, approve) => { + setHostBusy(host.node_id); + setError(''); + try { + await hubFetch(`/v1/groups/${groupId}/hosts/${host.node_id}`, + { method: approve ? 'POST' : 'DELETE', token }); + loadHosts(); + } catch (err) { + setError(err.message); + } finally { + setHostBusy(''); + } + }, [groupId, token, loadHosts]); + useEffect(() => { hubFetch('/v1/users/me/preferences', { token }) .then(prefs => setInviteByEmail(prefs[INVITE_EMAIL_PREF] === 'true')) @@ -1382,12 +1414,58 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef, </td> </tr> `)} + ${invited.map(m => html` + <tr key=${m.user_id}> + <td>${m.username}</td> + <td><span class="badge">${t('members.invited')}</span></td> + <td class="admin-actions"> + ${isAdmin && html` + <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> + `)} </tbody> </table> ${isAdmin && members.length > 1 && html` <p class="settings-hint">${t('members.remove_hint')}</p> `} </${CollapsibleSection}> + + ${ownsGroup && html` + <${CollapsibleSection} title=${t('hosts.title')}> + <p class="settings-hint">${t('hosts.hint')}</p> + ${hosts.length === 0 && html`<p class="settings-hint">${t('hosts.none')}</p>`} + ${hosts.length > 0 && html` + <table class="admin-table"> + <tbody> + ${hosts.map(h => html` + <tr key=${h.node_id}> + <td>${t('hosts.from', { name: h.username })} + <br /><code class="node-key">${h.pk_node}</code></td> + <td><span class="badge">${t(`hosts.status_${h.status}`)}</span> + ${h.online && html` <span class="badge">${t('hosts.online')}</span>`}</td> + <td class="admin-actions"> + ${h.status !== 'approved' && html` + <button class="admin-btn" disabled=${hostBusy === h.node_id} + onClick=${() => decideHost(h, true)}>${t('hosts.approve')}</button>`} + ${h.status !== 'refused' && html` + <button class="admin-btn danger" disabled=${hostBusy === h.node_id} + onClick=${() => decideHost(h, false)}>${t('hosts.refuse')}</button>`} + </td> + </tr> + `)} + </tbody> + </table> + `} + </${CollapsibleSection}> + `} </div> `; } |