implementing account management

This commit is contained in:
balzack 2025-02-18 22:14:54 -08:00
parent f798dfe689
commit db77fbd0a4
10 changed files with 164 additions and 33 deletions

View File

@ -1,31 +1,105 @@
import React, {useState} from 'react';
import React, {useEffect, useState} from 'react';
import {FlatList, SafeAreaView, Image, View, Pressable} from 'react-native';
import {Text, IconButton, Divider, Surface, useTheme} from 'react-native-paper';
import {useAccounts} from './useAccounts.hook';
import {styles} from './Accounts.styled';
import { Card } from '../card/Card';
import { Colors } from '../constants/Colors';
import { Confirm } from '../confirm/Confirm';
export function Accounts({ setup }: { setup: ()=>void }) {
const { state, actions } = useAccounts();
const theme = useTheme();
const [loading, setLoading] = useState('');
const [failed, setFailed] = useState(false);
const [remove, setRemove] = useState(null);
const [removeParams, setRemoveParams] = useState({});
const [removing, setRemoving] = useState(null);
const [blocking, setBlocking] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadAccounts();
}, []);
const loadAccounts = async () => {
if (!loading) {
setLoading(true);
try {
await actions.reload();
} catch (err) {
console.log(err);
}
setLoading(false);
}
}
const failedParams = {
title: state.strings.operationFailed,
prompt: state.strings.tryAgain,
cancel: {
label: state.strings.close,
action: ()=>{setFailed(false)},
},
};
const blockAccount = async (accountId: number, block: boolean) => {
if (!blocking) {
setBlocking(accountId);
try {
await actions.blockAccount(accountId, block);
} catch (err) {
console.log(err);
setFailed(true);
}
setBlocking(null);
}
}
const removeAccount = (accountId: number) => {
if (!remove) {
setRemoveParams({
title: state.strings.confirmDelete,
prompt: state.strings.areSure,
confirm: {
label: state.strings.remove,
action: async () => {
if (!removing) {
setRemoving(accountId);
try {
await actions.removeAccount(accountId);
} catch (err) {
console.log(err);
setFailed(true);
}
setRemoving(false);
setRemove(false);
}
},
},
cancel: {
label: state.strings.cancel,
action: () => {setRemove(false)},
},
})
setRemove(true);
}
}
return (
<View style={styles.accounts}>
{ state.layout === 'large' && (
<View style={styles.header}>
<Text style={styles.largeTitle}>{ state.strings.accounts }</Text>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={()=>{}} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="cog-outline" onPress={setup} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={loadAccounts} />
<IconButton style={styles.icon} loading={false} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
<IconButton style={styles.icon} loading={false} iconColor={Colors.primary} mode="contained" icon="cog-outline" onPress={setup} />
</View>
)}
{ state.layout === 'small' && (
<View style={styles.header}>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={()=>{}} />
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="refresh" onPress={loadAccounts} />
<Text style={styles.smallTitle}>{ state.strings.accounts }</Text>
<IconButton style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
<IconButton style={styles.icon} loading={false} iconColor={Colors.primary} mode="contained" icon="account-plus-outline" onPress={()=>{}} />
</View>
)}
<Divider style={styles.line} bold={true} />
@ -37,9 +111,9 @@ export function Accounts({ setup }: { setup: ()=>void }) {
showsVerticalScrollIndicator={false}
renderItem={({item}) => {
const options = [
<IconButton key="disable" style={styles.icon} loading={loading} iconColor={Colors.primary} mode="contained" icon="lock-open-variant-outline" onPress={()=>{}} />,
<IconButton key="reset" style={styles.icon} loading={loading} iconColor={Colors.pending} mode="contained" icon="account-cancel-outline" onPress={()=>{}} />,
<IconButton key="remove" style={styles.icon} loading={loading} iconColor={Colors.offsync} mode="contained" icon="trash-can-outline" onPress={()=>{}} />
<IconButton key="disable" style={styles.icon} loading={false} iconColor={Colors.primary} mode="contained" icon="lock-open-variant-outline" onPress={()=>{}} />,
<IconButton key="reset" style={styles.icon} loading={blocking === item.accountId} iconColor={Colors.pending} mode="contained" icon={item.disabled ? 'account-check-outline' : 'account-cancel-outline'} onPress={()=>{blockAccount(item.accountId, !item.disabled)}} />,
<IconButton key="remove" style={styles.icon} loading={removing === item.accountId} iconColor={Colors.offsync} mode="contained" icon="trash-can-outline" onPress={()=>{removeAccount(item.accountId)}} />
];
return (
<Card
@ -64,6 +138,8 @@ export function Accounts({ setup }: { setup: ()=>void }) {
</View>
)}
<Divider style={styles.line} bold={true} />
<Confirm show={failed} params={failedParams} />
<Confirm show={remove} busy={removing} params={removeParams} />
</View>
);
}

View File

@ -11,6 +11,7 @@ export function useAccounts() {
layout: '',
strings: {},
members: [] as Member[],
loading: false,
});
const updateState = (value: any) => {
@ -18,12 +19,16 @@ export function useAccounts() {
};
const sync = async () => {
try {
const service = app.state.service;
const members = await service.getMembers();
updateState({ members });
} catch (err) {
console.log(err);
if (!state.loading) {
try {
updateState({ loading: true });
const service = app.state.service;
const members = await service.getMembers();
updateState({ members, loading: false });
} catch (err) {
console.log(err);
updateState({ loading: false });
}
}
}
@ -32,13 +37,22 @@ export function useAccounts() {
updateState({ layout, strings});
}, [display.state]);
useEffect(() => {
if (app.state.service) {
sync();
}
}, [app.state.service]);
const actions = {
reload: sync,
addAccount: async () => {
return await app.state.service.createMemberAccess();
},
accessAccount: async (accountId: number) => {
return await app.state.service.resetMemberAccess(accoutId);
},
blockAccount: async (accountId: number, flag: boolean) => {
await app.state.service.blockMember(accountId, flag);
await sync();
},
removeAccount: async (accountId: number) => {
await app.state.service.removeMember(accountId);
await sync();
},
};
return {state, actions};

View File

@ -22,7 +22,7 @@ export function Confirm({show, busy, params}) {
{params.prompt && <Text style={styles.prompt}>{params.prompt}</Text>}
<View style={styles.controls}>
{params.cancel && (
<Button mode="outlined" onPress={params.cancel.action}>
<Button mode="outlined" disabled={busy} onPress={params.cancel.action}>
{params.cancel.label}
</Button>
)}

View File

@ -18,8 +18,6 @@ export function Setup() {
},
};
console.log(state.setup);
return (
<View style={styles.setup}>
<View style={styles.header}>

View File

@ -164,8 +164,8 @@ export interface Focus {
export interface Service {
getMembers(): Promise<Member[]>;
createMemberAccess(): Promise<string>;
resetMemberAccess(): Promise<string>;
blockMember(flag: boolean): Promise<void>;
resetMemberAccess(accontId: number): Promise<string>;
blockMember(accountId: number, flag: boolean): Promise<void>;
removeMember(accountId: number): Promise<void>;
getSetup(): Promise<Setup>;
setSetup(setup: Setup): Promise<void>;

View File

@ -0,0 +1,9 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addNodeAccount(server: string, secure: boolean, token: string): Promise<string> {
const endpoint = `http${secure ? 's' : ''}://${server}/admin/accounts?token=${token}`;
const create = await fetchWithTimeout(endpoint, { method: 'POST' });
checkResponse(create.status);
return await create.json();
}

View File

@ -0,0 +1,8 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addNodeAccountAccess(server: string, secure: boolean, token: string, accountId: number): Promise<string> {
const endpoint = `http${secure ? 's' : ''}://${server}/admin/accounts/${accountId}/auth?token=${token}`;
const access = await fetchWithTimeout(endpoint, { method: 'POST' });
checkResponse(access.status);
return await access.json();
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeNodeAccount(server: string, secure: boolean, token: string, accountId: number) {
const endpoint = `http${secure ? 's' : ''}://${server}/admin/accounts/${accountId}?token=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
checkResponse(status);
}

View File

@ -0,0 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setNodeAccount(server: string, secure: boolean, token: string, accountId: number, disabled: boolean) {
const endpoint = `http${secure ? 's' : ''}://${server}/admin/accounts/${accountId}/status?token=${token}`;
const { status } = await fetchWithTimeout(endpoint, { method: 'PUT', body: JSON.stringify(disabled) });
checkResponse(status);
}

View File

@ -10,6 +10,10 @@ import { addAdminMFAuth } from './net/addAdminMFAuth';
import { removeAdminMFAuth } from './net/removeAdminMFAuth';
import { getNodeConfig } from './net/getNodeConfig';
import { setNodeConfig } from './net/setNodeConfig';
import { addNodeAccount } from './net/addNodeAccount';
import { addNodeAccountAccess } from './net/addNodeAccountAccess';
import { removeNodeAccount } from './net/removeNodeAccount';
import { setNodeAccount } from './net/setNodeAccount';
export class ServiceModule implements Service {
private log: Logging;
@ -25,16 +29,24 @@ export class ServiceModule implements Service {
}
public async createMemberAccess(): Promise<string> {
return '';
const { node, secure, token } = this;
return await addNodeAccount(node, secure, token);
}
public async resetMemberAccess(): Promise<string> {
return '';
public async resetMemberAccess(accountId: number): Promise<string> {
const { node, secure, token } = this;
return await addNodeAccountAccess(node, secure, token, accountId);
}
public async blockMember(flag: boolean): Promise<void> {}
public async blockMember(accountId: number, flag: boolean): Promise<void> {
const { node, secure, token } = this;
await setNodeAccount(node, secure, token, accountId, flag);
}
public async removeMember(accountId: number): Promise<void> {}
public async removeMember(accountId: number): Promise<void> {
const { node, secure, token } = this;
await removeNodeAccount(node, secure, token, accountId);
}
public async getMembers(): Promise<Member[]> {
const { node, secure, token } = this;
@ -42,7 +54,7 @@ export class ServiceModule implements Service {
return accounts.map(account => {
const { accountId, guid, handle, name, imageSet, revision, disabled, storageUsed } = account;
const imageUrl = imageSet ? getMemberImageUrl(node, secure, token, accountId, revision) : avatar;
return { accountId, guid, handle, name, imageUrl, storageUsed };
return { accountId, guid, handle, name, imageUrl, disabled, storageUsed };
});
}