Device Registrations: managing devices
(The Device Registrations page, showing the Register and Remove Device buttons.)
View the source code for this file.
The code for the Device Registration page contains some similar code to the Dashboard. We do some imports, define a device configuration, and fetch that configuration.
On this page, we start exposing functionality that performs calls to the IoT Center server itself.
DevicesPage
A lot happens in this block, which makes up most of the file. The block includes definitions of functions to add and remove authorizations, and code that creates and populates elements of the UI.
const DevicesPage: FunctionComponent<Props> = ({helpCollapsed}) => {
// Some setup happens here.
// useEffect(() => {...}
const removeAuthorization {...}
const addAuthorization {...}
// Define table columns.
// Return page content.
}
(Source) This code has been condensed.
One piece of data needed is the registration time of each device. This uses a Flux query.
fetchLastEntryTime
const fetchLastEntryTime = async (deviceId: string): Promise<LastEntryTime> => {
const config = await fetchDeviceConfig(deviceId)
const {
// influx_url: url, // use '/influx' proxy to avoid problem with InfluxDB v2 Beta (Docker)
influx_token: token,
influx_org: org,
influx_bucket: bucket,
id,
} = config
const queryApi = new InfluxDB({url: '/influx', token}).getQueryApi(org)
const result = await queryTable(
queryApi,
flux`
import "influxdata/influxdb/v1"
from(bucket: ${bucket})
|> range(start: -1y)
|> filter(fn: (r) => r.clientId == ${id})
|> filter(fn: (r) => r._measurement == "environment")
|> keep(columns: ["_time"])
|> max(column: "_time")
`
)
const lastColumn = result.getColumn('_time')
if (lastColumn !== null) {
const [lastEntry] = lastColumn as number[]
return {lastEntry, deviceId}
}
return {deviceId}
}
Let’s examine the Flux query called into the code above.
It uses the
range()
,
filter()
,
keep()
, and
max()
functions.
import "influxdata/influxdb/v1"
from(bucket: ${bucket})
|> range(start: -1y)
|> filter(fn: (r) => r.clientId == ${id})
|> filter(fn: (r) => r._measurement == "environment")
|> keep(columns: ["_time"])
|> max(column: "_time")
Buttons: register and remove devices
Here we focus on the functionality provided in the browser UI for the IoT Center. The following code registers a new device from the UI button and form.
Register a device
const addAuthorization = async (deviceId: string) => {
try {
setLoading(true)
const response = await fetch(`/api/env/${deviceId}`)
if (response.status >= 300) {
const text = await response.text()
throw new Error(`${response.status} ${text}`)
}
const {newlyRegistered} = await response.json()
setLoading(false)
if (newlyRegistered) {
antdMessage.success(`Device '${deviceId}' was registered`, 2)
} else {
antdMessage.success(`Device '${deviceId}' is already registered`, 2)
}
} catch (e) {
setLoading(false)
setMessage({
title: 'Cannot register device',
description: String(e),
type: 'error',
})
} finally {
setDataStamp(dataStamp + 1)
}
}
The fourth line here /api/env/${deviceId}
calls to an API endpoint we’ll look at in Chapter 6.
Remove Device
Once a device is registered, it appears on the page.
Clicking on the Remove Device button will send an API call via an HTTP DELETE request to /api/devices/<deviceID>
.
The function responsible for this is removeAuthorization
:
const removeAuthorization = async (device: DeviceInfo) => {
try {
setLoading(true)
const response = await fetch(`/api/devices/${device.deviceId}`, {
method: 'DELETE',
})
if (response.status >= 300) {
const text = await response.text()
throw new Error(`${response.status} ${text}`)
}
setLoading(false)
antdMessage.success(`Device ${device.deviceId} was unregistered`, 2)
} catch (e) {
setLoading(false)
setMessage({
title: 'Cannot remove device',
description: String(e),
type: 'error',
})
} finally {
setDataStamp(dataStamp + 1)
}
}
Page code: building the page
Defining the devices table
The table sorts devices by DeviceID and Registration Time.
// define table columns
const columnDefinitions: ColumnsType<DeviceInfo> = [
{
title: 'Device ID',
dataIndex: 'deviceId',
defaultSortOrder: 'ascend',
render: (deviceId: string) => (
<Link to={`/devices/${deviceId}`}>{deviceId}</Link>
),
sorter: (a: DeviceInfo, b: DeviceInfo) =>
a.deviceId > b.deviceId ? 1 : -1,
},
{
title: 'Registration Time',
dataIndex: 'createdAt',
responsive: helpLayout(['lg'], ['xxl']),
},
{
title: 'Last Entry',
dataIndex: 'deviceId',
render: (id: string) => {
const lastEntry = lastEntries.find(({deviceId}) => deviceId === id)
?.lastEntry
if (lastEntry != null && lastEntry !== 0)
return timeFormatter({
timeZone: 'UTC',
format: 'YYYY-MM-DD HH:mm:ss ZZ',
})(lastEntry)
},
responsive: helpLayout(['xl'], []),
},
{
title: '',
key: 'action',
align: 'right',
render: (_: string, device: DeviceInfo) => (
<>
<Tooltip title="Go to device settings" placement="topRight">
<Button
icon={<SettingFilled />}
href={`/devices/${device.deviceId}`}
/>
</Tooltip>
<Tooltip title="Go to device dashboard" placement="topRight">
<Button
icon={<AreaChartOutlined />}
href={`/dashboard/${device.deviceId}`}
/>
</Tooltip>
<Popconfirm
icon={<ExclamationCircleFilled style={{color: 'red'}} />}
title={`Are you sure to remove '${device.deviceId}' ?`}
onConfirm={() => removeAuthorization(device)}
okText="Yes"
okType="danger"
cancelText="No"
>
<Tooltip title="Remove device" placement="topRight" color="red">
<Button type="default" icon={<DeleteFilled />} danger />
</Tooltip>
</Popconfirm>
</>
),
},
]
Returning page content
return (
<PageContent
title="Device Registrations"
spin={loading}
message={message}
titleExtra={
<>
<Tooltip title="Register a new Device">
<Button
type="dashed"
onClick={() => {
let deviceId = ''
Modal.confirm({
title: 'Register Device',
icon: '',
content: (
<Form name="registerDevice" initialValues={{deviceId}}>
<Form.Item
name="deviceId"
rules={[
{required: true, message: 'Please input device ID !'},
]}
>
<Input
placeholder="Device ID"
onChange={(e) => (deviceId = e.target.value)}
/>
</Form.Item>
</Form>
),
onOk: () => {
addAuthorization(deviceId)
},
okText: 'Register',
})
}}
>
Register
</Button>
</Tooltip>
<Tooltip title="Reload Table">
<Button
type="primary"
onClick={() => setDataStamp(dataStamp + 1)}
style={{marginRight: '8px'}}
>
Reload
</Button>
</Tooltip>
</>
}
>
<Table dataSource={data} columns={columnDefinitions}></Table>
</PageContent>
)
}