completed admin dashboard

This commit is contained in:
Roland Osborne 2022-10-14 12:56:03 -07:00
parent 62f4bda0f5
commit 532fe0b647
10 changed files with 165 additions and 26 deletions

View File

@ -354,6 +354,8 @@ PODS:
- React-logger (= 0.69.5) - React-logger (= 0.69.5)
- React-perflogger (= 0.69.5) - React-perflogger (= 0.69.5)
- ReactCommon/turbomodule/core (= 0.69.5) - ReactCommon/turbomodule/core (= 0.69.5)
- RNCClipboard (1.11.1):
- React-Core
- RNGestureHandler (2.7.0): - RNGestureHandler (2.7.0):
- React-Core - React-Core
- RNImageCropPicker (0.38.0): - RNImageCropPicker (0.38.0):
@ -443,6 +445,7 @@ DEPENDENCIES:
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNReanimated (from `../node_modules/react-native-reanimated`) - RNReanimated (from `../node_modules/react-native-reanimated`)
@ -543,6 +546,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor" :path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
ReactCommon: ReactCommon:
:path: "../node_modules/react-native/ReactCommon" :path: "../node_modules/react-native/ReactCommon"
RNCClipboard:
:path: "../node_modules/@react-native-clipboard/clipboard"
RNGestureHandler: RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler" :path: "../node_modules/react-native-gesture-handler"
RNImageCropPicker: RNImageCropPicker:
@ -600,6 +605,7 @@ SPEC CHECKSUMS:
React-RCTVibration: 42b34fde72e42446d9b08d2b9a3ddc2fa9ac6189 React-RCTVibration: 42b34fde72e42446d9b08d2b9a3ddc2fa9ac6189
React-runtimeexecutor: c778439c3c430a5719d027d3c67423b390a221fe React-runtimeexecutor: c778439c3c430a5719d027d3c67423b390a221fe
ReactCommon: ab1003b81be740fecd82509c370a45b1a7dda0c1 ReactCommon: ab1003b81be740fecd82509c370a45b1a7dda0c1
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33 RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33
RNImageCropPicker: ffbba608264885c241cbf3a8f78eb7aeeb978241 RNImageCropPicker: ffbba608264885c241cbf3a8f78eb7aeeb978241
RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa RNReanimated: 7faa787e8d4493fbc95fab2ad331fa7625828cfa

View File

@ -9,6 +9,7 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@react-native-clipboard/clipboard": "^1.11.1",
"@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/bottom-tabs": "^6.4.0",
"@react-navigation/drawer": "^6.5.0", "@react-navigation/drawer": "^6.5.0",
"@react-navigation/native": "^6.0.13", "@react-navigation/native": "^6.0.13",

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addAccountAccess(token, accountId) { export async function addAccountAccess(server, token, accountId) {
let access = await fetchWithTimeout(`/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' }) let access = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/auth?token=${token}`, { method: 'POST' })
checkResponse(access); checkResponse(access);
return await access.json() return await access.json()
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function addAccountCreate(token) { export async function addAccountCreate(server, token) {
let access = await fetchWithTimeout(`/admin/accounts?token=${token}`, { method: 'POST' }) let access = await fetchWithTimeout(`https://${server}/admin/accounts?token=${token}`, { method: 'POST' })
checkResponse(access); checkResponse(access);
return await access.json() return await access.json()
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function removeAccount(token, accountId) { export async function removeAccount(server, token, accountId) {
let res = await fetchWithTimeout(`/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' }) let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}?token=${token}`, { method: 'DELETE' })
checkResponse(res); checkResponse(res);
} }

View File

@ -1,7 +1,7 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function setAccountStatus(token, accountId, disabled) { export async function setAccountStatus(server, token, accountId, disabled) {
let res = await fetchWithTimeout(`/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) }) let res = await fetchWithTimeout(`https://${server}/admin/accounts/${accountId}/status?token=${token}`, { method: 'PUT', body: JSON.stringify(disabled) })
checkResponse(res); checkResponse(res);
} }

View File

@ -1,6 +1,8 @@
import { TextInput, Alert, Switch, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native'; import { TextInput, Alert, Switch, TouchableOpacity, View, Text, Modal, FlatList, KeyboardAvoidingView } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import Ionicons from '@expo/vector-icons/AntDesign'; import AntIcon from '@expo/vector-icons/AntDesign';
import MatIcon from '@expo/vector-icons/MaterialCommunityIcons';
import { styles } from './Dashboard.styled'; import { styles } from './Dashboard.styled';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useDashboard } from './useDashboard.hook'; import { useDashboard } from './useDashboard.hook';
@ -26,22 +28,83 @@ export function Dashboard(props) {
} }
} }
const addUser = async () => {
try {
await actions.addUser();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Generate Access Token',
'Please try again.',
);
}
}
const accessUser = async (accountId) => {
try {
await actions.accessUser(accountId);
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Generate Access Token',
'Please try again.',
);
}
}
const removeUser = (accountId) => {
Alert.alert(
"Deleting Account",
"Confirm?",
[
{ text: "Cancel", onPress: () => {}, },
{ text: "Delete", onPress: async() => {
try {
await actions.removeUser(accountId);
}
catch (err) {
console.log(err);
Alert.alert(
"Failed to Delete Account",
"Please try again.",
);
}
}}
]
)
}
const enableUser = async (accountId, enabled) => {
try {
await actions.enableUser(accountId, enabled);
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Update Account',
'Please try again.',
);
}
}
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.headerLabel}>Accounts</Text> <Text style={styles.headerLabel}>Accounts</Text>
<TouchableOpacity onPress={actions.refresh}> <TouchableOpacity onPress={actions.refresh}>
<Ionicons style={styles.icon} name={'reload1'} size={20} /> <AntIcon style={styles.icon} name={'reload1'} size={20} />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={actions.showEditConfig}> <TouchableOpacity onPress={actions.showEditConfig}>
<Ionicons style={styles.icon} name={'setting'} size={20} /> <AntIcon style={styles.icon} name={'setting'} size={20} />
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={actions.logout}> <TouchableOpacity onPress={actions.logout}>
<Ionicons style={styles.icon} name={'logout'} size={20} /> <AntIcon style={styles.icon} name={'logout'} size={20} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.end}> <View style={styles.end}>
<TouchableOpacity onPress={actions.showAddUser}> <TouchableOpacity onPress={addUser}>
<Ionicons style={styles.icon} name={'adduser'} size={24} /> <AntIcon style={styles.icon} name={'adduser'} size={24} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
@ -57,11 +120,22 @@ export function Dashboard(props) {
<Text style={styles.handle}>{ item.handle }</Text> <Text style={styles.handle}>{ item.handle }</Text>
</View> </View>
<View style={styles.control}> <View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => actions.showAccessUser(item.accountId)}> <TouchableOpacity onPress={() => accessUser(item.accountId)}>
<Ionicons style={styles.icon} name={'unlock'} size={20} /> <AntIcon style={styles.icon} name={'unlock'} size={20} />
</TouchableOpacity> </TouchableOpacity>
<Ionicons style={styles.disable} name={'closecircleo'} size={20} /> { item.disabled && (
<Ionicons style={styles.delete} name={'deleteuser'} size={20} /> <TouchableOpacity onPress={() => enableUser(item.accountId, true)}>
<AntIcon style={styles.disable} name={'playcircleo'} size={20} />
</TouchableOpacity>
)}
{ !item.disabled && (
<TouchableOpacity onPress={() => enableUser(item.accountId, false)}>
<MatIcon style={styles.disable} name={'block-helper'} size={20} />
</TouchableOpacity>
)}
<TouchableOpacity onPress={() => removeUser(item.accountId)}>
<AntIcon style={styles.delete} name={'deleteuser'} size={20} />
</TouchableOpacity>
</View> </View>
</View> </View>
)} )}
@ -149,7 +223,16 @@ export function Dashboard(props) {
> >
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}> <KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
<View style={styles.modalContainer}> <View style={styles.modalContainer}>
<Text style={styles.modalHeader}>Add User:</Text> <View style={styles.modalHeader}>
<Text style={styles.modalHeaderText}>Create Account:</Text>
</View>
<View style={styles.accessToken}>
<Text style={styles.tokenLabel}>Token:</Text>
<TouchableOpacity style={styles.copy} onPress={() => Clipboard.setString(state.createToken)}>
<Text style={styles.token}>{ state.createToken }</Text>
<AntIcon style={styles.icon} name={'copy1'} size={20} />
</TouchableOpacity>
</View>
<View style={styles.modalControls}> <View style={styles.modalControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideAddUser}> <TouchableOpacity style={styles.cancel} onPress={actions.hideAddUser}>
<Text>Done</Text> <Text>Done</Text>
@ -168,7 +251,16 @@ export function Dashboard(props) {
> >
<KeyboardAvoidingView behavior="height" style={styles.modalBackground}> <KeyboardAvoidingView behavior="height" style={styles.modalBackground}>
<View style={styles.modalContainer}> <View style={styles.modalContainer}>
<Text style={styles.modalHeader}>Access User:</Text> <View style={styles.modalHeader}>
<Text style={styles.modalHeaderText}>Access Account:</Text>
</View>
<View style={styles.accessToken}>
<Text style={styles.tokenLabel}>Token:</Text>
<TouchableOpacity style={styles.copy}>
<Text style={styles.token}>{ state.accessToken }</Text>
<AntIcon style={styles.icon} name={'copy1'} size={20} />
</TouchableOpacity>
</View>
<View style={styles.modalControls}> <View style={styles.modalControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideAccessUser}> <TouchableOpacity style={styles.cancel} onPress={actions.hideAccessUser}>
<Text>Done</Text> <Text>Done</Text>

View File

@ -142,6 +142,12 @@ export const styles = StyleSheet.create({
modalBody: { modalBody: {
padding: 8, padding: 8,
}, },
accessToken: {
padding: 16,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
modalLabel: { modalLabel: {
paddingTop: 8, paddingTop: 8,
color: Colors.text, color: Colors.text,
@ -203,4 +209,18 @@ export const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
marginTop: 8, marginTop: 8,
}, },
tokenLabel: {
fontSize: 16,
color: Colors.text,
},
copy: {
marginLeft: 8,
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
padding: 8,
borderWidth: 1,
borderRadius: 4,
borderColor: Colors.divider,
},
}); });

View File

@ -7,6 +7,10 @@ import { getNodeConfig } from 'api/getNodeConfig';
import { setNodeConfig } from 'api/setNodeConfig'; import { setNodeConfig } from 'api/setNodeConfig';
import { getNodeAccounts } from 'api/getNodeAccounts'; import { getNodeAccounts } from 'api/getNodeAccounts';
import { getAccountImageUrl } from 'api/getAccountImageUrl'; import { getAccountImageUrl } from 'api/getAccountImageUrl';
import { removeAccount } from 'api/removeAccount';
import { addAccountCreate } from 'api/addAccountCreate';
import { setAccountStatus } from 'api/setAccountStatus';
import { addAccountAccess } from 'api/addAccountAccess';
export function useDashboard(config, server, token) { export function useDashboard(config, server, token) {
@ -23,6 +27,7 @@ export function useDashboard(config, server, token) {
enableImage: true, enableImage: true,
enableAudio: true, enableAudio: true,
enableVideo: true, enableVideo: true,
createToken: null,
}); });
const navigate = useNavigate(); const navigate = useNavigate();
@ -32,7 +37,7 @@ export function useDashboard(config, server, token) {
} }
const setAccountItem = (item) => { const setAccountItem = (item) => {
const { name, handle, imageSet, accountId } = item; const { name, handle, imageSet, accountId, disabled } = item;
let logo; let logo;
if (imageSet) { if (imageSet) {
@ -41,7 +46,7 @@ export function useDashboard(config, server, token) {
else { else {
logo = 'avatar'; logo = 'avatar';
} }
return { logo, name, handle, accountId }; return { logo, name, handle, accountId, disabled };
} }
const refreshAccounts = async () => { const refreshAccounts = async () => {
@ -71,14 +76,16 @@ export function useDashboard(config, server, token) {
hideEditConfig: () => { hideEditConfig: () => {
updateState({ editConfig: false }); updateState({ editConfig: false });
}, },
showAddUser: () => { addUser: async () => {
updateState({ addUser: true }); const createToken = await addAccountCreate(server, token);
updateState({ addUser: true, createToken });
}, },
hideAddUser: () => { hideAddUser: () => {
updateState({ addUser: false }); updateState({ addUser: false });
}, },
showAccessUser: (accessId) => { accessUser: async (accountId) => {
updateState({ accessUser: true, accountId: accessId }); const accessToken = await addAccountAccess(server, token, accountId);
updateState({ accessUser: true, accessToken });
}, },
hideAccessUser: () => { hideAccessUser: () => {
updateState({ accessUser: false }); updateState({ accessUser: false });
@ -106,6 +113,14 @@ export function useDashboard(config, server, token) {
const config = { accountStorage: Number(storage), domain, keyType, enableImage, enableAudio, enableVideo }; const config = { accountStorage: Number(storage), domain, keyType, enableImage, enableAudio, enableVideo };
await setNodeConfig(server, token, config); await setNodeConfig(server, token, config);
}, },
enableUser: async (accountId, enabled) => {
await setAccountStatus(server, token, accountId, !enabled);
await refreshAccounts();
},
removeUser: async (accountId) => {
await removeAccount(server, token, accountId);
await refreshAccounts();
},
}; };
return { state, actions }; return { state, actions };

View File

@ -1511,6 +1511,11 @@
mkdirp "^1.0.4" mkdirp "^1.0.4"
rimraf "^3.0.2" rimraf "^3.0.2"
"@react-native-clipboard/clipboard@^1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz#d3a9e685ce2383b1e92b89a334896c5575cc103d"
integrity sha512-nvSIIHzybVWqYxcJE5hpT17ekxAAg383Ggzw5WrYHtkKX61N1AwaKSNmXs5xHV7pmKSOe/yWjtSwxIzfW51I5Q==
"@react-native-community/cli-clean@^8.0.4": "@react-native-community/cli-clean@^8.0.4":
version "8.0.4" version "8.0.4"
resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz" resolved "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz"