Fork of the excellent esp8266-react - https://github.com/rjwats/esp8266-react
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
8.3 KiB

  1. import React, { Component, Fragment } from 'react';
  2. import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions, Box } from '@material-ui/core';
  3. import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core';
  4. import DevicesIcon from '@material-ui/icons/Devices';
  5. import MemoryIcon from '@material-ui/icons/Memory';
  6. import ShowChartIcon from '@material-ui/icons/ShowChart';
  7. import SdStorageIcon from '@material-ui/icons/SdStorage';
  8. import FolderIcon from '@material-ui/icons/Folder';
  9. import DataUsageIcon from '@material-ui/icons/DataUsage';
  10. import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
  11. import RefreshIcon from '@material-ui/icons/Refresh';
  12. import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
  13. import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication';
  14. import { RestFormProps, FormButton, ErrorButton } from '../components';
  15. import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api';
  16. import { SystemStatus } from './types';
  17. interface SystemStatusFormState {
  18. confirmRestart: boolean;
  19. confirmFactoryReset: boolean;
  20. processing: boolean;
  21. }
  22. type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps<SystemStatus>;
  23. function formatNumber(num: number) {
  24. return new Intl.NumberFormat().format(num);
  25. }
  26. class SystemStatusForm extends Component<SystemStatusFormProps, SystemStatusFormState> {
  27. state: SystemStatusFormState = {
  28. confirmRestart: false,
  29. confirmFactoryReset: false,
  30. processing: false
  31. }
  32. approxHeapFragmentation = (): number => {
  33. const { data: { max_alloc_heap, free_heap } } = this.props;
  34. return 100 - Math.round((max_alloc_heap / free_heap) * 100);
  35. }
  36. createListItems() {
  37. const { data } = this.props
  38. return (
  39. <Fragment>
  40. <ListItem >
  41. <ListItemAvatar>
  42. <Avatar>
  43. <DevicesIcon />
  44. </Avatar>
  45. </ListItemAvatar>
  46. <ListItemText primary="Device (Platform / SDK)" secondary={data.esp_platform + ' / ' + data.sdk_version} />
  47. </ListItem>
  48. <Divider variant="inset" component="li" />
  49. <ListItem >
  50. <ListItemAvatar>
  51. <Avatar>
  52. <ShowChartIcon />
  53. </Avatar>
  54. </ListItemAvatar>
  55. <ListItemText primary="CPU Frequency" secondary={data.cpu_freq_mhz + ' MHz'} />
  56. </ListItem>
  57. <Divider variant="inset" component="li" />
  58. <ListItem >
  59. <ListItemAvatar>
  60. <Avatar>
  61. <MemoryIcon />
  62. </Avatar>
  63. </ListItemAvatar>
  64. <ListItemText primary="Heap (Free / Max Alloc)" secondary={formatNumber(data.free_heap) + ' / ' + formatNumber(data.max_alloc_heap) + ' bytes (~' + this.approxHeapFragmentation() + '%\xa0fragmentation)'} />
  65. </ListItem>
  66. <Divider variant="inset" component="li" />
  67. <ListItem >
  68. <ListItemAvatar>
  69. <Avatar>
  70. <DataUsageIcon />
  71. </Avatar>
  72. </ListItemAvatar>
  73. <ListItemText primary="Sketch (Size / Free)" secondary={formatNumber(data.sketch_size) + ' / ' + formatNumber(data.free_sketch_space) + ' bytes'} />
  74. </ListItem>
  75. <Divider variant="inset" component="li" />
  76. <ListItem >
  77. <ListItemAvatar>
  78. <Avatar>
  79. <SdStorageIcon />
  80. </Avatar>
  81. </ListItemAvatar>
  82. <ListItemText primary="Flash Chip (Size / Speed)" secondary={formatNumber(data.flash_chip_size) + ' bytes / ' + (data.flash_chip_speed / 1000000).toFixed(0) + ' MHz'} />
  83. </ListItem>
  84. <Divider variant="inset" component="li" />
  85. <ListItem >
  86. <ListItemAvatar>
  87. <Avatar>
  88. <FolderIcon />
  89. </Avatar>
  90. </ListItemAvatar>
  91. <ListItemText primary="File System (Used / Total)" secondary={formatNumber(data.fs_used) + ' / ' + formatNumber(data.fs_total) + ' bytes (' + formatNumber(data.fs_total - data.fs_used) + '\xa0bytes free)'} />
  92. </ListItem>
  93. <Divider variant="inset" component="li" />
  94. </Fragment>
  95. );
  96. }
  97. renderRestartDialog() {
  98. return (
  99. <Dialog
  100. open={this.state.confirmRestart}
  101. onClose={this.onRestartRejected}
  102. >
  103. <DialogTitle>Confirm Restart</DialogTitle>
  104. <DialogContent dividers>
  105. Are you sure you want to restart the device?
  106. </DialogContent>
  107. <DialogActions>
  108. <Button startIcon={<PowerSettingsNewIcon />} variant="contained" onClick={this.onRestartConfirmed} disabled={this.state.processing} color="primary" autoFocus>
  109. Restart
  110. </Button>
  111. <Button variant="contained" onClick={this.onRestartRejected} color="secondary">
  112. Cancel
  113. </Button>
  114. </DialogActions>
  115. </Dialog>
  116. )
  117. }
  118. onRestart = () => {
  119. this.setState({ confirmRestart: true });
  120. }
  121. onRestartRejected = () => {
  122. this.setState({ confirmRestart: false });
  123. }
  124. onRestartConfirmed = () => {
  125. this.setState({ processing: true });
  126. redirectingAuthorizedFetch(RESTART_ENDPOINT, { method: 'POST' })
  127. .then(response => {
  128. if (response.status === 200) {
  129. this.props.enqueueSnackbar("Device is restarting", { variant: 'info' });
  130. this.setState({ processing: false, confirmRestart: false });
  131. } else {
  132. throw Error("Invalid status code: " + response.status);
  133. }
  134. })
  135. .catch(error => {
  136. this.props.enqueueSnackbar(error.message || "Problem restarting device", { variant: 'error' });
  137. this.setState({ processing: false, confirmRestart: false });
  138. });
  139. }
  140. renderFactoryResetDialog() {
  141. return (
  142. <Dialog
  143. open={this.state.confirmFactoryReset}
  144. onClose={this.onFactoryResetRejected}
  145. >
  146. <DialogTitle>Confirm Factory Reset</DialogTitle>
  147. <DialogContent dividers>
  148. Are you sure you want to reset the device to its factory defaults?
  149. </DialogContent>
  150. <DialogActions>
  151. <ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus>
  152. Factory Reset
  153. </ErrorButton>
  154. <Button variant="contained" onClick={this.onFactoryResetRejected} color="secondary">
  155. Cancel
  156. </Button>
  157. </DialogActions>
  158. </Dialog>
  159. )
  160. }
  161. onFactoryReset = () => {
  162. this.setState({ confirmFactoryReset: true });
  163. }
  164. onFactoryResetRejected = () => {
  165. this.setState({ confirmFactoryReset: false });
  166. }
  167. onFactoryResetConfirmed = () => {
  168. this.setState({ processing: true });
  169. redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' })
  170. .then(response => {
  171. if (response.status === 200) {
  172. this.props.enqueueSnackbar("Factory reset in progress.", { variant: 'error' });
  173. this.setState({ processing: false, confirmFactoryReset: false });
  174. } else {
  175. throw Error("Invalid status code: " + response.status);
  176. }
  177. })
  178. .catch(error => {
  179. this.props.enqueueSnackbar(error.message || "Problem factory resetting device", { variant: 'error' });
  180. this.setState({ processing: false, confirmRestart: false });
  181. });
  182. }
  183. render() {
  184. const me = this.props.authenticatedContext.me;
  185. return (
  186. <Fragment>
  187. <List>
  188. {this.createListItems()}
  189. </List>
  190. <Box display="flex" flexWrap="wrap">
  191. <Box flexGrow={1} padding={1}>
  192. <FormButton startIcon={<RefreshIcon />} variant="contained" color="secondary" onClick={this.props.loadData}>
  193. Refresh
  194. </FormButton>
  195. </Box>
  196. {me.admin &&
  197. <Box flexWrap="none" padding={1} whiteSpace="nowrap">
  198. <FormButton startIcon={<PowerSettingsNewIcon />} variant="contained" color="primary" onClick={this.onRestart}>
  199. Restart
  200. </FormButton>
  201. <ErrorButton startIcon={<SettingsBackupRestoreIcon />} variant="contained" onClick={this.onFactoryReset}>
  202. Factory reset
  203. </ErrorButton>
  204. </Box>
  205. }
  206. </Box>
  207. {this.renderRestartDialog()}
  208. {this.renderFactoryResetDialog()}
  209. </Fragment>
  210. );
  211. }
  212. }
  213. export default withAuthenticatedContext(SystemStatusForm);