mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-06-16 14:28:14 +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).
|
||||
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 {
|
||||
|
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;
|
||||
|
||||
// Tags (user defined).
|
||||
// These tags are exposed in the event payloads or to integration. Tags are
|
||||
// intended for aggregation and filtering.
|
||||
// These tags can be used to add additional information to the device.
|
||||
// These tags are exposed in all the integration events.
|
||||
map<string, string> tags = 9;
|
||||
|
||||
// JoinEUI (optional, EUI64).
|
||||
|
53
api/proto/api/device_profile.proto
vendored
53
api/proto/api/device_profile.proto
vendored
@ -28,7 +28,8 @@ enum MeasurementKind {
|
||||
// Unknown (in which case it is not tracked).
|
||||
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;
|
||||
|
||||
// Counters that do get reset upon reading.
|
||||
@ -79,7 +80,6 @@ enum SecondChAckOffset {
|
||||
|
||||
// 3200 kHz.
|
||||
KHZ_3200 = 5;
|
||||
|
||||
}
|
||||
|
||||
enum RelayModeActivation {
|
||||
@ -96,49 +96,51 @@ enum RelayModeActivation {
|
||||
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 {
|
||||
// Create the given device-profile.
|
||||
rpc Create(CreateDeviceProfileRequest) returns (CreateDeviceProfileResponse) {
|
||||
option(google.api.http) = {
|
||||
post: "/api/device-profiles"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
post : "/api/device-profiles"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the device-profile for the given ID.
|
||||
rpc Get(GetDeviceProfileRequest) returns (GetDeviceProfileResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/device-profiles/{id}"
|
||||
option (google.api.http) = {
|
||||
get : "/api/device-profiles/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the given device-profile.
|
||||
rpc Update(UpdateDeviceProfileRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
put: "/api/device-profiles/{device_profile.id}"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
put : "/api/device-profiles/{device_profile.id}"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the device-profile with the given ID.
|
||||
rpc Delete(DeleteDeviceProfileRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
delete: "/api/device-profiles/{id}"
|
||||
option (google.api.http) = {
|
||||
delete : "/api/device-profiles/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// List the available device-profiles.
|
||||
rpc List(ListDeviceProfilesRequest) returns (ListDeviceProfilesResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/device-profiles"
|
||||
option (google.api.http) = {
|
||||
get : "/api/device-profiles"
|
||||
};
|
||||
}
|
||||
|
||||
// List available ADR algorithms.
|
||||
rpc ListAdrAlgorithms(google.protobuf.Empty) returns (ListDeviceProfileAdrAlgorithmsResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/device-profiles/adr-algorithms"
|
||||
rpc ListAdrAlgorithms(google.protobuf.Empty)
|
||||
returns (ListDeviceProfileAdrAlgorithmsResponse) {
|
||||
option (google.api.http) = {
|
||||
get : "/api/device-profiles/adr-algorithms"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -185,8 +187,8 @@ message DeviceProfile {
|
||||
uint32 uplink_interval = 11;
|
||||
|
||||
// Device-status request interval (times / day).
|
||||
// This defines the times per day that ChirpStack will request the device-status
|
||||
// from the device.
|
||||
// This defines the times per day that ChirpStack will request the
|
||||
// device-status from the device.
|
||||
uint32 device_status_req_interval = 12;
|
||||
|
||||
// Supports OTAA.
|
||||
@ -199,7 +201,8 @@ message DeviceProfile {
|
||||
bool supports_class_c = 15;
|
||||
|
||||
// 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;
|
||||
|
||||
// Class-B ping-slots per beacon period.
|
||||
@ -215,7 +218,8 @@ message DeviceProfile {
|
||||
uint32 class_b_ping_slot_freq = 19;
|
||||
|
||||
// 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;
|
||||
|
||||
// RX1 delay (for ABP).
|
||||
@ -230,7 +234,10 @@ message DeviceProfile {
|
||||
// RX2 frequency (for ABP, Hz).
|
||||
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;
|
||||
|
||||
// Measurements.
|
||||
|
53
api/proto/api/tenant.proto
vendored
53
api/proto/api/tenant.proto
vendored
@ -16,76 +16,76 @@ import "google/protobuf/empty.proto";
|
||||
service TenantService {
|
||||
// Create a new tenant.
|
||||
rpc Create(CreateTenantRequest) returns (CreateTenantResponse) {
|
||||
option(google.api.http) = {
|
||||
post: "/api/tenants"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
post : "/api/tenants"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the tenant for the given ID.
|
||||
rpc Get(GetTenantRequest) returns (GetTenantResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{id}"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the given tenant.
|
||||
rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
put: "/api/tenants/{tenant.id}"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
put : "/api/tenants/{tenant.id}"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the tenant with the given ID.
|
||||
rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
delete: "/api/tenants/{id}"
|
||||
option (google.api.http) = {
|
||||
delete : "/api/tenants/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the list of tenants.
|
||||
rpc List(ListTenantsRequest) returns (ListTenantsResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants"
|
||||
};
|
||||
}
|
||||
|
||||
// Add an user to the tenant.
|
||||
// Note: the user must already exist.
|
||||
rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
post: "/api/tenants/{tenant_user.tenant_id}/users"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
post : "/api/tenants/{tenant_user.tenant_id}/users"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the the tenant user for the given tenant and user IDs.
|
||||
rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the given tenant user.
|
||||
rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
put: "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
put : "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the given tenant user.
|
||||
rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
delete: "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
option (google.api.http) = {
|
||||
delete : "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the list of tenant users.
|
||||
rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{tenant_id}/users"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{tenant_id}/users"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -122,6 +122,11 @@ message Tenant {
|
||||
// do want to share uplinks with other tenants (private_gateways_up=false),
|
||||
// but you want to prevent other tenants from using gateway airtime.
|
||||
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 {
|
||||
|
@ -459,6 +459,12 @@ message Application {
|
||||
|
||||
// Tenant ID (UUID).
|
||||
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 {
|
||||
|
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;
|
||||
|
||||
// Tags (user defined).
|
||||
// These tags are exposed in the event payloads or to integration. Tags are
|
||||
// intended for aggregation and filtering.
|
||||
// These tags can be used to add additional information to the device.
|
||||
// These tags are exposed in all the integration events.
|
||||
map<string, string> tags = 9;
|
||||
|
||||
// JoinEUI (optional, EUI64).
|
||||
|
53
api/rust/proto/chirpstack/api/tenant.proto
vendored
53
api/rust/proto/chirpstack/api/tenant.proto
vendored
@ -16,76 +16,76 @@ import "google/protobuf/empty.proto";
|
||||
service TenantService {
|
||||
// Create a new tenant.
|
||||
rpc Create(CreateTenantRequest) returns (CreateTenantResponse) {
|
||||
option(google.api.http) = {
|
||||
post: "/api/tenants"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
post : "/api/tenants"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the tenant for the given ID.
|
||||
rpc Get(GetTenantRequest) returns (GetTenantResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{id}"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the given tenant.
|
||||
rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
put: "/api/tenants/{tenant.id}"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
put : "/api/tenants/{tenant.id}"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the tenant with the given ID.
|
||||
rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
delete: "/api/tenants/{id}"
|
||||
option (google.api.http) = {
|
||||
delete : "/api/tenants/{id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the list of tenants.
|
||||
rpc List(ListTenantsRequest) returns (ListTenantsResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants"
|
||||
};
|
||||
}
|
||||
|
||||
// Add an user to the tenant.
|
||||
// Note: the user must already exist.
|
||||
rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
post: "/api/tenants/{tenant_user.tenant_id}/users"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
post : "/api/tenants/{tenant_user.tenant_id}/users"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the the tenant user for the given tenant and user IDs.
|
||||
rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Update the given tenant user.
|
||||
rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
put: "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
|
||||
body: "*"
|
||||
option (google.api.http) = {
|
||||
put : "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
|
||||
body : "*"
|
||||
};
|
||||
}
|
||||
|
||||
// Delete the given tenant user.
|
||||
rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) {
|
||||
option(google.api.http) = {
|
||||
delete: "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
option (google.api.http) = {
|
||||
delete : "/api/tenants/{tenant_id}/users/{user_id}"
|
||||
};
|
||||
}
|
||||
|
||||
// Get the list of tenant users.
|
||||
rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/api/tenants/{tenant_id}/users"
|
||||
option (google.api.http) = {
|
||||
get : "/api/tenants/{tenant_id}/users"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -122,6 +122,11 @@ message Tenant {
|
||||
// do want to share uplinks with other tenants (private_gateways_up=false),
|
||||
// but you want to prevent other tenants from using gateway airtime.
|
||||
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 {
|
||||
|
@ -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::helpers;
|
||||
use crate::certificate;
|
||||
use crate::storage::application;
|
||||
use crate::storage::{application, fields};
|
||||
|
||||
pub struct Application {
|
||||
validator: validator::RequestValidator,
|
||||
@ -47,6 +47,7 @@ impl ApplicationService for Application {
|
||||
tenant_id,
|
||||
name: req_app.name.clone(),
|
||||
description: req_app.description.clone(),
|
||||
tags: fields::KeyValue::new(req_app.tags.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -86,6 +87,7 @@ impl ApplicationService for Application {
|
||||
tenant_id: a.tenant_id.to_string(),
|
||||
name: a.name,
|
||||
description: a.description,
|
||||
tags: a.tags.into_hashmap(),
|
||||
}),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&a.created_at)),
|
||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&a.updated_at)),
|
||||
@ -120,6 +122,7 @@ impl ApplicationService for Application {
|
||||
id: app_id,
|
||||
name: req_app.name.to_string(),
|
||||
description: req_app.description.to_string(),
|
||||
tags: fields::KeyValue::new(req_app.tags.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
|
@ -9,7 +9,7 @@ use chirpstack_api::api::tenant_service_server::TenantService;
|
||||
use super::auth::{validator, AuthID};
|
||||
use super::error::ToStatus;
|
||||
use super::helpers;
|
||||
use crate::storage::{tenant, user};
|
||||
use crate::storage::{fields, tenant, user};
|
||||
|
||||
pub struct Tenant {
|
||||
validator: validator::RequestValidator,
|
||||
@ -49,6 +49,7 @@ impl TenantService for Tenant {
|
||||
max_gateway_count: req_tenant.max_gateway_count as i32,
|
||||
private_gateways_up: req_tenant.private_gateways_up,
|
||||
private_gateways_down: req_tenant.private_gateways_down,
|
||||
tags: fields::KeyValue::new(req_tenant.tags.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -89,6 +90,7 @@ impl TenantService for Tenant {
|
||||
max_device_count: t.max_device_count as u32,
|
||||
private_gateways_up: t.private_gateways_up,
|
||||
private_gateways_down: t.private_gateways_down,
|
||||
tags: t.tags.into_hashmap(),
|
||||
}),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&t.created_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,
|
||||
private_gateways_up: req_tenant.private_gateways_up,
|
||||
private_gateways_down: req_tenant.private_gateways_down,
|
||||
tags: fields::KeyValue::new(req_tenant.tags.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
|
@ -443,7 +443,8 @@ impl Data {
|
||||
device_class_enabled: self.device.enabled_class.to_proto().into(),
|
||||
dev_eui: self.device.dev_eui.to_string(),
|
||||
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
|
||||
},
|
||||
|
@ -441,7 +441,8 @@ impl TxAck {
|
||||
let dp = self.device_profile.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());
|
||||
|
||||
let pl = integration_pb::LogEvent {
|
||||
|
@ -36,8 +36,9 @@ pub async fn handle(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tags = (*dp.tags).clone();
|
||||
tags.clone_from(&*dev.tags);
|
||||
let mut tags = (*app.tags).clone();
|
||||
tags.extend((*dp.tags).clone());
|
||||
tags.extend((*dev.tags).clone());
|
||||
|
||||
let rx_time: DateTime<Utc> =
|
||||
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(),
|
||||
dev_eui: dev.dev_eui.to_string(),
|
||||
tags: {
|
||||
let mut tags = (*dp.tags).clone();
|
||||
let mut tags = (*app.tags).clone();
|
||||
tags.extend((*dp.tags).clone());
|
||||
tags.extend((*dev.tags).clone());
|
||||
tags
|
||||
},
|
||||
|
@ -16,8 +16,8 @@ use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::error::Error;
|
||||
use super::get_db_conn;
|
||||
use super::schema::{application, application_integration};
|
||||
use super::{fields, get_db_conn};
|
||||
|
||||
#[derive(Clone, Queryable, Insertable, PartialEq, Eq, Debug)]
|
||||
#[diesel(table_name = application)]
|
||||
@ -29,6 +29,7 @@ pub struct Application {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub mqtt_tls_cert: Option<Vec<u8>>,
|
||||
pub tags: fields::KeyValue,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
@ -52,6 +53,7 @@ impl Default for Application {
|
||||
name: "".into(),
|
||||
description: "".into(),
|
||||
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::name.eq(&a.name),
|
||||
application::description.eq(&a.description),
|
||||
application::tags.eq(&a.tags),
|
||||
))
|
||||
.get_result(&mut c)
|
||||
.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::private_gateways_up,
|
||||
tenant::dsl::private_gateways_down,
|
||||
tenant::dsl::tags,
|
||||
))
|
||||
.inner_join(application::table)
|
||||
.filter(application::dsl::id.eq(&d.application_id))
|
||||
.for_update()
|
||||
.first(c)?;
|
||||
|
||||
let dev_count: i64 = device::dsl::device
|
||||
|
@ -21,6 +21,7 @@ diesel::table! {
|
||||
name -> Varchar,
|
||||
description -> Text,
|
||||
mqtt_tls_cert -> Nullable<Bytea>,
|
||||
tags -> Jsonb,
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,6 +297,7 @@ diesel::table! {
|
||||
max_gateway_count -> Int4,
|
||||
private_gateways_up -> Bool,
|
||||
private_gateways_down -> Bool,
|
||||
tags -> Jsonb,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::dsl;
|
||||
@ -7,8 +9,8 @@ use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::error::Error;
|
||||
use super::get_db_conn;
|
||||
use super::schema::{tenant, tenant_user, user};
|
||||
use super::{fields, get_db_conn};
|
||||
|
||||
#[derive(Queryable, Insertable, PartialEq, Eq, Debug, Clone)]
|
||||
#[diesel(table_name = tenant)]
|
||||
@ -23,6 +25,7 @@ pub struct Tenant {
|
||||
pub max_gateway_count: i32,
|
||||
pub private_gateways_up: bool,
|
||||
pub private_gateways_down: bool,
|
||||
pub tags: fields::KeyValue,
|
||||
}
|
||||
|
||||
impl Tenant {
|
||||
@ -49,6 +52,7 @@ impl Default for Tenant {
|
||||
max_gateway_count: 0,
|
||||
private_gateways_up: 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::private_gateways_up.eq(&t.private_gateways_up),
|
||||
tenant::private_gateways_down.eq(&t.private_gateways_down),
|
||||
tenant::tags.eq(&t.tags),
|
||||
))
|
||||
.get_result(&mut c)
|
||||
.map_err(|e| Error::from_diesel(e, t.id.to_string()))
|
||||
@ -408,6 +413,7 @@ pub mod test {
|
||||
max_gateway_count: 10,
|
||||
private_gateways_up: true,
|
||||
private_gateways_down: true,
|
||||
tags: fields::KeyValue::new(HashMap::new()),
|
||||
};
|
||||
create(t).await.unwrap()
|
||||
}
|
||||
|
@ -387,7 +387,8 @@ impl Data {
|
||||
let dp = self.device_profile.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());
|
||||
|
||||
self.device_info = Some(integration_pb::DeviceInfo {
|
||||
@ -1142,7 +1143,8 @@ impl Data {
|
||||
|
||||
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());
|
||||
|
||||
integration::ack_event(
|
||||
|
@ -317,7 +317,8 @@ impl JoinRequest {
|
||||
let dp = self.device_profile.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());
|
||||
|
||||
self.device_info = Some(integration_pb::DeviceInfo {
|
||||
|
@ -184,7 +184,8 @@ impl JoinRequest {
|
||||
let dp = self.device_profile.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());
|
||||
|
||||
self.device_info = Some(integration_pb::DeviceInfo {
|
||||
|
@ -1,5 +1,6 @@
|
||||
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";
|
||||
|
||||
@ -19,17 +20,66 @@ function ApplicationForm(props: IProps) {
|
||||
app.setName(v.name);
|
||||
app.setDescription(v.description);
|
||||
|
||||
// tags
|
||||
for (const elm of v.tagsMap) {
|
||||
app.getTagsMap().set(elm[0], elm[1]);
|
||||
}
|
||||
|
||||
props.onFinish(app);
|
||||
};
|
||||
|
||||
return (
|
||||
<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!" }]}>
|
||||
<Input disabled={props.disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<Input.TextArea disabled={props.disabled} />
|
||||
</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>
|
||||
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
||||
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";
|
||||
|
||||
@ -24,11 +25,18 @@ function TenantForm(props: IProps) {
|
||||
tenant.setPrivateGatewaysUp(values.privateGatewaysUp);
|
||||
tenant.setPrivateGatewaysDown(values.privateGatewaysDown);
|
||||
|
||||
// tags
|
||||
for (const elm of v.tagsMap) {
|
||||
tenant.getTagsMap().set(elm[0], elm[1]);
|
||||
}
|
||||
|
||||
props.onFinish(tenant);
|
||||
};
|
||||
|
||||
return (
|
||||
<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!" }]}>
|
||||
<Input disabled={props.disabled} />
|
||||
</Form.Item>
|
||||
@ -88,6 +96,49 @@ function TenantForm(props: IProps) {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</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>
|
||||
<Button type="primary" htmlType="submit" disabled={props.disabled}>
|
||||
Submit
|
||||
|
Reference in New Issue
Block a user