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:
Orne Brocaar
2023-10-19 17:11:50 +01:00
parent c7e586a326
commit a087c4c18b
24 changed files with 1005 additions and 831 deletions

View File

@ -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 {

View File

@ -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).

View File

@ -14,502 +14,509 @@ import "google/protobuf/empty.proto";
import "common/common.proto"; import "common/common.proto";
enum CodecRuntime { enum CodecRuntime {
// None. // None.
NONE = 0; NONE = 0;
// Cayenne LPP. // Cayenne LPP.
CAYENNE_LPP = 1; CAYENNE_LPP = 1;
// JavaScript. // JavaScript.
JS = 2; JS = 2;
} }
enum MeasurementKind { 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
COUNTER = 1; // reading).
COUNTER = 1;
// Counters that do get reset upon reading. // Counters that do get reset upon reading.
ABSOLUTE = 2; ABSOLUTE = 2;
// E.g. a temperature value. // E.g. a temperature value.
GAUGE = 3; GAUGE = 3;
// E.g. a firmware version, true / false value. // E.g. a firmware version, true / false value.
STRING = 4; STRING = 4;
} }
enum CadPeriodicity { enum CadPeriodicity {
// 1 second. // 1 second.
SEC_1 = 0; SEC_1 = 0;
// 500 milliseconds // 500 milliseconds
MS_500 = 1; MS_500 = 1;
// 250 milliseconds // 250 milliseconds
MS_250 = 2; MS_250 = 2;
// 100 milliseconds // 100 milliseconds
MS_100 = 3; MS_100 = 3;
// 50 milliseconds // 50 milliseconds
MS_50 = 4; MS_50 = 4;
// 20 milliseconds // 20 milliseconds
MS_20 = 5; MS_20 = 5;
} }
enum SecondChAckOffset { enum SecondChAckOffset {
// 0 kHz. // 0 kHz.
KHZ_0 = 0; KHZ_0 = 0;
// 200 kHz. // 200 kHz.
KHZ_200 = 1; KHZ_200 = 1;
// 400 kHz. // 400 kHz.
KHZ_400 = 2; KHZ_400 = 2;
// 800 kHz. // 800 kHz.
KHZ_800 = 3; KHZ_800 = 3;
// 1600 kHz. // 1600 kHz.
KHZ_1600 = 4; KHZ_1600 = 4;
// 3200 kHz.
KHZ_3200 = 5;
// 3200 kHz.
KHZ_3200 = 5;
} }
enum RelayModeActivation { enum RelayModeActivation {
// Disable the relay mode. // Disable the relay mode.
DISABLE_RELAY_MODE = 0; DISABLE_RELAY_MODE = 0;
// Enable the relay model. // Enable the relay model.
ENABLE_RELAY_MODE = 1; ENABLE_RELAY_MODE = 1;
// Dynamic. // Dynamic.
DYNAMIC = 2; DYNAMIC = 2;
// End-device controlled. // End-device controlled.
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) {
option(google.api.http) = { option (google.api.http) = {
post: "/api/device-profiles" post : "/api/device-profiles"
body: "*" body : "*"
}; };
} }
// Get the device-profile for the given ID. // Get the device-profile for the given ID.
rpc Get(GetDeviceProfileRequest) returns (GetDeviceProfileResponse) { rpc Get(GetDeviceProfileRequest) returns (GetDeviceProfileResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/device-profiles/{id}" get : "/api/device-profiles/{id}"
}; };
} }
// Update the given device-profile. // Update the given device-profile.
rpc Update(UpdateDeviceProfileRequest) returns (google.protobuf.Empty) { rpc Update(UpdateDeviceProfileRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
put: "/api/device-profiles/{device_profile.id}" put : "/api/device-profiles/{device_profile.id}"
body: "*" body : "*"
}; };
} }
// Delete the device-profile with the given ID. // Delete the device-profile with the given ID.
rpc Delete(DeleteDeviceProfileRequest) returns (google.protobuf.Empty) { rpc Delete(DeleteDeviceProfileRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
delete: "/api/device-profiles/{id}" delete : "/api/device-profiles/{id}"
}; };
} }
// List the available device-profiles.
rpc List(ListDeviceProfilesRequest) returns (ListDeviceProfilesResponse) {
option(google.api.http) = {
get: "/api/device-profiles"
};
}
// List available ADR algorithms. // List the available device-profiles.
rpc ListAdrAlgorithms(google.protobuf.Empty) returns (ListDeviceProfileAdrAlgorithmsResponse) { rpc List(ListDeviceProfilesRequest) returns (ListDeviceProfilesResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/device-profiles/adr-algorithms" 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"
};
}
} }
message DeviceProfile { message DeviceProfile {
// Device-profile ID (UUID). // Device-profile ID (UUID).
// Note: on create this will be automatically generated. // Note: on create this will be automatically generated.
string id = 1; string id = 1;
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 2; string tenant_id = 2;
// Name. // Name.
string name = 3; string name = 3;
// Description. // Description.
string description = 26; string description = 26;
// Region. // Region.
common.Region region = 4; common.Region region = 4;
// LoRaWAN mac-version. // LoRaWAN mac-version.
common.MacVersion mac_version = 5; common.MacVersion mac_version = 5;
// Regional parameters revision. // Regional parameters revision.
common.RegParamsRevision reg_params_revision = 6; common.RegParamsRevision reg_params_revision = 6;
// ADR algorithm ID. // ADR algorithm ID.
string adr_algorithm_id = 7; string adr_algorithm_id = 7;
// Payload codec runtime. // Payload codec runtime.
CodecRuntime payload_codec_runtime = 8; CodecRuntime payload_codec_runtime = 8;
// Payload codec script. // Payload codec script.
string payload_codec_script = 9; string payload_codec_script = 9;
// Flush queue on device activation. // Flush queue on device activation.
bool flush_queue_on_activate = 10; bool flush_queue_on_activate = 10;
// Uplink interval (seconds). // Uplink interval (seconds).
// This defines the expected uplink interval which the device uses for // This defines the expected uplink interval which the device uses for
// communication. If the uplink interval has expired and no uplink has // communication. If the uplink interval has expired and no uplink has
// been received, the device is considered inactive. // been received, the device is considered inactive.
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.
bool supports_otaa = 13; bool supports_otaa = 13;
// Supports Class B. // Supports Class B.
bool supports_class_b = 14; bool supports_class_b = 14;
// Supports Class-C. // Supports Class-C.
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
uint32 class_b_timeout = 16; // from the device (if requested).
uint32 class_b_timeout = 16;
// Class-B ping-slots per beacon period. // Class-B ping-slots per beacon period.
// Valid options are: 0 - 7. // Valid options are: 0 - 7.
// //
// The actual number of ping-slots per beacon period equals to 2^k. // The actual number of ping-slots per beacon period equals to 2^k.
uint32 class_b_ping_slot_nb_k = 17; uint32 class_b_ping_slot_nb_k = 17;
// Class-B ping-slot DR. // Class-B ping-slot DR.
uint32 class_b_ping_slot_dr = 18; uint32 class_b_ping_slot_dr = 18;
// Class-B ping-slot freq (Hz). // Class-B ping-slot freq (Hz).
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
uint32 class_c_timeout = 20; // from the device (if requested).
uint32 class_c_timeout = 20;
// RX1 delay (for ABP). // RX1 delay (for ABP).
uint32 abp_rx1_delay = 21; uint32 abp_rx1_delay = 21;
// RX1 DR offset (for ABP). // RX1 DR offset (for ABP).
uint32 abp_rx1_dr_offset = 22; uint32 abp_rx1_dr_offset = 22;
// RX2 DR (for ABP). // RX2 DR (for ABP).
uint32 abp_rx2_dr = 23; uint32 abp_rx2_dr = 23;
// 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).
map<string, string> tags = 25; // 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. // Measurements.
// If defined, ChirpStack will visualize these metrics in the web-interface. // If defined, ChirpStack will visualize these metrics in the web-interface.
map<string, Measurement> measurements = 27; map<string, Measurement> measurements = 27;
// Auto-detect measurements. // Auto-detect measurements.
// If set to true, measurements will be automatically added based on the // If set to true, measurements will be automatically added based on the
// keys of the decoded payload. In cases where the decoded payload contains // keys of the decoded payload. In cases where the decoded payload contains
// random keys in the data, you want to set this to false. // random keys in the data, you want to set this to false.
bool auto_detect_measurements = 28; bool auto_detect_measurements = 28;
// Region configuration ID. // Region configuration ID.
// If set, devices will only use the associated region. If let blank, then // If set, devices will only use the associated region. If let blank, then
// devices will use all regions matching the selected common-name. Note // devices will use all regions matching the selected common-name. Note
// that multiple region configurations can exist for the same common-name, // that multiple region configurations can exist for the same common-name,
// e.g. to provide an 8 channel and 16 channel configuration for the US915 // e.g. to provide an 8 channel and 16 channel configuration for the US915
// band. // band.
string region_config_id = 29; string region_config_id = 29;
// Device is a Relay device. // Device is a Relay device.
// Enable this in case the device is a Relay. A Relay device implements TS011 // Enable this in case the device is a Relay. A Relay device implements TS011
// and is able to relay data from relay capable devices. // and is able to relay data from relay capable devices.
// See for more information the TS011 specification. // See for more information the TS011 specification.
bool is_relay = 30; bool is_relay = 30;
// Device is a Relay end-device. // Device is a Relay end-device.
// Enable this in case the device is an end-device that can operate under a // Enable this in case the device is an end-device that can operate under a
// Relay. Please refer to the TS011 specification for more information. // Relay. Please refer to the TS011 specification for more information.
bool is_relay_ed = 31; bool is_relay_ed = 31;
// End-device only accept data through relay. // End-device only accept data through relay.
// Only accept data for this device through a relay. This setting is useful // Only accept data for this device through a relay. This setting is useful
// for testing as in case of a test-setup, the end-device is usually within // for testing as in case of a test-setup, the end-device is usually within
// range of the gateway. // range of the gateway.
bool relay_ed_relay_only = 32; bool relay_ed_relay_only = 32;
// Relay must be enabled. // Relay must be enabled.
bool relay_enabled = 33; bool relay_enabled = 33;
// Relay CAD periodicity. // Relay CAD periodicity.
CadPeriodicity relay_cad_periodicity = 34; CadPeriodicity relay_cad_periodicity = 34;
// Relay default channel index. // Relay default channel index.
// Valid values are 0 and 1, please refer to the RP002 specification for // Valid values are 0 and 1, please refer to the RP002 specification for
// the meaning of these values. // the meaning of these values.
uint32 relay_default_channel_index = 35; uint32 relay_default_channel_index = 35;
// Relay second channel frequency (Hz). // Relay second channel frequency (Hz).
uint32 relay_second_channel_freq = 36; uint32 relay_second_channel_freq = 36;
// Relay second channel DR. // Relay second channel DR.
uint32 relay_second_channel_dr = 37; uint32 relay_second_channel_dr = 37;
// Relay second channel ACK offset. // Relay second channel ACK offset.
SecondChAckOffset relay_second_channel_ack_offset = 38; SecondChAckOffset relay_second_channel_ack_offset = 38;
// Relay end-device activation mode. // Relay end-device activation mode.
RelayModeActivation relay_ed_activation_mode = 39; RelayModeActivation relay_ed_activation_mode = 39;
// Relay end-device smart-enable level. // Relay end-device smart-enable level.
uint32 relay_ed_smart_enable_level = 40; uint32 relay_ed_smart_enable_level = 40;
// Relay end-device back-off (in case it does not receive WOR ACK frame). // Relay end-device back-off (in case it does not receive WOR ACK frame).
// 0 = Always send a LoRaWAN uplink // 0 = Always send a LoRaWAN uplink
// 1..63 = Send a LoRaWAN uplink after X WOR frames without a WOR ACK // 1..63 = Send a LoRaWAN uplink after X WOR frames without a WOR ACK
uint32 relay_ed_back_off = 41; uint32 relay_ed_back_off = 41;
// Relay end-device uplink limit bucket size. // Relay end-device uplink limit bucket size.
// //
// This field indicates the multiplier to determine the bucket size // This field indicates the multiplier to determine the bucket size
// according to the following formula: // according to the following formula:
// BucketSize TOKEN = _reload_rate x _bucket_size // BucketSize TOKEN = _reload_rate x _bucket_size
// //
// Valid values (0 - 3): // Valid values (0 - 3):
// 0 = 1 // 0 = 1
// 1 = 2 // 1 = 2
// 2 = 4 // 2 = 4
// 3 = 12 // 3 = 12
uint32 relay_ed_uplink_limit_bucket_size = 42; uint32 relay_ed_uplink_limit_bucket_size = 42;
// Relay end-device uplink limit reload rate. // Relay end-device uplink limit reload rate.
// //
// Valid values: // Valid values:
// * 0 - 62 = X tokens every hour // * 0 - 62 = X tokens every hour
// * 63 = no limitation // * 63 = no limitation
uint32 relay_ed_uplink_limit_reload_rate = 43; uint32 relay_ed_uplink_limit_reload_rate = 43;
// Relay join-request limit reload rate. // Relay join-request limit reload rate.
// //
// Valid values: // Valid values:
// * 0 - 126 = X tokens every hour // * 0 - 126 = X tokens every hour
// * 127 = no limitation // * 127 = no limitation
uint32 relay_join_req_limit_reload_rate = 44; uint32 relay_join_req_limit_reload_rate = 44;
// Relay notify limit reload rate. // Relay notify limit reload rate.
// //
// Valid values: // Valid values:
// * 0 - 126 = X tokens every hour // * 0 - 126 = X tokens every hour
// * 127 = no limitation // * 127 = no limitation
uint32 relay_notify_limit_reload_rate = 45; uint32 relay_notify_limit_reload_rate = 45;
// Relay global uplink limit reload rate. // Relay global uplink limit reload rate.
// //
// Valid values: // Valid values:
// * 0 - 126 = X tokens every hour // * 0 - 126 = X tokens every hour
// * 127 = no limitation // * 127 = no limitation
uint32 relay_global_uplink_limit_reload_rate = 46; uint32 relay_global_uplink_limit_reload_rate = 46;
// Relay overall limit reload rate. // Relay overall limit reload rate.
// //
// Valid values: // Valid values:
// * 0 - 126 = X tokens every hour // * 0 - 126 = X tokens every hour
// * 127 = no limitation // * 127 = no limitation
uint32 relay_overall_limit_reload_rate = 47; uint32 relay_overall_limit_reload_rate = 47;
// Relay join-request limit bucket size. // Relay join-request limit bucket size.
// //
// This field indicates the multiplier to determine the bucket size // This field indicates the multiplier to determine the bucket size
// according to the following formula: // according to the following formula:
// BucketSize TOKEN = _reload_rate x _bucket_size // BucketSize TOKEN = _reload_rate x _bucket_size
// //
// Valid values (0 - 3): // Valid values (0 - 3):
// 0 = 1 // 0 = 1
// 1 = 2 // 1 = 2
// 2 = 4 // 2 = 4
// 3 = 12 // 3 = 12
uint32 relay_join_req_limit_bucket_size = 48; uint32 relay_join_req_limit_bucket_size = 48;
// Relay notify limit bucket size. // Relay notify limit bucket size.
// //
// This field indicates the multiplier to determine the bucket size // This field indicates the multiplier to determine the bucket size
// according to the following formula: // according to the following formula:
// BucketSize TOKEN = _reload_rate x _bucket_size // BucketSize TOKEN = _reload_rate x _bucket_size
// //
// Valid values (0 - 3): // Valid values (0 - 3):
// 0 = 1 // 0 = 1
// 1 = 2 // 1 = 2
// 2 = 4 // 2 = 4
// 3 = 12 // 3 = 12
uint32 relay_notify_limit_bucket_size = 49; uint32 relay_notify_limit_bucket_size = 49;
// Relay globak uplink limit bucket size. // Relay globak uplink limit bucket size.
// //
// This field indicates the multiplier to determine the bucket size // This field indicates the multiplier to determine the bucket size
// according to the following formula: // according to the following formula:
// BucketSize TOKEN = _reload_rate x _bucket_size // BucketSize TOKEN = _reload_rate x _bucket_size
// //
// Valid values (0 - 3): // Valid values (0 - 3):
// 0 = 1 // 0 = 1
// 1 = 2 // 1 = 2
// 2 = 4 // 2 = 4
// 3 = 12 // 3 = 12
uint32 relay_global_uplink_limit_bucket_size = 50; uint32 relay_global_uplink_limit_bucket_size = 50;
// Relay overall limit bucket size. // Relay overall limit bucket size.
// //
// This field indicates the multiplier to determine the bucket size // This field indicates the multiplier to determine the bucket size
// according to the following formula: // according to the following formula:
// BucketSize TOKEN = _reload_rate x _bucket_size // BucketSize TOKEN = _reload_rate x _bucket_size
// //
// Valid values (0 - 3): // Valid values (0 - 3):
// 0 = 1 // 0 = 1
// 1 = 2 // 1 = 2
// 2 = 4 // 2 = 4
// 3 = 12 // 3 = 12
uint32 relay_overall_limit_bucket_size = 51; uint32 relay_overall_limit_bucket_size = 51;
} }
message Measurement { message Measurement {
// Name (user defined). // Name (user defined).
string name = 2; string name = 2;
// Kind. // Kind.
MeasurementKind kind = 3; MeasurementKind kind = 3;
} }
message DeviceProfileListItem { message DeviceProfileListItem {
// Device-profile ID (UUID). // Device-profile ID (UUID).
string id = 1; string id = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
// Name. // Name.
string name = 4; string name = 4;
// Region. // Region.
common.Region region = 5; common.Region region = 5;
// LoRaWAN mac-version. // LoRaWAN mac-version.
common.MacVersion mac_version = 6; common.MacVersion mac_version = 6;
// Regional parameters revision. // Regional parameters revision.
common.RegParamsRevision reg_params_revision = 7; common.RegParamsRevision reg_params_revision = 7;
// Supports OTAA. // Supports OTAA.
bool supports_otaa = 8; bool supports_otaa = 8;
// Supports Class-B. // Supports Class-B.
bool supports_class_b = 9; bool supports_class_b = 9;
// Supports Class-C. // Supports Class-C.
bool supports_class_c = 10; bool supports_class_c = 10;
} }
message CreateDeviceProfileRequest { message CreateDeviceProfileRequest {
// Object to create. // Object to create.
DeviceProfile device_profile = 1; DeviceProfile device_profile = 1;
} }
message CreateDeviceProfileResponse { message CreateDeviceProfileResponse {
// ID (UUID). // ID (UUID).
string id = 1; string id = 1;
} }
message GetDeviceProfileRequest { message GetDeviceProfileRequest {
// ID (UUID). // ID (UUID).
string id = 1; string id = 1;
} }
message GetDeviceProfileResponse { message GetDeviceProfileResponse {
// Device-profile object. // Device-profile object.
DeviceProfile device_profile = 1; DeviceProfile device_profile = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
} }
message UpdateDeviceProfileRequest { message UpdateDeviceProfileRequest {
// Device-profile object. // Device-profile object.
DeviceProfile device_profile = 1; DeviceProfile device_profile = 1;
} }
message DeleteDeviceProfileRequest { message DeleteDeviceProfileRequest {
// ID (UUID). // ID (UUID).
string id = 1; string id = 1;
} }
message ListDeviceProfilesRequest { message ListDeviceProfilesRequest {
// Max number of device-profiles to return in the result-set. // Max number of device-profiles to return in the result-set.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
uint32 offset = 2; uint32 offset = 2;
// If set, the given string will be used to search on name. // If set, the given string will be used to search on name.
string search = 3; string search = 3;
// Tenant ID to list the device-profiles for. // Tenant ID to list the device-profiles for.
string tenant_id = 4; string tenant_id = 4;
} }
message ListDeviceProfilesResponse { message ListDeviceProfilesResponse {
// Total number of device-profiles. // Total number of device-profiles.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated DeviceProfileListItem result = 2; repeated DeviceProfileListItem result = 2;
} }
message ListDeviceProfileAdrAlgorithmsResponse { message ListDeviceProfileAdrAlgorithmsResponse {
// Total number of algorithms. // Total number of algorithms.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated AdrAlgorithmListItem result = 2; repeated AdrAlgorithmListItem result = 2;
} }
message AdrAlgorithmListItem { message AdrAlgorithmListItem {
// Algorithm ID. // Algorithm ID.
string id = 1; string id = 1;
// Algorithm name. // Algorithm name.
string name = 2; string name = 2;
} }

View File

@ -14,308 +14,313 @@ import "google/protobuf/empty.proto";
// TenantService is the service providing API methods for managing tenants. // TenantService is the service providing API methods for managing tenants.
service TenantService { service TenantService {
// Create a new tenant. // Create a new tenant.
rpc Create(CreateTenantRequest) returns (CreateTenantResponse) { rpc Create(CreateTenantRequest) returns (CreateTenantResponse) {
option(google.api.http) = { option (google.api.http) = {
post: "/api/tenants" post : "/api/tenants"
body: "*" body : "*"
}; };
} }
// Get the tenant for the given ID. // Get the tenant for the given ID.
rpc Get(GetTenantRequest) returns (GetTenantResponse) { rpc Get(GetTenantRequest) returns (GetTenantResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{id}" get : "/api/tenants/{id}"
}; };
} }
// Update the given tenant. // Update the given tenant.
rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) { rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
put: "/api/tenants/{tenant.id}" put : "/api/tenants/{tenant.id}"
body: "*" body : "*"
}; };
} }
// Delete the tenant with the given ID. // Delete the tenant with the given ID.
rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) { rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
delete: "/api/tenants/{id}" delete : "/api/tenants/{id}"
}; };
} }
// Get the list of tenants. // Get the list of tenants.
rpc List(ListTenantsRequest) returns (ListTenantsResponse) { rpc List(ListTenantsRequest) returns (ListTenantsResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants" get : "/api/tenants"
}; };
} }
// Add an user to the tenant. // Add an user to the tenant.
// Note: the user must already exist. // Note: the user must already exist.
rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) { rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
post: "/api/tenants/{tenant_user.tenant_id}/users" post : "/api/tenants/{tenant_user.tenant_id}/users"
body: "*" body : "*"
}; };
} }
// Get the the tenant user for the given tenant and user IDs. // Get the the tenant user for the given tenant and user IDs.
rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) { rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{tenant_id}/users/{user_id}" get : "/api/tenants/{tenant_id}/users/{user_id}"
}; };
} }
// Update the given tenant user. // Update the given tenant user.
rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) { rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
put: "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}" put : "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
body: "*" body : "*"
}; };
} }
// Delete the given tenant user. // Delete the given tenant user.
rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) { rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
delete: "/api/tenants/{tenant_id}/users/{user_id}" delete : "/api/tenants/{tenant_id}/users/{user_id}"
}; };
} }
// Get the list of tenant users. // Get the list of tenant users.
rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) { rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{tenant_id}/users" get : "/api/tenants/{tenant_id}/users"
}; };
} }
} }
message Tenant { message Tenant {
// Tenant ID (UUID). // Tenant ID (UUID).
// Note: this value will be automatically generated on create. // Note: this value will be automatically generated on create.
string id = 1; string id = 1;
// Tenant name, // Tenant name,
string name = 2; string name = 2;
// Tenant description. // Tenant description.
string description = 3; string description = 3;
// Can the tenant create and "own" Gateways? // Can the tenant create and "own" Gateways?
bool can_have_gateways = 4; bool can_have_gateways = 4;
// Max. gateway count for tenant. // Max. gateway count for tenant.
// When set to 0, the tenant can have unlimited gateways. // When set to 0, the tenant can have unlimited gateways.
uint32 max_gateway_count = 5; uint32 max_gateway_count = 5;
// Max. device count for tenant. // Max. device count for tenant.
// When set to 0, the tenant can have unlimited devices. // When set to 0, the tenant can have unlimited devices.
uint32 max_device_count = 6; uint32 max_device_count = 6;
// Private gateways (uplink). // Private gateways (uplink).
// If enabled, then uplink messages will not be shared with other tenants. // If enabled, then uplink messages will not be shared with other tenants.
bool private_gateways_up = 7; bool private_gateways_up = 7;
// Private gateways (downlink). // Private gateways (downlink).
// If enabled, then other tenants will not be able to schedule downlink // If enabled, then other tenants will not be able to schedule downlink
// messages through the gateways of this tenant. For example, in case you // messages through the gateways of this tenant. For example, in case you
// 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 {
// Tenant ID (UUID). // Tenant ID (UUID).
string id = 1; string id = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
// Tenant name. // Tenant name.
string name = 4; string name = 4;
// Can the tenant create and "own" Gateways? // Can the tenant create and "own" Gateways?
bool can_have_gateways = 5; bool can_have_gateways = 5;
// Private gateways (uplink). // Private gateways (uplink).
bool private_gateways_up = 6; bool private_gateways_up = 6;
// Private gateways (downlink). // Private gateways (downlink).
bool private_gateways_down = 9; bool private_gateways_down = 9;
// Max gateway count. // Max gateway count.
// 0 = unlimited. // 0 = unlimited.
uint32 max_gateway_count = 7; uint32 max_gateway_count = 7;
// Max device count. // Max device count.
// 0 = unlimited. // 0 = unlimited.
uint32 max_device_count = 8; uint32 max_device_count = 8;
} }
message CreateTenantRequest { message CreateTenantRequest {
// Tenant object to create. // Tenant object to create.
Tenant tenant = 1; Tenant tenant = 1;
} }
message CreateTenantResponse { message CreateTenantResponse {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message GetTenantRequest { message GetTenantRequest {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message GetTenantResponse { message GetTenantResponse {
// Tenant object. // Tenant object.
Tenant tenant = 1; Tenant tenant = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
} }
message UpdateTenantRequest { message UpdateTenantRequest {
// Tenant object. // Tenant object.
Tenant tenant = 1; Tenant tenant = 1;
} }
message DeleteTenantRequest { message DeleteTenantRequest {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message ListTenantsRequest { message ListTenantsRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
uint32 offset = 2; uint32 offset = 2;
// If set, the given string will be used to search on name. // If set, the given string will be used to search on name.
string search = 3; string search = 3;
// If set, filters the result set to the tenants of the user. // If set, filters the result set to the tenants of the user.
// Only global API keys are able to filter by this field. // Only global API keys are able to filter by this field.
string user_id = 4; string user_id = 4;
} }
message ListTenantsResponse { message ListTenantsResponse {
// Total number of tenants. // Total number of tenants.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated TenantListItem result = 2; repeated TenantListItem result = 2;
} }
message TenantUser { message TenantUser {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
// User is admin within the context of the tenant. // User is admin within the context of the tenant.
// There is no need to set the is_device_admin and is_gateway_admin flags. // There is no need to set the is_device_admin and is_gateway_admin flags.
bool is_admin = 3; bool is_admin = 3;
// User is able to modify device related resources (applications, // User is able to modify device related resources (applications,
// device-profiles, devices, multicast-groups). // device-profiles, devices, multicast-groups).
bool is_device_admin = 4; bool is_device_admin = 4;
// User is able to modify gateways. // User is able to modify gateways.
bool is_gateway_admin = 5; bool is_gateway_admin = 5;
// Email (only used on get and when adding a user to a tenant). // Email (only used on get and when adding a user to a tenant).
string email = 6; string email = 6;
} }
message TenantUserListItem { message TenantUserListItem {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 3; google.protobuf.Timestamp created_at = 3;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 4; google.protobuf.Timestamp updated_at = 4;
// Email. // Email.
string email = 5; string email = 5;
// User is admin within the context of the tenant. // User is admin within the context of the tenant.
// There is no need to set the is_device_admin and is_gateway_admin flags. // There is no need to set the is_device_admin and is_gateway_admin flags.
bool is_admin = 6; bool is_admin = 6;
// User is able to modify device related resources (applications, // User is able to modify device related resources (applications,
// device-profiles, devices, multicast-groups). // device-profiles, devices, multicast-groups).
bool is_device_admin = 7; bool is_device_admin = 7;
// User is able to modify gateways. // User is able to modify gateways.
bool is_gateway_admin = 8; bool is_gateway_admin = 8;
} }
message AddTenantUserRequest { message AddTenantUserRequest {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
} }
message GetTenantUserRequest { message GetTenantUserRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
} }
message GetTenantUserResponse { message GetTenantUserResponse {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
} }
message UpdateTenantUserRequest { message UpdateTenantUserRequest {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
} }
message DeleteTenantUserRequest { message DeleteTenantUserRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
} }
message ListTenantUsersRequest { message ListTenantUsersRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
uint32 limit = 2; uint32 limit = 2;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
uint32 offset = 3; uint32 offset = 3;
} }
message ListTenantUsersResponse { message ListTenantUsersResponse {
// Total number of tenants. // Total number of tenants.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated TenantUserListItem result = 2; repeated TenantUserListItem result = 2;
} }

View File

@ -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 {

View File

@ -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).

View File

@ -14,308 +14,313 @@ import "google/protobuf/empty.proto";
// TenantService is the service providing API methods for managing tenants. // TenantService is the service providing API methods for managing tenants.
service TenantService { service TenantService {
// Create a new tenant. // Create a new tenant.
rpc Create(CreateTenantRequest) returns (CreateTenantResponse) { rpc Create(CreateTenantRequest) returns (CreateTenantResponse) {
option(google.api.http) = { option (google.api.http) = {
post: "/api/tenants" post : "/api/tenants"
body: "*" body : "*"
}; };
} }
// Get the tenant for the given ID. // Get the tenant for the given ID.
rpc Get(GetTenantRequest) returns (GetTenantResponse) { rpc Get(GetTenantRequest) returns (GetTenantResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{id}" get : "/api/tenants/{id}"
}; };
} }
// Update the given tenant. // Update the given tenant.
rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) { rpc Update(UpdateTenantRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
put: "/api/tenants/{tenant.id}" put : "/api/tenants/{tenant.id}"
body: "*" body : "*"
}; };
} }
// Delete the tenant with the given ID. // Delete the tenant with the given ID.
rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) { rpc Delete(DeleteTenantRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
delete: "/api/tenants/{id}" delete : "/api/tenants/{id}"
}; };
} }
// Get the list of tenants. // Get the list of tenants.
rpc List(ListTenantsRequest) returns (ListTenantsResponse) { rpc List(ListTenantsRequest) returns (ListTenantsResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants" get : "/api/tenants"
}; };
} }
// Add an user to the tenant. // Add an user to the tenant.
// Note: the user must already exist. // Note: the user must already exist.
rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) { rpc AddUser(AddTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
post: "/api/tenants/{tenant_user.tenant_id}/users" post : "/api/tenants/{tenant_user.tenant_id}/users"
body: "*" body : "*"
}; };
} }
// Get the the tenant user for the given tenant and user IDs. // Get the the tenant user for the given tenant and user IDs.
rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) { rpc GetUser(GetTenantUserRequest) returns (GetTenantUserResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{tenant_id}/users/{user_id}" get : "/api/tenants/{tenant_id}/users/{user_id}"
}; };
} }
// Update the given tenant user. // Update the given tenant user.
rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) { rpc UpdateUser(UpdateTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
put: "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}" put : "/api/tenants/{tenant_user.tenant_id}/users/{tenant_user.user_id}"
body: "*" body : "*"
}; };
} }
// Delete the given tenant user. // Delete the given tenant user.
rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) { rpc DeleteUser(DeleteTenantUserRequest) returns (google.protobuf.Empty) {
option(google.api.http) = { option (google.api.http) = {
delete: "/api/tenants/{tenant_id}/users/{user_id}" delete : "/api/tenants/{tenant_id}/users/{user_id}"
}; };
} }
// Get the list of tenant users. // Get the list of tenant users.
rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) { rpc ListUsers(ListTenantUsersRequest) returns (ListTenantUsersResponse) {
option(google.api.http) = { option (google.api.http) = {
get: "/api/tenants/{tenant_id}/users" get : "/api/tenants/{tenant_id}/users"
}; };
} }
} }
message Tenant { message Tenant {
// Tenant ID (UUID). // Tenant ID (UUID).
// Note: this value will be automatically generated on create. // Note: this value will be automatically generated on create.
string id = 1; string id = 1;
// Tenant name, // Tenant name,
string name = 2; string name = 2;
// Tenant description. // Tenant description.
string description = 3; string description = 3;
// Can the tenant create and "own" Gateways? // Can the tenant create and "own" Gateways?
bool can_have_gateways = 4; bool can_have_gateways = 4;
// Max. gateway count for tenant. // Max. gateway count for tenant.
// When set to 0, the tenant can have unlimited gateways. // When set to 0, the tenant can have unlimited gateways.
uint32 max_gateway_count = 5; uint32 max_gateway_count = 5;
// Max. device count for tenant. // Max. device count for tenant.
// When set to 0, the tenant can have unlimited devices. // When set to 0, the tenant can have unlimited devices.
uint32 max_device_count = 6; uint32 max_device_count = 6;
// Private gateways (uplink). // Private gateways (uplink).
// If enabled, then uplink messages will not be shared with other tenants. // If enabled, then uplink messages will not be shared with other tenants.
bool private_gateways_up = 7; bool private_gateways_up = 7;
// Private gateways (downlink). // Private gateways (downlink).
// If enabled, then other tenants will not be able to schedule downlink // If enabled, then other tenants will not be able to schedule downlink
// messages through the gateways of this tenant. For example, in case you // messages through the gateways of this tenant. For example, in case you
// 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 {
// Tenant ID (UUID). // Tenant ID (UUID).
string id = 1; string id = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
// Tenant name. // Tenant name.
string name = 4; string name = 4;
// Can the tenant create and "own" Gateways? // Can the tenant create and "own" Gateways?
bool can_have_gateways = 5; bool can_have_gateways = 5;
// Private gateways (uplink). // Private gateways (uplink).
bool private_gateways_up = 6; bool private_gateways_up = 6;
// Private gateways (downlink). // Private gateways (downlink).
bool private_gateways_down = 9; bool private_gateways_down = 9;
// Max gateway count. // Max gateway count.
// 0 = unlimited. // 0 = unlimited.
uint32 max_gateway_count = 7; uint32 max_gateway_count = 7;
// Max device count. // Max device count.
// 0 = unlimited. // 0 = unlimited.
uint32 max_device_count = 8; uint32 max_device_count = 8;
} }
message CreateTenantRequest { message CreateTenantRequest {
// Tenant object to create. // Tenant object to create.
Tenant tenant = 1; Tenant tenant = 1;
} }
message CreateTenantResponse { message CreateTenantResponse {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message GetTenantRequest { message GetTenantRequest {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message GetTenantResponse { message GetTenantResponse {
// Tenant object. // Tenant object.
Tenant tenant = 1; Tenant tenant = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
} }
message UpdateTenantRequest { message UpdateTenantRequest {
// Tenant object. // Tenant object.
Tenant tenant = 1; Tenant tenant = 1;
} }
message DeleteTenantRequest { message DeleteTenantRequest {
// Tenant ID. // Tenant ID.
string id = 1; string id = 1;
} }
message ListTenantsRequest { message ListTenantsRequest {
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
uint32 limit = 1; uint32 limit = 1;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
uint32 offset = 2; uint32 offset = 2;
// If set, the given string will be used to search on name. // If set, the given string will be used to search on name.
string search = 3; string search = 3;
// If set, filters the result set to the tenants of the user. // If set, filters the result set to the tenants of the user.
// Only global API keys are able to filter by this field. // Only global API keys are able to filter by this field.
string user_id = 4; string user_id = 4;
} }
message ListTenantsResponse { message ListTenantsResponse {
// Total number of tenants. // Total number of tenants.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated TenantListItem result = 2; repeated TenantListItem result = 2;
} }
message TenantUser { message TenantUser {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
// User is admin within the context of the tenant. // User is admin within the context of the tenant.
// There is no need to set the is_device_admin and is_gateway_admin flags. // There is no need to set the is_device_admin and is_gateway_admin flags.
bool is_admin = 3; bool is_admin = 3;
// User is able to modify device related resources (applications, // User is able to modify device related resources (applications,
// device-profiles, devices, multicast-groups). // device-profiles, devices, multicast-groups).
bool is_device_admin = 4; bool is_device_admin = 4;
// User is able to modify gateways. // User is able to modify gateways.
bool is_gateway_admin = 5; bool is_gateway_admin = 5;
// Email (only used on get and when adding a user to a tenant). // Email (only used on get and when adding a user to a tenant).
string email = 6; string email = 6;
} }
message TenantUserListItem { message TenantUserListItem {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 3; google.protobuf.Timestamp created_at = 3;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 4; google.protobuf.Timestamp updated_at = 4;
// Email. // Email.
string email = 5; string email = 5;
// User is admin within the context of the tenant. // User is admin within the context of the tenant.
// There is no need to set the is_device_admin and is_gateway_admin flags. // There is no need to set the is_device_admin and is_gateway_admin flags.
bool is_admin = 6; bool is_admin = 6;
// User is able to modify device related resources (applications, // User is able to modify device related resources (applications,
// device-profiles, devices, multicast-groups). // device-profiles, devices, multicast-groups).
bool is_device_admin = 7; bool is_device_admin = 7;
// User is able to modify gateways. // User is able to modify gateways.
bool is_gateway_admin = 8; bool is_gateway_admin = 8;
} }
message AddTenantUserRequest { message AddTenantUserRequest {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
} }
message GetTenantUserRequest { message GetTenantUserRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
} }
message GetTenantUserResponse { message GetTenantUserResponse {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
// Created at timestamp. // Created at timestamp.
google.protobuf.Timestamp created_at = 2; google.protobuf.Timestamp created_at = 2;
// Last update timestamp. // Last update timestamp.
google.protobuf.Timestamp updated_at = 3; google.protobuf.Timestamp updated_at = 3;
} }
message UpdateTenantUserRequest { message UpdateTenantUserRequest {
// Tenant user object. // Tenant user object.
TenantUser tenant_user = 1; TenantUser tenant_user = 1;
} }
message DeleteTenantUserRequest { message DeleteTenantUserRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// User ID (UUID). // User ID (UUID).
string user_id = 2; string user_id = 2;
} }
message ListTenantUsersRequest { message ListTenantUsersRequest {
// Tenant ID (UUID). // Tenant ID (UUID).
string tenant_id = 1; string tenant_id = 1;
// Max number of tenants to return in the result-set. // Max number of tenants to return in the result-set.
uint32 limit = 2; uint32 limit = 2;
// Offset in the result-set (for pagination). // Offset in the result-set (for pagination).
uint32 offset = 3; uint32 offset = 3;
} }
message ListTenantUsersResponse { message ListTenantUsersResponse {
// Total number of tenants. // Total number of tenants.
uint32 total_count = 1; uint32 total_count = 1;
// Result-set. // Result-set.
repeated TenantUserListItem result = 2; repeated TenantUserListItem result = 2;
} }

View File

@ -0,0 +1,2 @@
alter table application drop column tags;
alter table tenant drop column tags;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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
}, },

View File

@ -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 {

View File

@ -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();

View File

@ -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
}, },

View File

@ -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()))?;

View File

@ -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

View File

@ -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,
} }
} }

View File

@ -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()
} }

View File

@ -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(

View File

@ -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 {

View File

@ -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 {

View File

@ -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}>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}> <Tabs>
<Input disabled={props.disabled} /> <Tabs.TabPane tab="General" key="1">
</Form.Item> <Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Form.Item label="Description" name="description"> <Input disabled={props.disabled} />
<Input.TextArea disabled={props.disabled} /> </Form.Item>
</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> <Form.Item>
<Button type="primary" htmlType="submit" disabled={props.disabled}> <Button type="primary" htmlType="submit" disabled={props.disabled}>
Submit Submit

View File

@ -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,70 +25,120 @@ 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}>
<Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}> <Tabs>
<Input disabled={props.disabled} /> <Tabs.TabPane tab="General" key="1">
</Form.Item> <Form.Item label="Name" name="name" rules={[{ required: true, message: "Please enter a name!" }]}>
<Form.Item label="Description" name="description"> <Input disabled={props.disabled} />
<Input.TextArea disabled={props.disabled} />
</Form.Item>
<Row>
<Col span={8}>
<Form.Item
label="Tenant can have gateways"
name="canHaveGateways"
tooltip="When checked, the tenant can add gateways. Note that the usage of the gateways is not limited to this tenant only unless these are marked private."
valuePropName="checked"
>
<Switch disabled={props.disabled} />
</Form.Item> </Form.Item>
</Col> <Form.Item label="Description" name="description">
<Col span={8}> <Input.TextArea disabled={props.disabled} />
<Form.Item
label="Gateways are private (uplink)"
name="privateGatewaysUp"
tooltip="Uplink received by gateways of this tenant can only be used by the devices of this tenant."
valuePropName="checked"
>
<Switch disabled={props.disabled} />
</Form.Item> </Form.Item>
</Col> <Row>
<Col span={8}> <Col span={8}>
<Form.Item <Form.Item
label="Gateways are private (downlink)" label="Tenant can have gateways"
name="privateGatewaysDown" name="canHaveGateways"
tooltip="Other tenants can not use the gateways of this tenant for downlinks. This can be useful in case uplinks are shared with other tenants, but you want to avoid other tenants using downlink airtime of your gateways." tooltip="When checked, the tenant can add gateways. Note that the usage of the gateways is not limited to this tenant only unless these are marked private."
valuePropName="checked" valuePropName="checked"
> >
<Switch disabled={props.disabled} /> <Switch disabled={props.disabled} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> <Col span={8}>
<Row> <Form.Item
<Col span={12}> label="Gateways are private (uplink)"
<Form.Item name="privateGatewaysUp"
label="Max. gateway count" tooltip="Uplink received by gateways of this tenant can only be used by the devices of this tenant."
name="maxGatewayCount" valuePropName="checked"
tooltip="The maximum number of gateways that can be added by this tenant (0 = unlimited)." >
> <Switch disabled={props.disabled} />
<InputNumber min={0} disabled={props.disabled} /> </Form.Item>
</Form.Item> </Col>
</Col> <Col span={8}>
<Col span={12}> <Form.Item
<Form.Item label="Gateways are private (downlink)"
label="Max. device count" name="privateGatewaysDown"
name="maxDeviceCount" tooltip="Other tenants can not use the gateways of this tenant for downlinks. This can be useful in case uplinks are shared with other tenants, but you want to avoid other tenants using downlink airtime of your gateways."
required={true} valuePropName="checked"
tooltip="The maximum number of devices that can be added by this tenant (0 = unlimited)." >
> <Switch disabled={props.disabled} />
<InputNumber min={0} disabled={props.disabled} /> </Form.Item>
</Form.Item> </Col>
</Col> </Row>
</Row> <Row>
<Col span={12}>
<Form.Item
label="Max. gateway count"
name="maxGatewayCount"
tooltip="The maximum number of gateways that can be added by this tenant (0 = unlimited)."
>
<InputNumber min={0} disabled={props.disabled} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="Max. device count"
name="maxDeviceCount"
required={true}
tooltip="The maximum number of devices that can be added by this tenant (0 = unlimited)."
>
<InputNumber min={0} disabled={props.disabled} />
</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> <Form.Item>
<Button type="primary" htmlType="submit" disabled={props.disabled}> <Button type="primary" htmlType="submit" disabled={props.disabled}>
Submit Submit