mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-02-21 01:21:21 +00:00
ui: Implement queue-item expires-at timestamp in UI.
This commit is contained in:
parent
3829f591e4
commit
4d7a4b22e1
@ -17,6 +17,9 @@ import type {
|
||||
RemoveGatewayFromMulticastGroupRequest,
|
||||
ListMulticastGroupQueueRequest,
|
||||
ListMulticastGroupQueueResponse,
|
||||
EnqueueMulticastGroupQueueItemRequest,
|
||||
EnqueueMulticastGroupQueueItemResponse,
|
||||
FlushMulticastGroupQueueRequest,
|
||||
} from "@chirpstack/chirpstack-api-grpc-web/api/multicast_group_pb";
|
||||
|
||||
import SessionStore from "./SessionStore";
|
||||
@ -154,6 +157,20 @@ class MulticastGroupStore extends EventEmitter {
|
||||
});
|
||||
};
|
||||
|
||||
enqueue = (
|
||||
req: EnqueueMulticastGroupQueueItemRequest,
|
||||
callbackFunc: (resp: EnqueueMulticastGroupQueueItemResponse) => void,
|
||||
) => {
|
||||
this.client.enqueue(req, SessionStore.getMetadata(), (err, resp) => {
|
||||
if (err !== null) {
|
||||
HandleError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callbackFunc(resp);
|
||||
});
|
||||
};
|
||||
|
||||
listQueue = (req: ListMulticastGroupQueueRequest, callbackFunc: (resp: ListMulticastGroupQueueResponse) => void) => {
|
||||
this.client.listQueue(req, SessionStore.getMetadata(), (err, resp) => {
|
||||
if (err !== null) {
|
||||
@ -164,6 +181,17 @@ class MulticastGroupStore extends EventEmitter {
|
||||
callbackFunc(resp);
|
||||
});
|
||||
};
|
||||
|
||||
flushQueue = (req: FlushMulticastGroupQueueRequest, callbackFunc: () => void) => {
|
||||
this.client.flushQueue(req, SessionStore.getMetadata(), err => {
|
||||
if (err !== null) {
|
||||
HandleError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callbackFunc();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const multicastGroupStore = new MulticastGroupStore();
|
||||
|
@ -1,17 +1,32 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { Struct } from "google-protobuf/google/protobuf/struct_pb";
|
||||
import { format } from "date-fns";
|
||||
import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb";
|
||||
|
||||
import { Switch, notification } from "antd";
|
||||
import { Button, Tabs, Space, Card, Row, Form, Input, InputNumber, Popconfirm } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Tabs,
|
||||
Space,
|
||||
Card,
|
||||
Row,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Popconfirm,
|
||||
DatePicker,
|
||||
DatePickerProps,
|
||||
} from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { RedoOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
import type { Device, GetDeviceQueueItemsResponse } from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
|
||||
import {
|
||||
EnqueueDeviceQueueItemRequest,
|
||||
GetDeviceQueueItemsRequest,
|
||||
GetDeviceQueueItemsResponse,
|
||||
Device,
|
||||
FlushDeviceQueueRequest,
|
||||
DeviceQueueItem,
|
||||
} from "@chirpstack/chirpstack-api-grpc-web/api/device_pb";
|
||||
@ -34,6 +49,7 @@ interface FormRules {
|
||||
hex: string;
|
||||
base64: string;
|
||||
json: string;
|
||||
expiresAt?: DatePickerProps["value"];
|
||||
}
|
||||
|
||||
function DeviceQueue(props: IProps) {
|
||||
@ -114,6 +130,21 @@ function DeviceQueue(props: IProps) {
|
||||
return Buffer.from(record.data as string, "base64").toString("hex");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Expires at",
|
||||
dataIndex: "expiresAt",
|
||||
key: "expiresAt",
|
||||
width: 250,
|
||||
render: (_text, record) => {
|
||||
if (record.expiresAt !== undefined) {
|
||||
const ts = new Date(0);
|
||||
ts.setUTCSeconds(record.expiresAt.seconds);
|
||||
return format(ts, "yyyy-MM-dd HH:mm:ss");
|
||||
} else {
|
||||
return "Never";
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
|
||||
@ -148,6 +179,10 @@ function DeviceQueue(props: IProps) {
|
||||
item.setIsEncrypted(values.isEncrypted);
|
||||
item.setFCntDown(values.fCntDown);
|
||||
|
||||
if (values.expiresAt !== null && values.expiresAt !== undefined) {
|
||||
item.setExpiresAt(Timestamp.fromDate(values.expiresAt.toDate()));
|
||||
}
|
||||
|
||||
if (values.hex !== undefined) {
|
||||
item.setData(new Uint8Array(Buffer.from(values.hex, "hex")));
|
||||
}
|
||||
@ -217,6 +252,13 @@ function DeviceQueue(props: IProps) {
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name="expiresAt"
|
||||
label="Expires at"
|
||||
tooltip="If set, the queue-item will automatically expire at the given timestamp if it wasn't sent yet."
|
||||
>
|
||||
<DatePicker showTime />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Row>
|
||||
<Tabs defaultActiveKey="1">
|
||||
|
@ -21,6 +21,7 @@ import ListMulticastGroupDevices from "./ListMulticastGroupDevices";
|
||||
import ListMulticastGroupGateways from "./ListMulticastGroupGateways";
|
||||
import EditMulticastGroup from "./EditMulticastGroup";
|
||||
import Admin from "../../components/Admin";
|
||||
import MulticastGroupQueue from "./MulticastGroupQueue";
|
||||
|
||||
interface IProps {
|
||||
tenant: Tenant;
|
||||
@ -68,6 +69,9 @@ function MulticastGroupLayout(props: IProps) {
|
||||
if (path.endsWith("edit")) {
|
||||
tab = "edit";
|
||||
}
|
||||
if (path.endsWith("queue")) {
|
||||
tab = "queue";
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
@ -131,11 +135,17 @@ function MulticastGroupLayout(props: IProps) {
|
||||
Configuration
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="queue">
|
||||
<Link to={`/tenants/${tenant.getId()}/applications/${app.getId()}/multicast-groups/${mg.getId()}/queue`}>
|
||||
Queue
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
<Routes>
|
||||
<Route path="/" element={<ListMulticastGroupDevices multicastGroup={mg} />} />
|
||||
<Route path="/gateways" element={<ListMulticastGroupGateways multicastGroup={mg} application={app} />} />
|
||||
<Route path="/edit" element={<EditMulticastGroup application={app} multicastGroup={mg} />} />
|
||||
<Route path="/queue" element={<MulticastGroupQueue multicastGroup={mg} />} />
|
||||
</Routes>
|
||||
</Card>
|
||||
</Space>
|
||||
|
195
ui/src/views/multicast-groups/MulticastGroupQueue.tsx
Normal file
195
ui/src/views/multicast-groups/MulticastGroupQueue.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Tabs,
|
||||
Space,
|
||||
Card,
|
||||
Row,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Popconfirm,
|
||||
DatePicker,
|
||||
DatePickerProps,
|
||||
} from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { RedoOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
import {
|
||||
EnqueueMulticastGroupQueueItemRequest,
|
||||
ListMulticastGroupQueueRequest,
|
||||
FlushMulticastGroupQueueRequest,
|
||||
MulticastGroupQueueItem,
|
||||
MulticastGroup,
|
||||
ListMulticastGroupQueueResponse,
|
||||
} from "@chirpstack/chirpstack-api-grpc-web/api/multicast_group_pb";
|
||||
|
||||
import { onFinishFailed } from "../helpers";
|
||||
import type { GetPageCallbackFunc } from "../../components/DataTable";
|
||||
import DataTable from "../../components/DataTable";
|
||||
import MulticastGroupStore from "../../stores/MulticastGroupStore";
|
||||
|
||||
interface IProps {
|
||||
multicastGroup: MulticastGroup;
|
||||
}
|
||||
|
||||
interface FormRules {
|
||||
fPort: number;
|
||||
hex: string;
|
||||
base64: string;
|
||||
expiresAt?: DatePickerProps["value"];
|
||||
}
|
||||
|
||||
function MulticastGroupQueue(props: IProps) {
|
||||
const [refreshCounter, setRefreshCounter] = useState<number>(0);
|
||||
const [form] = Form.useForm<FormRules>();
|
||||
|
||||
const columns: ColumnsType<MulticastGroupQueueItem.AsObject> = [
|
||||
{
|
||||
title: "Frame-counter",
|
||||
dataIndex: "fCnt",
|
||||
key: "fCnt",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "FPort",
|
||||
dataIndex: "fPort",
|
||||
key: "fPort",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "Data (HEX)",
|
||||
dataIndex: "data",
|
||||
key: "data",
|
||||
render: (text, record) => {
|
||||
return Buffer.from(record.data as string, "base64").toString("hex");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Expires at",
|
||||
dataIndex: "expiresAt",
|
||||
key: "expiresAt",
|
||||
width: 250,
|
||||
render: (_text, record) => {
|
||||
if (record.expiresAt !== undefined) {
|
||||
const ts = new Date(0);
|
||||
ts.setUTCSeconds(record.expiresAt.seconds);
|
||||
return format(ts, "yyyy-MM-dd HH:mm:ss");
|
||||
} else {
|
||||
return "Never";
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
|
||||
const req = new ListMulticastGroupQueueRequest();
|
||||
req.setMulticastGroupId(props.multicastGroup.getId());
|
||||
|
||||
MulticastGroupStore.listQueue(req, (resp: ListMulticastGroupQueueResponse) => {
|
||||
const obj = resp.toObject();
|
||||
callbackFunc(obj.itemsList.length, obj.itemsList);
|
||||
});
|
||||
};
|
||||
|
||||
const refreshQueue = () => {
|
||||
setRefreshCounter(refreshCounter + 1);
|
||||
};
|
||||
|
||||
const flushQueue = () => {
|
||||
const req = new FlushMulticastGroupQueueRequest();
|
||||
req.setMulticastGroupId(props.multicastGroup.getId());
|
||||
MulticastGroupStore.flushQueue(req, () => {
|
||||
refreshQueue();
|
||||
});
|
||||
};
|
||||
|
||||
const onEnqueue = (values: FormRules) => {
|
||||
const req = new EnqueueMulticastGroupQueueItemRequest();
|
||||
const item = new MulticastGroupQueueItem();
|
||||
|
||||
item.setMulticastGroupId(props.multicastGroup.getId());
|
||||
item.setFPort(values.fPort);
|
||||
|
||||
if (values.expiresAt !== null && values.expiresAt !== undefined) {
|
||||
item.setExpiresAt(Timestamp.fromDate(values.expiresAt.toDate()));
|
||||
}
|
||||
|
||||
if (values.base64 !== undefined) {
|
||||
item.setData(new Uint8Array(Buffer.from(values.base64, "base64")));
|
||||
}
|
||||
|
||||
if (values.hex !== undefined) {
|
||||
item.setData(new Uint8Array(Buffer.from(values.hex, "hex")));
|
||||
}
|
||||
|
||||
req.setQueueItem(item);
|
||||
|
||||
MulticastGroupStore.enqueue(req, _ => {
|
||||
form.resetFields();
|
||||
refreshQueue();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<Card title="Enqueue">
|
||||
<Form
|
||||
layout="horizontal"
|
||||
onFinish={onEnqueue}
|
||||
onFinishFailed={onFinishFailed}
|
||||
form={form}
|
||||
initialValues={{ fPort: 1 }}
|
||||
>
|
||||
<Row>
|
||||
<Space direction="horizontal" style={{ width: "100%" }} size="large">
|
||||
<Form.Item name="fPort" label="FPort">
|
||||
<InputNumber min={1} max={254} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="expiresAt"
|
||||
label="Expires at"
|
||||
tooltip="If set, the queue-item will automatically expire at the given timestamp if it wasn't sent yet."
|
||||
>
|
||||
<DatePicker showTime />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Row>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="HEX" key="1">
|
||||
<Form.Item name="hex">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="BASE64" key="2">
|
||||
<Form.Item name="base64">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Enqueue
|
||||
</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
<Row justify="end">
|
||||
<Space direction="horizontal" size="large">
|
||||
<Button icon={<RedoOutlined />} onClick={refreshQueue}>
|
||||
Reload
|
||||
</Button>
|
||||
<Popconfirm title="Are you sure you want to flush the queue?" placement="left" onConfirm={flushQueue}>
|
||||
<Button icon={<DeleteOutlined />}>Flush queue</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</Row>
|
||||
<DataTable columns={columns} getPage={getPage} refreshKey={refreshCounter} rowKey="id" noPagination />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
export default MulticastGroupQueue;
|
Loading…
x
Reference in New Issue
Block a user