General cleanup and refinement of the DeviceDetails screen. Implemented more expected functionality, streamlines some patterns. Also fixed some minor issues with the wrong strings due to cut and paste errors.

This commit is contained in:
Dan Lefrancois
2021-11-23 12:06:19 -08:00
parent 4a08482936
commit 0a2b3f7987
4 changed files with 126 additions and 84 deletions

View File

@@ -13,6 +13,16 @@ export function showGeneralMessage(message) {
Alert.alert(strings.messages.titleMessage, message); Alert.alert(strings.messages.titleMessage, message);
} }
export function displayValue(obj, key) {
if (obj && key) {
if (key in obj) {
return obj[key];
}
}
return strings.messages.empty;
}
export function logStringifyPretty(obj, title) { export function logStringifyPretty(obj, title) {
if (title) { if (title) {
console.log(title, JSON.stringify(obj, null, '\t')); console.log(title, JSON.stringify(obj, null, '\t'));

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 B

View File

@@ -20,15 +20,16 @@ export const strings = new LocalizedStrings({
missingEndpoints: 'System is not set up correct - missing configuration information.', missingEndpoints: 'System is not set up correct - missing configuration information.',
samePassword: 'Password is the same as old password.', samePassword: 'Password is the same as old password.',
titleDashboard: 'Dashboard', titleDashboard: 'Dashboard',
titleSignIn: 'Sign In Error', titleDeviceDetails: 'Device Error',
titleSystemSetup: 'System Setup Error',
titleDeviceList: 'Devices Error', titleDeviceList: 'Devices Error',
titleForgotPassword: 'Forgot Password Error', titleForgotPassword: 'Forgot Password Error',
titleMfa: 'Multi-Factor Authentication Error',
titleNetwork: 'Network Error', titleNetwork: 'Network Error',
titleProfile: 'Profile Error', titleProfile: 'Profile Error',
titleResetPassword: 'Reset Password Error', titleResetPassword: 'Reset Password Error',
titleSignIn: 'Sign In Error',
titleSms: 'SMS Error', titleSms: 'SMS Error',
titleMfa: 'Multi-Factor Authentication Error', titleSystemSetup: 'System Setup Error',
titleUpdate: 'Update Error', titleUpdate: 'Update Error',
token: 'Credentials no longer valid, please sign-in again.', token: 'Credentials no longer valid, please sign-in again.',
unknown: 'Unknown error.', unknown: 'Unknown error.',
@@ -85,6 +86,7 @@ export const strings = new LocalizedStrings({
submit: 'Submit', submit: 'Submit',
termsConditions: 'Terms & Conditions', termsConditions: 'Terms & Conditions',
update: 'Update', update: 'Update',
unpause: 'Unpause',
validate: 'Validate', validate: 'Validate',
verify: 'Verify', verify: 'Verify',
}, },
@@ -134,8 +136,11 @@ export const strings = new LocalizedStrings({
wiredClients: 'Wired Clients', wiredClients: 'Wired Clients',
}, },
deviceDetails: { deviceDetails: {
connected: 'Connected',
connectionDetails: 'Connection Details', connectionDetails: 'Connection Details',
connectionType: 'Connection Type', connectionType: 'Connection Type',
connectionTypeWifi: 'WiFi ({0})',
connectionTypeWired: 'Wired ({0})',
description: 'Description', description: 'Description',
deviceDetails: 'Device Details', deviceDetails: 'Device Details',
ipAddress: 'IP Address', ipAddress: 'IP Address',

View File

@@ -13,7 +13,7 @@ import {
import { StyleSheet, SafeAreaView, ScrollView, View, Text } from 'react-native'; import { StyleSheet, SafeAreaView, ScrollView, View, Text } from 'react-native';
import { subscriberDevicesApi, handleApiError } from '../api/apiHandler'; import { subscriberDevicesApi, handleApiError } from '../api/apiHandler';
import { useFocusEffect } from '@react-navigation/native'; import { useFocusEffect } from '@react-navigation/native';
import { showGeneralError } from '../Utils'; import { showGeneralError, displayValue } from '../Utils';
import AccordionSection from '../components/AccordionSection'; import AccordionSection from '../components/AccordionSection';
import ButtonStyled from '../components/ButtonStyled'; import ButtonStyled from '../components/ButtonStyled';
import ImageWithBadge from '../components/ImageWithBadge'; import ImageWithBadge from '../components/ImageWithBadge';
@@ -44,12 +44,13 @@ const DeviceDetails = props => {
const getSubsciberDevice = async (accessPointToQuery, clientToQuery) => { const getSubsciberDevice = async (accessPointToQuery, clientToQuery) => {
if (!subscriberDevicesApi) { if (!subscriberDevicesApi) {
// This is expected to be temporary
return; return;
} }
try { try {
if (!accessPointToQuery || !clientToQuery) { if (!accessPointToQuery || !clientToQuery) {
showGeneralError(strings.errors.titleNetwork, strings.errors.internal); showGeneralError(strings.errors.titleDeviceDetails, strings.errors.internal);
return; return;
} }
@@ -61,24 +62,22 @@ const DeviceDetails = props => {
const response = await subscriberDevicesApi.getSubscriberDevices(accessPointToQuery.id); const response = await subscriberDevicesApi.getSubscriberDevices(accessPointToQuery.id);
console.log(response.data); console.log(response.data);
if (response && response.data) {
const searchResult = response.data.devices.find(
deviceTemp => deviceTemp.macAddress === clientToQuery.macAddress,
);
if (searchResult) { if (!response || !response.data || !response.data.devices) {
setDevice(searchResult);
} else {
// List changed, so clear the current access point
setDevice(null);
showGeneralError(strings.errors.titleNetwork, strings.errors.noSubscriberDevice);
}
} else {
console.error('Invalid response from getSubsciberDevice'); console.error('Invalid response from getSubsciberDevice');
showGeneralError(strings.errors.titleNetwork, strings.errors.invalidResponse); showGeneralError(strings.errors.titleDeviceDetails, strings.errors.invalidResponse);
return;
}
const foundDevice = response.data.devices.find(deviceTemp => deviceTemp.macAddress === clientToQuery.macAddress);
if (foundDevice) {
setDevice(foundDevice);
} else {
setDevice(null);
showGeneralError(strings.errors.titleDeviceDetails, strings.errors.noSubscriberDevice);
} }
} catch (error) { } catch (error) {
handleApiError(strings.errors.titleNetwork, error); handleApiError(strings.errors.titleDeviceDetails, error);
} finally { } finally {
setDeviceLoading(false); setDeviceLoading(false);
} }
@@ -86,7 +85,7 @@ const DeviceDetails = props => {
const updateDeviceValue = jsonObject => { const updateDeviceValue = jsonObject => {
if (jsonObject) { if (jsonObject) {
// We are looping, but really only expect one // Loop though all key/values, but really only expect one pair in the current flow
for (const [key, value] of Object.entries(jsonObject)) { for (const [key, value] of Object.entries(jsonObject)) {
updateSubsciberDevice(accessPoint, client, key, value); updateSubsciberDevice(accessPoint, client, key, value);
} }
@@ -95,81 +94,113 @@ const DeviceDetails = props => {
const updateSubsciberDevice = async (accessPointToUpdate, clientToUpdate, keyToUpdate, valueToUpdate) => { const updateSubsciberDevice = async (accessPointToUpdate, clientToUpdate, keyToUpdate, valueToUpdate) => {
if (!subscriberDevicesApi) { if (!subscriberDevicesApi) {
// This is expected to be temporary
return; return;
} }
try { try {
if (!accessPointToUpdate || !clientToUpdate || !keyToUpdate || !valueToUpdate) { if (!accessPointToUpdate || !clientToUpdate || !keyToUpdate || !valueToUpdate) {
showGeneralError(strings.errors.titleNetwork, strings.errors.internal); showGeneralError(strings.errors.titleDeviceDetails, strings.errors.internal);
return; return;
} }
// Get the most current subscriber devices, we want to be as current as possible to ensure we have the latest // Get the most current subscriber devices, we want to be as current as possible to ensure we have the latest
// values before updating the new information // values before updating the new information
const responseGet = await subscriberDevicesApi.getSubscriberDevices(accessPointToUpdate.id); const responseGet = await subscriberDevicesApi.getSubscriberDevices(accessPointToUpdate.id);
if (responseGet && responseGet.data) { if (!responseGet || !responseGet.data || !responseGet.data.devices) {
let subscriberDevices = responseGet.data.devices; console.error('Invalid response from getSubsciberDevice');
let deviceToUpdate = subscriberDevices.find(deviceTemp => deviceTemp.macAddress === clientToUpdate.macAddress); showGeneralError(strings.errors.titleDeviceDetails, strings.errors.invalidResponse);
return;
// Update the value
deviceToUpdate[keyToUpdate] = valueToUpdate;
const responseModify = await subscriberDevicesApi.get.modifySubscriberDevices(
accessPointToUpdate.id,
false,
subscriberDevices,
);
console.log(responseModify.data);
if (responseModify && responseModify.data) {
// Updated - no need to do anyting
} else {
console.error('Invalid response from modifySubscriberDevices');
showGeneralError(strings.errors.titleNetwork, strings.errors.invalidResponse);
}
} else {
console.error('Invalid response from getSubscriberDevices');
showGeneralError(strings.errors.titleNetwork, strings.errors.invalidResponse);
} }
let subscriberDevices = responseGet.data.devices;
let deviceToUpdate = subscriberDevices.find(deviceTemp => deviceTemp.macAddress === clientToUpdate.macAddress);
// Update the value for the key (which will update the list as well)
deviceToUpdate[keyToUpdate] = valueToUpdate;
const responseModify = await subscriberDevicesApi.get.modifySubscriberDevices(
accessPointToUpdate.id,
false,
subscriberDevices,
);
console.log(responseModify.data);
if (!responseModify || !responseModify.data) {
console.error('Invalid response from modifySubscriberDevices');
showGeneralError(strings.errors.titleDeviceDetails, strings.errors.invalidResponse);
}
// TODO: Verify the response code once API has been implemented
// Do nothing if everything work as expected
} catch (error) { } catch (error) {
handleApiError(strings.errors.titleNetwork, error); handleApiError(strings.errors.titleDeviceDetails, error);
} }
}; };
const getClientIcon = () => { const getDeviceIcon = () => {
// TODO: Handle proper icon based on device type
return require('../assets/laptop-solid.png'); return require('../assets/laptop-solid.png');
}; };
const getClientBadgeIcon = () => { const getDeviceBadgeIcon = () => {
return require('../assets/wifi-solid.png'); if ('ssid' in client) {
return require('../assets/wifi-solid.png');
} else {
return require('../assets/network-wired-solid.png');
}
}; };
const getClientStatusColor = () => { const getDeviceBadgeStatusColor = () => {
// Random choice for the moment, until the actual device parsing is implemented if ('ssid' in client) {
let choice = Math.floor(Math.random() * 10) % 3; const rssi = client.rssi;
switch (choice) { // Handle WIFI status
case 2: // TODO: Verify this
if (rssi <= -80) {
return errorColor; return errorColor;
} else if (rssi > -80 && rssi <= -50) {
case 1:
return warnColor; return warnColor;
} else if (rssi > -50 && rssi < 0) {
default:
case 0:
return okColor; return okColor;
} else {
return errorColor;
}
} else {
// Wired client
// TODO: Check to see if there is more to look at here
return okColor;
} }
}; };
const getClientName = () => { const getClientName = () => {
return client ? client.name : strings.messages.empty; displayValue(client, 'name');
}; };
const onPausePress = async () => { const onPauseUnpauseButtonLabel = () => {
// Handle pause if (device && !device.suspended) {
return strings.buttons.unpause;
}
return strings.buttons.pause;
};
const onPauseUnpausePress = async () => {
if (device) {
// Swap the current suspend state
updateDeviceValue({ suspended: !device.suspended });
} else {
showGeneralError(strings.errors.titleDeviceDetails, strings.errors.invalidResponse);
}
}; };
const getConnectionType = () => { const getConnectionType = () => {
return device ? device.name : strings.messages.empty; // TODO: get proper connection type
if ('ssid' in client) {
return strings.formatString(strings.deviceDetails.connectionTypeWifi, displayValue(client, 'ssid'));
} else {
return strings.formatString(strings.deviceDetails.connectionTypeWired, displayValue(client, 'ssid'));
}
}; };
// Styles // Styles
@@ -207,19 +238,19 @@ const DeviceDetails = props => {
<View style={componentStyles.sectionDevice}> <View style={componentStyles.sectionDevice}>
<ImageWithBadge <ImageWithBadge
style={componentStyles.sectionDeviceIcon} style={componentStyles.sectionDeviceIcon}
source={getClientIcon()} source={getDeviceIcon()}
badgeSource={getClientBadgeIcon()} badgeSource={getDeviceBadgeIcon()}
badgeTintColor={whiteColor} badgeTintColor={whiteColor}
badgeBackgroundColor={getClientStatusColor()} badgeBackgroundColor={getDeviceBadgeStatusColor()}
badgeSize="small" badgeSize="large"
/> />
<Text style={componentStyles.sectionDeviceText}>{getClientName()}</Text> <Text style={componentStyles.sectionDeviceText}>{getClientName()}</Text>
<ButtonStyled <ButtonStyled
title={strings.buttons.pause} title={onPauseUnpauseButtonLabel()}
type="outline" type="outline"
onPress={onPausePress} onPress={onPauseUnpausePress}
size="small" size="small"
disabled={!client} disabled={!device}
/> />
</View> </View>
@@ -228,7 +259,11 @@ const DeviceDetails = props => {
title={strings.deviceDetails.connectionDetails} title={strings.deviceDetails.connectionDetails}
disableAccordion={true} disableAccordion={true}
isLoading={deviceLoading}> isLoading={deviceLoading}>
<ItemTextWithLabel key="status" label={strings.deviceDetails.status} value="Connected" /> <ItemTextWithLabel
key="status"
label={strings.deviceDetails.status}
value={strings.deviceDetails.connected}
/>
<ItemTextWithLabel key="type" label={strings.deviceDetails.connectionType} value={getConnectionType()} /> <ItemTextWithLabel key="type" label={strings.deviceDetails.connectionType} value={getConnectionType()} />
</AccordionSection> </AccordionSection>
@@ -237,44 +272,36 @@ const DeviceDetails = props => {
title={strings.deviceDetails.deviceDetails} title={strings.deviceDetails.deviceDetails}
disableAccordion={true} disableAccordion={true}
isLoading={deviceLoading}> isLoading={deviceLoading}>
<ItemTextWithLabel <ItemTextWithLabel key="name" label={strings.deviceDetails.name} value={displayValue(device, 'name')} />
key="name"
label={strings.deviceDetails.name}
value={device ? device.name : strings.messages.empty}
/>
<ItemTextWithLabelEditable <ItemTextWithLabelEditable
key="group" key="group"
label={strings.deviceDetails.group} label={strings.deviceDetails.group}
value={device ? device.group : strings.messages.empty} value={displayValue(device, 'group')}
editKey="group" editKey="group"
onEdit={updateDeviceValue} onEdit={updateDeviceValue}
/> />
<ItemTextWithLabelEditable <ItemTextWithLabelEditable
key="description" key="description"
label={strings.deviceDetails.description} label={strings.deviceDetails.description}
value={device ? device.description : strings.messages.empty} value={displayValue(device, 'description')}
editKey="description" editKey="description"
onEdit={updateDeviceValue} onEdit={updateDeviceValue}
/> />
<ItemTextWithLabel <ItemTextWithLabel key="type" label={strings.deviceDetails.type} value={displayValue(device, 'type')} />
key="type"
label={strings.deviceDetails.type}
value={device ? device.type : strings.messages.empty}
/>
<ItemTextWithLabel <ItemTextWithLabel
key="manufacturer" key="manufacturer"
label={strings.deviceDetails.manufacturer} label={strings.deviceDetails.manufacturer}
value={device ? device.manufacturer : strings.messages.empty} value={displayValue(device, 'manufacturer')}
/> />
<ItemTextWithLabel <ItemTextWithLabel
key="ipAddress" key="ipAddress"
label={strings.deviceDetails.ipAddress} label={strings.deviceDetails.ipAddress}
value={device ? device.ip : strings.messages.empty} value={displayValue(device, 'ip')}
/> />
<ItemTextWithLabel <ItemTextWithLabel
key="macAddress" key="macAddress"
label={strings.deviceDetails.macAddress} label={strings.deviceDetails.macAddress}
value={device ? device.macAddress : strings.messages.empty} value={displayValue(device, 'macAddress')}
/> />
</AccordionSection> </AccordionSection>
</View> </View>