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

View File

@ -2,7 +2,6 @@ import { Component } from "react";
import SessionStore from "../stores/SessionStore"; import SessionStore from "../stores/SessionStore";
interface IProps { interface IProps {
tenantId?: string; tenantId?: string;
isDeviceAdmin?: boolean; isDeviceAdmin?: boolean;
@ -68,14 +67,14 @@ class Admin extends Component<IProps, IState> {
}); });
} }
} }
} };
render() { render() {
if (this.state.admin) { 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"; import { ReloadOutlined } from "@ant-design/icons";
interface IProps { interface IProps {
formRef: React.RefObject<any>, formRef: React.RefObject<any>;
label: string, label: string;
name: string, name: string;
required?: boolean; required?: boolean;
value?: string; value?: string;
disabled?: boolean; disabled?: boolean;
@ -18,7 +18,6 @@ interface IState {
value: string; value: string;
} }
class AesKeyInput extends Component<IProps, IState> { class AesKeyInput extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -39,7 +38,7 @@ class AesKeyInput extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({ this.props.formRef.current.setFieldsValue({
[this.props.name]: value, [this.props.name]: value,
}); });
} };
componentDidMount() { componentDidMount() {
if (this.props.value) { if (this.props.value) {
@ -62,10 +61,13 @@ class AesKeyInput extends Component<IProps, IState> {
} }
} }
this.setState({ this.setState(
value: value, {
}, this.updateField); value: value,
} },
this.updateField,
);
};
onByteOrderSelect = (v: string) => { onByteOrderSelect = (v: string) => {
if (v === this.state.byteOrder) { if (v === this.state.byteOrder) {
@ -79,21 +81,27 @@ class AesKeyInput extends Component<IProps, IState> {
const current = this.state.value; const current = this.state.value;
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || []; const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
this.setState({ this.setState(
value: bytes.reverse().join(""), {
}, this.updateField); value: bytes.reverse().join(""),
} },
this.updateField,
);
};
generateRandom = () => { generateRandom = () => {
let cryptoObj = window.crypto || window.Crypto; let cryptoObj = window.crypto || window.Crypto;
let b = new Uint8Array(16); let b = new Uint8Array(16);
cryptoObj.getRandomValues(b); cryptoObj.getRandomValues(b);
let key = Buffer.from(b).toString('hex'); let key = Buffer.from(b).toString("hex");
this.setState({ this.setState(
value: key, {
}, this.updateField); value: key,
} },
this.updateField,
);
};
render() { render() {
const addon = ( const addon = (
@ -102,23 +110,34 @@ class AesKeyInput extends Component<IProps, IState> {
<Select.Option value="msb">MSB</Select.Option> <Select.Option value="msb">MSB</Select.Option>
<Select.Option value="lsb">LSB</Select.Option> <Select.Option value="lsb">LSB</Select.Option>
</Select> </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> </Space>
); );
return( return (
<Form.Item <Form.Item
rules={[{ rules={[
required: this.props.required, {
message: `Please enter a valid ${this.props.label}`, required: this.props.required,
pattern: new RegExp(/[A-Fa-f0-9]{32}/g), message: `Please enter a valid ${this.props.label}`,
}]} pattern: new RegExp(/[A-Fa-f0-9]{32}/g),
},
]}
label={this.props.label} label={this.props.label}
name={this.props.name} name={this.props.name}
tooltip={this.props.tooltip} tooltip={this.props.tooltip}
> >
<Input hidden /> <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> </Form.Item>
); );
} }

View File

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

View File

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

View File

@ -1,15 +1,14 @@
import React, { Component } from "react"; import React, { Component } from "react";
import {Controlled as CodeMirror} from "react-codemirror2"; import { Controlled as CodeMirror } from "react-codemirror2";
import { Form } from "antd"; import { Form } from "antd";
import "codemirror/mode/javascript/javascript"; import "codemirror/mode/javascript/javascript";
interface IProps { interface IProps {
formRef: React.RefObject<any>, formRef: React.RefObject<any>;
label?: string, label?: string;
name: string, name: string;
required?: boolean; required?: boolean;
value?: string; value?: string;
disabled?: boolean; disabled?: boolean;
@ -20,7 +19,6 @@ interface IState {
value: string; value: string;
} }
class CodeEditor extends Component<IProps, IState> { class CodeEditor extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -43,13 +41,16 @@ class CodeEditor extends Component<IProps, IState> {
this.props.formRef.current.setFieldsValue({ this.props.formRef.current.setFieldsValue({
[this.props.name]: value, [this.props.name]: value,
}); });
} };
handleChange = (editor: any, data: any, newCode: string) => { handleChange = (editor: any, data: any, newCode: string) => {
this.setState({ this.setState(
value: newCode, {
}, this.updateField); value: newCode,
} },
this.updateField,
);
};
render() { render() {
const codeMirrorOptions = { const codeMirrorOptions = {
@ -60,21 +61,13 @@ class CodeEditor extends Component<IProps, IState> {
}; };
return ( return (
<Form.Item <Form.Item label={this.props.label} name={this.props.name} tooltip={this.props.tooltip}>
label={this.props.label} <div style={{ border: "1px solid #cccccc" }}>
name={this.props.name} <CodeMirror value={this.state.value} options={codeMirrorOptions} onBeforeChange={this.handleChange} />
tooltip={this.props.tooltip}
>
<div style={{border: "1px solid #cccccc"}}>
<CodeMirror
value={this.state.value}
options={codeMirrorOptions}
onBeforeChange={this.handleChange}
/>
</div> </div>
</Form.Item> </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"; import SessionStore from "../stores/SessionStore";
export type GetPageCallbackFunc = (totalCount: number, rows: object[]) => void; export type GetPageCallbackFunc = (totalCount: number, rows: object[]) => void;
interface IProps { interface IProps {
columns: ColumnsType<any>; columns: ColumnsType<any>;
getPage: (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => void; getPage: (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => void;
@ -23,10 +21,9 @@ interface IState {
pageSize: number; pageSize: number;
currentPage: number; currentPage: number;
rows: object[]; rows: object[];
loading: boolean, loading: boolean;
} }
class DataTable extends Component<IProps, IState> { class DataTable extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -53,37 +50,40 @@ class DataTable extends Component<IProps, IState> {
} }
onChangePage = (page: number, pageSize?: number | void) => { onChangePage = (page: number, pageSize?: number | void) => {
this.setState({ this.setState(
loading: true, {
}, () => { loading: true,
let pz = pageSize; },
if (!pz) { () => {
pz = this.state.pageSize; let pz = pageSize;
} if (!pz) {
pz = this.state.pageSize;
}
this.props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => { this.props.getPage(pz, (page - 1) * pz, (totalCount: number, rows: object[]) => {
this.setState({ this.setState({
currentPage: page, currentPage: page,
totalCount: totalCount, totalCount: totalCount,
rows: rows, rows: rows,
pageSize: pz || 0, pageSize: pz || 0,
loading: false, loading: false,
});
}); });
}); },
}); );
} };
onShowSizeChange = (page: number, pageSize: number) => { onShowSizeChange = (page: number, pageSize: number) => {
this.onChangePage(page, pageSize); this.onChangePage(page, pageSize);
SessionStore.setRowsPerPage(pageSize); SessionStore.setRowsPerPage(pageSize);
} };
onRowsSelectChange = (ids: React.Key[]) => { onRowsSelectChange = (ids: React.Key[]) => {
const idss = ids as string[]; const idss = ids as string[];
if (this.props.onRowsSelectChange) { if (this.props.onRowsSelectChange) {
this.props.onRowsSelectChange(idss); this.props.onRowsSelectChange(idss);
} }
} };
render() { render() {
const { getPage, refreshKey, ...otherProps } = this.props; const { getPage, refreshKey, ...otherProps } = this.props;
@ -97,12 +97,12 @@ class DataTable extends Component<IProps, IState> {
let pagination = undefined; let pagination = undefined;
if (this.props.noPagination === undefined || this.props.noPagination === false) { if (this.props.noPagination === undefined || this.props.noPagination === false) {
pagination = { pagination = {
current: this.state.currentPage, current: this.state.currentPage,
total: this.state.totalCount, total: this.state.totalCount,
pageSize: this.state.pageSize, pageSize: this.state.pageSize,
onChange: this.onChangePage, onChange: this.onChangePage,
showSizeChanger: true, showSizeChanger: true,
onShowSizeChange: this.onShowSizeChange, onShowSizeChange: this.onShowSizeChange,
}; };
} }
@ -113,8 +113,7 @@ class DataTable extends Component<IProps, IState> {
}; };
} }
return (
return(
<Table <Table
loading={loadingProps} loading={loadingProps}
dataSource={this.state.rows} dataSource={this.state.rows}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,11 +3,10 @@ import moment from "moment";
import JSONTreeOriginal from "react-json-tree"; import JSONTreeOriginal from "react-json-tree";
import { Tag, Drawer, Button, Table, Spin } from "antd"; 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"; import { LogItem } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
interface IProps { interface IProps {
logs: LogItem[]; logs: LogItem[];
} }
@ -15,8 +14,7 @@ interface IProps {
interface IState { interface IState {
drawerOpen: boolean; drawerOpen: boolean;
body: any; body: any;
}; }
class LogTable extends Component<IProps, IState> { class LogTable extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
@ -32,7 +30,7 @@ class LogTable extends Component<IProps, IState> {
this.setState({ this.setState({
drawerOpen: false, drawerOpen: false,
}); });
} };
onDrawerOpen = (body: any) => { onDrawerOpen = (body: any) => {
return () => { return () => {
@ -41,44 +39,56 @@ class LogTable extends Component<IProps, IState> {
drawerOpen: true, drawerOpen: true,
}); });
}; };
} };
render() { render() {
let items = this.props.logs.map((l, i) => l.toObject()); let items = this.props.logs.map((l, i) => l.toObject());
let body = JSON.parse(this.state.body); let body = JSON.parse(this.state.body);
const theme = { const theme = {
scheme: 'google', scheme: "google",
author: 'seth wright (http://sethawright.com)', author: "seth wright (http://sethawright.com)",
base00: '#000000', base00: "#000000",
base01: '#282a2e', base01: "#282a2e",
base02: '#373b41', base02: "#373b41",
base03: '#969896', base03: "#969896",
base04: '#b4b7b4', base04: "#b4b7b4",
base05: '#c5c8c6', base05: "#c5c8c6",
base06: '#e0e0e0', base06: "#e0e0e0",
base07: '#ffffff', base07: "#ffffff",
base08: '#CC342B', base08: "#CC342B",
base09: '#F96A38', base09: "#F96A38",
base0A: '#FBA922', base0A: "#FBA922",
base0B: '#198844', base0B: "#198844",
base0C: '#3971ED', base0C: "#3971ED",
base0D: '#3971ED', base0D: "#3971ED",
base0E: '#A36AC7', base0E: "#A36AC7",
base0F: '#3971ED', base0F: "#3971ED",
}; };
return( return (
<div> <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 <JSONTreeOriginal
data={body} data={body}
theme={theme} theme={theme}
hideRoot={true} hideRoot={true}
shouldExpandNode={() => {return true}} shouldExpandNode={() => {
return true;
}}
/> />
</Drawer> </Drawer>
{items.length !== 0 && <div className="spinner"><Spin /></div>} {items.length !== 0 && (
<div className="spinner">
<Spin />
</div>
)}
<Table <Table
showHeader={false} showHeader={false}
loading={items.length === 0} loading={items.length === 0}
@ -101,19 +111,36 @@ class LogTable extends Component<IProps, IState> {
dataIndex: "description", dataIndex: "description",
key: "description", key: "description",
width: 200, 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", title: "Properties",
dataIndex: "properties", dataIndex: "properties",
key: "properties", key: "properties",
render: (text, obj) => obj.propertiesMap.map((p, i) => { render: (text, obj) =>
if (p[1] !== "") { obj.propertiesMap.map((p, i) => {
return <Tag><pre>{p[0]}: {p[1]}</pre></Tag> 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 L, { LatLngTuple, FitBoundsOptions } from "leaflet";
import "leaflet.awesome-markers"; import "leaflet.awesome-markers";
import { MarkerProps as LMarkerProps } from "react-leaflet"; 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 { interface IProps {
height: number; height: number;
@ -17,7 +16,6 @@ interface IState {
map?: L.Map; map?: L.Map;
} }
class Map extends Component<IProps, IState> { class Map extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -25,22 +23,25 @@ class Map extends Component<IProps, IState> {
} }
setMap = (map: L.Map) => { setMap = (map: L.Map) => {
this.setState({ this.setState(
map: map, {
}, () => { 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. // This is needed as setMap is called after the map has been created.
// In such case, the map would never update to the new center. // There is a small amount of time where componentDidUpdate can't update
if (this.props.center !== undefined) { // the map with the new center because setMap hasn't been called yet.
map.panTo(this.props.center); // 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) { if (this.props.bounds !== undefined) {
map.fitBounds(this.props.bounds, this.props.boundsOptions); map.fitBounds(this.props.bounds, this.props.boundsOptions);
} }
}); },
} );
};
componentDidUpdate(oldProps: IProps) { componentDidUpdate(oldProps: IProps) {
if (this.props === oldProps) { if (this.props === oldProps) {
@ -63,7 +64,7 @@ class Map extends Component<IProps, IState> {
height: this.props.height, height: this.props.height,
}; };
return( return (
<MapContainer <MapContainer
bounds={this.props.bounds} bounds={this.props.bounds}
boundsOptions={this.props.boundsOptions} 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 { interface MarkerProps extends LMarkerProps {
position: [number, number]; position: [number, number];
@ -102,9 +113,10 @@ export class Marker extends Component<MarkerProps> {
markerColor: color, markerColor: color,
}); });
return (
return( <LMarker icon={iconMarker} position={position} {...otherProps}>
<LMarker icon={iconMarker} position={position} {...otherProps}>{this.props.children}</LMarker> {this.props.children}
</LMarker>
); );
} }
} }

View File

@ -2,9 +2,22 @@ import React, { Component } from "react";
import { withRouter, RouteComponentProps, Link } from "react-router-dom"; import { withRouter, RouteComponentProps, Link } from "react-router-dom";
import { Menu, MenuProps } from "antd"; 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 Autocomplete, { OptionCallbackFunc, OptionsCallbackFunc } from "../components/Autocomplete";
import TenantStore from "../stores/TenantStore"; import TenantStore from "../stores/TenantStore";
@ -48,7 +61,7 @@ class SideMenu extends Component<RouteComponentProps, IState> {
this.setState({ this.setState({
tenantId: SessionStore.getTenantId(), tenantId: SessionStore.getTenantId(),
}); });
} };
getTenantOptions = (search: string, fn: OptionsCallbackFunc) => { getTenantOptions = (search: string, fn: OptionsCallbackFunc) => {
let req = new ListTenantsRequest(); let req = new ListTenantsRequest();
@ -56,85 +69,86 @@ class SideMenu extends Component<RouteComponentProps, IState> {
req.setLimit(10); req.setLimit(10);
TenantStore.list(req, (resp: ListTenantsResponse) => { 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); fn(options);
}); });
} };
getTenantOption = (id: string, fn: OptionCallbackFunc) => { getTenantOption = (id: string, fn: OptionCallbackFunc) => {
TenantStore.get(id, (resp: GetTenantResponse) => { TenantStore.get(id, (resp: GetTenantResponse) => {
const tenant = resp.getTenant(); const tenant = resp.getTenant();
if (tenant) { if (tenant) {
fn({label: tenant.getName(), value: tenant.getId()}); fn({ label: tenant.getName(), value: tenant.getId() });
} }
}); });
} };
onTenantSelect = (value: string) => { onTenantSelect = (value: string) => {
SessionStore.setTenantId(value); SessionStore.setTenantId(value);
this.props.history.push(`/tenants/${value}`); this.props.history.push(`/tenants/${value}`);
} };
parseLocation = () => { parseLocation = () => {
const path = this.props.history.location.pathname; const path = this.props.history.location.pathname;
const tenantRe = /\/tenants\/([\w-]{36})/g; const tenantRe = /\/tenants\/([\w-]{36})/g;
const match = tenantRe.exec(path); 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]); SessionStore.setTenantId(match[1]);
} }
// ns dashboard // ns dashboard
if (path === "/dashboard") { if (path === "/dashboard") {
this.setState({selectedKey: "ns-dashboard"}); this.setState({ selectedKey: "ns-dashboard" });
} }
// ns tenants // ns tenants
if (/\/tenants(\/([\w-]{36}\/edit|create))?/g.exec(path)) { if (/\/tenants(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-tenants"}); this.setState({ selectedKey: "ns-tenants" });
} }
// ns tenants // ns tenants
if (/\/users(\/([\w-]{36}\/edit|create))?/g.exec(path)) { if (/\/users(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-users"}); this.setState({ selectedKey: "ns-users" });
} }
// ns api keys // ns api keys
if (/\/api-keys(\/([\w-]{36}\/edit|create))?/g.exec(path)) { if (/\/api-keys(\/([\w-]{36}\/edit|create))?/g.exec(path)) {
this.setState({selectedKey: "ns-api-keys"}); this.setState({ selectedKey: "ns-api-keys" });
} }
// tenant dashboard // tenant dashboard
if (/\/tenants\/[\w-]{36}/g.exec(path)) { if (/\/tenants\/[\w-]{36}/g.exec(path)) {
this.setState({selectedKey: "tenant-dashboard"}); this.setState({ selectedKey: "tenant-dashboard" });
} }
// tenant users // tenant users
if (/\/tenants\/[\w-]{36}\/users.*/g.exec(path)) { if (/\/tenants\/[\w-]{36}\/users.*/g.exec(path)) {
this.setState({selectedKey: "tenant-users"}); this.setState({ selectedKey: "tenant-users" });
} }
// tenant api-keys // tenant api-keys
if (/\/tenants\/[\w-]{36}\/api-keys.*/g.exec(path)) { if (/\/tenants\/[\w-]{36}\/api-keys.*/g.exec(path)) {
this.setState({selectedKey: "tenant-api-keys"}); this.setState({ selectedKey: "tenant-api-keys" });
} }
// tenant device-profiles // tenant device-profiles
if (/\/tenants\/[\w-]{36}\/device-profiles.*/g.exec(path)) { if (/\/tenants\/[\w-]{36}\/device-profiles.*/g.exec(path)) {
this.setState({selectedKey: "tenant-device-profiles"}); this.setState({ selectedKey: "tenant-device-profiles" });
} }
// tenant gateways // tenant gateways
if (/\/tenants\/[\w-]{36}\/gateways.*/g.exec(path)) { if (/\/tenants\/[\w-]{36}\/gateways.*/g.exec(path)) {
this.setState({selectedKey: "tenant-gateways"}); this.setState({ selectedKey: "tenant-gateways" });
} }
// tenant applications // tenant applications
if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) { if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) {
this.setState({selectedKey: "tenant-applications"}); this.setState({ selectedKey: "tenant-applications" });
} }
} };
render() { render() {
const tenantId = this.state.tenantId; const tenantId = this.state.tenantId;
@ -170,7 +184,7 @@ class SideMenu extends Component<RouteComponentProps, IState> {
}); });
} }
return( return (
<div> <div>
<Autocomplete <Autocomplete
placeholder="Select tenant" placeholder="Select tenant"

View File

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

View File

@ -1,88 +1,88 @@
.layout { .layout {
margin-left: 300px; margin-left: 300px;
} }
.layout-header { .layout-header {
background: #ffffff; background: #ffffff;
box-shadow: 0px 0px 10px 0px #ccc; box-shadow: 0px 0px 10px 0px #ccc;
z-index: 999; z-index: 999;
position: fixed; position: fixed;
width: 100%; width: 100%;
} }
.layout-header .logo { .layout-header .logo {
float: left; float: left;
margin-top: 16px; margin-top: 16px;
height: 32px; height: 32px;
} }
.layout-header .actions { .layout-header .actions {
float: right; float: right;
} }
.layout-header .actions > div { .layout-header .actions > div {
display: inline; display: inline;
padding-left: 24px; padding-left: 24px;
} }
.layout-menu { .layout-menu {
padding-top: 85px; padding-top: 85px;
overflow: auto; overflow: auto;
position: fixed; position: fixed;
height: 100vh; height: 100vh;
left: 0; left: 0;
} }
.layout-menu .organiation-select { .layout-menu .organiation-select {
margin-left: 24px; margin-left: 24px;
margin-bottom: 12px; margin-bottom: 12px;
width: 252px; width: 252px;
} }
.layout-content { .layout-content {
margin-top: 65px; margin-top: 65px;
} }
.layout-content .content-header > * { .layout-content .content-header > * {
float: right; float: right;
} }
.layout-content .content-header h3 { .layout-content .content-header h3 {
float: left; float: left;
display: inline; display: inline;
} }
.layout-content .content { .layout-content .content {
background: #ffffff; background: #ffffff;
} }
.spinner { .spinner {
text-align: center; text-align: center;
} }
pre { pre {
margin: 0px; margin: 0px;
} }
.chart-doughtnut { .chart-doughtnut {
max-width: 350; max-width: 350;
padding: 0; padding: 0;
margin: auto; margin: auto;
display: block; display: block;
} }
.dashboard-chart > .ant-card-body { .dashboard-chart > .ant-card-body {
height: 300px; height: 300px;
} }
.integration-card { .integration-card {
margin-bottom: 24px; margin-bottom: 24px;
} }
.integration-card .ant-card-meta-description { .integration-card .ant-card-meta-description {
min-height: 75px; min-height: 75px;
} }
.search-dropdown .ant-select-dropdown-menu-item-group-title { .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, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, Title } from "chart.js";
import { Chart, registerables } 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 "chartjs-adapter-moment";
import App from "./App"; import App from "./App";
@ -23,7 +23,7 @@ ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root"),
); );
// If you want to start measuring performance in your app, pass a function // 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) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { 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); getCLS(onPerfEntry);
getFID(onPerfEntry); getFID(onPerfEntry);
getFCP(onPerfEntry); getFCP(onPerfEntry);

View File

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

View File

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

View File

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

View File

@ -18,7 +18,6 @@ import {
import SessionStore from "./SessionStore"; import SessionStore from "./SessionStore";
import { HandleError } from "./helpers"; import { HandleError } from "./helpers";
class GatewayStore extends EventEmitter { class GatewayStore extends EventEmitter {
client: GatewayServiceClient; client: GatewayServiceClient;
@ -28,7 +27,7 @@ class GatewayStore extends EventEmitter {
} }
create = (req: CreateGatewayRequest, callbackFunc: () => void) => { create = (req: CreateGatewayRequest, callbackFunc: () => void) => {
this.client.create(req, SessionStore.getMetadata(), (err) => { this.client.create(req, SessionStore.getMetadata(), err => {
if (err !== null) { if (err !== null) {
HandleError(err); HandleError(err);
return; return;
@ -41,7 +40,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(); callbackFunc();
}); });
} };
get = (req: GetGatewayRequest, callbackFunc: (resp: GetGatewayResponse) => void) => { get = (req: GetGatewayRequest, callbackFunc: (resp: GetGatewayResponse) => void) => {
this.client.get(req, SessionStore.getMetadata(), (err, resp) => { this.client.get(req, SessionStore.getMetadata(), (err, resp) => {
@ -52,10 +51,10 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
update = (req: UpdateGatewayRequest, callbackFunc: () => void) => { update = (req: UpdateGatewayRequest, callbackFunc: () => void) => {
this.client.update(req, SessionStore.getMetadata(), (err) => { this.client.update(req, SessionStore.getMetadata(), err => {
if (err !== null) { if (err !== null) {
HandleError(err); HandleError(err);
return; return;
@ -68,10 +67,10 @@ class GatewayStore extends EventEmitter {
callbackFunc(); callbackFunc();
}); });
} };
delete = (req: DeleteGatewayRequest, callbackFunc: () => void) => { delete = (req: DeleteGatewayRequest, callbackFunc: () => void) => {
this.client.delete(req, SessionStore.getMetadata(), (err) => { this.client.delete(req, SessionStore.getMetadata(), err => {
if (err !== null) { if (err !== null) {
HandleError(err); HandleError(err);
return; return;
@ -84,7 +83,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(); callbackFunc();
}); });
} };
list = (req: ListGatewaysRequest, callbackFunc: (resp: ListGatewaysResponse) => void) => { list = (req: ListGatewaysRequest, callbackFunc: (resp: ListGatewaysResponse) => void) => {
this.client.list(req, SessionStore.getMetadata(), (err, resp) => { this.client.list(req, SessionStore.getMetadata(), (err, resp) => {
@ -95,7 +94,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
getStats = (req: GetGatewayStatsRequest, callbackFunc: (resp: GetGatewayStatsResponse) => void) => { getStats = (req: GetGatewayStatsRequest, callbackFunc: (resp: GetGatewayStatsResponse) => void) => {
this.client.getStats(req, SessionStore.getMetadata(), (err, resp) => { this.client.getStats(req, SessionStore.getMetadata(), (err, resp) => {
@ -106,9 +105,12 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
generateClientCertificate = (req: GenerateGatewayClientCertificateRequest, callbackFunc: (resp: GenerateGatewayClientCertificateResponse) => void) => { generateClientCertificate = (
req: GenerateGatewayClientCertificateRequest,
callbackFunc: (resp: GenerateGatewayClientCertificateResponse) => void,
) => {
this.client.generateClientCertificate(req, SessionStore.getMetadata(), (err, resp) => { this.client.generateClientCertificate(req, SessionStore.getMetadata(), (err, resp) => {
if (err !== null) { if (err !== null) {
HandleError(err); HandleError(err);
@ -117,7 +119,7 @@ class GatewayStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
} }
const gatewayStore = new GatewayStore(); 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 google_protobuf_empty_pb from "google-protobuf/google/protobuf/empty_pb";
import { notification } from "antd"; import { notification } from "antd";
@ -26,7 +26,6 @@ import {
import SessionStore from "./SessionStore"; import SessionStore from "./SessionStore";
import { HandleError } from "./helpers"; import { HandleError } from "./helpers";
class InternalStore extends EventEmitter { class InternalStore extends EventEmitter {
client: InternalServiceClient; client: InternalServiceClient;
@ -49,7 +48,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
deleteApiKey = (req: DeleteApiKeyRequest, callbackFunc: () => void) => { deleteApiKey = (req: DeleteApiKeyRequest, callbackFunc: () => void) => {
this.client.deleteApiKey(req, SessionStore.getMetadata(), (err, resp) => { this.client.deleteApiKey(req, SessionStore.getMetadata(), (err, resp) => {
@ -65,7 +64,7 @@ class InternalStore extends EventEmitter {
callbackFunc(); callbackFunc();
}); });
} };
listApiKeys = (req: ListApiKeysRequest, callbackFunc: (resp: ListApiKeysResponse) => void) => { listApiKeys = (req: ListApiKeysRequest, callbackFunc: (resp: ListApiKeysResponse) => void) => {
this.client.listApiKeys(req, SessionStore.getMetadata(), (err, resp) => { this.client.listApiKeys(req, SessionStore.getMetadata(), (err, resp) => {
@ -76,26 +75,25 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); 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; var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => { let setup = () => {
console.log("Setting up gRPC stream"); console.log("Setting up gRPC stream");
stream = this.client.streamGatewayFrames(req, SessionStore.getMetadata()); stream = this.client.streamGatewayFrames(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => { stream = stream.on("data", resp => {
callbackFunc(resp); callbackFunc(resp);
}); });
stream = stream.on('end', function() { stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting"); console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000); setTimeout(setup, 1000);
}); });
}; };
setup(); setup();
return () => { return () => {
@ -103,26 +101,25 @@ class InternalStore extends EventEmitter {
stream.cancel(); 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; var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => { let setup = () => {
console.log("Setting up gRPC stream"); console.log("Setting up gRPC stream");
stream = this.client.streamDeviceFrames(req, SessionStore.getMetadata()); stream = this.client.streamDeviceFrames(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => { stream = stream.on("data", resp => {
callbackFunc(resp); callbackFunc(resp);
}); });
stream = stream.on('end', function() { stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting"); console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000); setTimeout(setup, 1000);
}); });
}; };
setup(); setup();
return () => { return () => {
@ -130,20 +127,20 @@ class InternalStore extends EventEmitter {
stream.cancel(); 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; var stream: grpcWeb.ClientReadableStream<LogItem> | undefined = undefined;
let setup = () => { let setup = () => {
console.log("Setting up gRPC stream"); console.log("Setting up gRPC stream");
stream = this.client.streamDeviceEvents(req, SessionStore.getMetadata()); stream = this.client.streamDeviceEvents(req, SessionStore.getMetadata());
stream = stream.on("data", (resp) => { stream = stream.on("data", resp => {
callbackFunc(resp); callbackFunc(resp);
}); });
stream = stream.on('end', function() { stream = stream.on("end", function () {
console.log("gRPC stream end, reconnecting"); console.log("gRPC stream end, reconnecting");
setTimeout(setup, 1000); setTimeout(setup, 1000);
}); });
@ -156,7 +153,7 @@ class InternalStore extends EventEmitter {
stream.cancel(); stream.cancel();
} }
}; };
} };
getGatewaysSummary = (req: GetGatewaysSummaryRequest, callbackFunc: (resp: GetGatewaysSummaryResponse) => void) => { getGatewaysSummary = (req: GetGatewaysSummaryRequest, callbackFunc: (resp: GetGatewaysSummaryResponse) => void) => {
this.client.getGatewaysSummary(req, SessionStore.getMetadata(), (err, resp) => { this.client.getGatewaysSummary(req, SessionStore.getMetadata(), (err, resp) => {
@ -167,7 +164,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
getDevicesSummary = (req: GetDevicesSummaryRequest, callbackFunc: (resp: GetDevicesSummaryResponse) => void) => { getDevicesSummary = (req: GetDevicesSummaryRequest, callbackFunc: (resp: GetDevicesSummaryResponse) => void) => {
this.client.getDevicesSummary(req, SessionStore.getMetadata(), (err, resp) => { this.client.getDevicesSummary(req, SessionStore.getMetadata(), (err, resp) => {
@ -178,7 +175,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
settings = (callbackFunc: (resp: SettingsResponse) => void) => { settings = (callbackFunc: (resp: SettingsResponse) => void) => {
this.client.settings(new google_protobuf_empty_pb.Empty(), {}, (err, resp) => { this.client.settings(new google_protobuf_empty_pb.Empty(), {}, (err, resp) => {
@ -189,7 +186,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
globalSearch = (req: GlobalSearchRequest, callbackFunc: (resp: GlobalSearchResponse) => void) => { globalSearch = (req: GlobalSearchRequest, callbackFunc: (resp: GlobalSearchResponse) => void) => {
this.client.globalSearch(req, SessionStore.getMetadata(), (err, resp) => { this.client.globalSearch(req, SessionStore.getMetadata(), (err, resp) => {
@ -200,7 +197,7 @@ class InternalStore extends EventEmitter {
callbackFunc(resp); callbackFunc(resp);
}); });
} };
} }
const internalStore = new InternalStore(); const internalStore = new InternalStore();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,28 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Link } from "react-router-dom"; 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"; import { CreateApiKeyResponse } from "@chirpstack/chirpstack-api-grpc-web/api/internal_pb";
interface IProps { interface IProps {
createApiKeyResponse: CreateApiKeyResponse; createApiKeyResponse: CreateApiKeyResponse;
} }
class ApiKeyToken extends Component<IProps> { class ApiKeyToken extends Component<IProps> {
render() { render() {
return ( return (
<Space direction="vertical" style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }}>
<Typography> <Typography>
<Typography.Paragraph> <Typography.Paragraph>
Use the following API token when making API requests. This token can Use the following API token when making API requests. This token can be revoked at any time by deleting it.
be revoked at any time by deleting it. Please note that this token can Please note that this token can only be retrieved once:
only be retrieved once:
</Typography.Paragraph> </Typography.Paragraph>
</Typography> </Typography>
<Input.TextArea rows={4} value={this.props.createApiKeyResponse.getToken()} /> <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> </Space>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,28 +7,30 @@ import ApplicationStore from "../../stores/ApplicationStore";
import ApplicationForm from "./ApplicationForm"; import ApplicationForm from "./ApplicationForm";
import SessionStore from "../../stores/SessionStore"; import SessionStore from "../../stores/SessionStore";
interface IProps extends RouteComponentProps { interface IProps extends RouteComponentProps {
application: Application, application: Application;
} }
class EditApplication extends Component<IProps> { class EditApplication extends Component<IProps> {
onFinish = (obj: Application) => { onFinish = (obj: Application) => {
let req = new UpdateApplicationRequest(); let req = new UpdateApplicationRequest();
req.setApplication(obj); req.setApplication(obj);
ApplicationStore.update(req, () => { 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() { render() {
const disabled = !(SessionStore.isAdmin() || SessionStore.isTenantAdmin(this.props.application.getTenantId()) || SessionStore.isTenantDeviceAdmin(this.props.application.getTenantId())); const disabled = !(
SessionStore.isAdmin() ||
return( SessionStore.isTenantAdmin(this.props.application.getTenantId()) ||
<ApplicationForm initialValues={this.props.application} disabled={disabled} onFinish={this.onFinish} /> 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 { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table"; 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 { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import DataTable, { GetPageCallbackFunc } from "../../components/DataTable"; import DataTable, { GetPageCallbackFunc } from "../../components/DataTable";
import ApplicationStore from "../../stores/ApplicationStore"; import ApplicationStore from "../../stores/ApplicationStore";
import Admin from "../../components/Admin"; import Admin from "../../components/Admin";
interface IProps { interface IProps {
tenant: Tenant; tenant: Tenant;
} }
class ListApplications extends Component<IProps> { class ListApplications extends Component<IProps> {
columns = (): ColumnsType<ApplicationListItem.AsObject> => { columns = (): ColumnsType<ApplicationListItem.AsObject> => {
return [ return [
@ -35,7 +37,7 @@ class ListApplications extends Component<IProps> {
key: "description", key: "description",
}, },
]; ];
} };
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => { getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListApplicationsRequest(); let req = new ListApplicationsRequest();
@ -47,35 +49,37 @@ class ListApplications extends Component<IProps> {
const obj = resp.toObject(); const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList); callbackFunc(obj.totalCount, obj.resultList);
}); });
} };
render() { render() {
return( return (
<Space direction="vertical" style={{width: "100%"}} size="large"> <Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader <PageHeader
breadcrumbRender={() => <Breadcrumb> breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Tenants</span> <span>Tenants</span>
</Breadcrumb.Item> </Breadcrumb.Item>
<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>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Applications</span> <span>Applications</span>
</Breadcrumb.Item> </Breadcrumb.Item>
</Breadcrumb>} </Breadcrumb>
)}
title="Applications" title="Applications"
extra={[ extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin> <Admin tenantId={this.props.tenant.getId()} isDeviceAdmin>
<Button type="primary"><Link to={`/tenants/${this.props.tenant.getId()}/applications/create`}>Add application</Link></Button> <Button type="primary">
</Admin> <Link to={`/tenants/${this.props.tenant.getId()}/applications/create`}>Add application</Link>
</Button>
</Admin>,
]} ]}
/> />
<DataTable <DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" />
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
/>
</Space> </Space>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,14 +24,16 @@ class CreateAwsSnsIntegration extends Component<IProps> {
req.setIntegration(obj); req.setIntegration(obj);
ApplicationStore.createAwsSnsIntegration(req, () => { 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() { render() {
const i = new AwsSnsIntegration(); const i = new AwsSnsIntegration();
return( return (
<Card title="Add AWS SNS integration"> <Card title="Add AWS SNS integration">
<AwsSnsIntegrationForm initialValues={i} onFinish={this.onFinish} /> <AwsSnsIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card> </Card>

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import {
import HttpIntegrationForm from "./HttpIntegrationForm"; import HttpIntegrationForm from "./HttpIntegrationForm";
import ApplicationStore from "../../../stores/ApplicationStore"; import ApplicationStore from "../../../stores/ApplicationStore";
interface IProps extends RouteComponentProps { interface IProps extends RouteComponentProps {
application: Application; application: Application;
} }
@ -25,14 +24,16 @@ class CreateHttpIntegration extends Component<IProps> {
req.setIntegration(obj); req.setIntegration(obj);
ApplicationStore.createHttpIntegration(req, () => { 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() { render() {
const i = new HttpIntegration(); const i = new HttpIntegration();
return( return (
<Card title="Add HTTP integration"> <Card title="Add HTTP integration">
<HttpIntegrationForm initialValues={i} onFinish={this.onFinish} /> <HttpIntegrationForm initialValues={i} onFinish={this.onFinish} />
</Card> </Card>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,17 +2,13 @@ import React, { Component } from "react";
import { Form, Input, Button, Typography } from "antd"; import { Form, Input, Button, Typography } from "antd";
import { import { ThingsBoardIntegration } from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
ThingsBoardIntegration,
} from "@chirpstack/chirpstack-api-grpc-web/api/application_pb";
interface IProps { interface IProps {
initialValues: ThingsBoardIntegration; initialValues: ThingsBoardIntegration;
onFinish: (obj: ThingsBoardIntegration) => void; onFinish: (obj: ThingsBoardIntegration) => void;
} }
class ThingsBoardIntegrationForm extends Component<IProps> { class ThingsBoardIntegrationForm extends Component<IProps> {
onFinish = (values: ThingsBoardIntegration.AsObject) => { onFinish = (values: ThingsBoardIntegration.AsObject) => {
const v = Object.assign(this.props.initialValues.toObject(), values); const v = Object.assign(this.props.initialValues.toObject(), values);
@ -22,25 +18,28 @@ class ThingsBoardIntegrationForm extends Component<IProps> {
i.setServer(v.server); i.setServer(v.server);
this.props.onFinish(i); this.props.onFinish(i);
} };
render() { render() {
return( return (
<Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}> <Form layout="vertical" initialValues={this.props.initialValues.toObject()} onFinish={this.onFinish}>
<Form.Item <Form.Item
label="ThingsBoard server" label="ThingsBoard server"
name="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" /> <Input placeholder="http://host:port" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Typography.Paragraph> <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> </Typography.Paragraph>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit">Submit</Button> <Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item> </Form.Item>
</Form> </Form>
); );

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,11 @@ import { Link } from "react-router-dom";
import { Space, Breadcrumb, Button, PageHeader } from "antd"; import { Space, Breadcrumb, Button, PageHeader } from "antd";
import { ColumnsType } from "antd/es/table"; 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 { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Region } from "@chirpstack/chirpstack-api-grpc-web/common/common_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 DeviceProfileStore from "../../stores/DeviceProfileStore";
import Admin from "../../components/Admin"; import Admin from "../../components/Admin";
interface IProps { interface IProps {
tenant: Tenant; tenant: Tenant;
} }
class ListDeviceProfiles extends Component<IProps> { class ListDeviceProfiles extends Component<IProps> {
columns = (): ColumnsType<DeviceProfileListItem.AsObject> => { columns = (): ColumnsType<DeviceProfileListItem.AsObject> => {
return [ return [
@ -97,7 +99,7 @@ class ListDeviceProfiles extends Component<IProps> {
}, },
}, },
]; ];
} };
getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => { getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDeviceProfilesRequest(); let req = new ListDeviceProfilesRequest();
@ -109,35 +111,37 @@ class ListDeviceProfiles extends Component<IProps> {
const obj = resp.toObject(); const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList); callbackFunc(obj.totalCount, obj.resultList);
}); });
} };
render() { render() {
return( return (
<Space direction="vertical" style={{width: "100%"}} size="large"> <Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader <PageHeader
breadcrumbRender={() => <Breadcrumb> breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Tenants</span> <span>Tenants</span>
</Breadcrumb.Item> </Breadcrumb.Item>
<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>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Device profiles</span> <span>Device profiles</span>
</Breadcrumb.Item> </Breadcrumb.Item>
</Breadcrumb>} </Breadcrumb>
)}
title="Device profiles" title="Device profiles"
extra={[ extra={[
<Admin tenantId={this.props.tenant.getId()} isDeviceAdmin> <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> <Button type="primary">
</Admin> <Link to={`/tenants/${this.props.tenant.getId()}/device-profiles/create`}>Add device profile</Link>
</Button>
</Admin>,
]} ]}
/> />
<DataTable <DataTable columns={this.columns()} getPage={this.getPage} rowKey="id" />
columns={this.columns()}
getPage={this.getPage}
rowKey="id"
/>
</Space> </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 { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
import { Application } from "@chirpstack/chirpstack-api-grpc-web/api/application_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 { 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 DeviceForm from "./DeviceForm";
import DeviceStore from "../../stores/DeviceStore"; import DeviceStore from "../../stores/DeviceStore";
import DeviceProfileStore from "../../stores/DeviceProfileStore"; import DeviceProfileStore from "../../stores/DeviceProfileStore";
interface IProps extends RouteComponentProps { interface IProps extends RouteComponentProps {
tenant: Tenant; tenant: Tenant;
application: Application; application: Application;
} }
class CreateDevice extends Component<IProps> { class CreateDevice extends Component<IProps> {
onFinish = (obj: Device) => { onFinish = (obj: Device) => {
obj.setApplicationId(this.props.application.getId()); obj.setApplicationId(this.props.application.getId());
@ -33,38 +34,52 @@ class CreateDevice extends Component<IProps> {
DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => { DeviceProfileStore.get(req, (resp: GetDeviceProfileResponse) => {
let dp = resp.getDeviceProfile()!; let dp = resp.getDeviceProfile()!;
if (dp.getSupportsOtaa()) { 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 { } 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() { render() {
let device = new Device(); let device = new Device();
device.setApplicationId(this.props.application.getId()); device.setApplicationId(this.props.application.getId());
return( return (
<Space direction="vertical" style={{width: "100%"}} size="large"> <Space direction="vertical" style={{ width: "100%" }} size="large">
<PageHeader <PageHeader
breadcrumbRender={() => <Breadcrumb> breadcrumbRender={() => (
<Breadcrumb>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Tenants</span> <span>Tenants</span>
</Breadcrumb.Item> </Breadcrumb.Item>
<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>
<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>
<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>
<Breadcrumb.Item> <Breadcrumb.Item>
<span>Add device</span> <span>Add device</span>
</Breadcrumb.Item> </Breadcrumb.Item>
</Breadcrumb>} </Breadcrumb>
)}
title="Add device" title="Add device"
/> />
<Card> <Card>

View File

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

View File

@ -12,14 +12,11 @@ import {
GetDeviceStatsRequest, GetDeviceStatsRequest,
GetDeviceStatsResponse, GetDeviceStatsResponse,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb"; } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
import { import { DeviceProfile } from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
DeviceProfile,
} from "@chirpstack/chirpstack-api-grpc-web/api/device_profile_pb";
import DeviceStore from "../../stores/DeviceStore"; import DeviceStore from "../../stores/DeviceStore";
import Heatmap from "../../components/Heatmap"; import Heatmap from "../../components/Heatmap";
interface IProps { interface IProps {
device: Device; device: Device;
deviceProfile: DeviceProfile; deviceProfile: DeviceProfile;
@ -32,7 +29,7 @@ interface IState {
statsUpFreq: HeatmapStats[]; statsUpFreq: HeatmapStats[];
statsUpDr?: any; statsUpDr?: any;
statsGwRssi?: any; statsGwRssi?: any;
statsGwSnr?: any, statsGwSnr?: any;
} }
interface HeatmapStats { interface HeatmapStats {
@ -78,7 +75,7 @@ class DeviceDashboard extends Component<IProps, IState> {
lineTension: number; lineTension: number;
pointBackgroundColor: string; pointBackgroundColor: string;
data: number[]; data: number[];
}[], }[];
} = { } = {
labels: [], labels: [],
datasets: [ datasets: [
@ -129,28 +126,32 @@ class DeviceDashboard extends Component<IProps, IState> {
let statsGwRssiData: (number | null)[] = []; let statsGwRssiData: (number | null)[] = [];
let statsGwRssi = { let statsGwRssi = {
labels: statsGwRssiLabels, labels: statsGwRssiLabels,
datasets: [{ datasets: [
label: "rssi (reported by gateways)", {
borderColor: "rgba(33, 150, 243, 1)", label: "rssi (reported by gateways)",
backgroundColor: "rgba(0, 0, 0, 0)", borderColor: "rgba(33, 150, 243, 1)",
lineTension: 0, backgroundColor: "rgba(0, 0, 0, 0)",
pointBackgroundColor: "rgba(33, 150, 243, 1)", lineTension: 0,
data: statsGwRssiData, pointBackgroundColor: "rgba(33, 150, 243, 1)",
}], data: statsGwRssiData,
},
],
}; };
let statsGwSnrLabels: string[] = []; let statsGwSnrLabels: string[] = [];
let statsGwSnrData: (number | null)[] = []; let statsGwSnrData: (number | null)[] = [];
let statsGwSnr = { let statsGwSnr = {
labels: statsGwSnrLabels, labels: statsGwSnrLabels,
datasets: [{ datasets: [
label: "rssi (reported by gateways)", {
borderColor: "rgba(33, 150, 243, 1)", label: "rssi (reported by gateways)",
backgroundColor: "rgba(0, 0, 0, 0)", borderColor: "rgba(33, 150, 243, 1)",
lineTension: 0, backgroundColor: "rgba(0, 0, 0, 0)",
pointBackgroundColor: "rgba(33, 150, 243, 1)", lineTension: 0,
data: statsGwSnrData, pointBackgroundColor: "rgba(33, 150, 243, 1)",
}], data: statsGwSnrData,
},
],
}; };
let statsUpFreq: HeatmapStats[] = []; let statsUpFreq: HeatmapStats[] = [];
@ -160,7 +161,10 @@ class DeviceDashboard extends Component<IProps, IState> {
statsUpFreq.push({ statsUpFreq.push({
x: moment(row.getTime()!.toDate()).format("YYYY-MM-DD"), 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")); 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]); statsErrorsSet[v[0]].push(v[1]);
} }
for (const v of row.getRxPacketsPerDrMap().toObject()) { 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]) => { Object.entries(statsErrorsSet).forEach(([k, v]) => {
statsErrors.datasets.push({ statsErrors.datasets.push({
label: k, 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]) => { Object.entries(statsUpDrSet).forEach(([k, v]) => {
statsUpDr.datasets.push({ statsUpDr.datasets.push({
label: k, label: k,
@ -231,8 +266,7 @@ class DeviceDashboard extends Component<IProps, IState> {
statsGwSnr: statsGwSnr, statsGwSnr: statsGwSnr,
}); });
}); });
} };
render() { render() {
const animation: false = false; 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"); lastSeenAt = moment(this.props.lastSeenAt).format("YYYY-MM-DD HH:mm:ss");
} }
return( return (
<Space direction="vertical" style={{width: "100%"}} size="large"> <Space direction="vertical" style={{ width: "100%" }} size="large">
<Card> <Card>
<Descriptions> <Descriptions>
<Descriptions.Item label="Last seen">{lastSeenAt}</Descriptions.Item> <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="Device profile">
<Descriptions.Item label="Enabled">{this.props.device.getIsDisabled() ? "no" : "yes"}</Descriptions.Item> <Link
<Descriptions.Item label="Description">{this.props.device.getDescription()}</Descriptions.Item> to={`/tenants/${this.props.deviceProfile.getTenantId()}/device-profiles/${this.props.deviceProfile.getId()}/edit`}
</Descriptions> >
{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> </Card>
<Row gutter={24}> <Row gutter={24}>
<Col span={12}> <Col span={12}>

View File

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

View File

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

View File

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

View File

@ -14,21 +14,20 @@ interface IProps extends RouteComponentProps {
device: Device; device: Device;
} }
class EditDevice extends Component<IProps> { class EditDevice extends Component<IProps> {
onFinish = (obj: Device) => { onFinish = (obj: Device) => {
let req = new UpdateDeviceRequest(); let req = new UpdateDeviceRequest();
req.setDevice(obj); req.setDevice(obj);
DeviceStore.update(req, () => { 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() { render() {
return( return <DeviceForm initialValues={this.props.device} onFinish={this.onFinish} tenant={this.props.tenant} update />;
<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 { Space, Button, Dropdown, Menu, Modal, Select } from "antd";
import { ColumnsType } from "antd/es/table"; import { ColumnsType } from "antd/es/table";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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 { 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 { import {
ListMulticastGroupsRequest, ListMulticastGroupsRequest,
ListMulticastGroupsResponse, ListMulticastGroupsResponse,
@ -21,20 +31,17 @@ import DeviceStore from "../../stores/DeviceStore";
import MulticastGroupStore from "../../stores/MulticastGroupStore"; import MulticastGroupStore from "../../stores/MulticastGroupStore";
import Admin from "../../components/Admin"; import Admin from "../../components/Admin";
interface IProps { interface IProps {
application: Application; application: Application;
} }
interface IState { interface IState {
selectedRowIds: string[]; selectedRowIds: string[];
multicastGroups: MulticastGroupListItem[]; multicastGroups: MulticastGroupListItem[];
mgModalVisible: boolean; mgModalVisible: boolean;
mgSelected: string, mgSelected: string;
} }
class ListDevices extends Component<IProps, IState> { class ListDevices extends Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -71,7 +78,7 @@ class ListDevices extends Component<IProps, IState> {
ts.setUTCSeconds(record.lastSeenAt.seconds); ts.setUTCSeconds(record.lastSeenAt.seconds);
return moment(ts).format("YYYY-MM-DD HH:mm:ss"); 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", key: "devEui",
width: 250, width: 250,
render: (text, record) => ( 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", dataIndex: "deviceProfileName",
key: "deviceProfileName", key: "deviceProfileName",
render: (text, record) => ( 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) => { getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
let req = new ListDevicesRequest(); let req = new ListDevicesRequest();
@ -131,31 +146,31 @@ class ListDevices extends Component<IProps, IState> {
const obj = resp.toObject(); const obj = resp.toObject();
callbackFunc(obj.totalCount, obj.resultList); callbackFunc(obj.totalCount, obj.resultList);
}); });
} };
onRowsSelectChange = (ids: string[]) => { onRowsSelectChange = (ids: string[]) => {
this.setState({ this.setState({
selectedRowIds: ids, selectedRowIds: ids,
}); });
} };
showMgModal = () => { showMgModal = () => {
this.setState({ this.setState({
mgModalVisible: true, mgModalVisible: true,
}); });
} };
hideMgModal = () => { hideMgModal = () => {
this.setState({ this.setState({
mgModalVisible: false, mgModalVisible: false,
}); });
} };
onMgSelected = (value: string) => { onMgSelected = (value: string) => {
this.setState({ this.setState({
mgSelected: value, mgSelected: value,
}); });
} };
handleMgModalOk = () => { handleMgModalOk = () => {
for (let devEui of this.state.selectedRowIds) { for (let devEui of this.state.selectedRowIds) {
@ -169,7 +184,7 @@ class ListDevices extends Component<IProps, IState> {
this.setState({ this.setState({
mgModalVisible: false, mgModalVisible: false,
}); });
} };
render() { render() {
const menu = ( const menu = (
@ -178,21 +193,42 @@ class ListDevices extends Component<IProps, IState> {
</Menu> </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( return (
<Space direction="vertical" size="large" style={{width: "100%"}}> <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 === ""}}> <Modal
<Space direction="vertical" size="large" style={{width: "100%"}}> title="Add selected devices to multicast-group"
<Select style={{width: "100%"}} onChange={this.onMgSelected} placeholder="Select 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} {mgOptions}
</Select> </Select>
</Space> </Space>
</Modal> </Modal>
<Admin tenantId={this.props.application.getTenantId()} isDeviceAdmin> <Admin tenantId={this.props.application.getTenantId()} isDeviceAdmin>
<Space direction="horizontal" style={{float: "right"}}> <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> <Button type="primary">
<Dropdown placement="bottomRight" overlay={menu} trigger={['click']} disabled={this.state.selectedRowIds.length === 0}><Button>Selected devices</Button></Dropdown> <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> </Space>
</Admin> </Admin>
<DataTable <DataTable

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