aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/group-settings.js
diff options
context:
space:
mode:
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.js78
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>
`;
}