Implement automatic formatting for JS / TSX source code.

Implemented automatic code formatting for JS/TSX using prettier and husky
with pre-commit hook

Signed-off-by: SAGAR PATEL <sagar.a.patel@slscorp.com>

Co-authored-by: Orne Brocaar <info@brocaar.com>
This commit is contained in:
SAGAR PATEL
2022-05-02 15:28:26 +05:30
committed by GitHub
parent d8377cd26a
commit 2ea86b2ca2
137 changed files with 3139 additions and 2515 deletions

View File

@ -2,7 +2,6 @@ import { Component } from "react";
import SessionStore from "../stores/SessionStore";
interface IProps {
tenantId?: string;
isDeviceAdmin?: boolean;
@ -68,14 +67,14 @@ class Admin extends Component<IProps, IState> {
});
}
}
}
};
render() {
if (this.state.admin) {
return(this.props.children);
return this.props.children;
}
return(null);
return null;
}
}

View File

@ -4,9 +4,9 @@ import { Input, Select, Button, Space, Form } from "antd";
import { ReloadOutlined } from "@ant-design/icons";
interface IProps {
formRef: React.RefObject<any>,
label: string,
name: string,
formRef: React.RefObject<any>;
label: string;
name: string;
required?: boolean;
value?: string;
disabled?: boolean;
@ -18,7 +18,6 @@ interface IState {
value: string;
}
class AesKeyInput extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -39,7 +38,7 @@ class AesKeyInput extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({
[this.props.name]: value,
});
}
};
componentDidMount() {
if (this.props.value) {
@ -51,7 +50,7 @@ class AesKeyInput extends Component<IProps, IState> {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
const match = v.match(/[A-Fa-f0-9]/g);
let value = "";
if (match) {
@ -62,10 +61,13 @@ class AesKeyInput extends Component<IProps, IState> {
}
}
this.setState({
value: value,
}, this.updateField);
}
this.setState(
{
value: value,
},
this.updateField,
);
};
onByteOrderSelect = (v: string) => {
if (v === this.state.byteOrder) {
@ -79,21 +81,27 @@ class AesKeyInput extends Component<IProps, IState> {
const current = this.state.value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
this.setState({
value: bytes.reverse().join(""),
}, this.updateField);
}
this.setState(
{
value: bytes.reverse().join(""),
},
this.updateField,
);
};
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);
}
let key = Buffer.from(b).toString("hex");
this.setState(
{
value: key,
},
this.updateField,
);
};
render() {
const addon = (
@ -102,23 +110,34 @@ class AesKeyInput extends Component<IProps, IState> {
<Select.Option value="msb">MSB</Select.Option>
<Select.Option value="lsb">LSB</Select.Option>
</Select>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}><ReloadOutlined /></Button>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}>
<ReloadOutlined />
</Button>
</Space>
);
return(
return (
<Form.Item
rules={[{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{32}/g),
}]}
rules={[
{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{32}/g),
},
]}
label={this.props.label}
name={this.props.name}
tooltip={this.props.tooltip}
>
<Input hidden />
<Input.Password id={`${this.props.name}Render`} onChange={this.onChange} addonAfter={!this.props.disabled && addon} style={{fontFamily: "monospace"}} value={this.state.value} disabled={this.props.disabled} />
<Input.Password
id={`${this.props.name}Render`}
onChange={this.onChange}
addonAfter={!this.props.disabled && addon}
style={{ fontFamily: "monospace" }}
value={this.state.value}
disabled={this.props.disabled}
/>
</Form.Item>
);
}

View File

@ -2,24 +2,23 @@ import React, { Component } from "react";
import { Select } from "antd";
export type OptionsCallbackFunc = (o: {label: string, value: string}[]) => void;
export type OptionCallbackFunc = (o: {label: string, value: string}) => void;
export type OptionsCallbackFunc = (o: { label: string; value: string }[]) => void;
export type OptionCallbackFunc = (o: { label: string; value: string }) => void;
interface IProps {
placeholder: string;
className: string;
value?: string,
getOption: (s: string, fn: OptionCallbackFunc) => void,
getOptions: (s: string, fn: OptionsCallbackFunc) => void,
onSelect?: (s: string) => void,
value?: string;
getOption: (s: string, fn: OptionCallbackFunc) => void;
getOptions: (s: string, fn: OptionsCallbackFunc) => void;
onSelect?: (s: string) => void;
}
interface IState {
option?: {label: string, value: string};
options: {label: string, value: string}[];
option?: { label: string; value: string };
options: { label: string; value: string }[];
}
class Autocomplete extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -31,7 +30,7 @@ class Autocomplete extends Component<IProps, IState> {
componentDidMount() {
if (this.props.value && this.props.value !== "") {
this.props.getOption(this.props.value, (o: {label: string, value: string}) => {
this.props.getOption(this.props.value, (o: { label: string; value: string }) => {
this.setState({
options: [o],
});
@ -45,7 +44,7 @@ class Autocomplete extends Component<IProps, IState> {
}
if (this.props.value && this.props.value !== "") {
this.props.getOption(this.props.value, (o: {label: string, value: string}) => {
this.props.getOption(this.props.value, (o: { label: string; value: string }) => {
this.setState({
options: [o],
});
@ -54,7 +53,7 @@ class Autocomplete extends Component<IProps, IState> {
}
onFocus = () => {
this.props.getOptions("", (options) => {
this.props.getOptions("", options => {
if (this.state.option !== undefined) {
const selected = this.state.option.value;
@ -67,30 +66,30 @@ class Autocomplete extends Component<IProps, IState> {
options: options,
});
});
}
};
onSearch = (value: string) => {
this.props.getOptions(value, (options) => {
this.props.getOptions(value, options => {
this.setState({
options: options,
});
});
}
};
onSelect = (value: string, option: any) => {
this.setState({
option: {label: option.label, value: option.value},
option: { label: option.label, value: option.value },
});
if (this.props.onSelect !== undefined) {
this.props.onSelect(value);
}
}
};
render() {
const { getOption, getOptions, ...otherProps } = this.props;
return(
return (
<Select
showSearch
options={this.state.options}

View File

@ -4,26 +4,26 @@ import { Form } from "antd";
import Autocomplete, { OptionCallbackFunc, OptionsCallbackFunc } from "./Autocomplete";
interface IProps {
formRef: React.RefObject<any>,
label: string,
name: string,
formRef: React.RefObject<any>;
label: string;
name: string;
required?: boolean;
value?: string;
getOption: (s: string, fn: OptionCallbackFunc) => void,
getOptions: (s: string, fn: OptionsCallbackFunc) => void,
getOption: (s: string, fn: OptionCallbackFunc) => void;
getOptions: (s: string, fn: OptionsCallbackFunc) => void;
}
class AutocompleteInput extends Component<IProps> {
render() {
return(
return (
<Form.Item
rules={[{
required: this.props.required,
message: `Please select a ${this.props.label}`,
}]}
rules={[
{
required: this.props.required,
message: `Please select a ${this.props.label}`,
},
]}
label={this.props.label}
name={this.props.name}
>

View File

@ -1,15 +1,14 @@
import React, { Component } from "react";
import {Controlled as CodeMirror} from "react-codemirror2";
import { Controlled as CodeMirror } from "react-codemirror2";
import { Form } from "antd";
import "codemirror/mode/javascript/javascript";
interface IProps {
formRef: React.RefObject<any>,
label?: string,
name: string,
formRef: React.RefObject<any>;
label?: string;
name: string;
required?: boolean;
value?: string;
disabled?: boolean;
@ -20,7 +19,6 @@ interface IState {
value: string;
}
class CodeEditor extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -43,13 +41,16 @@ class CodeEditor extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({
[this.props.name]: value,
});
}
};
handleChange = (editor: any, data: any, newCode: string) => {
this.setState({
value: newCode,
}, this.updateField);
}
this.setState(
{
value: newCode,
},
this.updateField,
);
};
render() {
const codeMirrorOptions = {
@ -60,21 +61,13 @@ class CodeEditor extends Component<IProps, IState> {
};
return (
<Form.Item
label={this.props.label}
name={this.props.name}
tooltip={this.props.tooltip}
>
<div style={{border: "1px solid #cccccc"}}>
<CodeMirror
value={this.state.value}
options={codeMirrorOptions}
onBeforeChange={this.handleChange}
/>
<Form.Item label={this.props.label} name={this.props.name} tooltip={this.props.tooltip}>
<div style={{ border: "1px solid #cccccc" }}>
<CodeMirror value={this.state.value} options={codeMirrorOptions} onBeforeChange={this.handleChange} />
</div>
</Form.Item>
);
}
}
export default CodeEditor
export default CodeEditor;

View File

@ -5,10 +5,8 @@ import { ColumnsType } from "antd/es/table";
import SessionStore from "../stores/SessionStore";
export type GetPageCallbackFunc = (totalCount: number, rows: object[]) => void;
interface IProps {
columns: ColumnsType<any>;
getPage: (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => void;
@ -23,10 +21,9 @@ interface IState {
pageSize: number;
currentPage: number;
rows: object[];
loading: boolean,
loading: boolean;
}
class DataTable extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -53,37 +50,40 @@ class DataTable extends Component<IProps, IState> {
}
onChangePage = (page: number, pageSize?: number | void) => {
this.setState({
loading: true,
}, () => {
let pz = pageSize;
if (!pz) {
pz = this.state.pageSize;
}
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,
this.props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => {
this.setState({
currentPage: page,
totalCount: totalCount,
rows: rows,
pageSize: pz || 0,
loading: false,
});
});
});
});
}
},
);
};
onShowSizeChange = (page: number, pageSize: number) => {
this.onChangePage(page, pageSize);
SessionStore.setRowsPerPage(pageSize);
}
};
onRowsSelectChange = (ids: React.Key[]) => {
const idss = ids as string[];
if (this.props.onRowsSelectChange) {
this.props.onRowsSelectChange(idss);
}
}
};
render() {
const { getPage, refreshKey, ...otherProps } = this.props;
@ -97,12 +97,12 @@ class DataTable extends Component<IProps, IState> {
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,
current: this.state.currentPage,
total: this.state.totalCount,
pageSize: this.state.pageSize,
onChange: this.onChangePage,
showSizeChanger: true,
onShowSizeChange: this.onShowSizeChange,
};
}
@ -113,8 +113,7 @@ class DataTable extends Component<IProps, IState> {
};
}
return(
return (
<Table
loading={loadingProps}
dataSource={this.state.rows}

View File

@ -1,7 +1,6 @@
import React, { Component } from "react";
import { Popover, Button, Typography, Space, Input } from 'antd';
import { Popover, Button, Typography, Space, Input } from "antd";
interface IProps {
typ: string;
@ -9,17 +8,15 @@ interface IProps {
onConfirm: () => void;
}
interface ConfirmState {
confirm: string;
}
class DeleteConfirmContent extends Component<IProps, ConfirmState> {
constructor(props: IProps) {
super(props);
this.state = {
confirm: '',
confirm: "",
};
}
@ -27,24 +24,31 @@ class DeleteConfirmContent extends Component<IProps, ConfirmState> {
this.setState({
confirm: e.target.value,
});
}
};
render() {
return(
return (
<Space direction="vertical">
<Typography.Text>Enter '{this.props.confirm}' to confirm you want to delete this {this.props.typ}:</Typography.Text>
<Input placeholder={this.props.confirm} onChange={this.onChange} />
<Button onClick={this.props.onConfirm} disabled={this.state.confirm !== this.props.confirm} style={{float: "right"}}>Delete</Button>
<Typography.Text>
Enter '{this.props.confirm}' to confirm you want to delete this {this.props.typ}:
</Typography.Text>
<Input placeholder={this.props.confirm} onChange={this.onChange} />
<Button
onClick={this.props.onConfirm}
disabled={this.state.confirm !== this.props.confirm}
style={{ float: "right" }}
>
Delete
</Button>
</Space>
);
}
}
class DeleteConfirm extends Component<IProps> {
render() {
return(
<Popover content={<DeleteConfirmContent {...this.props}/>} trigger="click" placement="left">
return (
<Popover content={<DeleteConfirmContent {...this.props} />} trigger="click" placement="left">
{this.props.children}
</Popover>
);

View File

@ -3,21 +3,15 @@ import React, { Component } from "react";
import { Input, Select, Button, Space, Form } from "antd";
import { ReloadOutlined } from "@ant-design/icons";
import {
GetRandomDevAddrRequest,
GetRandomDevAddrResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import { GetRandomDevAddrRequest, GetRandomDevAddrResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import DeviceStore from "../stores/DeviceStore";
interface IProps {
formRef: React.RefObject<any>,
label: string,
name: string,
devEui: string,
formRef: React.RefObject<any>;
label: string;
name: string;
devEui: string;
required?: boolean;
value?: string;
disabled?: boolean;
@ -28,7 +22,6 @@ interface IState {
value: string;
}
class DevAddrInput extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -49,7 +42,7 @@ class DevAddrInput extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({
[this.props.name]: value,
});
}
};
componentDidMount() {
if (this.props.value) {
@ -61,7 +54,7 @@ class DevAddrInput extends Component<IProps, IState> {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
const match = v.match(/[A-Fa-f0-9]/g);
let value = "";
if (match) {
@ -72,10 +65,13 @@ class DevAddrInput extends Component<IProps, IState> {
}
}
this.setState({
value: value,
}, this.updateField);
}
this.setState(
{
value: value,
},
this.updateField,
);
};
onByteOrderSelect = (v: string) => {
if (v === this.state.byteOrder) {
@ -89,21 +85,27 @@ class DevAddrInput extends Component<IProps, IState> {
const current = this.state.value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
this.setState({
value: bytes.reverse().join(""),
}, this.updateField);
}
this.setState(
{
value: bytes.reverse().join(""),
},
this.updateField,
);
};
generateRandom = () => {
let req = new GetRandomDevAddrRequest();
req.setDevEui(this.props.devEui);
DeviceStore.getRandomDevAddr(req, (resp: GetRandomDevAddrResponse) => {
this.setState({
value: resp.getDevAddr(),
}, this.updateField);
this.setState(
{
value: resp.getDevAddr(),
},
this.updateField,
);
});
}
};
render() {
const addon = (
@ -112,22 +114,33 @@ class DevAddrInput extends Component<IProps, IState> {
<Select.Option value="msb">MSB</Select.Option>
<Select.Option value="lsb">LSB</Select.Option>
</Select>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}><ReloadOutlined /></Button>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}>
<ReloadOutlined />
</Button>
</Space>
);
return(
return (
<Form.Item
rules={[{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{8}/g),
}]}
rules={[
{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{8}/g),
},
]}
label={this.props.label}
name={this.props.name}
>
<Input hidden />
<Input id={`${this.props.name}Render`} onChange={this.onChange} addonAfter={!this.props.disabled && addon} style={{fontFamily: "monospace"}} value={this.state.value} disabled={this.props.disabled} />
<Input
id={`${this.props.name}Render`}
onChange={this.onChange}
addonAfter={!this.props.disabled && addon}
style={{ fontFamily: "monospace" }}
value={this.state.value}
disabled={this.props.disabled}
/>
</Form.Item>
);
}

View File

@ -4,9 +4,9 @@ import { Input, Select, Button, Space, Form } from "antd";
import { ReloadOutlined } from "@ant-design/icons";
interface IProps {
formRef: React.RefObject<any>,
label: string,
name: string,
formRef: React.RefObject<any>;
label: string;
name: string;
required?: boolean;
value?: string;
disabled?: boolean;
@ -17,7 +17,6 @@ interface IState {
value: string;
}
class EuiInput extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -38,7 +37,7 @@ class EuiInput extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({
[this.props.name]: value,
});
}
};
componentDidMount() {
if (this.props.value) {
@ -50,7 +49,7 @@ class EuiInput extends Component<IProps, IState> {
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = e.target.value;
const match = v.match(/[A-Fa-f0-9]/g);
const match = v.match(/[A-Fa-f0-9]/g);
let value = "";
if (match) {
@ -61,10 +60,13 @@ class EuiInput extends Component<IProps, IState> {
}
}
this.setState({
value: value,
}, this.updateField);
}
this.setState(
{
value: value,
},
this.updateField,
);
};
onByteOrderSelect = (v: string) => {
if (v === this.state.byteOrder) {
@ -78,21 +80,27 @@ class EuiInput extends Component<IProps, IState> {
const current = this.state.value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
this.setState({
value: bytes.reverse().join(""),
}, this.updateField);
}
this.setState(
{
value: bytes.reverse().join(""),
},
this.updateField,
);
};
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);
}
let key = Buffer.from(b).toString("hex");
this.setState(
{
value: key,
},
this.updateField,
);
};
render() {
const addon = (
@ -101,22 +109,33 @@ class EuiInput extends Component<IProps, IState> {
<Select.Option value="msb">MSB</Select.Option>
<Select.Option value="lsb">LSB</Select.Option>
</Select>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}><ReloadOutlined /></Button>
<Button type="text" size="small" shape="circle" onClick={this.generateRandom}>
<ReloadOutlined />
</Button>
</Space>
);
return(
return (
<Form.Item
rules={[{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{16}/g),
}]}
rules={[
{
required: this.props.required,
message: `Please enter a valid ${this.props.label}`,
pattern: new RegExp(/[A-Fa-f0-9]{16}/g),
},
]}
label={this.props.label}
name={this.props.name}
>
<Input hidden />
<Input id={`${this.props.name}Render`} onChange={this.onChange} addonAfter={!this.props.disabled && addon} style={{fontFamily: "monospace"}} value={this.state.value} disabled={this.props.disabled} />
<Input
id={`${this.props.name}Render`}
onChange={this.onChange}
addonAfter={!this.props.disabled && addon}
style={{ fontFamily: "monospace" }}
value={this.state.value}
disabled={this.props.disabled}
/>
</Form.Item>
);
}

View File

@ -13,27 +13,20 @@ import {
import InternalStore from "../stores/InternalStore";
interface IProps {
user: User;
}
interface IState {
searchResult?: GlobalSearchResponse;
settings?: SettingsResponse,
settings?: SettingsResponse;
}
const renderTitle = (title: string) => (
<span>
{title}
</span>
);
const renderTitle = (title: string) => <span>{title}</span>;
const renderItem = (title: string, url: string) => ({
value: title,
label: (
<Link to={url}>{title}</Link>
),
label: <Link to={url}>{title}</Link>,
});
class Header extends Component<IProps, IState> {
@ -65,7 +58,7 @@ class Header extends Component<IProps, IState> {
searchResult: resp,
});
});
}
};
render() {
if (this.state.settings === undefined) {
@ -76,9 +69,11 @@ class Header extends Component<IProps, IState> {
const menu = (
<Menu>
{!oidcEnabled && <Menu.Item>
<Link to={`/users/${this.props.user.getId()}/password`}>Change password</Link>
</Menu.Item>}
{!oidcEnabled && (
<Menu.Item>
<Link to={`/users/${this.props.user.getId()}/password`}>Change password</Link>
</Menu.Item>
)}
<Menu.Item>
<Link to="/login">Logout</Link>
</Menu.Item>
@ -90,19 +85,19 @@ class Header extends Component<IProps, IState> {
options: any[];
}[] = [
{
label: renderTitle('Tenants'),
label: renderTitle("Tenants"),
options: [],
},
{
label: renderTitle('Gateways'),
label: renderTitle("Gateways"),
options: [],
},
{
label: renderTitle('Applications'),
label: renderTitle("Applications"),
options: [],
},
{
label: renderTitle('Devices'),
label: renderTitle("Devices"),
options: [],
},
];
@ -110,24 +105,36 @@ class Header extends Component<IProps, IState> {
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()}`))
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()}`))
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()}`))
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()}`));
options[3].options.push(
renderItem(
res.getDeviceName(),
`/tenants/${res.getTenantId()}/applications/${res.getApplicationId()}/devices/${res.getDeviceDevEui()}`,
),
);
}
}
}
return(
return (
<div>
<img className="logo" alt="ChirpStack" src="/logo.png" />
<div className="actions">
@ -138,15 +145,19 @@ class Header extends Component<IProps, IState> {
options={options}
onSearch={this.onSearch}
>
<Input.Search placeholder="Search..." style={{width: 500, marginTop: -5}} />
<Input.Search placeholder="Search..." style={{ width: 500, marginTop: -5 }} />
</AutoComplete>
</div>
<div className="help">
<a href="https://www.chirpstack.io" target="_blank" rel="noreferrer"><Button icon={<QuestionOutlined />}/></a>
<a href="https://www.chirpstack.io" target="_blank" rel="noreferrer">
<Button icon={<QuestionOutlined />} />
</a>
</div>
<div className="user">
<Dropdown overlay={menu} placement="bottomRight" trigger={['click']}>
<Button type="primary" icon={<UserOutlined />}>{this.props.user.getEmail()} <DownOutlined /></Button>
<Dropdown overlay={menu} placement="bottomRight" trigger={["click"]}>
<Button type="primary" icon={<UserOutlined />}>
{this.props.user.getEmail()} <DownOutlined />
</Button>
</Dropdown>
</div>
</div>

View File

@ -3,28 +3,25 @@ import React, { Component } from "react";
import { color } from "chart.js/helpers";
import { Chart } from "react-chartjs-2";
interface HeatmapData {
x: string;
y: Array<[string, number]>;
}
interface IProps {
data: HeatmapData[];
fromColor: string;
toColor: string;
}
class Heatmap extends Component<IProps> {
render() {
if (this.props.data.length === 0) {
return null;
}
let xSet: {[key: string]: any} = {};
let ySet: {[key: string]: any} = {};
let xSet: { [key: string]: any } = {};
let ySet: { [key: string]: any } = {};
let dataData: {
x: string;
@ -45,18 +42,22 @@ class Heatmap extends Component<IProps> {
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();
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;
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]));
result[i] = Math.round(result[i] + factor * (ctx.dataset.toColor[i] - ctx.dataset.fromColor[i]));
}
return color(result).rgbString();
@ -94,19 +95,18 @@ class Heatmap extends Component<IProps> {
grid: {
display: false,
},
},
},
plugins: {
legend: {display: false},
legend: { display: false },
tooltip: {
callbacks: {
title: () => {
return '';
return "";
},
label: (ctx: any) => {
const v = ctx.dataset.data[ctx.dataIndex].v;
return 'Count: ' + v;
return "Count: " + v;
},
},
},
@ -136,9 +136,7 @@ class Heatmap extends Component<IProps> {
}
}
return(
<Chart type="matrix" data={data} options={options} />
);
return <Chart type="matrix" data={data} options={options} />;
}
}

View File

@ -3,11 +3,10 @@ import moment from "moment";
import JSONTreeOriginal from "react-json-tree";
import { Tag, Drawer, Button, Table, Spin } from "antd";
import { ZoomInOutlined } from '@ant-design/icons';
import { ZoomInOutlined } from "@ant-design/icons";
import { LogItem } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
interface IProps {
logs: LogItem[];
}
@ -15,8 +14,7 @@ interface IProps {
interface IState {
drawerOpen: boolean;
body: any;
};
}
class LogTable extends Component<IProps, IState> {
constructor(props: IProps) {
@ -32,7 +30,7 @@ class LogTable extends Component<IProps, IState> {
this.setState({
drawerOpen: false,
});
}
};
onDrawerOpen = (body: any) => {
return () => {
@ -41,47 +39,59 @@ class LogTable extends Component<IProps, IState> {
drawerOpen: true,
});
};
}
};
render() {
let items = this.props.logs.map((l, i) => l.toObject());
let body = JSON.parse(this.state.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',
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(
return (
<div>
<Drawer title="Details" placement="right" width={650} onClose={this.onDrawerClose} visible={this.state.drawerOpen}>
<Drawer
title="Details"
placement="right"
width={650}
onClose={this.onDrawerClose}
visible={this.state.drawerOpen}
>
<JSONTreeOriginal
data={body}
theme={theme}
hideRoot={true}
shouldExpandNode={() => {return true}}
shouldExpandNode={() => {
return true;
}}
/>
</Drawer>
{items.length !== 0 && <div className="spinner"><Spin /></div>}
{items.length !== 0 && (
<div className="spinner">
<Spin />
</div>
)}
<Table
showHeader={false}
loading={items.length === 0}
loading={items.length === 0}
dataSource={items}
pagination={false}
columns={[
@ -94,26 +104,43 @@ class LogTable extends Component<IProps, IState> {
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) => <Button icon={<ZoomInOutlined />} type="primary" shape="round" size="small" onClick={this.onDrawerOpen(obj.body)}>{text}</Button>,
render: (text, obj) => (
<Button
icon={<ZoomInOutlined />}
type="primary"
shape="round"
size="small"
onClick={this.onDrawerOpen(obj.body)}
>
{text}
</Button>
),
},
{
title: "Properties",
dataIndex: "properties",
key: "properties",
render: (text, obj) => obj.propertiesMap.map((p, i) => {
if (p[1] !== "") {
return <Tag><pre>{p[0]}: {p[1]}</pre></Tag>
}
render: (text, obj) =>
obj.propertiesMap.map((p, i) => {
if (p[1] !== "") {
return (
<Tag>
<pre>
{p[0]}: {p[1]}
</pre>
</Tag>
);
}
return null;
}),
return null;
}),
},
]}
/>

View File

@ -3,8 +3,7 @@ import React, { Component } from "react";
import L, { LatLngTuple, FitBoundsOptions } from "leaflet";
import "leaflet.awesome-markers";
import { MarkerProps as LMarkerProps } from "react-leaflet";
import { MapContainer, Marker as LMarker, TileLayer } from 'react-leaflet';
import { MapContainer, Marker as LMarker, TileLayer } from "react-leaflet";
interface IProps {
height: number;
@ -17,7 +16,6 @@ interface IState {
map?: L.Map;
}
class Map extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -25,22 +23,25 @@ class Map extends Component<IProps, IState> {
}
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);
}
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);
}
});
}
if (this.props.bounds !== undefined) {
map.fitBounds(this.props.bounds, this.props.boundsOptions);
}
},
);
};
componentDidUpdate(oldProps: IProps) {
if (this.props === oldProps) {
@ -63,7 +64,7 @@ class Map extends Component<IProps, IState> {
height: this.props.height,
};
return(
return (
<MapContainer
bounds={this.props.bounds}
boundsOptions={this.props.boundsOptions}
@ -84,7 +85,17 @@ class Map extends Component<IProps, IState> {
}
}
export type MarkerColor = "red" | "darkred" | "orange" | "green" | "darkgreen" | "blue" | "purple" | "darkpurple" | "cadetblue" | undefined;
export type MarkerColor =
| "red"
| "darkred"
| "orange"
| "green"
| "darkgreen"
| "blue"
| "purple"
| "darkpurple"
| "cadetblue"
| undefined;
interface MarkerProps extends LMarkerProps {
position: [number, number];
@ -100,11 +111,12 @@ export class Marker extends Component<MarkerProps> {
icon: faIcon,
prefix: "fa",
markerColor: color,
});
});
return(
<LMarker icon={iconMarker} position={position} {...otherProps}>{this.props.children}</LMarker>
return (
<LMarker icon={iconMarker} position={position} {...otherProps}>
{this.props.children}
</LMarker>
);
}
}

View File

@ -2,9 +2,22 @@ import React, { Component } from "react";
import { withRouter, RouteComponentProps, Link } from "react-router-dom";
import { Menu, MenuProps } from "antd";
import { CloudOutlined, HomeOutlined, UserOutlined, DashboardOutlined, KeyOutlined, WifiOutlined, ControlOutlined, AppstoreOutlined } from "@ant-design/icons";
import {
CloudOutlined,
HomeOutlined,
UserOutlined,
DashboardOutlined,
KeyOutlined,
WifiOutlined,
ControlOutlined,
AppstoreOutlined,
} from "@ant-design/icons";
import { GetTenantResponse, ListTenantsRequest, ListTenantsResponse } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import {
GetTenantResponse,
ListTenantsRequest,
ListTenantsResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import Autocomplete, { OptionCallbackFunc, OptionsCallbackFunc } from "../components/Autocomplete";
import TenantStore from "../stores/TenantStore";
@ -48,7 +61,7 @@ class SideMenu extends Component<RouteComponentProps, IState> {
this.setState({
tenantId: SessionStore.getTenantId(),
});
}
};
getTenantOptions = (search: string, fn: OptionsCallbackFunc) => {
let req = new ListTenantsRequest();
@ -56,85 +69,86 @@ class SideMenu extends Component<RouteComponentProps, IState> {
req.setLimit(10);
TenantStore.list(req, (resp: ListTenantsResponse) => {
const options = resp.getResultList().map((o, i) => {return {label: o.getName(), value: o.getId()}});
const options = resp.getResultList().map((o, i) => {
return { label: o.getName(), value: o.getId() };
});
fn(options);
});
}
};
getTenantOption = (id: string, fn: OptionCallbackFunc) => {
TenantStore.get(id, (resp: GetTenantResponse) => {
const tenant = resp.getTenant();
if (tenant) {
fn({label: tenant.getName(), value: tenant.getId()});
fn({ label: tenant.getName(), value: tenant.getId() });
}
});
}
};
onTenantSelect = (value: string) => {
SessionStore.setTenantId(value);
this.props.history.push(`/tenants/${value}`);
}
};
parseLocation = () => {
const path = this.props.history.location.pathname;
const tenantRe = /\/tenants\/([\w-]{36})/g;
const match = tenantRe.exec(path);
if (match !== null && (this.state.tenantId !== match[1])) {
if (match !== null && this.state.tenantId !== match[1]) {
SessionStore.setTenantId(match[1]);
}
// ns dashboard
if (path === "/dashboard") {
this.setState({selectedKey: "ns-dashboard"});
this.setState({ selectedKey: "ns-dashboard" });
}
// ns tenants
if (/\/tenants(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-tenants"});
this.setState({ selectedKey: "ns-tenants" });
}
// ns tenants
if (/\/users(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-users"});
this.setState({ selectedKey: "ns-users" });
}
// ns api keys
if (/\/api-keys(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-api-keys"});
this.setState({ selectedKey: "ns-api-keys" });
}
// tenant dashboard
if (/\/tenants\/[\w-]{36}/g.exec(path)) {
this.setState({selectedKey: "tenant-dashboard"});
this.setState({ selectedKey: "tenant-dashboard" });
}
// tenant users
if (/\/tenants\/[\w-]{36}\/users.*/g.exec(path)) {
this.setState({selectedKey: "tenant-users"});
this.setState({ selectedKey: "tenant-users" });
}
// tenant api-keys
if (/\/tenants\/[\w-]{36}\/api-keys.*/g.exec(path)) {
this.setState({selectedKey: "tenant-api-keys"});
this.setState({ selectedKey: "tenant-api-keys" });
}
// tenant device-profiles
if (/\/tenants\/[\w-]{36}\/device-profiles.*/g.exec(path)) {
this.setState({selectedKey: "tenant-device-profiles"});
this.setState({ selectedKey: "tenant-device-profiles" });
}
// tenant gateways
if (/\/tenants\/[\w-]{36}\/gateways.*/g.exec(path)) {
this.setState({selectedKey: "tenant-gateways"});
this.setState({ selectedKey: "tenant-gateways" });
}
// tenant applications
if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) {
this.setState({selectedKey: "tenant-applications"});
this.setState({ selectedKey: "tenant-applications" });
}
}
};
render() {
const tenantId = this.state.tenantId;
@ -170,13 +184,13 @@ class SideMenu extends Component<RouteComponentProps, IState> {
});
}
return(
return (
<div>
<Autocomplete
placeholder="Select tenant"
className="organiation-select"
getOption={this.getTenantOption}
getOptions={this.getTenantOptions}
getOptions={this.getTenantOptions}
onSelect={this.onTenantSelect}
value={this.state.tenantId}
/>