diff --git a/ui/src/components/Admin.tsx b/ui/src/components/Admin.tsx index 1da05d55..d1727032 100644 --- a/ui/src/components/Admin.tsx +++ b/ui/src/components/Admin.tsx @@ -12,29 +12,29 @@ interface IProps { function Admin(props: PropsWithChildren) { const [admin, setAdmin] = useState(false); - const setIsAdmin = () => { - if (!props.isDeviceAdmin && !props.isGatewayAdmin && !props.isTenantAdmin) { - setAdmin(SessionStore.isAdmin()); - } else { - if (props.tenantId === undefined) { - throw new Error("No tenantId is given"); - } - - if (props.isTenantAdmin) { - setAdmin(SessionStore.isAdmin() || SessionStore.isTenantAdmin(props.tenantId)); - } - - if (props.isDeviceAdmin) { - setAdmin(SessionStore.isAdmin() || SessionStore.isTenantDeviceAdmin(props.tenantId)); - } - - if (props.isGatewayAdmin) { - setAdmin(SessionStore.isAdmin() || SessionStore.isTenantGatewayAdmin(props.tenantId)); - } - } - }; - useEffect(() => { + const setIsAdmin = () => { + if (!props.isDeviceAdmin && !props.isGatewayAdmin && !props.isTenantAdmin) { + setAdmin(SessionStore.isAdmin()); + } else { + if (props.tenantId === undefined) { + throw new Error("No tenantId is given"); + } + + if (props.isTenantAdmin) { + setAdmin(SessionStore.isAdmin() || SessionStore.isTenantAdmin(props.tenantId)); + } + + if (props.isDeviceAdmin) { + setAdmin(SessionStore.isAdmin() || SessionStore.isTenantDeviceAdmin(props.tenantId)); + } + + if (props.isGatewayAdmin) { + setAdmin(SessionStore.isAdmin() || SessionStore.isTenantGatewayAdmin(props.tenantId)); + } + } + }; + SessionStore.on("change", setIsAdmin); setIsAdmin(); diff --git a/ui/src/components/DataTable.tsx b/ui/src/components/DataTable.tsx index 4848f419..d4f439ec 100644 --- a/ui/src/components/DataTable.tsx +++ b/ui/src/components/DataTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Table } from "antd"; import { ColumnsType } from "antd/es/table"; @@ -23,21 +23,24 @@ function DataTable(props: IProps) { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); - const onChangePage = (page: number, pz?: number | void) => { - setLoading(true); + const onChangePage = useCallback( + (page: number, pz?: number | void) => { + setLoading(true); - if (!pz) { - pz = pageSize; - } + if (!pz) { + pz = pageSize; + } - props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => { - setCurrentPage(page); - setTotalCount(totalCount); - setRows(rows); - setPageSize(pz || 0); - setLoading(false); - }); - }; + props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => { + setCurrentPage(page); + setTotalCount(totalCount); + setRows(rows); + setPageSize(pz || 0); + setLoading(false); + }); + }, + [props, pageSize], + ); const onShowSizeChange = (page: number, pageSize: number) => { onChangePage(page, pageSize); @@ -53,7 +56,7 @@ function DataTable(props: IProps) { useEffect(() => { onChangePage(currentPage, pageSize); - }, [props, currentPage, pageSize]); + }, [props, currentPage, pageSize, onChangePage]); const { getPage, refreshKey, ...otherProps } = props; let loadingProps = undefined; diff --git a/ui/src/components/Menu.tsx b/ui/src/components/Menu.tsx index 52e77b1c..9d4fcf17 100644 --- a/ui/src/components/Menu.tsx +++ b/ui/src/components/Menu.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { Menu, MenuProps, Typography } from "antd"; @@ -67,7 +67,7 @@ function SideMenu() { navigate(`/tenants/${value}`); }; - const parseLocation = () => { + const parseLocation = useCallback(() => { const path = location.pathname; const tenantRe = /\/tenants\/([\w-]{36})/g; const match = tenantRe.exec(path); @@ -134,7 +134,7 @@ function SideMenu() { if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) { setSelectedKey("tenant-applications"); } - }; + }, [location.pathname, tenantId]); useEffect(() => { SessionStore.on("tenant.change", setTenant); @@ -150,11 +150,11 @@ function SideMenu() { return () => { SessionStore.removeListener("tenant.change", setTenant); }; - }, []); + }, [parseLocation]); useEffect(() => { parseLocation(); - }, [location]); + }, [location, parseLocation]); let items: MenuProps["items"] = []; diff --git a/ui/src/views/applications/ApplicationLoader.tsx b/ui/src/views/applications/ApplicationLoader.tsx index ee9b7d99..7b55cea6 100644 --- a/ui/src/views/applications/ApplicationLoader.tsx +++ b/ui/src/views/applications/ApplicationLoader.tsx @@ -26,6 +26,16 @@ function ApplicationLoader(props: IProps) { const [measurementKeys, setMeasurementKeys] = useState([]); useEffect(() => { + const loadApplication = () => { + let req = new GetApplicationRequest(); + req.setId(applicationId!); + + ApplicationStore.get(req, (resp: GetApplicationResponse) => { + setApplication(resp.getApplication()); + setMeasurementKeys(resp.getMeasurementKeysList()); + }); + }; + ApplicationStore.on("change", loadApplication); loadApplication(); @@ -34,16 +44,6 @@ function ApplicationLoader(props: IProps) { }; }, [applicationId]); - const loadApplication = () => { - let req = new GetApplicationRequest(); - req.setId(applicationId!); - - ApplicationStore.get(req, (resp: GetApplicationResponse) => { - setApplication(resp.getApplication()); - setMeasurementKeys(resp.getMeasurementKeysList()); - }); - }; - const app = application; if (!app) { return null; diff --git a/ui/src/views/applications/ListIntegrations.tsx b/ui/src/views/applications/ListIntegrations.tsx index 51fcfad0..d04703d4 100644 --- a/ui/src/views/applications/ListIntegrations.tsx +++ b/ui/src/views/applications/ListIntegrations.tsx @@ -32,111 +32,111 @@ function ListIntegrations(props: IProps) { const [available, setAvailable] = useState([]); useEffect(() => { + const loadIntegrations = () => { + let req = new ListIntegrationsRequest(); + req.setApplicationId(props.application.getId()); + + ApplicationStore.listIntegrations(req, (resp: ListIntegrationsResponse) => { + let configured: any[] = []; + let available: any[] = []; + + const includes = (integrations: IntegrationListItem[], kind: IntegrationKind) => { + for (let x of integrations) { + if (x.getKind() === kind) { + return true; + } + } + + return false; + }; + + // AWS SNS + if (includes(resp.getResultList(), IntegrationKind.AWS_SNS)) { + configured.push(); + } else { + available.push(); + } + + // Azure Service-Bus + if (includes(resp.getResultList(), IntegrationKind.AZURE_SERVICE_BUS)) { + configured.push(); + } else { + available.push(); + } + + // GCP Pub/Sub + if (includes(resp.getResultList(), IntegrationKind.GCP_PUB_SUB)) { + configured.push(); + } else { + available.push(); + } + + // HTTP + if (includes(resp.getResultList(), IntegrationKind.HTTP)) { + configured.push(); + } else { + available.push(); + } + + // IFTTT + if (includes(resp.getResultList(), IntegrationKind.IFTTT)) { + configured.push(); + } else { + available.push(); + } + + // InfluxDB + if (includes(resp.getResultList(), IntegrationKind.INFLUX_DB)) { + configured.push(); + } else { + available.push(); + } + + // MQTT + if (includes(resp.getResultList(), IntegrationKind.MQTT_GLOBAL)) { + configured.push(); + } + + // myDevices + if (includes(resp.getResultList(), IntegrationKind.MY_DEVICES)) { + configured.push(); + } else { + available.push(); + } + + // Pilot Things + if (includes(resp.getResultList(), IntegrationKind.PILOT_THINGS)) { + configured.push(); + } else { + available.push(); + } + + // Semtech LoRa Cloud + if (includes(resp.getResultList(), IntegrationKind.LORA_CLOUD)) { + configured.push(); + } else { + available.push(); + } + + // ThingsBoard + if (includes(resp.getResultList(), IntegrationKind.THINGS_BOARD)) { + configured.push(); + } else { + available.push(); + } + + setConfigured(configured); + setAvailable(available); + }); + }; + ApplicationStore.on("integration.delete", loadIntegrations); loadIntegrations(); return () => { ApplicationStore.removeAllListeners("integration.delete"); }; - }, []); - - const loadIntegrations = () => { - let req = new ListIntegrationsRequest(); - req.setApplicationId(props.application.getId()); - - ApplicationStore.listIntegrations(req, (resp: ListIntegrationsResponse) => { - let configured: any[] = []; - let available: any[] = []; - - const includes = (integrations: IntegrationListItem[], kind: IntegrationKind) => { - for (let x of integrations) { - if (x.getKind() === kind) { - return true; - } - } - - return false; - }; - - // AWS SNS - if (includes(resp.getResultList(), IntegrationKind.AWS_SNS)) { - configured.push(); - } else { - available.push(); - } - - // Azure Service-Bus - if (includes(resp.getResultList(), IntegrationKind.AZURE_SERVICE_BUS)) { - configured.push(); - } else { - available.push(); - } - - // GCP Pub/Sub - if (includes(resp.getResultList(), IntegrationKind.GCP_PUB_SUB)) { - configured.push(); - } else { - available.push(); - } - - // HTTP - if (includes(resp.getResultList(), IntegrationKind.HTTP)) { - configured.push(); - } else { - available.push(); - } - - // IFTTT - if (includes(resp.getResultList(), IntegrationKind.IFTTT)) { - configured.push(); - } else { - available.push(); - } - - // InfluxDB - if (includes(resp.getResultList(), IntegrationKind.INFLUX_DB)) { - configured.push(); - } else { - available.push(); - } - - // MQTT - if (includes(resp.getResultList(), IntegrationKind.MQTT_GLOBAL)) { - configured.push(); - } - - // myDevices - if (includes(resp.getResultList(), IntegrationKind.MY_DEVICES)) { - configured.push(); - } else { - available.push(); - } - - // Pilot Things - if (includes(resp.getResultList(), IntegrationKind.PILOT_THINGS)) { - configured.push(); - } else { - available.push(); - } - - // Semtech LoRa Cloud - if (includes(resp.getResultList(), IntegrationKind.LORA_CLOUD)) { - configured.push(); - } else { - available.push(); - } - - // ThingsBoard - if (includes(resp.getResultList(), IntegrationKind.THINGS_BOARD)) { - configured.push(); - } else { - available.push(); - } - - setConfigured(configured); - setAvailable(available); - }); - }; + }, [props.application]); return ( diff --git a/ui/src/views/devices/DeviceDashboard.tsx b/ui/src/views/devices/DeviceDashboard.tsx index c4bc0075..74219331 100644 --- a/ui/src/views/devices/DeviceDashboard.tsx +++ b/ui/src/views/devices/DeviceDashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Link } from "react-router-dom"; import moment from "moment"; @@ -33,11 +33,50 @@ function DeviceDashboard(props: IProps) { const [deviceLinkMetrics, setDeviceLinkMetrics] = useState(undefined); const [deviceLinkMetricsLoaded, setDeviceLinkMetricsLoaded] = useState(false); - useEffect(() => { - loadMetrics(); - }, [props, metricsAggregation]); + const loadDeviceMetrics = useCallback( + (start: Date, end: Date, agg: Aggregation) => { + let startPb = new Timestamp(); + let endPb = new Timestamp(); - const loadMetrics = () => { + startPb.fromDate(start); + endPb.fromDate(end); + + let req = new GetDeviceMetricsRequest(); + req.setDevEui(props.device.getDevEui()); + req.setStart(startPb); + req.setEnd(endPb); + req.setAggregation(agg); + + DeviceStore.getMetrics(req, (resp: GetDeviceMetricsResponse) => { + setDeviceMetrics(resp); + }); + }, + [props.device], + ); + + const loadLinkMetrics = useCallback( + (start: Date, end: Date, agg: Aggregation) => { + let startPb = new Timestamp(); + let endPb = new Timestamp(); + + startPb.fromDate(start); + endPb.fromDate(end); + + let req = new GetDeviceLinkMetricsRequest(); + req.setDevEui(props.device.getDevEui()); + req.setStart(startPb); + req.setEnd(endPb); + req.setAggregation(agg); + + DeviceStore.getLinkMetrics(req, (resp: GetDeviceLinkMetricsResponse) => { + setDeviceLinkMetrics(resp); + setDeviceLinkMetricsLoaded(true); + }); + }, + [props.device], + ); + + const loadMetrics = useCallback(() => { const agg = metricsAggregation; const end = moment(); let start = moment(); @@ -53,44 +92,11 @@ function DeviceDashboard(props: IProps) { setDeviceLinkMetricsLoaded(false); loadLinkMetrics(start.toDate(), end.toDate(), agg); loadDeviceMetrics(start.toDate(), end.toDate(), agg); - }; + }, [loadLinkMetrics, loadDeviceMetrics, metricsAggregation]); - const loadDeviceMetrics = (start: Date, end: Date, agg: Aggregation) => { - let startPb = new Timestamp(); - let endPb = new Timestamp(); - - startPb.fromDate(start); - endPb.fromDate(end); - - let req = new GetDeviceMetricsRequest(); - req.setDevEui(props.device.getDevEui()); - req.setStart(startPb); - req.setEnd(endPb); - req.setAggregation(agg); - - DeviceStore.getMetrics(req, (resp: GetDeviceMetricsResponse) => { - setDeviceMetrics(resp); - }); - }; - - const loadLinkMetrics = (start: Date, end: Date, agg: Aggregation) => { - let startPb = new Timestamp(); - let endPb = new Timestamp(); - - startPb.fromDate(start); - endPb.fromDate(end); - - let req = new GetDeviceLinkMetricsRequest(); - req.setDevEui(props.device.getDevEui()); - req.setStart(startPb); - req.setEnd(endPb); - req.setAggregation(agg); - - DeviceStore.getLinkMetrics(req, (resp: GetDeviceLinkMetricsResponse) => { - setDeviceLinkMetrics(resp); - setDeviceLinkMetricsLoaded(true); - }); - }; + useEffect(() => { + loadMetrics(); + }, [props, metricsAggregation, loadMetrics]); const onMetricsAggregationChange = (e: RadioChangeEvent) => { setMetricsAggregation(e.target.value); diff --git a/ui/src/views/devices/DeviceLayout.tsx b/ui/src/views/devices/DeviceLayout.tsx index 0f2a9774..d69575cc 100644 --- a/ui/src/views/devices/DeviceLayout.tsx +++ b/ui/src/views/devices/DeviceLayout.tsx @@ -46,6 +46,25 @@ function DeviceLayout(props: IProps) { const [lastSeenAt, setLastSeenAt] = useState(undefined); useEffect(() => { + const loadDevice = () => { + let req = new GetDeviceRequest(); + req.setDevEui(devEui!); + + DeviceStore.get(req, (resp: GetDeviceResponse) => { + setDevice(resp.getDevice()); + + if (resp.getLastSeenAt() !== undefined) { + setLastSeenAt(resp.getLastSeenAt()!.toDate()); + } + + let req = new GetDeviceProfileRequest(); + req.setId(resp.getDevice()!.getDeviceProfileId()); + DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => { + setDeviceProfile(resp.getDeviceProfile()); + }); + }); + }; + DeviceStore.on("change", loadDevice); loadDevice(); @@ -54,25 +73,6 @@ function DeviceLayout(props: IProps) { }; }, [devEui]); - const loadDevice = () => { - let req = new GetDeviceRequest(); - req.setDevEui(devEui!); - - DeviceStore.get(req, (resp: GetDeviceResponse) => { - setDevice(resp.getDevice()); - - if (resp.getLastSeenAt() !== undefined) { - setLastSeenAt(resp.getLastSeenAt()!.toDate()); - } - - let req = new GetDeviceProfileRequest(); - req.setId(resp.getDevice()!.getDeviceProfileId()); - DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => { - setDeviceProfile(resp.getDeviceProfile()); - }); - }); - }; - const deleteDevice = () => { let req = new DeleteDeviceRequest(); req.setDevEui(devEui!); diff --git a/ui/src/views/devices/DeviceQueue.tsx b/ui/src/views/devices/DeviceQueue.tsx index b70b0172..9dd4348e 100644 --- a/ui/src/views/devices/DeviceQueue.tsx +++ b/ui/src/views/devices/DeviceQueue.tsx @@ -3,7 +3,7 @@ import React, { useState } from "react"; import { Struct } from "google-protobuf/google/protobuf/struct_pb"; import { Switch, notification } from "antd"; -import { Button, Tabs, Space, Card, Row, Form, Input, InputNumber, Checkbox, Popconfirm } from "antd"; +import { Button, Tabs, Space, Card, Row, Form, Input, InputNumber, Popconfirm } from "antd"; import { ColumnsType } from "antd/es/table"; import { RedoOutlined, DeleteOutlined } from "@ant-design/icons"; import { Buffer } from "buffer"; diff --git a/ui/src/views/gateways/GatewayDashboard.tsx b/ui/src/views/gateways/GatewayDashboard.tsx index a2284526..83cc7c01 100644 --- a/ui/src/views/gateways/GatewayDashboard.tsx +++ b/ui/src/views/gateways/GatewayDashboard.tsx @@ -23,7 +23,7 @@ interface IProps { } function GatewayDashboard(props: IProps) { - const [metricsAggregation, setMetricsAggregation] = useState(Aggregation.DAY); + const [metricsAggregation] = useState(Aggregation.DAY); const [gatewayMetrics, setGatewayMetrics] = useState(undefined); useEffect(() => { diff --git a/ui/src/views/gateways/GatewayForm.tsx b/ui/src/views/gateways/GatewayForm.tsx index 37412e68..69ccf4c9 100644 --- a/ui/src/views/gateways/GatewayForm.tsx +++ b/ui/src/views/gateways/GatewayForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { Form, Input, InputNumber, Row, Col, Button, Tabs, Space, Card } from "antd"; import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; @@ -24,6 +24,29 @@ function GatewayForm(props: IProps) { const [lonValue, setLonValue] = useState(0); const [locationPending, setLocationPending] = useState(false); + const setLocationFields = useCallback( + (lat: number, lon: number) => { + form.setFieldsValue({ + location: { + latitude: lat, + longitude: lon, + }, + }); + }, + [form], + ); + + const getCurrentLocation = useCallback(() => { + setLocationPending(true); + + LocationStore.getLocation((loc: [number, number]) => { + setLatValue(loc[0]); + setLonValue(loc[1]); + setLocationPending(false); + setLocationFields(loc[0], loc[1]); + }); + }, [setLocationFields]); + useEffect(() => { if (!props.update) { getCurrentLocation(); @@ -34,18 +57,7 @@ function GatewayForm(props: IProps) { setLonValue(loc.getLongitude()); } } - }, [props]); - - const getCurrentLocation = () => { - setLocationPending(true); - - LocationStore.getLocation((loc: [number, number]) => { - setLatValue(loc[0]); - setLonValue(loc[1]); - setLocationPending(false); - setLocationFields(loc[0], loc[1]); - }); - }; + }, [props, getCurrentLocation]); const onFinish = (values: Gateway.AsObject) => { const v = Object.assign(props.initialValues.toObject(), values); @@ -79,15 +91,6 @@ function GatewayForm(props: IProps) { setLocationFields(loc.lat, loc.lng); }; - const setLocationFields = (lat: number, lon: number) => { - form.setFieldsValue({ - location: { - latitude: lat, - longitude: lon, - }, - }); - }; - const location: [number, number] = [latValue, lonValue]; return (