diff --git a/shell.nix b/shell.nix
index 1db93206..ba92dcf3 100644
--- a/shell.nix
+++ b/shell.nix
@@ -18,4 +18,4 @@ pkgs.mkShell {
BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.llvmPackages.libclang.lib}/lib/clang/${pkgs.llvmPackages.libclang.version}/include";
DOCKER_BUILDKIT = "1";
NIX_STORE = "/nix/store";
-}
\ No newline at end of file
+}
diff --git a/ui/Makefile b/ui/Makefile
index 66211a3e..b0635c7b 100644
--- a/ui/Makefile
+++ b/ui/Makefile
@@ -12,3 +12,6 @@ dependencies:
clean:
rm -rf build
+
+format:
+ ./node_modules/.bin/prettier --write .
diff --git a/ui/README.md b/ui/README.md
deleted file mode 100644
index b58e0af8..00000000
--- a/ui/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `yarn start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
-
-### `yarn test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `yarn build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `yarn eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/ui/package.json b/ui/package.json
index a4f21473..eb05955d 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -3,76 +3,71 @@
"version": "4.4.3",
"private": true,
"dependencies": {
- "@ant-design/colors": "^6.0.0",
+ "@ant-design/colors": "^7.0.0",
+ "@ant-design/pro-layout": "^7.16.3",
"@chirpstack/chirpstack-api-grpc-web": "file:../api/grpc-web",
- "@fortawesome/fontawesome-free": "^6.1.1",
- "@fortawesome/fontawesome-svg-core": "^6.1.1",
- "@fortawesome/free-solid-svg-icons": "^6.1.1",
- "@fortawesome/react-fontawesome": "^0.1.18",
- "@testing-library/jest-dom": "^5.11.4",
- "@testing-library/react": "^11.1.0",
- "@testing-library/user-event": "^12.1.10",
- "@types/jest": "^26.0.15",
- "@types/leaflet": "^1.7.5",
+ "@fortawesome/fontawesome-free": "^6.4.0",
+ "@fortawesome/fontawesome-svg-core": "^6.4.0",
+ "@fortawesome/free-solid-svg-icons": "^6.4.0",
+ "@fortawesome/react-fontawesome": "^0.2.0",
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/user-event": "^13.5.0",
+ "@types/jest": "^27.5.2",
+ "@types/leaflet": "^1.9.3",
"@types/leaflet.awesome-markers": "^2.0.25",
- "@types/node": "^12.0.0",
- "@types/react": "^17.0.0",
- "@types/react-dom": "^17.0.0",
- "@types/react-router-dom": "^5.3.3",
- "antd": "^4.20.6",
- "antd-mask-input": "^2.0.7",
+ "@types/node": "^16.18.38",
+ "@types/react": "^18.2.15",
+ "@types/react-dom": "^18.2.7",
+ "antd": "^5.7.1",
"buffer": "^6.0.3",
- "chart.js": "^3.7.1",
- "chartjs-adapter-moment": "^1.0.0",
- "chartjs-chart-matrix": "^1.1.1",
- "codemirror": "^5.65.3",
- "google-protobuf": "^3.21.2",
- "grpc-web": "^1.4.2",
+ "chart.js": "^4.3.0",
+ "chartjs-adapter-moment": "^1.0.1",
+ "chartjs-chart-matrix": "^2.0.1",
+ "codemirror": "5.65.3",
+ "history": "^5.3.0",
"js-file-download": "^0.4.12",
- "leaflet": "^1.7.1",
+ "leaflet": "^1.9.4",
"leaflet.awesome-markers": "^2.0.5",
- "react": "^17.0.2",
- "react-chartjs-2": "^4.1.0",
+ "moment": "^2.29.4",
+ "react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
"react-codemirror2": "^7.2.1",
- "react-dom": "^17.0.2",
- "react-json-tree": "^0.15.1",
- "react-leaflet": "^3.2.1",
- "react-markdown": "^8.0.3",
- "react-router-dom": "^5.3.1",
+ "react-dom": "^18.2.0",
+ "react-json-tree": "0.15.1",
+ "react-leaflet": "^4.2.1",
+ "react-markdown": "^8.0.7",
+ "react-router-dom": "^6.14.2",
"react-scripts": "5.0.1",
- "typescript": "^4.6.4",
+ "typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject",
- "lint": "prettier --check .",
- "format": "prettier --write ."
- },
- "husky": {
- "hooks": {
- "pre-commit": "yarn format"
- }
+ "eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
- ],
- "ignorePatterns": [
- "**/*_pb.js"
]
},
- "browserslist": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "proxy": "http://chirpstack:8080/",
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "proxy": "http://127.0.0.1:8080/",
"devDependencies": {
- "husky": "^7.0.4",
- "prettier": "^2.6.2"
+ "prettier": "^3.0.0"
}
}
diff --git a/ui/public/index.html b/ui/public/index.html
index 7ff5767f..5723dc0e 100644
--- a/ui/public/index.html
+++ b/ui/public/index.html
@@ -1,4 +1,4 @@
-
+
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 4563fdcf..e4cad445 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,18 +1,22 @@
-import React, { Component } from "react";
-import { Router, Route, Switch } from "react-router-dom";
+import React, { useState } from "react";
+import { Router, Routes, Route } from "react-router-dom";
import { Layout } from "antd";
import { User } from "@chirpstack/chirpstack-api-grpc-web/api/user_pb";
-import Menu from "./components/Menu";
import Header from "./components/Header";
+import Menu from "./components/Menu";
// dashboard
import Dashboard from "./views/dashboard/Dashboard";
// users
import Login from "./views/users/Login";
+import ListUsers from "./views/users/ListUsers";
+import CreateUser from "./views/users/CreateUser";
+import EditUser from "./views/users/EditUser";
+import ChangeUserPassword from "./views/users/ChangeUserPassword";
// tenants
import TenantRedirect from "./views/tenants/TenantRedirect";
@@ -20,20 +24,14 @@ import ListTenants from "./views/tenants/ListTenants";
import CreateTenant from "./views/tenants/CreateTenant";
import TenantLoader from "./views/tenants/TenantLoader";
-// users
-import ListUsers from "./views/users/ListUsers";
-import CreateUser from "./views/users/CreateUser";
-import EditUser from "./views/users/EditUser";
-import ChangeUserPassword from "./views/users/ChangeUserPassword";
-
// api keys
import ListAdminApiKeys from "./views/api-keys/ListAdminApiKeys";
import CreateAdminApiKey from "./views/api-keys/CreateAdminApiKey";
// device-profile templates
import ListDeviceProfileTemplates from "./views/device-profile-templates/ListDeviceProfileTemplates";
-import EditDeviceProfileTemplate from "./views/device-profile-templates/EditDeviceProfileTemplate";
import CreateDeviceProfileTemplate from "./views/device-profile-templates/CreateDeviceProfileTemplate";
+import EditDeviceProfileTemplate from "./views/device-profile-templates/EditDeviceProfileTemplate";
// regions
import ListRegions from "./views/regions/ListRegions";
@@ -44,85 +42,72 @@ import SessionStore from "./stores/SessionStore";
import history from "./history";
-interface IProps {}
+const CustomRouter = ({ history, ...props }: any) => {
+ const [state, setState] = useState({
+ action: history.action,
+ location: history.location,
+ });
-interface IState {
- user?: User;
-}
+ React.useLayoutEffect(() => history.listen(setState), [history]);
-class App extends Component {
- constructor(props: IProps) {
- super(props);
+ return ;
+};
- this.state = {
- user: undefined,
- };
- }
+function App() {
+ const [user, setUser] = useState(SessionStore.getUser());
+ SessionStore.on("change", () => {
+ setUser(SessionStore.getUser());
+ });
- componentDidMount() {
- SessionStore.on("change", () => {
- this.setState({
- user: SessionStore.getUser(),
- });
- });
+ return (
+
+
+
+ } />
+ } />
+
- this.setState({
- user: SessionStore.getUser(),
- });
- }
+ {user && (
+
+
+
+
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
- render() {
- return (
-
-
-
-
-
- {this.state.user && (
-
-
-
-
-
-
-
-
-
-
-
+ } />
+ } />
+ } />
+ } />
-
-
-
+ } />
+ } />
-
-
-
-
+ } />
+ } />
+ }
+ />
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
- );
- }
+ } />
+ } />
+
+
+
+
+ )}
+
+
+ );
}
export default App;
diff --git a/ui/src/components/Admin.tsx b/ui/src/components/Admin.tsx
index 511fe034..1da05d55 100644
--- a/ui/src/components/Admin.tsx
+++ b/ui/src/components/Admin.tsx
@@ -1,4 +1,4 @@
-import { Component } from "react";
+import React, { PropsWithChildren, useState, useEffect } from "react";
import SessionStore from "../stores/SessionStore";
@@ -9,73 +9,45 @@ interface IProps {
isTenantAdmin?: boolean;
}
-interface IState {
- admin: boolean;
-}
+function Admin(props: PropsWithChildren) {
+ const [admin, setAdmin] = useState(false);
-class Admin extends Component {
- constructor(props: IProps) {
- super(props);
-
- this.state = {
- admin: false,
- };
- }
-
- componentDidMount() {
- SessionStore.on("change", this.setIsAdmin);
- this.setIsAdmin();
- }
-
- componentWillUnmount() {
- SessionStore.removeListener("change", this.setIsAdmin);
- }
-
- componentDidUpdate(prevProps: IProps) {
- if (prevProps === this.props) {
- return;
- }
-
- this.setIsAdmin();
- }
-
- setIsAdmin = () => {
- if (!this.props.isDeviceAdmin && !this.props.isGatewayAdmin && !this.props.isTenantAdmin) {
- this.setState({
- admin: SessionStore.isAdmin(),
- });
+ const setIsAdmin = () => {
+ if (!props.isDeviceAdmin && !props.isGatewayAdmin && !props.isTenantAdmin) {
+ setAdmin(SessionStore.isAdmin());
} else {
- if (this.props.tenantId === undefined) {
+ if (props.tenantId === undefined) {
throw new Error("No tenantId is given");
}
- if (this.props.isTenantAdmin) {
- this.setState({
- admin: SessionStore.isAdmin() || SessionStore.isTenantAdmin(this.props.tenantId),
- });
+ if (props.isTenantAdmin) {
+ setAdmin(SessionStore.isAdmin() || SessionStore.isTenantAdmin(props.tenantId));
}
- if (this.props.isDeviceAdmin) {
- this.setState({
- admin: SessionStore.isAdmin() || SessionStore.isTenantDeviceAdmin(this.props.tenantId),
- });
+ if (props.isDeviceAdmin) {
+ setAdmin(SessionStore.isAdmin() || SessionStore.isTenantDeviceAdmin(props.tenantId));
}
- if (this.props.isGatewayAdmin) {
- this.setState({
- admin: SessionStore.isAdmin() || SessionStore.isTenantGatewayAdmin(this.props.tenantId),
- });
+ if (props.isGatewayAdmin) {
+ setAdmin(SessionStore.isAdmin() || SessionStore.isTenantGatewayAdmin(props.tenantId));
}
}
};
- render() {
- if (this.state.admin) {
- return this.props.children;
- }
+ useEffect(() => {
+ SessionStore.on("change", setIsAdmin);
+ setIsAdmin();
- return null;
+ return () => {
+ SessionStore.removeListener("change", setIsAdmin);
+ };
+ }, [props]);
+
+ if (admin) {
+ return {props.children}
;
}
+
+ return null;
}
export default Admin;
diff --git a/ui/src/components/AesKeyInput.tsx b/ui/src/components/AesKeyInput.tsx
index 1639a000..e23ab68d 100644
--- a/ui/src/components/AesKeyInput.tsx
+++ b/ui/src/components/AesKeyInput.tsx
@@ -1,11 +1,10 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { notification, Input, Select, Button, Space, Form, Dropdown, Menu } from "antd";
import { ReloadOutlined, CopyOutlined } from "@ant-design/icons";
import { Buffer } from "buffer";
interface IProps {
- formRef: React.RefObject;
label: string;
name: string;
required?: boolean;
@@ -14,42 +13,29 @@ interface IProps {
tooltip?: string;
}
-interface IState {
- byteOrder: string;
- value: string;
-}
+function AesKeyInput(props: IProps) {
+ const form = Form.useFormInstance();
+ const [byteOrder, setByteOrder] = useState("msb");
+ const [value, setValue] = useState("");
-class AesKeyInput extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- byteOrder: "msb",
- value: "",
- };
- }
+ useEffect(() => {
+ if (props.value) {
+ setValue(props.value);
+ }
+ }, [props]);
- updateField = () => {
- let value = this.state.value;
-
- if (this.state.byteOrder === "lsb") {
- const bytes = value.match(/[A-Fa-f0-9]{2}/g) || [];
- value = bytes.reverse().join("");
+ const updateField = (v: string) => {
+ if (byteOrder === "lsb") {
+ const bytes = v.match(/[A-Fa-f0-9]{2}/g) || [];
+ v = bytes.reverse().join("");
}
- this.props.formRef.current.setFieldsValue({
- [this.props.name]: value,
+ form.setFieldsValue({
+ [props.name]: v,
});
};
- componentDidMount() {
- if (this.props.value) {
- this.setState({
- value: this.props.value,
- });
- }
- }
-
- onChange = (e: React.ChangeEvent) => {
+ const onChange = (e: React.ChangeEvent) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
@@ -62,50 +48,37 @@ class AesKeyInput extends Component {
}
}
- this.setState(
- {
- value: value,
- },
- this.updateField,
- );
+ setValue(value);
+ updateField(value);
};
- onByteOrderSelect = (v: string) => {
- if (v === this.state.byteOrder) {
+ const onByteOrderSelect = (v: string) => {
+ if (v === byteOrder) {
return;
}
- this.setState({
- byteOrder: v,
- });
+ setByteOrder(v);
- const current = this.state.value;
+ const current = value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
+ const vv = bytes.reverse().join("");
- this.setState(
- {
- value: bytes.reverse().join(""),
- },
- this.updateField,
- );
+ setValue(vv);
+ updateField(vv);
};
- generateRandom = () => {
+ const generateRandom = () => {
let cryptoObj = window.crypto || window.Crypto;
let b = new Uint8Array(16);
cryptoObj.getRandomValues(b);
let key = Buffer.from(b).toString("hex");
- this.setState(
- {
- value: key,
- },
- this.updateField,
- );
+ setValue(key);
+ updateField(key);
};
- copyToClipboard = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboard = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -126,8 +99,8 @@ class AesKeyInput extends Component {
}
};
- copyToClipboardHexArray = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboardHexArray = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -153,72 +126,70 @@ class AesKeyInput extends Component {
}
};
- render() {
- const copyMenu = (
-
- HEX string
-
- ),
- },
- {
- key: "2",
- label: (
-
- HEX array
-
- ),
- },
- ]}
- />
- );
+ const copyMenu = (
+
+ HEX string
+
+ ),
+ },
+ {
+ key: "2",
+ label: (
+
+ HEX array
+
+ ),
+ },
+ ]}
+ />
+ );
- const addon = (
-
-
- MSB
- LSB
-
-
-
+ const addon = (
+
+
+ MSB
+ LSB
+
+
+
+
+
+
+
-
-
-
-
-
-
- );
+
+
+ );
- return (
-
-
-
-
- );
- }
+ return (
+
+
+
+
+ );
}
export default AesKeyInput;
diff --git a/ui/src/components/Autocomplete.tsx b/ui/src/components/Autocomplete.tsx
index 9bc92efe..91f3c4e8 100644
--- a/ui/src/components/Autocomplete.tsx
+++ b/ui/src/components/Autocomplete.tsx
@@ -1,10 +1,15 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Select } from "antd";
export type OptionsCallbackFunc = (o: { label: string; value: string }[]) => void;
export type OptionCallbackFunc = (o: { label: string; value: string }) => void;
+interface Option {
+ label: string;
+ value: string;
+}
+
interface IProps {
placeholder: string;
className: string;
@@ -14,93 +19,59 @@ interface IProps {
onSelect?: (s: string) => void;
}
-interface IState {
- option?: { label: string; value: string };
- options: { label: string; value: string }[];
-}
+function AutoComplete({ placeholder, className, value, getOption, getOptions, onSelect }: IProps) {
+ const [option, setOption] = useState(undefined);
+ const [options, setOptions] = useState ([]);
-class Autocomplete extends Component {
- constructor(props: IProps) {
- super(props);
-
- this.state = {
- options: [],
- };
- }
-
- componentDidMount() {
- if (this.props.value && this.props.value !== "") {
- this.props.getOption(this.props.value, (o: { label: string; value: string }) => {
- this.setState({
- options: [o],
- });
+ useEffect(() => {
+ if (value && value !== "") {
+ getOption(value, (o: Option) => {
+ setOptions([o]);
});
}
- }
+ }, [value, getOption]);
- componentDidUpdate(prevProps: IProps) {
- if (this.props.value === prevProps.value) {
- return;
- }
-
- if (this.props.value && this.props.value !== "") {
- this.props.getOption(this.props.value, (o: { label: string; value: string }) => {
- this.setState({
- options: [o],
- });
- });
- }
- }
-
- onFocus = () => {
- this.props.getOptions("", options => {
- if (this.state.option !== undefined) {
- const selected = this.state.option.value;
+ const onFocus = () => {
+ getOptions("", options => {
+ if (option !== undefined) {
+ const selected = option.value;
if (options.find(e => e.value === selected) === undefined) {
- options.unshift(this.state.option);
+ options.unshift(option);
}
}
- this.setState({
- options: options,
- });
+ setOptions(options);
});
};
- onSearch = (value: string) => {
- this.props.getOptions(value, options => {
- this.setState({
- options: options,
- });
+ const onSearch = (value: string) => {
+ getOptions(value, options => {
+ setOptions(options);
});
};
- onSelect = (value: string, option: any) => {
- this.setState({
- option: { label: option.label, value: option.value },
- });
+ const onSelectFn = (value: string, option: any) => {
+ setOption({ label: option.label, value: option.value });
- if (this.props.onSelect !== undefined) {
- this.props.onSelect(value);
+ if (onSelect !== undefined) {
+ onSelect(value);
}
};
- render() {
- const { getOption, getOptions, ...otherProps } = this.props;
-
- return (
-
- );
- }
+ return (
+
+ );
}
-export default Autocomplete;
+export default AutoComplete;
diff --git a/ui/src/components/AutocompleteInput.tsx b/ui/src/components/AutocompleteInput.tsx
index a2650ddb..47a9093b 100644
--- a/ui/src/components/AutocompleteInput.tsx
+++ b/ui/src/components/AutocompleteInput.tsx
@@ -1,11 +1,8 @@
-import React, { Component } from "react";
-
import { Form } from "antd";
import Autocomplete, { OptionCallbackFunc, OptionsCallbackFunc } from "./Autocomplete";
interface IProps {
- formRef: React.RefObject;
label: string;
name: string;
required?: boolean;
@@ -14,28 +11,35 @@ interface IProps {
getOptions: (s: string, fn: OptionsCallbackFunc) => void;
}
-class AutocompleteInput extends Component {
- render() {
- return (
-
-
-
- );
- }
+function AutocompleteInput(props: IProps) {
+ const form = Form.useFormInstance();
+
+ const onSelect = (value: string) => {
+ form.setFieldsValue({
+ [props.name]: value,
+ });
+ };
+
+ return (
+
+
+
+ );
}
export default AutocompleteInput;
diff --git a/ui/src/components/CodeEditor.tsx b/ui/src/components/CodeEditor.tsx
index e8b2026f..f43b3a8a 100644
--- a/ui/src/components/CodeEditor.tsx
+++ b/ui/src/components/CodeEditor.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Controlled as CodeMirror } from "react-codemirror2";
import { Form } from "antd";
@@ -6,88 +6,49 @@ import { Form } from "antd";
import "codemirror/mode/javascript/javascript";
interface IProps {
- formRef: React.RefObject;
label?: string;
name: string;
required?: boolean;
- value?: string;
disabled?: boolean;
tooltip?: string;
}
-interface IState {
- value: string;
- reloadKey: number;
-}
+function CodeEditor(props: IProps) {
+ const form = Form.useFormInstance();
+ const [value, setValue] = useState("");
+ const [reloadKey, setReloadKey] = useState(1);
-class CodeEditor extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- value: "",
- reloadKey: 0,
- };
- }
+ useEffect(() => {
+ setValue(form.getFieldValue(props.name));
+ setReloadKey(k => k + 1);
+ }, [form, props]);
- componentDidMount() {
- if (this.props.value) {
- this.setState({
- value: this.props.value,
- });
- }
- }
-
- componentDidUpdate(oldProps: IProps) {
- if (this.props === oldProps) {
- return;
- }
-
- if (this.props.value) {
- this.setState({
- value: this.props.value,
- reloadKey: this.state.reloadKey + 1,
- });
- }
- }
-
- updateField = () => {
- let value = this.state.value;
-
- this.props.formRef.current.setFieldsValue({
- [this.props.name]: value,
+ const handleChange = (editor: any, data: any, newCode: string) => {
+ setValue(newCode);
+ form.setFieldsValue({
+ [props.name]: newCode,
});
};
- handleChange = (editor: any, data: any, newCode: string) => {
- this.setState(
- {
- value: newCode,
- },
- this.updateField,
- );
+ const codeMirrorOptions = {
+ lineNumbers: true,
+ mode: "javascript",
+ theme: "base16-light",
+ readOnly: props.disabled,
};
- render() {
- const codeMirrorOptions = {
- lineNumbers: true,
- mode: "javascript",
- theme: "base16-light",
- readOnly: this.props.disabled,
- };
-
- return (
-
-
-
-
-
- );
- }
+ return (
+
+
+
+
+
+ );
}
export default CodeEditor;
diff --git a/ui/src/components/DataTable.tsx b/ui/src/components/DataTable.tsx
index 39ca6f8d..4848f419 100644
--- a/ui/src/components/DataTable.tsx
+++ b/ui/src/components/DataTable.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Table } from "antd";
import { ColumnsType } from "antd/es/table";
@@ -16,113 +16,81 @@ interface IProps {
noPagination?: boolean;
}
-interface IState {
- totalCount: number;
- pageSize: number;
- currentPage: number;
- rows: object[];
- loading: boolean;
-}
+function DataTable(props: IProps) {
+ const [totalCount, setTotalCount] = useState(0);
+ const [pageSize, setPageSize] = useState(SessionStore.getRowsPerPage());
+ const [currentPage, setCurrentPage] = useState(1);
+ const [rows, setRows] = useState([]);
+ const [loading, setLoading] = useState(true);
-class DataTable extends Component {
- constructor(props: IProps) {
- super(props);
+ const onChangePage = (page: number, pz?: number | void) => {
+ setLoading(true);
- this.state = {
- totalCount: 0,
- pageSize: SessionStore.getRowsPerPage(),
- currentPage: 1,
- rows: [],
- loading: true,
- };
- }
-
- componentDidMount() {
- this.onChangePage(this.state.currentPage, this.state.pageSize);
- }
-
- componentDidUpdate(prevProps: IProps) {
- if (this.props === prevProps) {
- return;
+ if (!pz) {
+ pz = pageSize;
}
- this.onChangePage(this.state.currentPage, this.state.pageSize);
- }
-
- onChangePage = (page: number, pageSize?: number | void) => {
- this.setState(
- {
- loading: true,
- },
- () => {
- let pz = pageSize;
- if (!pz) {
- pz = this.state.pageSize;
- }
-
- this.props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => {
- this.setState({
- currentPage: page,
- totalCount: totalCount,
- rows: rows,
- pageSize: pz || 0,
- loading: false,
- });
- });
- },
- );
+ props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => {
+ setCurrentPage(page);
+ setTotalCount(totalCount);
+ setRows(rows);
+ setPageSize(pz || 0);
+ setLoading(false);
+ });
};
- onShowSizeChange = (page: number, pageSize: number) => {
- this.onChangePage(page, pageSize);
+ const onShowSizeChange = (page: number, pageSize: number) => {
+ onChangePage(page, pageSize);
SessionStore.setRowsPerPage(pageSize);
};
- onRowsSelectChange = (ids: React.Key[]) => {
+ const onRowsSelectChange = (ids: React.Key[]) => {
const idss = ids as string[];
- if (this.props.onRowsSelectChange) {
- this.props.onRowsSelectChange(idss);
+ if (props.onRowsSelectChange) {
+ props.onRowsSelectChange(idss);
}
};
- render() {
- const { getPage, refreshKey, ...otherProps } = this.props;
- let loadingProps = undefined;
- if (this.state.loading) {
- loadingProps = {
- delay: 300,
- };
- }
+ useEffect(() => {
+ onChangePage(currentPage, pageSize);
+ }, [props, currentPage, pageSize]);
- let pagination = undefined;
- if (this.props.noPagination === undefined || this.props.noPagination === false) {
- pagination = {
- current: this.state.currentPage,
- total: this.state.totalCount,
- pageSize: this.state.pageSize,
- onChange: this.onChangePage,
- showSizeChanger: true,
- onShowSizeChange: this.onShowSizeChange,
- };
- }
-
- let rowSelection = undefined;
- if (this.props.onRowsSelectChange) {
- rowSelection = {
- onChange: this.onRowsSelectChange,
- };
- }
-
- return (
-
- );
+ const { getPage, refreshKey, ...otherProps } = props;
+ let loadingProps = undefined;
+ if (loading) {
+ loadingProps = {
+ delay: 300,
+ };
}
+
+ let pagination = undefined;
+ if (props.noPagination === undefined || props.noPagination === false) {
+ pagination = {
+ current: currentPage,
+ total: totalCount,
+ pageSize: pageSize,
+ onChange: onChangePage,
+ showSizeChanger: true,
+ onShowSizeChange: onShowSizeChange,
+ };
+ }
+
+ let rowSelection = undefined;
+ if (props.onRowsSelectChange) {
+ rowSelection = {
+ onChange: onRowsSelectChange,
+ };
+ }
+
+ return (
+
+ );
}
export default DataTable;
diff --git a/ui/src/components/DeleteConfirm.tsx b/ui/src/components/DeleteConfirm.tsx
index 2f425553..b7408c65 100644
--- a/ui/src/components/DeleteConfirm.tsx
+++ b/ui/src/components/DeleteConfirm.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-
+import { useState, PropsWithChildren } from "react";
import { Popover, Button, Typography, Space, Input } from "antd";
interface IProps {
@@ -8,51 +7,32 @@ interface IProps {
onConfirm: () => void;
}
-interface ConfirmState {
- confirm: string;
-}
+function DeleteConfirmContent(props: IProps) {
+ const [confirm, setConfirm] = useState("");
-class DeleteConfirmContent extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- confirm: "",
- };
- }
-
- onChange = (e: React.ChangeEvent) => {
- this.setState({
- confirm: e.target.value,
- });
+ const onChange = (e: React.ChangeEvent) => {
+ setConfirm(e.target.value);
};
- render() {
- return (
-
-
- Enter '{this.props.confirm}' to confirm you want to delete this {this.props.typ}:
-
-
-
- Delete
-
-
- );
- }
+ return (
+
+
+ Enter '{props.confirm}' to confirm you want to delete this {props.typ}:
+
+
+
+ Delete
+
+
+ );
}
-class DeleteConfirm extends Component {
- render() {
- return (
- } trigger="click" placement="left">
- {this.props.children}
-
- );
- }
+function DeleteConfirm(props: PropsWithChildren) {
+ return (
+ } trigger="click" placement="left">
+ {props.children}
+
+ );
}
export default DeleteConfirm;
diff --git a/ui/src/components/DevAddrInput.tsx b/ui/src/components/DevAddrInput.tsx
index 15a4c15d..e72f6795 100644
--- a/ui/src/components/DevAddrInput.tsx
+++ b/ui/src/components/DevAddrInput.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { notification, Input, Select, Button, Space, Form, Dropdown, Menu } from "antd";
import { ReloadOutlined, CopyOutlined } from "@ant-design/icons";
@@ -8,7 +8,6 @@ import { GetRandomDevAddrRequest, GetRandomDevAddrResponse } from "@chirpstack/c
import DeviceStore from "../stores/DeviceStore";
interface IProps {
- formRef: React.RefObject;
label: string;
name: string;
devEui: string;
@@ -17,42 +16,29 @@ interface IProps {
disabled?: boolean;
}
-interface IState {
- byteOrder: string;
- value: string;
-}
+function DevAddrInput(props: IProps) {
+ const form = Form.useFormInstance();
+ const [byteOrder, setByteOrder] = useState("msb");
+ const [value, setValue] = useState("");
-class DevAddrInput extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- byteOrder: "msb",
- value: "",
- };
- }
+ useEffect(() => {
+ if (props.value) {
+ setValue(props.value);
+ }
+ }, [props]);
- updateField = () => {
- let value = this.state.value;
-
- if (this.state.byteOrder === "lsb") {
- const bytes = value.match(/[A-Fa-f0-9]{2}/g) || [];
- value = bytes.reverse().join("");
+ const updateField = (v: string) => {
+ if (byteOrder === "lsb") {
+ const bytes = v.match(/[A-Fa-f0-9]{2}/g) || [];
+ v = bytes.reverse().join("");
}
- this.props.formRef.current.setFieldsValue({
- [this.props.name]: value,
+ form.setFieldsValue({
+ [props.name]: v,
});
};
- componentDidMount() {
- if (this.props.value) {
- this.setState({
- value: this.props.value,
- });
- }
- }
-
- onChange = (e: React.ChangeEvent) => {
+ const onChange = (e: React.ChangeEvent) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
@@ -65,50 +51,37 @@ class DevAddrInput extends Component {
}
}
- this.setState(
- {
- value: value,
- },
- this.updateField,
- );
+ setValue(value);
+ updateField(value);
};
- onByteOrderSelect = (v: string) => {
- if (v === this.state.byteOrder) {
+ const onByteOrderSelect = (v: string) => {
+ if (v === byteOrder) {
return;
}
- this.setState({
- byteOrder: v,
- });
+ setByteOrder(v);
- const current = this.state.value;
+ const current = value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
+ const vv = bytes.reverse().join("");
- this.setState(
- {
- value: bytes.reverse().join(""),
- },
- this.updateField,
- );
+ setValue(vv);
+ updateField(vv);
};
- generateRandom = () => {
+ const generateRandom = () => {
let req = new GetRandomDevAddrRequest();
- req.setDevEui(this.props.devEui);
+ req.setDevEui(props.devEui);
DeviceStore.getRandomDevAddr(req, (resp: GetRandomDevAddrResponse) => {
- this.setState(
- {
- value: resp.getDevAddr(),
- },
- this.updateField,
- );
+ setValue(resp.getDevAddr());
+ updateField(resp.getDevAddr());
});
};
- copyToClipboard = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboard = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -129,8 +102,8 @@ class DevAddrInput extends Component {
}
};
- copyToClipboardHexArray = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboardHexArray = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -156,71 +129,69 @@ class DevAddrInput extends Component {
}
};
- render() {
- const copyMenu = (
-
- HEX string
-
- ),
- },
- {
- key: "2",
- label: (
-
- HEX array
-
- ),
- },
- ]}
- />
- );
+ const copyMenu = (
+
+ HEX string
+
+ ),
+ },
+ {
+ key: "2",
+ label: (
+
+ HEX array
+
+ ),
+ },
+ ]}
+ />
+ );
- const addon = (
-
-
- MSB
- LSB
-
-
-
+ const addon = (
+
+
+ MSB
+ LSB
+
+
+
+
+
+
+
-
-
-
-
-
-
- );
+
+
+ );
- return (
-
-
-
-
- );
- }
+ return (
+
+
+
+
+ );
}
export default DevAddrInput;
diff --git a/ui/src/components/EuiInput.tsx b/ui/src/components/EuiInput.tsx
index 4d2174bc..791f8361 100644
--- a/ui/src/components/EuiInput.tsx
+++ b/ui/src/components/EuiInput.tsx
@@ -1,11 +1,10 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { notification, Input, Select, Button, Space, Form, Dropdown, Menu } from "antd";
import { ReloadOutlined, CopyOutlined } from "@ant-design/icons";
import { Buffer } from "buffer";
interface IProps {
- formRef: React.RefObject;
label: string;
name: string;
required?: boolean;
@@ -14,42 +13,29 @@ interface IProps {
tooltip?: string;
}
-interface IState {
- byteOrder: string;
- value: string;
-}
+function EuiInput(props: IProps) {
+ const form = Form.useFormInstance();
+ const [byteOrder, setByteOrder] = useState("msb");
+ const [value, setValue] = useState("");
-class EuiInput extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- byteOrder: "msb",
- value: "",
- };
- }
+ useEffect(() => {
+ if (props.value) {
+ setValue(props.value);
+ }
+ }, [props]);
- updateField = () => {
- let value = this.state.value;
-
- if (this.state.byteOrder === "lsb") {
- const bytes = value.match(/[A-Fa-f0-9]{2}/g) || [];
- value = bytes.reverse().join("");
+ const updateField = (v: string) => {
+ if (byteOrder === "lsb") {
+ const bytes = v.match(/[A-Fa-f0-9]{2}/g) || [];
+ v = bytes.reverse().join("");
}
- this.props.formRef.current.setFieldsValue({
- [this.props.name]: value,
+ form.setFieldsValue({
+ [props.name]: v,
});
};
- componentDidMount() {
- if (this.props.value) {
- this.setState({
- value: this.props.value,
- });
- }
- }
-
- onChange = (e: React.ChangeEvent) => {
+ const onChange = (e: React.ChangeEvent) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
@@ -62,50 +48,37 @@ class EuiInput extends Component {
}
}
- this.setState(
- {
- value: value,
- },
- this.updateField,
- );
+ setValue(value);
+ updateField(value);
};
- onByteOrderSelect = (v: string) => {
- if (v === this.state.byteOrder) {
+ const onByteOrderSelect = (v: string) => {
+ if (v === byteOrder) {
return;
}
- this.setState({
- byteOrder: v,
- });
+ setByteOrder(v);
- const current = this.state.value;
+ const current = value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
+ const vv = bytes.reverse().join("");
- this.setState(
- {
- value: bytes.reverse().join(""),
- },
- this.updateField,
- );
+ setValue(vv);
+ updateField(vv);
};
- generateRandom = () => {
+ const generateRandom = () => {
let cryptoObj = window.crypto || window.Crypto;
let b = new Uint8Array(8);
cryptoObj.getRandomValues(b);
let key = Buffer.from(b).toString("hex");
- this.setState(
- {
- value: key,
- },
- this.updateField,
- );
+ setValue(key);
+ updateField(key);
};
- copyToClipboard = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboard = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -126,8 +99,8 @@ class EuiInput extends Component {
}
};
- copyToClipboardHexArray = () => {
- const bytes = this.state.value.match(/[A-Fa-f0-9]{2}/g);
+ const copyToClipboardHexArray = () => {
+ const bytes = value.match(/[A-Fa-f0-9]{2}/g);
if (bytes !== null && navigator.clipboard !== undefined) {
navigator.clipboard
@@ -153,72 +126,70 @@ class EuiInput extends Component {
}
};
- render() {
- const copyMenu = (
-
- HEX string
-
- ),
- },
- {
- key: "2",
- label: (
-
- HEX array
-
- ),
- },
- ]}
- />
- );
+ const copyMenu = (
+
+ HEX string
+
+ ),
+ },
+ {
+ key: "2",
+ label: (
+
+ HEX array
+
+ ),
+ },
+ ]}
+ />
+ );
- const addon = (
-
-
- MSB
- LSB
-
-
-
+ const addon = (
+
+
+ MSB
+ LSB
+
+
+
+
+
+
+
-
-
-
-
-
-
- );
+
+
+ );
- return (
-
-
-
-
- );
- }
+ return (
+
+
+
+
+ );
}
export default EuiInput;
diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx
index 8c21bb97..eb7c02c6 100644
--- a/ui/src/components/Header.tsx
+++ b/ui/src/components/Header.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { Link, withRouter, RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { Link, useNavigate } from "react-router-dom";
import { Button, Menu, Dropdown, Input, AutoComplete } from "antd";
import { UserOutlined, DownOutlined, QuestionOutlined } from "@ant-design/icons";
@@ -14,15 +14,6 @@ import {
import InternalStore from "../stores/InternalStore";
import SessionStore from "../stores/SessionStore";
-interface IProps extends RouteComponentProps {
- user: User;
-}
-
-interface IState {
- searchResult?: GlobalSearchResponse;
- settings?: SettingsResponse;
-}
-
const renderTitle = (title: string) => {title} ;
const renderItem = (title: string, url: string) => ({
@@ -30,22 +21,19 @@ const renderItem = (title: string, url: string) => ({
label: {title},
});
-class Header extends Component {
- constructor(props: IProps) {
- super(props);
+function Header({ user }: { user: User }) {
+ const navigate = useNavigate();
- this.state = {};
- }
+ const [settings, setSettings] = useState(undefined);
+ const [searchResult, setSearchResult] = useState(undefined);
- componentDidMount() {
+ useEffect(() => {
InternalStore.settings((resp: SettingsResponse) => {
- this.setState({
- settings: resp,
- });
+ setSettings(resp);
});
- }
+ }, [user]);
- onSearch = (search: string) => {
+ const onSearch = (search: string) => {
if (search.length < 3) {
return;
}
@@ -55,14 +43,11 @@ class Header extends Component {
req.setSearch(search);
InternalStore.globalSearch(req, (resp: GlobalSearchResponse) => {
- this.setState({
- searchResult: resp,
- });
+ setSearchResult(resp);
});
};
- onLogout = () => {
- let settings = this.state.settings;
+ const onLogout = () => {
if (settings === undefined) {
return;
}
@@ -71,117 +56,112 @@ class Header extends Component {
if (!oidc.getEnabled() || oidc.getLogoutUrl() === "") {
SessionStore.logout(true, () => {
- this.props.history.push("/login");
+ navigate("/login");
});
} else {
SessionStore.logout(false, () => {
- window.location.assign(oidc.getLogoutUrl());
+ navigate(oidc.getLogoutUrl());
});
}
};
- render() {
- if (this.state.settings === undefined) {
- return null;
- }
+ if (settings === undefined) {
+ return null;
+ }
- let oidcEnabled = this.state.settings!.getOpenidConnect()!.getEnabled();
+ let oidcEnabled = settings!.getOpenidConnect()!.getEnabled();
- const menu = (
-
- {!oidcEnabled && (
-
- Change password
-
- )}
- Logout
-
- );
+ const menu = (
+
+ {!oidcEnabled && (
+
+ Change password
+
+ )}
+ Logout
+
+ );
- let options: {
- label: any;
- options: any[];
- }[] = [
- {
- label: renderTitle("Tenants"),
- options: [],
- },
- {
- label: renderTitle("Gateways"),
- options: [],
- },
- {
- label: renderTitle("Applications"),
- options: [],
- },
- {
- label: renderTitle("Devices"),
- options: [],
- },
- ];
+ let options: {
+ label: any;
+ options: any[];
+ }[] = [
+ {
+ label: renderTitle("Tenants"),
+ options: [],
+ },
+ {
+ label: renderTitle("Gateways"),
+ options: [],
+ },
+ {
+ label: renderTitle("Applications"),
+ options: [],
+ },
+ {
+ label: renderTitle("Devices"),
+ options: [],
+ },
+ ];
- if (this.state.searchResult !== undefined) {
- for (const res of this.state.searchResult.getResultList()) {
- if (res.getKind() === "tenant") {
- options[0].options.push(renderItem(res.getTenantName(), `/tenants/${res.getTenantId()}`));
- }
+ if (searchResult !== undefined) {
+ for (const res of searchResult.getResultList()) {
+ if (res.getKind() === "tenant") {
+ options[0].options.push(renderItem(res.getTenantName(), `/tenants/${res.getTenantId()}`));
+ }
- if (res.getKind() === "gateway") {
- options[1].options.push(
- renderItem(res.getGatewayName(), `/tenants/${res.getTenantId()}/gateways/${res.getGatewayId()}`),
- );
- }
+ if (res.getKind() === "gateway") {
+ options[1].options.push(
+ renderItem(res.getGatewayName(), `/tenants/${res.getTenantId()}/gateways/${res.getGatewayId()}`),
+ );
+ }
- if (res.getKind() === "application") {
- options[2].options.push(
- renderItem(
- res.getApplicationName(),
- `/tenants/${res.getTenantId()}/applications/${res.getApplicationId()}`,
- ),
- );
- }
+ if (res.getKind() === "application") {
+ options[2].options.push(
+ renderItem(res.getApplicationName(), `/tenants/${res.getTenantId()}/applications/${res.getApplicationId()}`),
+ );
+ }
- if (res.getKind() === "device") {
- options[3].options.push(
- renderItem(
- res.getDeviceName(),
- `/tenants/${res.getTenantId()}/applications/${res.getApplicationId()}/devices/${res.getDeviceDevEui()}`,
- ),
- );
- }
+ if (res.getKind() === "device") {
+ options[3].options.push(
+ renderItem(
+ res.getDeviceName(),
+ `/tenants/${res.getTenantId()}/applications/${res.getApplicationId()}/devices/${res.getDeviceDevEui()}`,
+ ),
+ );
}
}
+ }
- return (
-
-
-
-
-
-
-
- }>
- {this.props.user.getEmail()}
-
-
-
+ return (
+
+
+
+
+
+
+
+ }>
+ {user.getEmail()}
+
+
- );
- }
+
+ );
}
-export default withRouter(Header);
+export default Header;
diff --git a/ui/src/components/LogTable.tsx b/ui/src/components/LogTable.tsx
index 3a663e9e..784533d0 100644
--- a/ui/src/components/LogTable.tsx
+++ b/ui/src/components/LogTable.tsx
@@ -1,4 +1,5 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
+
import moment from "moment";
import JSONTreeOriginal from "react-json-tree";
import fileDownload from "js-file-download";
@@ -12,160 +13,142 @@ interface IProps {
logs: LogItem[];
}
-interface IState {
- drawerOpen: boolean;
- body: any;
- drawerTitle: any;
-}
+function LogTable(props: IProps) {
+ const [drawerOpen, setDrawerOpen] = useState
(false);
+ const [body, setBody] = useState(null);
+ const [drawerTitle, setDrawerTitle] = useState(null);
-class LogTable extends Component {
- constructor(props: IProps) {
- super(props);
-
- this.state = {
- drawerOpen: false,
- body: null,
- drawerTitle: null,
- };
- }
-
- onDrawerClose = () => {
- this.setState({
- drawerOpen: false,
- });
+ const onDrawerClose = () => {
+ setDrawerOpen(false);
};
- onDrawerOpen = (time: any, body: any) => {
+ const onDrawerOpen = (time: any, body: any) => {
let ts = new Date(0);
ts.setUTCSeconds(time.seconds);
let drawerTitle = moment(ts).format("YYYY-MM-DD HH:mm:ss");
return () => {
- this.setState({
- body: body,
- drawerTitle: drawerTitle,
- drawerOpen: true,
- });
+ setBody(body);
+ setDrawerTitle(drawerTitle);
+ setDrawerOpen(true);
};
};
- downloadSingleFrame = () => {
- fileDownload(JSON.stringify(JSON.parse(this.state.body), null, 4), "single-log.json", "application/json");
+ const downloadSingleFrame = () => {
+ fileDownload(JSON.stringify(JSON.parse(body), null, 4), "single-log.json", "application/json");
};
- downloadFrames = () => {
- let items = this.props.logs.map((l, i) => JSON.parse(l.getBody()));
+ const downloadFrames = () => {
+ let items = props.logs.map((l, i) => JSON.parse(l.getBody()));
fileDownload(JSON.stringify(items, null, 4), "log.json");
};
- render() {
- let items = this.props.logs.map((l, i) => l.toObject());
- let body = JSON.parse(this.state.body);
+ let items = props.logs.map((l, i) => l.toObject());
+ let bodyJson = JSON.parse(body);
- const theme = {
- scheme: "google",
- author: "seth wright (http://sethawright.com)",
- base00: "#000000",
- base01: "#282a2e",
- base02: "#373b41",
- base03: "#969896",
- base04: "#b4b7b4",
- base05: "#c5c8c6",
- base06: "#e0e0e0",
- base07: "#ffffff",
- base08: "#CC342B",
- base09: "#F96A38",
- base0A: "#FBA922",
- base0B: "#198844",
- base0C: "#3971ED",
- base0D: "#3971ED",
- base0E: "#A36AC7",
- base0F: "#3971ED",
- };
+ const theme = {
+ scheme: "google",
+ author: "seth wright (http://sethawright.com)",
+ base00: "#000000",
+ base01: "#282a2e",
+ base02: "#373b41",
+ base03: "#969896",
+ base04: "#b4b7b4",
+ base05: "#c5c8c6",
+ base06: "#e0e0e0",
+ base07: "#ffffff",
+ base08: "#CC342B",
+ base09: "#F96A38",
+ base0A: "#FBA922",
+ base0B: "#198844",
+ base0C: "#3971ED",
+ base0D: "#3971ED",
+ base0E: "#A36AC7",
+ base0F: "#3971ED",
+ };
- return (
-
- Download}
- >
- {
- return true;
- }}
- />
-
- {items.length !== 0 && (
-
-
- Download
-
- )}
- {
- let ts = new Date(0);
- ts.setUTCSeconds(obj.time!.seconds);
- return moment(ts).format("YYYY-MM-DD HH:mm:ss");
- },
- },
- {
- title: "Type",
- dataIndex: "description",
- key: "description",
- width: 200,
- render: (text, obj) => (
- }
- type="primary"
- shape="round"
- size="small"
- onClick={this.onDrawerOpen(obj.time, obj.body)}
- >
- {text}
-
- ),
- },
- {
- title: "Properties",
- dataIndex: "properties",
- key: "properties",
- render: (text, obj) =>
- obj.propertiesMap.map((p, i) => {
- if (p[1] !== "") {
- return (
-
-
- {p[0]}: {p[1]}
-
-
- );
- }
-
- return null;
- }),
- },
- ]}
+ return (
+
+ Download}
+ >
+ {
+ return true;
+ }}
/>
-
- );
- }
+
+ {items.length !== 0 && (
+
+
+ Download
+
+ )}
+ {
+ let ts = new Date(0);
+ ts.setUTCSeconds(obj.time!.seconds);
+ return moment(ts).format("YYYY-MM-DD HH:mm:ss");
+ },
+ },
+ {
+ title: "Type",
+ dataIndex: "description",
+ key: "description",
+ width: 200,
+ render: (text, obj) => (
+ }
+ type="primary"
+ shape="round"
+ size="small"
+ onClick={onDrawerOpen(obj.time, obj.body)}
+ >
+ {text}
+
+ ),
+ },
+ {
+ title: "Properties",
+ dataIndex: "properties",
+ key: "properties",
+ render: (text, obj) =>
+ obj.propertiesMap.map((p, i) => {
+ if (p[1] !== "") {
+ return (
+
+
+ {p[0]}: {p[1]}
+
+
+ );
+ }
+
+ return null;
+ }),
+ },
+ ]}
+ />
+
+ );
}
export default LogTable;
diff --git a/ui/src/components/Map.tsx b/ui/src/components/Map.tsx
index d61bc2e6..f38d824a 100644
--- a/ui/src/components/Map.tsx
+++ b/ui/src/components/Map.tsx
@@ -1,8 +1,8 @@
-import React, { Component } from "react";
+import React, { useEffect, PropsWithChildren } from "react";
import L, { LatLngTuple, FitBoundsOptions } from "leaflet";
import "leaflet.awesome-markers";
-import { MarkerProps as LMarkerProps } from "react-leaflet";
+import { MarkerProps as LMarkerProps, useMap } from "react-leaflet";
import { MapContainer, Marker as LMarker, TileLayer } from "react-leaflet";
interface IProps {
@@ -12,77 +12,48 @@ interface IProps {
boundsOptions?: FitBoundsOptions;
}
-interface IState {
- map?: L.Map;
-}
+function MapControl(props: { center?: [number, number]; bounds?: LatLngTuple[]; boundsOptions?: FitBoundsOptions }) {
+ const map = useMap();
-class Map extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- setMap = (map: L.Map) => {
- this.setState(
- {
- map: map,
- },
- () => {
- // This is needed as setMap is called after the map has been created.
- // There is a small amount of time where componentDidUpdate can't update
- // the map with the new center because setMap hasn't been called yet.
- // In such case, the map would never update to the new center.
- if (this.props.center !== undefined) {
- map.panTo(this.props.center);
- }
-
- if (this.props.bounds !== undefined) {
- map.fitBounds(this.props.bounds, this.props.boundsOptions);
- }
- },
- );
- };
-
- componentDidUpdate(oldProps: IProps) {
- if (this.props === oldProps) {
+ useEffect(() => {
+ if (map === undefined) {
return;
}
- if (this.state.map) {
- if (this.props.center !== undefined) {
- this.state.map.flyTo(this.props.center);
- }
-
- if (this.props.bounds !== undefined) {
- this.state.map.flyToBounds(this.props.bounds, this.props.boundsOptions);
- }
+ if (props.center !== undefined) {
+ map.flyTo(props.center);
}
- }
- render() {
- const style = {
- height: this.props.height,
- };
+ if (props.bounds !== undefined) {
+ map.flyToBounds(props.bounds, props.boundsOptions);
+ }
+ });
- return (
-
-
- {this.props.children}
-
- );
- }
+ return null;
+}
+
+function Map(props: PropsWithChildren) {
+ const style = {
+ height: props.height,
+ };
+
+ return (
+
+
+ {props.children}
+
+
+ );
}
export type MarkerColor =
@@ -103,22 +74,20 @@ interface MarkerProps extends LMarkerProps {
color: MarkerColor;
}
-export class Marker extends Component {
- render() {
- const { faIcon, color, position, ...otherProps } = this.props;
+export function Marker(props: MarkerProps) {
+ const { faIcon, color, position, ...otherProps } = props;
- const iconMarker = L.AwesomeMarkers.icon({
- icon: faIcon,
- prefix: "fa",
- markerColor: color,
- });
+ const iconMarker = L.AwesomeMarkers.icon({
+ icon: faIcon,
+ prefix: "fa",
+ markerColor: color,
+ });
- return (
-
- {this.props.children}
-
- );
- }
+ return (
+
+ {props.children}
+
+ );
}
export default Map;
diff --git a/ui/src/components/Menu.tsx b/ui/src/components/Menu.tsx
index 85c7a026..73da121e 100644
--- a/ui/src/components/Menu.tsx
+++ b/ui/src/components/Menu.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { withRouter, RouteComponentProps, Link } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { Link, useLocation, useNavigate } from "react-router-dom";
import { Menu, MenuProps } from "antd";
import {
@@ -24,46 +24,18 @@ import Autocomplete, { OptionCallbackFunc, OptionsCallbackFunc } from "../compon
import TenantStore from "../stores/TenantStore";
import SessionStore from "../stores/SessionStore";
-interface IState {
- tenantId: string;
- selectedKey: string;
-}
+function SideMenu() {
+ const [tenantId, setTenantId] = useState("");
+ const [selectedKey, setSelectedKey] = useState("");
-class SideMenu extends Component {
- constructor(props: RouteComponentProps) {
- super(props);
+ const location = useLocation();
+ const navigate = useNavigate();
- this.state = {
- tenantId: "",
- selectedKey: "ns-dashboard",
- };
- }
-
- componentDidMount() {
- SessionStore.on("tenant.change", this.setTenant);
- this.setTenant();
- this.parseLocation();
- }
-
- componentWillUnmount() {
- SessionStore.removeListener("tenant.change", this.setTenant);
- }
-
- componentDidUpdate(prevProps: RouteComponentProps) {
- if (this.props === prevProps) {
- return;
- }
-
- this.parseLocation();
- }
-
- setTenant = () => {
- this.setState({
- tenantId: SessionStore.getTenantId(),
- });
+ const setTenant = () => {
+ setTenantId(SessionStore.getTenantId());
};
- getTenantOptions = (search: string, fn: OptionsCallbackFunc) => {
+ const getTenantOptions = (search: string, fn: OptionsCallbackFunc) => {
let req = new ListTenantsRequest();
req.setSearch(search);
req.setLimit(10);
@@ -76,7 +48,7 @@ class SideMenu extends Component {
});
};
- getTenantOption = (id: string, fn: OptionCallbackFunc) => {
+ const getTenantOption = (id: string, fn: OptionCallbackFunc) => {
TenantStore.get(id, (resp: GetTenantResponse) => {
const tenant = resp.getTenant();
if (tenant) {
@@ -85,167 +57,208 @@ class SideMenu extends Component {
});
};
- onTenantSelect = (value: string) => {
+ const onTenantSelect = (value: string) => {
SessionStore.setTenantId(value);
- this.props.history.push(`/tenants/${value}`);
+ navigate(`/tenants/${value}`);
};
- parseLocation = () => {
- const path = this.props.history.location.pathname;
+ const parseLocation = () => {
+ const path = location.pathname;
const tenantRe = /\/tenants\/([\w-]{36})/g;
const match = tenantRe.exec(path);
- if (match !== null && this.state.tenantId !== match[1]) {
+ if (match !== null && tenantId !== match[1]) {
SessionStore.setTenantId(match[1]);
}
// ns dashboard
if (path === "/dashboard") {
- this.setState({ selectedKey: "ns-dashboard" });
+ setSelectedKey("ns-dashboard");
}
// ns tenants
if (/\/tenants(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
- this.setState({ selectedKey: "ns-tenants" });
+ setSelectedKey("ns-tenants");
}
// ns tenants
if (/\/users(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
- this.setState({ selectedKey: "ns-users" });
+ setSelectedKey("ns-users");
}
// ns api keys
if (/\/api-keys(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
- this.setState({ selectedKey: "ns-api-keys" });
+ setSelectedKey("ns-api-keys");
}
// ns device-profile templates
if (/\/device-profile-templates(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
- this.setState({ selectedKey: "ns-device-profile-templates" });
+ setSelectedKey("ns-device-profile-templates");
}
if (/\/regions.*/g.exec(path)) {
- this.setState({ selectedKey: "ns-regions" });
+ setSelectedKey("ns-regions");
}
// tenant dashboard
if (/\/tenants\/[\w-]{36}/g.exec(path)) {
- this.setState({ selectedKey: "tenant-dashboard" });
+ setSelectedKey("tenant-dashboard");
}
// tenant users
if (/\/tenants\/[\w-]{36}\/users.*/g.exec(path)) {
- this.setState({ selectedKey: "tenant-users" });
+ setSelectedKey("tenant-users");
}
// tenant api-keys
if (/\/tenants\/[\w-]{36}\/api-keys.*/g.exec(path)) {
- this.setState({ selectedKey: "tenant-api-keys" });
+ setSelectedKey("tenant-api-keys");
}
// tenant device-profiles
if (/\/tenants\/[\w-]{36}\/device-profiles.*/g.exec(path)) {
- this.setState({ selectedKey: "tenant-device-profiles" });
+ setSelectedKey("tenant-device-profiles");
}
// tenant gateways
if (/\/tenants\/[\w-]{36}\/gateways.*/g.exec(path)) {
- this.setState({ selectedKey: "tenant-gateways" });
+ setSelectedKey("tenant-gateways");
}
// tenant applications
if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) {
- this.setState({ selectedKey: "tenant-applications" });
+ setSelectedKey("tenant-applications");
}
};
- render() {
- const tenantId = this.state.tenantId;
- let items: MenuProps["items"] = [];
+ useEffect(() => {
+ SessionStore.on("tenant.change", setTenant);
+ setTenant();
+ parseLocation();
- if (SessionStore.isAdmin()) {
- items.push({
- key: "ns",
- label: "Network Server",
- icon: ,
- children: [
- { key: "ns-dashboard", icon: , label: Dashboard },
- { key: "ns-tenants", icon: , label: Tenants },
- { key: "ns-users", icon: , label: Users },
- { key: "ns-api-keys", icon: , label: API Keys },
- {
- key: "ns-device-profile-templates",
- icon: ,
- label: Device Profile Templates,
- },
- { key: "ns-regions", icon: , label: Regions },
- ],
- });
- } else {
- items.push({
- key: "ns",
- label: "Network Server",
- icon: ,
- children: [{ key: "ns-regions", icon: , label: Regions }],
- });
- }
+ return () => {
+ SessionStore.removeListener("tenant.change", setTenant);
+ };
+ }, []);
- if (tenantId !== "") {
- items.push({
- key: "tenant",
- label: "Tenant",
- icon: ,
- children: [
- {
- key: "tenant-dashboard",
- icon: ,
- label: Dashboard,
- },
- { key: "tenant-users", icon: , label: Users },
- {
- key: "tenant-api-keys",
- icon: ,
- label: API Keys,
- },
- {
- key: "tenant-device-profiles",
- icon: ,
- label: Device Profiles,
- },
- {
- key: "tenant-gateways",
- icon: ,
- label: Gateways,
- },
- {
- key: "tenant-applications",
- icon: ,
- label: Applications,
- },
- ],
- });
- }
+ useEffect(() => {
+ parseLocation();
+ }, [location]);
- return (
- }
- items={items}
- />
-
- );
+ let items: MenuProps["items"] = [];
+
+ if (SessionStore.isAdmin()) {
+ items.push({
+ key: "ns",
+ label: "Network Server",
+ icon: ,
+ children: [
+ {
+ key: "ns-dashboard",
+ icon: ,
+ label: Dashboard,
+ },
+ {
+ key: "ns-tenants",
+ icon: ,
+ label: Tenants,
+ },
+ {
+ key: "ns-users",
+ icon: ,
+ label: Users,
+ },
+ {
+ key: "ns-api-keys",
+ icon: ,
+ label: API Keys,
+ },
+ {
+ key: "ns-device-profile-templates",
+ icon: ,
+ label: Device Profile Templates,
+ },
+ {
+ key: "ns-regions",
+ icon: ,
+ label: Regions,
+ },
+ ],
+ });
+ } else {
+ items.push({
+ key: "ns",
+ label: "Network Server",
+ icon: ,
+ children: [
+ {
+ key: "ns-regions",
+ icon: ,
+ label: Regions,
+ },
+ ],
+ });
}
+
+ if (tenantId !== "") {
+ items.push({
+ key: "tenant",
+ label: "Tenant",
+ icon: ,
+ children: [
+ {
+ key: "tenant-dashboard",
+ icon: ,
+ label: Dashboard,
+ },
+ {
+ key: "tenant-users",
+ icon: ,
+ label: Users,
+ },
+ {
+ key: "tenant-api-keys",
+ icon: ,
+ label: API Keys,
+ },
+ {
+ key: "tenant-device-profiles",
+ icon: ,
+ label: Device Profiles,
+ },
+ {
+ key: "tenant-gateways",
+ icon: ,
+ label: Gateways,
+ },
+ {
+ key: "tenant-applications",
+ icon: ,
+ label: Applications,
+ },
+ ],
+ });
+ }
+
+ return (
+ }
+ items={items}
+ />
+
+ );
}
-export default withRouter(SideMenu);
+export default SideMenu;
diff --git a/ui/src/components/MetricBar.tsx b/ui/src/components/MetricBar.tsx
index 4e6dde65..e769e301 100644
--- a/ui/src/components/MetricBar.tsx
+++ b/ui/src/components/MetricBar.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Card } from "antd";
import { TimeUnit } from "chart.js";
@@ -13,82 +11,80 @@ interface IProps {
aggregation: Aggregation;
}
-class MetricBar extends Component {
- render() {
- let unit: TimeUnit = "hour";
- if (this.props.aggregation === Aggregation.DAY) {
- unit = "day";
- } else if (this.props.aggregation === Aggregation.MONTH) {
- unit = "month";
- }
-
- let backgroundColors = [
- "#8bc34a",
- "#ff5722",
- "#ff9800",
- "#ffc107",
- "#ffeb3b",
- "#cddc39",
- "#4caf50",
- "#009688",
- "#00bcd4",
- "#03a9f4",
- "#2196f3",
- "#3f51b5",
- "#673ab7",
- "#9c27b0",
- "#e91e63",
- ];
-
- const animation: false = false;
-
- const options = {
- animation: animation,
- plugins: {
- legend: {
- display: true,
- },
- },
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- },
- x: {
- type: "time" as const,
- time: {
- unit: unit,
- },
- },
- },
- };
-
- let data: {
- labels: number[];
- datasets: {
- label: string;
- data: number[];
- backgroundColor: string;
- }[];
- } = {
- labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
- datasets: [],
- };
-
- for (let ds of this.props.metric.getDatasetsList()) {
- data.datasets.push({
- label: ds.getLabel(),
- data: ds.getDataList(),
- backgroundColor: backgroundColors.shift()!,
- });
- }
-
- return (
-
-
-
- );
+function MetricBar(props: IProps) {
+ let unit: TimeUnit = "hour";
+ if (props.aggregation === Aggregation.DAY) {
+ unit = "day";
+ } else if (props.aggregation === Aggregation.MONTH) {
+ unit = "month";
}
+
+ let backgroundColors = [
+ "#8bc34a",
+ "#ff5722",
+ "#ff9800",
+ "#ffc107",
+ "#ffeb3b",
+ "#cddc39",
+ "#4caf50",
+ "#009688",
+ "#00bcd4",
+ "#03a9f4",
+ "#2196f3",
+ "#3f51b5",
+ "#673ab7",
+ "#9c27b0",
+ "#e91e63",
+ ];
+
+ const animation: false = false;
+
+ const options = {
+ animation: animation,
+ plugins: {
+ legend: {
+ display: true,
+ },
+ },
+ maintainAspectRatio: false,
+ scales: {
+ y: {
+ beginAtZero: true,
+ },
+ x: {
+ type: "time" as const,
+ time: {
+ unit: unit,
+ },
+ },
+ },
+ };
+
+ let data: {
+ labels: number[];
+ datasets: {
+ label: string;
+ data: number[];
+ backgroundColor: string;
+ }[];
+ } = {
+ labels: props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
+ datasets: [],
+ };
+
+ for (let ds of props.metric.getDatasetsList()) {
+ data.datasets.push({
+ label: ds.getLabel(),
+ data: ds.getDataList(),
+ backgroundColor: backgroundColors.shift()!,
+ });
+ }
+
+ return (
+
+
+
+ );
}
export default MetricBar;
diff --git a/ui/src/components/MetricChart.tsx b/ui/src/components/MetricChart.tsx
index 91015ae5..45961d7d 100644
--- a/ui/src/components/MetricChart.tsx
+++ b/ui/src/components/MetricChart.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Card } from "antd";
import { TimeUnit } from "chart.js";
@@ -14,83 +12,81 @@ interface IProps {
zeroToNull?: boolean;
}
-class MetricChart extends Component {
- render() {
- let unit: TimeUnit = "hour";
- let tooltipFormat = "LT";
- if (this.props.aggregation === Aggregation.DAY) {
- unit = "day";
- tooltipFormat = "MMM Do";
- } else if (this.props.aggregation === Aggregation.MONTH) {
- unit = "month";
- tooltipFormat = "MMM YYYY";
- }
-
- const animation: false = false;
-
- const options = {
- animation: animation,
- plugins: {
- legend: {
- display: false,
- },
- },
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- },
- x: {
- type: "time" as const,
- time: {
- unit: unit,
- tooltipFormat: tooltipFormat,
- },
- },
- },
- };
-
- let prevValue = 0;
- let data = {
- labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
- datasets: this.props.metric.getDatasetsList().map(v => {
- return {
- label: v.getLabel(),
- borderColor: "rgba(33, 150, 243, 1)",
- backgroundColor: "rgba(0, 0, 0, 0)",
- lineTension: 0,
- pointBackgroundColor: "rgba(33, 150, 243, 1)",
- data: v.getDataList().map(v => {
- if (v === 0 && this.props.zeroToNull) {
- return null;
- } else {
- if (this.props.metric.getKind() === MetricKind.COUNTER) {
- let val = v - prevValue;
- prevValue = v;
- if (val < 0) {
- return 0;
- }
- return val;
- } else {
- return v;
- }
- }
- }),
- };
- }),
- };
-
- let name = this.props.metric.getName();
- if (this.props.metric.getKind() === MetricKind.COUNTER) {
- name = `${name} (per ${unit})`;
- }
-
- return (
-
-
-
- );
+function MetricChart(props: IProps) {
+ let unit: TimeUnit = "hour";
+ let tooltipFormat = "LT";
+ if (props.aggregation === Aggregation.DAY) {
+ unit = "day";
+ tooltipFormat = "MMM Do";
+ } else if (props.aggregation === Aggregation.MONTH) {
+ unit = "month";
+ tooltipFormat = "MMM YYYY";
}
+
+ const animation: false = false;
+
+ const options = {
+ animation: animation,
+ plugins: {
+ legend: {
+ display: false,
+ },
+ },
+ maintainAspectRatio: false,
+ scales: {
+ y: {
+ beginAtZero: true,
+ },
+ x: {
+ type: "time" as const,
+ time: {
+ unit: unit,
+ tooltipFormat: tooltipFormat,
+ },
+ },
+ },
+ };
+
+ let prevValue = 0;
+ let data = {
+ labels: props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
+ datasets: props.metric.getDatasetsList().map(v => {
+ return {
+ label: v.getLabel(),
+ borderColor: "rgba(33, 150, 243, 1)",
+ backgroundColor: "rgba(0, 0, 0, 0)",
+ lineTension: 0,
+ pointBackgroundColor: "rgba(33, 150, 243, 1)",
+ data: v.getDataList().map(v => {
+ if (v === 0 && props.zeroToNull) {
+ return null;
+ } else {
+ if (props.metric.getKind() === MetricKind.COUNTER) {
+ let val = v - prevValue;
+ prevValue = v;
+ if (val < 0) {
+ return 0;
+ }
+ return val;
+ } else {
+ return v;
+ }
+ }
+ }),
+ };
+ }),
+ };
+
+ let name = props.metric.getName();
+ if (props.metric.getKind() === MetricKind.COUNTER) {
+ name = `${name} (per ${unit})`;
+ }
+
+ return (
+
+
+
+ );
}
export default MetricChart;
diff --git a/ui/src/components/MetricHeatmap.tsx b/ui/src/components/MetricHeatmap.tsx
index 77867b10..3b741af8 100644
--- a/ui/src/components/MetricHeatmap.tsx
+++ b/ui/src/components/MetricHeatmap.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Card } from "antd";
import { color } from "chart.js/helpers";
@@ -16,138 +14,136 @@ interface IProps {
aggregation: Aggregation;
}
-class MetricHeatmap extends Component {
- render() {
- let unit: TimeUnit = "hour";
- if (this.props.aggregation === Aggregation.DAY) {
- unit = "day";
- } else if (this.props.aggregation === Aggregation.MONTH) {
- unit = "month";
- }
+function MetricHeatmap(props: IProps) {
+ let unit: TimeUnit = "hour";
+ if (props.aggregation === Aggregation.DAY) {
+ unit = "day";
+ } else if (props.aggregation === Aggregation.MONTH) {
+ unit = "month";
+ }
- const animation: false = false;
+ const animation: false = false;
- let options = {
- animation: animation,
- maintainAspectRatio: false,
- scales: {
- y: {
- type: "category" as const,
- offset: true,
- grid: {
- display: false,
- },
+ let options = {
+ animation: animation,
+ maintainAspectRatio: false,
+ scales: {
+ y: {
+ type: "category" as const,
+ offset: true,
+ grid: {
+ display: false,
},
- x: {
- type: "time" as const,
- time: {
- unit: unit,
+ },
+ x: {
+ type: "time" as const,
+ time: {
+ unit: unit,
+ },
+ offset: true,
+ labels: props.metric.getTimestampsList().map(v => moment(v.toDate().valueOf())),
+ grid: {
+ display: false,
+ },
+ },
+ },
+ plugins: {
+ legend: { display: false },
+ tooltip: {
+ callbacks: {
+ title: () => {
+ return "";
},
- offset: true,
- labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate().valueOf())),
- grid: {
- display: false,
+ label: (ctx: any) => {
+ const v = ctx.dataset.data[ctx.dataIndex].v;
+ return "Count: " + v;
},
},
},
- plugins: {
- legend: { display: false },
- tooltip: {
- callbacks: {
- title: () => {
- return "";
- },
- label: (ctx: any) => {
- const v = ctx.dataset.data[ctx.dataIndex].v;
- return "Count: " + v;
- },
- },
+ },
+ };
+
+ let dataData: {
+ x: number;
+ y: string;
+ v: number;
+ }[] = [];
+
+ let data = {
+ labels: props.metric.getDatasetsList().map(v => v.getLabel()),
+ datasets: [
+ {
+ label: "Heatmap",
+ data: dataData,
+ minValue: -1,
+ maxValue: -1,
+ fromColor: props.fromColor.match(/\d+/g)!.map(Number),
+ toColor: props.toColor.match(/\d+/g)!.map(Number),
+ backgroundColor: (ctx: any): string => {
+ if (
+ ctx.dataset === undefined ||
+ ctx.dataset.data === undefined ||
+ ctx.dataset.data[ctx.dataIndex] === undefined
+ ) {
+ return color("white").rgbString();
+ }
+
+ const value = ctx.dataset.data[ctx.dataIndex].v;
+ const steps = ctx.dataset.maxValue - ctx.dataset.minValue + 1;
+ const step = value - ctx.dataset.minValue;
+ const factor = (1 / steps) * step;
+
+ let result: [number, number, number] = ctx.dataset.fromColor.slice();
+ for (var i = 0; i < 3; i++) {
+ result[i] = Math.round(result[i] + factor * (ctx.dataset.toColor[i] - ctx.dataset.fromColor[i]));
+ }
+
+ return color(result).rgbString();
+ },
+ borderWidth: 0,
+ width: (ctx: any) => {
+ return (ctx.chart.chartArea || {}).width / props.metric.getTimestampsList().length - 1;
+ },
+ height: (ctx: any) => {
+ return (ctx.chart.chartArea || {}).height / props.metric.getDatasetsList().length - 1;
},
},
- };
+ ],
+ };
- let dataData: {
- x: number;
- y: string;
- v: number;
- }[] = [];
+ data.labels.sort();
- let data = {
- labels: this.props.metric.getDatasetsList().map(v => v.getLabel()),
- datasets: [
- {
- label: "Heatmap",
- data: dataData,
- minValue: -1,
- maxValue: -1,
- fromColor: this.props.fromColor.match(/\d+/g)!.map(Number),
- toColor: this.props.toColor.match(/\d+/g)!.map(Number),
- backgroundColor: (ctx: any): string => {
- if (
- ctx.dataset === undefined ||
- ctx.dataset.data === undefined ||
- ctx.dataset.data[ctx.dataIndex] === undefined
- ) {
- return color("white").rgbString();
- }
+ const tsList = props.metric.getTimestampsList();
+ const dsList = props.metric.getDatasetsList();
- const value = ctx.dataset.data[ctx.dataIndex].v;
- const steps = ctx.dataset.maxValue - ctx.dataset.minValue + 1;
- const step = value - ctx.dataset.minValue;
- const factor = (1 / steps) * step;
+ for (let i = 0; i < tsList.length; i++) {
+ for (let ds of dsList) {
+ let v = ds.getDataList()[i];
+ if (v === 0) {
+ continue;
+ }
- let result: [number, number, number] = ctx.dataset.fromColor.slice();
- for (var i = 0; i < 3; i++) {
- result[i] = Math.round(result[i] + factor * (ctx.dataset.toColor[i] - ctx.dataset.fromColor[i]));
- }
+ data.datasets[0].data.push({
+ x: moment(tsList[i].toDate()).valueOf(),
+ y: ds.getLabel(),
+ v: v,
+ });
- return color(result).rgbString();
- },
- borderWidth: 0,
- width: (ctx: any) => {
- return (ctx.chart.chartArea || {}).width / this.props.metric.getTimestampsList().length - 1;
- },
- height: (ctx: any) => {
- return (ctx.chart.chartArea || {}).height / this.props.metric.getDatasetsList().length - 1;
- },
- },
- ],
- };
+ if (data.datasets[0].minValue === -1 || data.datasets[0].minValue > v) {
+ data.datasets[0].minValue = v;
+ }
- data.labels.sort();
-
- const tsList = this.props.metric.getTimestampsList();
- const dsList = this.props.metric.getDatasetsList();
-
- for (let i = 0; i < tsList.length; i++) {
- for (let ds of dsList) {
- let v = ds.getDataList()[i];
- if (v === 0) {
- continue;
- }
-
- data.datasets[0].data.push({
- x: moment(tsList[i].toDate()).valueOf(),
- y: ds.getLabel(),
- v: v,
- });
-
- if (data.datasets[0].minValue === -1 || data.datasets[0].minValue > v) {
- data.datasets[0].minValue = v;
- }
-
- if (data.datasets[0].maxValue < v) {
- data.datasets[0].maxValue = v;
- }
+ if (data.datasets[0].maxValue < v) {
+ data.datasets[0].maxValue = v;
}
}
-
- return (
-
-
-
- );
}
+
+ return (
+
+
+
+ );
}
export default MetricHeatmap;
diff --git a/ui/src/index.css b/ui/src/index.css
index 026e1317..f6c07a9c 100644
--- a/ui/src/index.css
+++ b/ui/src/index.css
@@ -31,7 +31,7 @@
padding-top: 85px;
overflow: auto;
- position: fixed;
+ position: fixed !important;
height: 100vh;
left: 0;
}
@@ -117,5 +117,10 @@ pre {
}
.ant-drawer-body {
- padding-bottom: 88px; /* 64 + 24 */
+ padding-bottom: 88px;
+ /* 64 + 24 */
+}
+
+.input-code input {
+ font-family: monospace !important;
}
diff --git a/ui/src/index.tsx b/ui/src/index.tsx
index a7f1d607..965fbc9e 100644
--- a/ui/src/index.tsx
+++ b/ui/src/index.tsx
@@ -1,7 +1,6 @@
import React from "react";
-import ReactDOM from "react-dom";
+import ReactDOM from "react-dom/client";
-// import { Chart, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, Title } from "chart.js";
import { Chart, registerables } from "chart.js";
import { MatrixElement, MatrixController } from "chartjs-chart-matrix";
import "chartjs-adapter-moment";
@@ -9,7 +8,7 @@ import "chartjs-adapter-moment";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
-import "antd/dist/antd.min.css";
+import "antd/dist/reset.css";
import "leaflet/dist/leaflet.css";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "@fortawesome/fontawesome-free/css/all.css";
@@ -19,12 +18,8 @@ import "./index.css";
Chart.register(MatrixController, MatrixElement, ...registerables);
-ReactDOM.render(
-
-
- ,
- document.getElementById("root"),
-);
+const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
+root.render( );
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
diff --git a/ui/src/stores/ApplicationStore.ts b/ui/src/stores/ApplicationStore.ts
index 7fddba6c..c8e8ee9f 100644
--- a/ui/src/stores/ApplicationStore.ts
+++ b/ui/src/stores/ApplicationStore.ts
@@ -111,6 +111,8 @@ class ApplicationStore extends EventEmitter {
return;
}
+ this.emit("change");
+
notification.success({
message: "Application updated",
duration: 3,
diff --git a/ui/src/stores/RelayStore.ts b/ui/src/stores/RelayStore.ts
index 04f3deca..bb0b4484 100644
--- a/ui/src/stores/RelayStore.ts
+++ b/ui/src/stores/RelayStore.ts
@@ -31,7 +31,7 @@ class RelayStore extends EventEmitter {
callbackFunc(resp);
});
- }
+ };
addDevice = (req: AddRelayDeviceRequest, callbackFunc: () => void) => {
this.client.addDevice(req, SessionStore.getMetadata(), err => {
@@ -47,7 +47,7 @@ class RelayStore extends EventEmitter {
callbackFunc();
});
- }
+ };
removeDevice = (req: RemoveRelayDeviceRequest, callbackFunc: () => void) => {
this.client.removeDevice(req, SessionStore.getMetadata(), err => {
@@ -63,7 +63,7 @@ class RelayStore extends EventEmitter {
callbackFunc();
});
- }
+ };
listDevices = (req: ListRelayDevicesRequest, callbackFunc: (resp: ListRelayDevicesResponse) => void) => {
this.client.listDevices(req, SessionStore.getMetadata(), (err, resp) => {
@@ -74,7 +74,7 @@ class RelayStore extends EventEmitter {
callbackFunc(resp);
});
- }
+ };
}
const relayStore = new RelayStore();
diff --git a/ui/src/views/api-keys/ApiKeyForm.tsx b/ui/src/views/api-keys/ApiKeyForm.tsx
index f5726815..0b8c7bc3 100644
--- a/ui/src/views/api-keys/ApiKeyForm.tsx
+++ b/ui/src/views/api-keys/ApiKeyForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button } from "antd";
import { ApiKey } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
@@ -9,29 +7,25 @@ interface IProps {
onFinish: (obj: ApiKey) => void;
}
-interface IState {}
-
-class ApiKeyForm extends Component {
- onFinish = (values: ApiKey.AsObject) => {
+function ApiKeyForm(props: IProps) {
+ const onFinish = (values: ApiKey.AsObject) => {
let apiKey = new ApiKey();
apiKey.setName(values.name);
- this.props.onFinish(apiKey);
+ props.onFinish(apiKey);
};
- render() {
- return (
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default ApiKeyForm;
diff --git a/ui/src/views/api-keys/ApiKeyToken.tsx b/ui/src/views/api-keys/ApiKeyToken.tsx
index b50163c8..206723e3 100644
--- a/ui/src/views/api-keys/ApiKeyToken.tsx
+++ b/ui/src/views/api-keys/ApiKeyToken.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Input, Typography, Button, Space } from "antd";
@@ -9,23 +8,21 @@ interface IProps {
createApiKeyResponse: CreateApiKeyResponse;
}
-class ApiKeyToken extends Component {
- render() {
- return (
-
-
-
- Use the following API token when making API requests. This token can be revoked at any time by deleting it.
- Please note that this token can only be retrieved once:
-
-
-
-
- Back
-
-
- );
- }
+function ApiKeyToken(props: IProps) {
+ return (
+
+
+
+ Use the following API token when making API requests. This token can be revoked at any time by deleting it.
+ Please note that this token can only be retrieved once:
+
+
+
+
+ Back
+
+
+ );
}
export default ApiKeyToken;
diff --git a/ui/src/views/api-keys/CreateAdminApiKey.tsx b/ui/src/views/api-keys/CreateAdminApiKey.tsx
index 0572a597..f9414c2a 100644
--- a/ui/src/views/api-keys/CreateAdminApiKey.tsx
+++ b/ui/src/views/api-keys/CreateAdminApiKey.tsx
@@ -1,7 +1,8 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import { Link } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { ApiKey, CreateApiKeyRequest, CreateApiKeyResponse } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
@@ -9,61 +10,48 @@ import ApiKeyForm from "./ApiKeyForm";
import ApiKeyToken from "./ApiKeyToken";
import InternalStore from "../../stores/InternalStore";
-interface IProps {}
+function CreateAdminApiKey() {
+ const [createApiKeyResponse, setCreateApiKeyResponse] = useState(undefined);
-interface IState {
- createApiKeyResponse?: CreateApiKeyResponse;
-}
-
-class CreateAdminApiKey extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- onFinish = (obj: ApiKey) => {
+ const onFinish = (obj: ApiKey) => {
obj.setIsAdmin(true);
let req = new CreateApiKeyRequest();
req.setApiKey(obj);
InternalStore.createApiKey(req, (resp: CreateApiKeyResponse) => {
- this.setState({
- createApiKeyResponse: resp,
- });
+ setCreateApiKeyResponse(resp);
});
};
- render() {
- const apiKey = new ApiKey();
+ const apiKey = new ApiKey();
- return (
-
- (
-
-
- Network-server
-
-
-
- API keys
-
-
-
- Add
-
-
- )}
- />
-
- {!this.state.createApiKeyResponse && }
- {this.state.createApiKeyResponse && }
-
-
- );
- }
+ return (
+
+ (
+
+
+ Network-server
+
+
+
+ API keys
+
+
+
+ Add
+
+
+ )}
+ />
+
+ {!createApiKeyResponse && }
+ {createApiKeyResponse && }
+
+
+ );
}
export default CreateAdminApiKey;
diff --git a/ui/src/views/api-keys/CreateTenantApiKey.tsx b/ui/src/views/api-keys/CreateTenantApiKey.tsx
index f250660b..e5feb33e 100644
--- a/ui/src/views/api-keys/CreateTenantApiKey.tsx
+++ b/ui/src/views/api-keys/CreateTenantApiKey.tsx
@@ -1,7 +1,8 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import { Link } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { ApiKey, CreateApiKeyRequest, CreateApiKeyResponse } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
@@ -10,68 +11,57 @@ import ApiKeyForm from "./ApiKeyForm";
import ApiKeyToken from "./ApiKeyToken";
import InternalStore from "../../stores/InternalStore";
-interface IState {
- createApiKeyResponse?: CreateApiKeyResponse;
-}
-
interface IProps {
tenant: Tenant;
}
-class CreateTenantApiKey extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
+function CreateTenantApiKey(props: IProps) {
+ const [createApiKeyResponse, setCreateApiKeyResponse] = useState(undefined);
- onFinish = (obj: ApiKey) => {
- obj.setTenantId(this.props.tenant.getId());
+ const onFinish = (obj: ApiKey) => {
+ obj.setTenantId(props.tenant.getId());
let req = new CreateApiKeyRequest();
req.setApiKey(obj);
InternalStore.createApiKey(req, (resp: CreateApiKeyResponse) => {
- this.setState({
- createApiKeyResponse: resp,
- });
+ setCreateApiKeyResponse(resp);
});
};
- render() {
- const apiKey = new ApiKey();
+ const apiKey = new ApiKey();
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- API Keys
-
-
-
- Add
-
-
- )}
- title="Add API key"
- />
-
- {!this.state.createApiKeyResponse && }
- {this.state.createApiKeyResponse && }
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ API Keys
+
+
+
+ Add
+
+
+ )}
+ title="Add API key"
+ />
+
+ {!createApiKeyResponse && }
+ {createApiKeyResponse && }
+
+
+ );
}
export default CreateTenantApiKey;
diff --git a/ui/src/views/api-keys/ListAdminApiKeys.tsx b/ui/src/views/api-keys/ListAdminApiKeys.tsx
index 5a7dfb5b..81dce64a 100644
--- a/ui/src/views/api-keys/ListAdminApiKeys.tsx
+++ b/ui/src/views/api-keys/ListAdminApiKeys.tsx
@@ -1,10 +1,10 @@
-import React, { Component } from "react";
-
+import React, { useState } from "react";
import { Link } from "react-router-dom";
import { DeleteOutlined } from "@ant-design/icons";
-import { Space, Breadcrumb, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Button } from "antd";
import { ColumnsType } from "antd/es/table";
+import { PageHeader } from "@ant-design/pro-layout";
import {
ListApiKeysRequest,
@@ -17,62 +17,47 @@ import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import InternalStore from "../../stores/InternalStore";
import DeleteConfirm from "../../components/DeleteConfirm";
-interface IProps {}
+function ListAdminApiKeys() {
+ const [refreshKey, setRefreshKey] = useState(1);
-interface IState {
- refreshKey: number;
-}
+ const columns: ColumnsType = [
+ {
+ title: "ID",
+ dataIndex: "id",
+ key: "id",
+ width: 400,
+ },
+ {
+ title: "Name",
+ dataIndex: "name",
+ key: "name",
+ },
+ {
+ title: "Action",
+ dataIndex: "id",
+ key: "action",
+ width: 100,
+ render: (text, record) => (
+
+ } />
+
+ ),
+ },
+ ];
-class ListAdminApiKeys extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- refreshKey: 1,
- };
- }
-
- columns = (): ColumnsType => {
- return [
- {
- title: "ID",
- dataIndex: "id",
- key: "id",
- width: 400,
- },
- {
- title: "Name",
- dataIndex: "name",
- key: "name",
- },
- {
- title: "Action",
- dataIndex: "id",
- key: "action",
- width: 100,
- render: (text, record) => (
-
- } />
-
- ),
- },
- ];
- };
-
- deleteApiKey = (id: string): (() => void) => {
+ const deleteApiKey = (id: string): (() => void) => {
return () => {
let req = new DeleteApiKeyRequest();
req.setId(id);
InternalStore.deleteApiKey(req, () => {
// trigger a data-table reload
- this.setState({
- refreshKey: this.state.refreshKey + 1,
- });
+ setRefreshKey(refreshKey + 1);
});
};
};
- getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
+ const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApiKeysRequest();
req.setLimit(limit);
req.setOffset(offset);
@@ -84,31 +69,29 @@ class ListAdminApiKeys extends Component {
});
};
- render() {
- return (
-
- (
-
-
- Network Server
-
-
- API keys
-
-
- )}
- title="API keys"
- extra={[
-
- Add API key
- ,
- ]}
- />
-
-
- );
- }
+ return (
+
+ (
+
+
+ Network Server
+
+
+ API keys
+
+
+ )}
+ title="API keys"
+ extra={[
+
+ Add API key
+ ,
+ ]}
+ />
+
+
+ );
}
export default ListAdminApiKeys;
diff --git a/ui/src/views/api-keys/ListTenantApiKeys.tsx b/ui/src/views/api-keys/ListTenantApiKeys.tsx
index 1dd13a8a..e6225078 100644
--- a/ui/src/views/api-keys/ListTenantApiKeys.tsx
+++ b/ui/src/views/api-keys/ListTenantApiKeys.tsx
@@ -1,8 +1,9 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import { Link } from "react-router-dom";
import { DeleteOutlined } from "@ant-design/icons";
-import { Space, Breadcrumb, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Button } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { ColumnsType } from "antd/es/table";
import {
@@ -20,69 +21,55 @@ import Admin from "../../components/Admin";
interface IProps {
tenant: Tenant;
- isAdmin: boolean;
}
-interface IState {
- refreshKey: number;
-}
+function ListTenantApiKeys(props: IProps) {
+ const [refreshKey, setRefreshKey] = useState(1);
-class ListTenantApiKeys extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- refreshKey: 1,
- };
- }
+ const columns: ColumnsType = [
+ {
+ title: "ID",
+ dataIndex: "id",
+ key: "id",
+ width: 400,
+ },
+ {
+ title: "Name",
+ dataIndex: "name",
+ key: "name",
+ },
+ {
+ title: "Action",
+ dataIndex: "id",
+ key: "action",
+ width: 100,
+ render: (text, record) => (
+
+
+ } />
+
+
+ ),
+ },
+ ];
- columns = (): ColumnsType => {
- return [
- {
- title: "ID",
- dataIndex: "id",
- key: "id",
- width: 400,
- },
- {
- title: "Name",
- dataIndex: "name",
- key: "name",
- },
- {
- title: "Action",
- dataIndex: "id",
- key: "action",
- width: 100,
- render: (text, record) => (
-
-
- } />
-
-
- ),
- },
- ];
- };
-
- deleteApiKey = (id: string): (() => void) => {
+ const deleteApiKey = (id: string): (() => void) => {
return () => {
let req = new DeleteApiKeyRequest();
req.setId(id);
InternalStore.deleteApiKey(req, () => {
// trigger a data-table reload
- this.setState({
- refreshKey: this.state.refreshKey + 1,
- });
+ setRefreshKey(refreshKey + 1);
});
};
};
- getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
+ const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApiKeysRequest();
req.setLimit(limit);
req.setOffset(offset);
- req.setTenantId(this.props.tenant.getId());
+ req.setTenantId(props.tenant.getId());
InternalStore.listApiKeys(req, (resp: ListApiKeysResponse) => {
const obj = resp.toObject();
@@ -90,38 +77,36 @@ class ListTenantApiKeys extends Component {
});
};
- render() {
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
- API Keys
-
-
- )}
- title="API keys"
- extra={[
-
-
- Add API key
-
- ,
- ]}
- />
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+ API Keys
+
+
+ )}
+ title="API keys"
+ extra={[
+
+
+ Add API key
+
+ ,
+ ]}
+ />
+
+
+ );
}
export default ListTenantApiKeys;
diff --git a/ui/src/views/applications/ApplicationForm.tsx b/ui/src/views/applications/ApplicationForm.tsx
index b4eab948..fcb637b1 100644
--- a/ui/src/views/applications/ApplicationForm.tsx
+++ b/ui/src/views/applications/ApplicationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Form, Input, Button } from "antd";
@@ -9,9 +7,9 @@ interface IProps {
disabled?: boolean;
}
-class ApplicationForm extends Component {
- onFinish = (values: Application.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function ApplicationForm(props: IProps) {
+ const onFinish = (values: Application.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let app = new Application();
app.setId(v.id);
@@ -19,26 +17,24 @@ class ApplicationForm extends Component {
app.setName(v.name);
app.setDescription(v.description);
- this.props.onFinish(app);
+ props.onFinish(app);
};
- render() {
- return (
-
-
-
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default ApplicationForm;
diff --git a/ui/src/views/applications/ApplicationLayout.tsx b/ui/src/views/applications/ApplicationLayout.tsx
index 36e4c935..1dacc518 100644
--- a/ui/src/views/applications/ApplicationLayout.tsx
+++ b/ui/src/views/applications/ApplicationLayout.tsx
@@ -1,7 +1,7 @@
-import React, { Component } from "react";
-import { Route, Switch, RouteComponentProps, Link } from "react-router-dom";
+import { Route, Routes, Link, useNavigate, useLocation } from "react-router-dom";
-import { Space, Breadcrumb, Card, Button, PageHeader, Menu } from "antd";
+import { Space, Breadcrumb, Card, Button, Menu } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application, DeleteApplicationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -38,247 +38,158 @@ import GenerateMqttCertificate from "./integrations/GenerateMqttCertificate";
import CreateIftttIntegration from "./integrations/CreateIftttIntegration";
import EditIftttIntegration from "./integrations/EditIftttIntegration";
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
application: Application;
measurementKeys: string[];
}
-class ApplicationLayout extends Component {
- deleteApplication = () => {
+function ApplicationLayout(props: IProps) {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const deleteApplication = () => {
let req = new DeleteApplicationRequest();
- req.setId(this.props.application.getId());
+ req.setId(props.application.getId());
ApplicationStore.delete(req, () => {
- this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications`);
+ navigate(`/tenants/${props.tenant.getId()}/applications`);
});
};
- render() {
- const tenant = this.props.tenant;
- const app = this.props.application;
+ const tenant = props.tenant;
+ const app = props.application;
- if (!app) {
- return null;
- }
-
- const path = this.props.history.location.pathname;
- let tab = "devices";
-
- if (path.endsWith("/multicast-groups")) {
- tab = "mg";
- }
- if (path.endsWith("/relays")) {
- tab = "relay";
- }
- if (path.endsWith("/edit")) {
- tab = "edit";
- }
- if (path.match(/.*\/integrations.*/g)) {
- tab = "integrations";
- }
-
- const showIntegrations =
- SessionStore.isAdmin() ||
- SessionStore.isTenantAdmin(tenant.getId()) ||
- SessionStore.isTenantDeviceAdmin(tenant.getId());
-
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- Applications
-
-
-
- {app.getName()}
-
-
- )}
- title={app.getName()}
- subTitle={`application id: ${app.getId()}`}
- extra={[
-
-
-
- Delete application
-
-
- ,
- ]}
- />
-
-
-
- Devices
-
-
-
- Multicast groups
-
-
-
-
- Relays
-
-
-
- Application configuration
-
- {showIntegrations && (
-
- Integrations
-
- )}
-
-
- } />
- }
- />
- }
- />
- }
- />
- }
- />
-
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- (
-
- )}
- />
- (
-
- )}
- />
-
-
-
- );
+ if (!app) {
+ return null;
}
+
+ const path = location.pathname;
+ let tab = "devices";
+
+ if (path.endsWith("/multicast-groups")) {
+ tab = "mg";
+ }
+ if (path.endsWith("/relays")) {
+ tab = "relay";
+ }
+ if (path.endsWith("/edit")) {
+ tab = "edit";
+ }
+ if (path.match(/.*\/integrations.*/g)) {
+ tab = "integrations";
+ }
+
+ const showIntegrations =
+ SessionStore.isAdmin() ||
+ SessionStore.isTenantAdmin(tenant.getId()) ||
+ SessionStore.isTenantDeviceAdmin(tenant.getId());
+
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ Applications
+
+
+
+ {app.getName()}
+
+
+ )}
+ title={app.getName()}
+ subTitle={`application id: ${app.getId()}`}
+ extra={[
+
+
+
+ Delete application
+
+
+ ,
+ ]}
+ />
+
+
+
+ Devices
+
+
+ Multicast groups
+
+
+ Relays
+
+
+ Application configuration
+
+ {showIntegrations && (
+
+ Integrations
+
+ )}
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ }
+ />
+
+
+
+ );
}
export default ApplicationLayout;
diff --git a/ui/src/views/applications/ApplicationLoader.tsx b/ui/src/views/applications/ApplicationLoader.tsx
index c46fd721..ee9b7d99 100644
--- a/ui/src/views/applications/ApplicationLoader.tsx
+++ b/ui/src/views/applications/ApplicationLoader.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { Route, Switch, RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { Route, Routes, useParams } from "react-router-dom";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import {
@@ -16,90 +16,57 @@ import MulticastGroupLayout from "../multicast-groups/MulticastGroupLayout";
import CreateMulticastGroup from "../multicast-groups/CreateMulticastGroup";
import RelayLayout from "../relays/RelayLayout";
-interface MatchParams {
- applicationId: string;
-}
-
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
}
-interface IState {
- application?: Application;
- measurementKeys: string[];
-}
+function ApplicationLoader(props: IProps) {
+ const { applicationId } = useParams();
+ const [application, setApplication] = useState(undefined);
+ const [measurementKeys, setMeasurementKeys] = useState([]);
-class ApplicationLoader extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- measurementKeys: [],
+ useEffect(() => {
+ ApplicationStore.on("change", loadApplication);
+ loadApplication();
+
+ return () => {
+ ApplicationStore.removeAllListeners("change");
};
- }
+ }, [applicationId]);
- componentDidMount() {
- this.getApplication();
- }
-
- getApplication = () => {
+ const loadApplication = () => {
let req = new GetApplicationRequest();
- req.setId(this.props.match.params.applicationId);
+ req.setId(applicationId!);
ApplicationStore.get(req, (resp: GetApplicationResponse) => {
- this.setState({
- application: resp.getApplication(),
- measurementKeys: resp.getMeasurementKeysList(),
- });
+ setApplication(resp.getApplication());
+ setMeasurementKeys(resp.getMeasurementKeysList());
});
};
- render() {
- const app = this.state.application;
- if (!app) {
- return null;
- }
-
- const path = this.props.match.path;
- const tenant = this.props.tenant;
-
- return (
-
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
- (
-
- )}
- />
-
- );
+ const app = application;
+ if (!app) {
+ return null;
}
+
+ const tenant = props.tenant;
+
+ return (
+
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ }
+ />
+
+ );
}
export default ApplicationLoader;
diff --git a/ui/src/views/applications/CreateApplication.tsx b/ui/src/views/applications/CreateApplication.tsx
index e9dd0abc..205bac0d 100644
--- a/ui/src/views/applications/CreateApplication.tsx
+++ b/ui/src/views/applications/CreateApplication.tsx
@@ -1,7 +1,7 @@
-import React, { Component } from "react";
-import { Link, RouteComponentProps } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import {
@@ -13,56 +13,56 @@ import {
import ApplicationForm from "./ApplicationForm";
import ApplicationStore from "../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
}
-class CreateApplication extends Component {
- onFinish = (obj: Application) => {
- obj.setTenantId(this.props.tenant.getId());
+function CreateApplication(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: Application) => {
+ obj.setTenantId(props.tenant.getId());
let req = new CreateApplicationRequest();
req.setApplication(obj);
ApplicationStore.create(req, (resp: CreateApplicationResponse) => {
- this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications/${resp.getId()}`);
+ navigate(`/tenants/${props.tenant.getId()}/applications/${resp.getId()}`);
});
};
- render() {
- const app = new Application();
+ const app = new Application();
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- Applications
-
-
-
- Add
-
-
- )}
- title="Add application"
- />
-
-
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ Applications
+
+
+
+ Add
+
+
+ )}
+ title="Add application"
+ />
+
+
+
+
+ );
}
export default CreateApplication;
diff --git a/ui/src/views/applications/EditApplication.tsx b/ui/src/views/applications/EditApplication.tsx
index 1f3dbf66..7fd50129 100644
--- a/ui/src/views/applications/EditApplication.tsx
+++ b/ui/src/views/applications/EditApplication.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Application, UpdateApplicationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -7,31 +6,29 @@ import ApplicationStore from "../../stores/ApplicationStore";
import ApplicationForm from "./ApplicationForm";
import SessionStore from "../../stores/SessionStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class EditApplication extends Component {
- onFinish = (obj: Application) => {
+function EditApplication(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: Application) => {
let req = new UpdateApplicationRequest();
req.setApplication(obj);
ApplicationStore.update(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}`);
});
};
- render() {
- const disabled = !(
- SessionStore.isAdmin() ||
- SessionStore.isTenantAdmin(this.props.application.getTenantId()) ||
- SessionStore.isTenantDeviceAdmin(this.props.application.getTenantId())
- );
+ const disabled = !(
+ SessionStore.isAdmin() ||
+ SessionStore.isTenantAdmin(props.application.getTenantId()) ||
+ SessionStore.isTenantDeviceAdmin(props.application.getTenantId())
+ );
- return ;
- }
+ return ;
}
export default EditApplication;
diff --git a/ui/src/views/applications/ListApplications.tsx b/ui/src/views/applications/ListApplications.tsx
index ca422b63..76d53a40 100644
--- a/ui/src/views/applications/ListApplications.tsx
+++ b/ui/src/views/applications/ListApplications.tsx
@@ -1,8 +1,8 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
-import { Space, Breadcrumb, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Button } from "antd";
import { ColumnsType } from "antd/es/table";
+import { PageHeader } from "@ant-design/pro-layout";
import {
ListApplicationsRequest,
@@ -19,29 +19,25 @@ interface IProps {
tenant: Tenant;
}
-class ListApplications extends Component {
- columns = (): ColumnsType => {
- return [
- {
- title: "Name",
- dataIndex: "name",
- key: "name",
- width: 250,
- render: (text, record) => (
- {text}
- ),
- },
- {
- title: "Description",
- dataIndex: "description",
- key: "description",
- },
- ];
- };
+function ListApplications(props: IProps) {
+ const columns: ColumnsType = [
+ {
+ title: "Name",
+ dataIndex: "name",
+ key: "name",
+ width: 250,
+ render: (text, record) => {text},
+ },
+ {
+ title: "Description",
+ dataIndex: "description",
+ key: "description",
+ },
+ ];
- getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
+ const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApplicationsRequest();
- req.setTenantId(this.props.tenant.getId());
+ req.setTenantId(props.tenant.getId());
req.setLimit(limit);
req.setOffset(offset);
@@ -51,38 +47,36 @@ class ListApplications extends Component {
});
};
- render() {
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
- Applications
-
-
- )}
- title="Applications"
- extra={[
-
-
- Add application
-
- ,
- ]}
- />
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+ Applications
+
+
+ )}
+ title="Applications"
+ extra={[
+
+
+ Add application
+
+ ,
+ ]}
+ />
+
+
+ );
}
export default ListApplications;
diff --git a/ui/src/views/applications/ListIntegrations.tsx b/ui/src/views/applications/ListIntegrations.tsx
index 3da4834b..51fcfad0 100644
--- a/ui/src/views/applications/ListIntegrations.tsx
+++ b/ui/src/views/applications/ListIntegrations.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Row } from "antd";
@@ -27,32 +27,22 @@ interface IProps {
application: Application;
}
-interface IState {
- configured: any[];
- available: any[];
-}
+function ListIntegrations(props: IProps) {
+ const [configured, setConfigured] = useState([]);
+ const [available, setAvailable] = useState([]);
-class ListIntegrations extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- configured: [],
- available: [],
+ useEffect(() => {
+ ApplicationStore.on("integration.delete", loadIntegrations);
+ loadIntegrations();
+
+ return () => {
+ ApplicationStore.removeAllListeners("integration.delete");
};
- }
+ }, []);
- componentDidMount() {
- ApplicationStore.on("integration.delete", this.loadIntegrations);
- this.loadIntegrations();
- }
-
- componentWillUnmount() {
- ApplicationStore.removeAllListeners("integration.delete");
- }
-
- loadIntegrations = () => {
+ const loadIntegrations = () => {
let req = new ListIntegrationsRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.listIntegrations(req, (resp: ListIntegrationsResponse) => {
let configured: any[] = [];
@@ -70,94 +60,90 @@ class ListIntegrations extends Component {
// AWS SNS
if (includes(resp.getResultList(), IntegrationKind.AWS_SNS)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// Azure Service-Bus
if (includes(resp.getResultList(), IntegrationKind.AZURE_SERVICE_BUS)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// GCP Pub/Sub
if (includes(resp.getResultList(), IntegrationKind.GCP_PUB_SUB)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// HTTP
if (includes(resp.getResultList(), IntegrationKind.HTTP)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// IFTTT
if (includes(resp.getResultList(), IntegrationKind.IFTTT)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// InfluxDB
if (includes(resp.getResultList(), IntegrationKind.INFLUX_DB)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// MQTT
if (includes(resp.getResultList(), IntegrationKind.MQTT_GLOBAL)) {
- configured.push( );
+ configured.push( );
}
// myDevices
if (includes(resp.getResultList(), IntegrationKind.MY_DEVICES)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// Pilot Things
if (includes(resp.getResultList(), IntegrationKind.PILOT_THINGS)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// Semtech LoRa Cloud
if (includes(resp.getResultList(), IntegrationKind.LORA_CLOUD)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
// ThingsBoard
if (includes(resp.getResultList(), IntegrationKind.THINGS_BOARD)) {
- configured.push( );
+ configured.push( );
} else {
- available.push( );
+ available.push( );
}
- this.setState({
- configured: configured,
- available: available,
- });
+ setConfigured(configured);
+ setAvailable(available);
});
};
- render() {
- return (
-
- {this.state.configured}
- {this.state.available}
-
- );
- }
+ return (
+
+ {configured}
+ {available}
+
+ );
}
export default ListIntegrations;
diff --git a/ui/src/views/applications/integrations/AwsSnsCard.tsx b/ui/src/views/applications/integrations/AwsSnsCard.tsx
index 84e68c1a..de9e9feb 100644
--- a/ui/src/views/applications/integrations/AwsSnsCard.tsx
+++ b/ui/src/views/applications/integrations/AwsSnsCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,47 +12,45 @@ interface IProps {
add?: boolean;
}
-class AwsSns extends Component {
- onDelete = () => {
+function AwsSns(props: IProps) {
+ const onDelete = () => {
let req = new DeleteAwsSnsIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteAwsSnsIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default AwsSns;
diff --git a/ui/src/views/applications/integrations/AwsSnsIntegrationForm.tsx b/ui/src/views/applications/integrations/AwsSnsIntegrationForm.tsx
index 9e049344..77922264 100644
--- a/ui/src/views/applications/integrations/AwsSnsIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/AwsSnsIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button, Select } from "antd";
import { AwsSnsIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -9,9 +7,9 @@ interface IProps {
onFinish: (obj: AwsSnsIntegration) => void;
}
-class AwsSnsIntegrationForm extends Component {
- onFinish = (values: AwsSnsIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function AwsSnsIntegrationForm(props: IProps) {
+ const onFinish = (values: AwsSnsIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new AwsSnsIntegration();
i.setApplicationId(v.applicationId);
@@ -21,54 +19,52 @@ class AwsSnsIntegrationForm extends Component {
i.setSecretAccessKey(v.secretAccessKey);
i.setTopicArn(v.topicArn);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
- JSON
- Protobuf (binary)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+ JSON
+ Protobuf (binary)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default AwsSnsIntegrationForm;
diff --git a/ui/src/views/applications/integrations/AzureServiceBusCard.tsx b/ui/src/views/applications/integrations/AzureServiceBusCard.tsx
index be4d30f7..bd5ffa31 100644
--- a/ui/src/views/applications/integrations/AzureServiceBusCard.tsx
+++ b/ui/src/views/applications/integrations/AzureServiceBusCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -16,46 +15,44 @@ interface IProps {
add?: boolean;
}
-class AzureServiceBusCard extends Component {
- onDelete = () => {
+function AzureServiceBusCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteAzureServiceBusIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteAzureServiceBusIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default AzureServiceBusCard;
diff --git a/ui/src/views/applications/integrations/AzureServiceBusIntegrationForm.tsx b/ui/src/views/applications/integrations/AzureServiceBusIntegrationForm.tsx
index aa0ccc99..7089f270 100644
--- a/ui/src/views/applications/integrations/AzureServiceBusIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/AzureServiceBusIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button, Select } from "antd";
import { AzureServiceBusIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -9,9 +7,9 @@ interface IProps {
onFinish: (obj: AzureServiceBusIntegration) => void;
}
-class AzureServiceBusIntegrationForm extends Component {
- onFinish = (values: AzureServiceBusIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function AzureServiceBusIntegrationForm(props: IProps) {
+ const onFinish = (values: AzureServiceBusIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new AzureServiceBusIntegration();
i.setApplicationId(v.applicationId);
@@ -19,45 +17,53 @@ class AzureServiceBusIntegrationForm extends Component {
i.setConnectionString(v.connectionString);
i.setPublishName(v.publishName);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
- JSON
- Protobuf (binary)
-
-
-
-
-
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+ JSON
+ Protobuf (binary)
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default AzureServiceBusIntegrationForm;
diff --git a/ui/src/views/applications/integrations/CreateAwsSnsIntegration.tsx b/ui/src/views/applications/integrations/CreateAwsSnsIntegration.tsx
index f96ff64e..0e6dffdb 100644
--- a/ui/src/views/applications/integrations/CreateAwsSnsIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateAwsSnsIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import AwsSnsIntegrationForm from "./AwsSnsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateAwsSnsIntegration extends Component {
- onFinish = (obj: AwsSnsIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateAwsSnsIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: AwsSnsIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateAwsSnsIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createAwsSnsIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new AwsSnsIntegration();
+ const i = new AwsSnsIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateAwsSnsIntegration;
diff --git a/ui/src/views/applications/integrations/CreateAzureServiceBusIntegration.tsx b/ui/src/views/applications/integrations/CreateAzureServiceBusIntegration.tsx
index 4e7e38af..9d00d4c6 100644
--- a/ui/src/views/applications/integrations/CreateAzureServiceBusIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateAzureServiceBusIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateAzureServiceBusIntegration extends Component {
- onFinish = (obj: AzureServiceBusIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateAzureServiceBusIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: AzureServiceBusIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateAzureServiceBusIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createAzureServiceBusIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new AzureServiceBusIntegration();
+ const i = new AzureServiceBusIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateAzureServiceBusIntegration;
diff --git a/ui/src/views/applications/integrations/CreateGcpPubSubIntegration.tsx b/ui/src/views/applications/integrations/CreateGcpPubSubIntegration.tsx
index 8c513065..d42edf72 100644
--- a/ui/src/views/applications/integrations/CreateGcpPubSubIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateGcpPubSubIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import GcpPubSubIntegrationForm from "./GcpPubSubIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateGcpPubSubIntegration extends Component {
- onFinish = (obj: GcpPubSubIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateGcpPubSubIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: GcpPubSubIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateGcpPubSubIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createGcpPubSubIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new GcpPubSubIntegration();
+ const i = new GcpPubSubIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateGcpPubSubIntegration;
diff --git a/ui/src/views/applications/integrations/CreateHttpIntegration.tsx b/ui/src/views/applications/integrations/CreateHttpIntegration.tsx
index ffabf643..15150e24 100644
--- a/ui/src/views/applications/integrations/CreateHttpIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateHttpIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import HttpIntegrationForm from "./HttpIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateHttpIntegration extends Component {
- onFinish = (obj: HttpIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateHttpIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: HttpIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateHttpIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createHttpIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new HttpIntegration();
+ const i = new HttpIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateHttpIntegration;
diff --git a/ui/src/views/applications/integrations/CreateIftttIntegration.tsx b/ui/src/views/applications/integrations/CreateIftttIntegration.tsx
index 1fb8e226..20959ddc 100644
--- a/ui/src/views/applications/integrations/CreateIftttIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateIftttIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,35 +11,33 @@ import {
import IftttIntegrationForm from "./IftttIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
measurementKeys: string[];
}
-class CreateIftttIntegration extends Component {
- onFinish = (obj: IftttIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateIftttIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: IftttIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateIftttIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createIftttIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new IftttIntegration();
- i.setUplinkValuesList(["", ""]);
+ const i = new IftttIntegration();
+ i.setUplinkValuesList(["", ""]);
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateIftttIntegration;
diff --git a/ui/src/views/applications/integrations/CreateInfluxDbIntegration.tsx b/ui/src/views/applications/integrations/CreateInfluxDbIntegration.tsx
index 3470c663..3ec2f393 100644
--- a/ui/src/views/applications/integrations/CreateInfluxDbIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateInfluxDbIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import InfluxDbIntegrationForm from "./InfluxDbIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateInfluxDbIntegration extends Component {
- onFinish = (obj: InfluxDbIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateInfluxDbIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: InfluxDbIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateInfluxDbIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createInfluxDbIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new InfluxDbIntegration();
+ const i = new InfluxDbIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateInfluxDbIntegration;
diff --git a/ui/src/views/applications/integrations/CreateLoRaCloudIntegration.tsx b/ui/src/views/applications/integrations/CreateLoRaCloudIntegration.tsx
index 26dc77e2..b8520ba9 100644
--- a/ui/src/views/applications/integrations/CreateLoRaCloudIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateLoRaCloudIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -13,38 +12,36 @@ import {
import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateLoRaCloudIntegration extends Component {
- onFinish = (obj: LoraCloudIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateLoRaCloudIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: LoraCloudIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateLoraCloudIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createLoraCloudIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- let i = new LoraCloudIntegration();
- let mgs = new LoraCloudModemGeolocationServices();
- mgs.setModemEnabled(true);
- mgs.setForwardFPortsList([192, 197, 198, 199]);
+ let i = new LoraCloudIntegration();
+ let mgs = new LoraCloudModemGeolocationServices();
+ mgs.setModemEnabled(true);
+ mgs.setForwardFPortsList([192, 197, 198, 199]);
- i.setModemGeolocationServices(mgs);
+ i.setModemGeolocationServices(mgs);
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateLoRaCloudIntegration;
diff --git a/ui/src/views/applications/integrations/CreateMyDevicesIntegration.tsx b/ui/src/views/applications/integrations/CreateMyDevicesIntegration.tsx
index e91ae81b..3a3a4d72 100644
--- a/ui/src/views/applications/integrations/CreateMyDevicesIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateMyDevicesIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateMyDevicesIntegration extends Component {
- onFinish = (obj: MyDevicesIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateMyDevicesIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: MyDevicesIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateMyDevicesIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createMyDevicesIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new MyDevicesIntegration();
+ const i = new MyDevicesIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateMyDevicesIntegration;
diff --git a/ui/src/views/applications/integrations/CreatePilotThingsIntegration.tsx b/ui/src/views/applications/integrations/CreatePilotThingsIntegration.tsx
index 989d5e0c..b2c030d6 100644
--- a/ui/src/views/applications/integrations/CreatePilotThingsIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreatePilotThingsIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreatePilotThingsIntegration extends Component {
- onFinish = (obj: PilotThingsIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreatePilotThingsIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: PilotThingsIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreatePilotThingsIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createPilotThingsIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new PilotThingsIntegration();
+ const i = new PilotThingsIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreatePilotThingsIntegration;
diff --git a/ui/src/views/applications/integrations/CreateThingsBoardIntegration.tsx b/ui/src/views/applications/integrations/CreateThingsBoardIntegration.tsx
index a4fcabe9..79ad2003 100644
--- a/ui/src/views/applications/integrations/CreateThingsBoardIntegration.tsx
+++ b/ui/src/views/applications/integrations/CreateThingsBoardIntegration.tsx
@@ -1,5 +1,4 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -12,33 +11,31 @@ import {
import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-class CreateThingsBoardIntegration extends Component {
- onFinish = (obj: ThingsBoardIntegration) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateThingsBoardIntegration(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: ThingsBoardIntegration) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateThingsBoardIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.createThingsBoardIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- const i = new ThingsBoardIntegration();
+ const i = new ThingsBoardIntegration();
- return (
-
-
-
- );
- }
+ return (
+
+
+
+ );
}
export default CreateThingsBoardIntegration;
diff --git a/ui/src/views/applications/integrations/EditAwsSnsIntegration.tsx b/ui/src/views/applications/integrations/EditAwsSnsIntegration.tsx
index d616cfc6..4e64223e 100644
--- a/ui/src/views/applications/integrations/EditAwsSnsIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditAwsSnsIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import AwsSnsIntegrationForm from "./AwsSnsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: AwsSnsIntegration;
-}
+function EditAwsSnsIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditAwsSnsIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetAwsSnsIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getAwsSnsIntegration(req, (resp: GetAwsSnsIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: AwsSnsIntegration) => {
+ const onFinish = (obj: AwsSnsIntegration) => {
let req = new UpdateAwsSnsIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateAwsSnsIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditAwsSnsIntegration;
diff --git a/ui/src/views/applications/integrations/EditAzureServiceBusIntegration.tsx b/ui/src/views/applications/integrations/EditAzureServiceBusIntegration.tsx
index 4b40cf24..cd0290ad 100644
--- a/ui/src/views/applications/integrations/EditAzureServiceBusIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditAzureServiceBusIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: AzureServiceBusIntegration;
-}
+function EditAzureServiceBusIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditAzureServiceBusIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetAzureServiceBusIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getAzureServiceBusIntegration(req, (resp: GetAzureServiceBusIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: AzureServiceBusIntegration) => {
+ const onFinish = (obj: AzureServiceBusIntegration) => {
let req = new UpdateAzureServiceBusIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateAzureServiceBusIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditAzureServiceBusIntegration;
diff --git a/ui/src/views/applications/integrations/EditGcpPubSubIntegration.tsx b/ui/src/views/applications/integrations/EditGcpPubSubIntegration.tsx
index 4f316866..74e12ae1 100644
--- a/ui/src/views/applications/integrations/EditGcpPubSubIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditGcpPubSubIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import GcpPubSubIntegrationForm from "./GcpPubSubIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: GcpPubSubIntegration;
-}
+function EditGcpPubSubIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditGcpPubSubIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetGcpPubSubIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getGcpPubSubIntegration(req, (resp: GetGcpPubSubIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: GcpPubSubIntegration) => {
+ const onFinish = (obj: GcpPubSubIntegration) => {
let req = new UpdateGcpPubSubIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateGcpPubSubIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditGcpPubSubIntegration;
diff --git a/ui/src/views/applications/integrations/EditHttpIntegration.tsx b/ui/src/views/applications/integrations/EditHttpIntegration.tsx
index 2ff58391..b0b42de9 100644
--- a/ui/src/views/applications/integrations/EditHttpIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditHttpIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import HttpIntegrationForm from "./HttpIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: HttpIntegration;
-}
+function EditHttpIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditHttpIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetHttpIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getHttpIntegration(req, (resp: GetHttpIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: HttpIntegration) => {
+ const onFinish = (obj: HttpIntegration) => {
let req = new UpdateHttpIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateHttpIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditHttpIntegration;
diff --git a/ui/src/views/applications/integrations/EditIftttIntegration.tsx b/ui/src/views/applications/integrations/EditIftttIntegration.tsx
index ab6b6af0..5e9c97e6 100644
--- a/ui/src/views/applications/integrations/EditIftttIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditIftttIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,58 +14,42 @@ import {
import IftttIntegrationForm from "./IftttIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
measurementKeys: string[];
}
-interface IState {
- integration?: IftttIntegration;
-}
+function EditIftttIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditIftttIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetIftttIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getIftttIntegration(req, (resp: GetIftttIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: IftttIntegration) => {
+ const onFinish = (obj: IftttIntegration) => {
let req = new UpdateIftttIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateIftttIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditIftttIntegration;
diff --git a/ui/src/views/applications/integrations/EditInfluxDbIntegration.tsx b/ui/src/views/applications/integrations/EditInfluxDbIntegration.tsx
index 004ae9c5..9e665fd1 100644
--- a/ui/src/views/applications/integrations/EditInfluxDbIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditInfluxDbIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import InfluxDbIntegrationForm from "./InfluxDbIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: InfluxDbIntegration;
-}
+function EditInfluxDbIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditInfluxDbIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetInfluxDbIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getInfluxDbIntegration(req, (resp: GetInfluxDbIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: InfluxDbIntegration) => {
+ const onFinish = (obj: InfluxDbIntegration) => {
let req = new UpdateInfluxDbIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateInfluxDbIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditInfluxDbIntegration;
diff --git a/ui/src/views/applications/integrations/EditLoRaCloudIntegration.tsx b/ui/src/views/applications/integrations/EditLoRaCloudIntegration.tsx
index 56b8c174..cb454c5f 100644
--- a/ui/src/views/applications/integrations/EditLoRaCloudIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditLoRaCloudIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: LoraCloudIntegration;
-}
+function EditLoRaCloudIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditLoRaCloudIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetLoraCloudIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getLoraCloudIntegration(req, (resp: GetLoraCloudIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: LoraCloudIntegration) => {
+ const onFinish = (obj: LoraCloudIntegration) => {
let req = new UpdateLoraCloudIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateLoraCloudIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditLoRaCloudIntegration;
diff --git a/ui/src/views/applications/integrations/EditMyDevicesIntegration.tsx b/ui/src/views/applications/integrations/EditMyDevicesIntegration.tsx
index e78b77b1..35ad7722 100644
--- a/ui/src/views/applications/integrations/EditMyDevicesIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditMyDevicesIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: MyDevicesIntegration;
-}
+function EditMyDevicesIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditMyDevicesIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetMyDevicesIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getMyDevicesIntegration(req, (resp: GetMyDevicesIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: MyDevicesIntegration) => {
+ const onFinish = (obj: MyDevicesIntegration) => {
let req = new UpdateMyDevicesIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateMyDevicesIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditMyDevicesIntegration;
diff --git a/ui/src/views/applications/integrations/EditPilotThingsIntegration.tsx b/ui/src/views/applications/integrations/EditPilotThingsIntegration.tsx
index 40dd6da3..0e7005a3 100644
--- a/ui/src/views/applications/integrations/EditPilotThingsIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditPilotThingsIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: PilotThingsIntegration;
-}
+function EditPilotThingsIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditPilotThingsIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetPilotThingsIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getPilotThingsIntegration(req, (resp: GetPilotThingsIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: PilotThingsIntegration) => {
+ const onFinish = (obj: PilotThingsIntegration) => {
let req = new UpdatePilotThingsIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updatePilotThingsIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditPilotThingsIntegration;
diff --git a/ui/src/views/applications/integrations/EditThingsBoardIntegration.tsx b/ui/src/views/applications/integrations/EditThingsBoardIntegration.tsx
index 71b46359..ff689f89 100644
--- a/ui/src/views/applications/integrations/EditThingsBoardIntegration.tsx
+++ b/ui/src/views/applications/integrations/EditThingsBoardIntegration.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Card } from "antd";
@@ -14,53 +14,41 @@ import {
import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
application: Application;
}
-interface IState {
- integration?: ThingsBoardIntegration;
-}
+function EditThingsBoardIntegration(props: IProps) {
+ const navigate = useNavigate();
+ const [integration, setIntegration] = useState(undefined);
-class EditThingsBoardIntegration extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetThingsBoardIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.getThingsBoardIntegration(req, (resp: GetThingsBoardIntegrationResponse) => {
- this.setState({
- integration: resp.getIntegration(),
- });
+ setIntegration(resp.getIntegration());
});
- }
+ }, [props]);
- onFinish = (obj: ThingsBoardIntegration) => {
+ const onFinish = (obj: ThingsBoardIntegration) => {
let req = new UpdateThingsBoardIntegrationRequest();
req.setIntegration(obj);
ApplicationStore.updateThingsBoardIntegration(req, () => {
- this.props.history.push(
- `/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
- );
+ navigate(`/tenants/${props.application.getTenantId()}/applications/${props.application.getId()}/integrations`);
});
};
- render() {
- if (this.state.integration === undefined) {
- return null;
- }
-
- return (
-
-
-
- );
+ if (integration === undefined) {
+ return null;
}
+
+ return (
+
+
+
+ );
}
export default EditThingsBoardIntegration;
diff --git a/ui/src/views/applications/integrations/GcpPubSubCard.tsx b/ui/src/views/applications/integrations/GcpPubSubCard.tsx
index dfb65030..ff021ae8 100644
--- a/ui/src/views/applications/integrations/GcpPubSubCard.tsx
+++ b/ui/src/views/applications/integrations/GcpPubSubCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class GcpPubSubCard extends Component {
- onDelete = () => {
+function GcpPubSubCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteGcpPubSubIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteGcpPubSubIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default GcpPubSubCard;
diff --git a/ui/src/views/applications/integrations/GcpPubSubIntegrationForm.tsx b/ui/src/views/applications/integrations/GcpPubSubIntegrationForm.tsx
index 7e8ffcaf..0ae282f5 100644
--- a/ui/src/views/applications/integrations/GcpPubSubIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/GcpPubSubIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button, Select } from "antd";
import { GcpPubSubIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -9,9 +7,9 @@ interface IProps {
onFinish: (obj: GcpPubSubIntegration) => void;
}
-class GcpPubSubIntegrationForm extends Component {
- onFinish = (values: GcpPubSubIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function GcpPubSubIntegrationForm(props: IProps) {
+ const onFinish = (values: GcpPubSubIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new GcpPubSubIntegration();
i.setApplicationId(v.applicationId);
@@ -20,52 +18,55 @@ class GcpPubSubIntegrationForm extends Component {
i.setTopicName(v.topicName);
i.setCredentialsFile(v.credentialsFile);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
- JSON
- Protobuf (binary)
-
-
-
-
-
-
-
-
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+ JSON
+ Protobuf (binary)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default GcpPubSubIntegrationForm;
diff --git a/ui/src/views/applications/integrations/GenerateMqttCertificate.tsx b/ui/src/views/applications/integrations/GenerateMqttCertificate.tsx
index 13a32693..125d9fc0 100644
--- a/ui/src/views/applications/integrations/GenerateMqttCertificate.tsx
+++ b/ui/src/views/applications/integrations/GenerateMqttCertificate.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import moment from "moment";
import { Card, Button, Form, Input } from "antd";
@@ -15,39 +15,27 @@ interface IProps {
application: Application;
}
-interface IState {
- certificate?: GenerateMqttIntegrationClientCertificateResponse;
- buttonDisabled: boolean;
-}
+function GenerateMqttCertificate(props: IProps) {
+ const [certificate, setCertificate] = useState(
+ undefined,
+ );
+ const [buttonDisabled, setButtonDisabled] = useState(false);
-class GenerateMqttCertificate extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- certificate: undefined,
- buttonDisabled: false,
- };
- }
-
- requestCertificate = () => {
- this.setState({
- buttonDisabled: true,
- });
+ const requestCertificate = () => {
+ setButtonDisabled(true);
let req = new GenerateMqttIntegrationClientCertificateRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.generateMqttIntegrationClientCertificate(
req,
(resp: GenerateMqttIntegrationClientCertificateResponse) => {
- this.setState({
- certificate: resp,
- });
+ setCertificate(resp);
},
);
};
- renderRequest = () => {
+ const renderRequest = () => {
return (
@@ -59,7 +47,7 @@ class GenerateMqttCertificate extends Component {
-
+
Generate certificate
@@ -67,14 +55,14 @@ class GenerateMqttCertificate extends Component {
);
};
- renderResponse = () => {
- const certificate = this.state.certificate!;
+ const renderResponse = () => {
+ const cert = certificate!;
const initial = {
- expiresAt: moment(certificate.getExpiresAt()!.toDate()!).format("YYYY-MM-DD HH:mm:ss"),
- caCert: certificate.getCaCert(),
- tlsCert: certificate.getTlsCert(),
- tlsKey: certificate.getTlsKey(),
+ expiresAt: moment(cert.getExpiresAt()!.toDate()!).format("YYYY-MM-DD HH:mm:ss"),
+ caCert: cert.getCaCert(),
+ tlsCert: cert.getTlsCert(),
+ tlsKey: cert.getTlsKey(),
};
return (
@@ -103,15 +91,13 @@ class GenerateMqttCertificate extends Component {
);
};
- render() {
- let content = this.renderRequest();
+ let content = renderRequest();
- if (this.state.certificate !== undefined) {
- content = this.renderResponse();
- }
-
- return {content} ;
+ if (certificate !== undefined) {
+ content = renderResponse();
}
+
+ return {content} ;
}
export default GenerateMqttCertificate;
diff --git a/ui/src/views/applications/integrations/HttpCard.tsx b/ui/src/views/applications/integrations/HttpCard.tsx
index e78ee6b2..b045e300 100644
--- a/ui/src/views/applications/integrations/HttpCard.tsx
+++ b/ui/src/views/applications/integrations/HttpCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class HttpCard extends Component {
- onDelete = () => {
+function HttpCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteHttpIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteHttpIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default HttpCard;
diff --git a/ui/src/views/applications/integrations/HttpIntegrationForm.tsx b/ui/src/views/applications/integrations/HttpIntegrationForm.tsx
index 5407da29..72613820 100644
--- a/ui/src/views/applications/integrations/HttpIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/HttpIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button, Select, Row, Col, Typography, Space } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
@@ -10,9 +8,9 @@ interface IProps {
onFinish: (obj: HttpIntegration) => void;
}
-class HttpIntegrationForm extends Component {
- onFinish = (values: HttpIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function HttpIntegrationForm(props: IProps) {
+ const onFinish = (values: HttpIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new HttpIntegration();
i.setApplicationId(v.applicationId);
@@ -24,79 +22,77 @@ class HttpIntegrationForm extends Component {
i.getHeadersMap().set(elm[0], elm[1]);
}
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
- JSON
- Protobuf (binary)
-
-
-
-
-
-
- Headers
-
- {(fields, { add, remove }) => (
- <>
- {fields.map(({ key, name, ...restField }) => (
-
-
-
-
-
-
-
-
-
-
-
-
- remove(name)} />
-
-
- ))}
-
- add()} block icon={ }>
- Add header
-
-
- >
- )}
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+ JSON
+ Protobuf (binary)
+
+
+
+
+
+
+ Headers
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ remove(name)} />
+
+
+ ))}
+
+ add()} block icon={ }>
+ Add header
+
+
+ >
+ )}
+
+
+
+
+ Submit
+
+
+
+ );
}
export default HttpIntegrationForm;
diff --git a/ui/src/views/applications/integrations/IftttCard.tsx b/ui/src/views/applications/integrations/IftttCard.tsx
index fc6ac4db..8b2e656d 100644
--- a/ui/src/views/applications/integrations/IftttCard.tsx
+++ b/ui/src/views/applications/integrations/IftttCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class IftttCard extends Component {
- onDelete = () => {
+function IftttCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteIftttIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteIftttIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default IftttCard;
diff --git a/ui/src/views/applications/integrations/IftttIntegrationForm.tsx b/ui/src/views/applications/integrations/IftttIntegrationForm.tsx
index a238ceff..15525e9b 100644
--- a/ui/src/views/applications/integrations/IftttIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/IftttIntegrationForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Form, Input, AutoComplete, Button, Row, Col, Switch } from "antd";
@@ -10,29 +10,15 @@ interface IProps {
onFinish: (obj: IftttIntegration) => void;
}
-interface IState {
- arbitraryJson: boolean;
-}
+function IftttIntegrationForm(props: IProps) {
+ const [arbitraryJson, setArbitraryJson] = useState(false);
-class IftttIntegrationForm extends Component {
- constructor(props: IProps) {
- super(props);
+ useEffect(() => {
+ setArbitraryJson(props.initialValues.getArbitraryJson());
+ }, [props]);
- this.state = {
- arbitraryJson: false,
- };
- }
-
- componentDidMount() {
- const v = this.props.initialValues;
-
- this.setState({
- arbitraryJson: v.getArbitraryJson(),
- });
- }
-
- onFinish = (values: IftttIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: IftttIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new IftttIntegration();
i.setApplicationId(v.applicationId);
@@ -41,55 +27,58 @@ class IftttIntegrationForm extends Component {
i.setArbitraryJson(v.arbitraryJson);
i.setUplinkValuesList(v.uplinkValuesList);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- onArbitraryJsonChange = (checked: boolean) => {
- this.setState({
- arbitraryJson: checked,
- });
- }
+ const onArbitraryJsonChange = (checked: boolean) => {
+ setArbitraryJson(checked);
+ };
- render() {
- const options: {
- value: string;
- }[] = this.props.measurementKeys.map(v => {
- return { value: v };
- });
+ const options: {
+ value: string;
+ }[] = props.measurementKeys.map(v => {
+ return { value: v };
+ });
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!this.state.arbitraryJson &&
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!arbitraryJson && (
+
{fields => (
{fields.map((field, i) => (
@@ -105,15 +94,15 @@ class IftttIntegrationForm extends Component {
))}
)}
- }
-
-
- Submit
-
-
-
- );
- }
+
+ )}
+
+
+ Submit
+
+
+
+ );
}
export default IftttIntegrationForm;
diff --git a/ui/src/views/applications/integrations/InfluxDbIntegrationForm.tsx b/ui/src/views/applications/integrations/InfluxDbIntegrationForm.tsx
index 91eef013..5fd79eef 100644
--- a/ui/src/views/applications/integrations/InfluxDbIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/InfluxDbIntegrationForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import { Form, Input, Button, Select } from "antd";
@@ -13,20 +13,11 @@ interface IProps {
onFinish: (obj: InfluxDbIntegration) => void;
}
-interface IState {
- selectedVersion: InfluxDbVersion;
-}
+function InfluxDbIntegrationForm(props: IProps) {
+ const [selectedVersion, setSelectedVersion] = useState(InfluxDbVersion.INFLUXDB_1);
-class InfluxDbIntegrationForm extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- selectedVersion: InfluxDbVersion.INFLUXDB_1,
- };
- }
-
- onFinish = (values: InfluxDbIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: InfluxDbIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new InfluxDbIntegration();
i.setApplicationId(v.applicationId);
@@ -41,98 +32,90 @@ class InfluxDbIntegrationForm extends Component {
i.setBucket(v.bucket);
i.setToken(v.token);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- onVersionChange = (version: InfluxDbVersion) => {
- this.setState({
- selectedVersion: version,
- });
+ const onVersionChange = (version: InfluxDbVersion) => {
+ setSelectedVersion(version);
};
- render() {
- return (
-
+
+ InfluxDB v1
+ InfluxDB v2
+
+
+
+
+
+ {selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
+
+
+
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
+
+
+
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
+
+
+
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
- InfluxDB v1
- InfluxDB v2
+
+
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
+
+
+ Nanosecond
+ Microsecond
+ Millisecond
+ Second
+ Minute
+ Hour
-
-
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
+
+
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
-
-
- Nanosecond
- Microsecond
- Millisecond
- Second
- Minute
- Hour
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
-
-
-
- )}
- {this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
-
-
-
- )}
-
-
- Submit
-
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
+
+
-
- );
- }
+ )}
+ {selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
+
+
+
+ )}
+
+
+ Submit
+
+
+
+ );
}
export default InfluxDbIntegrationForm;
diff --git a/ui/src/views/applications/integrations/InfluxdbCard.tsx b/ui/src/views/applications/integrations/InfluxdbCard.tsx
index 088605f9..438934aa 100644
--- a/ui/src/views/applications/integrations/InfluxdbCard.tsx
+++ b/ui/src/views/applications/integrations/InfluxdbCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class InfluxdbCard extends Component {
- onDelete = () => {
+function InfluxdbCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteInfluxDbIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteInfluxDbIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default InfluxdbCard;
diff --git a/ui/src/views/applications/integrations/LoRaCloudCard.tsx b/ui/src/views/applications/integrations/LoRaCloudCard.tsx
index 53ee0496..ddf605f4 100644
--- a/ui/src/views/applications/integrations/LoRaCloudCard.tsx
+++ b/ui/src/views/applications/integrations/LoRaCloudCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class LoRaCloudCard extends Component {
- onDelete = () => {
+function LoRaCloudCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteLoraCloudIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteLoraCloudIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default LoRaCloudCard;
diff --git a/ui/src/views/applications/integrations/LoRaCloudIntegrationForm.tsx b/ui/src/views/applications/integrations/LoRaCloudIntegrationForm.tsx
index c5d0bf1b..43fc9f36 100644
--- a/ui/src/views/applications/integrations/LoRaCloudIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/LoRaCloudIntegrationForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Form, Input, InputNumber, Switch, Button, Tabs, Collapse } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
@@ -13,43 +13,28 @@ interface IProps {
onFinish: (obj: LoraCloudIntegration) => void;
}
-interface IState {
- modemEnabled: boolean;
- geolocationTdoa: boolean;
- geolocationRssi: boolean;
- geolocationWifi: boolean;
- geolocationGnss: boolean;
-}
+function LoRaCloudIntegrationForm(props: IProps) {
+ const [modemEnabled, setModemEnabled] = useState(false);
+ const [geolocationTdoa, setGeolocationTdoa] = useState(false);
+ const [geolocationRssi, setGeolocationRssi] = useState(false);
+ const [geolocationWifi, setGeolocationWifi] = useState(false);
+ const [geolocationGnss, setGeolocationGnss] = useState(false);
-class LoRaCloudIntegrationForm extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- modemEnabled: false,
- geolocationTdoa: false,
- geolocationRssi: false,
- geolocationWifi: false,
- geolocationGnss: false,
- };
- }
-
- componentDidMount() {
- const v = this.props.initialValues;
+ useEffect(() => {
+ const v = props.initialValues;
const mgs = v.getModemGeolocationServices();
if (mgs !== undefined) {
- this.setState({
- modemEnabled: mgs.getModemEnabled(),
- geolocationTdoa: mgs.getGeolocationTdoa(),
- geolocationRssi: mgs.getGeolocationRssi(),
- geolocationWifi: mgs.getGeolocationWifi(),
- geolocationGnss: mgs.getGeolocationGnss(),
- });
+ setModemEnabled(mgs.getModemEnabled());
+ setGeolocationTdoa(mgs.getGeolocationTdoa());
+ setGeolocationRssi(mgs.getGeolocationRssi());
+ setGeolocationWifi(mgs.getGeolocationWifi());
+ setGeolocationGnss(mgs.getGeolocationGnss());
}
- }
+ }, [props]);
- onFinish = (values: LoraCloudIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: LoraCloudIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
const mgsv = v.modemGeolocationServices;
let mgs = new LoraCloudModemGeolocationServices();
@@ -76,202 +61,194 @@ class LoRaCloudIntegrationForm extends Component {
i.setApplicationId(v.applicationId);
i.setModemGeolocationServices(mgs);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- onModemEnabledChange = (v: boolean) => {
- this.setState({
- modemEnabled: v,
- });
+ const onModemEnabledChange = (v: boolean) => {
+ setModemEnabled(v);
};
- onGeolocationTdoaChange = (v: boolean) => {
- this.setState({
- geolocationTdoa: v,
- });
+ const onGeolocationTdoaChange = (v: boolean) => {
+ setGeolocationTdoa(v);
};
- onGeolocationRssiChange = (v: boolean) => {
- this.setState({
- geolocationRssi: v,
- });
+ const onGeolocationRssiChange = (v: boolean) => {
+ setGeolocationRssi(v);
};
- onGeolocationWifiChange = (v: boolean) => {
- this.setState({
- geolocationWifi: v,
- });
+ const onGeolocationWifiChange = (v: boolean) => {
+ setGeolocationWifi(v);
};
- onGeolocationGnssChange = (v: boolean) => {
- this.setState({
- geolocationGnss: v,
- });
+ const onGeolocationGnssChange = (v: boolean) => {
+ setGeolocationGnss(v);
};
- render() {
- return (
-
- );
- }
+ )}
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default LoRaCloudIntegrationForm;
diff --git a/ui/src/views/applications/integrations/MqttCard.tsx b/ui/src/views/applications/integrations/MqttCard.tsx
index ec6d0c62..ae3815a4 100644
--- a/ui/src/views/applications/integrations/MqttCard.tsx
+++ b/ui/src/views/applications/integrations/MqttCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card } from "antd";
@@ -9,23 +8,21 @@ interface IProps {
application: Application;
}
-class HttpCard extends Component {
- render() {
- let actions: any[] = [ Get certificate];
+function MqttCard(props: IProps) {
+ let actions: any[] = [ Get certificate];
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
- }
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
-export default HttpCard;
+export default MqttCard;
diff --git a/ui/src/views/applications/integrations/MyDevicesCard.tsx b/ui/src/views/applications/integrations/MyDevicesCard.tsx
index 9102dc67..34e3e327 100644
--- a/ui/src/views/applications/integrations/MyDevicesCard.tsx
+++ b/ui/src/views/applications/integrations/MyDevicesCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -13,46 +12,44 @@ interface IProps {
add?: boolean;
}
-class MyDevicesCard extends Component {
- onDelete = () => {
+function MyDevicesCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteMyDevicesIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteMyDevicesIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default MyDevicesCard;
diff --git a/ui/src/views/applications/integrations/MyDevicesIntegrationForm.tsx b/ui/src/views/applications/integrations/MyDevicesIntegrationForm.tsx
index 99c46a98..7bb671c5 100644
--- a/ui/src/views/applications/integrations/MyDevicesIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/MyDevicesIntegrationForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState } from "react";
import { Form, Input, Button, Select } from "antd";
@@ -9,79 +9,63 @@ interface IProps {
onFinish: (obj: MyDevicesIntegration) => void;
}
-interface IState {
- selectedEndpoint: string;
- customEndpoint: string;
-}
+function MyDevicesIntegrationForm(props: IProps) {
+ const [selectedEndpoint, setSelectedEndpoint] = useState("");
+ const [customEndpoint, setCustomEndpoint] = useState("");
-class MyDevicesIntegrationForm extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- selectedEndpoint: "",
- customEndpoint: "",
- };
- }
-
- onFinish = (values: MyDevicesIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: MyDevicesIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new MyDevicesIntegration();
i.setApplicationId(v.applicationId);
if (v.endpoint === "custom") {
- i.setEndpoint(this.state.customEndpoint);
+ i.setEndpoint(customEndpoint);
} else {
i.setEndpoint(v.endpoint);
}
- this.props.onFinish(i);
+ props.onFinish(i);
};
- onEndpointChange = (v: string) => {
- this.setState({
- selectedEndpoint: v,
- });
+ const onEndpointChange = (v: string) => {
+ setSelectedEndpoint(v);
};
- onCustomEndpointChange = (e: React.ChangeEvent) => {
- this.setState({
- customEndpoint: e.target.value,
- });
+ const onCustomEndpointChange = (e: React.ChangeEvent) => {
+ setCustomEndpoint(e.target.value);
};
- render() {
- return (
-
+ return (
+
+
+
+ Cayenne
+
+ IoT in a Box
+
+ Custom endpoint URL
+
+
+ {selectedEndpoint === "custom" && (
-
- Cayenne
-
- IoT in a Box
-
- Custom endpoint URL
-
+
- {this.state.selectedEndpoint === "custom" && (
-
-
-
- )}
-
-
- Submit
-
-
-
- );
- }
+ )}
+
+
+ Submit
+
+
+
+ );
}
export default MyDevicesIntegrationForm;
diff --git a/ui/src/views/applications/integrations/PilotThingsCard.tsx b/ui/src/views/applications/integrations/PilotThingsCard.tsx
index ba0fd694..08e32136 100644
--- a/ui/src/views/applications/integrations/PilotThingsCard.tsx
+++ b/ui/src/views/applications/integrations/PilotThingsCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -16,46 +15,44 @@ interface IProps {
add?: boolean;
}
-class PilotThingsCard extends Component {
- onDelete = () => {
+function PilotThingsCard(props: IProps) {
+ const onDelete = () => {
let req = new DeletePilotThingsIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deletePilotThingsIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default PilotThingsCard;
diff --git a/ui/src/views/applications/integrations/PilotThingsIntegrationForm.tsx b/ui/src/views/applications/integrations/PilotThingsIntegrationForm.tsx
index 5550a459..06e0a63c 100644
--- a/ui/src/views/applications/integrations/PilotThingsIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/PilotThingsIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button } from "antd";
import { PilotThingsIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -9,43 +7,41 @@ interface IProps {
onFinish: (obj: PilotThingsIntegration) => void;
}
-class PilotThingsIntegrationForm extends Component {
- onFinish = (values: PilotThingsIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function PilotThingsIntegrationForm(props: IProps) {
+ const onFinish = (values: PilotThingsIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new PilotThingsIntegration();
i.setApplicationId(v.applicationId);
i.setServer(v.server);
i.setToken(v.token);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
-
-
-
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+
+
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default PilotThingsIntegrationForm;
diff --git a/ui/src/views/applications/integrations/ThingsBoardCard.tsx b/ui/src/views/applications/integrations/ThingsBoardCard.tsx
index aac2f41a..e0ea07a2 100644
--- a/ui/src/views/applications/integrations/ThingsBoardCard.tsx
+++ b/ui/src/views/applications/integrations/ThingsBoardCard.tsx
@@ -1,4 +1,3 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from "antd";
@@ -16,46 +15,44 @@ interface IProps {
add?: boolean;
}
-class ThingsBoardCard extends Component {
- onDelete = () => {
+function ThingsBoardCard(props: IProps) {
+ const onDelete = () => {
let req = new DeleteThingsBoardIntegrationRequest();
- req.setApplicationId(this.props.application.getId());
+ req.setApplicationId(props.application.getId());
ApplicationStore.deleteThingsBoardIntegration(req, () => {});
};
- render() {
- let actions: any[] = [];
+ let actions: any[] = [];
- if (!!this.props.add) {
- actions = [
-
-
- ,
- ];
- } else {
- actions = [
-
-
- ,
-
-
- ,
- ];
- }
-
- return (
-
- }
- actions={actions}
- >
-
-
-
- );
+ if (!!props.add) {
+ actions = [
+
+
+ ,
+ ];
+ } else {
+ actions = [
+
+
+ ,
+
+
+ ,
+ ];
}
+
+ return (
+
+ }
+ actions={actions}
+ >
+
+
+
+ );
}
export default ThingsBoardCard;
diff --git a/ui/src/views/applications/integrations/ThingsBoardIntegrationForm.tsx b/ui/src/views/applications/integrations/ThingsBoardIntegrationForm.tsx
index 90018644..aec99b5f 100644
--- a/ui/src/views/applications/integrations/ThingsBoardIntegrationForm.tsx
+++ b/ui/src/views/applications/integrations/ThingsBoardIntegrationForm.tsx
@@ -1,5 +1,3 @@
-import React, { Component } from "react";
-
import { Form, Input, Button, Typography } from "antd";
import { ThingsBoardIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -9,41 +7,44 @@ interface IProps {
onFinish: (obj: ThingsBoardIntegration) => void;
}
-class ThingsBoardIntegrationForm extends Component {
- onFinish = (values: ThingsBoardIntegration.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+function ThingsBoardIntegrationForm(props: IProps) {
+ const onFinish = (values: ThingsBoardIntegration.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let i = new ThingsBoardIntegration();
i.setApplicationId(v.applicationId);
i.setServer(v.server);
- this.props.onFinish(i);
+ props.onFinish(i);
};
- render() {
- return (
-
-
-
-
-
-
- Each device must have a 'ThingsBoardAccessToken' variable assigned. This access-token is generated by
- ThingsBoard.
-
-
-
-
- Submit
-
-
-
- );
- }
+ return (
+
+
+
+
+
+
+ Each device must have a 'ThingsBoardAccessToken' variable assigned. This access-token is generated by
+ ThingsBoard.
+
+
+
+
+ Submit
+
+
+
+ );
}
export default ThingsBoardIntegrationForm;
diff --git a/ui/src/views/dashboard/Dashboard.tsx b/ui/src/views/dashboard/Dashboard.tsx
index b330e52c..f891cd01 100644
--- a/ui/src/views/dashboard/Dashboard.tsx
+++ b/ui/src/views/dashboard/Dashboard.tsx
@@ -1,8 +1,9 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { presetPalettes } from "@ant-design/colors";
-import { Space, Breadcrumb, Card, Row, Col, PageHeader, Empty } from "antd";
+import { Space, Breadcrumb, Card, Row, Col, Empty } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import moment from "moment";
import { LatLngTuple, PointTuple } from "leaflet";
@@ -27,167 +28,125 @@ import InternalStore from "../../stores/InternalStore";
import GatewayStore from "../../stores/GatewayStore";
import Map, { Marker, MarkerColor } from "../../components/Map";
-interface GatewaysMapState {
- items: GatewayListItem[];
-}
+function GatewaysMap() {
+ const [items, setItems] = useState([]);
-class GatewaysMap extends Component<{}, GatewaysMapState> {
- constructor(props: {}) {
- super(props);
-
- this.state = {
- items: [],
- };
- }
-
- componentDidMount() {
- this.loadData();
- }
-
- loadData = () => {
+ useEffect(() => {
let req = new ListGatewaysRequest();
req.setLimit(9999);
GatewayStore.list(req, (resp: ListGatewaysResponse) => {
- this.setState({
- items: resp.getResultList(),
- });
+ setItems(resp.getResultList());
});
+ }, []);
+
+ if (items.length === 0) {
+ return ;
+ }
+
+ const boundsOptions: {
+ padding: PointTuple;
+ } = {
+ padding: [50, 50],
};
- render() {
- if (this.state.items.length === 0) {
- return ;
+ let bounds: LatLngTuple[] = [];
+ let markers: any[] = [];
+
+ for (const item of items) {
+ const pos: LatLngTuple = [item.getLocation()!.getLatitude(), item.getLocation()!.getLongitude()];
+ bounds.push(pos);
+
+ let color: MarkerColor = "orange";
+ let lastSeen: string = "Never seen online";
+
+ if (item.getState() === GatewayState.OFFLINE) {
+ color = "red";
+ } else if (item.getState() === GatewayState.ONLINE) {
+ color = "green";
}
- const boundsOptions: {
- padding: PointTuple;
- } = {
- padding: [50, 50],
- };
-
- let bounds: LatLngTuple[] = [];
- let markers: any[] = [];
-
- for (const item of this.state.items) {
- const pos: LatLngTuple = [item.getLocation()!.getLatitude(), item.getLocation()!.getLongitude()];
- bounds.push(pos);
-
- let color: MarkerColor = "orange";
- let lastSeen: string = "Never seen online";
-
- if (item.getState() === GatewayState.OFFLINE) {
- color = "red";
- } else if (item.getState() === GatewayState.ONLINE) {
- color = "green";
- }
-
- if (item.getLastSeenAt() !== undefined) {
- let ts = moment(item.getLastSeenAt()!.toDate());
- lastSeen = ts.fromNow();
- }
-
- markers.push(
-
-
- {item.getName()}
-
- {item.getGatewayId()}
-
-
- {lastSeen}
-
- ,
- );
+ if (item.getLastSeenAt() !== undefined) {
+ let ts = moment(item.getLastSeenAt()!.toDate());
+ lastSeen = ts.fromNow();
}
- return (
-
- {markers}
-
+ markers.push(
+
+
+ {item.getName()}
+
+ {item.getGatewayId()}
+
+
+ {lastSeen}
+
+ ,
);
}
+
+ return (
+
+ {markers}
+
+ );
}
-interface GatewayProps {
- summary?: GetGatewaysSummaryResponse;
-}
-
-class GatewaysActiveInactive extends Component {
- render() {
- if (
- this.props.summary === undefined ||
- (this.props.summary.getNeverSeenCount() === 0 &&
- this.props.summary.getOfflineCount() === 0 &&
- this.props.summary.getOnlineCount() === 0)
- ) {
- return ;
- }
-
- const data = {
- labels: ["Never seen", "Offline", "Online"],
- datasets: [
- {
- data: [
- this.props.summary.getNeverSeenCount(),
- this.props.summary.getOfflineCount(),
- this.props.summary.getOnlineCount(),
- ],
- backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
- },
- ],
- };
-
- const options: {
- animation: false;
- } = {
- animation: false,
- };
-
- return ;
+function DevicesActiveInactive({ summary }: { summary?: GetDevicesSummaryResponse }) {
+ if (
+ summary === undefined ||
+ (summary.getNeverSeenCount() === 0 && summary.getInactiveCount() === 0 && summary.getActiveCount() === 0)
+ ) {
+ return ;
}
+
+ const data = {
+ labels: ["Never seen", "Inactive", "Active"],
+ datasets: [
+ {
+ data: [summary.getNeverSeenCount(), summary.getInactiveCount(), summary.getActiveCount()],
+ backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
+ },
+ ],
+ };
+
+ const options: {
+ animation: false;
+ } = {
+ animation: false,
+ };
+
+ return ;
}
-interface DeviceProps {
- summary?: GetDevicesSummaryResponse;
-}
-
-class DevicesActiveInactive extends Component {
- render() {
- if (
- this.props.summary === undefined ||
- (this.props.summary.getNeverSeenCount() === 0 &&
- this.props.summary.getInactiveCount() === 0 &&
- this.props.summary.getActiveCount() === 0)
- ) {
- return ;
- }
-
- const data = {
- labels: ["Never seen", "Inactive", "Active"],
- datasets: [
- {
- data: [
- this.props.summary.getNeverSeenCount(),
- this.props.summary.getInactiveCount(),
- this.props.summary.getActiveCount(),
- ],
- backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
- },
- ],
- };
-
- const options: {
- animation: false;
- } = {
- animation: false,
- };
-
- return ;
+function GatewaysActiveInactive({ summary }: { summary?: GetGatewaysSummaryResponse }) {
+ if (
+ summary === undefined ||
+ (summary.getNeverSeenCount() === 0 && summary.getOfflineCount() === 0 && summary.getOnlineCount() === 0)
+ ) {
+ return ;
}
+
+ const data = {
+ labels: ["Never seen", "Offline", "Online"],
+ datasets: [
+ {
+ data: [summary.getNeverSeenCount(), summary.getOfflineCount(), summary.getOnlineCount()],
+ backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
+ },
+ ],
+ };
+
+ const options: {
+ animation: false;
+ } = {
+ animation: false,
+ };
+
+ return ;
}
-class DevicesDataRates extends Component {
- getColor = (dr: number) => {
+function DevicesDataRates({ summary }: { summary?: GetDevicesSummaryResponse }) {
+ const getColor = (dr: number) => {
return [
"#ff5722",
"#ff9800",
@@ -207,109 +166,92 @@ class DevicesDataRates extends Component {
][dr];
};
- render() {
- if (this.props.summary === undefined || this.props.summary.getDrCountMap().toArray().length === 0) {
- return ;
- }
-
- let data: {
- labels: string[];
- datasets: {
- data: number[];
- backgroundColor: string[];
- }[];
- } = {
- labels: [],
- datasets: [
- {
- data: [],
- backgroundColor: [],
- },
- ],
- };
-
- for (const elm of this.props.summary.getDrCountMap().toArray()) {
- data.labels.push(`DR${elm[0]}`);
- data.datasets[0].data.push(elm[1]);
- data.datasets[0].backgroundColor.push(this.getColor(elm[0]));
- }
-
- const options: {
- animation: false;
- } = {
- animation: false,
- };
-
- return ;
- }
-}
-
-interface IProps {}
-
-interface IState {
- gatewaysSummary?: GetGatewaysSummaryResponse;
- devicesSummary?: GetDevicesSummaryResponse;
-}
-
-class Dashboard extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
+ if (summary === undefined || summary.getDrCountMap().toArray().length === 0) {
+ return ;
}
- componentDidMount() {
+ let data: {
+ labels: string[];
+ datasets: {
+ data: number[];
+ backgroundColor: string[];
+ }[];
+ } = {
+ labels: [],
+ datasets: [
+ {
+ data: [],
+ backgroundColor: [],
+ },
+ ],
+ };
+
+ for (const elm of summary.getDrCountMap().toArray()) {
+ data.labels.push(`DR${elm[0]}`);
+ data.datasets[0].data.push(elm[1]);
+ data.datasets[0].backgroundColor.push(getColor(elm[0]));
+ }
+
+ const options: {
+ animation: false;
+ } = {
+ animation: false,
+ };
+
+ return ;
+}
+
+function Dashboard() {
+ const [gatewaysSummary, setGatewaysSummary] = useState(undefined);
+ const [devicesSummary, setDevicesSummary] = useState(undefined);
+
+ useEffect(() => {
InternalStore.getGatewaysSummary(new GetGatewaysSummaryRequest(), (resp: GetGatewaysSummaryResponse) => {
- this.setState({
- gatewaysSummary: resp,
- });
+ setGatewaysSummary(resp);
});
InternalStore.getDevicesSummary(new GetDevicesSummaryRequest(), (resp: GetDevicesSummaryResponse) => {
- this.setState({
- devicesSummary: resp,
- });
+ setDevicesSummary(resp);
});
- }
+ }, []);
- render() {
- return (
-
- (
-
-
- Network Server
-
-
- Dashboard
-
-
- )}
- title="Dashboard"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
+ return (
+
+ (
+
+
+ Network Server
+
+
+ Dashboard
+
+
+ )}
+ title="Dashboard"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
}
export default Dashboard;
diff --git a/ui/src/views/device-profile-templates/CreateDeviceProfileTemplate.tsx b/ui/src/views/device-profile-templates/CreateDeviceProfileTemplate.tsx
index 00ee0a84..89e2878d 100644
--- a/ui/src/views/device-profile-templates/CreateDeviceProfileTemplate.tsx
+++ b/ui/src/views/device-profile-templates/CreateDeviceProfileTemplate.tsx
@@ -1,7 +1,7 @@
-import React, { Component } from "react";
-import { Link, RouteComponentProps } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { MacVersion, RegParamsRevision } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
import {
@@ -12,86 +12,86 @@ import {
import DeviceProfileTemplateForm from "./DeviceProfileTemplateForm";
import DeviceProfileTemplateStore from "../../stores/DeviceProfileTemplateStore";
-class CreateDeviceProfileTemplate extends Component {
- onFinish = (obj: DeviceProfileTemplate) => {
+function CreateDeviceProfileTemplate() {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: DeviceProfileTemplate) => {
let req = new CreateDeviceProfileTemplateRequest();
req.setDeviceProfileTemplate(obj);
DeviceProfileTemplateStore.create(req, () => {
- this.props.history.push(`/device-profile-templates`);
+ navigate(`/device-profile-templates`);
});
};
- render() {
- const codecScript = `// Decode uplink function.
-//
-// Input is an object with the following fields:
-// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
-// - fPort = Uplink fPort.
-// - variables = Object containing the configured device variables.
-//
-// Output must be an object with the following fields:
-// - data = Object representing the decoded payload.
-function decodeUplink(input) {
- return {
- data: {
- temp: 22.5
- }
- };
-}
-
-// Encode downlink function.
-//
-// Input is an object with the following fields:
-// - data = Object representing the payload that must be encoded.
-// - variables = Object containing the configured device variables.
-//
-// Output must be an object with the following fields:
-// - bytes = Byte array containing the downlink payload.
-function encodeDownlink(input) {
- return {
- bytes: [225, 230, 255, 0]
- };
-}
-`;
-
- let deviceProfileTemplate = new DeviceProfileTemplate();
- deviceProfileTemplate.setPayloadCodecScript(codecScript);
- deviceProfileTemplate.setSupportsOtaa(true);
- deviceProfileTemplate.setUplinkInterval(3600);
- deviceProfileTemplate.setDeviceStatusReqInterval(1);
- deviceProfileTemplate.setAdrAlgorithmId("default");
- deviceProfileTemplate.setMacVersion(MacVersion.LORAWAN_1_0_3);
- deviceProfileTemplate.setRegParamsRevision(RegParamsRevision.A);
- deviceProfileTemplate.setFlushQueueOnActivate(true);
- deviceProfileTemplate.setAutoDetectMeasurements(true);
-
- return (
-
- (
-
-
- Network Server
-
-
-
- Device-profile templates
-
-
-
- Add
-
-
- )}
- title="Add device-profile template"
- />
-
-
-
-
- );
+ const codecScript = `// Decode uplink function.
+ //
+ // Input is an object with the following fields:
+ // - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
+ // - fPort = Uplink fPort.
+ // - variables = Object containing the configured device variables.
+ //
+ // Output must be an object with the following fields:
+ // - data = Object representing the decoded payload.
+ function decodeUplink(input) {
+ return {
+ data: {
+ temp: 22.5
+ }
+ };
}
+
+ // Encode downlink function.
+ //
+ // Input is an object with the following fields:
+ // - data = Object representing the payload that must be encoded.
+ // - variables = Object containing the configured device variables.
+ //
+ // Output must be an object with the following fields:
+ // - bytes = Byte array containing the downlink payload.
+ function encodeDownlink(input) {
+ return {
+ bytes: [225, 230, 255, 0]
+ };
+ }
+ `;
+
+ let deviceProfileTemplate = new DeviceProfileTemplate();
+ deviceProfileTemplate.setPayloadCodecScript(codecScript);
+ deviceProfileTemplate.setSupportsOtaa(true);
+ deviceProfileTemplate.setUplinkInterval(3600);
+ deviceProfileTemplate.setDeviceStatusReqInterval(1);
+ deviceProfileTemplate.setAdrAlgorithmId("default");
+ deviceProfileTemplate.setMacVersion(MacVersion.LORAWAN_1_0_3);
+ deviceProfileTemplate.setRegParamsRevision(RegParamsRevision.A);
+ deviceProfileTemplate.setFlushQueueOnActivate(true);
+ deviceProfileTemplate.setAutoDetectMeasurements(true);
+
+ return (
+
+ (
+
+
+ Network Server
+
+
+
+ Device-profile templates
+
+
+
+ Add
+
+
+ )}
+ title="Add device-profile template"
+ />
+
+
+
+
+ );
}
export default CreateDeviceProfileTemplate;
diff --git a/ui/src/views/device-profile-templates/DeviceProfileTemplateForm.tsx b/ui/src/views/device-profile-templates/DeviceProfileTemplateForm.tsx
index 8373556d..4eae5871 100644
--- a/ui/src/views/device-profile-templates/DeviceProfileTemplateForm.tsx
+++ b/ui/src/views/device-profile-templates/DeviceProfileTemplateForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Form, Input, Select, InputNumber, Switch, Row, Col, Button, Tabs, Card } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
@@ -17,37 +17,20 @@ interface IProps {
update?: boolean;
}
-interface IState {
- supportsOtaa: boolean;
- supportsClassB: boolean;
- supportsClassC: boolean;
- payloadCodecRuntime: CodecRuntime;
- adrAlgorithms: [string, string][];
-}
+function DeviceProfileTemplateForm(props: IProps) {
+ const [form] = Form.useForm();
+ const [supportsOtaa, setSupportsOtaa] = useState(false);
+ const [supportsClassB, setSupportsClassB] = useState(false);
+ const [supportsClassC, setSupportsClassC] = useState(false);
+ const [payloadCodecRuntime, setPayloadCodecRuntime] = useState(CodecRuntime.NONE);
+ const [adrAlgorithms, setAdrAlgorithms] = useState<[string, string][]>([]);
-class DeviceProfileTemplateForm extends Component {
- formRef = React.createRef();
-
- constructor(props: IProps) {
- super(props);
- this.state = {
- supportsOtaa: false,
- supportsClassB: false,
- supportsClassC: false,
- payloadCodecRuntime: CodecRuntime.NONE,
- adrAlgorithms: [],
- };
- }
-
- componentDidMount() {
- const v = this.props.initialValues;
-
- this.setState({
- supportsOtaa: v.getSupportsOtaa(),
- supportsClassB: v.getSupportsClassB(),
- supportsClassC: v.getSupportsClassC(),
- payloadCodecRuntime: v.getPayloadCodecRuntime(),
- });
+ useEffect(() => {
+ const v = props.initialValues;
+ setSupportsOtaa(v.getSupportsOtaa());
+ setSupportsClassB(v.getSupportsClassB());
+ setSupportsClassC(v.getSupportsClassC());
+ setPayloadCodecRuntime(v.getPayloadCodecRuntime());
DeviceProfileStore.listAdrAlgorithms((resp: ListDeviceProfileAdrAlgorithmsResponse) => {
let adrAlgorithms: [string, string][] = [];
@@ -55,14 +38,12 @@ class DeviceProfileTemplateForm extends Component {
adrAlgorithms.push([a.getId(), a.getName()]);
}
- this.setState({
- adrAlgorithms: adrAlgorithms,
- });
+ setAdrAlgorithms(adrAlgorithms);
});
- }
+ }, [props.initialValues]);
- onFinish = (values: DeviceProfileTemplate.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: DeviceProfileTemplate.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let dp = new DeviceProfileTemplate();
dp.setId(v.id);
@@ -114,454 +95,487 @@ class DeviceProfileTemplateForm extends Component {
}
dp.setAutoDetectMeasurements(v.autoDetectMeasurements);
- this.props.onFinish(dp);
+ props.onFinish(dp);
};
- onSupportsOtaaChange = (checked: boolean) => {
- this.setState({
- supportsOtaa: checked,
- });
+ const onSupportsOtaaChange = (checked: boolean) => {
+ setSupportsOtaa(checked);
};
- onSupportsClassBChnage = (checked: boolean) => {
- this.setState({
- supportsClassB: checked,
- });
+ const onSupportsClassBChnage = (checked: boolean) => {
+ setSupportsClassB(checked);
};
- onSupportsClassCChange = (checked: boolean) => {
- this.setState({
- supportsClassC: checked,
- });
+ const onSupportsClassCChange = (checked: boolean) => {
+ setSupportsClassC(checked);
};
- onPayloadCodecRuntimeChange = (value: CodecRuntime) => {
- this.setState({
- payloadCodecRuntime: value,
- });
+ const onPayloadCodecRuntimeChange = (value: CodecRuntime) => {
+ setPayloadCodecRuntime(value);
};
- render() {
- const adrOptions = this.state.adrAlgorithms.map(v => {v[1]} );
+ const adrOptions = adrAlgorithms.map(v => {v[1]} );
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- AS923
- AS923-2
- AS923-3
- AS923-4
- AU915
- CN779
- EU433
- EU868
- IN865
- ISM2400
- KR920
- RU864
- US915
-
-
-
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AS923
+ AS923-2
+ AS923-3
+ AS923-4
+ AU915
+ CN779
+ EU433
+ EU868
+ IN865
+ ISM2400
+ KR920
+ RU864
+ US915
+
+
+
+
+
+
+ LoRaWAN 1.0.0
+ LoRaWAN 1.0.1
+ LoRaWAN 1.0.2
+ LoRaWAN 1.0.3
+ LoRaWAN 1.0.4
+ LoRaWAN 1.1.0
+
+
+
+
+
+
+ A
+ B
+ RP002-1.0.0
+ RP002-1.0.1
+ RP002-1.0.2
+ RP002-1.0.3
+
+
+
+
+
+ {adrOptions}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!supportsOtaa && (
+
-
- LoRaWAN 1.0.0
- LoRaWAN 1.0.1
- LoRaWAN 1.0.2
- LoRaWAN 1.0.3
- LoRaWAN 1.0.4
- LoRaWAN 1.1.0
-
+
-
- A
- B
- RP002-1.0.0
- RP002-1.0.1
- RP002-1.0.2
- RP002-1.0.3
-
+
-
- {adrOptions}
-
-
-
+ )}
+ {!supportsOtaa && (
+
+
-
+
-
+
-
-
-
-
-
-
+
-
-
-
-
-
- {!this.state.supportsOtaa && (
-
+ )}
+
+
+
+
+
+ {supportsClassB && (
+ <>
+
-
+
-
+
+ Every second
+ Every 2 seconds
+ Every 4 seconds
+ Every 8 seconds
+ Every 16 seconds
+ Every 32 seconds
+ Every 64 seconds
+ Every 128 seconds
+
- )}
- {!this.state.supportsOtaa && (
-
+
-
+
- )}
-
-
-
-
+ >
+ )}
+
+
+
+
+
+ {supportsClassC && (
+
+
- {this.state.supportsClassB && (
+ )}
+
+
+
+
+ None
+ Cayenne LPP
+ JavaScript functions
+
+
+ {payloadCodecRuntime === CodecRuntime.JS && }
+
+
+
+ {(fields, { add, remove }) => (
<>
-
-
-
-
-
-
-
-
-
- Every second
- Every 2 seconds
- Every 4 seconds
- Every 8 seconds
- Every 16 seconds
- Every 32 seconds
- Every 64 seconds
- Every 128 seconds
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ remove(name)} />
+
+
+ ))}
+
+ add()} block icon={ }>
+ Add tag
+
+
>
)}
-
-
-
-
-
- {this.state.supportsClassC && (
-
-
-
+
+
+
+
+
+ ChirpStack can aggregate and visualize decoded device measurements in the device dashboard. To setup the
+ aggregation of device measurements, you must configure the key, kind of measurement and name
+ (user-defined). The following measurement-kinds can be selected:
+
+
+
+ Unknown / unset : Default for auto-detected keys. This disables the aggregation of this
+ metric.
+
+
+ Counter : For continuous incrementing counters.
+
+
+ Absolute : For counters which get reset upon reading / uplink.
+
+
+ Gauge : For temperature, humidity, pressure etc...
+
+
+ String : For boolean or string values.
+
+
+
+
+
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+ Unknown / unset
+ Counter
+ Absolute
+ Gauge
+ String
+
+
+
+
+
+
+
+
+
+ remove(name)} />
+
+
+ ))}
+
+ add()} block icon={ }>
+ Add measurement
+
+
+ >
)}
-
-
-
-
- None
- Cayenne LPP
- JavaScript functions
-
-
- {this.state.payloadCodecRuntime === CodecRuntime.JS && (
-
- )}
-
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map(({ key, name, ...restField }) => (
-
-
-
-
-
-
-
-
-
-
-
-
- remove(name)} />
-
-
- ))}
-
- add()} block icon={ }>
- Add tag
-
-
- >
- )}
-
-
-
-
-
- ChirpStack can aggregate and visualize decoded device measurements in the device dashboard. To setup the
- aggregation of device measurements, you must configure the key, kind of measurement and name
- (user-defined). The following measurement-kinds can be selected:
-
-
-
- Unknown / unset : Default for auto-detected keys. This disables the aggregation of
- this metric.
-
-
- Counter : For continuous incrementing counters.
-
-
- Absolute : For counters which get reset upon reading / uplink.
-
-
- Gauge : For temperature, humidity, pressure etc...
-
-
- String : For boolean or string values.
-
-
-
-
-
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map(({ key, name, ...restField }) => (
-
-
-
-
-
-
-
-
-
- Unknown / unset
- Counter
- Absolute
- Gauge
- String
-
-
-
-
-
-
-
-
-
- remove(name)} />
-
-
- ))}
-
- add()} block icon={ }>
- Add measurement
-
-
- >
- )}
-
-
-
-
-
- Submit
-
-
-
- );
- }
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default DeviceProfileTemplateForm;
diff --git a/ui/src/views/device-profile-templates/EditDeviceProfileTemplate.tsx b/ui/src/views/device-profile-templates/EditDeviceProfileTemplate.tsx
index cdd9ec63..348db401 100644
--- a/ui/src/views/device-profile-templates/EditDeviceProfileTemplate.tsx
+++ b/ui/src/views/device-profile-templates/EditDeviceProfileTemplate.tsx
@@ -1,7 +1,9 @@
-import React, { Component } from "react";
-import { RouteComponentProps, Link } from "react-router-dom";
+import React, { useState, useEffect } from "react";
-import { Space, Breadcrumb, Card, Button, PageHeader } from "antd";
+import { useParams, Link, useNavigate } from "react-router-dom";
+
+import { Space, Breadcrumb, Card, Button } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import {
DeviceProfileTemplate,
@@ -15,101 +17,78 @@ import DeviceProfileTemplateForm from "./DeviceProfileTemplateForm";
import DeviceProfileTemplateStore from "../../stores/DeviceProfileTemplateStore";
import DeleteConfirm from "../../components/DeleteConfirm";
-interface IState {
- deviceProfileTemplate?: DeviceProfileTemplate;
-}
+function EditDeviceProfileTemplate() {
+ const navigate = useNavigate();
+ const [deviceProfileTemplate, setDeviceProfileTemplate] = useState(undefined);
+ const { deviceProfileTemplateId } = useParams();
-interface MatchParams {
- deviceProfileTemplateId: string;
-}
-
-interface IProps extends RouteComponentProps {}
-
-class EditDeviceProfileTemplate extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
- this.getDeviceProfileTemplate();
- }
-
- getDeviceProfileTemplate = () => {
- const id = this.props.match.params.deviceProfileTemplateId;
+ useEffect(() => {
+ const id = deviceProfileTemplateId!;
let req = new GetDeviceProfileTemplateRequest();
req.setId(id);
DeviceProfileTemplateStore.get(req, (resp: GetDeviceProfileTemplateResponse) => {
- this.setState({
- deviceProfileTemplate: resp.getDeviceProfileTemplate(),
- });
+ setDeviceProfileTemplate(resp.getDeviceProfileTemplate());
});
- };
+ }, [deviceProfileTemplateId]);
- onFinish = (obj: DeviceProfileTemplate) => {
+ const onFinish = (obj: DeviceProfileTemplate) => {
let req = new UpdateDeviceProfileTemplateRequest();
req.setDeviceProfileTemplate(obj);
DeviceProfileTemplateStore.update(req, () => {
- this.props.history.push(`/device-profile-templates`);
+ navigate(`/device-profile-templates`);
});
};
- deleteDeviceProfileTemplate = () => {
+ const deleteDeviceProfileTemplate = () => {
let req = new DeleteDeviceProfileTemplateRequest();
- req.setId(this.props.match.params.deviceProfileTemplateId);
+ req.setId(deviceProfileTemplateId!);
DeviceProfileTemplateStore.delete(req, () => {
- this.props.history.push(`/device-profile-templates`);
+ navigate(`/device-profile-templates`);
});
};
- render() {
- const dp = this.state.deviceProfileTemplate;
+ const dp = deviceProfileTemplate;
- if (!dp) {
- return null;
- }
-
- return (
-
- (
-
-
- Network Server
-
-
-
- Device-profile templates
-
-
-
- {dp.getName()}
-
-
- )}
- title={dp.getName()}
- subTitle={`device-profile template id: ${dp.getId()}`}
- extra={[
-
-
- Delete device-profile template
-
- ,
- ]}
- />
-
-
-
-
- );
+ if (!dp) {
+ return null;
}
+
+ return (
+
+ (
+
+
+ Network Server
+
+
+
+ Device-profile templates
+
+
+
+ {dp.getName()}
+
+
+ )}
+ title={dp.getName()}
+ subTitle={`device-profile template id: ${dp.getId()}`}
+ extra={[
+
+
+ Delete device-profile template
+
+ ,
+ ]}
+ />
+
+
+
+
+ );
}
export default EditDeviceProfileTemplate;
diff --git a/ui/src/views/device-profile-templates/ListDeviceProfileTemplates.tsx b/ui/src/views/device-profile-templates/ListDeviceProfileTemplates.tsx
index 147d51fb..a5b85073 100644
--- a/ui/src/views/device-profile-templates/ListDeviceProfileTemplates.tsx
+++ b/ui/src/views/device-profile-templates/ListDeviceProfileTemplates.tsx
@@ -1,8 +1,8 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
-import { Space, Breadcrumb, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Button } from "antd";
import { ColumnsType } from "antd/es/table";
+import { PageHeader } from "@ant-design/pro-layout";
import {
ListDeviceProfileTemplatesRequest,
@@ -15,38 +15,36 @@ import { getEnumName } from "../helpers";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import DeviceProfileTemplateStore from "../../stores/DeviceProfileTemplateStore";
-class ListDeviceProfileTemplates extends Component {
- columns = (): ColumnsType => {
- return [
- {
- title: "Vendor",
- dataIndex: "vendor",
- key: "vendor",
+function ListDeviceProfileTemplates() {
+ const columns: ColumnsType = [
+ {
+ title: "Vendor",
+ dataIndex: "vendor",
+ key: "vendor",
+ },
+ {
+ title: "Name",
+ dataIndex: "name",
+ key: "name",
+ render: (text, record) => {text},
+ },
+ {
+ title: "Firmware",
+ dataIndex: "firmware",
+ key: "firmware",
+ },
+ {
+ title: "Region",
+ dataIndex: "region",
+ key: "region",
+ width: 150,
+ render: (text, record) => {
+ return getEnumName(Region, record.region);
},
- {
- title: "Name",
- dataIndex: "name",
- key: "name",
- render: (text, record) => {text},
- },
- {
- title: "Firmware",
- dataIndex: "firmware",
- key: "firmware",
- },
- {
- title: "Region",
- dataIndex: "region",
- key: "region",
- width: 150,
- render: (text, record) => {
- return getEnumName(Region, record.region);
- },
- },
- ];
- };
+ },
+ ];
- getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
+ const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDeviceProfileTemplatesRequest();
req.setLimit(limit);
req.setOffset(offset);
@@ -57,31 +55,29 @@ class ListDeviceProfileTemplates extends Component {
});
};
- render() {
- return (
-
- (
-
-
- Network Server
-
-
- Device-profile templates
-
-
- )}
- title="Device-profile templates"
- extra={[
-
- Add device-profile template
- ,
- ]}
- />
-
-
- );
- }
+ return (
+
+ (
+
+
+ Network Server
+
+
+ Device-profile templates
+
+
+ )}
+ title="Device-profile templates"
+ extra={[
+
+ Add device-profile template
+ ,
+ ]}
+ />
+
+
+ );
}
export default ListDeviceProfileTemplates;
diff --git a/ui/src/views/device-profiles/CreateDeviceProfile.tsx b/ui/src/views/device-profiles/CreateDeviceProfile.tsx
index 1cb0ce51..9ea64760 100644
--- a/ui/src/views/device-profiles/CreateDeviceProfile.tsx
+++ b/ui/src/views/device-profiles/CreateDeviceProfile.tsx
@@ -1,7 +1,7 @@
-import React, { Component } from "react";
-import { Link, RouteComponentProps } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { MacVersion, RegParamsRevision } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
import {
@@ -15,97 +15,97 @@ import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import DeviceProfileForm from "./DeviceProfileForm";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
}
-class CreateDeviceProfile extends Component {
- onFinish = (obj: DeviceProfile) => {
- obj.setTenantId(this.props.tenant.getId());
+function CreateDeviceProfile(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: DeviceProfile) => {
+ obj.setTenantId(props.tenant.getId());
let req = new CreateDeviceProfileRequest();
req.setDeviceProfile(obj);
DeviceProfileStore.create(req, (_resp: CreateDeviceProfileResponse) => {
- this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
+ navigate(`/tenants/${props.tenant.getId()}/device-profiles`);
});
};
- render() {
- const codecScript = `// Decode uplink function.
-//
-// Input is an object with the following fields:
-// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
-// - fPort = Uplink fPort.
-// - variables = Object containing the configured device variables.
-//
-// Output must be an object with the following fields:
-// - data = Object representing the decoded payload.
-function decodeUplink(input) {
- return {
- data: {
- temp: 22.5
- }
- };
-}
-
-// Encode downlink function.
-//
-// Input is an object with the following fields:
-// - data = Object representing the payload that must be encoded.
-// - variables = Object containing the configured device variables.
-//
-// Output must be an object with the following fields:
-// - bytes = Byte array containing the downlink payload.
-function encodeDownlink(input) {
- return {
- bytes: [225, 230, 255, 0]
- };
-}
-`;
-
- let deviceProfile = new DeviceProfile();
- deviceProfile.setPayloadCodecScript(codecScript);
- deviceProfile.setSupportsOtaa(true);
- deviceProfile.setUplinkInterval(3600);
- deviceProfile.setDeviceStatusReqInterval(1);
- deviceProfile.setAdrAlgorithmId("default");
- deviceProfile.setMacVersion(MacVersion.LORAWAN_1_0_3);
- deviceProfile.setRegParamsRevision(RegParamsRevision.A);
- deviceProfile.setFlushQueueOnActivate(true);
- deviceProfile.setAutoDetectMeasurements(true);
-
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- Device profiles
-
-
-
- Add
-
-
- )}
- title="Add device profile"
- />
-
-
-
-
- );
+ const codecScript = `// Decode uplink function.
+ //
+ // Input is an object with the following fields:
+ // - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
+ // - fPort = Uplink fPort.
+ // - variables = Object containing the configured device variables.
+ //
+ // Output must be an object with the following fields:
+ // - data = Object representing the decoded payload.
+ function decodeUplink(input) {
+ return {
+ data: {
+ temp: 22.5
+ }
+ };
}
+
+ // Encode downlink function.
+ //
+ // Input is an object with the following fields:
+ // - data = Object representing the payload that must be encoded.
+ // - variables = Object containing the configured device variables.
+ //
+ // Output must be an object with the following fields:
+ // - bytes = Byte array containing the downlink payload.
+ function encodeDownlink(input) {
+ return {
+ bytes: [225, 230, 255, 0]
+ };
+ }
+ `;
+
+ let deviceProfile = new DeviceProfile();
+ deviceProfile.setPayloadCodecScript(codecScript);
+ deviceProfile.setSupportsOtaa(true);
+ deviceProfile.setUplinkInterval(3600);
+ deviceProfile.setDeviceStatusReqInterval(1);
+ deviceProfile.setAdrAlgorithmId("default");
+ deviceProfile.setMacVersion(MacVersion.LORAWAN_1_0_3);
+ deviceProfile.setRegParamsRevision(RegParamsRevision.A);
+ deviceProfile.setFlushQueueOnActivate(true);
+ deviceProfile.setAutoDetectMeasurements(true);
+
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ Device profiles
+
+
+
+ Add
+
+
+ )}
+ title="Add device profile"
+ />
+
+
+
+
+ );
}
export default CreateDeviceProfile;
diff --git a/ui/src/views/device-profiles/DeviceProfileForm.tsx b/ui/src/views/device-profiles/DeviceProfileForm.tsx
index c26f16f7..a8394b00 100644
--- a/ui/src/views/device-profiles/DeviceProfileForm.tsx
+++ b/ui/src/views/device-profiles/DeviceProfileForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Form, Input, Select, InputNumber, Switch, Row, Col, Button, Tabs, Modal, Spin, Cascader, Card } from "antd";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
@@ -36,157 +36,135 @@ interface ModalProps {
visible: boolean;
}
-interface ModalState {
- templates: DeviceProfileTemplateListItem[];
- templatesLoaded: boolean;
- templateId?: string;
-}
-
interface Option {
value: string;
label: string;
children?: Option[];
}
-class TemplateModal extends Component {
- constructor(props: ModalProps) {
- super(props);
- this.state = {
- templates: [],
- templatesLoaded: false,
- };
- }
+function TemplateModal(props: ModalProps) {
+ const [templates, setTemplates] = useState([]);
+ const [templatesLoaded, setTemplatesLoaded] = useState(false);
+ const [templateId, setTemplateId] = useState(undefined);
- componentDidUpdate(prevProps: ModalProps) {
- if (prevProps === this.props) {
- return;
- }
-
- if (this.props.visible) {
- this.setState({
- templatesLoaded: false,
- });
+ useEffect(() => {
+ if (props.visible) {
+ setTemplatesLoaded(false);
let req = new ListDeviceProfileTemplatesRequest();
req.setLimit(99999);
DeviceProfileTemplateStore.list(req, (resp: ListDeviceProfileTemplatesResponse) => {
- this.setState({
- templatesLoaded: true,
- templates: resp.getResultList(),
- });
+ setTemplatesLoaded(true);
+ setTemplates(resp.getResultList());
});
}
- }
+ }, [props]);
- onChange = (value: (string | number)[]) => {
- this.setState({
- templateId: value.at(-1)! as string,
- });
+ const onChange = (value: (string | number)[]) => {
+ setTemplateId(value.at(-1)! as string);
};
- onOk = () => {
- if (this.state.templateId) {
+ const onOk = () => {
+ if (templateId) {
let req = new GetDeviceProfileTemplateRequest();
- req.setId(this.state.templateId);
+ req.setId(templateId);
DeviceProfileTemplateStore.get(req, (resp: GetDeviceProfileTemplateResponse) => {
const dp = resp.getDeviceProfileTemplate();
if (dp) {
- this.props.onOk(dp);
+ props.onOk(dp);
}
});
}
};
- render() {
- let options: Option[] = [];
- let vendor = "";
- let device = "";
- let firmware = "";
- let region = "";
+ let options: Option[] = [];
+ let vendor = "";
+ let device = "";
+ let firmware = "";
+ let region = "";
- for (const item of this.state.templates) {
- if (vendor !== item.getVendor()) {
- options.push({
- value: item.getId(),
- label: item.getVendor(),
- children: [],
- });
+ for (const item of templates) {
+ if (vendor !== item.getVendor()) {
+ options.push({
+ value: item.getId(),
+ label: item.getVendor(),
+ children: [],
+ });
- vendor = item.getVendor();
- device = "";
- firmware = "";
- region = "";
- }
-
- if (device !== item.getName()) {
- options.at(-1)!.children!.push({
- value: item.getId(),
- label: item.getName(),
- children: [],
- });
-
- device = item.getName();
- firmware = "";
- region = "";
- }
-
- if (firmware !== item.getFirmware()) {
- options
- .at(-1)!
- .children!.at(-1)!
- .children!.push({
- value: item.getId(),
- label: "FW version: " + item.getFirmware(),
- children: [],
- });
-
- firmware = item.getFirmware();
- region = "";
- }
-
- if (region !== getEnumName(Region, item.getRegion())) {
- options
- .at(-1)!
- .children!.at(-1)!
- .children!.at(-1)!
- .children!.push({
- value: item.getId(),
- label: getEnumName(Region, item.getRegion()),
- children: [],
- });
-
- region = getEnumName(Region, item.getRegion());
- }
+ vendor = item.getVendor();
+ device = "";
+ firmware = "";
+ region = "";
}
- return (
-
- {!this.state.templatesLoaded && (
-
-
-
- )}
- {this.state.templatesLoaded && (
-
- )}
-
- );
+ if (device !== item.getName()) {
+ options.at(-1)!.children!.push({
+ value: item.getId(),
+ label: item.getName(),
+ children: [],
+ });
+
+ device = item.getName();
+ firmware = "";
+ region = "";
+ }
+
+ if (firmware !== item.getFirmware()) {
+ options
+ .at(-1)!
+ .children!.at(-1)!
+ .children!.push({
+ value: item.getId(),
+ label: "FW version: " + item.getFirmware(),
+ children: [],
+ });
+
+ firmware = item.getFirmware();
+ region = "";
+ }
+
+ if (region !== getEnumName(Region, item.getRegion())) {
+ options
+ .at(-1)!
+ .children!.at(-1)!
+ .children!.at(-1)!
+ .children!.push({
+ value: item.getId(),
+ label: getEnumName(Region, item.getRegion()),
+ children: [],
+ });
+
+ region = getEnumName(Region, item.getRegion());
+ }
}
+
+ return (
+
+ {!templatesLoaded && (
+
+
+
+ )}
+ {templatesLoaded && (
+
+ )}
+
+ );
}
interface IProps {
@@ -195,69 +173,41 @@ interface IProps {
disabled?: boolean;
}
-interface IState {
- supportsOtaa: boolean;
- supportsClassB: boolean;
- supportsClassC: boolean;
- isRelay: boolean;
- isRelayEd: boolean,
- payloadCodecRuntime: CodecRuntime;
- adrAlgorithms: [string, string][];
- regionConfigurations: RegionListItem[];
- regionConfigurationsFiltered: [string, string][];
- templateModalVisible: boolean;
- tabActive: string;
-}
+function DeviceProfileForm(props: IProps) {
+ const [form] = Form.useForm();
-class DeviceProfileForm extends Component {
- formRef = React.createRef();
+ const [supportsOtaa, setSupportsOtaa] = useState(false);
+ const [supportsClassB, setSupportsClassB] = useState(false);
+ const [supportsClassC, setSupportsClassC] = useState(false);
+ const [isRelay, setIsRelay] = useState(false);
+ const [isRelayEd, setIsRelayEd] = useState(false);
+ const [payloadCodecRuntime, setPayloadCodecRuntime] = useState(CodecRuntime.NONE);
+ const [adrAlgorithms, setAdrAlgorithms] = useState<[string, string][]>([]);
+ const [regionConfigurations, setRegionConfigurations] = useState([]);
+ const [regionConfigurationsFiltered, setRegionConfigurationsFiltered] = useState<[string, string][]>([]);
+ const [templateModalVisible, setTemplateModalVisible] = useState(false);
+ const [tabActive, setTabActive] = useState("1");
- constructor(props: IProps) {
- super(props);
- this.state = {
- supportsOtaa: false,
- supportsClassB: false,
- supportsClassC: false,
- isRelay: false,
- isRelayEd: false,
- payloadCodecRuntime: CodecRuntime.NONE,
- adrAlgorithms: [],
- regionConfigurations: [],
- regionConfigurationsFiltered: [],
- templateModalVisible: false,
- tabActive: "1",
- };
- }
-
- componentDidMount() {
- const v = this.props.initialValues;
-
- this.setState({
- supportsOtaa: v.getSupportsOtaa(),
- supportsClassB: v.getSupportsClassB(),
- supportsClassC: v.getSupportsClassC(),
- payloadCodecRuntime: v.getPayloadCodecRuntime(),
- isRelay: v.getIsRelay(),
- isRelayEd: v.getIsRelayEd(),
- });
+ useEffect(() => {
+ const v = props.initialValues;
+ setSupportsOtaa(v.getSupportsOtaa());
+ setSupportsClassB(v.getSupportsClassB());
+ setSupportsClassC(v.getSupportsClassC());
+ setPayloadCodecRuntime(v.getPayloadCodecRuntime());
+ setIsRelay(v.getIsRelay());
+ setIsRelayEd(v.getIsRelayEd());
InternalStore.listRegions((resp: ListRegionsResponse) => {
- this.setState(
- {
- regionConfigurations: resp.getRegionsList(),
- },
- () => {
- let regionConfigurationsFiltered: [string, string][] = [];
- for (const r of this.state.regionConfigurations) {
- if (v.getRegion() === r.getRegion()) {
- regionConfigurationsFiltered.push([r.getId(), r.getDescription()]);
- }
- }
- this.setState({
- regionConfigurationsFiltered: regionConfigurationsFiltered,
- });
- },
- );
+ setRegionConfigurations(resp.getRegionsList());
+
+ let regionConfigurationsFiltered: [string, string][] = [];
+ for (const r of resp.getRegionsList()) {
+ if (v.getRegion() === r.getRegion()) {
+ regionConfigurationsFiltered.push([r.getId(), r.getDescription()]);
+ }
+ }
+
+ setRegionConfigurationsFiltered(regionConfigurationsFiltered);
});
DeviceProfileStore.listAdrAlgorithms((resp: ListDeviceProfileAdrAlgorithmsResponse) => {
@@ -266,20 +216,16 @@ class DeviceProfileForm extends Component {
adrAlgorithms.push([a.getId(), a.getName()]);
}
- this.setState({
- adrAlgorithms: adrAlgorithms,
- });
+ setAdrAlgorithms(adrAlgorithms);
});
- }
+ }, [props]);
- onTabChange = (activeKey: string) => {
- this.setState({
- tabActive: activeKey,
- });
+ const onTabChange = (activeKey: string) => {
+ setTabActive(activeKey);
};
- onFinish = (values: DeviceProfile.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: DeviceProfile.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let dp = new DeviceProfile();
dp.setId(v.id);
@@ -356,57 +302,41 @@ class DeviceProfileForm extends Component {
}
dp.setAutoDetectMeasurements(v.autoDetectMeasurements);
- this.props.onFinish(dp);
+ props.onFinish(dp);
};
- onSupportsOtaaChange = (checked: boolean) => {
- this.setState({
- supportsOtaa: checked,
- });
+ const onSupportsOtaaChange = (checked: boolean) => {
+ setSupportsOtaa(checked);
};
- onSupportsClassBChnage = (checked: boolean) => {
- this.setState({
- supportsClassB: checked,
- });
+ const onSupportsClassBChnage = (checked: boolean) => {
+ setSupportsClassB(checked);
};
- onSupportsClassCChange = (checked: boolean) => {
- this.setState({
- supportsClassC: checked,
- });
+ const onSupportsClassCChange = (checked: boolean) => {
+ setSupportsClassC(checked);
};
- onPayloadCodecRuntimeChange = (value: CodecRuntime) => {
- this.setState({
- payloadCodecRuntime: value,
- });
+ const onPayloadCodecRuntimeChange = (value: CodecRuntime) => {
+ setPayloadCodecRuntime(value);
};
- onIsRelayChange = (checked: boolean) => {
- this.setState({
- isRelay: checked,
- });
- }
-
- onIsRelayEdChange = (checked: boolean) => {
- this.setState({
- isRelayEd: checked,
- });
- }
-
- showTemplateModal = () => {
- this.setState({
- templateModalVisible: true,
- });
+ const onIsRelayChange = (checked: boolean) => {
+ setIsRelay(checked);
};
- onTemplateModalOk = (dp: DeviceProfileTemplate) => {
- this.setState({
- templateModalVisible: false,
- });
+ const onIsRelayEdChange = (checked: boolean) => {
+ setIsRelayEd(checked);
+ };
- this.formRef.current.setFieldsValue({
+ const showTemplateModal = () => {
+ setTemplateModalVisible(true);
+ };
+
+ const onTemplateModalOk = (dp: DeviceProfileTemplate) => {
+ setTemplateModalVisible(false);
+
+ form.setFieldsValue({
name: dp.getName(),
description: dp.getDescription(),
region: dp.getRegion(),
@@ -434,833 +364,794 @@ class DeviceProfileForm extends Component {
measurementsMap: dp.toObject().measurementsMap,
});
- const tabActive = this.state.tabActive;
-
- this.setState(
- {
- supportsOtaa: dp.getSupportsOtaa(),
- supportsClassB: dp.getSupportsClassB(),
- supportsClassC: dp.getSupportsClassC(),
- payloadCodecRuntime: dp.getPayloadCodecRuntime(),
- },
- () => {
- // This is a workaround as without rendering the TabPane (e.g. the user
- // does not click through the different tabs), setFieldsValue does not
- // actually update the fields. For example if selecting a template with
- // a codec script and immediately click the save button, no codec script
- // is passed to the onFinish function. This seems to be with every field
- // that is not actually rendered before clicking the Save button.
- this.setState(
- {
- tabActive: "1",
- },
- () => {
- this.setState(
- {
- tabActive: "2",
- },
- () => {
- this.setState(
- {
- tabActive: "3",
- },
- () => {
- this.setState(
- {
- tabActive: "4",
- },
- () => {
- this.setState(
- {
- tabActive: "5",
- },
- () => {
- this.setState(
- {
- tabActive: "6",
- },
- () => {
- this.setState(
- {
- tabActive: "7",
- },
- () => {
- this.setState(
- {
- tabActive: "8",
- },
- () => {
- this.setState({
- tabActive: tabActive,
- });
- },
- );
- },
- );
- },
- );
- },
- );
- },
- );
- },
- );
- },
- );
- },
- );
- },
- );
+ setSupportsOtaa(dp.getSupportsOtaa());
+ setSupportsClassB(dp.getSupportsClassB());
+ setSupportsClassC(dp.getSupportsClassC());
+ setPayloadCodecRuntime(dp.getPayloadCodecRuntime());
};
- onTemplateModalCancel = () => {
- this.setState({
- templateModalVisible: false,
- });
+ const onTemplateModalCancel = () => {
+ setTemplateModalVisible(false);
};
- onRegionChange = (region: Region) => {
+ const onRegionChange = (region: Region) => {
let regionConfigurationsFiltered: [string, string][] = [];
- for (const r of this.state.regionConfigurations) {
+ for (const r of regionConfigurations) {
if (region === r.getRegion()) {
regionConfigurationsFiltered.push([r.getId(), r.getDescription()]);
}
}
- this.setState({
- regionConfigurationsFiltered: regionConfigurationsFiltered,
- });
- this.formRef.current.setFieldsValue({
+
+ setRegionConfigurationsFiltered(regionConfigurationsFiltered);
+ form.setFieldsValue({
regionConfigId: "",
});
};
- render() {
- const adrOptions = this.state.adrAlgorithms.map(v => {v[1]} );
- const regionConfigOptions = this.state.regionConfigurationsFiltered.map(v => (
- {v[1]}
- ));
- const regionOptions = this.state.regionConfigurations
- .map(v => v.getRegion())
- .filter((v, i, a) => a.indexOf(v) === i)
- .map(v => {getEnumName(Region, v).replace("_", "-")} );
- const operations = (
-
- Select device-profile template
-
- );
+ const adrOptions = adrAlgorithms.map(v => {v[1]} );
+ const regionConfigOptions = regionConfigurationsFiltered.map(v => {v[1]} );
+ const regionOptions = regionConfigurations
+ .map(v => v.getRegion())
+ .filter((v, i, a) => a.indexOf(v) === i)
+ .map(v => {getEnumName(Region, v).replace("_", "-")} );
+ const operations = (
+
+ Select device-profile template
+
+ );
- return (
-
-
-
-
-
-
-
-
-
-
-
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {regionOptions}
+
+
+
+
+
+
+ {regionConfigOptions}
+
+
+
+
+
+
+
+
+ LoRaWAN 1.0.0
+ LoRaWAN 1.0.1
+ LoRaWAN 1.0.2
+ LoRaWAN 1.0.3
+ LoRaWAN 1.0.4
+ LoRaWAN 1.1.0
+
+
+
+
+
+
+ A
+ B
+ RP002-1.0.0
+ RP002-1.0.1
+ RP002-1.0.2
+ RP002-1.0.3
+
+
+
+
+
+ {adrOptions}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!supportsOtaa && (
+
-
- {regionOptions}
-
+
-
- {regionConfigOptions}
-
+
-
+ )}
+ {!supportsOtaa && (
+
-
- LoRaWAN 1.0.0
- LoRaWAN 1.0.1
- LoRaWAN 1.0.2
- LoRaWAN 1.0.3
- LoRaWAN 1.0.4
- LoRaWAN 1.1.0
-
+
-
- A
- B
- RP002-1.0.0
- RP002-1.0.1
- RP002-1.0.2
- RP002-1.0.3
-
+
+ )}
+
+
+
+
+
+ {supportsClassB && (
+ <>
+
+
+
+
+
+
+
+
+
+ Every second
+ Every 2 seconds
+ Every 4 seconds
+ Every 8 seconds
+ Every 16 seconds
+ Every 32 seconds
+ Every 64 seconds
+ Every 128 seconds
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ {supportsClassC && (
- {adrOptions}
+
+ )}
+
+
+
+
+ None
+ Cayenne LPP
+ JavaScript functions
+
+
+ {payloadCodecRuntime === CodecRuntime.JS && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {isRelay && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {isRelayEd && (
+
+
+
+ )}
+
+
+ {(isRelay || isRelayEd) && (
-
+
-
+
-
+
-
-
-
-
-
- {!this.state.supportsOtaa && (
-
-
+ )}
+ {(isRelay || isRelayEd) && (
+
+
+
+
+ 0 kHz
+ 200 kHz
+ 400 kHz
+ 800 kHz
+ 1600 kHz
+ 3200 kHz
+
+
+
+
+ {isRelay && (
-
+
+ 1 second
+ 500 milliseconds
+ 250 milliseconds
+ 100 milliseconds
+ 50 milliseconds
+ 20 milliseconds
+
-
-
-
-
-
-
-
- )}
- {!this.state.supportsOtaa && (
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
-
-
- {this.state.supportsClassB && (
+ )}
+
+
+ )}
+ {isRelayEd && (
+
+
+
+
+ Disable relay mode
+ Enable relay mode
+ Dynamic
+
+ End-device controlled
+
+
+
+
+
+
+
+ 8
+ 16
+ 32
+ 64
+
+
+
+
+
+
+
+
+
+ )}
+ {isRelayEd && (
+
+
+
+
+ 1 x reload rate
+ 2 x reload rate
+ 4 x reload rate
+ 12 x reload rate
+
+
+
+
+
+
+
+
+
+ )}
+ {isRelay && (
+
+
+
+
+ 1 x reload rate
+ 2 x reload rate
+ 4 x reload rate
+ 12 x reload rate
+
+
+
+
+
+
+
+
+
+
+
+ 1 x reload rate
+ 2 x reload rate
+ 4 x reload rate
+ 12 x reload rate
+
+
+
+
+
+
+
+
+
+ )}
+ {isRelay && (
+
+
+
+
+ 1 x reload rate
+ 2 x reload rate
+ 4 x reload rate
+ 12 x reload rate
+
+
+
+
+
+
+
+
+
+
+
+ 1 x reload rate
+ 2 x reload rate
+ 4 x reload rate
+ 12 x reload rate
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {(fields, { add, remove }) => (
<>
-
-
-
-
-
-
-
-
-
- Every second
- Every 2 seconds
- Every 4 seconds
- Every 8 seconds
- Every 16 seconds
- Every 32 seconds
- Every 64 seconds
- Every 128 seconds
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ remove(name)} />
+
+
+ ))}
+
+ add()} block icon={ }>
+ Add tag
+
+
>
)}
-
-
-
-
-
- {this.state.supportsClassC && (
-
-
-
- )}
-
-
-
-
- None
- Cayenne LPP
- JavaScript functions
-
-
- {this.state.payloadCodecRuntime === CodecRuntime.JS && (
-
- )}
-
-
-
-
-
-
+
+
+
+
+
+ ChirpStack can aggregate and visualize decoded device measurements in the device dashboard. To setup the
+ aggregation of device measurements, you must configure the key, kind of measurement and name
+ (user-defined). The following measurement-kinds can be selected:
+
+
+
+ Unknown / unset : Default for auto-detected keys. This disables the aggregation of this
+ metric.
+
+
+ Counter : For continuous incrementing counters.
+
+
+ Absolute : For counters which get reset upon reading / uplink.
+
+
+ Gauge : For temperature, humidity, pressure etc...
+
+
+ String : For boolean or string values.
+
+
+
+
+
+
+
+ {(fields, { add, remove }) => (
+ <>
+ {fields.map(({ key, name, ...restField }) => (
+
+
+
+
+
+
+
+
+
+ Unknown / unset
+ Counter
+ Absolute
+ Gauge
+ String
+
+
+
+
+
+
+
+
+
+ remove(name)} />
+
+
+ ))}
+
+ add()} block icon={ }>
+ Add measurement
+
-
-
- {this.state.isRelay && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
- { this.state.isRelayEd && (
-
-
-
- )}
-
-
- {(this.state.isRelay || this.state.isRelayEd ) && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
- {(this.state.isRelay || this.state.isRelayEd) && (
-
-
-
-
- 0 kHz
- 200 kHz
- 400 kHz
- 800 kHz
- 1600 kHz
- 3200 kHz
-
-
-
-
- {this.state.isRelay && (
-
-
- 1 second
- 500 milliseconds
- 250 milliseconds
- 100 milliseconds
- 50 milliseconds
- 20 milliseconds
-
-
- )}
-
-
+ >
)}
- {this.state.isRelayEd && (
-
-
-
-
- Disable relay mode
- Enable relay mode
- Dynamic
- End-device controlled
-
-
-
-
-
-
- 8
- 16
- 32
- 64
-
-
-
-
-
-
-
-
-
- )}
- {this.state.isRelayEd && (
-
-
-
-
- 1 x reload rate
- 2 x reload rate
- 4 x reload rate
- 12 x reload rate
-
-
-
-
-
-
-
-
-
- )}
- {this.state.isRelay && (
-
-
-
-
- 1 x reload rate
- 2 x reload rate
- 4 x reload rate
- 12 x reload rate
-
-
-
-
-
-
-
-
-
-
-
- 1 x reload rate
- 2 x reload rate
- 4 x reload rate
- 12 x reload rate
-
-
-
-
-
-
-
-
-
- )}
- {this.state.isRelay && (
-
-
-
-
- 1 x reload rate
- 2 x reload rate
- 4 x reload rate
- 12 x reload rate
-
-
-
-
-
-
-
-
-
-
-
- 1 x reload rate
- 2 x reload rate
- 4 x reload rate
- 12 x reload rate
-
-
-
-
-
-
-
-
-
- )}
-
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map(({ key, name, ...restField }) => (
-
-
-
-
-
-
-
-
-
-
-
-
- remove(name)} />
-
-
- ))}
-
- add()}
- block
- icon={ }
- >
- Add tag
-
-
- >
- )}
-
-
-
-
-
- ChirpStack can aggregate and visualize decoded device measurements in the device dashboard. To setup the
- aggregation of device measurements, you must configure the key, kind of measurement and name
- (user-defined). The following measurement-kinds can be selected:
-
-
-
- Unknown / unset : Default for auto-detected keys. This disables the aggregation of
- this metric.
-
-
- Counter : For continuous incrementing counters.
-
-
- Absolute : For counters which get reset upon reading / uplink.
-
-
- Gauge : For temperature, humidity, pressure etc...
-
-
- String : For boolean or string values.
-
-
-
-
-
-
-
- {(fields, { add, remove }) => (
- <>
- {fields.map(({ key, name, ...restField }) => (
-
-
-
-
-
-
-
-
-
- Unknown / unset
- Counter
- Absolute
- Gauge
- String
-
-
-
-
-
-
-
-
-
- remove(name)} />
-
-
- ))}
-
- add()}
- block
- icon={ }
- >
- Add measurement
-
-
- >
- )}
-
-
-
-
-
- Submit
-
-
-
- );
- }
+
+
+
+
+
+ Submit
+
+
+
+ );
}
export default DeviceProfileForm;
diff --git a/ui/src/views/device-profiles/EditDeviceProfile.tsx b/ui/src/views/device-profiles/EditDeviceProfile.tsx
index aee367c0..35c0d3d0 100644
--- a/ui/src/views/device-profiles/EditDeviceProfile.tsx
+++ b/ui/src/views/device-profiles/EditDeviceProfile.tsx
@@ -1,7 +1,8 @@
-import React, { Component } from "react";
-import { RouteComponentProps, Link } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate, Link, useParams } from "react-router-dom";
-import { Space, Breadcrumb, Card, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Card, Button } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import {
@@ -18,112 +19,95 @@ import SessionStore from "../../stores/SessionStore";
import DeleteConfirm from "../../components/DeleteConfirm";
import Admin from "../../components/Admin";
-interface IState {
- deviceProfile?: DeviceProfile;
-}
-
-interface MatchParams {
- deviceProfileId: string;
-}
-
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
}
-class EditDeviceProfile extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {};
- }
+function EditDeviceProfile(props: IProps) {
+ const navigate = useNavigate();
+ const [deviceProfile, setDeviceProfile] = useState(undefined);
+ const { deviceProfileId } = useParams();
- componentDidMount() {
- this.getDeviceProfile();
- }
-
- getDeviceProfile = () => {
- const id = this.props.match.params.deviceProfileId;
+ useEffect(() => {
+ const id = deviceProfileId!;
let req = new GetDeviceProfileRequest();
req.setId(id);
DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => {
- this.setState({
- deviceProfile: resp.getDeviceProfile(),
- });
+ setDeviceProfile(resp.getDeviceProfile());
});
- };
+ }, [deviceProfileId]);
- onFinish = (obj: DeviceProfile) => {
+ const onFinish = (obj: DeviceProfile) => {
let req = new UpdateDeviceProfileRequest();
req.setDeviceProfile(obj);
DeviceProfileStore.update(req, () => {
- this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
+ navigate(`/tenants/${props.tenant.getId()}/device-profiles`);
});
};
- deleteDeviceProfile = () => {
+ const deleteDeviceProfile = () => {
let req = new DeleteDeviceProfileRequest();
- req.setId(this.props.match.params.deviceProfileId);
+ req.setId(deviceProfileId!);
DeviceProfileStore.delete(req, () => {
- this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
+ navigate(`/tenants/${props.tenant.getId()}/device-profiles`);
});
};
- render() {
- const dp = this.state.deviceProfile;
+ const dp = deviceProfile;
- if (!dp) {
- return null;
- }
-
- const disabled = !(
- SessionStore.isAdmin() ||
- SessionStore.isTenantAdmin(this.props.tenant.getId()) ||
- SessionStore.isTenantDeviceAdmin(this.props.tenant.getId())
- );
-
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- Device profiles
-
-
-
- {dp.getName()}
-
-
- )}
- title={dp.getName()}
- subTitle={`device profile id: ${dp.getId()}`}
- extra={[
-
-
-
- Delete device profile
-
-
- ,
- ]}
- />
-
-
-
-
- );
+ if (!dp) {
+ return null;
}
+
+ const disabled = !(
+ SessionStore.isAdmin() ||
+ SessionStore.isTenantAdmin(props.tenant.getId()) ||
+ SessionStore.isTenantDeviceAdmin(props.tenant.getId())
+ );
+
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ Device profiles
+
+
+
+ {dp.getName()}
+
+
+ )}
+ title={dp.getName()}
+ subTitle={`device profile id: ${dp.getId()}`}
+ extra={[
+
+
+
+ Delete device profile
+
+
+ ,
+ ]}
+ />
+
+
+
+
+ );
}
export default EditDeviceProfile;
diff --git a/ui/src/views/device-profiles/ListDeviceProfiles.tsx b/ui/src/views/device-profiles/ListDeviceProfiles.tsx
index 5ada24e1..ef54ffd4 100644
--- a/ui/src/views/device-profiles/ListDeviceProfiles.tsx
+++ b/ui/src/views/device-profiles/ListDeviceProfiles.tsx
@@ -1,8 +1,8 @@
-import React, { Component } from "react";
import { Link } from "react-router-dom";
-import { Space, Breadcrumb, Button, PageHeader } from "antd";
+import { Space, Breadcrumb, Button } from "antd";
import { ColumnsType } from "antd/es/table";
+import { PageHeader } from "@ant-design/pro-layout";
import {
ListDeviceProfilesRequest,
@@ -21,89 +21,87 @@ interface IProps {
tenant: Tenant;
}
-class ListDeviceProfiles extends Component {
- columns = (): ColumnsType => {
- return [
- {
- title: "Name",
- dataIndex: "name",
- key: "name",
- render: (text, record) => (
- {text}
- ),
+function ListDeviceProfiles(props: IProps) {
+ const columns: ColumnsType = [
+ {
+ title: "Name",
+ dataIndex: "name",
+ key: "name",
+ render: (text, record) => (
+ {text}
+ ),
+ },
+ {
+ title: "Region",
+ dataIndex: "region",
+ key: "region",
+ width: 150,
+ render: (text, record) => {
+ return getEnumName(Region, record.region);
},
- {
- title: "Region",
- dataIndex: "region",
- key: "region",
- width: 150,
- render: (text, record) => {
- return getEnumName(Region, record.region);
- },
+ },
+ {
+ title: "MAC version",
+ dataIndex: "macVersion",
+ key: "macVersion",
+ width: 150,
+ render: (text, record) => {
+ return formatMacVersion(record.macVersion);
},
- {
- title: "MAC version",
- dataIndex: "macVersion",
- key: "macVersion",
- width: 150,
- render: (text, record) => {
- return formatMacVersion(record.macVersion);
- },
+ },
+ {
+ title: "Revision",
+ dataIndex: "regParamsRevision",
+ key: "regParamsRevision",
+ width: 150,
+ render: (text, record) => {
+ return formatRegParamsRevision(record.regParamsRevision);
},
- {
- title: "Revision",
- dataIndex: "regParamsRevision",
- key: "regParamsRevision",
- width: 150,
- render: (text, record) => {
- return formatRegParamsRevision(record.regParamsRevision);
- },
+ },
+ {
+ title: "Supports OTAA",
+ dataIndex: "supportsOtaa",
+ key: "supportsOtaa",
+ width: 150,
+ render: (text, record) => {
+ if (record.supportsOtaa) {
+ return "yes";
+ } else {
+ return "no";
+ }
},
- {
- title: "Supports OTAA",
- dataIndex: "supportsOtaa",
- key: "supportsOtaa",
- width: 150,
- render: (text, record) => {
- if (record.supportsOtaa) {
- return "yes";
- } else {
- return "no";
- }
- },
+ },
+ {
+ title: "Supports Class-B",
+ dataIndex: "supportsClassB",
+ key: "supportsClassB",
+ width: 150,
+ render: (text, record) => {
+ if (record.supportsClassB) {
+ return "yes";
+ } else {
+ return "no";
+ }
},
- {
- title: "Supports Class-B",
- dataIndex: "supportsClassB",
- key: "supportsClassB",
- width: 150,
- render: (text, record) => {
- if (record.supportsClassB) {
- return "yes";
- } else {
- return "no";
- }
- },
+ },
+ {
+ title: "Supports Class-C",
+ dataIndex: "supportsClassC",
+ key: "supportsClassC",
+ width: 150,
+ render: (text, record) => {
+ if (record.supportsClassC) {
+ return "yes";
+ } else {
+ return "no";
+ }
},
- {
- title: "Supports Class-C",
- dataIndex: "supportsClassC",
- key: "supportsClassC",
- width: 150,
- render: (text, record) => {
- if (record.supportsClassC) {
- return "yes";
- } else {
- return "no";
- }
- },
- },
- ];
- };
+ },
+ ];
- getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
+ const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDeviceProfilesRequest();
- req.setTenantId(this.props.tenant.getId());
+ req.setTenantId(props.tenant.getId());
req.setLimit(limit);
req.setOffset(offset);
@@ -113,38 +111,36 @@ class ListDeviceProfiles extends Component {
});
};
- render() {
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
- Device profiles
-
-
- )}
- title="Device profiles"
- extra={[
-
-
- Add device profile
-
- ,
- ]}
- />
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+ Device profiles
+
+
+ )}
+ title="Device profiles"
+ extra={[
+
+
+ Add device profile
+
+ ,
+ ]}
+ />
+
+
+ );
}
export default ListDeviceProfiles;
diff --git a/ui/src/views/devices/CreateDevice.tsx b/ui/src/views/devices/CreateDevice.tsx
index 46db75bb..8cee9d60 100644
--- a/ui/src/views/devices/CreateDevice.tsx
+++ b/ui/src/views/devices/CreateDevice.tsx
@@ -1,7 +1,7 @@
-import React, { Component } from "react";
-import { Link, RouteComponentProps } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
-import { Space, Breadcrumb, Card, PageHeader } from "antd";
+import { Space, Breadcrumb, Card } from "antd";
+import { PageHeader } from "@ant-design/pro-layout";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
@@ -15,14 +15,16 @@ import DeviceForm from "./DeviceForm";
import DeviceStore from "../../stores/DeviceStore";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
application: Application;
}
-class CreateDevice extends Component {
- onFinish = (obj: Device) => {
- obj.setApplicationId(this.props.application.getId());
+function CreateDevice(props: IProps) {
+ const navigate = useNavigate();
+
+ const onFinish = (obj: Device) => {
+ obj.setApplicationId(props.application.getId());
let req = new CreateDeviceRequest();
req.setDevice(obj);
@@ -34,60 +36,58 @@ class CreateDevice extends Component {
DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => {
let dp = resp.getDeviceProfile()!;
if (dp.getSupportsOtaa()) {
- this.props.history.push(
- `/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${obj.getDevEui()}/keys`,
+ navigate(
+ `/tenants/${props.tenant.getId()}/applications/${props.application.getId()}/devices/${obj.getDevEui()}/keys`,
);
} else {
- this.props.history.push(
- `/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${obj.getDevEui()}`,
+ navigate(
+ `/tenants/${props.tenant.getId()}/applications/${props.application.getId()}/devices/${obj.getDevEui()}`,
);
}
});
});
};
- render() {
- let device = new Device();
- device.setApplicationId(this.props.application.getId());
+ let device = new Device();
+ device.setApplicationId(props.application.getId());
- return (
-
- (
-
-
- Tenants
-
-
-
- {this.props.tenant.getName()}
-
-
-
-
- Applications
-
-
-
-
-
- {this.props.application.getName()}
-
-
-
-
- Add device
-
-
- )}
- title="Add device"
- />
-
-
-
-
- );
- }
+ return (
+
+ (
+
+
+ Tenants
+
+
+
+ {props.tenant.getName()}
+
+
+
+
+ Applications
+
+
+
+
+
+ {props.application.getName()}
+
+
+
+
+ Add device
+
+
+ )}
+ title="Add device"
+ />
+
+
+
+
+ );
}
export default CreateDevice;
diff --git a/ui/src/views/devices/DeviceActivation.tsx b/ui/src/views/devices/DeviceActivation.tsx
index 52b9ef13..b1ac4a67 100644
--- a/ui/src/views/devices/DeviceActivation.tsx
+++ b/ui/src/views/devices/DeviceActivation.tsx
@@ -1,5 +1,5 @@
-import React, { Component } from "react";
-import { RouteComponentProps } from "react-router-dom";
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
import { Space, Form, Button, Row, Col, InputNumber, Alert } from "antd";
@@ -26,11 +26,11 @@ interface FormProps {
onFinish: (obj: DeviceActivationPb) => void;
}
-class LW10DeviceActivationForm extends Component {
- formRef = React.createRef();
+function LW10DeviceActivationForm(props: FormProps) {
+ const [form] = Form.useForm();
- onFinish = (values: DeviceActivationPb.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: DeviceActivationPb.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let da = new DeviceActivationPb();
da.setDevAddr(v.devAddr);
@@ -42,66 +42,56 @@ class LW10DeviceActivationForm extends Component {
da.setAFCntDown(v.nFCntDown);
da.setNFCntDown(v.nFCntDown);
- this.props.onFinish(da);
+ props.onFinish(da);
};
- render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (Re)activate device
-
-
-
- );
- }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (Re)activate device
+
+
+
+ );
}
-class LW11DeviceActivationForm extends Component {
- formRef = React.createRef();
+function LW11DeviceActivationForm(props: FormProps) {
+ const [form] = Form.useForm();
- onFinish = (values: DeviceActivationPb.AsObject) => {
- const v = Object.assign(this.props.initialValues.toObject(), values);
+ const onFinish = (values: DeviceActivationPb.AsObject) => {
+ const v = Object.assign(props.initialValues.toObject(), values);
let da = new DeviceActivationPb();
da.setDevAddr(v.devAddr);
@@ -113,162 +103,133 @@ class LW11DeviceActivationForm extends Component {
da.setAFCntDown(v.aFCntDown);
da.setNFCntDown(v.nFCntDown);
- this.props.onFinish(da);
+ props.onFinish(da);
};
- render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (Re)activate device
-
-
-
- );
- }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (Re)activate device
+
+
+
+ );
}
-interface IProps extends RouteComponentProps {
+interface IProps {
tenant: Tenant;
application: Application;
device: Device;
deviceProfile: DeviceProfile;
}
-interface IState {
- deviceActivation?: DeviceActivationPb;
- deviceActivationRequested: boolean;
-}
+function DeviceActivation(props: IProps) {
+ const navigate = useNavigate();
+ const [deviceActivation, setDeviceActivation] = useState(undefined);
+ const [deviceActivationRequested, setDeviceActivationRequested] = useState(false);
-class DeviceActivation extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- deviceActivationRequested: false,
- };
- }
-
- componentDidMount() {
+ useEffect(() => {
let req = new GetDeviceActivationRequest();
- req.setDevEui(this.props.device.getDevEui());
+ req.setDevEui(props.device.getDevEui());
DeviceStore.getActivation(req, (resp: GetDeviceActivationResponse) => {
- this.setState({
- deviceActivation: resp.getDeviceActivation(),
- deviceActivationRequested: true,
- });
+ setDeviceActivation(resp.getDeviceActivation());
+ setDeviceActivationRequested(true);
});
- }
+ }, [props]);
- onFinish = (obj: DeviceActivationPb) => {
+ const onFinish = (obj: DeviceActivationPb) => {
let req = new ActivateDeviceRequest();
- obj.setDevEui(this.props.device.getDevEui());
+ obj.setDevEui(props.device.getDevEui());
req.setDeviceActivation(obj);
DeviceStore.activate(req, () => {
- this.props.history.push(
- `/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${this.props.device.getDevEui()}`,
+ navigate(
+ `/tenants/${props.tenant.getId()}/applications/${props.application.getId()}/devices/${props.device.getDevEui()}`,
);
});
};
- render() {
- if (!this.state.deviceActivationRequested) {
- return null;
- }
-
- if (!this.state.deviceActivation && this.props.deviceProfile.getSupportsOtaa()) {
- return ;
- }
-
- let macVersion = this.props.deviceProfile.getMacVersion();
- const lw11 = macVersion === MacVersion.LORAWAN_1_1_0;
-
- let initialValues = new DeviceActivationPb();
- if (this.state.deviceActivation) {
- initialValues = this.state.deviceActivation;
- }
-
- return (
-
- {!lw11 && (
-
- )}
- {lw11 && (
-
- )}
-
- );
+ if (!deviceActivationRequested) {
+ return null;
}
+
+ if (!deviceActivation && props.deviceProfile.getSupportsOtaa()) {
+ return ;
+ }
+
+ let macVersion = props.deviceProfile.getMacVersion();
+ const lw11 = macVersion === MacVersion.LORAWAN_1_1_0;
+
+ let initialValues = new DeviceActivationPb();
+ if (deviceActivation) {
+ initialValues = deviceActivation;
+ }
+
+ return (
+
+ {!lw11 && (
+
+ )}
+ {lw11 && (
+
+ )}
+
+ );
}
export default DeviceActivation;
diff --git a/ui/src/views/devices/DeviceDashboard.tsx b/ui/src/views/devices/DeviceDashboard.tsx
index 58571644..c4bc0075 100644
--- a/ui/src/views/devices/DeviceDashboard.tsx
+++ b/ui/src/views/devices/DeviceDashboard.tsx
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import moment from "moment";
@@ -27,31 +27,18 @@ interface IProps {
lastSeenAt?: Date;
}
-interface IState {
- metricsAggregation: Aggregation;
- deviceMetrics?: GetDeviceMetricsResponse;
- deviceLinkMetrics?: GetDeviceLinkMetricsResponse;
- deviceMetricsLoaded: boolean;
- deviceLinkMetricsLoaded: boolean;
-}
+function DeviceDashboard(props: IProps) {
+ const [metricsAggregation, setMetricsAggregation] = useState(Aggregation.DAY);
+ const [deviceMetrics, setDeviceMetrics] = useState(undefined);
+ const [deviceLinkMetrics, setDeviceLinkMetrics] = useState(undefined);
+ const [deviceLinkMetricsLoaded, setDeviceLinkMetricsLoaded] = useState(false);
-class DeviceDashboard extends Component {
- constructor(props: IProps) {
- super(props);
+ useEffect(() => {
+ loadMetrics();
+ }, [props, metricsAggregation]);
- this.state = {
- metricsAggregation: Aggregation.DAY,
- deviceMetricsLoaded: false,
- deviceLinkMetricsLoaded: false,
- };
- }
-
- componentDidMount() {
- this.loadMetrics();
- }
-
- loadMetrics = () => {
- const agg = this.state.metricsAggregation;
+ const loadMetrics = () => {
+ const agg = metricsAggregation;
const end = moment();
let start = moment();
@@ -63,19 +50,12 @@ class DeviceDashboard extends Component {
start = start.subtract(12, "months");
}
- this.setState(
- {
- deviceMetricsLoaded: false,
- deviceLinkMetricsLoaded: false,
- },
- () => {
- this.loadLinkMetrics(start.toDate(), end.toDate(), agg);
- this.loadDeviceMetrics(start.toDate(), end.toDate(), agg);
- },
- );
+ setDeviceLinkMetricsLoaded(false);
+ loadLinkMetrics(start.toDate(), end.toDate(), agg);
+ loadDeviceMetrics(start.toDate(), end.toDate(), agg);
};
- loadDeviceMetrics = (start: Date, end: Date, agg: Aggregation) => {
+ const loadDeviceMetrics = (start: Date, end: Date, agg: Aggregation) => {
let startPb = new Timestamp();
let endPb = new Timestamp();
@@ -83,20 +63,17 @@ class DeviceDashboard extends Component {
endPb.fromDate(end);
let req = new GetDeviceMetricsRequest();
- req.setDevEui(this.props.device.getDevEui());
+ req.setDevEui(props.device.getDevEui());
req.setStart(startPb);
req.setEnd(endPb);
req.setAggregation(agg);
DeviceStore.getMetrics(req, (resp: GetDeviceMetricsResponse) => {
- this.setState({
- deviceMetrics: resp,
- deviceMetricsLoaded: true,
- });
+ setDeviceMetrics(resp);
});
};
- loadLinkMetrics = (start: Date, end: Date, agg: Aggregation) => {
+ const loadLinkMetrics = (start: Date, end: Date, agg: Aggregation) => {
let startPb = new Timestamp();
let endPb = new Timestamp();
@@ -104,176 +81,153 @@ class DeviceDashboard extends Component {
endPb.fromDate(end);
let req = new GetDeviceLinkMetricsRequest();
- req.setDevEui(this.props.device.getDevEui());
+ req.setDevEui(props.device.getDevEui());
req.setStart(startPb);
req.setEnd(endPb);
req.setAggregation(agg);
DeviceStore.getLinkMetrics(req, (resp: GetDeviceLinkMetricsResponse) => {
- this.setState({
- deviceLinkMetrics: resp,
- deviceLinkMetricsLoaded: true,
- });
+ setDeviceLinkMetrics(resp);
+ setDeviceLinkMetricsLoaded(true);
});
};
- onMetricsAggregationChange = (e: RadioChangeEvent) => {
- this.setState(
- {
- metricsAggregation: e.target.value,
- },
- this.loadMetrics,
- );
+ const onMetricsAggregationChange = (e: RadioChangeEvent) => {
+ setMetricsAggregation(e.target.value);
};
- render() {
- if (this.state.deviceLinkMetrics === undefined || this.state.deviceMetrics === undefined) {
- return null;
- }
-
- let deviceMetrics = [];
-
- {
- let states = this.state.deviceMetrics.getStatesMap();
- let keys = states.toArray().map(v => v[0]);
- keys.sort();
-
- for (let i = 0; i < keys.length; i += 3) {
- let items = keys.slice(i, i + 3).map(k => {
- let m = states.get(k)!;
- return (
-
-
-
-
-
- );
- });
-
- deviceMetrics.push({items}
);
- }
- }
-
- {
- let metrics = this.state.deviceMetrics.getMetricsMap();
- let keys = metrics.toArray().map(v => v[0]);
- keys.sort();
-
- for (let i = 0; i < keys.length; i += 3) {
- let items = keys.slice(i, i + 3).map(k => {
- let m = metrics.get(k)!;
- return (
-
-
-
- );
- });
-
- deviceMetrics.push({items}
);
- }
- }
-
- let lastSeenAt = "Never";
- if (this.props.lastSeenAt !== undefined) {
- lastSeenAt = moment(this.props.lastSeenAt).format("YYYY-MM-DD HH:mm:ss");
- }
-
- const loading = !this.state.deviceLinkMetricsLoaded || !this.state.deviceMetrics;
-
- const aggregations = (
-
- {loading && }
-
-
- 24h
-
-
- 31d
-
-
- 1y
-
-
- } onClick={this.loadMetrics} disabled={loading} />
-
- );
-
- return (
-
-
-
-