Compare commits

...

8 Commits

Author SHA1 Message Date
b821c38cac Ensure the nix dev shell has jq (#705)
chirpstack/Makefile uses jq to populate the PKG_VERSION variable.
2025-07-09 13:59:05 +01:00
f42337f411 Bump version to 4.13.0 2025-06-16 12:07:59 +01:00
c5fa5e04c3 api: Update DeviceActivation field documentation.
Fixes chirpstack/chirpstack-rest-api#20.
2025-06-16 10:20:45 +01:00
b7d5394644 Add dev_nonce / join_eui to DevNonce already used log.
This adds the dev_nonce and join_eui to the DevNonce already used log
event to help debugging re-use of DevNonce issues (e.g. #601).
2025-06-16 10:11:02 +01:00
984300e936 Bump brace-expansion from 1.1.11 to 1.1.12 in /api/js (#696)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 15:51:34 +01:00
c58cc7ad8b Add option to configure max. gw time drift.
Closes #666.
2025-06-13 14:38:18 +01:00
952a49f35e Bump golang.org/x/net from 0.36.0 to 0.38.0 in /examples/frame_log/go (#685)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-13 13:30:03 +01:00
2565cca14a ui: Make it more clear if fuota job is running or scheduled.
Before, the spinner icon would show for both running jobs + jobs
that were scheduled in the future. With this change, a clock icon
is showed when a job is scheduled in the future which which gets changed
into the spinner icon once it will get executed. This also add a column
displaying the run at timestamp.
2025-06-13 13:08:55 +01:00
26 changed files with 191 additions and 49 deletions

12
Cargo.lock generated
View File

@ -636,7 +636,7 @@ dependencies = [
[[package]]
name = "backend"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"aes-kw",
"anyhow",
@ -838,7 +838,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chirpstack"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"aes",
"anyhow",
@ -930,7 +930,7 @@ dependencies = [
[[package]]
name = "chirpstack_api"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"hex",
"pbjson",
@ -947,7 +947,7 @@ dependencies = [
[[package]]
name = "chirpstack_integration"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"anyhow",
"async-trait",
@ -2767,7 +2767,7 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lrwn"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"aes",
"anyhow",
@ -2780,7 +2780,7 @@ dependencies = [
[[package]]
name = "lrwn_filters"
version = "4.13.0-test.2"
version = "4.13.0"
dependencies = [
"hex",
"lrwn",

View File

@ -1175,12 +1175,13 @@ type DeviceActivation struct {
// Application session key (HEX encoded).
AppSKey string `protobuf:"bytes,3,opt,name=app_s_key,json=appSKey,proto3" json:"app_s_key,omitempty"`
// Network session encryption key (HEX encoded).
// Note: For ABP in LoRaWAN 1.0.x, use this, the serving and the forwarding
// network session integrity key fields with the LoRaWAN 1.0.x 'NwkSKey`!
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
NwkSEncKey string `protobuf:"bytes,4,opt,name=nwk_s_enc_key,json=nwkSEncKey,proto3" json:"nwk_s_enc_key,omitempty"`
// Serving network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
SNwkSIntKey string `protobuf:"bytes,8,opt,name=s_nwk_s_int_key,json=sNwkSIntKey,proto3" json:"s_nwk_s_int_key,omitempty"`
// Forwarding network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
FNwkSIntKey string `protobuf:"bytes,9,opt,name=f_nwk_s_int_key,json=fNwkSIntKey,proto3" json:"f_nwk_s_int_key,omitempty"`
// Uplink frame-counter.
FCntUp uint32 `protobuf:"varint,5,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"`

View File

@ -1,6 +1,6 @@
{
"name": "@chirpstack/chirpstack-api-grpc-web",
"version": "4.13.0-test.2",
"version": "4.13.0",
"description": "Chirpstack gRPC-web API",
"license": "MIT",
"devDependencies": {

View File

@ -8,7 +8,7 @@ plugins {
}
group = "io.chirpstack"
version = "4.13.0-test.2"
version = "4.13.0"
repositories {
mavenCentral()

2
api/js/package.json vendored
View File

@ -1,6 +1,6 @@
{
"name": "@chirpstack/chirpstack-api",
"version": "4.13.0-test.2",
"version": "4.13.0",
"description": "Chirpstack JS and TS API",
"license": "MIT",
"devDependencies": {

6
api/js/yarn.lock vendored
View File

@ -146,9 +146,9 @@ balanced-match@^1.0.0:
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
version "1.1.12"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"

View File

@ -9,7 +9,7 @@ plugins {
}
group = "io.chirpstack"
version = "4.13.0-test.2"
version = "4.13.0"
repositories {
mavenCentral()

View File

@ -3,7 +3,7 @@
"description": "Chirpstack PHP API",
"license": "MIT",
"type": "library",
"version": "4.13.0-test.2",
"version": "4.13.0",
"require": {
"php": ">=7.0.0",
"grpc/grpc": "^v1.57.0",

View File

@ -412,14 +412,15 @@ message DeviceActivation {
string app_s_key = 3;
// Network session encryption key (HEX encoded).
// Note: For ABP in LoRaWAN 1.0.x, use this, the serving and the forwarding
// network session integrity key fields with the LoRaWAN 1.0.x 'NwkSKey`!
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string nwk_s_enc_key = 4;
// Serving network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string s_nwk_s_int_key = 8;
// Forwarding network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string f_nwk_s_int_key = 9;
// Uplink frame-counter.

View File

@ -18,7 +18,7 @@ CLASSIFIERS = [
setup(
name='chirpstack-api',
version = "4.13.0-test.2",
version = "4.13.0",
url='https://github.com/brocaar/chirpstack-api',
author='Orne Brocaar',
author_email='info@brocaar.com',

2
api/rust/Cargo.toml vendored
View File

@ -1,7 +1,7 @@
[package]
name = "chirpstack_api"
description = "ChirpStack Protobuf / gRPC API definitions."
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT"
homepage = "https://www.chirpstack.io"

View File

@ -412,14 +412,15 @@ message DeviceActivation {
string app_s_key = 3;
// Network session encryption key (HEX encoded).
// Note: For ABP in LoRaWAN 1.0.x, use this, the serving and the forwarding
// network session integrity key fields with the LoRaWAN 1.0.x 'NwkSKey`!
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string nwk_s_enc_key = 4;
// Serving network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string s_nwk_s_int_key = 8;
// Forwarding network session integrity key (HEX encoded).
// Note: For LoRaWAN 1.0.x devices, set this to the NwkSKey.
string f_nwk_s_int_key = 9;
// Uplink frame-counter.

View File

@ -1,6 +1,6 @@
[package]
name = "backend"
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
publish = false

View File

@ -3,13 +3,13 @@
description = "Library for building external ChirpStack integrations"
homepage = "https://www.chirpstack.io/"
license = "MIT"
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
repository = "https://github.com/chirpstack/chirpstack"
[dependencies]
chirpstack_api = { path = "../api/rust", version = "4.13.0-test.2" }
chirpstack_api = { path = "../api/rust", version = "4.13.0" }
redis = { version = "0.31", features = [
"cluster-async",
"tokio-rustls-comp",

View File

@ -3,7 +3,7 @@
description = "ChirpStack is an open-source LoRaWAN(TM) Network Server"
repository = "https://github.com/chirpstack/chirpstack"
homepage = "https://www.chirpstack.io/"
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
publish = false

View File

@ -172,6 +172,15 @@ pub fn run() {
# ChirpStack will be allowed.
allow_unknown_gateways={{ gateway.allow_unknown_gateways }}
# RX timestamp max. drift.
#
# If the delta between the gateway reported RX timestamp vs ChirpStack
# server time is bigger than the configured value, then ChirpStack will
# ignore it. ChirpStack will then use the RX timestamp from the other
# receiving gateways, or failing that, will fall back onto the current
# server time.
rx_timestamp_max_drift="{{ gateway.rx_timestamp_max_drift }}"
# Network related configuration.
[network]

View File

@ -137,6 +137,8 @@ pub struct Gateway {
pub ca_cert: String,
pub ca_key: String,
pub allow_unknown_gateways: bool,
#[serde(with = "humantime_serde")]
pub rx_timestamp_max_drift: Duration,
}
impl Default for Gateway {
@ -146,6 +148,7 @@ impl Default for Gateway {
ca_cert: "".to_string(),
ca_key: "".to_string(),
allow_unknown_gateways: false,
rx_timestamp_max_drift: Duration::from_secs(30),
}
}
}

View File

@ -3,12 +3,12 @@ use std::str::FromStr;
use std::time::{Duration, SystemTime};
use anyhow::Result;
use chrono::{DateTime, Utc};
use chrono::{DateTime, TimeDelta, Utc};
use crate::gpstime::ToDateTime;
use crate::region;
use chirpstack_api::{common, gw};
use crate::{config, gpstime::ToDateTime, region};
pub fn get_uplink_dr(
region_config_id: &str,
tx_info: &chirpstack_api::gw::UplinkTxInfo,
@ -54,6 +54,9 @@ pub fn get_uplink_ch(region_config_id: &str, frequency: u32, dr: u8) -> Result<u
}
pub fn get_rx_timestamp(rx_info: &[gw::UplinkRxInfo]) -> SystemTime {
let conf = config::get();
let rx_timestamp_max_drift = conf.gateway.rx_timestamp_max_drift;
// First search for time_since_gps_epoch.
for rxi in rx_info {
if let Some(gps_time) = &rxi.time_since_gps_epoch {
@ -71,7 +74,14 @@ pub fn get_rx_timestamp(rx_info: &[gw::UplinkRxInfo]) -> SystemTime {
if let Some(ts) = &rxi.gw_time {
let ts: Result<DateTime<Utc>> = (*ts).try_into().map_err(anyhow::Error::msg);
if let Ok(ts) = ts {
return ts.into();
let mut delta = Utc::now() - ts;
if delta < TimeDelta::default() {
delta = -delta;
}
let delta = delta.to_std().unwrap_or_default();
if delta < rx_timestamp_max_drift {
return ts.into();
}
}
}
}
@ -81,6 +91,9 @@ pub fn get_rx_timestamp(rx_info: &[gw::UplinkRxInfo]) -> SystemTime {
}
pub fn get_rx_timestamp_chrono(rx_info: &[gw::UplinkRxInfo]) -> DateTime<Utc> {
let conf = config::get();
let rx_timestamp_max_drift = conf.gateway.rx_timestamp_max_drift;
// First search for time_since_gps_epoch.
for rxi in rx_info {
if let Some(gps_time) = &rxi.time_since_gps_epoch {
@ -98,7 +111,14 @@ pub fn get_rx_timestamp_chrono(rx_info: &[gw::UplinkRxInfo]) -> DateTime<Utc> {
if let Some(ts) = &rxi.gw_time {
let ts: Result<DateTime<Utc>> = (*ts).try_into().map_err(anyhow::Error::msg);
if let Ok(ts) = ts {
return ts;
let mut delta = Utc::now() - ts;
if delta < TimeDelta::default() {
delta = -delta;
}
let delta = delta.to_std().unwrap_or_default();
if delta < rx_timestamp_max_drift {
return ts;
}
}
}
}
@ -188,3 +208,74 @@ pub fn set_uplink_modulation(
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_get_rx_timestamp_no_drift() {
let now = Utc::now();
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res: DateTime<Utc> = get_rx_timestamp(&[rx_info]).into();
assert_eq!(res, now);
}
#[test]
fn test_get_rx_timestamp_drift() {
let now = Utc::now() - chrono::Duration::seconds(60);
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res: DateTime<Utc> = get_rx_timestamp(&[rx_info]).into();
assert_ne!(res, now);
let now = Utc::now() + chrono::Duration::seconds(60);
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res: DateTime<Utc> = get_rx_timestamp(&[rx_info]).into();
assert_ne!(res, now);
}
#[test]
fn test_get_rx_timestamp_chrono_no_drift() {
let now = Utc::now();
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res = get_rx_timestamp_chrono(&[rx_info]);
assert_eq!(res, now);
}
#[test]
fn test_get_rx_timestamp_chrono_drift() {
let now = Utc::now() - chrono::Duration::seconds(60);
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res = get_rx_timestamp_chrono(&[rx_info]);
assert_ne!(res, now);
let now = Utc::now() + chrono::Duration::seconds(60);
let rx_info = gw::UplinkRxInfo {
gw_time: Some(now.try_into().unwrap()),
..Default::default()
};
let res = get_rx_timestamp_chrono(&[rx_info]);
assert_ne!(res, now);
}
}

View File

@ -475,10 +475,14 @@ impl JoinRequest {
level: integration_pb::LogLevel::Error.into(),
code: integration_pb::LogCode::Otaa.into(),
description: "DevNonce has already been used".into(),
context: [(
"deduplication_id".to_string(),
self.uplink_frame_set.uplink_set_id.to_string(),
)]
context: [
(
"deduplication_id".to_string(),
self.uplink_frame_set.uplink_set_id.to_string(),
),
("join_eui".to_string(), join_request.join_eui.to_string()),
("dev_nonce".to_string(), join_request.dev_nonce.to_string()),
]
.iter()
.cloned()
.collect(),

View File

@ -1,6 +1,6 @@
module frame-log
go 1.18
go 1.23.0
require (
github.com/chirpstack/chirpstack/api/go/v4 v4.6.0
@ -12,6 +12,6 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/google/go-cmp v0.5.9 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
)

View File

@ -5,19 +5,26 @@ github.com/chirpstack/chirpstack/api/go/v4 v4.6.0/go.mod h1:6+68s1PGHq2QWZ216RTw
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -3,7 +3,7 @@
description = "Library for filtering LoRaWAN payloads on DevAddr and JoinEUIs prefixes"
homepage = "https://www.chirpstack.io/"
license = "MIT"
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021"
repository = "https://github.com/chirpstack/chirpstack"

View File

@ -3,7 +3,7 @@
description = "Library for encoding / decoding LoRaWAN frames."
homepage = "https://www.chirpstack.io"
license = "MIT"
version = "4.13.0-test.2"
version = "4.13.0"
authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018"
repository = "https://github.com/chirpstack/chirpstack"

View File

@ -23,6 +23,7 @@ pkgs.mkShell {
pkgs.cargo-cross # cross-compiling
pkgs.cargo-deb # deb packaging
pkgs.diesel-cli # diesel cli
pkgs.jq # json query cli tool
];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.llvmPackages.libclang.lib}/lib/clang/${pkgs.llvmPackages.libclang.version}/include";

View File

@ -1,6 +1,6 @@
{
"name": "chirpstack-ui",
"version": "4.13.0-test.2",
"version": "4.13.0",
"private": true,
"type": "module",
"scripts": {

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { Spin, Button, Space, Timeline, Row, Col, TimelineProps, Card, Tag, Popover, Table } from "antd";
import { LoadingOutlined, ReloadOutlined } from "@ant-design/icons";
import { LoadingOutlined, ReloadOutlined, ClockCircleOutlined } from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import { format } from "date-fns";
@ -23,17 +23,25 @@ interface IProps {
function FuotaDeploymentDashboard(props: IProps) {
const [fuotaJobs, setFuotaJobs] = useState<FuotaDeploymentJob.AsObject[]>([]);
const [now, setNow] = useState<Date>(new Date());
useEffect(() => {
getFuotaJobs();
const interval = setInterval(() => {
const getFuotaJobsInterval = setInterval(() => {
if (!props.getFuotaDeploymentResponse.getCompletedAt()) {
getFuotaJobs();
}
}, 10000);
return () => clearInterval(interval);
const getNowInterval = setInterval(() => {
setNow(new Date());
});
return () => {
clearInterval(getFuotaJobsInterval);
clearInterval(getNowInterval);
};
}, [props.getFuotaDeploymentResponse]);
const jobs: Record<string, string> = {
@ -62,6 +70,15 @@ function FuotaDeploymentDashboard(props: IProps) {
</Popover>
);
} else if (!record.completedAt) {
if (record.schedulerRunAfter) {
const schedulerRunAfter = new Date(0);
schedulerRunAfter.setUTCSeconds(record.schedulerRunAfter.seconds);
if (schedulerRunAfter > now) {
return <ClockCircleOutlined />;
}
}
return <Spin indicator={<LoadingOutlined spin />} size="small" />;
} else if (record.warningMsg !== "") {
return (
@ -88,6 +105,13 @@ function FuotaDeploymentDashboard(props: IProps) {
render: (_text, record) => format_dt_from_secs(record.createdAt?.seconds),
width: 250,
},
{
title: "Run at",
dataIndex: "schedulerRunAfter",
key: "schedulerRunAfter",
render: (_text, record) => format_dt_from_secs(record.schedulerRunAfter?.seconds),
width: 250,
},
{
title: "Completed at",
dataIndex: "completedAt",