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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 3139 additions and 2515 deletions

2
ui/.prettierignore Normal file
View File

@ -0,0 +1,2 @@
build
node_modules

13
ui/.prettierrc.json Normal file
View File

@ -0,0 +1,13 @@
{
"trailingComma": "all",
"tabWidth": 2,
"endOfLine": "auto",
"semi": true,
"singleQuote": false,
"printWidth": 120,
"bracketSpacing": true,
"arrowParens": "avoid",
"jsxSingleQuote": false,
"quoteProps": "as-needed",
"jsxBracketSameLine": true
}

View File

@ -44,7 +44,14 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"lint": "prettier --check .",
"format": "prettier --write . && git add -A ."
},
"husky": {
"hooks": {
"pre-commit": "yarn format"
}
},
"eslintConfig": {
"extends": [
@ -60,5 +67,9 @@
"not dead",
"not op_mini all"
],
"proxy": "http://127.0.0.1:8080/"
"proxy": "http://127.0.0.1:8080/",
"devDependencies": {
"husky": "^7.0.4",
"prettier": "^2.6.2"
}
}

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Router, Route, Switch } from "react-router-dom";
import { Layout } from 'antd';
import { Layout } from "antd";
import { User } from "@chirpstack/chirpstack-api-grpc-web/api/user_pb";
@ -35,15 +35,12 @@ import SessionStore from "./stores/SessionStore";
import history from "./history";
interface IProps {
}
interface IProps {}
interface IState {
user?: User;
}
class App extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -66,13 +63,14 @@ class App extends Component<IProps, IState> {
}
render() {
return(
<Layout style={{minHeight:"100vh"}}>
return (
<Layout style={{ minHeight: "100vh" }}>
<Router history={history}>
<Switch>
<Route exact path="/" component={TenantRedirect} />
<Route exact path="/login" component={Login} />
{this.state.user && <Route>
<Route exact path="/" component={TenantRedirect} />
<Route exact path="/login" component={Login} />
{this.state.user && (
<Route>
<Layout.Header className="layout-header">
<Header user={this.state.user} />
</Layout.Header>
@ -80,7 +78,7 @@ class App extends Component<IProps, IState> {
<Layout.Sider width="300" theme="light" className="layout-menu">
<Menu />
</Layout.Sider>
<Layout.Content className="layout-content" style={{ padding: '24px 24px 24px' }}>
<Layout.Content className="layout-content" style={{ padding: "24px 24px 24px" }}>
<Switch>
<Route exact path="/dashboard" component={Dashboard} />
@ -98,7 +96,8 @@ class App extends Component<IProps, IState> {
</Switch>
</Layout.Content>
</Layout>
</Route>}
</Route>
)}
</Switch>
</Router>
</Layout>

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) {
@ -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) {
@ -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) {
@ -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,44 +39,56 @@ 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}
@ -101,19 +111,36 @@ class LogTable extends Component<IProps, IState> {
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];
@ -102,9 +113,10 @@ export class Marker extends Component<MarkerProps> {
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,7 +184,7 @@ class SideMenu extends Component<RouteComponentProps, IState> {
});
}
return(
return (
<div>
<Autocomplete
placeholder="Select tenant"

View File

@ -1,2 +1,2 @@
import { createHashHistory } from 'history';
import { createHashHistory } from "history";
export default createHashHistory();

View File

@ -1,88 +1,88 @@
.layout {
margin-left: 300px;
margin-left: 300px;
}
.layout-header {
background: #ffffff;
box-shadow: 0px 0px 10px 0px #ccc;
z-index: 999;
background: #ffffff;
box-shadow: 0px 0px 10px 0px #ccc;
z-index: 999;
position: fixed;
width: 100%;
position: fixed;
width: 100%;
}
.layout-header .logo {
float: left;
margin-top: 16px;
height: 32px;
float: left;
margin-top: 16px;
height: 32px;
}
.layout-header .actions {
float: right;
float: right;
}
.layout-header .actions > div {
display: inline;
padding-left: 24px;
display: inline;
padding-left: 24px;
}
.layout-menu {
padding-top: 85px;
padding-top: 85px;
overflow: auto;
position: fixed;
height: 100vh;
left: 0;
overflow: auto;
position: fixed;
height: 100vh;
left: 0;
}
.layout-menu .organiation-select {
margin-left: 24px;
margin-bottom: 12px;
width: 252px;
margin-left: 24px;
margin-bottom: 12px;
width: 252px;
}
.layout-content {
margin-top: 65px;
margin-top: 65px;
}
.layout-content .content-header > * {
float: right;
float: right;
}
.layout-content .content-header h3 {
float: left;
display: inline;
float: left;
display: inline;
}
.layout-content .content {
background: #ffffff;
background: #ffffff;
}
.spinner {
text-align: center;
text-align: center;
}
pre {
margin: 0px;
margin: 0px;
}
.chart-doughtnut {
max-width: 350;
padding: 0;
margin: auto;
display: block;
max-width: 350;
padding: 0;
margin: auto;
display: block;
}
.dashboard-chart > .ant-card-body {
height: 300px;
height: 300px;
}
.integration-card {
margin-bottom: 24px;
margin-bottom: 24px;
}
.integration-card .ant-card-meta-description {
min-height: 75px;
min-height: 75px;
}
.search-dropdown .ant-select-dropdown-menu-item-group-title {

View File

@ -3,7 +3,7 @@ import ReactDOM from "react-dom";
// 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 { MatrixElement, MatrixController } from "chartjs-chart-matrix";
import "chartjs-adapter-moment";
import App from "./App";
@ -23,7 +23,7 @@ ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
document.getElementById("root"),
);
// If you want to start measuring performance in your app, pass a function

View File

@ -1,8 +1,8 @@
import { ReportHandler } from 'web-vitals';
import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);

View File

@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import "@testing-library/jest-dom";

View File

@ -64,7 +64,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class ApplicationStore extends EventEmitter {
client: ApplicationServiceClient;
@ -87,7 +86,7 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
get = (req: GetApplicationRequest, callbackFunc: (resp: GetApplicationResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -98,10 +97,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateApplicationRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => {
this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -114,10 +113,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteApplicationRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => {
this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -130,7 +129,7 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListApplicationsRequest, callbackFunc: (resp: ListApplicationsResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -141,7 +140,7 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
listIntegrations = (req: ListIntegrationsRequest, callbackFunc: (resp: ListIntegrationsResponse) => void) => {
this.client.listIntegrations(req, SessionStore.getMetadata(), (err, resp) => {
@ -151,11 +150,11 @@ class ApplicationStore extends EventEmitter {
}
callbackFunc(resp);
})
}
});
};
createHttpIntegration = (req: CreateHttpIntegrationRequest, callbackFunc: () => void) => {
this.client.createHttpIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createHttpIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -168,7 +167,7 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getHttpIntegration = (req: GetHttpIntegrationRequest, callbackFunc: (resp: GetHttpIntegrationResponse) => void) => {
this.client.getHttpIntegration(req, SessionStore.getMetadata(), (err, resp) => {
@ -179,10 +178,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateHttpIntegration = (req: UpdateHttpIntegrationRequest, callbackFunc: () => void) => {
this.client.updateHttpIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateHttpIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -195,10 +194,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteHttpIntegration = (req: DeleteHttpIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteHttpIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteHttpIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -212,10 +211,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createAwsSnsIntegration = (req: CreateAwsSnsIntegrationRequest, callbackFunc: () => void) => {
this.client.createAwsSnsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createAwsSnsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -228,9 +227,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getAwsSnsIntegration = (req: GetAwsSnsIntegrationRequest, callbackFunc: (resp: GetAwsSnsIntegrationResponse) => void) => {
getAwsSnsIntegration = (
req: GetAwsSnsIntegrationRequest,
callbackFunc: (resp: GetAwsSnsIntegrationResponse) => void,
) => {
this.client.getAwsSnsIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -239,10 +241,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateAwsSnsIntegration = (req: UpdateAwsSnsIntegrationRequest, callbackFunc: () => void) => {
this.client.updateAwsSnsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateAwsSnsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -255,10 +257,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteAwsSnsIntegration = (req: DeleteAwsSnsIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteAwsSnsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteAwsSnsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -272,10 +274,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createAzureServiceBusIntegration = (req: CreateAzureServiceBusIntegrationRequest, callbackFunc: () => void) => {
this.client.createAzureServiceBusIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createAzureServiceBusIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -288,9 +290,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getAzureServiceBusIntegration = (req: GetAzureServiceBusIntegrationRequest, callbackFunc: (resp: GetAzureServiceBusIntegrationResponse) => void) => {
getAzureServiceBusIntegration = (
req: GetAzureServiceBusIntegrationRequest,
callbackFunc: (resp: GetAzureServiceBusIntegrationResponse) => void,
) => {
this.client.getAzureServiceBusIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -299,10 +304,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateAzureServiceBusIntegration = (req: UpdateAzureServiceBusIntegrationRequest, callbackFunc: () => void) => {
this.client.updateAzureServiceBusIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateAzureServiceBusIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -315,10 +320,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteAzureServiceBusIntegration = (req: DeleteAzureServiceBusIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteAzureServiceBusIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteAzureServiceBusIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -332,10 +337,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createGcpPubSubIntegration = (req: CreateGcpPubSubIntegrationRequest, callbackFunc: () => void) => {
this.client.createGcpPubSubIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createGcpPubSubIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -348,9 +353,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getGcpPubSubIntegration = (req: GetGcpPubSubIntegrationRequest, callbackFunc: (resp: GetGcpPubSubIntegrationResponse) => void) => {
getGcpPubSubIntegration = (
req: GetGcpPubSubIntegrationRequest,
callbackFunc: (resp: GetGcpPubSubIntegrationResponse) => void,
) => {
this.client.getGcpPubSubIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -359,10 +367,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateGcpPubSubIntegration = (req: UpdateGcpPubSubIntegrationRequest, callbackFunc: () => void) => {
this.client.updateGcpPubSubIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateGcpPubSubIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -375,10 +383,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteGcpPubSubIntegration = (req: DeleteGcpPubSubIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteGcpPubSubIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteGcpPubSubIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -392,10 +400,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createInfluxDbIntegration = (req: CreateInfluxDbIntegrationRequest, callbackFunc: () => void) => {
this.client.createInfluxDbIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createInfluxDbIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -408,9 +416,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getInfluxDbIntegration = (req: GetInfluxDbIntegrationRequest, callbackFunc: (resp: GetInfluxDbIntegrationResponse) => void) => {
getInfluxDbIntegration = (
req: GetInfluxDbIntegrationRequest,
callbackFunc: (resp: GetInfluxDbIntegrationResponse) => void,
) => {
this.client.getInfluxDbIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -419,10 +430,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateInfluxDbIntegration = (req: UpdateInfluxDbIntegrationRequest, callbackFunc: () => void) => {
this.client.updateInfluxDbIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateInfluxDbIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -435,10 +446,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteInfluxDbIntegration = (req: DeleteInfluxDbIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteInfluxDbIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteInfluxDbIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -452,10 +463,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createMyDevicesIntegration = (req: CreateMyDevicesIntegrationRequest, callbackFunc: () => void) => {
this.client.createMyDevicesIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createMyDevicesIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -468,9 +479,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getMyDevicesIntegration = (req: GetMyDevicesIntegrationRequest, callbackFunc: (resp: GetMyDevicesIntegrationResponse) => void) => {
getMyDevicesIntegration = (
req: GetMyDevicesIntegrationRequest,
callbackFunc: (resp: GetMyDevicesIntegrationResponse) => void,
) => {
this.client.getMyDevicesIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -479,10 +493,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateMyDevicesIntegration = (req: UpdateMyDevicesIntegrationRequest, callbackFunc: () => void) => {
this.client.updateMyDevicesIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateMyDevicesIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -495,10 +509,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteMyDevicesIntegration = (req: DeleteMyDevicesIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteMyDevicesIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteMyDevicesIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -512,10 +526,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createPilotThingsIntegration = (req: CreatePilotThingsIntegrationRequest, callbackFunc: () => void) => {
this.client.createPilotThingsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createPilotThingsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -528,9 +542,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getPilotThingsIntegration = (req: GetPilotThingsIntegrationRequest, callbackFunc: (resp: GetPilotThingsIntegrationResponse) => void) => {
getPilotThingsIntegration = (
req: GetPilotThingsIntegrationRequest,
callbackFunc: (resp: GetPilotThingsIntegrationResponse) => void,
) => {
this.client.getPilotThingsIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -539,10 +556,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updatePilotThingsIntegration = (req: UpdatePilotThingsIntegrationRequest, callbackFunc: () => void) => {
this.client.updatePilotThingsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updatePilotThingsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -555,10 +572,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deletePilotThingsIntegration = (req: DeletePilotThingsIntegrationRequest, callbackFunc: () => void) => {
this.client.deletePilotThingsIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deletePilotThingsIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -572,10 +589,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createLoraCloudIntegration = (req: CreateLoraCloudIntegrationRequest, callbackFunc: () => void) => {
this.client.createLoraCloudIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createLoraCloudIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -588,9 +605,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getLoraCloudIntegration = (req: GetLoraCloudIntegrationRequest, callbackFunc: (resp: GetLoraCloudIntegrationResponse) => void) => {
getLoraCloudIntegration = (
req: GetLoraCloudIntegrationRequest,
callbackFunc: (resp: GetLoraCloudIntegrationResponse) => void,
) => {
this.client.getLoraCloudIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -599,10 +619,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateLoraCloudIntegration = (req: UpdateLoraCloudIntegrationRequest, callbackFunc: () => void) => {
this.client.updateLoraCloudIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateLoraCloudIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -615,10 +635,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteLoraCloudIntegration = (req: DeleteLoraCloudIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteLoraCloudIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteLoraCloudIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -632,10 +652,10 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
createThingsBoardIntegration = (req: CreateThingsBoardIntegrationRequest, callbackFunc: () => void) => {
this.client.createThingsBoardIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.createThingsBoardIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -648,9 +668,12 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
getThingsBoardIntegration = (req: GetThingsBoardIntegrationRequest, callbackFunc: (resp: GetThingsBoardIntegrationResponse) => void) => {
getThingsBoardIntegration = (
req: GetThingsBoardIntegrationRequest,
callbackFunc: (resp: GetThingsBoardIntegrationResponse) => void,
) => {
this.client.getThingsBoardIntegration(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -659,10 +682,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateThingsBoardIntegration = (req: UpdateThingsBoardIntegrationRequest, callbackFunc: () => void) => {
this.client.updateThingsBoardIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.updateThingsBoardIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -675,10 +698,10 @@ class ApplicationStore extends EventEmitter {
callbackFunc();
});
}
};
deleteThingsBoardIntegration = (req: DeleteThingsBoardIntegrationRequest, callbackFunc: () => void) => {
this.client.deleteThingsBoardIntegration(req, SessionStore.getMetadata(), (err) => {
this.client.deleteThingsBoardIntegration(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -692,9 +715,12 @@ class ApplicationStore extends EventEmitter {
this.emit("integration.delete");
callbackFunc();
});
}
};
generateMqttIntegrationClientCertificate = (req: GenerateMqttIntegrationClientCertificateRequest, callbackFunc: (resp: GenerateMqttIntegrationClientCertificateResponse) => void) => {
generateMqttIntegrationClientCertificate = (
req: GenerateMqttIntegrationClientCertificateRequest,
callbackFunc: (resp: GenerateMqttIntegrationClientCertificateResponse) => void,
) => {
this.client.generateMqttIntegrationClientCertificate(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -703,7 +729,7 @@ class ApplicationStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const applicationStore = new ApplicationStore();

View File

@ -17,7 +17,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class DeviceProfileStore extends EventEmitter {
client: DeviceProfileServiceClient;
@ -40,7 +39,7 @@ class DeviceProfileStore extends EventEmitter {
callbackFunc(resp);
});
}
};
get = (req: GetDeviceProfileRequest, callbackFunc: (resp: GetDeviceProfileResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -51,10 +50,10 @@ class DeviceProfileStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateDeviceProfileRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => {
this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -67,10 +66,10 @@ class DeviceProfileStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteDeviceProfileRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => {
this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -83,7 +82,7 @@ class DeviceProfileStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListDeviceProfilesRequest, callbackFunc: (resp: ListDeviceProfilesResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -93,8 +92,8 @@ class DeviceProfileStore extends EventEmitter {
}
callbackFunc(resp);
})
}
});
};
listAdrAlgorithms = (callbackFunc: (resp: ListDeviceProfileAdrAlgorithmsResponse) => void) => {
this.client.listAdrAlgorithms(new google_protobuf_empty_pb.Empty(), SessionStore.getMetadata(), (err, resp) => {
@ -105,7 +104,7 @@ class DeviceProfileStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const deviceProfileStore = new DeviceProfileStore();

View File

@ -33,7 +33,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class DeviceStore extends EventEmitter {
client: DeviceServiceClient;
@ -43,7 +42,7 @@ class DeviceStore extends EventEmitter {
}
create = (req: CreateDeviceRequest, callbackFunc: () => void) => {
this.client.create(req, SessionStore.getMetadata(), (err) => {
this.client.create(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -56,7 +55,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
get = (req: GetDeviceRequest, callbackFunc: (resp: GetDeviceResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -67,10 +66,10 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateDeviceRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => {
this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -83,10 +82,10 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteDeviceRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => {
this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -99,7 +98,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListDevicesRequest, callbackFunc: (resp: ListDevicesResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -110,10 +109,10 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
createKeys = (req: CreateDeviceKeysRequest, callbackFunc: () => void) => {
this.client.createKeys(req, SessionStore.getMetadata(), (err) => {
this.client.createKeys(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -126,7 +125,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
getKeys = (req: GetDeviceKeysRequest, callbackFunc: (resp?: GetDeviceKeysResponse) => void) => {
this.client.getKeys(req, SessionStore.getMetadata(), (err, resp) => {
@ -139,10 +138,10 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateKeys = (req: UpdateDeviceKeysRequest, callbackFunc: () => void) => {
this.client.updateKeys(req, SessionStore.getMetadata(), (err) => {
this.client.updateKeys(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -155,10 +154,10 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
deleteKeys = (req: DeleteDeviceKeysRequest, callbackFunc: () => void) => {
this.client.deleteKeys(req, SessionStore.getMetadata(), (err) => {
this.client.deleteKeys(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -171,7 +170,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
getStats = (req: GetDeviceStatsRequest, callbackFunc: (resp: GetDeviceStatsResponse) => void) => {
this.client.getStats(req, SessionStore.getMetadata(), (err, resp) => {
@ -182,7 +181,7 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
enqueue = (req: EnqueueDeviceQueueItemRequest, callbackFunc: (resp: EnqueueDeviceQueueItemResponse) => void) => {
this.client.enqueue(req, SessionStore.getMetadata(), (err, resp) => {
@ -193,10 +192,10 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
flushQueue = (req: FlushDeviceQueueRequest, callbackFunc: () => void) => {
this.client.flushQueue(req, SessionStore.getMetadata(), (err) => {
this.client.flushQueue(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -204,10 +203,10 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
flushDevNonces = (req: FlushDevNoncesRequest, callbackFunc: () => void) => {
this.client.flushDevNonces(req, SessionStore.getMetadata(), (err) => {
this.client.flushDevNonces(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -220,7 +219,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
getQueue = (req: GetDeviceQueueItemsRequest, callbackFunc: (resp: GetDeviceQueueItemsResponse) => void) => {
this.client.getQueue(req, SessionStore.getMetadata(), (err, resp) => {
@ -231,10 +230,10 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
activate = (req: ActivateDeviceRequest, callbackFunc: () => void) => {
this.client.activate(req, SessionStore.getMetadata(), (err) => {
this.client.activate(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -247,7 +246,7 @@ class DeviceStore extends EventEmitter {
callbackFunc();
});
}
};
getActivation = (req: GetDeviceActivationRequest, callbackFunc: (resp: GetDeviceActivationResponse) => void) => {
this.client.getActivation(req, SessionStore.getMetadata(), (err, resp) => {
@ -258,7 +257,7 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
getRandomDevAddr = (req: GetRandomDevAddrRequest, callbackFunc: (resp: GetRandomDevAddrResponse) => void) => {
this.client.getRandomDevAddr(req, SessionStore.getMetadata(), (err, resp) => {
@ -269,7 +268,7 @@ class DeviceStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const deviceStore = new DeviceStore();

View File

@ -18,7 +18,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class GatewayStore extends EventEmitter {
client: GatewayServiceClient;
@ -28,7 +27,7 @@ class GatewayStore extends EventEmitter {
}
create = (req: CreateGatewayRequest, callbackFunc: () => void) => {
this.client.create(req, SessionStore.getMetadata(), (err) => {
this.client.create(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -41,7 +40,7 @@ class GatewayStore extends EventEmitter {
callbackFunc();
});
}
};
get = (req: GetGatewayRequest, callbackFunc: (resp: GetGatewayResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -52,10 +51,10 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateGatewayRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => {
this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -68,10 +67,10 @@ class GatewayStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteGatewayRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => {
this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -84,7 +83,7 @@ class GatewayStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListGatewaysRequest, callbackFunc: (resp: ListGatewaysResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -95,7 +94,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp);
});
}
};
getStats = (req: GetGatewayStatsRequest, callbackFunc: (resp: GetGatewayStatsResponse) => void) => {
this.client.getStats(req, SessionStore.getMetadata(), (err, resp) => {
@ -106,9 +105,12 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp);
});
}
};
generateClientCertificate = (req: GenerateGatewayClientCertificateRequest, callbackFunc: (resp: GenerateGatewayClientCertificateResponse) => void) => {
generateClientCertificate = (
req: GenerateGatewayClientCertificateRequest,
callbackFunc: (resp: GenerateGatewayClientCertificateResponse) => void,
) => {
this.client.generateClientCertificate(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) {
HandleError(err);
@ -117,7 +119,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const gatewayStore = new GatewayStore();

View File

@ -1,4 +1,4 @@
import * as grpcWeb from 'grpc-web';
import * as grpcWeb from "grpc-web";
import google_protobuf_empty_pb from "google-protobuf/google/protobuf/empty_pb";
import { notification } from "antd";
@ -26,7 +26,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class InternalStore extends EventEmitter {
client: InternalServiceClient;
@ -49,7 +48,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
deleteApiKey = (req: DeleteApiKeyRequest, callbackFunc: () => void) => {
this.client.deleteApiKey(req, SessionStore.getMetadata(), (err, resp) => {
@ -65,7 +64,7 @@ class InternalStore extends EventEmitter {
callbackFunc();
});
}
};
listApiKeys = (req: ListApiKeysRequest, callbackFunc: (resp: ListApiKeysResponse) => void) => {
this.client.listApiKeys(req, SessionStore.getMetadata(), (err, resp) => {
@ -76,26 +75,25 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
streamGatewayFrames = (req: StreamGatewayFramesRequest, callbackFunc: (resp: LogItem) => void): () => void => {
streamGatewayFrames = (req: StreamGatewayFramesRequest, callbackFunc: (resp: LogItem) => void): (() => void) => {
var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => {
console.log("Setting up gRPC stream");
stream = this.client.streamGatewayFrames(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => {
stream = stream.on("data", resp => {
callbackFunc(resp);
});
stream = stream.on('end', function() {
stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000);
});
};
setup();
return () => {
@ -103,26 +101,25 @@ class InternalStore extends EventEmitter {
stream.cancel();
}
};
}
};
streamDeviceFrames = (req: StreamDeviceFramesRequest, callbackFunc: (resp: LogItem) => void): () => void => {
streamDeviceFrames = (req: StreamDeviceFramesRequest, callbackFunc: (resp: LogItem) => void): (() => void) => {
var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => {
console.log("Setting up gRPC stream");
stream = this.client.streamDeviceFrames(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => {
stream = stream.on("data", resp => {
callbackFunc(resp);
});
stream = stream.on('end', function() {
stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000);
});
};
setup();
return () => {
@ -130,20 +127,20 @@ class InternalStore extends EventEmitter {
stream.cancel();
}
};
}
};
streamDeviceEvents = (req: StreamDeviceEventsRequest, callbackFunc: (resp: LogItem) => void): () => void => {
streamDeviceEvents = (req: StreamDeviceEventsRequest, callbackFunc: (resp: LogItem) => void): (() => void) => {
var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => {
console.log("Setting up gRPC stream");
stream = this.client.streamDeviceEvents(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => {
stream = stream.on("data", resp => {
callbackFunc(resp);
});
stream = stream.on('end', function() {
stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000);
});
@ -156,7 +153,7 @@ class InternalStore extends EventEmitter {
stream.cancel();
}
};
}
};
getGatewaysSummary = (req: GetGatewaysSummaryRequest, callbackFunc: (resp: GetGatewaysSummaryResponse) => void) => {
this.client.getGatewaysSummary(req, SessionStore.getMetadata(), (err, resp) => {
@ -167,7 +164,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
getDevicesSummary = (req: GetDevicesSummaryRequest, callbackFunc: (resp: GetDevicesSummaryResponse) => void) => {
this.client.getDevicesSummary(req, SessionStore.getMetadata(), (err, resp) => {
@ -178,7 +175,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
settings = (callbackFunc: (resp: SettingsResponse) => void) => {
this.client.settings(new google_protobuf_empty_pb.Empty(), {}, (err, resp) => {
@ -189,7 +186,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
globalSearch = (req: GlobalSearchRequest, callbackFunc: (resp: GlobalSearchResponse) => void) => {
this.client.globalSearch(req, SessionStore.getMetadata(), (err, resp) => {
@ -200,7 +197,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const internalStore = new InternalStore();

View File

@ -11,17 +11,21 @@ class LocationStore extends EventEmitter {
return;
}
navigator.geolocation.getCurrentPosition((p: GeolocationPosition) => {
callbackFunc([p.coords.latitude, p.coords.longitude]);
}, (e: GeolocationPositionError) => {
notification.error({
message: e.message,
duration: 3,
});
}, {
timeout: 3000,
});
}
navigator.geolocation.getCurrentPosition(
(p: GeolocationPosition) => {
callbackFunc([p.coords.latitude, p.coords.longitude]);
},
(e: GeolocationPositionError) => {
notification.error({
message: e.message,
duration: 3,
});
},
{
timeout: 3000,
},
);
};
}
const locationStore = new LocationStore();

View File

@ -20,7 +20,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class MulticastGroupStore extends EventEmitter {
client: MulticastGroupServiceClient;
@ -43,7 +42,7 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc(resp);
});
}
};
get = (req: GetMulticastGroupRequest, callbackFunc: (resp: GetMulticastGroupResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -54,10 +53,10 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateMulticastGroupRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => {
this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -70,10 +69,10 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteMulticastGroupRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => {
this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -86,7 +85,7 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListMulticastGroupsRequest, callbackFunc: (resp: ListMulticastGroupsResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -97,10 +96,10 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc(resp);
});
}
};
addDevice = (req: AddDeviceToMulticastGroupRequest, callbackFunc: () => void) => {
this.client.addDevice(req, SessionStore.getMetadata(), (err) => {
this.client.addDevice(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -113,10 +112,10 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc();
});
}
};
removeDevice = (req: RemoveDeviceFromMulticastGroupRequest, callbackFunc: () => void) => {
this.client.removeDevice(req, SessionStore.getMetadata(), (err) => {
this.client.removeDevice(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -124,7 +123,7 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc();
});
}
};
listQueue = (req: ListMulticastGroupQueueRequest, callbackFunc: (resp: ListMulticastGroupQueueResponse) => void) => {
this.client.listQueue(req, SessionStore.getMetadata(), (err, resp) => {
@ -135,7 +134,7 @@ class MulticastGroupStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const multicastGroupStore = new MulticastGroupStore();

View File

@ -3,7 +3,11 @@ import { Metadata } from "grpc-web";
import { EventEmitter } from "events";
import { InternalServiceClient } from "@chirpstack/chirpstack-api-grpc-web/api/internal_grpc_web_pb";
import { LoginRequest, UserTenantLink, OpenIdConnectLoginRequest } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import {
LoginRequest,
UserTenantLink,
OpenIdConnectLoginRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import { User } from "@chirpstack/chirpstack-api-grpc-web/api/user_pb";
import { HandleError, HandleLoginError } from "./helpers";
@ -35,7 +39,7 @@ class SessionStore extends EventEmitter {
this.setToken(resp.getJwt());
this.fetchProfile(callbackFunc);
});
}
};
openIdConnectLogin = (req: OpenIdConnectLoginRequest, callbackFunc: any) => {
this.client.openIdConnectLogin(req, {}, (err, resp) => {
@ -47,7 +51,7 @@ class SessionStore extends EventEmitter {
this.setToken(resp.getToken());
this.fetchProfile(callbackFunc);
});
}
};
logout = (emit: boolean, callbackFunc: () => void) => {
localStorage.clear();
@ -59,11 +63,11 @@ class SessionStore extends EventEmitter {
}
callbackFunc();
}
};
setToken = (s: string) => {
localStorage.setItem("token", s);
}
};
getToken = (): string => {
let token = localStorage.getItem("token");
@ -71,25 +75,25 @@ class SessionStore extends EventEmitter {
return "";
}
return token;
}
};
getTenantId = (): string => {
return localStorage.getItem("tenantId") || "";
}
};
setTenantId = (id: string) => {
console.log("tenantId set to", id);
localStorage.setItem("tenantId", id);
this.emit("tenant.change");
}
};
getRowsPerPage = (): number => {
return parseInt(localStorage.getItem("rowsPerPage") || "10", 10);
}
};
setRowsPerPage = (count: number) => {
localStorage.setItem("rowsPerPage", count.toString());
}
};
getMetadata = (): Metadata => {
if (this.getToken() === "") {
@ -99,7 +103,7 @@ class SessionStore extends EventEmitter {
return {
authorization: "Bearer " + this.getToken(),
};
}
};
fetchProfile = (callbackFunc: any) => {
if (this.getToken() === "") {
@ -118,19 +122,19 @@ class SessionStore extends EventEmitter {
callbackFunc();
});
}
};
getUser = (): User | undefined => {
return this.user;
}
};
isAdmin = (): boolean => {
if(!this.user) {
if (!this.user) {
return false;
}
return this.user.getIsAdmin();
}
};
isTenantAdmin = (tenantId: string): boolean => {
for (const t of this.tenants) {
@ -140,7 +144,7 @@ class SessionStore extends EventEmitter {
}
return false;
}
};
isTenantDeviceAdmin = (tenantId: string): boolean => {
for (const t of this.tenants) {
@ -150,7 +154,7 @@ class SessionStore extends EventEmitter {
}
return false;
}
};
isTenantGatewayAdmin = (tenantId: string): boolean => {
for (const t of this.tenants) {
@ -158,7 +162,7 @@ class SessionStore extends EventEmitter {
}
return false;
}
};
}
const sessionStore = new SessionStore();

View File

@ -22,7 +22,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class TenantStore extends EventEmitter {
client: TenantServiceClient;
@ -45,7 +44,7 @@ class TenantStore extends EventEmitter {
callbackFunc(resp);
});
}
};
get = (id: string, callbackFunc: (resp: GetTenantResponse) => void) => {
let req = new GetTenantRequest();
@ -59,7 +58,7 @@ class TenantStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateTenantRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err, resp) => {
@ -77,7 +76,7 @@ class TenantStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteTenantRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err, resp) => {
@ -93,7 +92,7 @@ class TenantStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListTenantsRequest, callbackFunc: (resp: ListTenantsResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -104,10 +103,10 @@ class TenantStore extends EventEmitter {
callbackFunc(resp);
});
}
};
addUser = (req: AddTenantUserRequest, callbackFunc: () => void) => {
this.client.addUser(req, SessionStore.getMetadata(), (err) => {
this.client.addUser(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -131,10 +130,10 @@ class TenantStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updateUser = (req: UpdateTenantUserRequest, callbackFunc: () => void) => {
this.client.updateUser(req, SessionStore.getMetadata(), (err) => {
this.client.updateUser(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -147,10 +146,10 @@ class TenantStore extends EventEmitter {
callbackFunc();
});
}
};
deleteUser = (req: DeleteTenantUserRequest, callbackFunc: () => void) => {
this.client.deleteUser(req, SessionStore.getMetadata(), (err) => {
this.client.deleteUser(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -163,7 +162,7 @@ class TenantStore extends EventEmitter {
callbackFunc();
});
}
};
listUsers = (req: ListTenantUsersRequest, callbackFunc: (resp: ListTenantUsersResponse) => void) => {
this.client.listUsers(req, SessionStore.getMetadata(), (err, resp) => {
@ -174,7 +173,7 @@ class TenantStore extends EventEmitter {
callbackFunc(resp);
});
}
};
}
const tenantStore = new TenantStore();

View File

@ -16,7 +16,6 @@ import {
import SessionStore from "./SessionStore";
import { HandleError } from "./helpers";
class UserStore extends EventEmitter {
client: UserServiceClient;
@ -39,7 +38,7 @@ class UserStore extends EventEmitter {
callbackFunc(resp);
});
}
};
get = (req: GetUserRequest, callbackFunc: (resp: GetUserResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -50,7 +49,7 @@ class UserStore extends EventEmitter {
callbackFunc(resp);
});
}
};
update = (req: UpdateUserRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err, resp) => {
@ -66,7 +65,7 @@ class UserStore extends EventEmitter {
callbackFunc();
});
}
};
delete = (req: DeleteUserRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err, resp) => {
@ -82,7 +81,7 @@ class UserStore extends EventEmitter {
callbackFunc();
});
}
};
list = (req: ListUsersRequest, callbackFunc: (resp: ListUsersResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -93,10 +92,10 @@ class UserStore extends EventEmitter {
callbackFunc(resp);
});
}
};
updatePassword = (req: UpdateUserPasswordRequest, callbackFunc: () => void) => {
this.client.updatePassword(req, SessionStore.getMetadata(), (err) => {
this.client.updatePassword(req, SessionStore.getMetadata(), err => {
if (err !== null) {
HandleError(err);
return;
@ -109,7 +108,7 @@ class UserStore extends EventEmitter {
callbackFunc();
});
}
};
}
const userStore = new UserStore();

View File

@ -6,7 +6,7 @@ import history from "../history";
export function HandleError(e: Error) {
console.log("API error: ", e);
if (e.code === 16 || e.code === 2) {
if (e.code === 16 || e.code === 2) {
history.push("/login");
return;
}

View File

@ -4,7 +4,6 @@ import { Form, Input, Button } from "antd";
import { ApiKey } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
interface IProps {
initialValues: ApiKey;
onFinish: (obj: ApiKey) => void;
@ -17,20 +16,18 @@ class ApiKeyForm extends Component<IProps, IState> {
let apiKey = new ApiKey();
apiKey.setName(values.name);
this.props.onFinish(apiKey);
}
};
render() {
return(
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Name"
name="name"
rules={[{required: true, message: "Please enter a name!"}]}
>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,31 +1,28 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Input, Typography, Button, Space } from 'antd';
import { Input, Typography, Button, Space } from "antd";
import { CreateApiKeyResponse } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
interface IProps {
createApiKeyResponse: CreateApiKeyResponse;
}
class ApiKeyToken extends Component<IProps> {
render() {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Typography>
<Typography.Paragraph>
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:
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:
</Typography.Paragraph>
</Typography>
<Input.TextArea rows={4} value={this.props.createApiKeyResponse.getToken()} />
<Button type="primary"><Link to="../api-keys">Back</Link></Button>
<Button type="primary">
<Link to="../api-keys">Back</Link>
</Button>
</Space>
);
}

View File

@ -9,14 +9,12 @@ import ApiKeyForm from "./ApiKeyForm";
import ApiKeyToken from "./ApiKeyToken";
import InternalStore from "../../stores/InternalStore";
interface IProps {}
interface IState {
createApiKeyResponse?: CreateApiKeyResponse;
}
class CreateAdminApiKey extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -34,26 +32,30 @@ class CreateAdminApiKey extends Component<IProps, IState> {
createApiKeyResponse: resp,
});
});
}
};
render() {
const apiKey = new ApiKey();
return(
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
title="Add API key"
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Network-server</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to="/api-keys">API keys</Link></span>
<span>
<Link to="/api-keys">API keys</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Add</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
/>
<Card>
{!this.state.createApiKeyResponse && <ApiKeyForm initialValues={apiKey} onFinish={this.onFinish} />}

View File

@ -10,7 +10,6 @@ import ApiKeyForm from "./ApiKeyForm";
import ApiKeyToken from "./ApiKeyToken";
import InternalStore from "../../stores/InternalStore";
interface IState {
createApiKeyResponse?: CreateApiKeyResponse;
}
@ -19,7 +18,6 @@ interface IProps {
tenant: Tenant;
}
class CreateTenantApiKey extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -37,28 +35,34 @@ class CreateTenantApiKey extends Component<IProps, IState> {
createApiKeyResponse: resp,
});
});
}
};
render() {
const apiKey = new ApiKey();
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/api-keys`}>API Keys</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/api-keys`}>API Keys</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Add</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Add API key"
/>
<Card>

View File

@ -6,20 +6,23 @@ import { DeleteOutlined } from "@ant-design/icons";
import { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table";
import { ListApiKeysRequest, ListApiKeysResponse, DeleteApiKeyRequest, ApiKey } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import {
ListApiKeysRequest,
ListApiKeysResponse,
DeleteApiKeyRequest,
ApiKey,
} from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import InternalStore from "../../stores/InternalStore";
import DeleteConfirm from "../../components/DeleteConfirm";
interface IProps {}
interface IState {
refreshKey: number;
}
class ListAdminApiKeys extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,19 +50,15 @@ class ListAdminApiKeys extends Component<IProps, IState> {
key: "action",
width: 100,
render: (text, record) => (
<DeleteConfirm
typ="API key"
confirm={record.name}
onConfirm={this.deleteApiKey(record.id)}
>
<DeleteConfirm typ="API key" confirm={record.name} onConfirm={this.deleteApiKey(record.id)}>
<Button shape="circle" icon={<DeleteOutlined />} />
</DeleteConfirm>
),
},
];
}
};
deleteApiKey = (id: string): () => void => {
deleteApiKey = (id: string): (() => void) => {
return () => {
let req = new DeleteApiKeyRequest();
req.setId(id);
@ -70,8 +69,8 @@ class ListAdminApiKeys extends Component<IProps, IState> {
refreshKey: this.state.refreshKey + 1,
});
});
}
}
};
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApiKeysRequest();
@ -83,31 +82,30 @@ class ListAdminApiKeys extends Component<IProps, IState> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
render() {
return(
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Network-server</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>API keys</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="API keys"
extra={[
<Button type="primary"><Link to="/api-keys/create">Add API key</Link></Button>
<Button type="primary">
<Link to="/api-keys/create">Add API key</Link>
</Button>,
]}
/>
<DataTable
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
refreshKey={this.state.refreshKey}
/>
<DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" refreshKey={this.state.refreshKey} />
</Space>
);
}

View File

@ -5,7 +5,12 @@ import { DeleteOutlined } from "@ant-design/icons";
import { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table";
import { ListApiKeysRequest, ListApiKeysResponse, DeleteApiKeyRequest, ApiKey } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import {
ListApiKeysRequest,
ListApiKeysResponse,
DeleteApiKeyRequest,
ApiKey,
} from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
@ -13,7 +18,6 @@ import InternalStore from "../../stores/InternalStore";
import DeleteConfirm from "../../components/DeleteConfirm";
import Admin from "../../components/Admin";
interface IProps {
tenant: Tenant;
isAdmin: boolean;
@ -23,13 +27,12 @@ interface IState {
refreshKey: number;
}
class ListTenantApiKeys extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
refreshKey: 1,
}
};
}
columns = (): ColumnsType<ApiKey.AsObject> => {
@ -52,20 +55,16 @@ class ListTenantApiKeys extends Component<IProps, IState> {
width: 100,
render: (text, record) => (
<Admin tenantId={this.props.tenant.getId()} isTenantAdmin>
<DeleteConfirm
typ="API key"
confirm={record.name}
onConfirm={this.deleteApiKey(record.id)}
>
<DeleteConfirm typ="API key" confirm={record.name} onConfirm={this.deleteApiKey(record.id)}>
<Button shape="circle" icon={<DeleteOutlined />} />
</DeleteConfirm>
</Admin>
),
},
];
}
};
deleteApiKey = (id: string): () => void => {
deleteApiKey = (id: string): (() => void) => {
return () => {
let req = new DeleteApiKeyRequest();
req.setId(id);
@ -76,8 +75,8 @@ class ListTenantApiKeys extends Component<IProps, IState> {
refreshKey: this.state.refreshKey + 1,
});
});
}
}
};
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApiKeysRequest();
@ -89,36 +88,37 @@ class ListTenantApiKeys extends Component<IProps, IState> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
render() {
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>API Keys</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="API keys"
extra={[
<Admin tenantId={this.props.tenant.getId()} isTenantAdmin>
<Button type="primary"><Link to={`/tenants/${this.props.tenant.getId()}/api-keys/create`}>Add API key</Link></Button>
</Admin>
<Button type="primary">
<Link to={`/tenants/${this.props.tenant.getId()}/api-keys/create`}>Add API key</Link>
</Button>
</Admin>,
]}
/>
<DataTable
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
refreshKey={this.state.refreshKey}
/>
<DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" refreshKey={this.state.refreshKey} />
</Space>
);
}

View File

@ -3,7 +3,6 @@ import React, { Component } from "react";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Form, Input, Button } from "antd";
interface IProps {
initialValues: Application;
onFinish: (obj: Application) => void;
@ -21,26 +20,21 @@ class ApplicationForm extends Component<IProps> {
app.setDescription(v.description);
this.props.onFinish(app);
}
};
render() {
return(
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Name"
name="name"
rules={[{required: true, message: "Please enter a name!"}]}
>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Input disabled={this.props.disabled} />
</Form.Item>
<Form.Item
label="Description"
name="description"
>
<Form.Item label="Description" name="description">
<Input.TextArea disabled={this.props.disabled} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={this.props.disabled}>Submit</Button>
<Button type="primary" htmlType="submit" disabled={this.props.disabled}>
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -35,13 +35,11 @@ import CreateThingsBoardIntegration from "./integrations/CreateThingsBoardIntegr
import EditThingsBoardIntegration from "./integrations/EditThingsBoardIntegration";
import GenerateMqttCertificate from "./integrations/GenerateMqttCertificate";
interface IProps extends RouteComponentProps {
tenant: Tenant;
application: Application;
}
class ApplicationLayout extends Component<IProps> {
deleteApplication = () => {
let req = new DeleteApplicationRequest();
@ -50,7 +48,7 @@ class ApplicationLayout extends Component<IProps> {
ApplicationStore.delete(req, () => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications`);
});
}
};
render() {
const tenant = this.props.tenant;
@ -73,73 +71,180 @@ class ApplicationLayout extends Component<IProps> {
tab = "integrations";
}
const showIntegrations = SessionStore.isAdmin() || SessionStore.isTenantAdmin(tenant.getId()) || SessionStore.isTenantDeviceAdmin(tenant.getId());
const showIntegrations =
SessionStore.isAdmin() ||
SessionStore.isTenantAdmin(tenant.getId()) ||
SessionStore.isTenantDeviceAdmin(tenant.getId());
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>{app.getName()}</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title={app.getName()}
subTitle={`application id: ${app.getId()}`}
extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin>
<DeleteConfirm
confirm={app.getName()}
typ="application"
onConfirm={this.deleteApplication}
>
<Button danger type="primary">Delete application</Button>
<DeleteConfirm confirm={app.getName()} typ="application" onConfirm={this.deleteApplication}>
<Button danger type="primary">
Delete application
</Button>
</DeleteConfirm>
</Admin>
</Admin>,
]}
/>
<Card>
<Menu mode="horizontal" selectedKeys={[tab]} style={{marginBottom: 24}}>
<Menu.Item key="devices"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}`}>Devices</Link></Menu.Item>
<Menu.Item key="mg"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/multicast-groups`}>Multicast groups</Link></Menu.Item>
<Menu.Item key="edit"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/edit`}>Application configuration</Link></Menu.Item>
{showIntegrations && <Menu.Item key="integrations"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/integrations`}>Integrations</Link></Menu.Item> }
</Menu>
<Switch>
<Route exact path={this.props.match.path} render={props => <ListDevices application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/edit`} render={props => <EditApplication application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations`} render={props => <ListIntegrations application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/multicast-groups`} render={props => <ListMulticastGroups application={app} {...props} />} />
<Card>
<Menu mode="horizontal" selectedKeys={[tab]} style={{ marginBottom: 24 }}>
<Menu.Item key="devices">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}`}>Devices</Link>
</Menu.Item>
<Menu.Item key="mg">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/multicast-groups`}>
Multicast groups
</Link>
</Menu.Item>
<Menu.Item key="edit">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/edit`}>Application configuration</Link>
</Menu.Item>
{showIntegrations && (
<Menu.Item key="integrations">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/integrations`}>Integrations</Link>
</Menu.Item>
)}
</Menu>
<Switch>
<Route exact path={this.props.match.path} render={props => <ListDevices application={app} {...props} />} />
<Route
exact
path={`${this.props.match.path}/edit`}
render={props => <EditApplication application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations`}
render={props => <ListIntegrations application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/multicast-groups`}
render={props => <ListMulticastGroups application={app} {...props} />}
/>
<Route exact path={`${this.props.match.path}/integrations/http/create`} render={props => <CreateHttpIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/http/edit`} render={props => <EditHttpIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/aws-sns/create`} render={props => <CreateAwsSnsIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/aws-sns/edit`} render={props => <EditAwsSnsIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/azure-service-bus/create`} render={props => <CreateAzureServiceBusIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/azure-service-bus/edit`} render={props => <EditAzureServiceBusIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/gcp-pub-sub/create`} render={props => <CreateGcpPubSubIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/gcp-pub-sub/edit`} render={props => <EditGcpPubSubIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/influxdb/create`} render={props => <CreateInfluxDbIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/influxdb/edit`} render={props => <EditInfluxDbIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/mydevices/create`} render={props => <CreateMyDevicesIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/mydevices/edit`} render={props => <EditMyDevicesIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/pilot-things/create`} render={props => <CreatePilotThingsIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/pilot-things/edit`} render={props => <EditPilotThingsIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/loracloud/create`} render={props => <CreateLoRaCloudIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/loracloud/edit`} render={props => <EditLoRaCloudIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/thingsboard/create`} render={props => <CreateThingsBoardIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/thingsboard/edit`} render={props => <EditThingsBoardIntegration application={app} {...props} />} />
<Route exact path={`${this.props.match.path}/integrations/mqtt/certificate`} render={props => <GenerateMqttCertificate application={app} {...props} />} />
</Switch>
</Card>
<Route
exact
path={`${this.props.match.path}/integrations/http/create`}
render={props => <CreateHttpIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/http/edit`}
render={props => <EditHttpIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/aws-sns/create`}
render={props => <CreateAwsSnsIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/aws-sns/edit`}
render={props => <EditAwsSnsIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/azure-service-bus/create`}
render={props => <CreateAzureServiceBusIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/azure-service-bus/edit`}
render={props => <EditAzureServiceBusIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/gcp-pub-sub/create`}
render={props => <CreateGcpPubSubIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/gcp-pub-sub/edit`}
render={props => <EditGcpPubSubIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/influxdb/create`}
render={props => <CreateInfluxDbIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/influxdb/edit`}
render={props => <EditInfluxDbIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/mydevices/create`}
render={props => <CreateMyDevicesIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/mydevices/edit`}
render={props => <EditMyDevicesIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/pilot-things/create`}
render={props => <CreatePilotThingsIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/pilot-things/edit`}
render={props => <EditPilotThingsIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/loracloud/create`}
render={props => <CreateLoRaCloudIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/loracloud/edit`}
render={props => <EditLoRaCloudIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/thingsboard/create`}
render={props => <CreateThingsBoardIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/thingsboard/edit`}
render={props => <EditThingsBoardIntegration application={app} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/integrations/mqtt/certificate`}
render={props => <GenerateMqttCertificate application={app} {...props} />}
/>
</Switch>
</Card>
</Space>
);
}

View File

@ -2,7 +2,11 @@ import React, { Component } from "react";
import { Route, Switch, RouteComponentProps } from "react-router-dom";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application, GetApplicationRequest, GetApplicationResponse } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import {
Application,
GetApplicationRequest,
GetApplicationResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../stores/ApplicationStore";
import ApplicationLayout from "./ApplicationLayout";
@ -11,7 +15,6 @@ import DeviceLayout from "../devices/DeviceLayout";
import MulticastGroupLayout from "../multicast-groups/MulticastGroupLayout";
import CreateMulticastGroup from "../multicast-groups/CreateMulticastGroup";
interface MatchParams {
applicationId: string;
}
@ -24,7 +27,6 @@ interface IState {
application?: Application;
}
class ApplicationLoader extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -44,7 +46,7 @@ class ApplicationLoader extends Component<IProps, IState> {
application: resp.getApplication(),
});
});
}
};
render() {
const app = this.state.application;
@ -55,13 +57,26 @@ class ApplicationLoader extends Component<IProps, IState> {
const path = this.props.match.path;
const tenant = this.props.tenant;
return(
return (
<Switch>
<Route exact path={`${path}/devices/create`} render={props => <CreateDevice tenant={tenant} application={app} {...props} />} />
<Route exact path={`${path}/multicast-groups/create`} render={props => <CreateMulticastGroup tenant={tenant} application={app} {...props} />} />
<Route path={`${path}/multicast-groups/:multicastGroupId([\\w-]{36})`} render={(props: any) => <MulticastGroupLayout tenant={tenant} application={app} {...props} />} />
<Route path={`${path}/devices/:devEui([0-9a-f]{16})`} component={(props: any) => <DeviceLayout tenant={tenant} application={app} {...props} />} />
<Route
exact
path={`${path}/devices/create`}
render={props => <CreateDevice tenant={tenant} application={app} {...props} />}
/>
<Route
exact
path={`${path}/multicast-groups/create`}
render={props => <CreateMulticastGroup tenant={tenant} application={app} {...props} />}
/>
<Route
path={`${path}/multicast-groups/:multicastGroupId([\\w-]{36})`}
render={(props: any) => <MulticastGroupLayout tenant={tenant} application={app} {...props} />}
/>
<Route
path={`${path}/devices/:devEui([0-9a-f]{16})`}
component={(props: any) => <DeviceLayout tenant={tenant} application={app} {...props} />}
/>
<Route path={path} render={props => <ApplicationLayout tenant={tenant} application={app} {...props} />} />
</Switch>
);

View File

@ -4,17 +4,19 @@ import { Link, RouteComponentProps } from "react-router-dom";
import { Space, Breadcrumb, Card, PageHeader } from "antd";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application, CreateApplicationRequest, CreateApplicationResponse } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import {
Application,
CreateApplicationRequest,
CreateApplicationResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationForm from "./ApplicationForm";
import ApplicationStore from "../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
tenant: Tenant;
}
class CreateApplication extends Component<IProps> {
onFinish = (obj: Application) => {
obj.setTenantId(this.props.tenant.getId());
@ -25,28 +27,34 @@ class CreateApplication extends Component<IProps> {
ApplicationStore.create(req, (resp: CreateApplicationResponse) => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications/${resp.getId()}`);
});
}
};
render() {
const app = new Application();
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Add</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Add application"
/>
<Card>

View File

@ -7,28 +7,30 @@ import ApplicationStore from "../../stores/ApplicationStore";
import ApplicationForm from "./ApplicationForm";
import SessionStore from "../../stores/SessionStore";
interface IProps extends RouteComponentProps {
application: Application,
application: Application;
}
class EditApplication extends Component<IProps> {
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()}`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}`,
);
});
}
};
render() {
const disabled = !(SessionStore.isAdmin() || SessionStore.isTenantAdmin(this.props.application.getTenantId()) || SessionStore.isTenantDeviceAdmin(this.props.application.getTenantId()));
return(
<ApplicationForm initialValues={this.props.application} disabled={disabled} onFinish={this.onFinish} />
const disabled = !(
SessionStore.isAdmin() ||
SessionStore.isTenantAdmin(this.props.application.getTenantId()) ||
SessionStore.isTenantDeviceAdmin(this.props.application.getTenantId())
);
return <ApplicationForm initialValues={this.props.application} disabled={disabled} onFinish={this.onFinish} />;
}
}

View File

@ -4,19 +4,21 @@ import { Link } from "react-router-dom";
import { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table";
import { ListApplicationsRequest, ListApplicationsResponse, ApplicationListItem } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import {
ListApplicationsRequest,
ListApplicationsResponse,
ApplicationListItem,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import ApplicationStore from "../../stores/ApplicationStore";
import Admin from "../../components/Admin";
interface IProps {
tenant: Tenant;
}
class ListApplications extends Component<IProps> {
columns = (): ColumnsType<ApplicationListItem.AsObject> => {
return [
@ -35,7 +37,7 @@ class ListApplications extends Component<IProps> {
key: "description",
},
];
}
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApplicationsRequest();
@ -47,35 +49,37 @@ class ListApplications extends Component<IProps> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
render() {
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Applications</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Applications"
extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin>
<Button type="primary"><Link to={`/tenants/${this.props.tenant.getId()}/applications/create`}>Add application</Link></Button>
</Admin>
<Button type="primary">
<Link to={`/tenants/${this.props.tenant.getId()}/applications/create`}>Add application</Link>
</Button>
</Admin>,
]}
/>
<DataTable
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
/>
<DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" />
</Space>
);
}

View File

@ -22,7 +22,6 @@ import PilotThingsCard from "./integrations/PilotThingsCard";
import LoRaCloudCard from "./integrations/LoRaCloudCard";
import ThingsBoardCard from "./integrations/ThingsBoardCard";
interface IProps {
application: Application;
}
@ -32,7 +31,6 @@ interface IState {
available: any[];
}
class ListIntegrations extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -70,83 +68,82 @@ class ListIntegrations extends Component<IProps, IState> {
};
// AWS SNS
if(includes(resp.getResultList(), IntegrationKind.AWS_SNS)) {
if (includes(resp.getResultList(), IntegrationKind.AWS_SNS)) {
configured.push(<AwsSnsCard application={this.props.application} />);
} else {
available.push(<AwsSnsCard application={this.props.application} add />);
}
// Azure Service-Bus
if(includes(resp.getResultList(), IntegrationKind.AZURE_SERVICE_BUS)) {
if (includes(resp.getResultList(), IntegrationKind.AZURE_SERVICE_BUS)) {
configured.push(<AzureServiceBusCard application={this.props.application} />);
} else {
available.push(<AzureServiceBusCard application={this.props.application} add />);
}
// GCP Pub/Sub
if(includes(resp.getResultList(), IntegrationKind.GCP_PUB_SUB)) {
if (includes(resp.getResultList(), IntegrationKind.GCP_PUB_SUB)) {
configured.push(<GcpPubSubCard application={this.props.application} />);
} else {
available.push(<GcpPubSubCard application={this.props.application} add />);
}
// HTTP
if(includes(resp.getResultList(), IntegrationKind.HTTP)) {
if (includes(resp.getResultList(), IntegrationKind.HTTP)) {
configured.push(<HttpCard application={this.props.application} />);
} else {
available.push(<HttpCard application={this.props.application} add />);
}
// InfluxDB
if(includes(resp.getResultList(), IntegrationKind.INFLUX_DB)) {
if (includes(resp.getResultList(), IntegrationKind.INFLUX_DB)) {
configured.push(<InfluxdbCard application={this.props.application} />);
} else {
available.push(<InfluxdbCard application={this.props.application} add />);
}
// MQTT
if(includes(resp.getResultList(), IntegrationKind.MQTT_GLOBAL)) {
if (includes(resp.getResultList(), IntegrationKind.MQTT_GLOBAL)) {
configured.push(<MqttCard application={this.props.application} />);
}
// myDevices
if(includes(resp.getResultList(), IntegrationKind.MY_DEVICES)) {
if (includes(resp.getResultList(), IntegrationKind.MY_DEVICES)) {
configured.push(<MyDevicesCard application={this.props.application} />);
} else {
available.push(<MyDevicesCard application={this.props.application} add />);
}
// Pilot Things
if(includes(resp.getResultList(), IntegrationKind.PILOT_THINGS)) {
if (includes(resp.getResultList(), IntegrationKind.PILOT_THINGS)) {
configured.push(<PilotThingsCard application={this.props.application} />);
} else {
available.push(<PilotThingsCard application={this.props.application} add />);
}
// Semtech LoRa Cloud
if(includes(resp.getResultList(), IntegrationKind.LORA_CLOUD)) {
if (includes(resp.getResultList(), IntegrationKind.LORA_CLOUD)) {
configured.push(<LoRaCloudCard application={this.props.application} />);
} else {
available.push(<LoRaCloudCard application={this.props.application} add />);
}
// ThingsBoard
if(includes(resp.getResultList(), IntegrationKind.THINGS_BOARD)) {
if (includes(resp.getResultList(), IntegrationKind.THINGS_BOARD)) {
configured.push(<ThingsBoardCard application={this.props.application} />);
} else {
available.push(<ThingsBoardCard application={this.props.application} add />);
}
this.setState({
configured: configured,
available: available,
});
});
}
};
render() {
return(
return (
<Row gutter={24}>
{this.state.configured}
{this.state.available}

View File

@ -1,45 +1,41 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteAwsSnsIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteAwsSnsIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class AwsSns extends Component<IProps> {
onDelete = () => {
let req = new DeleteAwsSnsIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteAwsSnsIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/aws-sns/create"><PlusOutlined /></Link>
<Link to="integrations/aws-sns/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/aws-sns/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/aws-sns/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -50,12 +46,10 @@ class AwsSns extends Component<IProps> {
<Card
title="AWS SNS"
className="integration-card"
cover={<img alt="AWS SNS" src="/integrations/aws_sns.png" style={{padding: 1}} />}
cover={<img alt="AWS SNS" src="/integrations/aws_sns.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The AWS SNS integration forwards events to an AWS SNS topic."
/>
<Card.Meta description="The AWS SNS integration forwards events to an AWS SNS topic." />
</Card>
</Col>
);

View File

@ -2,11 +2,7 @@ import React, { Component } from "react";
import { Form, Input, Button, Select } from "antd";
import {
AwsSnsIntegration,
Encoding,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { AwsSnsIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: AwsSnsIntegration;
@ -26,51 +22,49 @@ class AwsSnsIntegrationForm extends Component<IProps> {
i.setTopicArn(v.topicArn);
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Payload encoding"
name="encoding"
rules={[{required: true, message: "Please select an encoding!"}]}
rules={[{ required: true, message: "Please select an encoding!" }]}
>
<Select>
<Select.Option value={Encoding.JSON}>JSON</Select.Option>
<Select.Option value={Encoding.PROTOBUF}>Protobuf (binary)</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="AWS region"
name="region"
rules={[{required: true, message: "Please enter a region!"}]}
>
<Form.Item label="AWS region" name="region" rules={[{ required: true, message: "Please enter a region!" }]}>
<Input />
</Form.Item>
<Form.Item
label="AWS Access Key ID"
name="accessKeyId"
rules={[{required: true, message: "Please enter an Access Key ID!"}]}
rules={[{ required: true, message: "Please enter an Access Key ID!" }]}
>
<Input />
</Form.Item>
<Form.Item
label="AWS Secret Access Key"
name="secretAccessKey"
rules={[{required: true, message: "Please enter a Secret Access Key!"}]}
rules={[{ required: true, message: "Please enter a Secret Access Key!" }]}
>
<Input />
</Form.Item>
<Form.Item
label="AWS SNS topic ARN"
name="topicArn"
rules={[{required: true, message: "Please enter a SNS topic ARN!"}]}
rules={[{ required: true, message: "Please enter a SNS topic ARN!" }]}
>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
@ -11,34 +11,33 @@ import {
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class AzureServiceBusCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteAzureServiceBusIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteAzureServiceBusIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/azure-service-bus/create"><PlusOutlined /></Link>
<Link to="integrations/azure-service-bus/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/azure-service-bus/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/azure-service-bus/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +48,10 @@ class AzureServiceBusCard extends Component<IProps> {
<Card
title="Azure Service-Bus"
className="integration-card"
cover={<img alt="Azure Service-Bus" src="/integrations/azure_service_bus.png" style={{padding: 1}} />}
cover={<img alt="Azure Service-Bus" src="/integrations/azure_service_bus.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The Azure Service-Bus integration forwards events to an Azure Service-Bus topic or queue."
/>
<Card.Meta description="The Azure Service-Bus integration forwards events to an Azure Service-Bus topic or queue." />
</Card>
</Col>
);

View File

@ -2,18 +2,13 @@ import React, { Component } from "react";
import { Form, Input, Button, Select } from "antd";
import {
AzureServiceBusIntegration,
Encoding,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { AzureServiceBusIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: AzureServiceBusIntegration;
onFinish: (obj: AzureServiceBusIntegration) => void;
}
class AzureServiceBusIntegrationForm extends Component<IProps> {
onFinish = (values: AzureServiceBusIntegration.AsObject) => {
const v = Object.assign(this.props.initialValues.toObject(), values);
@ -25,15 +20,15 @@ class AzureServiceBusIntegrationForm extends Component<IProps> {
i.setPublishName(v.publishName);
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Payload encoding"
name="encoding"
rules={[{required: true, message: "Please select an encoding!"}]}
rules={[{ required: true, message: "Please select an encoding!" }]}
>
<Select>
<Select.Option value={Encoding.JSON}>JSON</Select.Option>
@ -44,19 +39,21 @@ class AzureServiceBusIntegrationForm extends Component<IProps> {
label="Azure Service-Bus connection string"
name="connectionString"
tooltip="This string can be obtained after creating a 'Shared access policy' with 'Send' permission."
rules={[{required: true, message: "Please enter an Azure Service-Bus connection string!"}]}
rules={[{ required: true, message: "Please enter an Azure Service-Bus connection string!" }]}
>
<Input />
</Form.Item>
<Form.Item
label="Azure Service-Bus topic / queue name"
name="publishName"
rules={[{required: true, message: "Please enter an Azure Service-Bus topic / queue name!"}]}
rules={[{ required: true, message: "Please enter an Azure Service-Bus topic / queue name!" }]}
>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -24,14 +24,16 @@ class CreateAwsSnsIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createAwsSnsIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new AwsSnsIntegration();
return(
return (
<Card title="Add AWS SNS integration">
<AwsSnsIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateAzureServiceBusIntegration extends Component<IProps> {
onFinish = (obj: AzureServiceBusIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreateAzureServiceBusIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createAzureServiceBusIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new AzureServiceBusIntegration();
return(
return (
<Card title="Add Azure Service-Bus integration">
<AzureServiceBusIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import GcpPubSubIntegrationForm from "./GcpPubSubIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateGcpPubSubIntegration extends Component<IProps> {
onFinish = (obj: GcpPubSubIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreateGcpPubSubIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createGcpPubSubIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new GcpPubSubIntegration();
return(
return (
<Card title="Add GCP Pub/Sub integration">
<GcpPubSubIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,7 +12,6 @@ import {
import HttpIntegrationForm from "./HttpIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
@ -25,14 +24,16 @@ class CreateHttpIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createHttpIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new HttpIntegration();
return(
return (
<Card title="Add HTTP integration">
<HttpIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import InfluxDbIntegrationForm from "./InfluxDbIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateInfluxDbIntegration extends Component<IProps> {
onFinish = (obj: InfluxDbIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreateInfluxDbIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createInfluxDbIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new InfluxDbIntegration();
return(
return (
<Card title="Add InfluxDB integration">
<InfluxDbIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -10,16 +10,13 @@ import {
CreateLoraCloudIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateLoRaCloudIntegration extends Component<IProps> {
onFinish = (obj: LoraCloudIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -28,9 +25,11 @@ class CreateLoRaCloudIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createLoraCloudIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
let i = new LoraCloudIntegration();
@ -41,8 +40,7 @@ class CreateLoRaCloudIntegration extends Component<IProps> {
i.setModemGeolocationServices(mgs);
return(
return (
<Card title="Add Semtech LoRa Cloud&trade; integration">
<LoRaCloudIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateMyDevicesIntegration extends Component<IProps> {
onFinish = (obj: MyDevicesIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreateMyDevicesIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createMyDevicesIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new MyDevicesIntegration();
return(
return (
<Card title="Add myDevices integration">
<MyDevicesIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreatePilotThingsIntegration extends Component<IProps> {
onFinish = (obj: PilotThingsIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreatePilotThingsIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createPilotThingsIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new PilotThingsIntegration();
return(
return (
<Card title="Add Pilot Things integration">
<PilotThingsIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -12,12 +12,10 @@ import {
import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
class CreateThingsBoardIntegration extends Component<IProps> {
onFinish = (obj: ThingsBoardIntegration) => {
obj.setApplicationId(this.props.application.getId());
@ -26,14 +24,16 @@ class CreateThingsBoardIntegration extends Component<IProps> {
req.setIntegration(obj);
ApplicationStore.createThingsBoardIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
const i = new ThingsBoardIntegration();
return(
return (
<Card title="Add ThingsBoard integration">
<ThingsBoardIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card>

View File

@ -22,7 +22,6 @@ interface IState {
integration?: AwsSnsIntegration;
}
class EditAwsSnsIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -45,16 +44,18 @@ class EditAwsSnsIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateAwsSnsIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update AWS SNS integration">
<AwsSnsIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,17 +14,14 @@ import {
import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
interface IState {
integration?: AzureServiceBusIntegration;
}
class EditAzureServiceBusIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,16 +44,18 @@ class EditAzureServiceBusIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateAzureServiceBusIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update Azure Service-Bus integration">
<AzureServiceBusIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,17 +14,14 @@ import {
import GcpPubSubIntegrationForm from "./GcpPubSubIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
interface IState {
integration?: GcpPubSubIntegration;
}
class EditGcpPubSubIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,19 +44,21 @@ class EditGcpPubSubIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateGcpPubSubIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
<Card title="Update GCP Pub/Sub integration">
<GcpPubSubIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>
return (
<Card title="Update GCP Pub/Sub integration">
<GcpPubSubIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>
);
}
}

View File

@ -14,7 +14,6 @@ import {
import HttpIntegrationForm from "./HttpIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
@ -45,16 +44,18 @@ class EditHttpIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateHttpIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update HTTP integration">
<HttpIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,17 +14,14 @@ import {
import InfluxDbIntegrationForm from "./InfluxDbIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
interface IState {
integration?: InfluxDbIntegration;
}
class EditInfluxDbIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,17 +44,18 @@ class EditInfluxDbIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateInfluxDbIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update InfluxDB integration">
<InfluxDbIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,17 +14,14 @@ import {
import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
interface IState {
integration?: LoraCloudIntegration;
}
class EditLoRaCloudIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,16 +44,18 @@ class EditLoRaCloudIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateLoraCloudIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update Semtech LoRa Cloud&trade; integration">
<LoRaCloudIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,7 +14,6 @@ import {
import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
@ -23,7 +22,6 @@ interface IState {
integration?: MyDevicesIntegration;
}
class EditMyDevicesIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -46,16 +44,18 @@ class EditMyDevicesIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateMyDevicesIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update myDevices integration">
<MyDevicesIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,17 +14,14 @@ import {
import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
interface IState {
integration?: PilotThingsIntegration;
}
class EditPilotThingsIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -47,16 +44,18 @@ class EditPilotThingsIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updatePilotThingsIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update Pilot Things integration">
<PilotThingsIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -14,7 +14,6 @@ import {
import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps {
application: Application;
}
@ -23,7 +22,6 @@ interface IState {
integration?: ThingsBoardIntegration;
}
class EditThingsBoardIntegration extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -46,16 +44,18 @@ class EditThingsBoardIntegration extends Component<IProps, IState> {
req.setIntegration(obj);
ApplicationStore.updateThingsBoardIntegration(req, () => {
this.props.history.push(`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`);
this.props.history.push(
`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/integrations`,
);
});
}
};
render() {
if (this.state.integration === undefined) {
return null;
}
return(
return (
<Card title="Update ThingsBoard integration">
<ThingsBoardIntegrationForm initialValues={this.state.integration} onFinish={this.onFinish} />
</Card>

View File

@ -1,44 +1,40 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteGcpPubSubIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteGcpPubSubIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class GcpPubSubCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteGcpPubSubIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteGcpPubSubIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/gcp-pub-sub/create"><PlusOutlined /></Link>
<Link to="integrations/gcp-pub-sub/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/gcp-pub-sub/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/gcp-pub-sub/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +45,10 @@ class GcpPubSubCard extends Component<IProps> {
<Card
title="GCP Pub/Sub"
className="integration-card"
cover={<img alt="GCP Pub/Sub" src="/integrations/gcp_pubsub.png" style={{padding: 1}} />}
cover={<img alt="GCP Pub/Sub" src="/integrations/gcp_pubsub.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The Google Cloud Pub/Sub integration forwards events to a GCP Pub/Sub topic."
/>
<Card.Meta description="The Google Cloud Pub/Sub integration forwards events to a GCP Pub/Sub topic." />
</Card>
</Col>
);

View File

@ -2,18 +2,13 @@ import React, { Component } from "react";
import { Form, Input, Button, Select } from "antd";
import {
GcpPubSubIntegration,
Encoding,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { GcpPubSubIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: GcpPubSubIntegration;
onFinish: (obj: GcpPubSubIntegration) => void;
}
class GcpPubSubIntegrationForm extends Component<IProps> {
onFinish = (values: GcpPubSubIntegration.AsObject) => {
const v = Object.assign(this.props.initialValues.toObject(), values);
@ -26,15 +21,15 @@ class GcpPubSubIntegrationForm extends Component<IProps> {
i.setCredentialsFile(v.credentialsFile);
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Payload encoding"
name="encoding"
rules={[{required: true, message: "Please select an encoding!"}]}
rules={[{ required: true, message: "Please select an encoding!" }]}
>
<Select>
<Select.Option value={Encoding.JSON}>JSON</Select.Option>
@ -44,14 +39,14 @@ class GcpPubSubIntegrationForm extends Component<IProps> {
<Form.Item
label="GCP project ID"
name="projectId"
rules={[{required: true, message: "Please enter a GCP project ID!"}]}
rules={[{ required: true, message: "Please enter a GCP project ID!" }]}
>
<Input />
</Form.Item>
<Form.Item
label="GCP Pub/Sub topic name"
name="topicName"
rules={[{required: true, message: "Please enter a GCP Pub/Sub topic name!"}]}
rules={[{ required: true, message: "Please enter a GCP Pub/Sub topic name!" }]}
>
<Input />
</Form.Item>
@ -59,12 +54,14 @@ class GcpPubSubIntegrationForm extends Component<IProps> {
label="GCP Service account credentials file"
name="credentialsFile"
tooltip="Under IAM create a Service account with 'Pub/Sub Publisher' role, then put the content of the JSON key in this field."
rules={[{required: true, message: "Please enter a GCP Service account credentials file!"}]}
rules={[{ required: true, message: "Please enter a GCP Service account credentials file!" }]}
>
<Input.TextArea rows={10} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import moment from "moment";
import { Card, Button, Form, Input } from 'antd';
import { Card, Button, Form, Input } from "antd";
import {
Application,
@ -11,18 +11,15 @@ import {
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application;
}
interface IState {
certificate?: GenerateMqttIntegrationClientCertificateResponse;
buttonDisabled: boolean;
}
class GenerateMqttCertificate extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -40,28 +37,35 @@ class GenerateMqttCertificate extends Component<IProps, IState> {
let req = new GenerateMqttIntegrationClientCertificateRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.generateMqttIntegrationClientCertificate(req, (resp: GenerateMqttIntegrationClientCertificateResponse) => {
this.setState({
certificate: resp,
});
});
}
ApplicationStore.generateMqttIntegrationClientCertificate(
req,
(resp: GenerateMqttIntegrationClientCertificateResponse) => {
this.setState({
certificate: resp,
});
},
);
};
renderRequest = () => {
return(
return (
<Card>
<p>
If required by the network, the MQTT client needs to be configured with a client certificate
in order to connect to the MQTT broker to receive device data. The generated certificate is
application specific.
<strong> Please note the expiration of the certificate and make sure to renew the certificate on time!</strong>
If required by the network, the MQTT client needs to be configured with a client certificate in order to
connect to the MQTT broker to receive device data. The generated certificate is application specific.
<strong>
{" "}
Please note the expiration of the certificate and make sure to renew the certificate on time!
</strong>
</p>
<p>
<Button onClick={this.requestCertificate} disabled={this.state.buttonDisabled}>Generate certificate</Button>
<Button onClick={this.requestCertificate} disabled={this.state.buttonDisabled}>
Generate certificate
</Button>
</p>
</Card>
);
}
};
renderResponse = () => {
const certificate = this.state.certificate!;
@ -73,39 +77,31 @@ class GenerateMqttCertificate extends Component<IProps, IState> {
tlsKey: certificate.getTlsKey(),
};
return(
return (
<Form layout="vertical" initialValues={initial}>
<Form.Item
label="Expiration date"
name="expiresAt"
tooltip="The certificate expires at this date. Make sure to generate and configure a new certificate for your gateway before this expiration date."
>
<Input disabled style={{cursor: "text", color: "black"}} />
<Input disabled style={{ cursor: "text", color: "black" }} />
</Form.Item>
<Form.Item
label="CA certificate"
name="caCert"
tooltip="The CA certificate is to authenticate the certificate of the server. Store this as a text-file, e.g. named 'ca.crt'."
>
<Input.TextArea autoSize disabled style={{cursor: "text", fontFamily: "monospace", color: "black"}} />
<Input.TextArea autoSize disabled style={{ cursor: "text", fontFamily: "monospace", color: "black" }} />
</Form.Item>
<Form.Item
label="TLS certificate"
name="tlsCert"
tooltip="Store this as a text-file, e.g. named 'cert.crt'"
>
<Input.TextArea autoSize disabled style={{cursor: "text", fontFamily: "monospace", color: "black"}} />
<Form.Item label="TLS certificate" name="tlsCert" tooltip="Store this as a text-file, e.g. named 'cert.crt'">
<Input.TextArea autoSize disabled style={{ cursor: "text", fontFamily: "monospace", color: "black" }} />
</Form.Item>
<Form.Item
label="TLS key"
name="tlsKey"
tooltip="Store this as a text-file, e.g. named 'cert.key'"
>
<Input.TextArea autoSize disabled style={{cursor: "text", fontFamily: "monospace", color: "black"}} />
<Form.Item label="TLS key" name="tlsKey" tooltip="Store this as a text-file, e.g. named 'cert.key'">
<Input.TextArea autoSize disabled style={{ cursor: "text", fontFamily: "monospace", color: "black" }} />
</Form.Item>
</Form>
);
}
};
render() {
let content = this.renderRequest();
@ -114,11 +110,7 @@ class GenerateMqttCertificate extends Component<IProps, IState> {
content = this.renderResponse();
}
return(
<Card title="Generate MQTT certificate">
{content}
</Card>
);
return <Card title="Generate MQTT certificate">{content}</Card>;
}
}

View File

@ -1,44 +1,40 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteHttpIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteHttpIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class HttpCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteHttpIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteHttpIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/http/create"><PlusOutlined /></Link>
<Link to="integrations/http/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/http/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/http/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +45,10 @@ class HttpCard extends Component<IProps> {
<Card
title="HTTP"
className="integration-card"
cover={<img alt="HTTP" src="/integrations/http.png" style={{padding: 1}} />}
cover={<img alt="HTTP" src="/integrations/http.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The HTTP integration forwards events to a user-configurable endpoint as POST requests."
/>
<Card.Meta description="The HTTP integration forwards events to a user-configurable endpoint as POST requests." />
</Card>
</Col>
);

View File

@ -1,13 +1,9 @@
import React, { Component } from "react";
import { Form, Input, Button, Select, Row, Col, Typography, Space } from "antd";
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import {
HttpIntegration,
Encoding,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import { HttpIntegration, Encoding } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: HttpIntegration;
@ -29,15 +25,15 @@ class HttpIntegrationForm extends Component<IProps> {
}
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Payload encoding"
name="encoding"
rules={[{required: true, message: "Please select an encoding!"}]}
rules={[{ required: true, message: "Please select an encoding!" }]}
>
<Select>
<Select.Option value={Encoding.JSON}>JSON</Select.Option>
@ -48,14 +44,14 @@ class HttpIntegrationForm extends Component<IProps> {
label="Event endpoint URL(s)"
name="eventEndpointUrl"
tooltip="ChirpStack will make a POST request to this URL(s) with 'event' as query parameter. Multiple URLs can be defined as a comma separated list. Whitespace will be automatically removed."
rules={[{required: true, message: "Please enter an event endpoint URL!"}]}
rules={[{ required: true, message: "Please enter an event endpoint URL!" }]}
>
<Input />
</Form.Item>
<Space direction="vertical" style={{width: "100%"}}>
<Space direction="vertical" style={{ width: "100%" }}>
<Typography.Text>Headers</Typography.Text>
<Form.List name="headersMap">
{(fields, { add, remove }) => (
{(fields, { add, remove }) => (
<>
{fields.map(( {key, name, ...restField} ) => (
<Row gutter={24}>
@ -64,7 +60,7 @@ class HttpIntegrationForm extends Component<IProps> {
{...restField}
name={[name, 0]}
fieldKey={[name, 0]}
rules={[{ required: true, message: 'Please enter a key!' }]}
rules={[{ required: true, message: "Please enter a key!" }]}
>
<Input placeholder="Key" />
</Form.Item>
@ -74,14 +70,14 @@ class HttpIntegrationForm extends Component<IProps> {
{...restField}
name={[name, 1]}
fieldKey={[name, 1]}
rules={[{ required: true, message: 'Please enter a value!' }]}
rules={[{ required: true, message: "Please enter a value!" }]}
>
<Input placeholder="Value" />
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
))}
<Form.Item>
@ -94,7 +90,9 @@ class HttpIntegrationForm extends Component<IProps> {
</Form.List>
</Space>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -8,18 +8,15 @@ import {
InfluxDbVersion,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: InfluxDbIntegration;
onFinish: (obj: InfluxDbIntegration) => void;
}
interface IState {
selectedVersion: InfluxDbVersion;
}
class InfluxDbIntegrationForm extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -45,21 +42,21 @@ class InfluxDbIntegrationForm extends Component<IProps, IState> {
i.setToken(v.token);
this.props.onFinish(i);
}
};
onVersionChange = (version: InfluxDbVersion) => {
this.setState({
selectedVersion: version,
});
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="InfluxDB version"
name="version"
rules={[{required: true, message: "Please select an InfluxDB version!"}]}
rules={[{ required: true, message: "Please select an InfluxDB version!" }]}
>
<Select onChange={this.onVersionChange}>
<Select.Option value={InfluxDbVersion.INFLUXDB_1}>InfluxDB v1</Select.Option>
@ -69,71 +66,69 @@ class InfluxDbIntegrationForm extends Component<IProps, IState> {
<Form.Item
label="API endpoint (write)"
name="endpoint"
rules={[{required: true, message: "Please enter an endpoint!"}]}
rules={[{ required: true, message: "Please enter an endpoint!" }]}
>
<Input
placeholder="http://localhost:8086/api/v2/write"
/>
<Input placeholder="http://localhost:8086/api/v2/write" />
</Form.Item>
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && <Form.Item
label="Username"
name="username"
>
<Input />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && <Form.Item
label="Password"
name="password"
>
<Input.Password />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && <Form.Item
label="Database name"
name="db"
rules={[{required: true, message: "Please enter database name!"}]}
>
<Input />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && <Form.Item
label="Retention policy name"
name="retentionPolicyName"
tooltip="Sets the target retention policy for the write. InfluxDB writes to the DEFAULT retention policy if you do not specify a retention policy."
>
<Input />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && <Form.Item
label="Select timestamp precision"
name="precision"
>
<Select>
<Select.Option value={InfluxDbPrecision.NS}>Nanosecond</Select.Option>
<Select.Option value={InfluxDbPrecision.U}>Microsecond</Select.Option>
<Select.Option value={InfluxDbPrecision.MS}>Millisecond</Select.Option>
<Select.Option value={InfluxDbPrecision.S}>Second</Select.Option>
<Select.Option value={InfluxDbPrecision.M}>Minute</Select.Option>
<Select.Option value={InfluxDbPrecision.H}>Hour</Select.Option>
</Select>
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && <Form.Item
label="Organization"
name="organization"
>
<Input />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && <Form.Item
label="Bucket"
name="bucket"
>
<Input />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && <Form.Item
label="Token"
name="token"
>
<Input.Password />
</Form.Item>}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
<Form.Item label="Username" name="username">
<Input />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
<Form.Item label="Password" name="password">
<Input.Password />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
<Form.Item
label="Database name"
name="db"
rules={[{ required: true, message: "Please enter database name!" }]}
>
<Input />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
<Form.Item
label="Retention policy name"
name="retentionPolicyName"
tooltip="Sets the target retention policy for the write. InfluxDB writes to the DEFAULT retention policy if you do not specify a retention policy."
>
<Input />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_1 && (
<Form.Item label="Select timestamp precision" name="precision">
<Select>
<Select.Option value={InfluxDbPrecision.NS}>Nanosecond</Select.Option>
<Select.Option value={InfluxDbPrecision.U}>Microsecond</Select.Option>
<Select.Option value={InfluxDbPrecision.MS}>Millisecond</Select.Option>
<Select.Option value={InfluxDbPrecision.S}>Second</Select.Option>
<Select.Option value={InfluxDbPrecision.M}>Minute</Select.Option>
<Select.Option value={InfluxDbPrecision.H}>Hour</Select.Option>
</Select>
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
<Form.Item label="Organization" name="organization">
<Input />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
<Form.Item label="Bucket" name="bucket">
<Input />
</Form.Item>
)}
{this.state.selectedVersion === InfluxDbVersion.INFLUXDB_2 && (
<Form.Item label="Token" name="token">
<Input.Password />
</Form.Item>
)}
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,44 +1,40 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteInfluxDbIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteInfluxDbIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class InfluxdbCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteInfluxDbIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteInfluxDbIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/influxdb/create"><PlusOutlined /></Link>
<Link to="integrations/influxdb/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/influxdb/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/influxdb/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +45,10 @@ class InfluxdbCard extends Component<IProps> {
<Card
title="InfluxDB"
className="integration-card"
cover={<img alt="InfluxDB" src="/integrations/influxdb.png" style={{padding: 1}} />}
cover={<img alt="InfluxDB" src="/integrations/influxdb.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The InfluxDB integration writes events into an InfluxDB time-series database."
/>
<Card.Meta description="The InfluxDB integration writes events into an InfluxDB time-series database." />
</Card>
</Col>
);

View File

@ -1,44 +1,40 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteLoraCloudIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteLoraCloudIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class LoRaCloudCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteLoraCloudIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteLoraCloudIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/loracloud/create"><PlusOutlined /></Link>
<Link to="integrations/loracloud/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/loracloud/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/loracloud/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +45,10 @@ class LoRaCloudCard extends Component<IProps> {
<Card
title="Semtech LoRa Cloud&trade;"
className="integration-card"
cover={<img alt="Semtech LoRa Cloud" src="/integrations/loracloud.png" style={{padding: 1}} />}
cover={<img alt="Semtech LoRa Cloud" src="/integrations/loracloud.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The Semtech LoRa Cloud integration provides Modem & Geolocation Services."
/>
<Card.Meta description="The Semtech LoRa Cloud integration provides Modem & Geolocation Services." />
</Card>
</Col>
);

View File

@ -7,13 +7,11 @@ import {
LoraCloudModemGeolocationServices,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: LoraCloudIntegration;
onFinish: (obj: LoraCloudIntegration) => void;
}
interface IState {
modemEnabled: boolean;
geolocationTdoa: boolean;
@ -22,7 +20,6 @@ interface IState {
geolocationGnss: boolean;
}
class LoRaCloudIntegrationForm extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -56,7 +53,6 @@ class LoRaCloudIntegrationForm extends Component<IProps, IState> {
let mgs = new LoraCloudModemGeolocationServices();
if (mgsv !== undefined) {
mgs.setToken(mgsv.token);
mgs.setModemEnabled(mgsv.modemEnabled);
@ -80,48 +76,48 @@ class LoRaCloudIntegrationForm extends Component<IProps, IState> {
i.setModemGeolocationServices(mgs);
this.props.onFinish(i);
}
};
onModemEnabledChange = (v: boolean) => {
this.setState({
modemEnabled: v,
});
}
};
onGeolocationTdoaChange = (v: boolean) => {
this.setState({
geolocationTdoa: v,
});
}
};
onGeolocationRssiChange = (v: boolean) => {
this.setState({
geolocationRssi: v,
});
}
};
onGeolocationWifiChange = (v: boolean) => {
this.setState({
geolocationWifi: v,
});
}
};
onGeolocationGnssChange = (v: boolean) => {
this.setState({
geolocationGnss: v,
});
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Tabs>
<Tabs.TabPane tab="Modem & Geolocation Services" key="1">
<Form.Item
label="Token"
name={["modemGeolocationServices", "token"]}
tooltip="This token can be obtained from loracloud.com"
rules={[{required: true, message: "Please enter a token!"}]}
rules={[{ required: true, message: "Please enter a token!" }]}
>
<Input />
</Form.Item>
@ -132,39 +128,47 @@ class LoRaCloudIntegrationForm extends Component<IProps, IState> {
>
<Switch onChange={this.onModemEnabledChange} />
</Form.Item>
{this.state.modemEnabled && <Form.Item
label="GNSS port (FPort)"
name={["modemGeolocationServices", "gnssPort"]}
tooltip="ChirpStack will only forward the FRMPayload for GNSS geolocation to LoRa Cloud when the uplink matches the configured port."
rules={[{required: true, message: "Please enter a port number!"}]}
>
<InputNumber min={0} max={255} />
</Form.Item>}
{this.state.modemEnabled && <Form.Item
label="Modem port (FPort)"
name={["modemGeolocationServices", "modemPort"]}
tooltip="ChirpStack will only forward the FRMPayload to LoRa Cloud when the uplink matches the configured port."
rules={[{required: true, message: "Please enter a port number!"}]}
>
<InputNumber min={0} max={255} />
</Form.Item>}
{this.state.modemEnabled && <Form.Item
label="Use receive timestamp for GNSS geolocation"
name={["modemGeolocationServices", "gnssUseRxTime"]}
tooltip="If enabled, the receive timestamp of the gateway will be used as reference instead of the timestamp included in the GNSS payload."
valuePropName="checked"
>
<Switch />
</Form.Item>}
{this.state.modemEnabled && <Form.Item
label="My device adheres to the LoRa Edge&trade; Tracker Reference Design protocol"
name={["modemGeolocationServices", "parseTlv"]}
tooltip="If enabled, ChirpStack Application Server will try to resolve the location of the device if a geolocation payload is detected."
valuePropName="checked"
>
<Switch />
</Form.Item>}
<Collapse style={{marginBottom: 24}}>
{this.state.modemEnabled && (
<Form.Item
label="GNSS port (FPort)"
name={["modemGeolocationServices", "gnssPort"]}
tooltip="ChirpStack will only forward the FRMPayload for GNSS geolocation to LoRa Cloud when the uplink matches the configured port."
rules={[{ required: true, message: "Please enter a port number!" }]}
>
<InputNumber min={0} max={255} />
</Form.Item>
)}
{this.state.modemEnabled && (
<Form.Item
label="Modem port (FPort)"
name={["modemGeolocationServices", "modemPort"]}
tooltip="ChirpStack will only forward the FRMPayload to LoRa Cloud when the uplink matches the configured port."
rules={[{ required: true, message: "Please enter a port number!" }]}
>
<InputNumber min={0} max={255} />
</Form.Item>
)}
{this.state.modemEnabled && (
<Form.Item
label="Use receive timestamp for GNSS geolocation"
name={["modemGeolocationServices", "gnssUseRxTime"]}
tooltip="If enabled, the receive timestamp of the gateway will be used as reference instead of the timestamp included in the GNSS payload."
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
{this.state.modemEnabled && (
<Form.Item
label="My device adheres to the LoRa Edge&trade; Tracker Reference Design protocol"
name={["modemGeolocationServices", "parseTlv"]}
tooltip="If enabled, ChirpStack Application Server will try to resolve the location of the device if a geolocation payload is detected."
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Collapse style={{ marginBottom: 24 }}>
<Collapse.Panel header="Advanced geolocation options" key={1}>
<Form.Item
label="TDOA based geolocation"
@ -198,47 +202,59 @@ class LoRaCloudIntegrationForm extends Component<IProps, IState> {
>
<Switch onChange={this.onGeolocationGnssChange} />
</Form.Item>
{(this.state.geolocationTdoa || this.state.geolocationRssi) && <Form.Item
label="Geolocation buffer (TTL in seconds)"
name={["modemGeolocationServices", "geolocationBufferTtl"]}
tooltip="The time in seconds that historical uplinks will be stored in the geolocation buffer. Used for TDOA and RSSI geolocation."
>
<InputNumber min={0} max={86400} />
</Form.Item>}
{(this.state.geolocationTdoa || this.state.geolocationRssi) && <Form.Item
label="Geolocation min buffer size"
name={["modemGeolocationServices", "geolocationMinBufferSize"]}
tooltip="The minimum buffer size required before using geolocation. Using multiple uplinks for geolocation can increase the accuracy of the geolocation results. Used for TDOA and RSSI geolocation."
>
<InputNumber min={0} />
</Form.Item>}
{this.state.geolocationWifi && <Form.Item
label="Wifi payload field"
name={["modemGeolocationServices", "geolocationWifiPayloadField"]}
tooltip="This must match the name of the field in the decoded payload which holds array of Wifi access-points. Each element in the array must contain two keys: 1) macAddress: array of 6 bytes, 2) signalStrength: RSSI of the access-point."
>
<Input />
</Form.Item>}
{this.state.geolocationGnss && <Form.Item
label="GNSS payload field"
name={["modemGeolocationServices", "geolocationGnssPayloadField"]}
tooltip="This must match the name of the field in the decoded payload which holds the LR1110 GNSS bytes."
>
<Input />
</Form.Item>}
{this.state.geolocationGnss && <Form.Item
label="Use receive timestamp for GNSS geolocation"
name={["modemGeolocationServices", "geolocationGnssUseRxTime"]}
valuePropName="checked"
>
<Switch />
</Form.Item>}
{(this.state.geolocationTdoa || this.state.geolocationRssi) && (
<Form.Item
label="Geolocation buffer (TTL in seconds)"
name={["modemGeolocationServices", "geolocationBufferTtl"]}
tooltip="The time in seconds that historical uplinks will be stored in the geolocation buffer. Used for TDOA and RSSI geolocation."
>
<InputNumber min={0} max={86400} />
</Form.Item>
)}
{(this.state.geolocationTdoa || this.state.geolocationRssi) && (
<Form.Item
label="Geolocation min buffer size"
name={["modemGeolocationServices", "geolocationMinBufferSize"]}
tooltip="The minimum buffer size required before using geolocation. Using multiple uplinks for geolocation can increase the accuracy of the geolocation results. Used for TDOA and RSSI geolocation."
>
<InputNumber min={0} />
</Form.Item>
)}
{this.state.geolocationWifi && (
<Form.Item
label="Wifi payload field"
name={["modemGeolocationServices", "geolocationWifiPayloadField"]}
tooltip="This must match the name of the field in the decoded payload which holds array of Wifi access-points. Each element in the array must contain two keys: 1) macAddress: array of 6 bytes, 2) signalStrength: RSSI of the access-point."
>
<Input />
</Form.Item>
)}
{this.state.geolocationGnss && (
<Form.Item
label="GNSS payload field"
name={["modemGeolocationServices", "geolocationGnssPayloadField"]}
tooltip="This must match the name of the field in the decoded payload which holds the LR1110 GNSS bytes."
>
<Input />
</Form.Item>
)}
{this.state.geolocationGnss && (
<Form.Item
label="Use receive timestamp for GNSS geolocation"
name={["modemGeolocationServices", "geolocationGnssUseRxTime"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
</Collapse.Panel>
</Collapse>
</Tabs.TabPane>
</Tabs>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,35 +1,27 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card } from 'antd';
import {
Application,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Col, Card } from "antd";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
application: Application,
application: Application;
}
class HttpCard extends Component<IProps> {
render() {
let actions: any[] = [
<Link to="integrations/mqtt/certificate">Get certificate</Link>
];
let actions: any[] = [<Link to="integrations/mqtt/certificate">Get certificate</Link>];
return (
<Col span={8}>
<Card
title="MQTT"
className="integration-card"
cover={<img alt="MQTT" src="/integrations/mqtt.png" style={{padding: 1}} />}
cover={<img alt="MQTT" src="/integrations/mqtt.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The MQTT integration forwards events to a MQTT broker."
/>
<Card.Meta description="The MQTT integration forwards events to a MQTT broker." />
</Card>
</Col>
);

View File

@ -1,44 +1,40 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteMyDevicesIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { Application, DeleteMyDevicesIntegrationRequest } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class MyDevicesCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteMyDevicesIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteMyDevicesIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/mydevices/create"><PlusOutlined /></Link>
<Link to="integrations/mydevices/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/mydevices/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/mydevices/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +45,10 @@ class MyDevicesCard extends Component<IProps> {
<Card
title="myDevices"
className="integration-card"
cover={<img alt="myDevices" src="/integrations/my_devices.png" style={{padding: 1}} />}
cover={<img alt="myDevices" src="/integrations/my_devices.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The myDevices integration forwards events to the myDevices platform."
/>
<Card.Meta description="The myDevices integration forwards events to the myDevices platform." />
</Card>
</Col>
);

View File

@ -2,23 +2,18 @@ import React, { Component } from "react";
import { Form, Input, Button, Select } from "antd";
import {
MyDevicesIntegration,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { MyDevicesIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: MyDevicesIntegration;
onFinish: (obj: MyDevicesIntegration) => void;
}
interface IState {
selectedEndpoint: string;
customEndpoint: string;
}
class MyDevicesIntegrationForm extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -40,43 +35,49 @@ class MyDevicesIntegrationForm extends Component<IProps, IState> {
}
this.props.onFinish(i);
}
};
onEndpointChange = (v: string) => {
this.setState({
selectedEndpoint: v,
});
}
};
onCustomEndpointChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
customEndpoint: e.target.value,
});
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Select myDevices endpoint"
name="endpoint"
rules={[{required: true, message: "Please select a myDevices endpoint!"}]}
rules={[{ required: true, message: "Please select a myDevices endpoint!" }]}
>
<Select onChange={this.onEndpointChange}>
<Select.Option value="https://lora.mydevices.com/v1/networks/chirpstackio/uplink">Cayenne</Select.Option>
<Select.Option value="https://lora.iotinabox.com/v1/networks/iotinabox.chirpstackio/uplink">IoT in a Box</Select.Option>
<Select.Option value="https://lora.iotinabox.com/v1/networks/iotinabox.chirpstackio/uplink">
IoT in a Box
</Select.Option>
<Select.Option value="custom">Custom endpoint URL</Select.Option>
</Select>
</Form.Item>
{this.state.selectedEndpoint === "custom" && <Form.Item
label="myDevices API endpoint"
name="customEndpoint"
rules={[{required: true, message: "Please enter an API endpoint!"}]}
>
<Input onChange={this.onCustomEndpointChange} />
</Form.Item>}
{this.state.selectedEndpoint === "custom" && (
<Form.Item
label="myDevices API endpoint"
name="customEndpoint"
rules={[{ required: true, message: "Please enter an API endpoint!" }]}
>
<Input onChange={this.onCustomEndpointChange} />
</Form.Item>
)}
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
@ -11,34 +11,33 @@ import {
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class PilotThingsCard extends Component<IProps> {
onDelete = () => {
let req = new DeletePilotThingsIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deletePilotThingsIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/pilot-things/create"><PlusOutlined /></Link>
<Link to="integrations/pilot-things/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/pilot-things/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/pilot-things/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +48,10 @@ class PilotThingsCard extends Component<IProps> {
<Card
title="Pilot Things"
className="integration-card"
cover={<img alt="Pilot Things" src="/integrations/pilot_things.png" style={{padding: 1}} />}
cover={<img alt="Pilot Things" src="/integrations/pilot_things.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The Pilot Things integration forwards messages to a Pilot Things instance."
/>
<Card.Meta description="The Pilot Things integration forwards messages to a Pilot Things instance." />
</Card>
</Col>
);

View File

@ -2,18 +2,13 @@ import React, { Component } from "react";
import { Form, Input, Button } from "antd";
import {
PilotThingsIntegration,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { PilotThingsIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: PilotThingsIntegration;
onFinish: (obj: PilotThingsIntegration) => void;
}
class PilotThingsIntegrationForm extends Component<IProps> {
onFinish = (values: PilotThingsIntegration.AsObject) => {
const v = Object.assign(this.props.initialValues.toObject(), values);
@ -24,27 +19,29 @@ class PilotThingsIntegrationForm extends Component<IProps> {
i.setToken(v.token);
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="Pilot Things server"
name="server"
rules={[{required: true, message: "Please enter a Pilot Things server!"}]}
rules={[{ required: true, message: "Please enter a Pilot Things server!" }]}
>
<Input placeholder="https://host:port" />
</Form.Item>
<Form.Item
label="Authentication token"
name="token"
rules={[{required: true, message: "Please enter a Pilot Things token!"}]}
rules={[{ required: true, message: "Please enter a Pilot Things token!" }]}
>
<Input.Password />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,44 +1,43 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Col, Card, Popconfirm } from 'antd';
import { Col, Card, Popconfirm } from "antd";
import { PlusOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons";
import {
Application,
DeleteThingsBoardIntegrationRequest
DeleteThingsBoardIntegrationRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps {
application: Application,
application: Application;
add?: boolean;
}
class ThingsBoardCard extends Component<IProps> {
onDelete = () => {
let req = new DeleteThingsBoardIntegrationRequest();
req.setApplicationId(this.props.application.getId());
ApplicationStore.deleteThingsBoardIntegration(req, () => {});
}
};
render() {
let actions: any[] = [];
if (!!this.props.add) {
actions = [
<Link to="integrations/thingsboard/create"><PlusOutlined /></Link>
<Link to="integrations/thingsboard/create">
<PlusOutlined />
</Link>,
];
} else {
actions = [
<Link to="integrations/thingsboard/edit"><EditOutlined /></Link>,
<Popconfirm
title="Are you sure you want to delete this integration?"
onConfirm={this.onDelete}
>
<Link to="integrations/thingsboard/edit">
<EditOutlined />
</Link>,
<Popconfirm title="Are you sure you want to delete this integration?" onConfirm={this.onDelete}>
<DeleteOutlined />
</Popconfirm>,
];
@ -49,12 +48,10 @@ class ThingsBoardCard extends Component<IProps> {
<Card
title="ThingsBoard"
className="integration-card"
cover={<img alt="ThingsBoard" src="/integrations/thingsboard.png" style={{padding: 1}} />}
cover={<img alt="ThingsBoard" src="/integrations/thingsboard.png" style={{ padding: 1 }} />}
actions={actions}
>
<Card.Meta
description="The ThingsBoard integration forwards events to a ThingsBoard instance."
/>
<Card.Meta description="The ThingsBoard integration forwards events to a ThingsBoard instance." />
</Card>
</Col>
);

View File

@ -2,17 +2,13 @@ import React, { Component } from "react";
import { Form, Input, Button, Typography } from "antd";
import {
ThingsBoardIntegration,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { ThingsBoardIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps {
initialValues: ThingsBoardIntegration;
onFinish: (obj: ThingsBoardIntegration) => void;
}
class ThingsBoardIntegrationForm extends Component<IProps> {
onFinish = (values: ThingsBoardIntegration.AsObject) => {
const v = Object.assign(this.props.initialValues.toObject(), values);
@ -22,25 +18,28 @@ class ThingsBoardIntegrationForm extends Component<IProps> {
i.setServer(v.server);
this.props.onFinish(i);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item
label="ThingsBoard server"
name="server"
rules={[{required: true, message: "Please enter the address to the ThingsBoard server!"}]}
rules={[{ required: true, message: "Please enter the address to the ThingsBoard server!" }]}
>
<Input placeholder="http://host:port" />
</Form.Item>
<Form.Item>
<Typography.Paragraph>
Each device must have a 'ThingsBoardAccessToken' variable assigned. This access-token is generated by ThingsBoard.
Each device must have a 'ThingsBoardAccessToken' variable assigned. This access-token is generated by
ThingsBoard.
</Typography.Paragraph>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { presetPalettes } from '@ant-design/colors';
import { presetPalettes } from "@ant-design/colors";
import { Space, Breadcrumb, Card, Row, Col, PageHeader, Empty } from "antd";
import moment from "moment";
@ -30,7 +30,7 @@ interface GatewaysMapState {
items: GatewayListItem[];
}
class GatewaysMap extends Component<{},GatewaysMapState> {
class GatewaysMap extends Component<{}, GatewaysMapState> {
constructor(props: {}) {
super(props);
@ -51,17 +51,15 @@ class GatewaysMap extends Component<{},GatewaysMapState> {
items: resp.getResultList(),
});
});
}
};
render() {
if (this.state.items.length === 0) {
return(
<Empty />
);
return <Empty />;
}
const boundsOptions: {
padding: PointTuple,
padding: PointTuple;
} = {
padding: [50, 50],
};
@ -79,7 +77,7 @@ class GatewaysMap extends Component<{},GatewaysMapState> {
if (item.getLastSeenAt() !== undefined) {
let ts = moment(item.getLastSeenAt()!.toDate());
lastSeen = ts.fromNow();
if (ts.isBefore(moment().subtract(5, 'minutes'))) {
if (ts.isBefore(moment().subtract(5, "minutes"))) {
color = "red";
} else {
color = "green";
@ -89,15 +87,18 @@ class GatewaysMap extends Component<{},GatewaysMapState> {
markers.push(
<Marker position={pos} faIcon="wifi" color={color}>
<Popup>
<Link to={`/tenants/${item.getTenantId()}/gateways/${item.getGatewayId()}`}>{item.getName()}</Link><br />
{item.getGatewayId()}<br /><br />
<Link to={`/tenants/${item.getTenantId()}/gateways/${item.getGatewayId()}`}>{item.getName()}</Link>
<br />
{item.getGatewayId()}
<br />
<br />
{lastSeen}
</Popup>
</Marker>
</Marker>,
);
}
return(
return (
<Map height={500} bounds={bounds} boundsOptions={boundsOptions}>
{markers}
</Map>
@ -105,18 +106,18 @@ class GatewaysMap extends Component<{},GatewaysMapState> {
}
}
interface GatewayProps {
summary?: GetGatewaysSummaryResponse;
}
class GatewaysActiveInactive extends Component<GatewayProps> {
render() {
if (this.props.summary === undefined || (
this.props.summary.getNeverSeenCount() === 0 &&
this.props.summary.getInactiveCount() === 0 &&
this.props.summary.getActiveCount() === 0
)) {
if (
this.props.summary === undefined ||
(this.props.summary.getNeverSeenCount() === 0 &&
this.props.summary.getInactiveCount() === 0 &&
this.props.summary.getActiveCount() === 0)
) {
return <Empty />;
}
@ -124,13 +125,13 @@ class GatewaysActiveInactive extends Component<GatewayProps> {
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,
data: [
this.props.summary.getNeverSeenCount(),
this.props.summary.getInactiveCount(),
this.props.summary.getActiveCount(),
],
}
backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
},
],
};
@ -140,24 +141,22 @@ class GatewaysActiveInactive extends Component<GatewayProps> {
animation: false,
};
return(
<Doughnut data={data} options={options} className="chart-doughtnut" />
);
return <Doughnut data={data} options={options} className="chart-doughtnut" />;
}
}
interface DeviceProps {
summary?: GetDevicesSummaryResponse;
}
class DevicesActiveInactive extends Component<DeviceProps> {
render() {
if (this.props.summary === undefined || (
this.props.summary.getNeverSeenCount() === 0 &&
this.props.summary.getInactiveCount() === 0 &&
this.props.summary.getActiveCount() === 0
)) {
if (
this.props.summary === undefined ||
(this.props.summary.getNeverSeenCount() === 0 &&
this.props.summary.getInactiveCount() === 0 &&
this.props.summary.getActiveCount() === 0)
) {
return <Empty />;
}
@ -165,13 +164,13 @@ class DevicesActiveInactive extends Component<DeviceProps> {
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,
data: [
this.props.summary.getNeverSeenCount(),
this.props.summary.getInactiveCount(),
this.props.summary.getActiveCount(),
],
}
backgroundColor: [presetPalettes.orange.primary, presetPalettes.red.primary, presetPalettes.green.primary],
},
],
};
@ -181,18 +180,30 @@ class DevicesActiveInactive extends Component<DeviceProps> {
animation: false,
};
return(
<Doughnut data={data} options={options} className="chart-doughtnut" />
);
return <Doughnut data={data} options={options} className="chart-doughtnut" />;
}
}
class DevicesDataRates extends Component<DeviceProps> {
getColor = (dr: number) => {
return ['#ff5722', '#ff9800', '#ffc107', '#ffeb3b', '#cddc39', '#8bc34a', '#4caf50', '#009688', '#00bcd4', '#03a9f4', '#2196f3', '#3f51b5', '#673ab7', '#9c27b0', '#e91e63'][dr];
}
return [
"#ff5722",
"#ff9800",
"#ffc107",
"#ffeb3b",
"#cddc39",
"#8bc34a",
"#4caf50",
"#009688",
"#00bcd4",
"#03a9f4",
"#2196f3",
"#3f51b5",
"#673ab7",
"#9c27b0",
"#e91e63",
][dr];
};
render() {
if (this.props.summary === undefined || this.props.summary.getDrCountMap().toArray().length === 0) {
@ -200,17 +211,19 @@ class DevicesDataRates extends Component<DeviceProps> {
}
let data: {
labels: string[],
labels: string[];
datasets: {
data: number[],
backgroundColor: string[],
}[],
data: number[];
backgroundColor: string[];
}[];
} = {
labels: [],
datasets: [{
data: [],
backgroundColor: [],
}],
datasets: [
{
data: [],
backgroundColor: [],
},
],
};
for (const elm of this.props.summary.getDrCountMap().toArray()) {
@ -225,14 +238,11 @@ class DevicesDataRates extends Component<DeviceProps> {
animation: false,
};
return(
<Doughnut data={data} options={options} className="chart-doughtnut" />
);
return <Doughnut data={data} options={options} className="chart-doughtnut" />;
}
}
interface IProps{}
interface IProps {}
interface IState {
gatewaysSummary?: GetGatewaysSummaryResponse;
@ -260,17 +270,19 @@ class Dashboard extends Component<IProps, IState> {
}
render() {
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Network Server</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Dashboard</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Dashboard"
/>
<Row gutter={24}>
@ -290,7 +302,9 @@ class Dashboard extends Component<IProps, IState> {
</Card>
</Col>
</Row>
<Card title="Gateway map"><GatewaysMap /></Card>
<Card title="Gateway map">
<GatewaysMap />
</Card>
</Space>
);
}

View File

@ -4,18 +4,21 @@ import { Link, RouteComponentProps } from "react-router-dom";
import { Space, Breadcrumb, Card, PageHeader } from "antd";
import { MacVersion, RegParamsRevision } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
import { DeviceProfile, CreateDeviceProfileRequest, CreateDeviceProfileResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
DeviceProfile,
CreateDeviceProfileRequest,
CreateDeviceProfileResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import DeviceProfileForm from "./DeviceProfileForm";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
interface IProps extends RouteComponentProps {
tenant: Tenant,
tenant: Tenant;
}
class CreateDeviceProfile extends Component<IProps> {
onFinish = (obj: DeviceProfile) => {
obj.setTenantId(this.props.tenant.getId());
@ -26,7 +29,7 @@ class CreateDeviceProfile extends Component<IProps> {
DeviceProfileStore.create(req, (_resp: CreateDeviceProfileResponse) => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
});
}
};
render() {
const codecScript = `// Decode uplink function.
@ -71,23 +74,29 @@ function encodeDownlink(input) {
deviceProfile.setRegParamsRevision(RegParamsRevision.A);
deviceProfile.setFlushQueueOnActivate(true);
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/device-profiles`}>Device profiles</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/device-profiles`}>Device profiles</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Add</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Add device profile"
/>
<Card>

View File

@ -1,7 +1,7 @@
import React, { Component } from "react";
import { Form, Input, Select, InputNumber, Switch, Row, Col, Button, Tabs } from "antd";
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import { DeviceProfile, CodecRuntime } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import { Region, MacVersion, RegParamsRevision } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
@ -9,7 +9,6 @@ import { ListDeviceProfileAdrAlgorithmsResponse } from "@chirpstack/chirpstack-a
import DeviceProfileStore from "../../stores/DeviceProfileStore";
import CodeEditor from "../../components/CodeEditor";
interface IProps {
initialValues: DeviceProfile;
onFinish: (obj: DeviceProfile) => void;
@ -24,7 +23,6 @@ interface IState {
adrAlgorithms: [string, string][];
}
class DeviceProfileForm extends Component<IProps, IState> {
formRef = React.createRef<any>();
@ -101,51 +99,48 @@ class DeviceProfileForm extends Component<IProps, IState> {
}
this.props.onFinish(dp);
}
};
onSupportsOtaaChange = (checked: boolean) => {
this.setState({
supportsOtaa: checked,
});
}
};
onSupportsClassBChnage = (checked: boolean) => {
this.setState({
supportsClassB: checked,
});
}
};
onSupportsClassCChange = (checked: boolean) => {
this.setState({
supportsClassC: checked,
});
}
};
onPayloadCodecRuntimeChange = (value: CodecRuntime) => {
this.setState({
payloadCodecRuntime: value,
});
}
};
render() {
const adrOptions = this.state.adrAlgorithms.map((v) => <Select.Option value={v[0]}>{v[1]}</Select.Option>);
const adrOptions = this.state.adrAlgorithms.map(v => <Select.Option value={v[0]}>{v[1]}</Select.Option>);
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish} ref={this.formRef}>
return (
<Form
layout="vertical"
initialValues={this.props.initialValues.toObject()}
onFinish={this.onFinish}
ref={this.formRef}
>
<Tabs>
<Tabs.TabPane tab="General" key="1">
<Form.Item
label="Name"
name="name"
rules={[{required: true, message: "Please enter a name!"}]}
>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Input disabled={this.props.disabled} />
</Form.Item>
<Form.Item
label="Region"
name="region"
rules={[{required: true, message: "Please select a region!"}]}
>
<Form.Item label="Region" name="region" rules={[{ required: true, message: "Please select a region!" }]}>
<Select disabled={this.props.disabled}>
<Select.Option value={Region.AS923}>AS923</Select.Option>
<Select.Option value={Region.AS923_2}>AS923-2</Select.Option>
@ -168,7 +163,7 @@ class DeviceProfileForm extends Component<IProps, IState> {
label="MAC version"
tooltip="The LoRaWAN MAC version supported by the device."
name="macVersion"
rules={[{required: true, message: "Please select a MAC version!"}]}
rules={[{ required: true, message: "Please select a MAC version!" }]}
>
<Select disabled={this.props.disabled}>
<Select.Option value={MacVersion.LORAWAN_1_0_0}>LoRaWAN 1.0.0</Select.Option>
@ -185,7 +180,7 @@ class DeviceProfileForm extends Component<IProps, IState> {
label="Regional parameters revision"
tooltip="Revision of the Regional Parameters specification supported by the device."
name="regParamsRevision"
rules={[{required: true, message: "Please select a regional parameters revision!"}]}
rules={[{ required: true, message: "Please select a regional parameters revision!" }]}
>
<Select disabled={this.props.disabled}>
<Select.Option value={RegParamsRevision.A}>A</Select.Option>
@ -202,11 +197,9 @@ class DeviceProfileForm extends Component<IProps, IState> {
label="ADR algorithm"
tooltip="The ADR algorithm that will be used for controlling the device data-rate."
name="adrAlgorithmId"
rules={[{required: true, message: "Please select an ADR algorithm!"}]}
rules={[{ required: true, message: "Please select an ADR algorithm!" }]}
>
<Select disabled={this.props.disabled}>
{adrOptions}
</Select>
<Select disabled={this.props.disabled}>{adrOptions}</Select>
</Form.Item>
<Row gutter={24}>
<Col span={8}>
@ -241,19 +234,16 @@ class DeviceProfileForm extends Component<IProps, IState> {
</Row>
</Tabs.TabPane>
<Tabs.TabPane tab="Join (OTAA / ABP)" key="2">
<Form.Item
label="Device supports OTAA"
name="supportsOtaa"
valuePropName="checked"
>
<Form.Item label="Device supports OTAA" name="supportsOtaa" valuePropName="checked">
<Switch onChange={this.onSupportsOtaaChange} disabled={this.props.disabled} />
</Form.Item>
{!this.state.supportsOtaa && <Row>
{!this.state.supportsOtaa && (
<Row>
<Col span={12}>
<Form.Item
label="RX1 delay"
name="abpRx1Delay"
rules={[{required: true, message: "Please enter a RX1 delay!"}]}
rules={[{ required: true, message: "Please enter a RX1 delay!" }]}
>
<InputNumber min={0} max={15} disabled={this.props.disabled} />
</Form.Item>
@ -263,19 +253,21 @@ class DeviceProfileForm extends Component<IProps, IState> {
label="RX1 data-rate offset"
tooltip="Please refer the LoRaWAN Regional Parameters specification for valid values."
name="abpRx1DrOffset"
rules={[{required: true, message: "Please enter a RX1 data-rate offset!"}]}
rules={[{ required: true, message: "Please enter a RX1 data-rate offset!" }]}
>
<InputNumber min={0} max={15} disabled={this.props.disabled} />
</Form.Item>
</Col>
</Row>}
{!this.state.supportsOtaa && <Row>
</Row>
)}
{!this.state.supportsOtaa && (
<Row>
<Col span={12}>
<Form.Item
label="RX2 data-rate"
tooltip="Please refer the LoRaWAN Regional Parameters specification for valid values."
name="abpRx2Dr"
rules={[{required: true, message: "Please enter a RX2 data-rate!"}]}
rules={[{ required: true, message: "Please enter a RX2 data-rate!" }]}
>
<InputNumber min={0} max={15} disabled={this.props.disabled} />
</Form.Item>
@ -284,46 +276,43 @@ class DeviceProfileForm extends Component<IProps, IState> {
<Form.Item
label="RX2 frequency (Hz)"
name="abpRx2Freq"
rules={[{required: true, message: "Please enter a RX2 frequency!"}]}
rules={[{ required: true, message: "Please enter a RX2 frequency!" }]}
>
<InputNumber min={0} style={{width: "200px"}} disabled={this.props.disabled} />
<InputNumber min={0} style={{ width: "200px" }} disabled={this.props.disabled} />
</Form.Item>
</Col>
</Row>}
</Row>
)}
</Tabs.TabPane>
<Tabs.TabPane tab="Class-B" key="3">
<Form.Item
label="Device supports Class-B"
name="supportsClassB"
valuePropName="checked"
>
<Form.Item label="Device supports Class-B" name="supportsClassB" valuePropName="checked">
<Switch onChange={this.onSupportsClassBChnage} disabled={this.props.disabled} />
</Form.Item>
{this.state.supportsClassB && <Form.Item
label="Class-B confirmed downlink timeout (seconds)"
tooltip="Class-B timeout (in seconds) for confirmed downlink transmissions."
name="classBTimeout"
rules={[{required: true, message: "Please enter a Class-B confirmed downlink timeout!"}]}
>
<InputNumber min={0} />
</Form.Item>}
{this.state.supportsClassB && (
<Form.Item
label="Class-B confirmed downlink timeout (seconds)"
tooltip="Class-B timeout (in seconds) for confirmed downlink transmissions."
name="classBTimeout"
rules={[{ required: true, message: "Please enter a Class-B confirmed downlink timeout!" }]}
>
<InputNumber min={0} />
</Form.Item>
)}
</Tabs.TabPane>
<Tabs.TabPane tab="Class-C" key="4">
<Form.Item
label="Device supports Class-C"
name="supportsClassC"
valuePropName="checked"
>
<Form.Item label="Device supports Class-C" name="supportsClassC" valuePropName="checked">
<Switch onChange={this.onSupportsClassCChange} disabled={this.props.disabled} />
</Form.Item>
{this.state.supportsClassC && <Form.Item
label="Class-C confirmed downlink timeout (seconds)"
tooltip="Class-C timeout (in seconds) for confirmed downlink transmissions."
name="classCTimeout"
rules={[{required: true, message: "Please enter a Class-C confirmed downlink timeout!"}]}
>
<InputNumber min={0} disabled={this.props.disabled} />
</Form.Item>}
{this.state.supportsClassC && (
<Form.Item
label="Class-C confirmed downlink timeout (seconds)"
tooltip="Class-C timeout (in seconds) for confirmed downlink transmissions."
name="classCTimeout"
rules={[{ required: true, message: "Please enter a Class-C confirmed downlink timeout!" }]}
>
<InputNumber min={0} disabled={this.props.disabled} />
</Form.Item>
)}
</Tabs.TabPane>
<Tabs.TabPane tab="Codec" key="5">
<Form.Item
@ -346,7 +335,7 @@ class DeviceProfileForm extends Component<IProps, IState> {
</Tabs.TabPane>
<Tabs.TabPane tab="Tags" key="6">
<Form.List name="tagsMap">
{(fields, { add, remove }) => (
{(fields, { add, remove }) => (
<>
{fields.map(( {key, name, ...restField} ) => (
<Row gutter={24}>
@ -355,7 +344,7 @@ class DeviceProfileForm extends Component<IProps, IState> {
{...restField}
name={[name, 0]}
fieldKey={[name, 0]}
rules={[{ required: true, message: 'Please enter a key!' }]}
rules={[{ required: true, message: "Please enter a key!" }]}
>
<Input placeholder="Key" disabled={this.props.disabled} />
</Form.Item>
@ -365,18 +354,24 @@ class DeviceProfileForm extends Component<IProps, IState> {
{...restField}
name={[name, 1]}
fieldKey={[name, 1]}
rules={[{ required: true, message: 'Please enter a value!' }]}
rules={[{ required: true, message: "Please enter a value!" }]}
>
<Input placeholder="Value" disabled={this.props.disabled} />
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
))}
<Form.Item>
<Button disabled={this.props.disabled} type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
<Button
disabled={this.props.disabled}
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
Add tag
</Button>
</Form.Item>
@ -386,7 +381,9 @@ class DeviceProfileForm extends Component<IProps, IState> {
</Tabs.TabPane>
</Tabs>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={this.props.disabled}>Submit</Button>
<Button type="primary" htmlType="submit" disabled={this.props.disabled}>
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -4,7 +4,13 @@ import { RouteComponentProps, Link } from "react-router-dom";
import { Space, Breadcrumb, Card, Button, PageHeader } from "antd";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { DeviceProfile, GetDeviceProfileRequest, GetDeviceProfileResponse, UpdateDeviceProfileRequest, DeleteDeviceProfileRequest } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
DeviceProfile,
GetDeviceProfileRequest,
GetDeviceProfileResponse,
UpdateDeviceProfileRequest,
DeleteDeviceProfileRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import DeviceProfileForm from "./DeviceProfileForm";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
@ -12,7 +18,6 @@ import SessionStore from "../../stores/SessionStore";
import DeleteConfirm from "../../components/DeleteConfirm";
import Admin from "../../components/Admin";
interface IState {
deviceProfile?: DeviceProfile;
}
@ -25,7 +30,6 @@ interface IProps extends RouteComponentProps<MatchParams> {
tenant: Tenant;
}
class EditDeviceProfile extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -46,7 +50,7 @@ class EditDeviceProfile extends Component<IProps, IState> {
deviceProfile: resp.getDeviceProfile(),
});
});
}
};
onFinish = (obj: DeviceProfile) => {
let req = new UpdateDeviceProfileRequest();
@ -55,7 +59,7 @@ class EditDeviceProfile extends Component<IProps, IState> {
DeviceProfileStore.update(req, () => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
});
}
};
deleteDeviceProfile = () => {
let req = new DeleteDeviceProfileRequest();
@ -64,7 +68,7 @@ class EditDeviceProfile extends Component<IProps, IState> {
DeviceProfileStore.delete(req, () => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/device-profiles`);
});
}
};
render() {
const dp = this.state.deviceProfile;
@ -73,42 +77,50 @@ class EditDeviceProfile extends Component<IProps, IState> {
return null;
}
const disabled = !(SessionStore.isAdmin() || SessionStore.isTenantAdmin(this.props.tenant.getId()) || SessionStore.isTenantDeviceAdmin(this.props.tenant.getId()));
const disabled = !(
SessionStore.isAdmin() ||
SessionStore.isTenantAdmin(this.props.tenant.getId()) ||
SessionStore.isTenantDeviceAdmin(this.props.tenant.getId())
);
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/device-profiles`}>Device profiles</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/device-profiles`}>Device profiles</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>{dp.getName()}</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title={dp.getName()}
subTitle={`device profile id: ${dp.getId()}`}
extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin>
<DeleteConfirm
typ="device profile"
confirm={dp.getName()}
onConfirm={this.deleteDeviceProfile}
>
<Button danger type="primary">Delete device profile</Button>
<DeleteConfirm typ="device profile" confirm={dp.getName()} onConfirm={this.deleteDeviceProfile}>
<Button danger type="primary">
Delete device profile
</Button>
</DeleteConfirm>
</Admin>
</Admin>,
]}
/>
<Card>
<DeviceProfileForm initialValues={dp} disabled={disabled} onFinish={this.onFinish} />
</Card>
<Card>
<DeviceProfileForm initialValues={dp} disabled={disabled} onFinish={this.onFinish} />
</Card>
</Space>
);
}

View File

@ -4,7 +4,11 @@ import { Link } from "react-router-dom";
import { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table";
import { ListDeviceProfilesRequest, ListDeviceProfilesResponse, DeviceProfileListItem } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
ListDeviceProfilesRequest,
ListDeviceProfilesResponse,
DeviceProfileListItem,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Region } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
@ -13,12 +17,10 @@ import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
import Admin from "../../components/Admin";
interface IProps {
tenant: Tenant;
}
class ListDeviceProfiles extends Component<IProps> {
columns = (): ColumnsType<DeviceProfileListItem.AsObject> => {
return [
@ -97,7 +99,7 @@ class ListDeviceProfiles extends Component<IProps> {
},
},
];
}
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDeviceProfilesRequest();
@ -109,35 +111,37 @@ class ListDeviceProfiles extends Component<IProps> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
render() {
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Device profiles</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Device profiles"
extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin>
<Button type="primary"><Link to={`/tenants/${this.props.tenant.getId()}/device-profiles/create`}>Add device profile</Link></Button>
</Admin>
<Button type="primary">
<Link to={`/tenants/${this.props.tenant.getId()}/device-profiles/create`}>Add device profile</Link>
</Button>
</Admin>,
]}
/>
<DataTable
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
/>
<DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" />
</Space>
);
}

View File

@ -6,19 +6,20 @@ import { Space, Breadcrumb, Card, PageHeader } from "antd";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { CreateDeviceRequest, Device } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import { GetDeviceProfileRequest, GetDeviceProfileResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
GetDeviceProfileRequest,
GetDeviceProfileResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import DeviceForm from "./DeviceForm";
import DeviceStore from "../../stores/DeviceStore";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
interface IProps extends RouteComponentProps {
tenant: Tenant;
application: Application;
}
class CreateDevice extends Component<IProps> {
onFinish = (obj: Device) => {
obj.setApplicationId(this.props.application.getId());
@ -33,38 +34,52 @@ class CreateDevice extends Component<IProps> {
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`);
this.props.history.push(
`/tenants/${this.props.tenant.getId()}/applications/${this.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()}`);
this.props.history.push(
`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${obj.getDevEui()}`,
);
}
});
});
}
};
render() {
let device = new Device();
device.setApplicationId(this.props.application.getId());
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>{this.props.application.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>
{this.props.application.getName()}
</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>Add device</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title="Add device"
/>
<Card>

View File

@ -19,14 +19,12 @@ import AesKeyInput from "../../components/AesKeyInput";
import DevAddrInput from "../../components/DevAddrInput";
import DeviceStore from "../../stores/DeviceStore";
interface FormProps {
initialValues: DeviceActivationPb;
device: Device;
onFinish: (obj: DeviceActivationPb) => void;
}
class LW10DeviceActivationForm extends Component<FormProps> {
formRef = React.createRef<any>();
@ -44,11 +42,16 @@ class LW10DeviceActivationForm extends Component<FormProps> {
da.setNFCntDown(v.nFCntDown);
this.props.onFinish(da);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish} ref={this.formRef}>
return (
<Form
layout="vertical"
initialValues={this.props.initialValues.toObject()}
onFinish={this.onFinish}
ref={this.formRef}
>
<DevAddrInput
label="Device address"
name="devAddr"
@ -73,31 +76,26 @@ class LW10DeviceActivationForm extends Component<FormProps> {
/>
<Row gutter={24}>
<Col span={6}>
<Form.Item
label="Uplink frame-counter"
name="fCntUp"
>
<Form.Item label="Uplink frame-counter" name="fCntUp">
<InputNumber min={0} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
label="Downlink frame-counter"
name="nFCntDown"
>
<Form.Item label="Downlink frame-counter" name="nFCntDown">
<InputNumber min={0} />
</Form.Item>
</Col>
</Row>
<Form.Item>
<Button type="primary" htmlType="submit">(Re)activate device</Button>
<Button type="primary" htmlType="submit">
(Re)activate device
</Button>
</Form.Item>
</Form>
);
}
}
class LW11DeviceActivationForm extends Component<FormProps> {
formRef = React.createRef<any>();
@ -115,11 +113,16 @@ class LW11DeviceActivationForm extends Component<FormProps> {
da.setNFCntDown(v.nFCntDown);
this.props.onFinish(da);
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish} ref={this.formRef}>
return (
<Form
layout="vertical"
initialValues={this.props.initialValues.toObject()}
onFinish={this.onFinish}
ref={this.formRef}
>
<DevAddrInput
label="Device address"
name="devAddr"
@ -158,39 +161,31 @@ class LW11DeviceActivationForm extends Component<FormProps> {
/>
<Row gutter={24}>
<Col span={6}>
<Form.Item
label="Uplink frame-counter"
name="fCntUp"
>
<Form.Item label="Uplink frame-counter" name="fCntUp">
<InputNumber min={0} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
label="Downlink frame-counter (network)"
name="nFCntDown"
>
<Form.Item label="Downlink frame-counter (network)" name="nFCntDown">
<InputNumber min={0} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
label="Downlink frame-counter (application)"
name="aFCntDown"
>
<Form.Item label="Downlink frame-counter (application)" name="aFCntDown">
<InputNumber min={0} />
</Form.Item>
</Col>
</Row>
<Form.Item>
<Button type="primary" htmlType="submit">(Re)activate device</Button>
<Button type="primary" htmlType="submit">
(Re)activate device
</Button>
</Form.Item>
</Form>
);
}
}
interface IProps extends RouteComponentProps {
tenant: Tenant;
application: Application;
@ -198,13 +193,11 @@ interface IProps extends RouteComponentProps {
deviceProfile: DeviceProfile;
}
interface IState {
deviceActivation?: DeviceActivationPb;
deviceActivationRequested: boolean;
}
class DeviceActivation extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -231,9 +224,11 @@ class DeviceActivation extends Component<IProps, IState> {
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()}`);
this.props.history.push(
`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${this.props.device.getDevEui()}`,
);
});
}
};
render() {
if (!this.state.deviceActivationRequested) {
@ -248,10 +243,14 @@ class DeviceActivation extends Component<IProps, IState> {
initialValues = this.state.deviceActivation;
}
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
{!lw11 && <LW10DeviceActivationForm initialValues={initialValues} device={this.props.device} onFinish={this.onFinish} /> }
{lw11 && <LW11DeviceActivationForm initialValues={initialValues} device={this.props.device} onFinish={this.onFinish} /> }
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
{!lw11 && (
<LW10DeviceActivationForm initialValues={initialValues} device={this.props.device} onFinish={this.onFinish} />
)}
{lw11 && (
<LW11DeviceActivationForm initialValues={initialValues} device={this.props.device} onFinish={this.onFinish} />
)}
</Space>
);
}

View File

@ -12,14 +12,11 @@ import {
GetDeviceStatsRequest,
GetDeviceStatsResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import {
DeviceProfile,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import { DeviceProfile } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import DeviceStore from "../../stores/DeviceStore";
import Heatmap from "../../components/Heatmap";
interface IProps {
device: Device;
deviceProfile: DeviceProfile;
@ -32,7 +29,7 @@ interface IState {
statsUpFreq: HeatmapStats[];
statsUpDr?: any;
statsGwRssi?: any;
statsGwSnr?: any,
statsGwSnr?: any;
}
interface HeatmapStats {
@ -78,7 +75,7 @@ class DeviceDashboard extends Component<IProps, IState> {
lineTension: number;
pointBackgroundColor: string;
data: number[];
}[],
}[];
} = {
labels: [],
datasets: [
@ -129,28 +126,32 @@ class DeviceDashboard extends Component<IProps, IState> {
let statsGwRssiData: (number | null)[] = [];
let statsGwRssi = {
labels: statsGwRssiLabels,
datasets: [{
label: "rssi (reported by gateways)",
borderColor: "rgba(33, 150, 243, 1)",
backgroundColor: "rgba(0, 0, 0, 0)",
lineTension: 0,
pointBackgroundColor: "rgba(33, 150, 243, 1)",
data: statsGwRssiData,
}],
datasets: [
{
label: "rssi (reported by gateways)",
borderColor: "rgba(33, 150, 243, 1)",
backgroundColor: "rgba(0, 0, 0, 0)",
lineTension: 0,
pointBackgroundColor: "rgba(33, 150, 243, 1)",
data: statsGwRssiData,
},
],
};
let statsGwSnrLabels: string[] = [];
let statsGwSnrData: (number | null)[] = [];
let statsGwSnr = {
labels: statsGwSnrLabels,
datasets: [{
label: "rssi (reported by gateways)",
borderColor: "rgba(33, 150, 243, 1)",
backgroundColor: "rgba(0, 0, 0, 0)",
lineTension: 0,
pointBackgroundColor: "rgba(33, 150, 243, 1)",
data: statsGwSnrData,
}],
datasets: [
{
label: "rssi (reported by gateways)",
borderColor: "rgba(33, 150, 243, 1)",
backgroundColor: "rgba(0, 0, 0, 0)",
lineTension: 0,
pointBackgroundColor: "rgba(33, 150, 243, 1)",
data: statsGwSnrData,
},
],
};
let statsUpFreq: HeatmapStats[] = [];
@ -160,7 +161,10 @@ class DeviceDashboard extends Component<IProps, IState> {
statsUpFreq.push({
x: moment(row.getTime()!.toDate()).format("YYYY-MM-DD"),
y: row.getRxPacketsPerFrequencyMap().toObject().map(v => [v[0].toString(), v[1]]),
y: row
.getRxPacketsPerFrequencyMap()
.toObject()
.map(v => [v[0].toString(), v[1]]),
});
statsErrors.labels.push(moment(row.getTime()!.toDate()).format("YYYY-MM-DD"));
@ -187,7 +191,6 @@ class DeviceDashboard extends Component<IProps, IState> {
}
statsErrorsSet[v[0]].push(v[1]);
}
for (const v of row.getRxPacketsPerDrMap().toObject()) {
@ -204,7 +207,23 @@ class DeviceDashboard extends Component<IProps, IState> {
}
}
let backgroundColors = ['#8bc34a', '#ff5722', '#ff9800', '#ffc107', '#ffeb3b', '#cddc39', '#4caf50', '#009688', '#00bcd4', '#03a9f4', '#2196f3', '#3f51b5', '#673ab7', '#9c27b0', '#e91e63'];
let backgroundColors = [
"#8bc34a",
"#ff5722",
"#ff9800",
"#ffc107",
"#ffeb3b",
"#cddc39",
"#4caf50",
"#009688",
"#00bcd4",
"#03a9f4",
"#2196f3",
"#3f51b5",
"#673ab7",
"#9c27b0",
"#e91e63",
];
Object.entries(statsErrorsSet).forEach(([k, v]) => {
statsErrors.datasets.push({
label: k,
@ -213,7 +232,23 @@ class DeviceDashboard extends Component<IProps, IState> {
});
});
backgroundColors = ['#8bc34a', '#ff5722', '#ff9800', '#ffc107', '#ffeb3b', '#cddc39', '#4caf50', '#009688', '#00bcd4', '#03a9f4', '#2196f3', '#3f51b5', '#673ab7', '#9c27b0', '#e91e63'];
backgroundColors = [
"#8bc34a",
"#ff5722",
"#ff9800",
"#ffc107",
"#ffeb3b",
"#cddc39",
"#4caf50",
"#009688",
"#00bcd4",
"#03a9f4",
"#2196f3",
"#3f51b5",
"#673ab7",
"#9c27b0",
"#e91e63",
];
Object.entries(statsUpDrSet).forEach(([k, v]) => {
statsUpDr.datasets.push({
label: k,
@ -231,8 +266,7 @@ class DeviceDashboard extends Component<IProps, IState> {
statsGwSnr: statsGwSnr,
});
});
}
};
render() {
const animation: false = false;
@ -287,15 +321,21 @@ class DeviceDashboard extends Component<IProps, IState> {
lastSeenAt = moment(this.props.lastSeenAt).format("YYYY-MM-DD HH:mm:ss");
}
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<Card>
<Descriptions>
<Descriptions.Item label="Last seen">{lastSeenAt}</Descriptions.Item>
<Descriptions.Item label="Device profile"><Link to={`/tenants/${this.props.deviceProfile.getTenantId()}/device-profiles/${this.props.deviceProfile.getId()}/edit`}>{this.props.deviceProfile.getName()}</Link></Descriptions.Item>
<Descriptions.Item label="Enabled">{this.props.device.getIsDisabled() ? "no" : "yes"}</Descriptions.Item>
<Descriptions.Item label="Description">{this.props.device.getDescription()}</Descriptions.Item>
</Descriptions>
<Descriptions>
<Descriptions.Item label="Last seen">{lastSeenAt}</Descriptions.Item>
<Descriptions.Item label="Device profile">
<Link
to={`/tenants/${this.props.deviceProfile.getTenantId()}/device-profiles/${this.props.deviceProfile.getId()}/edit`}
>
{this.props.deviceProfile.getName()}
</Link>
</Descriptions.Item>
<Descriptions.Item label="Enabled">{this.props.device.getIsDisabled() ? "no" : "yes"}</Descriptions.Item>
<Descriptions.Item label="Description">{this.props.device.getDescription()}</Descriptions.Item>
</Descriptions>
</Card>
<Row gutter={24}>
<Col span={12}>

View File

@ -6,14 +6,13 @@ import { StreamDeviceEventsRequest, LogItem } from "@chirpstack/chirpstack-api-g
import InternalStore from "../../stores/InternalStore";
import LogTable from "../../components/LogTable";
interface IProps {
device: Device;
}
interface IState {
events: LogItem[];
cancelFunc?: () => void,
cancelFunc?: () => void;
}
class DeviceEvents extends Component<IProps, IState> {
@ -44,7 +43,7 @@ class DeviceEvents extends Component<IProps, IState> {
this.setState({
cancelFunc: cancelFunc,
});
}
};
onMessage = (l: LogItem) => {
let events = this.state.events;
@ -55,10 +54,10 @@ class DeviceEvents extends Component<IProps, IState> {
events: events,
});
}
}
};
render() {
return(<LogTable logs={this.state.events} />);
return <LogTable logs={this.state.events} />;
}
}

View File

@ -1,18 +1,22 @@
import React, { Component } from "react";
import { Form, Input, Row, Col, Button, Tabs } from "antd";
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Device } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import { ListDeviceProfilesRequest, ListDeviceProfilesResponse, GetDeviceProfileRequest, GetDeviceProfileResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
ListDeviceProfilesRequest,
ListDeviceProfilesResponse,
GetDeviceProfileRequest,
GetDeviceProfileResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import EuiInput from "../../components/EuiInput";
import{ OptionsCallbackFunc, OptionCallbackFunc } from "../../components/Autocomplete";
import { OptionsCallbackFunc, OptionCallbackFunc } from "../../components/Autocomplete";
import AutocompleteInput from "../../components/AutocompleteInput";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
interface IProps {
tenant: Tenant;
initialValues: Device;
@ -20,7 +24,6 @@ interface IProps {
update?: boolean;
}
class DeviceForm extends Component<IProps> {
formRef = React.createRef<any>();
@ -45,7 +48,7 @@ class DeviceForm extends Component<IProps> {
}
this.props.onFinish(d);
}
};
getDeviceProfileOptions = (search: string, fn: OptionsCallbackFunc) => {
let req = new ListDeviceProfilesRequest();
@ -54,10 +57,12 @@ class DeviceForm extends Component<IProps> {
req.setLimit(10);
DeviceProfileStore.list(req, (resp: ListDeviceProfilesResponse) => {
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);
});
}
};
getDeviceProfileOption = (id: string, fn: OptionCallbackFunc) => {
let req = new GetDeviceProfileRequest();
@ -66,27 +71,25 @@ class DeviceForm extends Component<IProps> {
DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => {
const dp = resp.getDeviceProfile();
if (dp) {
fn({label: dp.getName(), value: dp.getId()});
fn({ label: dp.getName(), value: dp.getId() });
}
});
}
};
render() {
return(
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish} ref={this.formRef}>
return (
<Form
layout="vertical"
initialValues={this.props.initialValues.toObject()}
onFinish={this.onFinish}
ref={this.formRef}
>
<Tabs>
<Tabs.TabPane tab="Device" key="1">
<Form.Item
label="Name"
name="name"
rules={[{required: true, message: "Please enter a name!"}]}
>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Input />
</Form.Item>
<Form.Item
label="Description"
name="description"
>
<Form.Item label="Description" name="description">
<Input.TextArea />
</Form.Item>
<EuiInput
@ -108,7 +111,7 @@ class DeviceForm extends Component<IProps> {
</Tabs.TabPane>
<Tabs.TabPane tab="Tags" key="2">
<Form.List name="tagsMap">
{(fields, { add, remove }) => (
{(fields, { add, remove }) => (
<>
{fields.map(( {key, name, ...restField} ) => (
<Row gutter={24}>
@ -117,7 +120,7 @@ class DeviceForm extends Component<IProps> {
{...restField}
name={[name, 0]}
fieldKey={[name, 0]}
rules={[{ required: true, message: 'Please enter a key!' }]}
rules={[{ required: true, message: "Please enter a key!" }]}
>
<Input placeholder="Key" />
</Form.Item>
@ -127,14 +130,14 @@ class DeviceForm extends Component<IProps> {
{...restField}
name={[name, 1]}
fieldKey={[name, 1]}
rules={[{ required: true, message: 'Please enter a value!' }]}
rules={[{ required: true, message: "Please enter a value!" }]}
>
<Input placeholder="Value" />
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
))}
<Form.Item>
@ -148,7 +151,7 @@ class DeviceForm extends Component<IProps> {
</Tabs.TabPane>
<Tabs.TabPane tab="Variables" key="3">
<Form.List name="variablesMap">
{(fields, { add, remove }) => (
{(fields, { add, remove }) => (
<>
{fields.map(( {key, name, ...restField} ) => (
<Row gutter={24}>
@ -157,7 +160,7 @@ class DeviceForm extends Component<IProps> {
{...restField}
name={[name, 0]}
fieldKey={[name, 0]}
rules={[{ required: true, message: 'Please enter a key!' }]}
rules={[{ required: true, message: "Please enter a key!" }]}
>
<Input placeholder="Key" />
</Form.Item>
@ -167,14 +170,14 @@ class DeviceForm extends Component<IProps> {
{...restField}
name={[name, 1]}
fieldKey={[name, 1]}
rules={[{ required: true, message: 'Please enter a value!' }]}
rules={[{ required: true, message: "Please enter a value!" }]}
>
<Input placeholder="Value" />
</Form.Item>
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
<Col span={2}>
<MinusCircleOutlined onClick={() => remove(name)} />
</Col>
</Row>
))}
<Form.Item>
@ -188,7 +191,9 @@ class DeviceForm extends Component<IProps> {
</Tabs.TabPane>
</Tabs>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);

View File

@ -6,14 +6,13 @@ import { StreamDeviceFramesRequest, LogItem } from "@chirpstack/chirpstack-api-g
import InternalStore from "../../stores/InternalStore";
import LogTable from "../../components/LogTable";
interface IProps {
device: Device;
}
interface IState {
frames: LogItem[];
cancelFunc?: () => void,
cancelFunc?: () => void;
}
class DeviceFrames extends Component<IProps, IState> {
@ -44,7 +43,7 @@ class DeviceFrames extends Component<IProps, IState> {
this.setState({
cancelFunc: cancelFunc,
});
}
};
onMessage = (l: LogItem) => {
let frames = this.state.frames;
@ -55,10 +54,10 @@ class DeviceFrames extends Component<IProps, IState> {
frames: frames,
});
}
}
};
render() {
return(<LogTable logs={this.state.frames} />);
return <LogTable logs={this.state.frames} />;
}
}

View File

@ -5,8 +5,17 @@ import { Space, Breadcrumb, Card, Button, PageHeader, Menu } from "antd";
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { DeviceProfile, GetDeviceProfileRequest, GetDeviceProfileResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import { Device, GetDeviceRequest, GetDeviceResponse, DeleteDeviceRequest } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import {
DeviceProfile,
GetDeviceProfileRequest,
GetDeviceProfileResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import {
Device,
GetDeviceRequest,
GetDeviceResponse,
DeleteDeviceRequest,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import DeviceStore from "../../stores/DeviceStore";
import DeviceProfileStore from "../../stores/DeviceProfileStore";
@ -20,12 +29,11 @@ import DeviceEvents from "./DeviceEvents";
import DeviceQueue from "./DeviceQueue";
import DeviceActivation from "./DeviceActivation";
interface MatchParams {
devEui: string;
}
interface IProps extends RouteComponentProps<MatchParams>{
interface IProps extends RouteComponentProps<MatchParams> {
tenant: Tenant;
application: Application;
}
@ -36,7 +44,6 @@ interface IState {
lastSeenAt?: Date;
}
class DeviceLayout extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -52,9 +59,12 @@ class DeviceLayout extends Component<IProps, IState> {
req.setDevEui(this.props.match.params.devEui);
DeviceStore.get(req, (resp: GetDeviceResponse) => {
this.setState({
device: resp.getDevice(),
}, cb);
this.setState(
{
device: resp.getDevice(),
},
cb,
);
if (resp.getLastSeenAt() !== undefined) {
this.setState({
@ -62,7 +72,7 @@ class DeviceLayout extends Component<IProps, IState> {
});
}
});
}
};
getDeviceProfile = () => {
let req = new GetDeviceProfileRequest();
@ -73,7 +83,7 @@ class DeviceLayout extends Component<IProps, IState> {
deviceProfile: resp.getDeviceProfile(),
});
});
}
};
deleteDevice = () => {
let req = new DeleteDeviceRequest();
@ -82,7 +92,7 @@ class DeviceLayout extends Component<IProps, IState> {
DeviceStore.delete(req, () => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`);
});
}
};
render() {
const device = this.state.device;
@ -116,61 +126,137 @@ class DeviceLayout extends Component<IProps, IState> {
tab = "frames";
}
return(
<Space direction="vertical" style={{width: "100%"}} size="large">
return (
<Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader
breadcrumbRender={() => <Breadcrumb>
breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item>
<span>Tenants</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}`}>{this.props.tenant.getName()}</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications`}>Applications</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>{this.props.application.getName()}</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>
{this.props.application.getName()}
</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span><Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>Devices</Link></span>
<span>
<Link to={`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}`}>
Devices
</Link>
</span>
</Breadcrumb.Item>
<Breadcrumb.Item>
<span>{device.getName()}</span>
</Breadcrumb.Item>
</Breadcrumb>}
</Breadcrumb>
)}
title={device.getName()}
subTitle={`device eui: ${device.getDevEui()}`}
extra={[
<DeleteConfirm
typ="device"
confirm={device.getName()}
onConfirm={this.deleteDevice}
>
<Button danger type="primary">Delete device</Button>
</DeleteConfirm>
<DeleteConfirm typ="device" confirm={device.getName()} onConfirm={this.deleteDevice}>
<Button danger type="primary">
Delete device
</Button>
</DeleteConfirm>,
]}
/>
<Card>
<Menu mode="horizontal" selectedKeys={[tab]} style={{marginBottom: 24}}>
<Menu.Item key="dashboard"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}`}>Dashboard</Link></Menu.Item>
<Menu.Item key="edit"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/edit`}>Configuration</Link></Menu.Item>
<Menu.Item key="keys" disabled={!dp.getSupportsOtaa()}><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/keys`}>OTAA keys</Link></Menu.Item>
<Menu.Item key="activation"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/activation`}>Activation</Link></Menu.Item>
<Menu.Item key="queue"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/queue`}>Queue</Link></Menu.Item>
<Menu.Item key="events"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/events`}>Events</Link></Menu.Item>
<Menu.Item key="frames"><Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/frames`}>LoRaWAN frames</Link></Menu.Item>
</Menu>
<Switch>
<Route exact path={this.props.match.path} render={props => <DeviceDashboard device={device} lastSeenAt={this.state.lastSeenAt} deviceProfile={dp} {...props} />} />
<Route exact path={`${this.props.match.path}/edit`} render={props => <EditDevice device={device} application={app} tenant={tenant} {...props} />} />
<Route exact path={`${this.props.match.path}/keys`} render={props => <SetDeviceKeys device={device} application={app} tenant={tenant} deviceProfile={dp} {...props} />} />
<Route exact path={`${this.props.match.path}/frames`} render={props => <DeviceFrames device={device} {...props} /> } />
<Route exact path={`${this.props.match.path}/events`} render={props => <DeviceEvents device={device} {...props} /> } />
<Route exact path={`${this.props.match.path}/queue`} render={props => <DeviceQueue device={device} {...props} /> } />
<Route exact path={`${this.props.match.path}/activation`} render={props => <DeviceActivation device={device} deviceProfile={dp} tenant={tenant} application={app} {...props} /> } />
</Switch>
</Card>
<Card>
<Menu mode="horizontal" selectedKeys={[tab]} style={{ marginBottom: 24 }}>
<Menu.Item key="dashboard">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}`}>
Dashboard
</Link>
</Menu.Item>
<Menu.Item key="edit">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/edit`}>
Configuration
</Link>
</Menu.Item>
<Menu.Item key="keys" disabled={!dp.getSupportsOtaa()}>
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/keys`}>
OTAA keys
</Link>
</Menu.Item>
<Menu.Item key="activation">
<Link
to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/activation`}
>
Activation
</Link>
</Menu.Item>
<Menu.Item key="queue">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/queue`}>
Queue
</Link>
</Menu.Item>
<Menu.Item key="events">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/events`}>
Events
</Link>
</Menu.Item>
<Menu.Item key="frames">
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/devices/${device.getDevEui()}/frames`}>
LoRaWAN frames
</Link>
</Menu.Item>
</Menu>
<Switch>
<Route
exact
path={this.props.match.path}
render={props => (
<DeviceDashboard device={device} lastSeenAt={this.state.lastSeenAt} deviceProfile={dp} {...props} />
)}
/>
<Route
exact
path={`${this.props.match.path}/edit`}
render={props => <EditDevice device={device} application={app} tenant={tenant} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/keys`}
render={props => (
<SetDeviceKeys device={device} application={app} tenant={tenant} deviceProfile={dp} {...props} />
)}
/>
<Route
exact
path={`${this.props.match.path}/frames`}
render={props => <DeviceFrames device={device} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/events`}
render={props => <DeviceEvents device={device} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/queue`}
render={props => <DeviceQueue device={device} {...props} />}
/>
<Route
exact
path={`${this.props.match.path}/activation`}
render={props => (
<DeviceActivation device={device} deviceProfile={dp} tenant={tenant} application={app} {...props} />
)}
/>
</Switch>
</Card>
</Space>
);
}

View File

@ -13,13 +13,13 @@ import {
GetDeviceQueueItemsRequest,
GetDeviceQueueItemsResponse,
FlushDeviceQueueRequest,
DeviceQueueItem } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
DeviceQueueItem,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import DeviceStore from "../../stores/DeviceStore";
import CodeEditor from "../../components/CodeEditor";
interface IProps {
device: Device;
}
@ -28,7 +28,6 @@ interface IState {
refreshCounter: number;
}
class DeviceQueue extends Component<IProps, IState> {
formRef = React.createRef<any>();
@ -97,11 +96,11 @@ class DeviceQueue extends Component<IProps, IState> {
dataIndex: "data",
key: "data",
render: (text, record) => {
return Buffer.from(record.data as string, 'base64').toString('hex');
return Buffer.from(record.data as string, "base64").toString("hex");
},
},
]
}
];
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new GetDeviceQueueItemsRequest();
@ -111,13 +110,13 @@ class DeviceQueue extends Component<IProps, IState> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
refreshQueue = () => {
this.setState({
refreshCounter: this.state.refreshCounter + 1,
});
}
};
flushQueue = () => {
let req = new FlushDeviceQueueRequest();
@ -125,7 +124,7 @@ class DeviceQueue extends Component<IProps, IState> {
DeviceStore.flushQueue(req, () => {
this.refreshQueue();
});
}
};
onEnqueue = (values: any) => {
let req = new EnqueueDeviceQueueItemRequest();
@ -136,11 +135,11 @@ class DeviceQueue extends Component<IProps, IState> {
item.setConfirmed(values.confirmed);
if (values.hex !== undefined) {
item.setData(Buffer.from(values.hex, 'hex'));
item.setData(Buffer.from(values.hex, "hex"));
}
if (values.base64 !== undefined) {
item.setData(Buffer.from(values.base64, 'base64'));
item.setData(Buffer.from(values.base64, "base64"));
}
if (values.json !== undefined) {
@ -149,7 +148,7 @@ class DeviceQueue extends Component<IProps, IState> {
let struct = Struct.fromJavaScript(obj);
item.setObject(struct);
} catch(err) {
} catch (err) {
if (err instanceof Error) {
notification.error({
message: "Error",
@ -162,19 +161,19 @@ class DeviceQueue extends Component<IProps, IState> {
req.setItem(item);
DeviceStore.enqueue(req, (_) => {
DeviceStore.enqueue(req, _ => {
this.formRef.current.resetFields();
this.refreshQueue();
});
}
};
render() {
return (
<Space direction="vertical" style={{width: "100%"}} size="large">
<Space direction="vertical" style={{ width: "100%" }} size="large">
<Card title="Enqueue">
<Form layout="horizontal" onFinish={this.onEnqueue} ref={this.formRef} initialValues={{fPort: 1}}>
<Form layout="horizontal" onFinish={this.onEnqueue} ref={this.formRef} initialValues={{ fPort: 1 }}>
<Row>
<Space direction="horizontal" style={{width: "100%"}} size="large">
<Space direction="horizontal" style={{ width: "100%" }} size="large">
<Form.Item name="confirmed" label="Confirmed" valuePropName="checked">
<Checkbox />
</Form.Item>
@ -195,24 +194,20 @@ class DeviceQueue extends Component<IProps, IState> {
</Form.Item>
</Tabs.TabPane>
<Tabs.TabPane tab="JSON" key="3">
<CodeEditor
name="json"
value="{}"
formRef={this.formRef}
/>
<CodeEditor name="json" value="{}" formRef={this.formRef} />
</Tabs.TabPane>
</Tabs>
<Button type="primary" htmlType="submit">Enqueue</Button>
<Button type="primary" htmlType="submit">
Enqueue
</Button>
</Form>
</Card>
<Row justify="end">
<Space direction="horizontal" size="large">
<Button icon={<RedoOutlined />} onClick={this.refreshQueue}>Reload</Button>
<Popconfirm
title="Are you sure you want to flush the queue?"
placement="left"
onConfirm={this.flushQueue}
>
<Button icon={<RedoOutlined />} onClick={this.refreshQueue}>
Reload
</Button>
<Popconfirm title="Are you sure you want to flush the queue?" placement="left" onConfirm={this.flushQueue}>
<Button icon={<DeleteOutlined />}>Flush queue</Button>
</Popconfirm>
</Space>

View File

@ -14,21 +14,20 @@ interface IProps extends RouteComponentProps {
device: Device;
}
class EditDevice extends Component<IProps> {
onFinish = (obj: Device) => {
let req = new UpdateDeviceRequest();
req.setDevice(obj);
DeviceStore.update(req, () => {
this.props.history.push(`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${obj.getDevEui()}`);
this.props.history.push(
`/tenants/${this.props.tenant.getId()}/applications/${this.props.application.getId()}/devices/${obj.getDevEui()}`,
);
});
}
};
render() {
return(
<DeviceForm initialValues={this.props.device} onFinish={this.onFinish} tenant={this.props.tenant} update />
);
return <DeviceForm initialValues={this.props.device} onFinish={this.onFinish} tenant={this.props.tenant} update />;
}
}

View File

@ -5,10 +5,20 @@ import moment from "moment";
import { Space, Button, Dropdown, Menu, Modal, Select } from "antd";
import { ColumnsType } from "antd/es/table";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlug, faBatteryFull, faBatteryQuarter, faBatteryHalf, faBatteryThreeQuarters } from '@fortawesome/free-solid-svg-icons'
import {
faPlug,
faBatteryFull,
faBatteryQuarter,
faBatteryHalf,
faBatteryThreeQuarters,
} from "@fortawesome/free-solid-svg-icons";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
import { ListDevicesRequest, ListDevicesResponse, DeviceListItem } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import {
ListDevicesRequest,
ListDevicesResponse,
DeviceListItem,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import {
ListMulticastGroupsRequest,
ListMulticastGroupsResponse,
@ -21,20 +31,17 @@ import DeviceStore from "../../stores/DeviceStore";
import MulticastGroupStore from "../../stores/MulticastGroupStore";
import Admin from "../../components/Admin";
interface IProps {
application: Application;
}
interface IState {
selectedRowIds: string[];
multicastGroups: MulticastGroupListItem[];
mgModalVisible: boolean;
mgSelected: string,
mgSelected: string;
}
class ListDevices extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -71,7 +78,7 @@ class ListDevices extends Component<IProps, IState> {
ts.setUTCSeconds(record.lastSeenAt.seconds);
return moment(ts).format("YYYY-MM-DD HH:mm:ss");
}
return "Never";
return "Never";
},
},
{
@ -80,7 +87,13 @@ class ListDevices extends Component<IProps, IState> {
key: "devEui",
width: 250,
render: (text, record) => (
<Link to={`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/devices/${record.devEui}`}>{text}</Link>
<Link
to={`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/devices/${
record.devEui
}`}
>
{text}
</Link>
),
},
{
@ -93,7 +106,9 @@ class ListDevices extends Component<IProps, IState> {
dataIndex: "deviceProfileName",
key: "deviceProfileName",
render: (text, record) => (
<Link to={`/tenants/${this.props.application.getTenantId()}/device-profiles/${record.deviceProfileId}/edit`}>{text}</Link>
<Link to={`/tenants/${this.props.application.getTenantId()}/device-profiles/${record.deviceProfileId}/edit`}>
{text}
</Link>
),
},
{
@ -119,7 +134,7 @@ class ListDevices extends Component<IProps, IState> {
},
},
];
}
};
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDevicesRequest();
@ -131,31 +146,31 @@ class ListDevices extends Component<IProps, IState> {
const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList);
});
}
};
onRowsSelectChange = (ids: string[]) => {
this.setState({
selectedRowIds: ids,
});
}
};
showMgModal = () => {
this.setState({
mgModalVisible: true,
});
}
};
hideMgModal = () => {
this.setState({
mgModalVisible: false,
});
}
};
onMgSelected = (value: string) => {
this.setState({
mgSelected: value,
});
}
};
handleMgModalOk = () => {
for (let devEui of this.state.selectedRowIds) {
@ -169,7 +184,7 @@ class ListDevices extends Component<IProps, IState> {
this.setState({
mgModalVisible: false,
});
}
};
render() {
const menu = (
@ -178,21 +193,42 @@ class ListDevices extends Component<IProps, IState> {
</Menu>
);
const mgOptions = this.state.multicastGroups.map((mg, i) => <Select.Option value={mg.getId()}>{mg.getName()}</Select.Option>);
const mgOptions = this.state.multicastGroups.map((mg, i) => (
<Select.Option value={mg.getId()}>{mg.getName()}</Select.Option>
));
return(
<Space direction="vertical" size="large" style={{width: "100%"}}>
<Modal title="Add selected devices to multicast-group" visible={this.state.mgModalVisible} onOk={this.handleMgModalOk} onCancel={this.hideMgModal} okButtonProps={{disabled: this.state.mgSelected === ""}}>
<Space direction="vertical" size="large" style={{width: "100%"}}>
<Select style={{width: "100%"}} onChange={this.onMgSelected} placeholder="Select Multicast-group">
return (
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Modal
title="Add selected devices to multicast-group"
visible={this.state.mgModalVisible}
onOk={this.handleMgModalOk}
onCancel={this.hideMgModal}
okButtonProps={{ disabled: this.state.mgSelected === "" }}
>
<Space direction="vertical" size="large" style={{ width: "100%" }}>
<Select style={{ width: "100%" }} onChange={this.onMgSelected} placeholder="Select Multicast-group">
{mgOptions}
</Select>
</Space>
</Modal>
<Admin tenantId={this.props.application.getTenantId()} isDeviceAdmin>
<Space direction="horizontal" style={{float: "right"}}>
<Button type="primary"><Link to={`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/devices/create`}>Add device</Link></Button>
<Dropdown placement="bottomRight" overlay={menu} trigger={['click']} disabled={this.state.selectedRowIds.length === 0}><Button>Selected devices</Button></Dropdown>
<Space direction="horizontal" style={{ float: "right" }}>
<Button type="primary">
<Link
to={`/tenants/${this.props.application.getTenantId()}/applications/${this.props.application.getId()}/devices/create`}
>
Add device
</Link>
</Button>
<Dropdown
placement="bottomRight"
overlay={menu}
trigger={["click"]}
disabled={this.state.selectedRowIds.length === 0}
>
<Button>Selected devices</Button>
</Dropdown>
</Space>
</Admin>
<DataTable

Some files were not shown because too many files have changed in this diff Show More