From 6f1638e87aacc469e17f335bea37e29ad0382191 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Thu, 27 Jul 2023 13:06:07 +0100 Subject: [PATCH] Refactor UI to function elements & update React + Ant. This refactor the UI components from class based element into function based elements. This makes it possible to use hooks that are used now by most React components. This also updates React and Ant to the latest versions (+ other dependencies). --- shell.nix | 2 +- ui/Makefile | 3 + ui/README.md | 46 - ui/package.json | 95 +- ui/public/index.html | 2 +- ui/src/App.tsx | 145 +- ui/src/components/Admin.tsx | 78 +- ui/src/components/AesKeyInput.tsx | 217 +- ui/src/components/Autocomplete.tsx | 111 +- ui/src/components/AutocompleteInput.tsx | 54 +- ui/src/components/CodeEditor.tsx | 99 +- ui/src/components/DataTable.tsx | 154 +- ui/src/components/DeleteConfirm.tsx | 64 +- ui/src/components/DevAddrInput.tsx | 217 +- ui/src/components/EuiInput.tsx | 217 +- ui/src/components/Header.tsx | 220 +- ui/src/components/LogTable.tsx | 255 +- ui/src/components/Map.tsx | 129 +- ui/src/components/Menu.tsx | 293 +- ui/src/components/MetricBar.tsx | 150 +- ui/src/components/MetricChart.tsx | 152 +- ui/src/components/MetricHeatmap.tsx | 228 +- ui/src/index.css | 9 +- ui/src/index.tsx | 13 +- ui/src/stores/ApplicationStore.ts | 2 + ui/src/stores/RelayStore.ts | 8 +- ui/src/views/api-keys/ApiKeyForm.tsx | 36 +- ui/src/views/api-keys/ApiKeyToken.tsx | 33 +- ui/src/views/api-keys/CreateAdminApiKey.tsx | 80 +- ui/src/views/api-keys/CreateTenantApiKey.tsx | 90 +- ui/src/views/api-keys/ListAdminApiKeys.tsx | 127 +- ui/src/views/api-keys/ListTenantApiKeys.tsx | 145 +- ui/src/views/applications/ApplicationForm.tsx | 42 +- .../views/applications/ApplicationLayout.tsx | 373 +- .../views/applications/ApplicationLoader.tsx | 113 +- .../views/applications/CreateApplication.tsx | 82 +- ui/src/views/applications/EditApplication.tsx | 29 +- .../views/applications/ListApplications.tsx | 104 +- .../views/applications/ListIntegrations.tsx | 98 +- .../applications/integrations/AwsSnsCard.tsx | 67 +- .../integrations/AwsSnsIntegrationForm.tsx | 98 +- .../integrations/AzureServiceBusCard.tsx | 67 +- .../AzureServiceBusIntegrationForm.tsx | 90 +- .../integrations/CreateAwsSnsIntegration.tsx | 31 +- .../CreateAzureServiceBusIntegration.tsx | 31 +- .../CreateGcpPubSubIntegration.tsx | 31 +- .../integrations/CreateHttpIntegration.tsx | 31 +- .../integrations/CreateIftttIntegration.tsx | 33 +- .../CreateInfluxDbIntegration.tsx | 31 +- .../CreateLoRaCloudIntegration.tsx | 39 +- .../CreateMyDevicesIntegration.tsx | 31 +- .../CreatePilotThingsIntegration.tsx | 31 +- .../CreateThingsBoardIntegration.tsx | 31 +- .../integrations/EditAwsSnsIntegration.tsx | 52 +- .../EditAzureServiceBusIntegration.tsx | 52 +- .../integrations/EditGcpPubSubIntegration.tsx | 52 +- .../integrations/EditHttpIntegration.tsx | 52 +- .../integrations/EditIftttIntegration.tsx | 56 +- .../integrations/EditInfluxDbIntegration.tsx | 52 +- .../integrations/EditLoRaCloudIntegration.tsx | 52 +- .../integrations/EditMyDevicesIntegration.tsx | 52 +- .../EditPilotThingsIntegration.tsx | 52 +- .../EditThingsBoardIntegration.tsx | 52 +- .../integrations/GcpPubSubCard.tsx | 67 +- .../integrations/GcpPubSubIntegrationForm.tsx | 99 +- .../integrations/GenerateMqttCertificate.tsx | 60 +- .../applications/integrations/HttpCard.tsx | 67 +- .../integrations/HttpIntegrationForm.tsx | 148 +- .../applications/integrations/IftttCard.tsx | 67 +- .../integrations/IftttIntegrationForm.tsx | 141 +- .../integrations/InfluxDbIntegrationForm.tsx | 175 +- .../integrations/InfluxdbCard.tsx | 67 +- .../integrations/LoRaCloudCard.tsx | 67 +- .../integrations/LoRaCloudIntegrationForm.tsx | 369 +- .../applications/integrations/MqttCard.tsx | 33 +- .../integrations/MyDevicesCard.tsx | 67 +- .../integrations/MyDevicesIntegrationForm.tsx | 96 +- .../integrations/PilotThingsCard.tsx | 67 +- .../PilotThingsIntegrationForm.tsx | 58 +- .../integrations/ThingsBoardCard.tsx | 67 +- .../ThingsBoardIntegrationForm.tsx | 61 +- ui/src/views/dashboard/Dashboard.tsx | 416 +- .../CreateDeviceProfileTemplate.tsx | 150 +- .../DeviceProfileTemplateForm.tsx | 866 +- .../EditDeviceProfileTemplate.tsx | 131 +- .../ListDeviceProfileTemplates.tsx | 110 +- .../device-profiles/CreateDeviceProfile.tsx | 164 +- .../device-profiles/DeviceProfileForm.tsx | 1871 ++-- .../device-profiles/EditDeviceProfile.tsx | 152 +- .../device-profiles/ListDeviceProfiles.tsx | 214 +- ui/src/views/devices/CreateDevice.tsx | 104 +- ui/src/views/devices/DeviceActivation.tsx | 345 +- ui/src/views/devices/DeviceDashboard.tsx | 354 +- ui/src/views/devices/DeviceEvents.tsx | 68 +- ui/src/views/devices/DeviceForm.tsx | 319 +- ui/src/views/devices/DeviceFrames.tsx | 68 +- ui/src/views/devices/DeviceLayout.tsx | 371 +- ui/src/views/devices/DeviceQueue.tsx | 268 +- ui/src/views/devices/EditDevice.tsx | 19 +- ui/src/views/devices/ListDevices.tsx | 375 +- ui/src/views/devices/SetDeviceKeys.tsx | 233 +- ui/src/views/gateways/CreateGateway.tsx | 84 +- ui/src/views/gateways/EditGateway.tsx | 27 +- ui/src/views/gateways/GatewayCertificate.tsx | 56 +- ui/src/views/gateways/GatewayDashboard.tsx | 205 +- ui/src/views/gateways/GatewayForm.tsx | 358 +- ui/src/views/gateways/GatewayFrames.tsx | 69 +- ui/src/views/gateways/GatewayLayout.tsx | 240 +- ui/src/views/gateways/ListGateways.tsx | 378 +- .../multicast-groups/CreateMulticastGroup.tsx | 100 +- .../multicast-groups/EditMulticastGroup.tsx | 29 +- .../ListMulticastGroupDevices.tsx | 106 +- .../ListMulticastGroupGateways.tsx | 106 +- .../multicast-groups/ListMulticastGroups.tsx | 103 +- .../multicast-groups/MulticastGroupForm.tsx | 285 +- .../multicast-groups/MulticastGroupLayout.tsx | 235 +- ui/src/views/regions/ListRegions.tsx | 123 +- ui/src/views/regions/RegionDetails.tsx | 239 +- ui/src/views/relays/ListRelayDevices.tsx | 114 +- ui/src/views/relays/ListRelays.tsx | 75 +- ui/src/views/relays/RelayLayout.tsx | 159 +- ui/src/views/tenants/CreateTenant.tsx | 68 +- ui/src/views/tenants/CreateTenantUser.tsx | 82 +- ui/src/views/tenants/EditTenant.tsx | 21 +- ui/src/views/tenants/EditTenantUser.tsx | 151 +- ui/src/views/tenants/ListTenantUsers.tsx | 168 +- ui/src/views/tenants/ListTenants.tsx | 201 +- ui/src/views/tenants/TenantDashboard.tsx | 389 +- ui/src/views/tenants/TenantForm.tsx | 150 +- ui/src/views/tenants/TenantLayout.tsx | 129 +- ui/src/views/tenants/TenantLoader.tsx | 174 +- ui/src/views/tenants/TenantRedirect.tsx | 36 +- ui/src/views/tenants/TenantUserForm.tsx | 138 +- ui/src/views/users/ChangeUserPassword.tsx | 97 +- ui/src/views/users/CreateUser.tsx | 68 +- ui/src/views/users/EditUser.tsx | 131 +- ui/src/views/users/ListUsers.tsx | 123 +- ui/src/views/users/Login.tsx | 197 +- ui/src/views/users/PasswordForm.tsx | 48 +- ui/src/views/users/UserForm.tsx | 74 +- ui/yarn.lock | 8828 ++++++++--------- 141 files changed, 12760 insertions(+), 15459 deletions(-) delete mode 100644 ui/README.md 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: ( - - ), - }, - ]} - /> - ); + const copyMenu = ( + + HEX string + + ), + }, + { + key: "2", + label: ( + + ), + }, + ]} + /> + ); - const addon = ( - - - + + - - - - - ); + + + ); - 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