mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-06-16 22:38:23 +00:00
Add tags to tenants and applications.
Note that the integration events will contain the application + device-profile + device tags. Integration events will NOT contain the tenant tags. Most likely tenant tags will be used to store information about the tenant, data that is unrelated to the integration events. Fixes #211.
This commit is contained in:
6
api/proto/api/application.proto
vendored
6
api/proto/api/application.proto
vendored
@ -459,6 +459,12 @@ message Application {
|
|||||||
|
|
||||||
// Tenant ID (UUID).
|
// Tenant ID (UUID).
|
||||||
string tenant_id = 4;
|
string tenant_id = 4;
|
||||||
|
|
||||||
|
// Tags (user defined).
|
||||||
|
// These tags can be used to add additional information to the application.
|
||||||
|
// These tags are exposed in all the integration events of devices under
|
||||||
|
// this application.
|
||||||
|
map<string, string> tags = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ApplicationListItem {
|
message ApplicationListItem {
|
||||||
|
4
api/proto/api/device.proto
vendored
4
api/proto/api/device.proto
vendored
@ -208,8 +208,8 @@ message Device {
|
|||||||
map<string, string> variables = 8;
|
map<string, string> variables = 8;
|
||||||
|
|
||||||
// Tags (user defined).
|
// Tags (user defined).
|
||||||
// These tags are exposed in the event payloads or to integration. Tags are
|
// These tags can be used to add additional information to the device.
|
||||||
// intended for aggregation and filtering.
|
// These tags are exposed in all the integration events.
|
||||||
map<string, string> tags = 9;
|
map<string, string> tags = 9;
|
||||||
|
|
||||||
// JoinEUI (optional, EUI64).
|
// JoinEUI (optional, EUI64).
|
||||||
|
25
api/proto/api/device_profile.proto
vendored
25
api/proto/api/device_profile.proto
vendored
@ -28,7 +28,8 @@ enum MeasurementKind {
|
|||||||
// Unknown (in which case it is not tracked).
|
// Unknown (in which case it is not tracked).
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
|
|
||||||
// Incrementing counters that never decrease (these are not reset on each reading).
|
// Incrementing counters that never decrease (these are not reset on each
|
||||||
|
// reading).
|
||||||
COUNTER = 1;
|
COUNTER = 1;
|
||||||
|
|
||||||
// Counters that do get reset upon reading.
|
// Counters that do get reset upon reading.
|
||||||
@ -79,7 +80,6 @@ enum SecondChAckOffset {
|
|||||||
|
|
||||||
// 3200 kHz.
|
// 3200 kHz.
|
||||||
KHZ_3200 = 5;
|
KHZ_3200 = 5;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RelayModeActivation {
|
enum RelayModeActivation {
|
||||||
@ -96,7 +96,8 @@ enum RelayModeActivation {
|
|||||||
END_DEVICE_CONTROLLED = 3;
|
END_DEVICE_CONTROLLED = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceProfileService is the service providing API methods for managing device-profiles.
|
// DeviceProfileService is the service providing API methods for managing
|
||||||
|
// device-profiles.
|
||||||
service DeviceProfileService {
|
service DeviceProfileService {
|
||||||
// Create the given device-profile.
|
// Create the given device-profile.
|
||||||
rpc Create(CreateDeviceProfileRequest) returns (CreateDeviceProfileResponse) {
|
rpc Create(CreateDeviceProfileRequest) returns (CreateDeviceProfileResponse) {
|
||||||
@ -136,7 +137,8 @@ service DeviceProfileService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List available ADR algorithms.
|
// List available ADR algorithms.
|
||||||
rpc ListAdrAlgorithms(google.protobuf.Empty) returns (ListDeviceProfileAdrAlgorithmsResponse) {
|
rpc ListAdrAlgorithms(google.protobuf.Empty)
|
||||||
|
returns (ListDeviceProfileAdrAlgorithmsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get : "/api/device-profiles/adr-algorithms"
|
get : "/api/device-profiles/adr-algorithms"
|
||||||
};
|
};
|
||||||
@ -185,8 +187,8 @@ message DeviceProfile {
|
|||||||
uint32 uplink_interval = 11;
|
uint32 uplink_interval = 11;
|
||||||
|
|
||||||
// Device-status request interval (times / day).
|
// Device-status request interval (times / day).
|
||||||
// This defines the times per day that ChirpStack will request the device-status
|
// This defines the times per day that ChirpStack will request the
|
||||||
// from the device.
|
// device-status from the device.
|
||||||
uint32 device_status_req_interval = 12;
|
uint32 device_status_req_interval = 12;
|
||||||
|
|
||||||
// Supports OTAA.
|
// Supports OTAA.
|
||||||
@ -199,7 +201,8 @@ message DeviceProfile {
|
|||||||
bool supports_class_c = 15;
|
bool supports_class_c = 15;
|
||||||
|
|
||||||
// Class-B timeout (seconds).
|
// Class-B timeout (seconds).
|
||||||
// This is the maximum time ChirpStack will wait to receive an acknowledgement from the device (if requested).
|
// This is the maximum time ChirpStack will wait to receive an acknowledgement
|
||||||
|
// from the device (if requested).
|
||||||
uint32 class_b_timeout = 16;
|
uint32 class_b_timeout = 16;
|
||||||
|
|
||||||
// Class-B ping-slots per beacon period.
|
// Class-B ping-slots per beacon period.
|
||||||
@ -215,7 +218,8 @@ message DeviceProfile {
|
|||||||
uint32 class_b_ping_slot_freq = 19;
|
uint32 class_b_ping_slot_freq = 19;
|
||||||
|
|
||||||
// Class-C timeout (seconds).
|
// Class-C timeout (seconds).
|
||||||
// This is the maximum time ChirpStack will wait to receive an acknowledgement from the device (if requested).
|
// This is the maximum time ChirpStack will wait to receive an acknowledgement
|
||||||
|
// from the device (if requested).
|
||||||
uint32 class_c_timeout = 20;
|
uint32 class_c_timeout = 20;
|
||||||
|
|
||||||
// RX1 delay (for ABP).
|
// RX1 delay (for ABP).
|
||||||
@ -230,7 +234,10 @@ message DeviceProfile {
|
|||||||
// RX2 frequency (for ABP, Hz).
|
// RX2 frequency (for ABP, Hz).
|
||||||
uint32 abp_rx2_freq = 24;
|
uint32 abp_rx2_freq = 24;
|
||||||
|
|
||||||
// User defined tags.
|
// Tags (user defined).
|
||||||
|
// These tags can be used to add additional information the the
|
||||||
|
// device-profile. These tags are exposed in all the integration events of
|
||||||
|
// devices using this device-profile.
|
||||||
map<string, string> tags = 25;
|
map<string, string> tags = 25;
|
||||||
|
|
||||||
// Measurements.
|
// Measurements.
|
||||||
|
5
api/proto/api/tenant.proto
vendored
5
api/proto/api/tenant.proto
vendored
@ -122,6 +122,11 @@ message Tenant {
|
|||||||
// do want to share uplinks with other tenants (private_gateways_up=false),
|
// do want to share uplinks with other tenants (private_gateways_up=false),
|
||||||
// but you want to prevent other tenants from using gateway airtime.
|
// but you want to prevent other tenants from using gateway airtime.
|
||||||
bool private_gateways_down = 8;
|
bool private_gateways_down = 8;
|
||||||
|
|
||||||
|
// Tags (user defined).
|
||||||
|
// These tags can be used to add additional information to the tenant. These
|
||||||
|
// tags are NOT exposed in the integration events.
|
||||||
|
map<string, string> tags = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TenantListItem {
|
message TenantListItem {
|
||||||
|
@ -459,6 +459,12 @@ message Application {
|
|||||||
|
|
||||||
// Tenant ID (UUID).
|
// Tenant ID (UUID).
|
||||||
string tenant_id = 4;
|
string tenant_id = 4;
|
||||||
|
|
||||||
|
// Tags (user defined).
|
||||||
|
// These tags can be used to add additional information to the application.
|
||||||
|
// These tags are exposed in all the integration events of devices under
|
||||||
|
// this application.
|
||||||
|
map<string, string> tags = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ApplicationListItem {
|
message ApplicationListItem {
|
||||||
|
4
api/rust/proto/chirpstack/api/device.proto
vendored
4
api/rust/proto/chirpstack/api/device.proto
vendored
@ -208,8 +208,8 @@ message Device {
|
|||||||
map<string, string> variables = 8;
|
map<string, string> variables = 8;
|
||||||
|
|
||||||
// Tags (user defined).
|
// Tags (user defined).
|
||||||
// These tags are exposed in the event payloads or to integration. Tags are
|
// These tags can be used to add additional information to the device.
|
||||||
// intended for aggregation and filtering.
|
// These tags are exposed in all the integration events.
|
||||||
map<string, string> tags = 9;
|
map<string, string> tags = 9;
|
||||||
|
|
||||||
// JoinEUI (optional, EUI64).
|
// JoinEUI (optional, EUI64).
|
||||||
|
5
api/rust/proto/chirpstack/api/tenant.proto
vendored
5
api/rust/proto/chirpstack/api/tenant.proto
vendored
@ -122,6 +122,11 @@ message Tenant {
|
|||||||
// do want to share uplinks with other tenants (private_gateways_up=false),
|
// do want to share uplinks with other tenants (private_gateways_up=false),
|
||||||
// but you want to prevent other tenants from using gateway airtime.
|
// but you want to prevent other tenants from using gateway airtime.
|
||||||
bool private_gateways_down = 8;
|
bool private_gateways_down = 8;
|
||||||
|
|
||||||
|
// Tags (user defined).
|
||||||
|
// These tags can be used to add additional information to the tenant. These
|
||||||
|
// tags are NOT exposed in the integration events.
|
||||||
|
map<string, string> tags = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TenantListItem {
|
message TenantListItem {
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
alter table application drop column tags;
|
||||||
|
alter table tenant drop column tags;
|
@ -0,0 +1,15 @@
|
|||||||
|
alter table tenant
|
||||||
|
add column tags jsonb not null default '{}';
|
||||||
|
|
||||||
|
alter table tenant
|
||||||
|
alter column tags drop default;
|
||||||
|
|
||||||
|
create index idx_tenant_tags on tenant using gin (tags);
|
||||||
|
|
||||||
|
alter table application
|
||||||
|
add column tags jsonb not null default '{}';
|
||||||
|
|
||||||
|
alter table application
|
||||||
|
alter column tags drop default;
|
||||||
|
|
||||||
|
create index idx_application_tags on application using gin (tags);
|
@ -10,7 +10,7 @@ use super::auth::validator;
|
|||||||
use super::error::ToStatus;
|
use super::error::ToStatus;
|
||||||
use super::helpers;
|
use super::helpers;
|
||||||
use crate::certificate;
|
use crate::certificate;
|
||||||
use crate::storage::application;
|
use crate::storage::{application, fields};
|
||||||
|
|
||||||
pub struct Application {
|
pub struct Application {
|
||||||
validator: validator::RequestValidator,
|
validator: validator::RequestValidator,
|
||||||
@ -47,6 +47,7 @@ impl ApplicationService for Application {
|
|||||||
tenant_id,
|
tenant_id,
|
||||||
name: req_app.name.clone(),
|
name: req_app.name.clone(),
|
||||||
description: req_app.description.clone(),
|
description: req_app.description.clone(),
|
||||||
|
tags: fields::KeyValue::new(req_app.tags.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,6 +87,7 @@ impl ApplicationService for Application {
|
|||||||
tenant_id: a.tenant_id.to_string(),
|
tenant_id: a.tenant_id.to_string(),
|
||||||
name: a.name,
|
name: a.name,
|
||||||
description: a.description,
|
description: a.description,
|
||||||
|
tags: a.tags.into_hashmap(),
|
||||||
}),
|
}),
|
||||||
created_at: Some(helpers::datetime_to_prost_timestamp(&a.created_at)),
|
created_at: Some(helpers::datetime_to_prost_timestamp(&a.created_at)),
|
||||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&a.updated_at)),
|
updated_at: Some(helpers::datetime_to_prost_timestamp(&a.updated_at)),
|
||||||
@ -120,6 +122,7 @@ impl ApplicationService for Application {
|
|||||||
id: app_id,
|
id: app_id,
|
||||||
name: req_app.name.to_string(),
|
name: req_app.name.to_string(),
|
||||||
description: req_app.description.to_string(),
|
description: req_app.description.to_string(),
|
||||||
|
tags: fields::KeyValue::new(req_app.tags.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -9,7 +9,7 @@ use chirpstack_api::api::tenant_service_server::TenantService;
|
|||||||
use super::auth::{validator, AuthID};
|
use super::auth::{validator, AuthID};
|
||||||
use super::error::ToStatus;
|
use super::error::ToStatus;
|
||||||
use super::helpers;
|
use super::helpers;
|
||||||
use crate::storage::{tenant, user};
|
use crate::storage::{fields, tenant, user};
|
||||||
|
|
||||||
pub struct Tenant {
|
pub struct Tenant {
|
||||||
validator: validator::RequestValidator,
|
validator: validator::RequestValidator,
|
||||||
@ -49,6 +49,7 @@ impl TenantService for Tenant {
|
|||||||
max_gateway_count: req_tenant.max_gateway_count as i32,
|
max_gateway_count: req_tenant.max_gateway_count as i32,
|
||||||
private_gateways_up: req_tenant.private_gateways_up,
|
private_gateways_up: req_tenant.private_gateways_up,
|
||||||
private_gateways_down: req_tenant.private_gateways_down,
|
private_gateways_down: req_tenant.private_gateways_down,
|
||||||
|
tags: fields::KeyValue::new(req_tenant.tags.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ impl TenantService for Tenant {
|
|||||||
max_device_count: t.max_device_count as u32,
|
max_device_count: t.max_device_count as u32,
|
||||||
private_gateways_up: t.private_gateways_up,
|
private_gateways_up: t.private_gateways_up,
|
||||||
private_gateways_down: t.private_gateways_down,
|
private_gateways_down: t.private_gateways_down,
|
||||||
|
tags: t.tags.into_hashmap(),
|
||||||
}),
|
}),
|
||||||
created_at: Some(helpers::datetime_to_prost_timestamp(&t.created_at)),
|
created_at: Some(helpers::datetime_to_prost_timestamp(&t.created_at)),
|
||||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&t.updated_at)),
|
updated_at: Some(helpers::datetime_to_prost_timestamp(&t.updated_at)),
|
||||||
@ -128,6 +130,7 @@ impl TenantService for Tenant {
|
|||||||
max_gateway_count: req_tenant.max_gateway_count as i32,
|
max_gateway_count: req_tenant.max_gateway_count as i32,
|
||||||
private_gateways_up: req_tenant.private_gateways_up,
|
private_gateways_up: req_tenant.private_gateways_up,
|
||||||
private_gateways_down: req_tenant.private_gateways_down,
|
private_gateways_down: req_tenant.private_gateways_down,
|
||||||
|
tags: fields::KeyValue::new(req_tenant.tags.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -443,7 +443,8 @@ impl Data {
|
|||||||
device_class_enabled: self.device.enabled_class.to_proto().into(),
|
device_class_enabled: self.device.enabled_class.to_proto().into(),
|
||||||
dev_eui: self.device.dev_eui.to_string(),
|
dev_eui: self.device.dev_eui.to_string(),
|
||||||
tags: {
|
tags: {
|
||||||
let mut tags = (*self.device_profile.tags).clone();
|
let mut tags = (*self.application.tags).clone();
|
||||||
|
tags.extend((*self.device_profile.tags).clone());
|
||||||
tags.extend((*self.device.tags).clone());
|
tags.extend((*self.device.tags).clone());
|
||||||
tags
|
tags
|
||||||
},
|
},
|
||||||
|
@ -441,7 +441,8 @@ impl TxAck {
|
|||||||
let dp = self.device_profile.as_ref().unwrap();
|
let dp = self.device_profile.as_ref().unwrap();
|
||||||
let dev = self.device.as_ref().unwrap();
|
let dev = self.device.as_ref().unwrap();
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
let pl = integration_pb::LogEvent {
|
let pl = integration_pb::LogEvent {
|
||||||
|
@ -36,8 +36,9 @@ pub async fn handle(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
tags.clone_from(&*dev.tags);
|
tags.extend((*dp.tags).clone());
|
||||||
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
let rx_time: DateTime<Utc> =
|
let rx_time: DateTime<Utc> =
|
||||||
helpers::get_rx_timestamp(&uplink_frame_set.rx_info_set).into();
|
helpers::get_rx_timestamp(&uplink_frame_set.rx_info_set).into();
|
||||||
|
@ -36,7 +36,8 @@ pub async fn handle(
|
|||||||
device_class_enabled: dev.enabled_class.to_proto().into(),
|
device_class_enabled: dev.enabled_class.to_proto().into(),
|
||||||
dev_eui: dev.dev_eui.to_string(),
|
dev_eui: dev.dev_eui.to_string(),
|
||||||
tags: {
|
tags: {
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
tags
|
tags
|
||||||
},
|
},
|
||||||
|
@ -16,8 +16,8 @@ use tracing::info;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::error::Error;
|
use super::error::Error;
|
||||||
use super::get_db_conn;
|
|
||||||
use super::schema::{application, application_integration};
|
use super::schema::{application, application_integration};
|
||||||
|
use super::{fields, get_db_conn};
|
||||||
|
|
||||||
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
|
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
|
||||||
#[diesel(table_name = application)]
|
#[diesel(table_name = application)]
|
||||||
@ -29,6 +29,7 @@ pub struct Application {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub mqtt_tls_cert: Option<Vec<u8>>,
|
pub mqtt_tls_cert: Option<Vec<u8>>,
|
||||||
|
pub tags: fields::KeyValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
@ -52,6 +53,7 @@ impl Default for Application {
|
|||||||
name: "".into(),
|
name: "".into(),
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
mqtt_tls_cert: None,
|
mqtt_tls_cert: None,
|
||||||
|
tags: fields::KeyValue::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,6 +330,7 @@ pub async fn update(a: Application) -> Result<Application, Error> {
|
|||||||
application::updated_at.eq(Utc::now()),
|
application::updated_at.eq(Utc::now()),
|
||||||
application::name.eq(&a.name),
|
application::name.eq(&a.name),
|
||||||
application::description.eq(&a.description),
|
application::description.eq(&a.description),
|
||||||
|
application::tags.eq(&a.tags),
|
||||||
))
|
))
|
||||||
.get_result(&mut c)
|
.get_result(&mut c)
|
||||||
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
|
.map_err(|e| Error::from_diesel(e, a.id.to_string()))?;
|
||||||
|
@ -195,9 +195,11 @@ pub async fn create(d: Device) -> Result<Device, Error> {
|
|||||||
tenant::dsl::max_gateway_count,
|
tenant::dsl::max_gateway_count,
|
||||||
tenant::dsl::private_gateways_up,
|
tenant::dsl::private_gateways_up,
|
||||||
tenant::dsl::private_gateways_down,
|
tenant::dsl::private_gateways_down,
|
||||||
|
tenant::dsl::tags,
|
||||||
))
|
))
|
||||||
.inner_join(application::table)
|
.inner_join(application::table)
|
||||||
.filter(application::dsl::id.eq(&d.application_id))
|
.filter(application::dsl::id.eq(&d.application_id))
|
||||||
|
.for_update()
|
||||||
.first(c)?;
|
.first(c)?;
|
||||||
|
|
||||||
let dev_count: i64 = device::dsl::device
|
let dev_count: i64 = device::dsl::device
|
||||||
|
@ -21,6 +21,7 @@ diesel::table! {
|
|||||||
name -> Varchar,
|
name -> Varchar,
|
||||||
description -> Text,
|
description -> Text,
|
||||||
mqtt_tls_cert -> Nullable<Bytea>,
|
mqtt_tls_cert -> Nullable<Bytea>,
|
||||||
|
tags -> Jsonb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,6 +297,7 @@ diesel::table! {
|
|||||||
max_gateway_count -> Int4,
|
max_gateway_count -> Int4,
|
||||||
private_gateways_up -> Bool,
|
private_gateways_up -> Bool,
|
||||||
private_gateways_down -> Bool,
|
private_gateways_down -> Bool,
|
||||||
|
tags -> Jsonb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::dsl;
|
use diesel::dsl;
|
||||||
@ -7,8 +9,8 @@ use tracing::info;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::error::Error;
|
use super::error::Error;
|
||||||
use super::get_db_conn;
|
|
||||||
use super::schema::{tenant, tenant_user, user};
|
use super::schema::{tenant, tenant_user, user};
|
||||||
|
use super::{fields, get_db_conn};
|
||||||
|
|
||||||
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
|
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
|
||||||
#[diesel(table_name = tenant)]
|
#[diesel(table_name = tenant)]
|
||||||
@ -23,6 +25,7 @@ pub struct Tenant {
|
|||||||
pub max_gateway_count: i32,
|
pub max_gateway_count: i32,
|
||||||
pub private_gateways_up: bool,
|
pub private_gateways_up: bool,
|
||||||
pub private_gateways_down: bool,
|
pub private_gateways_down: bool,
|
||||||
|
pub tags: fields::KeyValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tenant {
|
impl Tenant {
|
||||||
@ -49,6 +52,7 @@ impl Default for Tenant {
|
|||||||
max_gateway_count: 0,
|
max_gateway_count: 0,
|
||||||
private_gateways_up: false,
|
private_gateways_up: false,
|
||||||
private_gateways_down: false,
|
private_gateways_down: false,
|
||||||
|
tags: fields::KeyValue::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,6 +149,7 @@ pub async fn update(t: Tenant) -> Result<Tenant, Error> {
|
|||||||
tenant::max_gateway_count.eq(&t.max_gateway_count),
|
tenant::max_gateway_count.eq(&t.max_gateway_count),
|
||||||
tenant::private_gateways_up.eq(&t.private_gateways_up),
|
tenant::private_gateways_up.eq(&t.private_gateways_up),
|
||||||
tenant::private_gateways_down.eq(&t.private_gateways_down),
|
tenant::private_gateways_down.eq(&t.private_gateways_down),
|
||||||
|
tenant::tags.eq(&t.tags),
|
||||||
))
|
))
|
||||||
.get_result(&mut c)
|
.get_result(&mut c)
|
||||||
.map_err(|e| Error::from_diesel(e, t.id.to_string()))
|
.map_err(|e| Error::from_diesel(e, t.id.to_string()))
|
||||||
@ -408,6 +413,7 @@ pub mod test {
|
|||||||
max_gateway_count: 10,
|
max_gateway_count: 10,
|
||||||
private_gateways_up: true,
|
private_gateways_up: true,
|
||||||
private_gateways_down: true,
|
private_gateways_down: true,
|
||||||
|
tags: fields::KeyValue::new(HashMap::new()),
|
||||||
};
|
};
|
||||||
create(t).await.unwrap()
|
create(t).await.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -387,7 +387,8 @@ impl Data {
|
|||||||
let dp = self.device_profile.as_ref().unwrap();
|
let dp = self.device_profile.as_ref().unwrap();
|
||||||
let dev = self.device.as_ref().unwrap();
|
let dev = self.device.as_ref().unwrap();
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
self.device_info = Some(integration_pb::DeviceInfo {
|
self.device_info = Some(integration_pb::DeviceInfo {
|
||||||
@ -1142,7 +1143,8 @@ impl Data {
|
|||||||
|
|
||||||
device_queue::delete_item(&qi.id).await?;
|
device_queue::delete_item(&qi.id).await?;
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
integration::ack_event(
|
integration::ack_event(
|
||||||
|
@ -317,7 +317,8 @@ impl JoinRequest {
|
|||||||
let dp = self.device_profile.as_ref().unwrap();
|
let dp = self.device_profile.as_ref().unwrap();
|
||||||
let dev = self.device.as_ref().unwrap();
|
let dev = self.device.as_ref().unwrap();
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
self.device_info = Some(integration_pb::DeviceInfo {
|
self.device_info = Some(integration_pb::DeviceInfo {
|
||||||
|
@ -184,7 +184,8 @@ impl JoinRequest {
|
|||||||
let dp = self.device_profile.as_ref().unwrap();
|
let dp = self.device_profile.as_ref().unwrap();
|
||||||
let dev = self.device.as_ref().unwrap();
|
let dev = self.device.as_ref().unwrap();
|
||||||
|
|
||||||
let mut tags = (*dp.tags).clone();
|
let mut tags = (*app.tags).clone();
|
||||||
|
tags.extend((*dp.tags).clone());
|
||||||
tags.extend((*dev.tags).clone());
|
tags.extend((*dev.tags).clone());
|
||||||
|
|
||||||
self.device_info = Some(integration_pb::DeviceInfo {
|
self.device_info = Some(integration_pb::DeviceInfo {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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, Tabs, Row, Col } from "antd";
|
||||||
|
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
import { onFinishFailed } from "../helpers";
|
import { onFinishFailed } from "../helpers";
|
||||||
|
|
||||||
@ -19,17 +20,66 @@ function ApplicationForm(props: IProps) {
|
|||||||
app.setName(v.name);
|
app.setName(v.name);
|
||||||
app.setDescription(v.description);
|
app.setDescription(v.description);
|
||||||
|
|
||||||
|
// tags
|
||||||
|
for (const elm of v.tagsMap) {
|
||||||
|
app.getTagsMap().set(elm[0], elm[1]);
|
||||||
|
}
|
||||||
|
|
||||||
props.onFinish(app);
|
props.onFinish(app);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form layout="vertical" initialValues={props.initialValues.toObject()} onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
<Form layout="vertical" initialValues={props.initialValues.toObject()} onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="General" key="1">
|
||||||
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
|
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
|
||||||
<Input disabled={props.disabled} />
|
<Input disabled={props.disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Description" name="description">
|
<Form.Item label="Description" name="description">
|
||||||
<Input.TextArea disabled={props.disabled} />
|
<Input.TextArea disabled={props.disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Tags" key="2">
|
||||||
|
<Form.List name="tagsMap">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 0]}
|
||||||
|
fieldKey={[name, 0]}
|
||||||
|
rules={[{ required: true, message: "Please enter a key!" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Key" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 1]}
|
||||||
|
fieldKey={[name, 1]}
|
||||||
|
rules={[{ required: true, message: "Please enter a value!" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Value" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
||||||
|
Add tag
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
||||||
Submit
|
Submit
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Form, Input, InputNumber, Switch, Row, Col, Button } from "antd";
|
import { Form, Input, InputNumber, Switch, Row, Col, Button, Tabs } from "antd";
|
||||||
|
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";
|
||||||
|
|
||||||
@ -24,11 +25,18 @@ function TenantForm(props: IProps) {
|
|||||||
tenant.setPrivateGatewaysUp(values.privateGatewaysUp);
|
tenant.setPrivateGatewaysUp(values.privateGatewaysUp);
|
||||||
tenant.setPrivateGatewaysDown(values.privateGatewaysDown);
|
tenant.setPrivateGatewaysDown(values.privateGatewaysDown);
|
||||||
|
|
||||||
|
// tags
|
||||||
|
for (const elm of v.tagsMap) {
|
||||||
|
tenant.getTagsMap().set(elm[0], elm[1]);
|
||||||
|
}
|
||||||
|
|
||||||
props.onFinish(tenant);
|
props.onFinish(tenant);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form layout="vertical" initialValues={props.initialValues.toObject()} onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
<Form layout="vertical" initialValues={props.initialValues.toObject()} onFinish={onFinish} onFinishFailed={onFinishFailed}>
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="General" key="1">
|
||||||
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
|
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
|
||||||
<Input disabled={props.disabled} />
|
<Input disabled={props.disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -88,6 +96,49 @@ function TenantForm(props: IProps) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Tags" key="2">
|
||||||
|
<Form.List name="tagsMap">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={6}>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 0]}
|
||||||
|
fieldKey={[name, 0]}
|
||||||
|
rules={[{ required: true, message: "Please enter a key!" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Key" disabled={props.disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 1]}
|
||||||
|
fieldKey={[name, 1]}
|
||||||
|
rules={[{ required: true, message: "Please enter a value!" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="Value" disabled={props.disabled} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
{!props.disabled &&
|
||||||
|
<Col span={2}>
|
||||||
|
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||||
|
</Col>}
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
{!props.disabled && <Form.Item>
|
||||||
|
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
|
||||||
|
Add tag
|
||||||
|
</Button>
|
||||||
|
</Form.Item>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
||||||
Submit
|
Submit
|
||||||
|
Reference in New Issue
Block a user