diff --git a/src/DeviceInterfaceValues.tsx b/src/DeviceInterfaceValues.tsx
index aaa583f6..1b9786cd 100644
--- a/src/DeviceInterfaceValues.tsx
+++ b/src/DeviceInterfaceValues.tsx
@@ -19,6 +19,7 @@
import React, { ChangeEvent, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Button, Card, Col, Container, Form, Modal, Row, Spinner, Table } from 'react-bootstrap';
+import DatePicker from 'react-datepicker';
import {
AstarteDataTuple,
AstarteDataTreeNode,
@@ -35,12 +36,10 @@ import _ from 'lodash';
import BackButton from './ui/BackButton';
import Empty from './components/Empty';
import WaitForData from './components/WaitForData';
-import useFetch from './hooks/useFetch';
import { useAstarte } from './AstarteManager';
import { AlertsBanner, useAlerts } from 'AlertManager';
import * as yup from 'yup';
import { getValidationSchema } from 'astarte-client/models/InterfaceValue';
-import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
const MAX_SHOWN_VALUES = 20;
@@ -120,14 +119,16 @@ const ObjectDatastreamTable = ({
if (treeData.length === 0) {
return
No data sent by the device.
;
}
-
const objectProperties = Object.keys(treeData[0].value);
const orderedData = _.orderBy(treeData, 'timestamp', 'desc');
-
return (
<>
- Path
- {dataTreeNode.endpoint}
+
+
+ Path
+ {dataTreeNode.endpoint}
+
+
@@ -517,31 +518,143 @@ const SendInterfaceDataModal = ({
);
};
+interface TimeRangeModalProps {
+ showTimeModal: boolean;
+ handleShowTimeModal: () => void;
+ requestingData: boolean;
+ fetchInterfaceTimeData: (since?: string, to?: string) => void;
+}
+
+const TimeRangeModal = ({
+ showTimeModal,
+ requestingData,
+ handleShowTimeModal,
+ fetchInterfaceTimeData,
+}: TimeRangeModalProps) => {
+ const [fromTime, setFromTime] = useState(null);
+ const [toTime, setToTime] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const handleFetchData = async () => {
+ setLoading(true);
+ try {
+ const from = fromTime ? fromTime.toISOString() : new Date(0).toISOString();
+ const to = toTime ? toTime.toISOString() : new Date().toISOString();
+ fetchInterfaceTimeData(from, to);
+ handleShowTimeModal();
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ handleFetchData();
+ };
+
+ return (
+
+
+
+ );
+};
+
export default (): React.ReactElement => {
const { deviceId = '', interfaceName = '' } = useParams();
const astarte = useAstarte();
- const deviceDataFetcher = useFetch(() =>
- astarte.client.getDeviceDataTree({
- deviceId,
- interfaceName,
- }),
- );
const [showModal, setShowModal] = useState(false);
+ const [showTimeModal, setShowTimeModal] = useState(false);
const [sendingData, setSendingData] = useState(false);
+ const [requestingData, setRequestingData] = useState(false);
+ const [filteredData, setFilteredData] =
+ useState>();
const [formAlerts, formAlertsController] = useAlerts();
- const iface = deviceDataFetcher.value?.interface as AstarteInterface;
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const iface = data?.interface as AstarteInterface;
const handleShowModal = () => {
setShowModal(!showModal);
};
+ const handleShowTimeModal = () => {
+ setShowTimeModal(!showTimeModal);
+ };
+
+ const fetchInterfaceData = async (since?: string, to?: string) => {
+ setRequestingData(true);
+ astarte.client
+ .getDeviceDataTree({
+ deviceId,
+ interfaceName,
+ since,
+ to,
+ })
+ .then((data) => {
+ setFilteredData(data as AstarteDataTreeNode);
+ handleShowTimeModal();
+ })
+ .catch((err) => {
+ formAlertsController.showError(
+ `Could not fetch data to interface: ${err.response.data.errors.detail}`,
+ );
+ handleShowModal();
+ })
+ .finally(() => {
+ setRequestingData(false);
+ });
+ };
+
const sendInterfaceData = (data: { endpoint: string; value: AstarteDataValue }) => {
setSendingData(true);
astarte.client
.sendDataToInterface({ deviceId, interfaceName, path: data.endpoint, data: data.value })
.then(() => {
handleShowModal();
- deviceDataFetcher.refresh();
})
.catch((err) => {
formAlertsController.showError(
@@ -554,6 +667,23 @@ export default (): React.ReactElement => {
});
};
+ useEffect(() => {
+ const fetchDeviceData = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const result = await astarte.client.getDeviceDataTree({ deviceId, interfaceName });
+ setData(result);
+ } catch (err) {
+ setError(err as Error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchDeviceData();
+ }, [deviceId, interfaceName]);
+
return (
@@ -566,11 +696,16 @@ export default (): React.ReactElement => {
'POST',
`devices/${deviceId}/interfaces/${interfaceName}`,
) &&
- iface?.ownership === 'server' && (
+ data?.interface.ownership === 'server' && (
)}
+ {data?.interface.type !== 'properties' && (
+
+ )}
@@ -579,18 +714,27 @@ export default (): React.ReactElement => {
}
errorFallback={
-
+ fetchInterfaceData(deviceId, interfaceName)}
+ />
}
>
- {(data) => }
+ {(data) =>
+ filteredData ? (
+
+ ) : (
+
+ )
+ }
@@ -603,6 +747,14 @@ export default (): React.ReactElement => {
sendInterfaceData={sendInterfaceData}
/>
)}
+ {showTimeModal && (
+
+ )}
);
};