mirror of
https://github.com/balzack/databag.git
synced 2025-03-13 09:00:06 +00:00
prparing for service config flow
This commit is contained in:
parent
0f5232c371
commit
1023d0781e
@ -6,7 +6,7 @@ import {NativeRouter} from 'react-router-native';
|
||||
import {Routes, Route} from 'react-router-dom';
|
||||
import {Root} from './src/root/Root';
|
||||
import {Access} from './src/access/Access';
|
||||
import {Node} from './src/node/Node';
|
||||
import {Service} from './src/service/Service';
|
||||
import {Session} from './src/session/Session';
|
||||
|
||||
import {useColorScheme} from 'react-native';
|
||||
@ -120,7 +120,7 @@ function App(): React.JSX.Element {
|
||||
<Routes>
|
||||
<Route path="/" element={<Text>EMPTY</Text>} />
|
||||
<Route path="/access" element={<Access />} />
|
||||
<Route path="/node" element={<Node />} />
|
||||
<Route path="/service" element={<Service />} />
|
||||
<Route path="/session" element={<Session />} />
|
||||
</Routes>
|
||||
</NativeRouter>
|
||||
|
@ -257,7 +257,7 @@ export function Access() {
|
||||
)}
|
||||
{state.mode === 'admin' && (
|
||||
<View style={styles.body}>
|
||||
<Text variant="headlineSmall">{state.strings.adminAccess}</Text>
|
||||
<Text variant="headlineSmall">{state.strings.admin}</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
mode="flat"
|
||||
|
8
app/client/mobile/src/accounts/Accounts.tsx
Normal file
8
app/client/mobile/src/accounts/Accounts.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {SafeAreaView, Image, View, Pressable} from 'react-native';
|
||||
import {Text} from 'react-native-paper';
|
||||
|
||||
export function Accounts() {
|
||||
return <Text>ACCOUNTS</Text>
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {useState, useEffect, useRef} from 'react';
|
||||
import {DatabagSDK, Session, Focus} from 'databag-client-sdk';
|
||||
import {DatabagSDK, Service, Session, Focus} from 'databag-client-sdk';
|
||||
import {Platform, PermissionsAndroid} from 'react-native';
|
||||
import {SessionStore} from '../SessionStore';
|
||||
import {NativeCrypto} from '../NativeCrypto';
|
||||
@ -44,6 +44,7 @@ export function useAppContext() {
|
||||
const local = useRef(new LocalStore());
|
||||
const sdk = useRef(databag);
|
||||
const [state, setState] = useState({
|
||||
service: null as null | Service,
|
||||
session: null as null | Session,
|
||||
focus: null as null | Focus,
|
||||
fullDayTime: false,
|
||||
@ -179,11 +180,11 @@ export function useAppContext() {
|
||||
return await sdk.current.username(username, token, node, secure);
|
||||
},
|
||||
adminLogin: async (token: string, node: string, secure: boolean, code: string) => {
|
||||
const login = await sdk.current.configure(node, secure, token, code);
|
||||
updateState({node: login});
|
||||
const service = await sdk.current.configure(node, secure, token, code);
|
||||
updateState({ service });
|
||||
},
|
||||
adminLogout: async () => {
|
||||
updateState({node: null});
|
||||
updateState({service: null});
|
||||
},
|
||||
};
|
||||
|
||||
|
BIN
app/client/mobile/src/images/splash.png
Normal file
BIN
app/client/mobile/src/images/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
@ -1,21 +0,0 @@
|
||||
import {Button} from 'react-native-paper';
|
||||
import {Text} from 'react-native';
|
||||
import {AppContext} from '../context/AppContext';
|
||||
import {View} from 'react-native';
|
||||
import React, {useContext} from 'react';
|
||||
|
||||
export function Node() {
|
||||
const app = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Text>NODE!</Text>
|
||||
<Text>NODE!</Text>
|
||||
<Text>NODE!</Text>
|
||||
<Text>NODE!</Text>
|
||||
<Button mode="contained" onPress={app.actions.adminLogout}>
|
||||
LOGOUT
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
@ -34,16 +34,16 @@ export function useRoot() {
|
||||
navigate('/');
|
||||
} else if (state.pathname === '/session' && !app.state.session) {
|
||||
navigate('/');
|
||||
} else if (state.pathname === '/node' && !app.state.node) {
|
||||
} else if (state.pathname === '/service' && !app.state.service) {
|
||||
navigate('/');
|
||||
} else if (state.pathname === '/' && !app.state.session && !app.state.node) {
|
||||
} else if (state.pathname === '/' && !app.state.session && !app.state.service) {
|
||||
navigate('/access');
|
||||
} else if (state.pathname !== '/node' && app.state.node) {
|
||||
navigate('/node');
|
||||
} else if (state.pathname !== '/service' && app.state.service) {
|
||||
navigate('/service');
|
||||
} else if (state.pathname !== '/session' && app.state.session) {
|
||||
navigate('/session');
|
||||
}
|
||||
}, [state.pathname, app.state.session, app.state.node, app.state.initialized, navigate]);
|
||||
}, [state.pathname, app.state.session, app.state.service, app.state.initialized, navigate]);
|
||||
|
||||
const actions = {};
|
||||
|
||||
|
111
app/client/mobile/src/service/Service.styled.ts
Normal file
111
app/client/mobile/src/service/Service.styled.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import { Colors } from '../constants/Colors';
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
service: {
|
||||
position: 'relative',
|
||||
},
|
||||
container: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
noHeader: {
|
||||
headerBackTitleVisible: false,
|
||||
},
|
||||
full: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
},
|
||||
alert: {
|
||||
position: 'absolute',
|
||||
top: '33%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignContent: 'center',
|
||||
},
|
||||
alertArea: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
borderRadius: 16,
|
||||
},
|
||||
alertLabel: {
|
||||
color: Colors.offsync,
|
||||
padding: 0,
|
||||
textAlign: 'center',
|
||||
lineHeight: 20,
|
||||
fontSize: 16,
|
||||
},
|
||||
frame: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
left: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '33%',
|
||||
maxWidth: 300,
|
||||
},
|
||||
right: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
workarea: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
identity: {
|
||||
flexShrink: 0,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
channels: {
|
||||
flexGrow: 1,
|
||||
height: 1,
|
||||
},
|
||||
screen: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
body: {
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
},
|
||||
tabs: {
|
||||
flexShrink: 0,
|
||||
height: 64,
|
||||
width: '100%',
|
||||
displlay: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
idleTab: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
opacity: 0.5,
|
||||
},
|
||||
activeTab: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
ring: {
|
||||
paddingLeft: 16,
|
||||
},
|
||||
});
|
143
app/client/mobile/src/service/Service.tsx
Normal file
143
app/client/mobile/src/service/Service.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import React, {useState, useCallback, useEffect} from 'react';
|
||||
import {SafeAreaView, Pressable, View, useColorScheme} from 'react-native';
|
||||
import {styles} from './Service.styled';
|
||||
import {IconButton, Surface, Text, Icon} from 'react-native-paper';
|
||||
import {Accounts} from '../accounts/Accounts';
|
||||
import {Setup} from '../setup/Setup';
|
||||
import {useService} from './useService.hook';
|
||||
import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native';
|
||||
import {createDrawerNavigator} from '@react-navigation/drawer';
|
||||
import {Colors} from '../constants/Colors';
|
||||
|
||||
const SetupDrawer = createDrawerNavigator();
|
||||
|
||||
export function Service() {
|
||||
const { state, actions } = useService();
|
||||
const [tab, setTab] = useState('accounts');
|
||||
const scheme = useColorScheme();
|
||||
const showAccounts = {display: tab === 'accounts' ? 'flex' : 'none'};
|
||||
const showSetup = {display: tab === 'setup' ? 'flex' : 'none'};
|
||||
|
||||
return (
|
||||
<View style={styles.service}>
|
||||
{state.layout !== 'large' && (
|
||||
<Surface elevation={3}>
|
||||
<SafeAreaView style={styles.full}>
|
||||
<View style={styles.screen}>
|
||||
<View
|
||||
style={{
|
||||
...styles.body,
|
||||
...showAccounts,
|
||||
}}>
|
||||
<Accounts />
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
...styles.body,
|
||||
...showSetup,
|
||||
}}>
|
||||
<Setup />
|
||||
</View>
|
||||
<View style={styles.tabs}>
|
||||
{tab === 'accounts' && (
|
||||
<IconButton
|
||||
style={styles.activeTab}
|
||||
mode="contained"
|
||||
icon={'contacts'}
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setTab('accounts');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tab !== 'accounts' && (
|
||||
<IconButton
|
||||
style={styles.idleTab}
|
||||
mode="contained"
|
||||
icon={'contacts-outline'}
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setTab('accounts');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tab === 'setup' && (
|
||||
<IconButton
|
||||
style={styles.activeTab}
|
||||
mode="contained"
|
||||
icon={'cog'}
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setTab('setup');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tab !== 'setup' && (
|
||||
<IconButton
|
||||
style={styles.idleTab}
|
||||
mode="contained"
|
||||
icon={'cog-outline'}
|
||||
size={28}
|
||||
onPress={() => {
|
||||
setTab('setup');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Surface>
|
||||
)}
|
||||
{state.layout === 'large' && (
|
||||
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
<View style={styles.container}>
|
||||
<SetupScreen />
|
||||
</View>
|
||||
</NavigationContainer>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function SetupScreen() {
|
||||
const SetupComponent = useCallback(
|
||||
() => (
|
||||
<Surface elevation={3} mode="flat">
|
||||
<SafeAreaView>
|
||||
<Setup />
|
||||
</SafeAreaView>
|
||||
</Surface>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<SetupDrawer.Navigator
|
||||
id="SetupDrawer"
|
||||
drawerContent={SetupComponent}
|
||||
screenOptions={{
|
||||
drawerStyle: {width: '50%'},
|
||||
drawerPosition: 'right',
|
||||
drawerType: 'front',
|
||||
headerShown: false,
|
||||
overlayColor: 'rgba(8,8,8,.9)',
|
||||
}}>
|
||||
<SetupDrawer.Screen name="home">{({navigation}) => <AccountScreen setup={navigation} />}</SetupDrawer.Screen>
|
||||
</SetupDrawer.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountScreen({setup}) {
|
||||
return (
|
||||
<View style={styles.frame}>
|
||||
<Accounts />
|
||||
<IconButton
|
||||
mode="contained"
|
||||
icon={'cog'}
|
||||
size={28}
|
||||
onPress={setup.openDrawer}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
31
app/client/mobile/src/service/useService.hook.ts
Normal file
31
app/client/mobile/src/service/useService.hook.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {useEffect, useState, useContext, useRef} from 'react';
|
||||
import {AppContext} from '../context/AppContext';
|
||||
import {DisplayContext} from '../context/DisplayContext';
|
||||
import {ContextType} from '../context/ContextType';
|
||||
|
||||
export function useService() {
|
||||
const display = useContext(DisplayContext) as ContextType;
|
||||
const app = useContext(AppContext) as ContextType;
|
||||
|
||||
const [state, setState] = useState({
|
||||
layout: null,
|
||||
strings: {},
|
||||
});
|
||||
|
||||
const updateState = (value: any) => {
|
||||
setState(s => ({...s, ...value}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const {layout, strings} = display.state;
|
||||
updateState({layout, strings});
|
||||
}, [display.state]);
|
||||
|
||||
const actions = {
|
||||
logout: async () => {
|
||||
await app.actions.adminLogout();
|
||||
},
|
||||
};
|
||||
|
||||
return {state, actions};
|
||||
}
|
8
app/client/mobile/src/setup/Setup.tsx
Normal file
8
app/client/mobile/src/setup/Setup.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import {SafeAreaView, Image, View, Pressable} from 'react-native';
|
||||
import {Text} from 'react-native-paper';
|
||||
|
||||
export function Setup() {
|
||||
return <Text>SETUP</Text>
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ export class DatabagSDK {
|
||||
|
||||
public async configure(node: string, secure: boolean, token: string, mfaCode: string | null): Promise<Service> {
|
||||
const access = await setAdmin(node, secure, token, mfaCode);
|
||||
return new ServiceModule(this.log, node, secure, token);
|
||||
return new ServiceModule(this.log, node, secure, access);
|
||||
}
|
||||
|
||||
public async automate(node: string, secure: boolean, token: string): Promise<Contributor> {
|
||||
|
9
app/sdk/src/net/addAdminMFAuth.ts
Normal file
9
app/sdk/src/net/addAdminMFAuth.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function addAdminMFAuth(server: string, secure: boolean, token: string): Promise<{ secretImage: string, secretText: string }> {
|
||||
const endpoint = `http${secure ? 's' : ''}://${server}/admin/mfauth?token=${token}`;
|
||||
const mfa = await fetchWithTimeout(endpoint, { method: 'POST' });
|
||||
checkResponse(mfa.status);
|
||||
return await mfa.json();
|
||||
}
|
||||
|
9
app/sdk/src/net/getAdminMFAuth.ts
Normal file
9
app/sdk/src/net/getAdminMFAuth.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function getAdminMFAuth(server: string, secure: boolean, token: string): Promise<boolean> {
|
||||
const endpoint = `http${secure ? 's' : ''}://${server}/admin/mfauth?token=${token}`;
|
||||
const mfa = await fetchWithTimeout(endpoint, { method: 'GET' });
|
||||
checkResponse(mfa.status);
|
||||
return await mfa.json();
|
||||
}
|
||||
|
8
app/sdk/src/net/removeAdminMFAuth.ts
Normal file
8
app/sdk/src/net/removeAdminMFAuth.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function removeAdminMFAuth(server: string, secure: boolean, token: string) {
|
||||
const endpoint = `http${secure ? 's' : ''}://${server}/admin/mfauth?token=${token}`;
|
||||
const { status } = await fetchWithTimeout(endpoint, { method: 'DELETE' });
|
||||
checkResponse(status);
|
||||
}
|
||||
|
8
app/sdk/src/net/setAdminMFAuth.ts
Normal file
8
app/sdk/src/net/setAdminMFAuth.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { checkResponse, fetchWithTimeout } from './fetchUtil';
|
||||
|
||||
export async function setAdminMFAuth(server: string, secure: boolean, token: string, code: string): Promise<void> {
|
||||
const endpoint = `http${secure ? 's' : ''}://${server}/admin/mfauth?token=${token}&code=${code}`;
|
||||
const { status } = await fetchWithTimeout(endpoint, { method: 'PUT' });
|
||||
checkResponse(status);
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import type { Service } from './api';
|
||||
import type { Member, Setup } from './types';
|
||||
import type { Logging } from './logging';
|
||||
import { getAdminMFAuth } from './net/getAdminMFAuth';
|
||||
import { setAdminMFAuth } from './net/setAdminMFAuth';
|
||||
import { addAdminMFAuth } from './net/addAdminMFAuth';
|
||||
import { removeAdminMFAuth } from './net/removeAdminMFAuth';
|
||||
|
||||
export class ServiceModule implements Service {
|
||||
private log: Logging;
|
||||
@ -54,4 +58,20 @@ export class ServiceModule implements Service {
|
||||
}
|
||||
|
||||
public async setSetup(config: Setup): Promise<void> {}
|
||||
|
||||
public async enableMFA(): Promise<{ secretImage: string, secretText: string}> {
|
||||
const { node, secure, token } = this;
|
||||
const { secretImage, secretText } = await addAdminMFAuth(node, secure, token);
|
||||
return { secretImage, secretText };
|
||||
}
|
||||
|
||||
public async disableMFA(): Promise<void> {
|
||||
const { node, secure, token } = this;
|
||||
await removeAdminMFAuth(node, secure, token);
|
||||
}
|
||||
|
||||
public async confirmMFA(code: string): Promise<void> {
|
||||
const { node, secure, token } = this;
|
||||
await setAdminMFAuth(node, secure, token, code);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user