mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-04-25 21:40:06 +00:00
Implement Gateway Mesh in UI.
This commit is contained in:
parent
4f5b14eeb8
commit
682d1b7b56
6
api/proto/api/gateway.proto
vendored
6
api/proto/api/gateway.proto
vendored
@ -382,6 +382,9 @@ message RelayGatewayListItem {
|
|||||||
// Please note that the state of the relay is driven by the last
|
// Please note that the state of the relay is driven by the last
|
||||||
// received stats packet sent by the relay-gateway.
|
// received stats packet sent by the relay-gateway.
|
||||||
GatewayState state = 10;
|
GatewayState state = 10;
|
||||||
|
|
||||||
|
// Region configuration ID.
|
||||||
|
string region_config_id = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateRelayGatewayRequest {
|
message UpdateRelayGatewayRequest {
|
||||||
@ -414,4 +417,7 @@ message RelayGateway {
|
|||||||
// This defines the expected interval in which the gateway sends its
|
// This defines the expected interval in which the gateway sends its
|
||||||
// statistics.
|
// statistics.
|
||||||
uint32 stats_interval = 5;
|
uint32 stats_interval = 5;
|
||||||
|
|
||||||
|
// Region configuration ID.
|
||||||
|
string region_config_id = 6;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "ApplicationProto";
|
option java_outer_classname = "ApplicationProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
2
api/rust/proto/chirpstack/api/device.proto
vendored
2
api/rust/proto/chirpstack/api/device.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "DeviceProto";
|
option java_outer_classname = "DeviceProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "DeviceProfileProto";
|
option java_outer_classname = "DeviceProfileProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "DeviceProfileTemplateProto";
|
option java_outer_classname = "DeviceProfileTemplateProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
8
api/rust/proto/chirpstack/api/gateway.proto
vendored
8
api/rust/proto/chirpstack/api/gateway.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "GatewayProto";
|
option java_outer_classname = "GatewayProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
@ -380,6 +382,9 @@ message RelayGatewayListItem {
|
|||||||
// Please note that the state of the relay is driven by the last
|
// Please note that the state of the relay is driven by the last
|
||||||
// received stats packet sent by the relay-gateway.
|
// received stats packet sent by the relay-gateway.
|
||||||
GatewayState state = 10;
|
GatewayState state = 10;
|
||||||
|
|
||||||
|
// Region configuration ID.
|
||||||
|
string region_config_id = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateRelayGatewayRequest {
|
message UpdateRelayGatewayRequest {
|
||||||
@ -412,4 +417,7 @@ message RelayGateway {
|
|||||||
// This defines the expected interval in which the gateway sends its
|
// This defines the expected interval in which the gateway sends its
|
||||||
// statistics.
|
// statistics.
|
||||||
uint32 stats_interval = 5;
|
uint32 stats_interval = 5;
|
||||||
|
|
||||||
|
// Region configuration ID.
|
||||||
|
string region_config_id = 6;
|
||||||
}
|
}
|
||||||
|
2
api/rust/proto/chirpstack/api/internal.proto
vendored
2
api/rust/proto/chirpstack/api/internal.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "InternalProto";
|
option java_outer_classname = "InternalProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "MulticastGroupProto";
|
option java_outer_classname = "MulticastGroupProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
2
api/rust/proto/chirpstack/api/relay.proto
vendored
2
api/rust/proto/chirpstack/api/relay.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "RelayProto";
|
option java_outer_classname = "RelayProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
2
api/rust/proto/chirpstack/api/tenant.proto
vendored
2
api/rust/proto/chirpstack/api/tenant.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "TenantProto";
|
option java_outer_classname = "TenantProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
2
api/rust/proto/chirpstack/api/user.proto
vendored
2
api/rust/proto/chirpstack/api/user.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "UserProto";
|
option java_outer_classname = "UserProto";
|
||||||
option csharp_namespace = "Chirpstack.Api";
|
option csharp_namespace = "Chirpstack.Api";
|
||||||
|
option php_namespace = "Chirpstack\\Api";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Api";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "CommonProto";
|
option java_outer_classname = "CommonProto";
|
||||||
option csharp_namespace = "Chirpstack.Common";
|
option csharp_namespace = "Chirpstack.Common";
|
||||||
|
option php_namespace = "Chirpstack\\Common";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Common";
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
2
api/rust/proto/chirpstack/gw/gw.proto
vendored
2
api/rust/proto/chirpstack/gw/gw.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.gw";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "GatewayProto";
|
option java_outer_classname = "GatewayProto";
|
||||||
option csharp_namespace = "Chirpstack.Gateway";
|
option csharp_namespace = "Chirpstack.Gateway";
|
||||||
|
option php_namespace = "Chirpstack\\Gateway";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Gateway";
|
||||||
|
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.integration";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "IntegrationProto";
|
option java_outer_classname = "IntegrationProto";
|
||||||
option csharp_namespace = "Chirpstack.Integration";
|
option csharp_namespace = "Chirpstack.Integration";
|
||||||
|
option php_namespace = "Chirpstack\\Integration";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Integration";
|
||||||
|
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
import "gw/gw.proto";
|
import "gw/gw.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.stream";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "ApiRequestProto";
|
option java_outer_classname = "ApiRequestProto";
|
||||||
option csharp_namespace = "Chirpstack.Stream";
|
option csharp_namespace = "Chirpstack.Stream";
|
||||||
|
option php_namespace = "Chirpstack\\Stream";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Stream";
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
|
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.stream";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "BackendInterfacesProto";
|
option java_outer_classname = "BackendInterfacesProto";
|
||||||
option csharp_namespace = "Chirpstack.Stream";
|
option csharp_namespace = "Chirpstack.Stream";
|
||||||
|
option php_namespace = "Chirpstack\\Stream";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Stream";
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
2
api/rust/proto/chirpstack/stream/frame.proto
vendored
2
api/rust/proto/chirpstack/stream/frame.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.stream";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "FrameProto";
|
option java_outer_classname = "FrameProto";
|
||||||
option csharp_namespace = "Chirpstack.Stream";
|
option csharp_namespace = "Chirpstack.Stream";
|
||||||
|
option php_namespace = "Chirpstack\\Stream";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Stream";
|
||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
|
2
api/rust/proto/chirpstack/stream/meta.proto
vendored
2
api/rust/proto/chirpstack/stream/meta.proto
vendored
@ -7,6 +7,8 @@ option java_package = "io.chirpstack.api.stream";
|
|||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
option java_outer_classname = "MetaProto";
|
option java_outer_classname = "MetaProto";
|
||||||
option csharp_namespace = "Chirpstack.Stream";
|
option csharp_namespace = "Chirpstack.Stream";
|
||||||
|
option php_namespace = "Chirpstack\\Stream";
|
||||||
|
option php_metadata_namespace = "GPBMetadata\\Chirpstack\\Stream";
|
||||||
|
|
||||||
import "common/common.proto";
|
import "common/common.proto";
|
||||||
import "gw/gw.proto";
|
import "gw/gw.proto";
|
||||||
|
@ -7,6 +7,7 @@ create table relay_gateway (
|
|||||||
name varchar(100) not null,
|
name varchar(100) not null,
|
||||||
description text not null,
|
description text not null,
|
||||||
stats_interval_secs integer not null,
|
stats_interval_secs integer not null,
|
||||||
|
region_config_id varchar(100) not null,
|
||||||
|
|
||||||
primary key (tenant_id, relay_id)
|
primary key (tenant_id, relay_id)
|
||||||
);
|
);
|
||||||
|
@ -815,6 +815,7 @@ impl GatewayService for Gateway {
|
|||||||
name: relay.name,
|
name: relay.name,
|
||||||
description: relay.description,
|
description: relay.description,
|
||||||
stats_interval: relay.stats_interval_secs as u32,
|
stats_interval: relay.stats_interval_secs as u32,
|
||||||
|
region_config_id: relay.region_config_id.to_string(),
|
||||||
}),
|
}),
|
||||||
created_at: Some(helpers::datetime_to_prost_timestamp(&relay.created_at)),
|
created_at: Some(helpers::datetime_to_prost_timestamp(&relay.created_at)),
|
||||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&relay.updated_at)),
|
updated_at: Some(helpers::datetime_to_prost_timestamp(&relay.updated_at)),
|
||||||
@ -859,6 +860,7 @@ impl GatewayService for Gateway {
|
|||||||
name: req_relay.name.clone(),
|
name: req_relay.name.clone(),
|
||||||
description: req_relay.description.clone(),
|
description: req_relay.description.clone(),
|
||||||
stats_interval_secs: req_relay.stats_interval as i32,
|
stats_interval_secs: req_relay.stats_interval as i32,
|
||||||
|
region_config_id: req_relay.region_config_id.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -962,6 +964,7 @@ impl GatewayService for Gateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
region_config_id: r.region_config_id.to_string(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
});
|
});
|
||||||
@ -1358,6 +1361,7 @@ pub mod test {
|
|||||||
relay_id: gateway::RelayId::from_be_bytes([1, 2, 3, 4]),
|
relay_id: gateway::RelayId::from_be_bytes([1, 2, 3, 4]),
|
||||||
name: "test-relay".into(),
|
name: "test-relay".into(),
|
||||||
description: "test relay".into(),
|
description: "test relay".into(),
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -1378,6 +1382,7 @@ pub mod test {
|
|||||||
name: "test-relay".into(),
|
name: "test-relay".into(),
|
||||||
description: "test relay".into(),
|
description: "test relay".into(),
|
||||||
stats_interval: 900,
|
stats_interval: 900,
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
}),
|
}),
|
||||||
get_relay_resp.get_ref().relay_gateway
|
get_relay_resp.get_ref().relay_gateway
|
||||||
);
|
);
|
||||||
@ -1390,6 +1395,7 @@ pub mod test {
|
|||||||
name: "updated-relay".into(),
|
name: "updated-relay".into(),
|
||||||
description: "updated relay".into(),
|
description: "updated relay".into(),
|
||||||
stats_interval: 600,
|
stats_interval: 600,
|
||||||
|
region_config_id: "us915_0".into(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
let mut up_relay_req = Request::new(up_relay_req);
|
let mut up_relay_req = Request::new(up_relay_req);
|
||||||
@ -1411,6 +1417,7 @@ pub mod test {
|
|||||||
name: "updated-relay".into(),
|
name: "updated-relay".into(),
|
||||||
description: "updated relay".into(),
|
description: "updated relay".into(),
|
||||||
stats_interval: 600,
|
stats_interval: 600,
|
||||||
|
region_config_id: "us915_0".into(),
|
||||||
}),
|
}),
|
||||||
get_relay_resp.get_ref().relay_gateway
|
get_relay_resp.get_ref().relay_gateway
|
||||||
);
|
);
|
||||||
|
@ -131,6 +131,7 @@ pub struct RelayGateway {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub stats_interval_secs: i32,
|
pub stats_interval_secs: i32,
|
||||||
|
pub region_config_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RelayGateway {
|
impl Default for RelayGateway {
|
||||||
@ -146,6 +147,7 @@ impl Default for RelayGateway {
|
|||||||
name: "".into(),
|
name: "".into(),
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
stats_interval_secs: 900,
|
stats_interval_secs: 900,
|
||||||
|
region_config_id: "".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,6 +167,7 @@ pub struct RelayGatewayListItem {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub stats_interval_secs: i32,
|
pub stats_interval_secs: i32,
|
||||||
|
pub region_config_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
|
pub async fn create(gw: Gateway) -> Result<Gateway, Error> {
|
||||||
@ -400,6 +403,7 @@ pub async fn update_relay_gateway(relay: RelayGateway) -> Result<RelayGateway, E
|
|||||||
relay_gateway::name.eq(&relay.name),
|
relay_gateway::name.eq(&relay.name),
|
||||||
relay_gateway::description.eq(&relay.description),
|
relay_gateway::description.eq(&relay.description),
|
||||||
relay_gateway::stats_interval_secs.eq(&relay.stats_interval_secs),
|
relay_gateway::stats_interval_secs.eq(&relay.stats_interval_secs),
|
||||||
|
relay_gateway::region_config_id.eq(&relay.region_config_id),
|
||||||
))
|
))
|
||||||
.get_result(&mut get_async_db_conn().await?)
|
.get_result(&mut get_async_db_conn().await?)
|
||||||
.await
|
.await
|
||||||
@ -450,6 +454,7 @@ pub async fn list_relay_gateways(
|
|||||||
relay_gateway::name,
|
relay_gateway::name,
|
||||||
relay_gateway::description,
|
relay_gateway::description,
|
||||||
relay_gateway::stats_interval_secs,
|
relay_gateway::stats_interval_secs,
|
||||||
|
relay_gateway::region_config_id,
|
||||||
))
|
))
|
||||||
.into_boxed();
|
.into_boxed();
|
||||||
|
|
||||||
@ -662,6 +667,7 @@ pub mod test {
|
|||||||
tenant_id: gw.tenant_id,
|
tenant_id: gw.tenant_id,
|
||||||
name: "test-relay".into(),
|
name: "test-relay".into(),
|
||||||
description: "test relay".into(),
|
description: "test relay".into(),
|
||||||
|
region_config_id: "eu868".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@ -675,6 +681,7 @@ pub mod test {
|
|||||||
|
|
||||||
// update
|
// update
|
||||||
relay.name = "updated-relay".into();
|
relay.name = "updated-relay".into();
|
||||||
|
relay.region_config_id = "us915_0".into();
|
||||||
relay = update_relay_gateway(relay).await.unwrap();
|
relay = update_relay_gateway(relay).await.unwrap();
|
||||||
let relay_get = get_relay_gateway(relay.tenant_id, relay.relay_id)
|
let relay_get = get_relay_gateway(relay.tenant_id, relay.relay_id)
|
||||||
.await
|
.await
|
||||||
|
@ -299,6 +299,8 @@ diesel::table! {
|
|||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
description -> Text,
|
description -> Text,
|
||||||
stats_interval_secs -> Int4,
|
stats_interval_secs -> Int4,
|
||||||
|
#[max_length = 100]
|
||||||
|
region_config_id -> Varchar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,11 @@ impl MeshStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
v.last_seen_at = Some(ts);
|
v.last_seen_at = Some(ts);
|
||||||
|
v.region_config_id = border_gw
|
||||||
|
.properties
|
||||||
|
.get("region_config_id")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
gateway::update_relay_gateway(v).await?;
|
gateway::update_relay_gateway(v).await?;
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
AppstoreOutlined,
|
AppstoreOutlined,
|
||||||
CompassOutlined,
|
CompassOutlined,
|
||||||
|
RadarChartOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -130,6 +131,11 @@ function SideMenu() {
|
|||||||
setSelectedKey("tenant-gateways");
|
setSelectedKey("tenant-gateways");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tenant gateway-mesh
|
||||||
|
if (/\/tenants\/[\w-]{36}\/gateways\/mesh.*/g.exec(path)) {
|
||||||
|
setSelectedKey("tenant-gateways-mesh");
|
||||||
|
}
|
||||||
|
|
||||||
// tenant applications
|
// tenant applications
|
||||||
if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) {
|
if (/\/tenants\/[\w-]{36}\/applications.*/g.exec(path)) {
|
||||||
setSelectedKey("tenant-applications");
|
setSelectedKey("tenant-applications");
|
||||||
@ -242,6 +248,11 @@ function SideMenu() {
|
|||||||
icon: <WifiOutlined />,
|
icon: <WifiOutlined />,
|
||||||
label: <Link to={`/tenants/${tenantId}/gateways`}>Gateways</Link>,
|
label: <Link to={`/tenants/${tenantId}/gateways`}>Gateways</Link>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "tenant-gateways-mesh",
|
||||||
|
icon: <RadarChartOutlined />,
|
||||||
|
label: <Link to={`/tenants/${tenantId}/gateways/mesh/relays`}>Gateway Mesh</Link>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "tenant-applications",
|
key: "tenant-applications",
|
||||||
icon: <AppstoreOutlined />,
|
icon: <AppstoreOutlined />,
|
||||||
|
202
ui/src/components/RelayIdInput.tsx
Normal file
202
ui/src/components/RelayIdInput.tsx
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { notification, Input, Select, Button, Space, Form, Dropdown, Menu } from "antd";
|
||||||
|
import { ReloadOutlined, CopyOutlined } from "@ant-design/icons";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
required?: boolean;
|
||||||
|
value?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayIdInput(props: IProps) {
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const [byteOrder, setByteOrder] = useState<string>("msb");
|
||||||
|
const [value, setValue] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.value) {
|
||||||
|
setValue(props.value);
|
||||||
|
}
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
|
const updateField = (v: string) => {
|
||||||
|
if (byteOrder === "lsb") {
|
||||||
|
const bytes = v.match(/[A-Fa-f0-9]{2}/g) || [];
|
||||||
|
v = bytes.reverse().join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setFieldsValue({
|
||||||
|
[props.name]: v,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
let v = e.target.value;
|
||||||
|
const match = v.match(/[A-Fa-f0-9]/g);
|
||||||
|
|
||||||
|
let value = "";
|
||||||
|
if (match) {
|
||||||
|
if (match.length > 8) {
|
||||||
|
value = match.slice(0, 8).join("");
|
||||||
|
} else {
|
||||||
|
value = match.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(value);
|
||||||
|
updateField(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onByteOrderSelect = (v: string) => {
|
||||||
|
if (v === byteOrder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setByteOrder(v);
|
||||||
|
|
||||||
|
const current = value;
|
||||||
|
const bytes = current.match(/[A-Fa-f0-9]{2}/g) || [];
|
||||||
|
const vv = bytes.reverse().join("");
|
||||||
|
|
||||||
|
setValue(vv);
|
||||||
|
updateField(vv);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateRandom = () => {
|
||||||
|
let cryptoObj = window.crypto || window.Crypto;
|
||||||
|
let b = new Uint8Array(4);
|
||||||
|
cryptoObj.getRandomValues(b);
|
||||||
|
|
||||||
|
let key = Buffer.from(b).toString("hex");
|
||||||
|
setValue(key);
|
||||||
|
updateField(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
const bytes = value.match(/[A-Fa-f0-9]{2}/g);
|
||||||
|
|
||||||
|
if (bytes !== null && navigator.clipboard !== undefined) {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(bytes.join("").toUpperCase())
|
||||||
|
.then(() => {
|
||||||
|
notification.success({
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: e,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: "Clipboard functionality is not available.",
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboardHexArray = () => {
|
||||||
|
const bytes = value.match(/[A-Fa-f0-9]{2}/g);
|
||||||
|
|
||||||
|
if (bytes !== null && navigator.clipboard !== undefined) {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(
|
||||||
|
bytes
|
||||||
|
.join(", ")
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(/[A-Fa-f0-9]{2}/g, "0x$&"),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
notification.success({
|
||||||
|
message: "Copied to clipboard",
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
notification.error({
|
||||||
|
message: "Error",
|
||||||
|
description: e,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyMenu = (
|
||||||
|
<Menu
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
label: (
|
||||||
|
<Button type="text" onClick={copyToClipboard}>
|
||||||
|
HEX string
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2",
|
||||||
|
label: (
|
||||||
|
<Button type="text" onClick={copyToClipboardHexArray}>
|
||||||
|
HEX array
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const addon = (
|
||||||
|
<Space size="large">
|
||||||
|
<Select value={byteOrder} onChange={onByteOrderSelect}>
|
||||||
|
<Select.Option value="msb">MSB</Select.Option>
|
||||||
|
<Select.Option value="lsb">LSB</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Button type="text" size="small" onClick={generateRandom}>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</Button>
|
||||||
|
<Dropdown overlay={copyMenu}>
|
||||||
|
<Button type="text" size="small">
|
||||||
|
<CopyOutlined />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: props.required,
|
||||||
|
message: `Please enter a valid ${props.label}`,
|
||||||
|
pattern: new RegExp(/[A-Fa-f0-9]{8}/g),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
label={props.label}
|
||||||
|
name={props.name}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
>
|
||||||
|
<Input hidden />
|
||||||
|
<Input
|
||||||
|
id={`${props.name}Render`}
|
||||||
|
onChange={onChange}
|
||||||
|
addonAfter={!props.disabled && addon}
|
||||||
|
className="input-code"
|
||||||
|
value={value}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelayIdInput;
|
@ -15,6 +15,12 @@ import {
|
|||||||
GetGatewayDutyCycleMetricsResponse,
|
GetGatewayDutyCycleMetricsResponse,
|
||||||
GenerateGatewayClientCertificateRequest,
|
GenerateGatewayClientCertificateRequest,
|
||||||
GenerateGatewayClientCertificateResponse,
|
GenerateGatewayClientCertificateResponse,
|
||||||
|
GetRelayGatewayRequest,
|
||||||
|
GetRelayGatewayResponse,
|
||||||
|
ListRelayGatewaysRequest,
|
||||||
|
ListRelayGatewaysResponse,
|
||||||
|
UpdateRelayGatewayRequest,
|
||||||
|
DeleteRelayGatewayRequest,
|
||||||
} from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
} from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
||||||
|
|
||||||
import SessionStore from "./SessionStore";
|
import SessionStore from "./SessionStore";
|
||||||
@ -136,6 +142,60 @@ class GatewayStore extends EventEmitter {
|
|||||||
callbackFunc(resp);
|
callbackFunc(resp);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getRelayGateway = (req: GetRelayGatewayRequest, callbackFunc: (resp: GetRelayGatewayResponse) => void) => {
|
||||||
|
this.client.getRelayGateway(req, SessionStore.getMetadata(), (err, resp) => {
|
||||||
|
if (err !== null) {
|
||||||
|
HandleError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackFunc(resp);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
listRelayGateways = (req: ListRelayGatewaysRequest, callbackFunc: (resp: ListRelayGatewaysResponse) => void) => {
|
||||||
|
this.client.listRelayGateways(req, SessionStore.getMetadata(), (err, resp) => {
|
||||||
|
if (err !== null) {
|
||||||
|
HandleError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackFunc(resp);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRelayGateway = (req: UpdateRelayGatewayRequest, callbackFunc: () => void) => {
|
||||||
|
this.client.updateRelayGateway(req, SessionStore.getMetadata(), (err) => {
|
||||||
|
if (err !== null) {
|
||||||
|
HandleError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.success({
|
||||||
|
message: "Relay Gateway updated",
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
callbackFunc();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRelayGateway = (req: DeleteRelayGatewayRequest, callbackFunc: () => void) => {
|
||||||
|
this.client.deleteRelayGateway(req, SessionStore.getMetadata(), (err) => {
|
||||||
|
if (err !== null) {
|
||||||
|
HandleError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.success({
|
||||||
|
message: "Relay Gateway deleted",
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
callbackFunc();
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const gatewayStore = new GatewayStore();
|
const gatewayStore = new GatewayStore();
|
||||||
|
34
ui/src/views/gateways/mesh/EditRelayGateway.tsx
Normal file
34
ui/src/views/gateways/mesh/EditRelayGateway.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { RelayGateway, UpdateRelayGatewayRequest } from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
||||||
|
|
||||||
|
import RelayGatewayForm from "./RelayGatewayForm";
|
||||||
|
import GatewayStore from "../../../stores/GatewayStore";
|
||||||
|
import SessionStore from "../../../stores/SessionStore";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
relayGateway: RelayGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditRelayGateway(props: IProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onFinish = (obj: RelayGateway) => {
|
||||||
|
let req = new UpdateRelayGatewayRequest();
|
||||||
|
req.setRelayGateway(obj);
|
||||||
|
|
||||||
|
GatewayStore.updateRelayGateway(req, () => {
|
||||||
|
navigate(`/tenants/${obj.getTenantId()}/gateways/mesh/relays`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabled = !(
|
||||||
|
SessionStore.isAdmin() ||
|
||||||
|
SessionStore.isTenantAdmin(props.relayGateway.getTenantId()) ||
|
||||||
|
SessionStore.isTenantGatewayAdmin(props.relayGateway.getTenantId())
|
||||||
|
);
|
||||||
|
|
||||||
|
return <RelayGatewayForm initialValues={props.relayGateway} onFinish={onFinish} disabled={disabled} update />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditRelayGateway;
|
119
ui/src/views/gateways/mesh/ListRelayGateways.tsx
Normal file
119
ui/src/views/gateways/mesh/ListRelayGateways.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import moment from "moment";
|
||||||
|
import { Space, Breadcrumb, Badge } from "antd";
|
||||||
|
import { ColumnsType } from "antd/es/table";
|
||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ListRelayGatewaysRequest,
|
||||||
|
ListRelayGatewaysResponse,
|
||||||
|
RelayGatewayListItem,
|
||||||
|
GatewayState,
|
||||||
|
} from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
||||||
|
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
|
||||||
|
|
||||||
|
import DataTable, { GetPageCallbackFunc } from "../../../components/DataTable";
|
||||||
|
import GatewayStore from "../../../stores/GatewayStore";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
tenant: Tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListRelayGateways(props: IProps) {
|
||||||
|
const columns: ColumnsType<RelayGatewayListItem.AsObject> = [
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
dataIndex: "state",
|
||||||
|
key: "state",
|
||||||
|
width: 150,
|
||||||
|
render: (text, record) => {
|
||||||
|
if (record.state === GatewayState.NEVER_SEEN) {
|
||||||
|
return <Badge status="warning" text="Never seen" />;
|
||||||
|
} else if (record.state === GatewayState.OFFLINE) {
|
||||||
|
return <Badge status="error" text="Offline" />;
|
||||||
|
} else if (record.state === GatewayState.ONLINE) {
|
||||||
|
return <Badge status="success" text="Online" />;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Last seen",
|
||||||
|
dataIndex: "lastSeenAt",
|
||||||
|
key: "lastSeenAt",
|
||||||
|
width: 250,
|
||||||
|
render: (text, record) => {
|
||||||
|
if (record.lastSeenAt !== undefined) {
|
||||||
|
let ts = new Date(0);
|
||||||
|
ts.setUTCSeconds(record.lastSeenAt.seconds);
|
||||||
|
return moment(ts).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Relay ID",
|
||||||
|
dataIndex: "relayId",
|
||||||
|
key: "relayId",
|
||||||
|
width: 250,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={`/tenants/${props.tenant.getId()}/gateways/mesh/relays/${record.relayId}/edit`}>{text}</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Region ID",
|
||||||
|
dataIndex: "regionConfigId",
|
||||||
|
key: "regionConfigId",
|
||||||
|
width: 150,
|
||||||
|
render: (text) => {
|
||||||
|
return <Link to={`/regions/${text}`}>{text}</Link>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getPage = (limit: number, offset: number, callbackFunc: GetPageCallbackFunc) => {
|
||||||
|
let req = new ListRelayGatewaysRequest();
|
||||||
|
req.setTenantId(props.tenant.getId());
|
||||||
|
req.setLimit(limit);
|
||||||
|
req.setOffset(offset);
|
||||||
|
|
||||||
|
GatewayStore.listRelayGateways(req, (resp: ListRelayGatewaysResponse) => {
|
||||||
|
const obj = resp.toObject();
|
||||||
|
callbackFunc(obj.totalCount, obj.resultList);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||||
|
<PageHeader
|
||||||
|
title="Relay Gateways"
|
||||||
|
breadcrumbRender={() => (
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>Tenants</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>
|
||||||
|
<Link to={`/tenants/${props.tenant.getId()}`}>{props.tenant.getName()}</Link>
|
||||||
|
</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>Gateway Mesh</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>Relay Gateways</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DataTable columns={columns} getPage={getPage} rowKey="relayId" />
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ListRelayGateways;
|
76
ui/src/views/gateways/mesh/RelayGatewayForm.tsx
Normal file
76
ui/src/views/gateways/mesh/RelayGatewayForm.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Form, Input, InputNumber, Row, Col, Button } from "antd";
|
||||||
|
|
||||||
|
import { RelayGateway } from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
||||||
|
|
||||||
|
import { onFinishFailed } from "../../helpers";
|
||||||
|
import RelayIdInput from "../../../components/RelayIdInput";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
initialValues: RelayGateway;
|
||||||
|
onFinish: (obj: RelayGateway) => void;
|
||||||
|
update?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayGatewayForm(props: IProps) {
|
||||||
|
const onFinish = (values: RelayGateway.AsObject) => {
|
||||||
|
const v = Object.assign(props.initialValues.toObject(), values);
|
||||||
|
let relay = new RelayGateway();
|
||||||
|
|
||||||
|
relay.setTenantId(v.tenantId);
|
||||||
|
relay.setRelayId(v.relayId);
|
||||||
|
relay.setName(v.name);
|
||||||
|
relay.setDescription(v.description);
|
||||||
|
relay.setStatsInterval(v.statsInterval);
|
||||||
|
relay.setRegionConfigId(v.regionConfigId);
|
||||||
|
|
||||||
|
props.onFinish(relay);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={props.initialValues.toObject()}
|
||||||
|
onFinish={onFinish}
|
||||||
|
onFinishFailed={onFinishFailed}
|
||||||
|
>
|
||||||
|
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
|
||||||
|
<Input disabled={props.disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Description" name="description">
|
||||||
|
<Input.TextArea disabled={props.disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<RelayIdInput
|
||||||
|
label="Relay ID (4 bytes)"
|
||||||
|
name="relayId"
|
||||||
|
value={props.initialValues.getRelayId()}
|
||||||
|
disabled={props.update || props.disabled}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Stats interval (secs)"
|
||||||
|
tooltip="The expected interval in seconds in which the relay gateway sends its statistics"
|
||||||
|
name="statsInterval"
|
||||||
|
rules={[{ required: true, message: "Please enter a stats interval!" }]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} disabled={props.disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelayGatewayForm;
|
108
ui/src/views/gateways/mesh/RelayGatewayLayout.tsx
Normal file
108
ui/src/views/gateways/mesh/RelayGatewayLayout.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { Route, Routes, Link, useParams, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Space, Breadcrumb, Card, Button } from "antd";
|
||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
|
|
||||||
|
import { Tenant } from "@chirpstack/chirpstack-api-grpc-web/api/tenant_pb";
|
||||||
|
import {
|
||||||
|
RelayGateway,
|
||||||
|
GetRelayGatewayRequest,
|
||||||
|
GetRelayGatewayResponse,
|
||||||
|
DeleteRelayGatewayRequest,
|
||||||
|
} from "@chirpstack/chirpstack-api-grpc-web/api/gateway_pb";
|
||||||
|
|
||||||
|
import Admin from "../../../components/Admin";
|
||||||
|
import SessionStore from "../../../stores/SessionStore";
|
||||||
|
import GatewayStore from "../../../stores/GatewayStore";
|
||||||
|
import DeleteConfirm from "../../../components/DeleteConfirm";
|
||||||
|
|
||||||
|
import EditRelayGateway from "./EditRelayGateway";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
tenant: Tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayGatewayLayout(props: IProps) {
|
||||||
|
const { relayId } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [relayGateway, setRelayGateway] = useState<RelayGateway | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let req = new GetRelayGatewayRequest();
|
||||||
|
req.setTenantId(props.tenant.getId());
|
||||||
|
req.setRelayId(relayId!);
|
||||||
|
|
||||||
|
GatewayStore.getRelayGateway(req, (resp: GetRelayGatewayResponse) => {
|
||||||
|
setRelayGateway(resp.getRelayGateway());
|
||||||
|
});
|
||||||
|
}, [props, relayId]);
|
||||||
|
|
||||||
|
const deleteRelayGateway = () => {
|
||||||
|
let req = new DeleteRelayGatewayRequest();
|
||||||
|
req.setTenantId(props.tenant.getId());
|
||||||
|
req.setRelayId(relayId!);
|
||||||
|
|
||||||
|
GatewayStore.deleteRelayGateway(req, () => {
|
||||||
|
navigate(`/tenants/${props.tenant.getId()}/gateways/mesh/relays`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relayGateway) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isGatewayAdmin =
|
||||||
|
SessionStore.isAdmin() ||
|
||||||
|
SessionStore.isTenantAdmin(props.tenant.getId()) ||
|
||||||
|
SessionStore.isTenantGatewayAdmin(props.tenant.getId());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||||
|
<PageHeader
|
||||||
|
breadcrumbRender={() => (
|
||||||
|
<Breadcrumb>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>Tenants</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>
|
||||||
|
<Link to={`/tenants/${props.tenant.getId()}`}>{props.tenant.getName()}</Link>
|
||||||
|
</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>Gateway Mesh</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>
|
||||||
|
<Link to={`/tenants/${props.tenant.getId()}/gateways/mesh/relays`}>Relay Gateways</Link>
|
||||||
|
</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<span>{relayGateway.getName()}</span>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
)}
|
||||||
|
title={relayGateway.getName()}
|
||||||
|
subTitle={`relay id: ${relayGateway.getRelayId()}`}
|
||||||
|
extra={[
|
||||||
|
<Admin tenantId={props.tenant.getId()} isGatewayAdmin>
|
||||||
|
<DeleteConfirm confirm={relayGateway.getName()} typ="relay gateway" onConfirm={deleteRelayGateway}>
|
||||||
|
<Button danger type="primary">
|
||||||
|
Delete Relay Gateway
|
||||||
|
</Button>
|
||||||
|
</DeleteConfirm>
|
||||||
|
</Admin>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Card>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/edit" element={<EditRelayGateway relayGateway={relayGateway} />} />
|
||||||
|
</Routes>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RelayGatewayLayout;
|
@ -19,8 +19,10 @@ import CreateDeviceProfile from "../device-profiles/CreateDeviceProfile";
|
|||||||
import EditDeviceProfile from "../device-profiles/EditDeviceProfile";
|
import EditDeviceProfile from "../device-profiles/EditDeviceProfile";
|
||||||
|
|
||||||
import ListGateways from "../gateways/ListGateways";
|
import ListGateways from "../gateways/ListGateways";
|
||||||
|
import ListRelayGateways from "../gateways/mesh/ListRelayGateways";
|
||||||
import CreateGateway from "../gateways/CreateGateway";
|
import CreateGateway from "../gateways/CreateGateway";
|
||||||
import GatewayLayout from "../gateways/GatewayLayout";
|
import GatewayLayout from "../gateways/GatewayLayout";
|
||||||
|
import RelayGatewayLayout from "../gateways/mesh/RelayGatewayLayout";
|
||||||
|
|
||||||
import ListApplications from "../applications/ListApplications";
|
import ListApplications from "../applications/ListApplications";
|
||||||
import CreateApplication from "../applications/CreateApplication";
|
import CreateApplication from "../applications/CreateApplication";
|
||||||
@ -65,6 +67,8 @@ function TenantLoader() {
|
|||||||
|
|
||||||
<Route path="/gateways" element={<ListGateways tenant={tenant} />} />
|
<Route path="/gateways" element={<ListGateways tenant={tenant} />} />
|
||||||
<Route path="/gateways/create" element={<CreateGateway tenant={tenant} />} />
|
<Route path="/gateways/create" element={<CreateGateway tenant={tenant} />} />
|
||||||
|
<Route path="/gateways/mesh/relays" element={<ListRelayGateways tenant={tenant} />} />
|
||||||
|
<Route path="/gateways/mesh/relays/:relayId/*" element={<RelayGatewayLayout tenant={tenant} />} />
|
||||||
<Route path="/gateways/:gatewayId/*" element={<GatewayLayout tenant={tenant} />} />
|
<Route path="/gateways/:gatewayId/*" element={<GatewayLayout tenant={tenant} />} />
|
||||||
|
|
||||||
<Route path="/applications" element={<ListApplications tenant={tenant} />} />
|
<Route path="/applications" element={<ListApplications tenant={tenant} />} />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user