├── .docker-compose └── postgresql │ └── initdb │ ├── 0001-init-chirpstack_as.sh │ ├── 0002-chirpstack_as_extensions.sh │ ├── 0003-init-chirpstack_integration.sh │ └── 0004-chirpstack_integration_extensions.sh ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1.bug.md │ ├── 2.feature.md │ └── config.yml └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── .goreleaser.yml ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile-devel ├── LICENSE ├── Makefile ├── README.md ├── cmd └── chirpstack-application-server │ ├── cmd │ ├── configfile.go │ ├── root.go │ ├── root_run.go │ ├── root_run_syslog.go │ ├── root_run_syslog_stub.go │ └── version.go │ └── main.go ├── docker-compose.yml ├── examples └── js-codecs │ └── README.md ├── go.mod ├── go.sum ├── internal ├── api │ ├── api.go │ ├── as │ │ ├── as.go │ │ └── as_test.go │ ├── external │ │ ├── application.go │ │ ├── application_test.go │ │ ├── auth │ │ │ ├── auth.go │ │ │ ├── auth_test.go │ │ │ ├── errors.go │ │ │ ├── validators.go │ │ │ └── validators_test.go │ │ ├── device.go │ │ ├── device_profile.go │ │ ├── device_profile_test.go │ │ ├── device_queue.go │ │ ├── device_queue_test.go │ │ ├── device_test.go │ │ ├── external.go │ │ ├── external_test.go │ │ ├── gateway.go │ │ ├── gateway_profile.go │ │ ├── gateway_profile_test.go │ │ ├── gateway_test.go │ │ ├── internal.go │ │ ├── internal_test.go │ │ ├── multicast_group_test.go │ │ ├── mutlicast_group.go │ │ ├── network_server.go │ │ ├── network_server_test.go │ │ ├── oidc │ │ │ ├── oidc.go │ │ │ └── oidc_test.go │ │ ├── organization.go │ │ ├── organization_test.go │ │ ├── service_profile.go │ │ ├── service_profile_test.go │ │ ├── user.go │ │ ├── user_test.go │ │ └── validator_test.go │ ├── helpers │ │ ├── errors.go │ │ └── helpers.go │ └── js │ │ ├── join_server.go │ │ ├── join_server_test.go │ │ └── prometheus.go ├── backend │ └── networkserver │ │ ├── mock │ │ ├── client.go │ │ └── pool.go │ │ └── networkserver.go ├── codec │ ├── cayennelpp │ │ ├── cayennelpp.go │ │ └── cayennelpp_test.go │ ├── codec.go │ └── js │ │ ├── js.go │ │ └── js_test.go ├── config │ └── config.go ├── downlink │ ├── downlink.go │ └── downlink_test.go ├── eventlog │ ├── eventlog.go │ └── eventlog_test.go ├── events │ └── uplink │ │ ├── errors.go │ │ └── uplink.go ├── gwping │ ├── gwping.go │ └── gwping_test.go ├── integration │ ├── amqp │ │ ├── amqp.go │ │ ├── amqp_test.go │ │ ├── channel_pool.go │ │ └── channel_pool_test.go │ ├── awssns │ │ └── aws_sns.go │ ├── azureservicebus │ │ ├── azure_service_bus.go │ │ └── azure_service_bus_test.go │ ├── gcppubsub │ │ └── gcppubsub.go │ ├── http │ │ ├── errors.go │ │ ├── http.go │ │ └── http_test.go │ ├── influxdb │ │ ├── errors.go │ │ ├── influxdb.go │ │ └── influxdb_test.go │ ├── integration.go │ ├── integration_test.go │ ├── kafka │ │ ├── kafka.go │ │ └── kafka_test.go │ ├── logger │ │ ├── logger.go │ │ └── logger_test.go │ ├── loracloud │ │ ├── buffer.go │ │ ├── buffer_test.go │ │ ├── client │ │ │ ├── das │ │ │ │ ├── das.go │ │ │ │ ├── das_test.go │ │ │ │ └── structs.go │ │ │ ├── geolocation │ │ │ │ ├── geolocation.go │ │ │ │ ├── geolocation_test.go │ │ │ │ ├── structs.go │ │ │ │ └── structs_test.go │ │ │ └── helpers │ │ │ │ ├── helpers.go │ │ │ │ └── helpers_test.go │ │ ├── frame_rx_info.go │ │ ├── frame_rx_info.pb.go │ │ ├── frame_rx_info.proto │ │ ├── loracloud.go │ │ ├── loracloud_test.go │ │ └── prometheus.go │ ├── marshaler │ │ ├── marshaler.go │ │ └── marshaler_test.go │ ├── mock │ │ └── mock.go │ ├── models │ │ ├── interfaces.go │ │ └── models.go │ ├── mqtt │ │ ├── certificates.go │ │ ├── metrics.go │ │ ├── mqtt.go │ │ └── mqtt_test.go │ ├── multi │ │ ├── multi.go │ │ └── multi_test.go │ ├── mydevices │ │ ├── mydevices.go │ │ └── mydevices_test.go │ ├── pilotthings │ │ ├── pilot_things.go │ │ └── pilot_things_test.go │ ├── postgresql │ │ ├── migrations │ │ │ ├── 0001_initial.down.sql │ │ │ ├── 0001_initial.up.sql │ │ │ ├── 0002_devaddr_uplink_type.down.sql │ │ │ ├── 0002_devaddr_uplink_type.up.sql │ │ │ ├── 0003_tx_ack.down.sql │ │ │ ├── 0003_tx_ack.up.sql │ │ │ ├── 0004_device_up_txinfo.down.sql │ │ │ └── 0004_device_up_txinfo.up.sql │ │ ├── postgresql.go │ │ └── postgresql_test.go │ └── thingsboard │ │ ├── thingsboard.go │ │ └── thingsboard_test.go ├── logging │ └── logging.go ├── migrations │ └── code │ │ ├── migrate_gateway_stats.go │ │ └── migrate_to_cluster_keys.go ├── monitoring │ ├── healthcheck.go │ └── monitoring.go ├── multicast │ └── multicast.go ├── storage │ ├── api_key.go │ ├── api_key_test.go │ ├── application.go │ ├── application_test.go │ ├── code_migration.go │ ├── code_migration_test.go │ ├── db.go │ ├── device.go │ ├── device_profile.go │ ├── device_profile_test.go │ ├── device_test.go │ ├── errors.go │ ├── gateway.go │ ├── gateway_profile.go │ ├── gateway_profile_test.go │ ├── gateway_test.go │ ├── integrations.go │ ├── integrations_test.go │ ├── metrics.go │ ├── metrics_test.go │ ├── migrations │ │ ├── 0001_initial.down.sql │ │ ├── 0001_initial.up.sql │ │ ├── 0002_join_accept_params.down.sql │ │ ├── 0002_join_accept_params.up.sql │ │ ├── 0003_rx_window_and_rx2_dr.down.sql │ │ ├── 0003_rx_window_and_rx2_dr.up.sql │ │ ├── 0004_add_node_apps_nwks_key_name_devaddr.down.sql │ │ ├── 0004_add_node_apps_nwks_key_name_devaddr.up.sql │ │ ├── 0005_add_queue.down.sql │ │ ├── 0005_add_queue.up.sql │ │ ├── 0006_remove_application_table.down.sql │ │ ├── 0006_remove_application_table.up.sql │ │ ├── 0007_migrate_channels_to_channel_list.down.sql │ │ ├── 0007_migrate_channels_to_channel_list.up.sql │ │ ├── 0008_relax_fcnt.down.sql │ │ ├── 0008_relax_fcnt.up.sql │ │ ├── 0009_adr_interval_and_install_margin.down.sql │ │ ├── 0009_adr_interval_and_install_margin.up.sql │ │ ├── 0010_recreate_application_table.down.sql │ │ ├── 0010_recreate_application_table.up.sql │ │ ├── 0011_node_description_and_is_abp.down.sql │ │ ├── 0011_node_description_and_is_abp.up.sql │ │ ├── 0012_class_c_node.down.sql │ │ ├── 0012_class_c_node.up.sql │ │ ├── 0013_application_settings.down.sql │ │ ├── 0013_application_settings.up.sql │ │ ├── 0014_users_and_application_users.down.sql │ │ ├── 0014_users_and_application_users.up.sql │ │ ├── 0015_organizations.down.sql │ │ ├── 0015_organizations.up.sql │ │ ├── 0016_delete_channel_list.down.sql │ │ ├── 0016_delete_channel_list.up.sql │ │ ├── 0017_integrations.down.sql │ │ ├── 0017_integrations.up.sql │ │ ├── 0018_gateway_ping.down.sql │ │ ├── 0018_gateway_ping.up.sql │ │ ├── 0019_node_prefix_search.down.sql │ │ ├── 0019_node_prefix_search.up.sql │ │ ├── 0020_backend_interfaces.down.sql │ │ ├── 0020_backend_interfaces.up.sql │ │ ├── 0021_user_email_and_note.down.sql │ │ ├── 0021_user_email_and_note.up.sql │ │ ├── 0022_add_device_queue_mapping.down.sql │ │ ├── 0022_add_device_queue_mapping.up.sql │ │ ├── 0023_payload_decoder.down.sql │ │ ├── 0023_payload_decoder.up.sql │ │ ├── 0024_network_server_certs.down.sql │ │ ├── 0024_network_server_certs.up.sql │ │ ├── 0025_device_status.down.sql │ │ ├── 0025_device_status.up.sql │ │ ├── 0026_network_server_gw_discovery.down.sql │ │ ├── 0026_network_server_gw_discovery.up.sql │ │ ├── 0027_global_search.down.sql │ │ ├── 0027_global_search.up.sql │ │ ├── 0028_gateway_profile.down.sql │ │ ├── 0028_gateway_profile.up.sql │ │ ├── 0029_cleanup_old_tables.down.sql │ │ ├── 0029_cleanup_old_tables.up.sql │ │ ├── 0030_lorawan_11_keys.down.sql │ │ ├── 0030_lorawan_11_keys.up.sql │ │ ├── 0031_cleanup_indices.down.sql │ │ ├── 0031_cleanup_indices.up.sql │ │ ├── 0032_fix_table_constraints.down.sql │ │ ├── 0032_fix_table_constraints.up.sql │ │ ├── 0033_drop_device_queue_mapping.down.sql │ │ ├── 0033_drop_device_queue_mapping.up.sql │ │ ├── 0034_drop_nwk_session_keys.down.sql │ │ ├── 0034_drop_nwk_session_keys.up.sql │ │ ├── 0035_multicast.down.sql │ │ ├── 0035_multicast.up.sql │ │ ├── 0036_device_location.down.sql │ │ ├── 0036_device_location.up.sql │ │ ├── 0037_fix_device_status.down.sql │ │ ├── 0037_fix_device_status.up.sql │ │ ├── 0038_device_profile_payload_codec.down.sql │ │ ├── 0038_device_profile_payload_codec.up.sql │ │ ├── 0039_application_add_dr.down.sql │ │ ├── 0039_application_add_dr.up.sql │ │ ├── 0040_fuota.down.sql │ │ ├── 0040_fuota.up.sql │ │ ├── 0041_device_variables.down.sql │ │ ├── 0041_device_variables.up.sql │ │ ├── 0042_drop_multicast_f_cnt.down.sql │ │ ├── 0042_drop_multicast_f_cnt.up.sql │ │ ├── 0043_extend_org_user_permissions.down.sql │ │ ├── 0043_extend_org_user_permissions.up.sql │ │ ├── 0044_gateway_location_first_and_last_seen.down.sql │ │ ├── 0044_gateway_location_first_and_last_seen.up.sql │ │ ├── 0045_code_migrations.down.sql │ │ ├── 0045_code_migrations.up.sql │ │ ├── 0046_devaddr_appskey_to_device.down.sql │ │ ├── 0046_devaddr_appskey_to_device.up.sql │ │ ├── 0047_cleanup_device_activation.down.sql │ │ ├── 0047_cleanup_device_activation.up.sql │ │ ├── 0048_change_device_tags_index.down.sql │ │ ├── 0048_change_device_tags_index.up.sql │ │ ├── 0049_gateway_tags_metadata.down.sql │ │ ├── 0049_gateway_tags_metadata.up.sql │ │ ├── 0050_device_profile_tags.down.sql │ │ ├── 0050_device_profile_tags.up.sql │ │ ├── 0051_api_keys.down.sql │ │ ├── 0051_api_keys.up.sql │ │ ├── 0052_user_openid_connect.down.sql │ │ ├── 0052_user_openid_connect.up.sql │ │ ├── 0053_org_max_gateway_device.down.sql │ │ ├── 0053_org_max_gateway_device.up.sql │ │ ├── 0054_device_profile_uplink_interval.down.sql │ │ ├── 0054_device_profile_uplink_interval.up.sql │ │ ├── 0055_gateway_profile_stats_interval.down.sql │ │ ├── 0055_gateway_profile_stats_interval.up.sql │ │ ├── 0056_remove_fuota.down.sql │ │ ├── 0056_remove_fuota.up.sql │ │ ├── 0057_mqtt_integration_cert.down.sql │ │ ├── 0057_mqtt_integration_cert.up.sql │ │ ├── 0058_gateway_service_profile.down.sql │ │ ├── 0058_gateway_service_profile.up.sql │ │ ├── 0059_multicast_group_application_id.down.sql │ │ ├── 0059_multicast_group_application_id.up.sql │ │ ├── 0060_dev_addr_to_global_search.down.sql │ │ ├── 0060_dev_addr_to_global_search.up.sql │ │ └── code │ │ │ ├── migrate_to_golang_migrate.go │ │ │ └── multicast_group_validation.go │ ├── multicast_group.go │ ├── multicast_group_test.go │ ├── network_server.go │ ├── network_server_helpers.go │ ├── network_server_test.go │ ├── organization.go │ ├── organization_test.go │ ├── search.go │ ├── search_test.go │ ├── service_profile.go │ ├── service_profile_test.go │ ├── storage.go │ ├── storage_test.go │ ├── user.go │ └── user_test.go ├── test │ ├── ca_cert.pem │ ├── ca_private.pem │ └── test.go └── tools │ ├── swagger │ └── main.go │ └── tools.go ├── packaging ├── files │ ├── chirpstack-application-server.init │ ├── chirpstack-application-server.rotate │ ├── chirpstack-application-server.service │ └── chirpstack-application-server.toml └── scripts │ ├── post-install.sh │ ├── post-remove.sh │ └── pre-install.sh ├── static ├── static.go ├── swagger │ └── index.html └── vendor │ └── swagger │ ├── LICENSE │ ├── css │ ├── print.css │ ├── reset.css │ ├── screen.css │ ├── style.css │ └── typography.css │ ├── fonts │ ├── DroidSans-Bold.ttf │ └── DroidSans.ttf │ ├── images │ ├── collapse.gif │ ├── expand.gif │ ├── explorer_icons.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo_small.png │ ├── pet_store_api.png │ ├── throbber.gif │ └── wordnik_api.png │ ├── lib │ ├── backbone-min.js │ ├── es5-shim.js │ ├── handlebars-4.0.5.js │ ├── highlight.9.1.0.pack.js │ ├── highlight.9.1.0.pack_extended.js │ ├── jquery-1.8.0.min.js │ ├── jquery.ba-bbq.min.js │ ├── jquery.slideto.min.js │ ├── jquery.wiggle.min.js │ ├── js-yaml.min.js │ ├── jsoneditor.min.js │ ├── lodash.min.js │ ├── marked.js │ ├── object-assign-pollyfill.js │ ├── sanitize-html.min.js │ └── swagger-oauth.js │ └── swagger-ui.min.js └── ui ├── README.md ├── package-lock.json ├── package.json ├── public ├── icon.png ├── index.html ├── integrations │ ├── aws_sns.png │ ├── azure_service_bus.png │ ├── gcp_pubsub.png │ ├── http.png │ ├── influxdb.png │ ├── loracloud.png │ ├── mqtt.png │ ├── my_devices.png │ ├── pilot_things.png │ └── thingsboard.png ├── logo │ └── logo.png └── manifest.json └── src ├── App.js ├── App.test.js ├── classes └── FormComponent.js ├── components ├── AESKeyField.js ├── Admin.js ├── AutocompleteSelect.js ├── DataTable.js ├── DevAddrField.js ├── DeviceAdmin.js ├── DurationField.js ├── EUI64Field.js ├── Footer.js ├── Form.js ├── FormControl.js ├── GatewayAdmin.js ├── Heatmap.js ├── JSONTree.js ├── KVForm.js ├── LoRaWANFrameLog.js ├── Loaded.js ├── MapTileLayer.js ├── Notifications.js ├── Paper.js ├── SetupHelper.js ├── SideNav.js ├── TableCellLink.js ├── TitleBar.js ├── TitleBarButton.js ├── TitleBarTitle.js └── TopNav.js ├── dispatcher.js ├── history.js ├── index.css ├── index.js ├── stores ├── ApplicationStore.js ├── DeviceProfileStore.js ├── DeviceQueueStore.js ├── DeviceStore.js ├── GatewayProfileStore.js ├── GatewayStore.js ├── InternalStore.js ├── LocationStore.js ├── MulticastGroupStore.js ├── NetworkServerStore.js ├── NotificationStore.js ├── OrganizationStore.js ├── ServiceProfileStore.js ├── SessionStore.js ├── UserStore.js └── helpers.js ├── theme.js └── views ├── api-keys ├── APIKeyForm.js ├── CreateAdminAPIKey.js ├── CreateOrganizationAPIKey.js ├── ListAdminAPIKeys.js └── ListOrganizationAPIKeys.js ├── applications ├── ApplicationForm.js ├── ApplicationLayout.js ├── CreateApplication.js ├── ListApplications.js ├── ListIntegrations.js ├── UpdateApplication.js └── integrations │ ├── AWSSNSCard.js │ ├── AWSSNSIntegrationForm.js │ ├── AzureServiceBusCard.js │ ├── AzureServiceBusIntegrationForm.js │ ├── CreateAWSSNSIntegration.js │ ├── CreateAzureServiceBusIntegration.js │ ├── CreateGCPPubSubIntegration.js │ ├── CreateHTTPIntegration.js │ ├── CreateInfluxDBIntegration.js │ ├── CreateLoRaCloudIntegration.js │ ├── CreateMyDevicesIntegration.js │ ├── CreatePilotThingsIntegration.js │ ├── CreateThingsBoardIntegration.js │ ├── GCPPubSub.js │ ├── GCPPubSubIntegrationForm.js │ ├── HTTP.js │ ├── HTTPIntegrationForm.js │ ├── InfluxDBCard.js │ ├── InfluxDBIntegrationForm.js │ ├── LoRaCloudCard.js │ ├── LoRaCloudIntegrationForm.js │ ├── MQTTCard.js │ ├── MQTTCertificate.js │ ├── MyDevicesCard.js │ ├── MyDevicesIntegrationForm.js │ ├── PilotThingsCard.js │ ├── PilotThingsIntegrationForm.js │ ├── ThingsBoardIntegrationForm.js │ ├── ThingsboardCard.js │ ├── UpdateAWSSNSIntegration.js │ ├── UpdateAzureServiceBusIntegration.js │ ├── UpdateGCPPubSubIntegration.js │ ├── UpdateHTTPIntegration.js │ ├── UpdateInfluxDBIntegration.js │ ├── UpdateLoRaCloudIntegration.js │ ├── UpdateMyDevicesIntegration.js │ ├── UpdatePilotThingsIntegration.js │ └── UpdateThingsBoardIntegration.js ├── dashboard └── Dashboard.js ├── device-profiles ├── CreateDeviceProfile.js ├── DeviceProfileForm.js ├── DeviceProfileLayout.js ├── ListDeviceProfiles.js └── UpdateDeviceProfile.js ├── devices ├── CreateDevice.js ├── DeviceActivation.js ├── DeviceData.js ├── DeviceDetails.js ├── DeviceForm.js ├── DeviceFrames.js ├── DeviceKeys.js ├── DeviceLayout.js ├── DeviceQueueItemForm.js ├── ListDevices.js └── UpdateDevice.js ├── gateway-profiles ├── CreateGatewayProfile.js ├── GatewayProfileForm.js ├── GatewayProfileLayout.js ├── ListGatewayProfiles.js └── UpdateGatewayProfile.js ├── gateways ├── CreateGateway.js ├── GatewayCertificate.js ├── GatewayDetails.js ├── GatewayDiscovery.js ├── GatewayForm.js ├── GatewayFrames.js ├── GatewayLayout.js ├── ListGateways.js └── UpdateGateway.js ├── multicast-groups ├── CreateMulticastGroup.js ├── ListMulticastGroupDevices.js ├── ListMulticastGroups.js ├── MulticastGroupForm.js ├── MulticastGroupLayout.js └── UpdateMulticastGroup.js ├── network-servers ├── CreateNetworkServer.js ├── ListNetworkServers.js ├── NetworkServerForm.js ├── NetworkServerLayout.js └── UpdateNetworkServer.js ├── organizations ├── CreateOrganization.js ├── CreateOrganizationUser.js ├── ListOrganizationUsers.js ├── ListOrganizations.js ├── OrganizationDashboard.js ├── OrganizationForm.js ├── OrganizationLayout.js ├── OrganizationRedirect.js ├── OrganizationUserForm.js ├── OrganizationUserLayout.js ├── UpdateOrganization.js └── UpdateOrganizationUser.js ├── search └── Search.js ├── service-profiles ├── CreateServiceProfile.js ├── ListServiceProfiles.js ├── ServiceProfileForm.js ├── ServiceProfileLayout.js └── UpdateServiceProfile.js └── users ├── ChangeUserPassword.js ├── CreateUser.js ├── ListUsers.js ├── Login.js ├── UpdateUser.js ├── UserForm.js └── UserLayout.js /.docker-compose/postgresql/initdb/0001-init-chirpstack_as.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 5 | create role chirpstack_as with login password 'chirpstack_as'; 6 | create database chirpstack_as with owner chirpstack_as; 7 | EOSQL 8 | -------------------------------------------------------------------------------- /.docker-compose/postgresql/initdb/0002-chirpstack_as_extensions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname="chirpstack_as" <<-EOSQL 5 | create extension pg_trgm; 6 | create extension hstore; 7 | EOSQL 8 | -------------------------------------------------------------------------------- /.docker-compose/postgresql/initdb/0003-init-chirpstack_integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 5 | create role chirpstack_integration with login password 'chirpstack_integration'; 6 | create database chirpstack_integration with owner chirpstack_integration; 7 | EOSQL 8 | -------------------------------------------------------------------------------- /.docker-compose/postgresql/initdb/0004-chirpstack_integration_extensions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname="chirpstack_integration" <<-EOSQL 5 | create extension hstore; 6 | EOSQL 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: chirpstack 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1.bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report for ChirpStack Application Server 4 | --- 5 | 6 | 10 | 11 | 12 | 13 | - [ ] The issue is present in the latest release. 14 | - [ ] I have searched the [issues](https://github.com/brocaar/chirpstack-application-server/issues) of this repository and believe that this is not a duplicate. 15 | 16 | ## What happened? 17 | 18 | ## What did you expect? 19 | 20 | ## Steps to reproduce this issue 21 | 22 | Steps: 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 4. 28 | 29 | ## Could you share your log output? 30 | 31 | 36 | ```shell 37 | 38 | ``` 39 | 40 | ## Your Environment 41 | 42 | 59 | 60 | 61 | | Component | Version | 62 | | --------------------| ------- | 63 | | Application Server | v?.?.? | 64 | | Network Server | | 65 | | Gateway Bridge | | 66 | | Chirpstack API | | 67 | | Geolocation | | 68 | | Concentratord | | 69 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new idea for ChirpStack Application Server 4 | --- 5 | 6 | 10 | 11 | 12 | 13 | - [ ] I have searched the [issues](https://github.com/brocaar/chirpstack-application-server/issues) of this repository and believe that this is not a duplicate. 14 | 15 | ## Summary 16 | 17 | 18 | 19 | ## What is the use-case? 20 | 21 | 22 | 23 | ## Implementation description 24 | 25 | 28 | 29 | ## Can you implement this by yourself and make a pull request? 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false # force the usage of a template 2 | contact_links: 3 | - name: ChirpStack Community Forum 4 | url: https://forum.chirpstack.io/ 5 | about: I need support with ChirpStack. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # hidden files 2 | .* 3 | 4 | # hidden files except .docker-compose 5 | !.docker-compose/ 6 | 7 | # logs 8 | *.log 9 | 10 | # config files 11 | /*.toml 12 | 13 | # coverage file 14 | /coverage.out 15 | 16 | # builds 17 | /dist 18 | /build 19 | /static/logo 20 | /static/integrations 21 | /static/static 22 | /static/swagger/*.json 23 | /static/index.html 24 | /static/asset-manifest.json 25 | /static/service-worker.js 26 | /static/manifest.json 27 | /static/icon.png 28 | /static/precache-manifest.*.js 29 | /ui/build 30 | /docs/public 31 | 32 | # dependencies 33 | /vendor 34 | 35 | # certificates 36 | /certs 37 | 38 | # node modules 39 | node_modules 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/themes/chirpstack-hugo-theme"] 2 | path = docs/themes/chirpstack-hugo-theme 3 | url = git@github.com:brocaar/chirpstack-hugo-theme.git 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: chirpstack-application-server 2 | 3 | builds: 4 | - main: cmd/chirpstack-application-server/main.go 5 | binary: chirpstack-application-server 6 | goos: 7 | - windows 8 | - darwin 9 | - linux 10 | goarch: 11 | - amd64 12 | - 386 13 | - arm 14 | - arm64 15 | goarm: 16 | - 5 17 | - 6 18 | - 7 19 | ignore: 20 | - goos: darwin 21 | goarch: 386 22 | 23 | release: 24 | disable: true 25 | 26 | nfpm: 27 | vendor: ChirpStack 28 | homepage: https://www.loraserver.io/ 29 | maintainer: Orne Brocaar 30 | description: ChirpStack Application Server 31 | license: MIT 32 | formats: 33 | - deb 34 | - rpm 35 | bindir: /usr/bin 36 | files: 37 | "packaging/files/chirpstack-application-server.rotate": "/etc/logrotate.d/chirpstack-application-server" 38 | "packaging/files/chirpstack-application-server.init": "/usr/lib/chirpstack-application-server/scripts/chirpstack-application-server.init" 39 | "packaging/files/chirpstack-application-server.service": "/usr/lib/chirpstack-application-server/scripts/chirpstack-application-server.service" 40 | config_files: 41 | "packaging/files/chirpstack-application-server.toml": "/etc/chirpstack-application-server/chirpstack-application-server.toml" 42 | scripts: 43 | preinstall: "packaging/scripts/pre-install.sh" 44 | postinstall: "packaging/scripts/post-install.sh" 45 | postremove: "packaging/scripts/post-remove.sh" 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | There are a couple of ways to get involved: 4 | 5 | * Join the discussions: 6 | * ChirpStack Network Server project forum [https://forum.chirpstack.io/](https://forum.chirpstack.io/) 7 | * Report bugs or make feature-requests by opening an issue at [https://github.com/brocaar/chirpstack-application-server/issues](https://github.com/brocaar/chirpstack-application-server/issues) 8 | * Help fixing issues or improve documentation by creating pull-requests 9 | 10 | 11 | When you would like to add new features, please discuss the feature first 12 | by creating an issue describing your feature, how you're planning to implement 13 | it, what the usecase is etc... 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.3-alpine3.16 AS development 2 | 3 | ENV PROJECT_PATH=/chirpstack-application-server 4 | ENV PATH=$PATH:$PROJECT_PATH/build 5 | ENV CGO_ENABLED=0 6 | ENV GO_EXTRA_BUILD_ARGS="-a -installsuffix cgo" 7 | 8 | RUN apk add --no-cache ca-certificates make git bash alpine-sdk nodejs npm 9 | 10 | RUN mkdir -p $PROJECT_PATH 11 | COPY . $PROJECT_PATH 12 | WORKDIR $PROJECT_PATH 13 | 14 | RUN make dev-requirements ui-requirements 15 | RUN make 16 | 17 | FROM alpine:3.17.0 AS production 18 | 19 | RUN apk --no-cache add ca-certificates 20 | COPY --from=development /chirpstack-application-server/build/chirpstack-application-server /usr/bin/chirpstack-application-server 21 | USER nobody:nogroup 22 | ENTRYPOINT ["/usr/bin/chirpstack-application-server"] 23 | -------------------------------------------------------------------------------- /Dockerfile-devel: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.3-alpine3.16 2 | 3 | ENV PROJECT_PATH=/chirpstack-application-server 4 | ENV PATH=$PATH:$PROJECT_PATH/build 5 | ENV CGO_ENABLED=0 6 | ENV GO_EXTRA_BUILD_ARGS="-a -installsuffix cgo" 7 | 8 | RUN apk add --no-cache ca-certificates make git bash alpine-sdk nodejs npm rpm protobuf 9 | 10 | RUN git clone https://github.com/protocolbuffers/protobuf.git /protobuf 11 | 12 | RUN mkdir -p $PROJECT_PATH 13 | COPY . $PROJECT_PATH 14 | WORKDIR $PROJECT_PATH 15 | 16 | RUN git config --global --add safe.directory $PROJECT_PATH 17 | RUN make dev-requirements 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Orne Brocaar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/chirpstack-application-server/cmd/root_run_syslog.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package cmd 4 | 5 | import ( 6 | "log/syslog" 7 | 8 | "github.com/pkg/errors" 9 | log "github.com/sirupsen/logrus" 10 | lsyslog "github.com/sirupsen/logrus/hooks/syslog" 11 | 12 | "github.com/brocaar/chirpstack-application-server/internal/config" 13 | ) 14 | 15 | func setSyslog() error { 16 | if !config.C.General.LogToSyslog { 17 | return nil 18 | } 19 | 20 | var prio syslog.Priority 21 | 22 | switch log.StandardLogger().Level { 23 | case log.DebugLevel: 24 | prio = syslog.LOG_USER | syslog.LOG_DEBUG 25 | case log.InfoLevel: 26 | prio = syslog.LOG_USER | syslog.LOG_INFO 27 | case log.WarnLevel: 28 | prio = syslog.LOG_USER | syslog.LOG_WARNING 29 | case log.ErrorLevel: 30 | prio = syslog.LOG_USER | syslog.LOG_ERR 31 | case log.FatalLevel: 32 | prio = syslog.LOG_USER | syslog.LOG_CRIT 33 | case log.PanicLevel: 34 | prio = syslog.LOG_USER | syslog.LOG_CRIT 35 | } 36 | 37 | hook, err := lsyslog.NewSyslogHook("", "", prio, "chirpstack-application-server") 38 | if err != nil { 39 | return errors.Wrap(err, "get syslog hook error") 40 | } 41 | 42 | log.AddHook(hook) 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /cmd/chirpstack-application-server/cmd/root_run_syslog_stub.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package cmd 4 | 5 | import ( 6 | log "github.com/sirupsen/logrus" 7 | 8 | "github.com/brocaar/chirpstack-application-server/internal/config" 9 | ) 10 | 11 | func setSyslog() error { 12 | if config.C.General.LogToSyslog { 13 | log.Fatal("syslog logging is not supported on Windows") 14 | } 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /cmd/chirpstack-application-server/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var versionCmd = &cobra.Command{ 10 | Use: "version", 11 | Short: "Print the ChirpStack Application Server version", 12 | Run: func(cmd *cobra.Command, args []string) { 13 | fmt.Println(version) 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /cmd/chirpstack-application-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "google.golang.org/grpc/grpclog" 8 | 9 | "github.com/brocaar/chirpstack-application-server/cmd/chirpstack-application-server/cmd" 10 | ) 11 | 12 | // grpcLogger implements a wrapper around the logrus Logger to make it 13 | // compatible with the grpc LoggerV2. It seems that V is not (always) 14 | // called, therefore the Info* methods are overridden as we want to 15 | // log these as debug info. 16 | type grpcLogger struct { 17 | *log.Logger 18 | } 19 | 20 | func (gl *grpcLogger) V(l int) bool { 21 | level, ok := map[log.Level]int{ 22 | log.DebugLevel: 0, 23 | log.InfoLevel: 1, 24 | log.WarnLevel: 2, 25 | log.ErrorLevel: 3, 26 | log.FatalLevel: 4, 27 | }[log.GetLevel()] 28 | if !ok { 29 | return false 30 | } 31 | 32 | return l >= level 33 | } 34 | 35 | func (gl *grpcLogger) Info(args ...interface{}) { 36 | if log.GetLevel() == log.DebugLevel { 37 | log.Debug(args...) 38 | } 39 | } 40 | 41 | func (gl *grpcLogger) Infoln(args ...interface{}) { 42 | if log.GetLevel() == log.DebugLevel { 43 | log.Debug(args...) 44 | } 45 | } 46 | 47 | func (gl *grpcLogger) Infof(format string, args ...interface{}) { 48 | if log.GetLevel() == log.DebugLevel { 49 | log.Debugf(format, args...) 50 | } 51 | } 52 | 53 | func init() { 54 | log.SetFormatter(&log.TextFormatter{ 55 | TimestampFormat: time.RFC3339Nano, 56 | }) 57 | 58 | grpclog.SetLoggerV2(&grpcLogger{log.StandardLogger()}) 59 | } 60 | 61 | var version string // set by the compiler 62 | 63 | func main() { 64 | cmd.Execute(version) 65 | } 66 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | chirpstack-application-server: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile-devel 7 | volumes: 8 | - ./:/chirpstack-application-server 9 | # - ../chirpstack-api:/chirpstack-api 10 | links: 11 | - postgres 12 | - redis 13 | - mosquitto 14 | - rabbitmq 15 | - zookeeper 16 | - kafka 17 | environment: 18 | - TEST_POSTGRES_DSN=postgres://chirpstack_as:chirpstack_as@postgres/chirpstack_as?sslmode=disable 19 | - TEST_POSTGRES_INTEGRATION_DSN=postgres://chirpstack_integration:chirpstack_integration@postgres/chirpstack_integration?sslmode=disable 20 | - TEST_REDIS_SERVERS=redis:6379 21 | - TEST_MQTT_SERVER=tcp://mosquitto:1883 22 | - TEST_RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/ 23 | - TEST_KAFKA_BROKER=kafka:9092 24 | 25 | postgres: 26 | image: postgres:13-alpine 27 | environment: 28 | - POSTGRES_HOST_AUTH_METHOD=trust 29 | volumes: 30 | - ./.docker-compose/postgresql/initdb:/docker-entrypoint-initdb.d 31 | 32 | redis: 33 | image: redis:6-alpine 34 | 35 | mosquitto: 36 | image: eclipse-mosquitto:1.6 37 | 38 | rabbitmq: 39 | image: rabbitmq:3-alpine 40 | 41 | zookeeper: 42 | image: 'bitnami/zookeeper:3' 43 | environment: 44 | - ALLOW_ANONYMOUS_LOGIN=yes 45 | kafka: 46 | image: 'bitnami/kafka:2' 47 | environment: 48 | - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 49 | - ALLOW_PLAINTEXT_LISTENER=yes 50 | depends_on: 51 | - zookeeper 52 | -------------------------------------------------------------------------------- /examples/js-codecs/README.md: -------------------------------------------------------------------------------- 1 | # Payload codecs 2 | 3 | This folder is intended for example payload codec functions. 4 | 5 | ## Adding a new function 6 | 7 | To add a payload codec to this repository, please use the following directory 8 | structure: `examples/js-codecs/[Vendor]/[Device].js`, where `[Vendor]` is 9 | replaced by the name of the device vendor and `[Device]` by the name and / or 10 | model of the device. 11 | -------------------------------------------------------------------------------- /internal/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/brocaar/chirpstack-application-server/internal/api/as" 7 | "github.com/brocaar/chirpstack-application-server/internal/api/external" 8 | "github.com/brocaar/chirpstack-application-server/internal/api/js" 9 | "github.com/brocaar/chirpstack-application-server/internal/config" 10 | ) 11 | 12 | // Setup configures the API endpoints. 13 | func Setup(conf config.Config) error { 14 | if err := as.Setup(conf); err != nil { 15 | return errors.Wrap(err, "setup application-server api error") 16 | } 17 | 18 | if err := external.Setup(conf); err != nil { 19 | return errors.Wrap(err, "setup external api error") 20 | } 21 | 22 | if err := js.Setup(conf); err != nil { 23 | return errors.Wrap(err, "setup join-server api error") 24 | } 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/api/external/auth/errors.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "errors" 4 | 5 | // errors 6 | var ( 7 | ErrNoMetadataInContext = errors.New("no metadata in context") 8 | ErrNoAuthorizationInMetadata = errors.New("no authorization-data in metadata") 9 | ErrInvalidAlgorithm = errors.New("invalid algorithm") 10 | ErrInvalidToken = errors.New("invalid token") 11 | ErrNotAuthorized = errors.New("not authorized") 12 | ) 13 | -------------------------------------------------------------------------------- /internal/api/external/external_test.go: -------------------------------------------------------------------------------- 1 | package external 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jmoiron/sqlx" 7 | "github.com/stretchr/testify/require" 8 | "github.com/stretchr/testify/suite" 9 | "golang.org/x/net/context" 10 | 11 | "github.com/brocaar/chirpstack-application-server/internal/config" 12 | "github.com/brocaar/chirpstack-application-server/internal/storage" 13 | "github.com/brocaar/chirpstack-application-server/internal/test" 14 | ) 15 | 16 | // DatabaseTestSuiteBase provides the setup and teardown of the database 17 | // for every test-run. 18 | type DatabaseTestSuiteBase struct { 19 | suite.Suite 20 | tx *storage.TxLogger 21 | } 22 | 23 | // SetupSuite is called once before starting the test-suite. 24 | func (b *DatabaseTestSuiteBase) SetupSuite() { 25 | conf := test.GetConfig() 26 | conf.Monitoring.PerDeviceEventLogMaxHistory = 10 27 | config.Set(conf) 28 | 29 | if err := storage.Setup(conf); err != nil { 30 | panic(err) 31 | } 32 | } 33 | 34 | // SetupTest is called before every test. 35 | func (b *DatabaseTestSuiteBase) SetupTest() { 36 | assert := require.New(b.T()) 37 | 38 | tx, err := storage.DB().Beginx() 39 | if err != nil { 40 | panic(err) 41 | } 42 | b.tx = tx 43 | 44 | storage.RedisClient().FlushAll(context.Background()) 45 | assert.NoError(storage.MigrateDown(storage.DB().DB)) 46 | assert.NoError(storage.MigrateUp(storage.DB().DB)) 47 | } 48 | 49 | // TearDownTest is called after every test. 50 | func (b *DatabaseTestSuiteBase) TearDownTest() { 51 | if err := b.tx.Rollback(); err != nil { 52 | panic(err) 53 | } 54 | } 55 | 56 | // Tx returns a database transaction (which is rolled back after every 57 | // test). 58 | func (b *DatabaseTestSuiteBase) Tx() sqlx.Ext { 59 | return b.tx 60 | } 61 | 62 | type APITestSuite struct { 63 | DatabaseTestSuiteBase 64 | } 65 | 66 | func TestAPI(t *testing.T) { 67 | suite.Run(t, new(APITestSuite)) 68 | } 69 | -------------------------------------------------------------------------------- /internal/api/external/oidc/oidc_test.go: -------------------------------------------------------------------------------- 1 | package oidc 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestNewAuthenticator(t *testing.T) { 12 | assert := require.New(t) 13 | 14 | t.Run("Disabled", func(t *testing.T) { 15 | // make sure that this errors when not setup 16 | _, err := newAuthenticator(context.Background()) 17 | assert.Equal("openid connect is not properly configured", err.Error()) 18 | }) 19 | 20 | t.Run("Claims Unmarshalling", func(t *testing.T) { 21 | emailVerifiedAsString := ` 22 | { 23 | "sub": "chirpstack-oidc", 24 | "name": "brocaar", 25 | "email": "chirpstack@chirpstack.io", 26 | "email_verified": "true", 27 | "user_info_claims": { 28 | "some_key": "some_value", 29 | "some_other": "another_value" 30 | } 31 | }` 32 | 33 | var userStr User 34 | 35 | err := json.Unmarshal([]byte(emailVerifiedAsString), &userStr) 36 | assert.NoError(err) 37 | assert.Equal(true, userStr.EmailVerified, "string parsing should return true") 38 | 39 | emailVerifiedAsBool := ` 40 | { 41 | "sub": "chirpstack-oidc", 42 | "name": "brocaar", 43 | "email": "chirpstack@chirpstack.io", 44 | "email_verified": true, 45 | "user_info_claims": { 46 | "some_key": "some_value", 47 | "some_other": "another_value" 48 | } 49 | }` 50 | 51 | var userBool User 52 | err = json.Unmarshal([]byte(emailVerifiedAsBool), &userBool) 53 | assert.NoError(err) 54 | assert.Equal(true, userBool.EmailVerified, "bool parsing should return true") 55 | 56 | emailVerifiedMissing := `{ 57 | "sub": "chirpstack-oidc", 58 | "name": "brocaar", 59 | "email": "chirpstack@chirpstack.io", 60 | "user_info_claims": { 61 | "some_key": "some_value", 62 | "some_other": "another_value" 63 | } 64 | }` 65 | 66 | var userMiss User 67 | err = json.Unmarshal([]byte(emailVerifiedMissing), &userMiss) 68 | assert.NoError(err) 69 | assert.Equal(false, userMiss.EmailVerified, "should default to false if missing") 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /internal/api/external/validator_test.go: -------------------------------------------------------------------------------- 1 | package external 2 | 3 | import ( 4 | "github.com/brocaar/chirpstack-application-server/internal/api/external/auth" 5 | "github.com/brocaar/chirpstack-application-server/internal/storage" 6 | "github.com/gofrs/uuid" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | type TestValidator struct { 11 | ctx context.Context 12 | validatorFuncs []auth.ValidatorFunc 13 | returnError error 14 | returnSubject string 15 | returnAPIKeyID uuid.UUID 16 | returnUser storage.User 17 | } 18 | 19 | func (v *TestValidator) Validate(ctx context.Context, funcs ...auth.ValidatorFunc) error { 20 | v.ctx = ctx 21 | v.validatorFuncs = funcs 22 | return v.returnError 23 | } 24 | 25 | func (v *TestValidator) GetSubject(ctx context.Context) (string, error) { 26 | return v.returnSubject, v.returnError 27 | } 28 | 29 | func (v *TestValidator) GetAPIKeyID(ctx context.Context) (uuid.UUID, error) { 30 | return v.returnAPIKeyID, v.returnError 31 | } 32 | 33 | func (v *TestValidator) GetUser(ctx context.Context) (storage.User, error) { 34 | return v.returnUser, v.returnError 35 | } 36 | -------------------------------------------------------------------------------- /internal/backend/networkserver/mock/pool.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "github.com/brocaar/chirpstack-application-server/internal/backend/networkserver" 5 | "github.com/brocaar/chirpstack-api/go/v3/ns" 6 | ) 7 | 8 | // Pool is a network-server pool for testing. 9 | type Pool struct { 10 | Client ns.NetworkServerServiceClient 11 | GetHostname string 12 | } 13 | 14 | // Get returns the Client. 15 | func (p *Pool) Get(hostname string, caCert, tlsCert, tlsKey []byte) (ns.NetworkServerServiceClient, error) { 16 | p.GetHostname = hostname 17 | return p.Client, nil 18 | } 19 | 20 | // NewPool creates a network-server client pool which always 21 | // returns the given client on Get. 22 | func NewPool(client ns.NetworkServerServiceClient) networkserver.Pool { 23 | return &Pool{ 24 | Client: client, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/brocaar/chirpstack-application-server/internal/codec/cayennelpp" 7 | "github.com/brocaar/chirpstack-application-server/internal/codec/js" 8 | "github.com/lib/pq/hstore" 9 | ) 10 | 11 | // Type defines the codec type. 12 | type Type string 13 | 14 | // Available codec types. 15 | const ( 16 | None = "" 17 | CayenneLPPType Type = "CAYENNE_LPP" 18 | CustomJSType Type = "CUSTOM_JS" 19 | ) 20 | 21 | // BinaryToJSON encodes the given binary payload to JSON. 22 | func BinaryToJSON(t Type, fPort uint8, variables hstore.Hstore, decodeScript string, b []byte) ([]byte, error) { 23 | vars := make(map[string]string) 24 | for k, v := range variables.Map { 25 | if v.Valid { 26 | vars[k] = v.String 27 | } 28 | } 29 | 30 | switch t { 31 | case CayenneLPPType: 32 | return cayennelpp.BinaryToJSON(b) 33 | case CustomJSType: 34 | return js.BinaryToJSON(fPort, vars, decodeScript, b) 35 | default: 36 | return nil, fmt.Errorf("unknown codec type: %s", t) 37 | } 38 | } 39 | 40 | // JSONToBinary encodes the given JSON to binary. 41 | func JSONToBinary(t Type, fPort uint8, variables hstore.Hstore, encodeScript string, jsonB []byte) ([]byte, error) { 42 | vars := make(map[string]string) 43 | for k, v := range variables.Map { 44 | if v.Valid { 45 | vars[k] = v.String 46 | } 47 | } 48 | 49 | switch t { 50 | case CayenneLPPType: 51 | return cayennelpp.JSONToBinary(jsonB) 52 | case CustomJSType: 53 | return js.JSONToBinary(fPort, vars, encodeScript, jsonB) 54 | default: 55 | return nil, fmt.Errorf("unknown codec type: %s", t) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/eventlog/eventlog_test.go: -------------------------------------------------------------------------------- 1 | package eventlog 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "log" 7 | "testing" 8 | "time" 9 | 10 | "github.com/golang/protobuf/jsonpb" 11 | "github.com/golang/protobuf/proto" 12 | "github.com/stretchr/testify/require" 13 | 14 | pb "github.com/brocaar/chirpstack-api/go/v3/as/integration" 15 | "github.com/brocaar/chirpstack-application-server/internal/config" 16 | "github.com/brocaar/chirpstack-application-server/internal/storage" 17 | "github.com/brocaar/chirpstack-application-server/internal/test" 18 | "github.com/brocaar/lorawan" 19 | ) 20 | 21 | func TestEventLog(t *testing.T) { 22 | assert := require.New(t) 23 | 24 | conf := test.GetConfig() 25 | conf.Monitoring.PerDeviceEventLogMaxHistory = 10 26 | config.Set(conf) 27 | 28 | assert.NoError(storage.Setup(conf)) 29 | 30 | storage.RedisClient().FlushAll(context.Background()) 31 | 32 | upEvent := pb.UplinkEvent{ 33 | Data: []byte{0x01, 0x02, 0x03, 0x03}, 34 | } 35 | 36 | t.Run("GetEventLogForDevice", func(t *testing.T) { 37 | devEUI := lorawan.EUI64{1, 2, 3, 4, 5, 6, 7, 8} 38 | logChannel := make(chan EventLog, 1) 39 | ctx := context.Background() 40 | cctx, cancel := context.WithCancel(ctx) 41 | defer cancel() 42 | 43 | go func() { 44 | if err := GetEventLogForDevice(cctx, devEUI, logChannel); err != nil { 45 | log.Fatal(err) 46 | } 47 | }() 48 | 49 | // some time to subscribe 50 | time.Sleep(time.Millisecond * 100) 51 | 52 | t.Run("LogEventForDevice", func(t *testing.T) { 53 | assert := require.New(t) 54 | assert.NoError(LogEventForDevice(devEUI, Uplink, &upEvent)) 55 | 56 | el := <-logChannel 57 | 58 | var pl pb.UplinkEvent 59 | um := &jsonpb.Unmarshaler{ 60 | AllowUnknownFields: true, 61 | } 62 | assert.NoError(um.Unmarshal(bytes.NewReader(el.Payload), &pl)) 63 | 64 | assert.Equal(Uplink, el.Type) 65 | assert.True(proto.Equal(&upEvent, &pl)) 66 | }) 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /internal/events/uplink/errors.go: -------------------------------------------------------------------------------- 1 | package uplink 2 | 3 | import "errors" 4 | 5 | // Errors. 6 | var ( 7 | ErrAbort = errors.New("abort") 8 | ) 9 | -------------------------------------------------------------------------------- /internal/integration/amqp/channel_pool_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/stretchr/testify/suite" 8 | 9 | "github.com/brocaar/chirpstack-application-server/internal/test" 10 | ) 11 | 12 | type ChannelPoolTestSuite struct { 13 | suite.Suite 14 | 15 | url string 16 | } 17 | 18 | func (ts *ChannelPoolTestSuite) SetupSuite() { 19 | conf := test.GetConfig() 20 | 21 | ts.url = conf.ApplicationServer.Integration.AMQP.URL 22 | } 23 | 24 | func (ts *ChannelPoolTestSuite) TestNew() { 25 | assert := require.New(ts.T()) 26 | 27 | p, err := newPool(10, ts.url) 28 | assert.NoError(err) 29 | defer p.close() 30 | assert.Len(p.chans, 10) 31 | } 32 | 33 | func (ts *ChannelPoolTestSuite) TestGet() { 34 | assert := require.New(ts.T()) 35 | 36 | p, err := newPool(10, ts.url) 37 | assert.NoError(err) 38 | defer p.close() 39 | assert.Len(p.chans, 10) 40 | 41 | _, err = p.get() 42 | assert.NoError(err) 43 | assert.Len(p.chans, 9) 44 | 45 | for i := 0; i < 9; i++ { 46 | _, err = p.get() 47 | assert.NoError(err) 48 | } 49 | 50 | assert.Len(p.chans, 0) 51 | 52 | _, err = p.get() 53 | assert.NoError(err) 54 | } 55 | 56 | func (ts *ChannelPoolTestSuite) TestPut() { 57 | assert := require.New(ts.T()) 58 | 59 | p, err := newPool(10, ts.url) 60 | assert.NoError(err) 61 | 62 | chans := make([]*poolChannel, 10) 63 | for i := 0; i < 10; i++ { 64 | pc, err := p.get() 65 | assert.NoError(err) 66 | chans[i] = pc 67 | } 68 | 69 | assert.Len(p.chans, 0) 70 | 71 | for _, pc := range chans { 72 | assert.NoError(pc.close()) 73 | } 74 | 75 | assert.Len(p.chans, 10) 76 | p.close() 77 | assert.Len(p.chans, 0) 78 | } 79 | 80 | func (ts *ChannelPoolTestSuite) TestPutUnusable() { 81 | assert := require.New(ts.T()) 82 | 83 | p, err := newPool(10, ts.url) 84 | assert.NoError(err) 85 | defer p.close() 86 | 87 | assert.Len(p.chans, 10) 88 | 89 | pc, err := p.get() 90 | assert.NoError(err) 91 | 92 | pc.markUnusable() 93 | 94 | assert.NoError(pc.close()) 95 | 96 | assert.Len(p.chans, 9) 97 | } 98 | 99 | func TestChannelPool(t *testing.T) { 100 | suite.Run(t, new(ChannelPoolTestSuite)) 101 | } 102 | -------------------------------------------------------------------------------- /internal/integration/http/errors.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "errors" 4 | 5 | // errors 6 | var ( 7 | ErrInvalidHeaderName = errors.New("Invalid header name") 8 | ) 9 | -------------------------------------------------------------------------------- /internal/integration/influxdb/errors.go: -------------------------------------------------------------------------------- 1 | package influxdb 2 | 3 | import "errors" 4 | 5 | // errors 6 | var ( 7 | ErrInvalidPrecision = errors.New("invalid precision value") 8 | ) 9 | -------------------------------------------------------------------------------- /internal/integration/loracloud/client/helpers/helpers_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/protobuf/ptypes" 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/brocaar/chirpstack-api/go/v3/gw" 10 | ) 11 | 12 | func TestHEXBytes(t *testing.T) { 13 | assert := require.New(t) 14 | 15 | hb1 := HEXBytes{1, 2, 3} 16 | b, err := hb1.MarshalText() 17 | assert.NoError(err) 18 | assert.Equal("010203", string(b)) 19 | 20 | var hb2 HEXBytes 21 | assert.NoError(hb2.UnmarshalText(b)) 22 | assert.Equal(hb1, hb2) 23 | } 24 | 25 | func TestGetTimestamp(t *testing.T) { 26 | assert := require.New(t) 27 | nowPB := ptypes.TimestampNow() 28 | 29 | rxInfo := []*gw.UplinkRXInfo{ 30 | { 31 | Time: nil, 32 | }, 33 | { 34 | Time: nowPB, 35 | }, 36 | } 37 | 38 | now, err := ptypes.Timestamp(nowPB) 39 | assert.NoError(err) 40 | assert.True(GetTimestamp(rxInfo).Equal(now)) 41 | } 42 | 43 | func TestEUI64(t *testing.T) { 44 | assert := require.New(t) 45 | 46 | eui1 := EUI64{1, 2, 3, 4, 5, 6, 7, 8} 47 | b, err := eui1.MarshalText() 48 | assert.NoError(err) 49 | assert.Equal("01-02-03-04-05-06-07-08", string(b)) 50 | 51 | var eui2 EUI64 52 | assert.NoError(eui2.UnmarshalText(b)) 53 | assert.Equal(eui1, eui2) 54 | } 55 | -------------------------------------------------------------------------------- /internal/integration/loracloud/frame_rx_info.go: -------------------------------------------------------------------------------- 1 | //go:generate protoc -I=/protobuf/src -I=/tmp/chirpstack-api/protobuf -I=. --go_out=. frame_rx_info.proto 2 | 3 | package loracloud 4 | -------------------------------------------------------------------------------- /internal/integration/loracloud/frame_rx_info.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package loracloud; 4 | 5 | import "gw/gw.proto"; 6 | 7 | message FrameRXInfo { 8 | // Uplink Gateway meta-data. 9 | repeated gw.UplinkRXInfo rx_info = 1; 10 | } 11 | -------------------------------------------------------------------------------- /internal/integration/loracloud/prometheus.go: -------------------------------------------------------------------------------- 1 | package loracloud 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/prometheus/client_golang/prometheus/promauto" 6 | ) 7 | 8 | var ( 9 | ad = promauto.NewHistogramVec(prometheus.HistogramOpts{ 10 | Name: "integration_loracloud_api_duration_seconds", 11 | Help: "The duration of LoRa Cloud API calls (per endpoint).", 12 | }, []string{"endpoint"}) 13 | ) 14 | 15 | func loRaCloudAPIDuration(e string) prometheus.Observer { 16 | return ad.With(prometheus.Labels{"endpoint": e}) 17 | } 18 | -------------------------------------------------------------------------------- /internal/integration/mqtt/metrics.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/prometheus/client_golang/prometheus/promauto" 6 | ) 7 | 8 | var ( 9 | ec = promauto.NewCounterVec(prometheus.CounterOpts{ 10 | Name: "integration_mqtt_event_count", 11 | Help: "The number of published events by the MQTT integration (per event type).", 12 | }, []string{"event"}) 13 | 14 | cc = promauto.NewCounterVec(prometheus.CounterOpts{ 15 | Name: "integration_mqtt_command_count", 16 | Help: "The number of received commands by the MQTT integration (per command).", 17 | }, []string{"command"}) 18 | ) 19 | 20 | func mqttEventCounter(e string) prometheus.Counter { 21 | return ec.With(prometheus.Labels{"event": e}) 22 | } 23 | 24 | func mqttCommandCounter(c string) prometheus.Counter { 25 | return cc.With(prometheus.Labels{"command": c}) 26 | } 27 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0001_initial.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS device_up; 2 | DROP TABLE IF EXISTS device_status; 3 | DROP TABLE IF EXISTS device_join; 4 | DROP TABLE IF EXISTS device_ack; 5 | DROP TABLE IF EXISTS device_error; 6 | DROP TABLE IF EXISTS device_location; -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0002_devaddr_uplink_type.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE device_up 2 | DROP COLUMN dev_addr, 3 | DROP COLUMN confirmed_uplink; 4 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0002_devaddr_uplink_type.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE device_up 2 | ADD COLUMN dev_addr bytea not null default '', 3 | ADD COLUMN confirmed_uplink boolean not null default false; 4 | 5 | ALTER TABLE device_up 6 | ALTER COLUMN dev_addr drop default, 7 | ALTER COLUMN confirmed_uplink drop default; 8 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0003_tx_ack.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE device_txack; 2 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0003_tx_ack.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE device_txack ( 2 | id uuid primary key, 3 | received_at timestamp with time zone not null, 4 | dev_eui bytea not null, 5 | device_name varchar(100) not null, 6 | application_id bigint not null, 7 | application_name varchar(100) not null, 8 | gateway_id bytea not null, 9 | f_cnt bigint not null, 10 | tags hstore not null, 11 | tx_info jsonb not null 12 | ); 13 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0004_device_up_txinfo.down.sql: -------------------------------------------------------------------------------- 1 | alter table device_up 2 | drop column tx_info; 3 | -------------------------------------------------------------------------------- /internal/integration/postgresql/migrations/0004_device_up_txinfo.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_up 2 | add column tx_info jsonb not null default 'null'; 3 | 4 | alter table device_up 5 | alter column tx_info drop default; 6 | -------------------------------------------------------------------------------- /internal/monitoring/healthcheck.go: -------------------------------------------------------------------------------- 1 | package monitoring 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/brocaar/chirpstack-application-server/internal/storage" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func healthCheckHandlerFunc(w http.ResponseWriter, r *http.Request) { 11 | _, err := storage.RedisClient().Ping(r.Context()).Result() 12 | if err != nil { 13 | w.WriteHeader(http.StatusServiceUnavailable) 14 | w.Write([]byte(errors.Wrap(err, "redis ping error").Error())) 15 | } 16 | 17 | err = storage.DB().Ping() 18 | if err != nil { 19 | w.WriteHeader(http.StatusServiceUnavailable) 20 | w.Write([]byte(errors.Wrap(err, "postgresql ping error").Error())) 21 | } 22 | 23 | w.WriteHeader(http.StatusOK) 24 | } 25 | -------------------------------------------------------------------------------- /internal/storage/code_migration.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jmoiron/sqlx" 7 | "github.com/lib/pq" 8 | ) 9 | 10 | // CodeMigration checks if the given function code has been applied and if not 11 | // it will execute the given function. 12 | func CodeMigration(name string, f func(db sqlx.Ext) error) error { 13 | return Transaction(func(tx sqlx.Ext) error { 14 | _, err := tx.Exec("lock table code_migration") 15 | if err != nil { 16 | // The table might not exist when the code migration is executed 17 | // before the schema migrations. 18 | return ErrTransactionRollback 19 | } 20 | 21 | res, err := tx.Exec(` 22 | insert into code_migration ( 23 | id, 24 | applied_at 25 | ) values ($1, $2) 26 | on conflict 27 | do nothing 28 | `, name, time.Now()) 29 | if err != nil { 30 | switch err := err.(type) { 31 | case *pq.Error: 32 | switch err.Code.Name() { 33 | case "unique_violation": 34 | return nil 35 | } 36 | } 37 | 38 | return err 39 | } 40 | 41 | ra, err := res.RowsAffected() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | if ra == 0 { 47 | return nil 48 | } 49 | 50 | return f(tx) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /internal/storage/code_migration_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jmoiron/sqlx" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func (ts *StorageTestSuite) TestCodeMigration() { 11 | assert := require.New(ts.T()) 12 | 13 | count := 0 14 | 15 | // returning an error does not mark the migration as completed 16 | assert.Error(CodeMigration("test_1", func(db sqlx.Ext) error { 17 | count++ 18 | return fmt.Errorf("BOOM") 19 | })) 20 | 21 | assert.Equal(1, count) 22 | 23 | // re-run the migration 24 | assert.NoError(CodeMigration("test_1", func(db sqlx.Ext) error { 25 | count++ 26 | return nil 27 | })) 28 | 29 | assert.Equal(2, count) 30 | 31 | // the migration has already been completed 32 | assert.NoError(CodeMigration("test_1", func(db sqlx.Ext) error { 33 | count++ 34 | return nil 35 | })) 36 | 37 | assert.Equal(2, count) 38 | 39 | // new migration should run 40 | assert.NoError(CodeMigration("test_2", func(db sqlx.Ext) error { 41 | count++ 42 | return nil 43 | })) 44 | 45 | assert.Equal(3, count) 46 | 47 | // migration has already been applied 48 | assert.NoError(CodeMigration("test_2", func(db sqlx.Ext) error { 49 | count++ 50 | return nil 51 | })) 52 | 53 | assert.Equal(3, count) 54 | } 55 | -------------------------------------------------------------------------------- /internal/storage/migrations/0001_initial.down.sql: -------------------------------------------------------------------------------- 1 | drop index node_app_eui; 2 | 3 | drop table node; 4 | 5 | drop table application; 6 | -------------------------------------------------------------------------------- /internal/storage/migrations/0001_initial.up.sql: -------------------------------------------------------------------------------- 1 | create table application ( 2 | app_eui bytea primary key, 3 | name character varying (100) not null 4 | ); 5 | 6 | create table node ( 7 | dev_eui bytea primary key, 8 | app_eui bytea references application on delete cascade not null, 9 | app_key bytea not null, 10 | used_dev_nonces bytea 11 | ); 12 | 13 | create index node_app_eui on node (app_eui); 14 | -------------------------------------------------------------------------------- /internal/storage/migrations/0002_join_accept_params.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column rx_delay, 3 | drop column rx1_dr_offset, 4 | drop column channel_list_id; 5 | 6 | drop table channel; 7 | 8 | drop table channel_list; 9 | -------------------------------------------------------------------------------- /internal/storage/migrations/0002_join_accept_params.up.sql: -------------------------------------------------------------------------------- 1 | create table channel_list ( 2 | id bigserial primary key, 3 | name character varying (100) not null 4 | ); 5 | 6 | create table channel ( 7 | id bigserial primary key, 8 | channel_list_id bigint references channel_list on delete cascade not null, 9 | channel integer not null, 10 | frequency integer not null, 11 | check (channel >= 3 and channel <= 7 and frequency > 0), 12 | unique (channel_list_id, channel) 13 | ); 14 | 15 | alter table node 16 | add column rx_delay int2 not null default 0, 17 | add column rx1_dr_offset int2 not null default 0, 18 | add column channel_list_id bigint references channel_list on delete set null; 19 | 20 | alter table node 21 | alter column rx_delay drop default, 22 | alter column rx1_dr_offset drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0003_rx_window_and_rx2_dr.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column rx_window, 3 | drop column rx2_dr; 4 | -------------------------------------------------------------------------------- /internal/storage/migrations/0003_rx_window_and_rx2_dr.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column rx_window int2 not null default 0, 3 | add column rx2_dr int2 not null default 0; 4 | -------------------------------------------------------------------------------- /internal/storage/migrations/0004_add_node_apps_nwks_key_name_devaddr.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column app_s_key, 3 | drop column nwk_s_key, 4 | drop column dev_addr, 5 | drop column name; -------------------------------------------------------------------------------- /internal/storage/migrations/0004_add_node_apps_nwks_key_name_devaddr.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column app_s_key bytea not null default E'\\x00000000000000000000000000000000', 3 | add column nwk_s_key bytea not null default E'\\x00000000000000000000000000000000', 4 | add column dev_addr bytea not null default E'\\x00000000', 5 | add column name varchar(100) not null default ''; 6 | 7 | update node set name=(select name from application where app_eui = node.app_eui); 8 | -------------------------------------------------------------------------------- /internal/storage/migrations/0005_add_queue.down.sql: -------------------------------------------------------------------------------- 1 | drop index downlink_queue_dev_eui; 2 | drop table downlink_queue; -------------------------------------------------------------------------------- /internal/storage/migrations/0005_add_queue.up.sql: -------------------------------------------------------------------------------- 1 | create table downlink_queue ( 2 | id bigserial, 3 | reference varchar(100) not null, 4 | dev_eui bytea references node on delete cascade not null, 5 | confirmed boolean not null default false, 6 | pending boolean not null default false, 7 | fport smallint not null, 8 | data bytea not null 9 | ); 10 | 11 | create index downlink_queue_dev_eui on downlink_queue(dev_eui); -------------------------------------------------------------------------------- /internal/storage/migrations/0006_remove_application_table.down.sql: -------------------------------------------------------------------------------- 1 | create table application ( 2 | app_eui bytea primary key, 3 | name character varying (100) not null 4 | ); 5 | 6 | insert into application 7 | select distinct(app_eui), 'no name' as name from node; 8 | 9 | alter table node 10 | add constraint node_app_eui_fkey foreign key(app_eui) references application on delete cascade; -------------------------------------------------------------------------------- /internal/storage/migrations/0006_remove_application_table.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop constraint node_app_eui_fkey; 3 | 4 | drop table application; -------------------------------------------------------------------------------- /internal/storage/migrations/0007_migrate_channels_to_channel_list.down.sql: -------------------------------------------------------------------------------- 1 | alter table channel_list 2 | drop column channels; 3 | 4 | create table channel ( 5 | id bigserial primary key, 6 | channel_list_id bigint references channel_list on delete cascade not null, 7 | channel integer not null, 8 | frequency integer not null, 9 | check (channel >= 3 and channel <= 7 and frequency > 0), 10 | unique (channel_list_id, channel) 11 | ); -------------------------------------------------------------------------------- /internal/storage/migrations/0007_migrate_channels_to_channel_list.up.sql: -------------------------------------------------------------------------------- 1 | alter table channel_list 2 | add column channels integer[]; 3 | 4 | update channel_list 5 | set channels=( 6 | select array( 7 | select frequency 8 | from 9 | channel 10 | where 11 | channel_list_id=channel_list.id 12 | order by 13 | channel)); 14 | 15 | drop table channel; 16 | -------------------------------------------------------------------------------- /internal/storage/migrations/0008_relax_fcnt.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column relax_fcnt; 3 | -------------------------------------------------------------------------------- /internal/storage/migrations/0008_relax_fcnt.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column relax_fcnt boolean not null default false; 3 | -------------------------------------------------------------------------------- /internal/storage/migrations/0009_adr_interval_and_install_margin.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column adr_interval, 3 | drop column installation_margin; -------------------------------------------------------------------------------- /internal/storage/migrations/0009_adr_interval_and_install_margin.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column adr_interval integer not null default 0, 3 | add column installation_margin decimal(5,2) not null default 0; -------------------------------------------------------------------------------- /internal/storage/migrations/0010_recreate_application_table.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_node_application_id; 2 | 3 | alter table node 4 | drop column application_id; 5 | 6 | drop index idx_application_name; 7 | drop table application; -------------------------------------------------------------------------------- /internal/storage/migrations/0010_recreate_application_table.up.sql: -------------------------------------------------------------------------------- 1 | create table application ( 2 | id bigserial primary key, 3 | name varchar(100) not null, 4 | description text not null, 5 | 6 | constraint application_name_key unique (name) 7 | ); 8 | 9 | create index idx_application_name on application(name); 10 | 11 | insert into application 12 | (name, description) 13 | select distinct(encode(app_eui, 'hex')) as name, 'Application ' || encode(app_eui, 'hex') as description from node; 14 | 15 | alter table node 16 | add column application_id bigint references application on delete cascade; 17 | 18 | update node set application_id = (select id from application where name = encode(node.app_eui, 'hex')); 19 | 20 | alter table node 21 | alter column application_id set not null; 22 | 23 | create index idx_node_application_id on node(application_id); 24 | -------------------------------------------------------------------------------- /internal/storage/migrations/0011_node_description_and_is_abp.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_node_name; 2 | 3 | update node set name = description; 4 | 5 | alter table node 6 | drop column description, 7 | drop column is_abp, 8 | drop constraint node_application_id_name_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0011_node_description_and_is_abp.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column description text, 3 | add column is_abp boolean not null default false; 4 | 5 | update node set description = name; 6 | update node set name = encode(dev_eui, 'hex'); 7 | 8 | alter table node 9 | alter column description set not null, 10 | add constraint node_application_id_name_key unique (application_id, name); 11 | 12 | create index idx_node_name on node(name); 13 | -------------------------------------------------------------------------------- /internal/storage/migrations/0012_class_c_node.down.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column is_class_c; 3 | -------------------------------------------------------------------------------- /internal/storage/migrations/0012_class_c_node.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | add column is_class_c boolean not null default false; 3 | -------------------------------------------------------------------------------- /internal/storage/migrations/0013_application_settings.down.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | drop column rx_delay, 3 | drop column rx1_dr_offset, 4 | drop column channel_list_id, 5 | drop column rx_window, 6 | drop column rx2_dr, 7 | drop column relax_fcnt, 8 | drop column adr_interval, 9 | drop column installation_margin, 10 | drop column is_abp, 11 | drop column is_class_c; 12 | 13 | alter table node 14 | drop column use_application_settings; -------------------------------------------------------------------------------- /internal/storage/migrations/0013_application_settings.up.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | add column rx_delay int2 not null default 0, 3 | add column rx1_dr_offset int2 not null default 0, 4 | add column channel_list_id bigint references channel_list on delete set null, 5 | add column rx_window int2 not null default 0, 6 | add column rx2_dr int2 not null default 0, 7 | add column relax_fcnt boolean not null default false, 8 | add column adr_interval integer not null default 0, 9 | add column installation_margin decimal(5,2) not null default 0, 10 | add column is_abp boolean not null default false, 11 | add column is_class_c boolean not null default false; 12 | 13 | alter table node 14 | add column use_application_settings boolean not null default false; 15 | -------------------------------------------------------------------------------- /internal/storage/migrations/0014_users_and_application_users.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_application_user_application_id; 2 | drop index idx_application_user_user_id; 3 | drop table application_user; 4 | 5 | drop index idx_user_username; 6 | drop index idx_user_username_prefix; 7 | drop table "user"; -------------------------------------------------------------------------------- /internal/storage/migrations/0014_users_and_application_users.up.sql: -------------------------------------------------------------------------------- 1 | create table "user" ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | updated_at timestamp with time zone not null, 5 | username character varying (100) not null, 6 | password_hash character varying (200) not null, 7 | session_ttl bigint not null, 8 | is_active boolean not null, 9 | is_admin boolean not null 10 | ); 11 | 12 | create unique index idx_user_username on "user"(username); 13 | create index idx_user_username_prefix on "user"(username varchar_pattern_ops); 14 | 15 | create table application_user ( 16 | id bigserial primary key, 17 | created_at timestamp with time zone not null, 18 | updated_at timestamp with time zone not null, 19 | user_id bigint not null references "user" on delete cascade, 20 | application_id bigint not null references application on delete cascade, 21 | is_admin boolean not null, 22 | 23 | unique(user_id, application_id) 24 | ); 25 | 26 | create index idx_application_user_user_id on application_user(user_id); 27 | create index idx_application_user_application_id on application_user(application_id); 28 | 29 | -- global admin (password: admin) 30 | insert into "user" ( 31 | created_at, 32 | updated_at, 33 | username, 34 | password_hash, 35 | session_ttl, 36 | is_active, 37 | is_admin 38 | ) values ( 39 | now(), 40 | now(), 41 | 'admin', 42 | 'PBKDF2$sha512$1$l8zGKtxRESq3PA2kFhHRWA==$H3lGMxOt55wjwoc+myeOoABofJY9oDpldJa7fhqdjbh700V6FLPML75UmBOt9J5VFNjAL1AvqCozA1HJM0QVGA==', 43 | 0, 44 | true, 45 | true 46 | ); -------------------------------------------------------------------------------- /internal/storage/migrations/0015_organizations.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_application_organization_id; 2 | alter table application 3 | drop constraint application_name_organization_id_key, 4 | add constraint application_name_key unique (name), 5 | drop column organization_id; 6 | 7 | drop index idx_gateway_organization_id; 8 | drop table gateway; 9 | 10 | drop index idx_organization_user_organization_id; 11 | drop index idx_organization_user_user_id; 12 | drop table organization_user; 13 | 14 | drop index idx_organization_display_name_prefix; 15 | drop index idx_organization_name; 16 | drop table organization; -------------------------------------------------------------------------------- /internal/storage/migrations/0016_delete_channel_list.down.sql: -------------------------------------------------------------------------------- 1 | create table channel_list ( 2 | id bigserial primary key, 3 | name character varying (100) not null, 4 | channels integer[] 5 | ); 6 | 7 | alter table node 8 | add column channel_list_id bigint references channel_list on delete set null; 9 | 10 | alter table application 11 | add column channel_list_id bigint references channel_list on delete set null; -------------------------------------------------------------------------------- /internal/storage/migrations/0016_delete_channel_list.up.sql: -------------------------------------------------------------------------------- 1 | alter table node 2 | drop column channel_list_id; 3 | 4 | alter table application 5 | drop column channel_list_id; 6 | 7 | drop table channel_list; -------------------------------------------------------------------------------- /internal/storage/migrations/0017_integrations.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_integration_application_id; 2 | drop index idx_integration_kind; 3 | drop table integration; -------------------------------------------------------------------------------- /internal/storage/migrations/0017_integrations.up.sql: -------------------------------------------------------------------------------- 1 | create table integration ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | updated_at timestamp with time zone not null, 5 | application_id bigint not null references application on delete cascade, 6 | kind character varying (20) not null, 7 | settings jsonb, 8 | 9 | constraint integration_kind_application_id unique (kind, application_id) 10 | ); 11 | 12 | create index idx_integration_kind on integration(kind); 13 | create index idx_integration_application_id on integration(application_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0018_gateway_ping.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_last_ping_sent_at; 2 | drop index idx_gateway_ping; 3 | alter table gateway 4 | drop column ping, 5 | drop column last_ping_id, 6 | drop column last_ping_sent_at; 7 | 8 | drop index idx_gateway_ping_rx_gateway_mac; 9 | drop index idx_gateway_ping_rx_ping_id; 10 | drop index idx_gateway_ping_rx_created_at; 11 | drop table gateway_ping_rx; 12 | 13 | drop index idx_gateway_ping_gateway_mac; 14 | drop index idx_gateway_ping_created_at; 15 | drop table gateway_ping; -------------------------------------------------------------------------------- /internal/storage/migrations/0018_gateway_ping.up.sql: -------------------------------------------------------------------------------- 1 | create table gateway_ping ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | gateway_mac bytea not null references gateway on delete cascade, 5 | frequency integer not null, 6 | dr integer not null 7 | ); 8 | 9 | create index idx_gateway_ping_created_at on gateway_ping(created_at); 10 | create index idx_gateway_ping_gateway_mac on gateway_ping(gateway_mac); 11 | 12 | create table gateway_ping_rx ( 13 | id bigserial primary key, 14 | created_at timestamp with time zone not null, 15 | ping_id bigint not null references gateway_ping on delete cascade, 16 | gateway_mac bytea not null references gateway on delete cascade, 17 | received_at timestamp with time zone, 18 | rssi integer not null, 19 | lora_snr decimal(3,1) not null, 20 | location point, 21 | altitude double precision 22 | ); 23 | 24 | create index idx_gateway_ping_rx_created_at on gateway_ping_rx(created_at); 25 | create index idx_gateway_ping_rx_ping_id on gateway_ping_rx(ping_id); 26 | create index idx_gateway_ping_rx_gateway_mac on gateway_ping_rx(gateway_mac); 27 | 28 | alter table gateway 29 | add column ping boolean not null default false, 30 | add column last_ping_id bigint references gateway_ping on delete set null, 31 | add column last_ping_sent_at timestamp with time zone; 32 | 33 | create index idx_gateway_ping on gateway(ping); 34 | create index idx_gateway_last_ping_sent_at on gateway(last_ping_sent_at); -------------------------------------------------------------------------------- /internal/storage/migrations/0019_node_prefix_search.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_node_dev_eui_prefix; 2 | drop index idx_node_name_prefix; -------------------------------------------------------------------------------- /internal/storage/migrations/0019_node_prefix_search.up.sql: -------------------------------------------------------------------------------- 1 | create index idx_node_dev_eui_prefix on node (encode(dev_eui, 'hex') varchar_pattern_ops); 2 | create index idx_node_name_prefix on node (name varchar_pattern_ops); -------------------------------------------------------------------------------- /internal/storage/migrations/0020_backend_interfaces.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_network_server_id; 2 | alter table gateway 3 | drop column network_server_id; 4 | 5 | drop index idx_application_service_profile_id; 6 | alter table application 7 | drop column service_profile_id, 8 | add column rx_delay int2 not null default 0, 9 | add column rx1_dr_offset int2 not null default 0, 10 | add column rx_window int2 not null default 0, 11 | add column rx2_dr int2 not null default 0, 12 | add column relax_fcnt boolean not null default false, 13 | add column adr_interval integer not null default 0, 14 | add column installation_margin decimal(5,2) not null default 0, 15 | add column is_abp boolean not null default false, 16 | add column is_class_c boolean not null default false; 17 | 18 | drop index idx_device_queue_dev_eui; 19 | drop index idx_device_queue_updated_at; 20 | drop index idx_device_queue_created_at; 21 | drop table device_queue; 22 | 23 | drop index idx_device_activation_dev_eui; 24 | drop index idx_device_activation_created_at; 25 | drop table device_activation; 26 | 27 | drop index idx_device_keys_updated_at; 28 | drop index idx_device_keys_created_at; 29 | drop table device_keys; 30 | 31 | drop index idx_device_dev_eui_prefix; 32 | drop index idx_device_name_prefix; 33 | drop index idx_device_application_id; 34 | drop index idx_device_device_profile_id; 35 | drop index idx_device_updated_at; 36 | drop index idx_device_created_at; 37 | drop table device; 38 | 39 | drop index idx_device_profile_updated_at; 40 | drop index idx_device_profile_created_at; 41 | drop index idx_device_profile_organization_id; 42 | drop index idx_device_profile_network_server_id; 43 | drop table device_profile; 44 | 45 | drop index idx_service_profile_updated_at; 46 | drop index idx_service_profile_created_at; 47 | drop index idx_service_profile_network_server_id; 48 | drop index idx_service_profile_organization_id; 49 | drop table service_profile; 50 | 51 | drop index idx_network_server_updated_at; 52 | drop index idx_network_server_created_at; 53 | drop table network_server; -------------------------------------------------------------------------------- /internal/storage/migrations/0021_user_email_and_note.down.sql: -------------------------------------------------------------------------------- 1 | alter table "user" 2 | drop column email, 3 | drop column note; -------------------------------------------------------------------------------- /internal/storage/migrations/0021_user_email_and_note.up.sql: -------------------------------------------------------------------------------- 1 | alter table "user" 2 | add column email text not null default '', 3 | add column note text not null default ''; -------------------------------------------------------------------------------- /internal/storage/migrations/0022_add_device_queue_mapping.down.sql: -------------------------------------------------------------------------------- 1 | drop index device_queue_mapping_dev_eui; 2 | drop index device_queue_mapping_created_at; 3 | drop table device_queue_mapping; -------------------------------------------------------------------------------- /internal/storage/migrations/0022_add_device_queue_mapping.up.sql: -------------------------------------------------------------------------------- 1 | create table device_queue_mapping ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | reference text not null, 5 | dev_eui bytea references device on delete cascade not null, 6 | f_cnt int not null 7 | ); 8 | 9 | create index device_queue_mapping_created_at on device_queue_mapping(created_at); 10 | create index device_queue_mapping_dev_eui on device_queue_mapping(dev_eui); -------------------------------------------------------------------------------- /internal/storage/migrations/0023_payload_decoder.down.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | drop column payload_codec, 3 | drop column payload_encoder_script, 4 | drop column payload_decoder_script; -------------------------------------------------------------------------------- /internal/storage/migrations/0023_payload_decoder.up.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | add column payload_codec text not null default '', 3 | add column payload_encoder_script text not null default '', 4 | add column payload_decoder_script text not null default ''; -------------------------------------------------------------------------------- /internal/storage/migrations/0024_network_server_certs.down.sql: -------------------------------------------------------------------------------- 1 | alter table network_server 2 | drop column ca_cert, 3 | drop column tls_cert, 4 | drop column tls_key, 5 | drop column routing_profile_ca_cert, 6 | drop column routing_profile_tls_cert, 7 | drop column routing_profile_tls_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0024_network_server_certs.up.sql: -------------------------------------------------------------------------------- 1 | alter table network_server 2 | add column ca_cert text not null default '', 3 | add column tls_cert text not null default '', 4 | add column tls_key text not null default '', 5 | add column routing_profile_ca_cert text not null default '', 6 | add column routing_profile_tls_cert text not null default '', 7 | add column routing_profile_tls_key text not null default ''; -------------------------------------------------------------------------------- /internal/storage/migrations/0025_device_status.down.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | drop column last_seen_at, 3 | drop column device_status_battery, 4 | drop column device_status_margin; -------------------------------------------------------------------------------- /internal/storage/migrations/0025_device_status.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column last_seen_at timestamp with time zone null, 3 | add column device_status_battery int null, 4 | add column device_status_margin int null; 5 | -------------------------------------------------------------------------------- /internal/storage/migrations/0026_network_server_gw_discovery.down.sql: -------------------------------------------------------------------------------- 1 | alter table network_server 2 | drop column gateway_discovery_enabled, 3 | drop column gateway_discovery_interval, 4 | drop column gateway_discovery_tx_frequency, 5 | drop column gateway_discovery_dr; -------------------------------------------------------------------------------- /internal/storage/migrations/0026_network_server_gw_discovery.up.sql: -------------------------------------------------------------------------------- 1 | alter table network_server 2 | add column gateway_discovery_enabled boolean not null default false, 3 | add column gateway_discovery_interval integer not null default 0, 4 | add column gateway_discovery_tx_frequency integer not null default 0, 5 | add column gateway_discovery_dr smallint not null default 0; -------------------------------------------------------------------------------- /internal/storage/migrations/0027_global_search.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_user_username_trgm; 2 | drop index idx_organization_name_trgm; 3 | drop index idx_device_name_trgm; 4 | drop index idx_device_dev_eui_trgm; 5 | drop index idx_gateway_name_trgm; 6 | drop index idx_gateway_mac_trgm; 7 | drop index idx_application_name_trgm; 8 | 9 | create index idx_user_username_prefix on "user"(username varchar_pattern_ops); 10 | 11 | create index idx_device_dev_eui_prefix on device(encode(dev_eui, 'hex') varchar_pattern_ops); 12 | create index idx_device_name_prefix on device(name varchar_pattern_ops); 13 | 14 | create index idx_organization_display_name_prefix on organization(lower(display_name) varchar_pattern_ops); 15 | 16 | create index idx_application_name on application(name); -------------------------------------------------------------------------------- /internal/storage/migrations/0027_global_search.up.sql: -------------------------------------------------------------------------------- 1 | drop index idx_application_name; 2 | drop index idx_organization_display_name_prefix; 3 | drop index idx_device_dev_eui_prefix; 4 | drop index idx_device_name_prefix; 5 | drop index idx_user_username_prefix; 6 | 7 | create index idx_application_name_trgm on application using gin (name gin_trgm_ops); 8 | 9 | create index idx_gateway_mac_trgm on gateway using gin (encode(mac, 'hex') gin_trgm_ops); 10 | create index idx_gateway_name_trgm on gateway using gin (name gin_trgm_ops); 11 | 12 | create index idx_device_dev_eui_trgm on device using gin (encode(dev_eui, 'hex') gin_trgm_ops); 13 | create index idx_device_name_trgm on device using gin (name gin_trgm_ops); 14 | 15 | create index idx_organization_name_trgm on organization using gin (name gin_trgm_ops); 16 | 17 | create index idx_user_username_trgm on "user" using gin (username gin_trgm_ops); -------------------------------------------------------------------------------- /internal/storage/migrations/0028_gateway_profile.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_gateway_profile_id; 2 | alter table gateway 3 | drop column gateway_profile_id; 4 | 5 | drop index idx_gateway_profile_network_server_id; 6 | drop index idx_gateway_profile_created_at; 7 | drop index idx_gateway_profile_updated_at; 8 | 9 | drop table gateway_profile; -------------------------------------------------------------------------------- /internal/storage/migrations/0028_gateway_profile.up.sql: -------------------------------------------------------------------------------- 1 | create table gateway_profile ( 2 | gateway_profile_id uuid primary key, 3 | network_server_id bigint not null references network_server, 4 | created_at timestamp with time zone not null, 5 | updated_at timestamp with time zone not null, 6 | name varchar(100) not null 7 | ); 8 | 9 | create index idx_gateway_profile_network_server_id on gateway_profile(network_server_id); 10 | create index idx_gateway_profile_created_at on gateway_profile(created_at); 11 | create index idx_gateway_profile_updated_at on gateway_profile(updated_at); 12 | 13 | alter table gateway 14 | add column gateway_profile_id uuid references gateway_profile; 15 | 16 | create index idx_gateway_gateway_profile_id on gateway(gateway_profile_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0029_cleanup_old_tables.up.sql: -------------------------------------------------------------------------------- 1 | drop table application_user; 2 | drop table device_queue; 3 | drop table downlink_queue; 4 | drop table node; -------------------------------------------------------------------------------- /internal/storage/migrations/0030_lorawan_11_keys.down.sql: -------------------------------------------------------------------------------- 1 | alter table device_activation 2 | drop column nwk_s_enc_key, 3 | drop column s_nwk_s_int_key; 4 | 5 | alter table device_activation 6 | rename column f_nwk_s_int_key to nwk_s_key; 7 | 8 | alter table device_keys 9 | drop column app_key; 10 | 11 | alter table device_keys 12 | rename column nwk_key to app_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0030_lorawan_11_keys.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_keys 2 | rename column app_key to nwk_key; 3 | 4 | alter table device_keys 5 | add column app_key bytea not null default decode('00000000000000000000000000000000', 'hex'); 6 | 7 | alter table device_keys 8 | alter column app_key drop default; 9 | 10 | alter table device_activation 11 | rename column nwk_s_key to f_nwk_s_int_key; 12 | 13 | alter table device_activation 14 | add column s_nwk_s_int_key bytea, 15 | add column nwk_s_enc_key bytea; 16 | 17 | update device_activation 18 | set 19 | s_nwk_s_int_key = f_nwk_s_int_key, 20 | nwk_s_enc_key = f_nwk_s_int_key; 21 | 22 | alter table device_activation 23 | alter column s_nwk_s_int_key set not null, 24 | alter column nwk_s_enc_key set not null; -------------------------------------------------------------------------------- /internal/storage/migrations/0031_cleanup_indices.down.sql: -------------------------------------------------------------------------------- 1 | create index idx_service_profile_updated_at on service_profile(updated_at); 2 | create index idx_service_profile_created_at on service_profile(created_at); 3 | 4 | create index idx_network_server_updated_at on network_server(updated_at); 5 | create index idx_network_server_created_at on network_server(created_at); 6 | 7 | create index idx_gateway_profile_updated_at on gateway_profile(updated_at); 8 | create index idx_gateway_profile_created_at on gateway_profile(created_at); 9 | 10 | create index idx_gateway_ping_rx_created_at on gateway_ping_rx(created_at); 11 | 12 | create index idx_gateway_ping_created_at on gateway_ping(created_at); 13 | 14 | create index device_queue_mapping_created_at on device_queue_mapping(created_at); 15 | 16 | create index idx_device_profile_updated_at on device_profile(updated_at); 17 | create index idx_device_profile_created_at on device_profile(created_at); 18 | 19 | create index idx_device_keys_updated_at on device_keys(updated_at); 20 | create index idx_device_keys_created_at on device_keys(created_at); 21 | 22 | create index idx_device_activation_created_at on device_activation(created_at); 23 | 24 | create index idx_device_updated_at on device(updated_at); 25 | create index idx_device_created_at on device(created_at); -------------------------------------------------------------------------------- /internal/storage/migrations/0031_cleanup_indices.up.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_created_at; 2 | drop index idx_device_updated_at; 3 | 4 | drop index idx_device_activation_created_at; 5 | 6 | drop index idx_device_keys_created_at; 7 | drop index idx_device_keys_updated_at; 8 | 9 | drop index idx_device_profile_created_at; 10 | drop index idx_device_profile_updated_at; 11 | 12 | drop index device_queue_mapping_created_at; 13 | 14 | drop index idx_gateway_ping_created_at; 15 | 16 | drop index idx_gateway_ping_rx_created_at; 17 | 18 | drop index idx_gateway_profile_created_at; 19 | drop index idx_gateway_profile_updated_at; 20 | 21 | drop index idx_network_server_created_at; 22 | drop index idx_network_server_updated_at; 23 | 24 | drop index idx_service_profile_created_at; 25 | drop index idx_service_profile_updated_at; -------------------------------------------------------------------------------- /internal/storage/migrations/0032_fix_table_constraints.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_name_organization_id; 2 | 3 | alter table gateway 4 | alter column network_server_id drop not null; 5 | 6 | drop index idx_device_name_application_id; 7 | 8 | alter table application 9 | alter column service_profile_id drop not null; -------------------------------------------------------------------------------- /internal/storage/migrations/0032_fix_table_constraints.up.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | alter column service_profile_id set not null; 3 | 4 | create unique index idx_device_name_application_id on device(name, application_id); 5 | 6 | alter table gateway 7 | alter column network_server_id set not null; 8 | 9 | create unique index idx_gateway_name_organization_id on gateway(name, organization_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0033_drop_device_queue_mapping.down.sql: -------------------------------------------------------------------------------- 1 | create table device_queue_mapping ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | reference text not null, 5 | dev_eui bytea references device on delete cascade not null, 6 | f_cnt int not null 7 | ); 8 | 9 | create index device_queue_mapping_dev_eui on device_queue_mapping(dev_eui); -------------------------------------------------------------------------------- /internal/storage/migrations/0033_drop_device_queue_mapping.up.sql: -------------------------------------------------------------------------------- 1 | drop table device_queue_mapping; -------------------------------------------------------------------------------- /internal/storage/migrations/0034_drop_nwk_session_keys.down.sql: -------------------------------------------------------------------------------- 1 | alter table device_activation 2 | add column f_nwk_s_int_key bytea, 3 | add column s_nwk_s_int_key bytea, 4 | add column nwk_s_enc_key bytea; -------------------------------------------------------------------------------- /internal/storage/migrations/0034_drop_nwk_session_keys.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_activation 2 | drop column f_nwk_s_int_key, 3 | drop column s_nwk_s_int_key, 4 | drop column nwk_s_enc_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0035_multicast.down.sql: -------------------------------------------------------------------------------- 1 | drop table device_multicast_group; 2 | 3 | drop index idx_multicast_group_service_profile_id; 4 | drop index idx_multicast_group_name_trgm; 5 | 6 | drop table multicast_group; -------------------------------------------------------------------------------- /internal/storage/migrations/0035_multicast.up.sql: -------------------------------------------------------------------------------- 1 | create table multicast_group ( 2 | id uuid primary key, 3 | created_at timestamp with time zone not null, 4 | updated_at timestamp with time zone not null, 5 | name varchar(100) not null, 6 | service_profile_id uuid not null references service_profile, 7 | mc_app_s_key bytea 8 | ); 9 | 10 | create index idx_multicast_group_name_trgm on multicast_group using gin (name gin_trgm_ops); 11 | create index idx_multicast_group_service_profile_id on multicast_group(service_profile_id); 12 | 13 | create table device_multicast_group ( 14 | dev_eui bytea references device on delete cascade, 15 | multicast_group_id uuid references multicast_group on delete cascade, 16 | created_at timestamp with time zone not null, 17 | 18 | primary key(multicast_group_id, dev_eui) 19 | ); -------------------------------------------------------------------------------- /internal/storage/migrations/0036_device_location.down.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | drop column latitude, 3 | drop column longitude, 4 | drop column altitude; -------------------------------------------------------------------------------- /internal/storage/migrations/0036_device_location.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column latitude double precision, 3 | add column longitude double precision, 4 | add column altitude double precision; -------------------------------------------------------------------------------- /internal/storage/migrations/0037_fix_device_status.down.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | drop column device_status_external_power_source, 3 | alter column device_status_battery type integer; -------------------------------------------------------------------------------- /internal/storage/migrations/0037_fix_device_status.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column device_status_external_power_source boolean not null default false, 3 | alter column device_status_battery type decimal(5,2); 4 | 5 | alter table device 6 | alter column device_status_external_power_source drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0038_device_profile_payload_codec.down.sql: -------------------------------------------------------------------------------- 1 | alter table device_profile 2 | drop column payload_codec, 3 | drop column payload_encoder_script, 4 | drop column payload_decoder_script; -------------------------------------------------------------------------------- /internal/storage/migrations/0038_device_profile_payload_codec.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_profile 2 | add column payload_codec text not null default '', 3 | add column payload_encoder_script text not null default '', 4 | add column payload_decoder_script text not null default ''; 5 | 6 | alter table device_profile 7 | alter column payload_codec drop default, 8 | alter column payload_encoder_script drop default, 9 | alter column payload_decoder_script drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0039_application_add_dr.down.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | drop column dr; -------------------------------------------------------------------------------- /internal/storage/migrations/0039_application_add_dr.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column dr smallint; -------------------------------------------------------------------------------- /internal/storage/migrations/0040_fuota.down.sql: -------------------------------------------------------------------------------- 1 | drop table fuota_deployment_device; 2 | 3 | drop index idx_fuota_deployment_next_step_after; 4 | drop index idx_fuota_deployment_state; 5 | drop index idx_fuota_deployment_multicast_group_id; 6 | drop table fuota_deployment; 7 | 8 | drop index idx_remote_fragmentation_session_retry_after; 9 | drop index idx_remote_fragmentation_session_state_provisioned; 10 | drop table remote_fragmentation_session; 11 | 12 | drop index idx_remote_multicast_class_c_session_state_retry_after; 13 | drop index idx_remote_multicast_class_c_session_state_provisioned; 14 | drop table remote_multicast_class_c_session; 15 | 16 | drop index idx_remote_multicast_setup_retry_after; 17 | drop index idx_remote_multicast_setup_state_provisioned; 18 | drop table remote_multicast_setup; 19 | 20 | alter table device_keys 21 | drop column gen_app_key; 22 | 23 | alter table multicast_group 24 | drop column mc_key, 25 | drop column f_cnt; -------------------------------------------------------------------------------- /internal/storage/migrations/0041_device_variables.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_tags; 2 | 3 | alter table device 4 | drop column variables, 5 | drop column tags; -------------------------------------------------------------------------------- /internal/storage/migrations/0041_device_variables.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column variables hstore, 3 | add column tags hstore; 4 | 5 | create index idx_device_tags on device(tags); -------------------------------------------------------------------------------- /internal/storage/migrations/0042_drop_multicast_f_cnt.down.sql: -------------------------------------------------------------------------------- 1 | alter table multicast_group 2 | add column f_cnt bigint not null default 0; 3 | 4 | alter table multicast_group 5 | alter column f_cnt drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0042_drop_multicast_f_cnt.up.sql: -------------------------------------------------------------------------------- 1 | alter table multicast_group 2 | drop column f_cnt; -------------------------------------------------------------------------------- /internal/storage/migrations/0043_extend_org_user_permissions.down.sql: -------------------------------------------------------------------------------- 1 | alter table organization_user 2 | drop column is_gateway_admin, 3 | drop column is_device_admin; -------------------------------------------------------------------------------- /internal/storage/migrations/0043_extend_org_user_permissions.up.sql: -------------------------------------------------------------------------------- 1 | alter table organization_user 2 | add column is_device_admin boolean not null default false, 3 | add column is_gateway_admin boolean not null default false; 4 | 5 | alter table organization_user 6 | alter column is_device_admin drop default, 7 | alter column is_gateway_admin drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0044_gateway_location_first_and_last_seen.down.sql: -------------------------------------------------------------------------------- 1 | alter table gateway 2 | drop column first_seen_at, 3 | drop column last_seen_at, 4 | drop column latitude, 5 | drop column longitude, 6 | drop column altitude; -------------------------------------------------------------------------------- /internal/storage/migrations/0044_gateway_location_first_and_last_seen.up.sql: -------------------------------------------------------------------------------- 1 | alter table gateway 2 | add column first_seen_at timestamp with time zone, 3 | add column last_seen_at timestamp with time zone, 4 | add column latitude double precision not null default 0, 5 | add column longitude double precision not null default 0, 6 | add column altitude double precision not null default 0; 7 | 8 | alter table gateway 9 | alter column latitude drop default, 10 | alter column longitude drop default, 11 | alter column altitude drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0045_code_migrations.down.sql: -------------------------------------------------------------------------------- 1 | drop table code_migration; -------------------------------------------------------------------------------- /internal/storage/migrations/0045_code_migrations.up.sql: -------------------------------------------------------------------------------- 1 | create table code_migration ( 2 | id text primary key, 3 | applied_at timestamp with time zone not null 4 | ); -------------------------------------------------------------------------------- /internal/storage/migrations/0046_devaddr_appskey_to_device.down.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | drop column app_s_key, 3 | drop column dev_addr; -------------------------------------------------------------------------------- /internal/storage/migrations/0046_devaddr_appskey_to_device.up.sql: -------------------------------------------------------------------------------- 1 | alter table device 2 | add column dev_addr bytea not null default '\x00000000', 3 | add column app_s_key bytea not null default '\x00000000000000000000000000000000'; 4 | 5 | update device d 6 | set 7 | dev_addr = da.dev_addr, 8 | app_s_key = da.app_s_key 9 | from 10 | ( 11 | select 12 | distinct on (dev_eui) * 13 | from 14 | device_activation 15 | order by 16 | dev_eui, 17 | created_at desc 18 | ) da 19 | where 20 | d.dev_eui = da.dev_eui; 21 | 22 | alter table device 23 | alter column dev_addr drop default, 24 | alter column app_s_key drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0047_cleanup_device_activation.down.sql: -------------------------------------------------------------------------------- 1 | create table device_activation ( 2 | id bigserial primary key, 3 | created_at timestamp with time zone not null, 4 | dev_eui bytea not null references device on delete cascade, 5 | dev_addr bytea not null, 6 | app_s_key bytea not null 7 | ); 8 | 9 | create index idx_device_activation_dev_eui on device_activation(dev_eui); -------------------------------------------------------------------------------- /internal/storage/migrations/0047_cleanup_device_activation.up.sql: -------------------------------------------------------------------------------- 1 | drop table device_activation; -------------------------------------------------------------------------------- /internal/storage/migrations/0048_change_device_tags_index.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_tags; 2 | create index idx_device_tags on device using btree (tags); -------------------------------------------------------------------------------- /internal/storage/migrations/0048_change_device_tags_index.up.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_tags; 2 | create index idx_device_tags on device using gin (tags); -------------------------------------------------------------------------------- /internal/storage/migrations/0049_gateway_tags_metadata.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_tags; 2 | 3 | alter table gateway 4 | drop column metadata, 5 | drop column tags; -------------------------------------------------------------------------------- /internal/storage/migrations/0049_gateway_tags_metadata.up.sql: -------------------------------------------------------------------------------- 1 | alter table gateway 2 | add column tags hstore, 3 | add column metadata hstore; 4 | 5 | create index idx_gateway_tags on gateway using gin (tags); -------------------------------------------------------------------------------- /internal/storage/migrations/0050_device_profile_tags.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_profile_tags; 2 | 3 | alter table device_profile 4 | drop column tags; -------------------------------------------------------------------------------- /internal/storage/migrations/0050_device_profile_tags.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_profile 2 | add column tags hstore; 3 | 4 | create index idx_device_profile_tags on device_profile using gin (tags); -------------------------------------------------------------------------------- /internal/storage/migrations/0051_api_keys.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_api_key_application_id; 2 | drop index idx_api_key_organization_id; 3 | drop table api_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0051_api_keys.up.sql: -------------------------------------------------------------------------------- 1 | create table api_key ( 2 | id uuid primary key, 3 | created_at timestamp with time zone not null, 4 | name varchar(100) not null, 5 | is_admin boolean not null default false, 6 | organization_id bigint references organization on delete cascade, 7 | application_id bigint references application on delete cascade 8 | ); 9 | 10 | create index idx_api_key_organization_id on api_key(organization_id); 11 | create index idx_api_key_application_id on api_key(application_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0052_user_openid_connect.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_user_external_id; 2 | drop index idx_user_email; 3 | 4 | alter table "user" 5 | alter column email type varchar(100), 6 | alter column "note" set default ''; 7 | 8 | alter table "user" 9 | drop column email_verified, 10 | drop column external_id; 11 | 12 | alter table "user" 13 | rename column email to username; 14 | 15 | alter table "user" 16 | rename column email_old to email; 17 | 18 | create index idx_user_username_trgm on "user" using gin (username gin_trgm_ops); 19 | create unique index idx_user_username on "user" (username); -------------------------------------------------------------------------------- /internal/storage/migrations/0052_user_openid_connect.up.sql: -------------------------------------------------------------------------------- 1 | drop index idx_user_username; 2 | drop index idx_user_username_trgm; 3 | 4 | alter table "user" 5 | rename column email to email_old; 6 | 7 | alter table "user" 8 | rename column username to email; 9 | 10 | alter table "user" 11 | add column external_id text null, 12 | add column email_verified bool not null default false; 13 | 14 | alter table "user" 15 | alter column email_verified drop default, 16 | alter column "note" drop default, 17 | alter column email type text; 18 | 19 | create unique index idx_user_email on "user" (email); 20 | create unique index idx_user_external_id on "user" (external_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0053_org_max_gateway_device.down.sql: -------------------------------------------------------------------------------- 1 | alter table organization 2 | drop column max_device_count, 3 | drop column max_gateway_count; -------------------------------------------------------------------------------- /internal/storage/migrations/0053_org_max_gateway_device.up.sql: -------------------------------------------------------------------------------- 1 | alter table organization 2 | add column max_device_count integer not null default 0, 3 | add column max_gateway_count integer not null default 0; 4 | 5 | alter table organization 6 | alter column max_device_count drop default, 7 | alter column max_gateway_count drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0054_device_profile_uplink_interval.down.sql: -------------------------------------------------------------------------------- 1 | alter table device_profile 2 | drop column uplink_interval; -------------------------------------------------------------------------------- /internal/storage/migrations/0054_device_profile_uplink_interval.up.sql: -------------------------------------------------------------------------------- 1 | alter table device_profile 2 | add column uplink_interval bigint not null default 86400000000000; 3 | 4 | alter table device_profile 5 | alter column uplink_interval drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0055_gateway_profile_stats_interval.down.sql: -------------------------------------------------------------------------------- 1 | alter table gateway_profile 2 | drop column stats_interval; -------------------------------------------------------------------------------- /internal/storage/migrations/0055_gateway_profile_stats_interval.up.sql: -------------------------------------------------------------------------------- 1 | alter table gateway_profile 2 | add column stats_interval bigint not null default 30000000000; 3 | 4 | alter table gateway_profile 5 | alter column stats_interval drop default; -------------------------------------------------------------------------------- /internal/storage/migrations/0056_remove_fuota.up.sql: -------------------------------------------------------------------------------- 1 | drop table fuota_deployment_device; 2 | 3 | drop index idx_fuota_deployment_next_step_after; 4 | drop index idx_fuota_deployment_state; 5 | drop index idx_fuota_deployment_multicast_group_id; 6 | drop table fuota_deployment; 7 | 8 | drop index idx_remote_fragmentation_session_retry_after; 9 | drop index idx_remote_fragmentation_session_state_provisioned; 10 | drop table remote_fragmentation_session; 11 | 12 | drop index idx_remote_multicast_class_c_session_state_retry_after; 13 | drop index idx_remote_multicast_class_c_session_state_provisioned; 14 | drop table remote_multicast_class_c_session; 15 | 16 | drop index idx_remote_multicast_setup_retry_after; 17 | drop index idx_remote_multicast_setup_state_provisioned; 18 | drop table remote_multicast_setup; 19 | 20 | alter table device_keys 21 | drop column gen_app_key; 22 | 23 | alter table multicast_group 24 | drop column mc_key; -------------------------------------------------------------------------------- /internal/storage/migrations/0057_mqtt_integration_cert.down.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | drop column mqtt_tls_cert; -------------------------------------------------------------------------------- /internal/storage/migrations/0057_mqtt_integration_cert.up.sql: -------------------------------------------------------------------------------- 1 | alter table application 2 | add column mqtt_tls_cert bytea; -------------------------------------------------------------------------------- /internal/storage/migrations/0058_gateway_service_profile.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_gateway_service_profile_id; 2 | 3 | alter table gateway 4 | drop column service_profile_id; -------------------------------------------------------------------------------- /internal/storage/migrations/0058_gateway_service_profile.up.sql: -------------------------------------------------------------------------------- 1 | alter table gateway 2 | add column service_profile_id uuid references service_profile; 3 | 4 | create index idx_gateway_service_profile_id on gateway(service_profile_id); -------------------------------------------------------------------------------- /internal/storage/migrations/0059_multicast_group_application_id.down.sql: -------------------------------------------------------------------------------- 1 | alter table multicast_group 2 | add column service_profile_id uuid null references service_profile; 3 | 4 | create index idx_multicast_group_service_profile_id on multicast_group (service_profile_id); 5 | 6 | update multicast_group 7 | set 8 | service_profile_id = a.service_profile_id 9 | from ( 10 | select 11 | service_profile_id, 12 | id 13 | from 14 | application 15 | ) as a 16 | where 17 | multicast_group.application_id = a.id; 18 | 19 | 20 | alter table multicast_group 21 | drop column application_id, 22 | alter column service_profile_id set not null; 23 | -------------------------------------------------------------------------------- /internal/storage/migrations/0059_multicast_group_application_id.up.sql: -------------------------------------------------------------------------------- 1 | alter table multicast_group 2 | add column application_id bigint null references application on delete cascade; 3 | 4 | create index idx_multicast_group_application_id on multicast_group (application_id); 5 | 6 | update multicast_group 7 | set 8 | application_id = meta.application_id 9 | from ( 10 | select 11 | d.application_id, 12 | dmg.multicast_group_id 13 | from device d 14 | inner join device_multicast_group dmg 15 | on dmg.dev_eui = d.dev_eui 16 | ) as meta 17 | where 18 | multicast_group.id = meta.multicast_group_id; 19 | 20 | alter table multicast_group 21 | drop column service_profile_id, 22 | alter column application_id set not null; 23 | -------------------------------------------------------------------------------- /internal/storage/migrations/0060_dev_addr_to_global_search.down.sql: -------------------------------------------------------------------------------- 1 | drop index idx_device_dev_addr_trgm; -------------------------------------------------------------------------------- /internal/storage/migrations/0060_dev_addr_to_global_search.up.sql: -------------------------------------------------------------------------------- 1 | create index idx_device_dev_addr_trgm on device using gin (encode(dev_addr, 'hex') gin_trgm_ops); -------------------------------------------------------------------------------- /internal/storage/migrations/code/multicast_group_validation.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | import ( 4 | "github.com/gofrs/uuid" 5 | "github.com/jmoiron/sqlx" 6 | "github.com/pkg/errors" 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type multicastGroupCount struct { 11 | ID uuid.UUID `db:"id"` 12 | Name string `db:"name"` 13 | Count int64 `db:"count"` 14 | } 15 | 16 | // Validate that multicast-group devices are under a single application. 17 | func ValidateMulticastGroupDevices(db sqlx.Ext) error { 18 | var items []multicastGroupCount 19 | 20 | err := sqlx.Select(db, &items, ` 21 | select 22 | mg.id, 23 | mg.name, 24 | count(distinct d.application_id) as count 25 | from 26 | multicast_group mg 27 | inner join device_multicast_group dmg 28 | on dmg.multicast_group_id = mg.id 29 | inner join device d 30 | on d.dev_eui = dmg.dev_eui 31 | group by 32 | mg.id 33 | `) 34 | if err != nil { 35 | return errors.Wrap(err, "select error") 36 | } 37 | 38 | for _, item := range items { 39 | if item.Count != 1 { 40 | log.WithFields(log.Fields{ 41 | "multicast_group_id": item.ID, 42 | "multicast_group_name": item.Name, 43 | }).Fatal("Multicast-group contains devices from multiple applications. Please read the changelog why you are seeing this error.") 44 | } 45 | } 46 | 47 | err = sqlx.Select(db, &items, ` 48 | select 49 | mg.id, 50 | mg.name, 51 | count(distinct dmg.dev_eui) as count 52 | from 53 | multicast_group mg 54 | left join device_multicast_group dmg 55 | on mg.id = dmg.multicast_group_id 56 | group by 57 | mg.id 58 | `) 59 | if err != nil { 60 | return errors.Wrap(err, "select error") 61 | } 62 | 63 | for _, item := range items { 64 | if item.Count == 0 { 65 | log.WithFields(log.Fields{ 66 | "multicast_group_id": item.ID, 67 | "multicast_group_name": item.Name, 68 | }).Fatal("Multicast-group does not contain any devices. Please read the changelog why you are seeing this error.") 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/storage/network_server_helpers.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/gofrs/uuid" 7 | "github.com/jmoiron/sqlx" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/brocaar/chirpstack-api/go/v3/ns" 11 | "github.com/brocaar/chirpstack-application-server/internal/backend/networkserver" 12 | ) 13 | 14 | func getNSClientForServiceProfile(ctx context.Context, db sqlx.Queryer, id uuid.UUID) (ns.NetworkServerServiceClient, error) { 15 | n, err := GetNetworkServerForServiceProfileID(ctx, db, id) 16 | if err != nil { 17 | return nil, errors.Wrap(err, "get network-server error") 18 | } 19 | 20 | return getNSClient(n) 21 | } 22 | 23 | func getNSClientForMulticastGroup(ctx context.Context, db sqlx.Queryer, id uuid.UUID) (ns.NetworkServerServiceClient, error) { 24 | n, err := GetNetworkServerForMulticastGroupID(ctx, db, id) 25 | if err != nil { 26 | return nil, errors.Wrap(err, "get network-server error") 27 | } 28 | return getNSClient(n) 29 | } 30 | 31 | func getNSClientForApplication(ctx context.Context, db sqlx.Queryer, id int64) (ns.NetworkServerServiceClient, error) { 32 | n, err := GetNetworkServerForApplicationID(ctx, db, id) 33 | if err != nil { 34 | return nil, errors.Wrap(err, "get network-server error") 35 | } 36 | return getNSClient(n) 37 | } 38 | 39 | func getNSClient(n NetworkServer) (ns.NetworkServerServiceClient, error) { 40 | return networkserver.GetPool().Get(n.Server, []byte(n.CACert), []byte(n.TLSCert), []byte(n.TLSKey)) 41 | } 42 | -------------------------------------------------------------------------------- /internal/test/ca_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAeugAwIBAgIUPGmPN8B060wQIeKHtjsevj1P62MwDQYJKoZIhvcNAQEL 3 | BQAwETEPMA0GA1UEAwwGdW51c2VkMB4XDTIwMDYyOTA2MTg0NloXDTIwMDcyOTA2 4 | MTg0NlowETEPMA0GA1UEAwwGdW51c2VkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEAuZW17mvrdG50wnAKHfz9tskN9HcrhajYsF+KBBBIr6Y9Hr9v4LoW 6 | 8sUUHDrOTmgGmcZ8pELwHuwY6Bbd3AR5UAFvgKnFfgakn7UPMOC9ettZWt+6gUWt 7 | +QIdH2i+WL5hJd9JkD6aoIjNNs+GDys3v/mEmnwF3B5smBIFMKgjG3dHRcmNGQqk 8 | LpaNlbqKB+zKnYhFAx2l3cblG4SOm0E0QaCcZ8A4byGmVwPqXuWX44DcfimBLB7e 9 | qT5wOgT27Lf9IS2D8wHlayNWtnemyq79pvdyuULos0C7FVcO+MpEczFnTNlSE8Mg 10 | 8Z3SFKhm0UIKLAg/8h3VbfHhXob70vRI1wIDAQABo1MwUTAdBgNVHQ4EFgQUXtlj 11 | c+8szZI6TQWUbATSNe2DBtYwHwYDVR0jBBgwFoAUXtljc+8szZI6TQWUbATSNe2D 12 | BtYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABjjae6XqWCIi 13 | 0LjnCtwKf59AEXr4+e1ndIiwjHRbMck+k+flpBwhAgZoyEJaf0LEWfIm19nFUwdx 14 | YrRm9MnnbeNoL9WJbCtYxBHFsEAYfsiU8laWshjQg7KGUHbygL/2/Jqs7vhJC02B 15 | WcNerhi+ILo5VAx8MLzfN5ZXodkCcxrxo31A9QEO+Lf0rRb0a2s68pENPZBExqKW 16 | Ngaczoz3568P5G1IwRqr9oO6GKlOECPVoTbs6XmATxvmAz/0s/o+qNePTXshTM+W 17 | pC6NZ+F6pO7HNK94PxjIX5unCxxHHg10vkwT58ZYz9jO+92+6+nu2YhaxsZVtzvH 18 | a+Qb1U+N3g== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /internal/test/ca_private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5lbXua+t0bnTC 3 | cAod/P22yQ30dyuFqNiwX4oEEEivpj0ev2/guhbyxRQcOs5OaAaZxnykQvAe7Bjo 4 | Ft3cBHlQAW+AqcV+BqSftQ8w4L1621la37qBRa35Ah0faL5YvmEl30mQPpqgiM02 5 | z4YPKze/+YSafAXcHmyYEgUwqCMbd0dFyY0ZCqQulo2VuooH7MqdiEUDHaXdxuUb 6 | hI6bQTRBoJxnwDhvIaZXA+pe5ZfjgNx+KYEsHt6pPnA6BPbst/0hLYPzAeVrI1a2 7 | d6bKrv2m93K5QuizQLsVVw74ykRzMWdM2VITwyDxndIUqGbRQgosCD/yHdVt8eFe 8 | hvvS9EjXAgMBAAECggEAS2X5rk4GMR2XLk/arGhWo+Z3VvT7p6f7sVx/dDsxbLt7 9 | ZXyddY3lOOi8zONPYid9Vwh/JzVS3R1K2TBBHtxqdQjARmOzKwqD46bp7w+/q6Rm 10 | Xzc6TL8EeYBUWKhzfRaL3ZkkCAiHReWOilAxxQPn+ITlTOZKcy2hLP9VQHlvGf7g 11 | ZJXevpIhcPPg2XuzxBJACfpOklrLztJtWkMiBj47SdOaQRUBV7Nj2/aKDoIl6STr 12 | OhyRGa1eViyrDyjWhQRi/JFLFcQC55TDhcpAzzKldRSRV6mri1QOyVHf2kY/hJeJ 13 | ttSPHnjpeFdn0hUMPhljR2xXBu6GJRvElI8BgHzRgQKBgQDo2AbGl0kTk/AvExoh 14 | 5w5TAKspUSbSgFvbFFcGdkIqr3YmjmL3vfaLdcYRfNg/Wt16VStqHS1Vji3KrOPv 15 | MaEvNiRfVGXmWNRlE0uKXbJnhuSaCP2i6uvtD3UC8nfFIA74xDjh8Nc0kdDjoa28 16 | ez1RnrZgtGbR9FV8fopnsAFA9wKBgQDMCoQJXOxtSeSskXdg62lNSWyhbcOKGtgM 17 | bNHER9ZaRp/fsy6UPJfZ3yk2xQ1mRTFoXH2beBqqvgExlLTUcpX3xWujlKNV5VWM 18 | VPzvjL1QzmMf80F99gYi8hb0bctWIKDNz7vvu3euwFychJW6PzJgltx5AXlG719E 19 | WuKeVTsfIQKBgFTA5V5+DNR6ge+wpU932ifuU18bseTnYggRlEe+3gDJk1kfdPi2 20 | hbpnqSwOZGxTN9DilYXvjuPYd+SRH/qd9wzHSWAFyX2aEd2ks1dvGZRDbox+/0vA 21 | 9RV6Dd5/wYbYT3rPeeEMGFmDRiTFYgb8WOqPudTjdZWwuTkymlB5qUsrAoGBAKMt 22 | lcLXKc2NVvAbtdkw65n/qny/h64fIcFuFaRfvzGbYahJaGrsupRnQFKo7Lppg8Xu 23 | khWPy+PY6KN6RdPRf9YTcK315VqAehYsscvyRkUliWi35hyScP3plioM1J0govl1 24 | tNtYlbKP7IDQF95HREay38d2QkNUlF/aP3pYt1uBAoGBAJK1uHuCxna9ETdK4E9w 25 | STn9RpCF7Z7Jp4+mr6G6sGbRYmZYsAUfbhIUBeZ/m7pMk8tToa7hToOCG1QQNYRP 26 | ePadJ0HwIZdWfN0ybTyslKGf9lEG4VIBEwfjDuceUMBTuOFXg8p1KWtL3DjFJfVI 27 | 3ELfVNnfkHCWuoiwOJM+OHNK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /internal/test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | log "github.com/sirupsen/logrus" 8 | 9 | "github.com/brocaar/chirpstack-application-server/internal/config" 10 | ) 11 | 12 | func init() { 13 | config.C.ApplicationServer.ID = "6d5db27e-4ce2-4b2b-b5d7-91f069397978" 14 | config.C.ApplicationServer.API.PublicHost = "localhost:8001" 15 | } 16 | 17 | // GetConfig returns the test configuration. 18 | func GetConfig() config.Config { 19 | log.SetLevel(log.ErrorLevel) 20 | 21 | var c config.Config 22 | 23 | c.PostgreSQL.DSN = "postgres://localhost/chirpstack_as_test?sslmode=disable" 24 | c.Redis.Servers = []string{"localhost:6379"} 25 | c.ApplicationServer.Integration.MQTT.Server = "tcp://localhost:1883" 26 | c.ApplicationServer.ID = "6d5db27e-4ce2-4b2b-b5d7-91f069397978" 27 | c.ApplicationServer.Integration.AMQP.EventRoutingKeyTemplate = "application.{{ .ApplicationID }}.device.{{ .DevEUI }}.event.{{ .EventType }}" 28 | c.ApplicationServer.Integration.Kafka.Topic = "chirpstack_as" 29 | c.ApplicationServer.Integration.Kafka.EventKeyTemplate = "application.{{ .ApplicationID }}.device.{{ .DevEUI }}.event.{{ .EventType }}" 30 | 31 | if v := os.Getenv("TEST_POSTGRES_DSN"); v != "" { 32 | c.PostgreSQL.DSN = v 33 | } 34 | 35 | if v := os.Getenv("TEST_REDIS_SERVERS"); v != "" { 36 | c.Redis.Servers = strings.Split(v, ",") 37 | } 38 | 39 | if v := os.Getenv("TEST_MQTT_SERVER"); v != "" { 40 | c.ApplicationServer.Integration.MQTT.Server = v 41 | } 42 | 43 | if v := os.Getenv("TEST_MQTT_USERNAME"); v != "" { 44 | c.ApplicationServer.Integration.MQTT.Username = v 45 | } 46 | 47 | if v := os.Getenv("TEST_MQTT_PASSWORD"); v != "" { 48 | c.ApplicationServer.Integration.MQTT.Password = v 49 | } 50 | 51 | if v := os.Getenv("TEST_RABBITMQ_URL"); v != "" { 52 | c.ApplicationServer.Integration.AMQP.URL = v 53 | } 54 | 55 | if v := os.Getenv("TEST_KAFKA_BROKER"); v != "" { 56 | c.ApplicationServer.Integration.Kafka.Brokers = []string{v} 57 | } 58 | 59 | return c 60 | } 61 | -------------------------------------------------------------------------------- /internal/tools/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/golang/protobuf/protoc-gen-go" 7 | _ "github.com/goreleaser/goreleaser" 8 | _ "github.com/goreleaser/nfpm" 9 | _ "golang.org/x/lint/golint" 10 | _ "golang.org/x/tools/cmd/stringer" 11 | ) 12 | -------------------------------------------------------------------------------- /packaging/files/chirpstack-application-server.init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ### BEGIN INIT INFO 3 | # Provides: chirpstack-application-server 4 | # Required-Start: $all 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: ChirpStack Application Server 9 | ### END INIT INFO 10 | 11 | 12 | NAME=chirpstack-application-server 13 | DESC="ChirpStack Application Server" 14 | DAEMON_USER=appserver 15 | DAEMON_GROUP=appserver 16 | DAEMON=/usr/bin/$NAME 17 | PID_FILE=/var/run/$NAME.pid 18 | 19 | 20 | # check root 21 | if [ "$UID" != "0" ]; then 22 | echo "You must be root to run this script" 23 | exit 1 24 | fi 25 | 26 | # check daemon 27 | if [ ! -x $DAEMON ]; then 28 | echo "Executable $DAEMON does not exist" 29 | exit 5 30 | fi 31 | 32 | # load functions and settings 33 | . /lib/lsb/init-functions 34 | 35 | if [ -r /etc/default/rcS ]; then 36 | . /etc/default/rcS 37 | fi 38 | 39 | function do_start { 40 | start-stop-daemon --start --background --chuid "$DAEMON_USER:$DAEMON_GROUP" --make-pidfile --pidfile "$PID_FILE" --startas /bin/bash -- -c "exec $DAEMON >> /var/log/$NAME/$NAME.log 2>&1" 41 | } 42 | 43 | function do_stop { 44 | start-stop-daemon --stop --retry=TERM/30/KILL/5 --pidfile "$PID_FILE" --exec "$DAEMON" 45 | retval="$?" 46 | sleep 1 47 | return "$retval" 48 | } 49 | 50 | case "$1" in 51 | start) 52 | log_daemon_msg "Starting $DESC" 53 | do_start 54 | case "$?" in 55 | 0|1) log_end_msg 0 ;; 56 | 2) log_end_msg 1 ;; 57 | esac 58 | ;; 59 | stop) 60 | log_daemon_msg "Stopping $DESC" 61 | do_stop 62 | case "$?" in 63 | 0|1) log_end_msg 0 ;; 64 | 2) log_end_msg 1 ;; 65 | esac 66 | ;; 67 | restart) 68 | log_daemon_msg "Restarting $DESC" 69 | do_stop 70 | case "$?" in 71 | 0|1) 72 | do_start 73 | case "$?" in 74 | 0) log_end_msg 0 ;; 75 | 1) log_end_msg 1 ;; 76 | *) log_end_msg 1 ;; 77 | esac 78 | ;; 79 | *) 80 | log_end_msg 1 81 | ;; 82 | esac 83 | ;; 84 | status) 85 | status_of_proc -p "$PID_FILE" "$DAEMON" "$NAME" && exit 0 || exit $? 86 | ;; 87 | *) 88 | echo "Usage: $NAME {start|stop|restart|status}" >&2 89 | exit 3 90 | ;; 91 | esac 92 | -------------------------------------------------------------------------------- /packaging/files/chirpstack-application-server.rotate: -------------------------------------------------------------------------------- 1 | /var/log/chirpstack-application-server/chirpstack-application-server.log { 2 | daily 3 | rotate 7 4 | missingok 5 | dateext 6 | copytruncate 7 | compress 8 | } 9 | -------------------------------------------------------------------------------- /packaging/files/chirpstack-application-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=ChirpStack Application Server 3 | Documentation=https://www.chirpstack.io/ 4 | Wants=network-online.target 5 | After=network-online.target 6 | 7 | [Service] 8 | User=appserver 9 | Group=appserver 10 | ExecStart=/usr/bin/chirpstack-application-server 11 | Restart=on-failure 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | Alias=lora-app-server.service 16 | -------------------------------------------------------------------------------- /packaging/scripts/post-remove.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OLD_NAME=lora-app-server 4 | NAME=chirpstack-application-server 5 | 6 | function remove_systemd { 7 | systemctl stop $NAME 8 | systemctl disable $NAME 9 | rm -f /lib/systemd/system/$NAME.service 10 | } 11 | 12 | function remove_initd { 13 | /etc/init.d/$NAME stop 14 | update-rc.d -f $NAME remove 15 | rm -f /etc/init.d/$NAME 16 | rm -f /etc/init.d/$OLD_NAME 17 | } 18 | 19 | which systemctl &>/dev/null 20 | if [[ $? -eq 0 ]]; then 21 | remove_systemd 22 | else 23 | remove_initd 24 | fi 25 | -------------------------------------------------------------------------------- /packaging/scripts/pre-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OLD_NAME=lora-app-server 4 | NAME=chirpstack-application-server 5 | 6 | # migrate config to new location 7 | if [[ -f /etc/$OLD_NAME/$OLD_NAME.toml ]] && [[ ! -h /etc/$OLD_NAME/$OLD_NAME.toml ]] && [[ ! -f /etc/$NAME/$NAME.toml ]]; then 8 | echo "Migrating /etc/$OLD_NAME/$OLD_NAME.toml to /etc/$NAME/$NAME.toml" 9 | 10 | mkdir -p /etc/$NAME 11 | mv /etc/$OLD_NAME/$OLD_NAME.toml /etc/$NAME/$NAME.toml 12 | 13 | echo "Creating symlink /etc/$OLD_NAME/$OLD_NAME.toml for backwards compatibility" 14 | ln -s /etc/$NAME/$NAME.toml /etc/$OLD_NAME/$OLD_NAME.toml 15 | fi 16 | 17 | function stop_init() { 18 | if [[ -f /etc/init.d/$OLD_NAME ]]; then 19 | echo "Stopping $OLD_NAME" 20 | /etc/init.d/$OLD_NAME stop 21 | fi 22 | } 23 | 24 | function stop_systemd() { 25 | if [[ -f /lib/systemd/system/$OLD_NAME.service ]]; then 26 | echo "Stopping $OLD_NAME" 27 | systemctl stop $OLD_NAME 28 | fi 29 | } 30 | 31 | # stop old service 32 | which systemctl &>/dev/null 33 | if [[ $? -eq 0 ]]; then 34 | stop_systemd 35 | else 36 | stop_init 37 | fi 38 | -------------------------------------------------------------------------------- /static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "embed" 4 | 5 | // FS contains the static FS. 6 | //go:embed integrations/* logo/* static/* swagger/* vendor/* *.json *.png *.html *.js 7 | var FS embed.FS 8 | -------------------------------------------------------------------------------- /static/vendor/swagger/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 SmartBear Software 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /static/vendor/swagger/css/reset.css: -------------------------------------------------------------------------------- 1 | a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0} -------------------------------------------------------------------------------- /static/vendor/swagger/css/typography.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/css/typography.css -------------------------------------------------------------------------------- /static/vendor/swagger/fonts/DroidSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/fonts/DroidSans-Bold.ttf -------------------------------------------------------------------------------- /static/vendor/swagger/fonts/DroidSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/fonts/DroidSans.ttf -------------------------------------------------------------------------------- /static/vendor/swagger/images/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/collapse.gif -------------------------------------------------------------------------------- /static/vendor/swagger/images/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/expand.gif -------------------------------------------------------------------------------- /static/vendor/swagger/images/explorer_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/explorer_icons.png -------------------------------------------------------------------------------- /static/vendor/swagger/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/favicon-16x16.png -------------------------------------------------------------------------------- /static/vendor/swagger/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/favicon-32x32.png -------------------------------------------------------------------------------- /static/vendor/swagger/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/favicon.ico -------------------------------------------------------------------------------- /static/vendor/swagger/images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/logo_small.png -------------------------------------------------------------------------------- /static/vendor/swagger/images/pet_store_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/pet_store_api.png -------------------------------------------------------------------------------- /static/vendor/swagger/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/throbber.gif -------------------------------------------------------------------------------- /static/vendor/swagger/images/wordnik_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/static/vendor/swagger/images/wordnik_api.png -------------------------------------------------------------------------------- /static/vendor/swagger/lib/highlight.9.1.0.pack_extended.js: -------------------------------------------------------------------------------- 1 | "use strict";!function(){var h,l;h=hljs.configure,hljs.configure=function(l){var i=l.highlightSizeThreshold;hljs.highlightSizeThreshold=i===+i?i:null,h.call(this,l)},l=hljs.highlightBlock,hljs.highlightBlock=function(h){var i=h.innerHTML,g=hljs.highlightSizeThreshold;(null==g||g>i.length)&&l.call(hljs,h)}}(); -------------------------------------------------------------------------------- /static/vendor/swagger/lib/jquery.slideto.min.js: -------------------------------------------------------------------------------- 1 | !function(i){i.fn.slideto=function(o){return o=i.extend({slide_duration:"slow",highlight_duration:3e3,highlight:!0,highlight_color:"#FFFF99"},o),this.each(function(){obj=i(this),i("body").animate({scrollTop:obj.offset().top},o.slide_duration,function(){o.highlight&&i.ui.version&&obj.effect("highlight",{color:o.highlight_color},o.highlight_duration)})})}}(jQuery); -------------------------------------------------------------------------------- /static/vendor/swagger/lib/jquery.wiggle.min.js: -------------------------------------------------------------------------------- 1 | jQuery.fn.wiggle=function(e){var a={speed:50,wiggles:3,travel:5,callback:null},e=jQuery.extend(a,e);return this.each(function(){var a=this,l=(jQuery(this).wrap('
').css("position","relative"),0);for(i=1;i<=e.wiggles;i++)jQuery(this).animate({left:"-="+e.travel},e.speed).animate({left:"+="+2*e.travel},2*e.speed).animate({left:"-="+e.travel},e.speed,function(){l++,jQuery(a).parent().hasClass("wiggle-wrap")&&jQuery(a).parent().replaceWith(a),l==e.wiggles&&jQuery.isFunction(e.callback)&&e.callback()})})}; -------------------------------------------------------------------------------- /static/vendor/swagger/lib/object-assign-pollyfill.js: -------------------------------------------------------------------------------- 1 | "function"!=typeof Object.assign&&!function(){Object.assign=function(n){"use strict";if(void 0===n||null===n)throw new TypeError("Cannot convert undefined or null to object");for(var t=Object(n),o=1;o0.2%", 49 | "not dead", 50 | "not ie <= 11", 51 | "not op_mini all" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /ui/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/icon.png -------------------------------------------------------------------------------- /ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | ChirpStack Application Server 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ui/public/integrations/aws_sns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/aws_sns.png -------------------------------------------------------------------------------- /ui/public/integrations/azure_service_bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/azure_service_bus.png -------------------------------------------------------------------------------- /ui/public/integrations/gcp_pubsub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/gcp_pubsub.png -------------------------------------------------------------------------------- /ui/public/integrations/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/http.png -------------------------------------------------------------------------------- /ui/public/integrations/influxdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/influxdb.png -------------------------------------------------------------------------------- /ui/public/integrations/loracloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/loracloud.png -------------------------------------------------------------------------------- /ui/public/integrations/mqtt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/mqtt.png -------------------------------------------------------------------------------- /ui/public/integrations/my_devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/my_devices.png -------------------------------------------------------------------------------- /ui/public/integrations/pilot_things.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/pilot_things.png -------------------------------------------------------------------------------- /ui/public/integrations/thingsboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/integrations/thingsboard.png -------------------------------------------------------------------------------- /ui/public/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brocaar/chirpstack-application-server/f2f2c349d13737fbef57eb9acdfb6af25657bac3/ui/public/logo/logo.png -------------------------------------------------------------------------------- /ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /ui/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /ui/src/components/Admin.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | 3 | import SessionStore from "../stores/SessionStore"; 4 | 5 | 6 | class Admin extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | admin: false, 11 | }; 12 | 13 | this.setIsAdmin = this.setIsAdmin.bind(this); 14 | } 15 | 16 | componentDidMount() { 17 | SessionStore.on("change", this.setIsAdmin); 18 | this.setIsAdmin(); 19 | } 20 | 21 | componentWillUnmount() { 22 | SessionStore.removeListener("change", this.setIsAdmin); 23 | } 24 | 25 | componentDidUpdate(prevProps) { 26 | if (prevProps === this.props) { 27 | return; 28 | } 29 | 30 | this.setIsAdmin(); 31 | } 32 | 33 | setIsAdmin() { 34 | if (this.props.organizationID !== undefined) { 35 | this.setState({ 36 | admin: SessionStore.isAdmin() || SessionStore.isOrganizationAdmin(this.props.organizationID), 37 | }); 38 | } else { 39 | this.setState({ 40 | admin: SessionStore.isAdmin(), 41 | }); 42 | } 43 | } 44 | 45 | render() { 46 | if (this.state.admin) { 47 | return(this.props.children); 48 | } 49 | 50 | return(null); 51 | } 52 | } 53 | 54 | export default Admin; 55 | -------------------------------------------------------------------------------- /ui/src/components/DeviceAdmin.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import SessionStore from "../stores/SessionStore"; 3 | 4 | 5 | class DeviceAdmin extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | admin: false, 10 | }; 11 | 12 | this.setIsAdmin = this.setIsAdmin.bind(this); 13 | } 14 | 15 | componentDidMount() { 16 | SessionStore.on("change", this.setIsAdmin); 17 | this.setIsAdmin(); 18 | } 19 | 20 | componentWillUnmount() { 21 | SessionStore.removeListener("change", this.setIsAdmin); 22 | } 23 | 24 | componentDidUpdate(prevProps) { 25 | if (prevProps === this.props) { 26 | return; 27 | } 28 | 29 | this.setIsAdmin(); 30 | } 31 | 32 | setIsAdmin() { 33 | if (this.props.organizationID !== undefined) { 34 | this.setState({ 35 | admin: SessionStore.isAdmin() || SessionStore.isOrganizationDeviceAdmin(this.props.organizationID), 36 | }); 37 | } else { 38 | this.setState({ 39 | admin: SessionStore.isAdmin(), 40 | }); 41 | } 42 | } 43 | 44 | render() { 45 | if (this.state.admin) { 46 | return(this.props.children); 47 | } 48 | 49 | return(null); 50 | } 51 | } 52 | 53 | export default DeviceAdmin; 54 | -------------------------------------------------------------------------------- /ui/src/components/DurationField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import TextField from "@material-ui/core/TextField"; 4 | 5 | class DurationField extends Component { 6 | constructor() { 7 | super(); 8 | 9 | this.state = { 10 | value: 0, 11 | }; 12 | } 13 | 14 | 15 | onChange = (e) => { 16 | this.setState({ 17 | value: e.target.value, 18 | }); 19 | 20 | this.props.onChange({ 21 | target: { 22 | value: `${e.target.value}s`, 23 | type: "text", 24 | id: this.props.id, 25 | }, 26 | }); 27 | } 28 | 29 | componentDidMount() { 30 | const str = this.props.value || ""; 31 | this.setState({ 32 | value: str.replace(/s/, ''), 33 | }); 34 | } 35 | 36 | render() { 37 | return( 38 | 49 | ); 50 | } 51 | } 52 | 53 | export default DurationField; 54 | 55 | -------------------------------------------------------------------------------- /ui/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import Typography from "@material-ui/core/Typography"; 5 | 6 | import InternalStore from "../stores/InternalStore"; 7 | import theme from "../theme"; 8 | 9 | const styles = { 10 | footer: { 11 | paddingBottom: theme.spacing(1), 12 | "& a": { 13 | color: theme.palette.primary.main, 14 | textDecoration: "none", 15 | }, 16 | }, 17 | }; 18 | 19 | class Footer extends Component { 20 | constructor() { 21 | super(); 22 | this.state = { 23 | footer: null, 24 | }; 25 | } 26 | 27 | componentDidMount() { 28 | InternalStore.settings(resp => { 29 | if (resp.branding.footer !== "") { 30 | this.setState({ 31 | footer: resp.branding.footer, 32 | }); 33 | } 34 | }); 35 | } 36 | 37 | render() { 38 | if (this.state.footer === null) { 39 | return(null); 40 | } 41 | 42 | return( 43 |
44 | 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default withStyles(styles)(Footer); 51 | -------------------------------------------------------------------------------- /ui/src/components/Form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import Button from "@material-ui/core/Button"; 5 | import { withStyles } from "@material-ui/core/styles"; 6 | 7 | 8 | const styles = { 9 | formControl: { 10 | paddingTop: 24, 11 | }, 12 | } 13 | 14 | 15 | class Form extends Component { 16 | render() { 17 | return( 18 |
19 | {this.props.children} 20 | 21 | 22 | {this.props.extraButtons} 23 | {this.props.submitLabel && } 24 | 25 |
26 | ); 27 | } 28 | } 29 | 30 | export default withStyles(styles)(Form); 31 | -------------------------------------------------------------------------------- /ui/src/components/FormControl.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import FormControl from '@material-ui/core/FormControl'; 4 | import FormLabel from '@material-ui/core/FormLabel'; 5 | import { withStyles } from "@material-ui/core/styles"; 6 | 7 | import theme from "../theme"; 8 | 9 | 10 | const styles = { 11 | formControl: { 12 | marginTop: theme.spacing(4), 13 | }, 14 | formLabel: { 15 | color: theme.palette.primary.main, 16 | }, 17 | }; 18 | 19 | 20 | class FormControlComponent extends Component { 21 | render() { 22 | return( 23 | 24 | 25 | {this.props.label} 26 | 27 | {this.props.children} 28 | 29 | ); 30 | } 31 | } 32 | 33 | export default withStyles(styles)(FormControlComponent); 34 | -------------------------------------------------------------------------------- /ui/src/components/GatewayAdmin.js: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import SessionStore from "../stores/SessionStore"; 3 | 4 | 5 | class GatewayAdmin extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | admin: false, 10 | }; 11 | 12 | this.setIsAdmin = this.setIsAdmin.bind(this); 13 | } 14 | 15 | componentDidMount() { 16 | SessionStore.on("change", this.setIsAdmin); 17 | this.setIsAdmin(); 18 | } 19 | 20 | componentWillUnmount() { 21 | SessionStore.removeListener("change", this.setIsAdmin); 22 | } 23 | 24 | componentDidUpdate(prevProps) { 25 | if (prevProps === this.props) { 26 | return; 27 | } 28 | 29 | this.setIsAdmin(); 30 | } 31 | 32 | setIsAdmin() { 33 | if (this.props.organizationID !== undefined) { 34 | this.setState({ 35 | admin: SessionStore.isAdmin() || SessionStore.isOrganizationGatewayAdmin(this.props.organizationID), 36 | }); 37 | } else { 38 | this.setState({ 39 | admin: SessionStore.isAdmin(), 40 | }); 41 | } 42 | } 43 | 44 | render() { 45 | if (this.state.admin) { 46 | return(this.props.children); 47 | } 48 | 49 | return(null); 50 | } 51 | } 52 | 53 | export default GatewayAdmin; 54 | -------------------------------------------------------------------------------- /ui/src/components/KVForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import Grid from "@material-ui/core/Grid"; 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import TextField from '@material-ui/core/TextField'; 7 | 8 | import Delete from "mdi-material-ui/Delete"; 9 | 10 | import FormComponent from "../classes/FormComponent"; 11 | import theme from "../theme"; 12 | 13 | 14 | const kvStyles = { 15 | formLabel: { 16 | fontSize: 12, 17 | }, 18 | delete: { 19 | marginTop: 3 * theme.spacing(1), 20 | }, 21 | }; 22 | 23 | 24 | class KVForm extends FormComponent { 25 | onChange(e) { 26 | super.onChange(e); 27 | this.props.onChange(this.props.index, this.state.object); 28 | } 29 | 30 | onDelete = (e) => { 31 | e.preventDefault(); 32 | this.props.onDelete(this.props.index); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 52 | 53 | 54 | 63 | 64 | 65 | {!!!this.props.disabled && 66 | 67 | } 68 | 69 | 70 | ); 71 | } 72 | } 73 | 74 | export default withStyles(kvStyles)(KVForm); 75 | -------------------------------------------------------------------------------- /ui/src/components/Loaded.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | 4 | class Loaded extends Component { 5 | constructor() { 6 | super(); 7 | 8 | this.state = { 9 | loaded: false, 10 | }; 11 | 12 | this.testLoaded = this.testLoaded.bind(this); 13 | } 14 | 15 | componentDidMount() { 16 | this.testLoaded(this.props.loaded); 17 | } 18 | 19 | componentDidUpdate(prevProps) { 20 | if (prevProps === this.props) { 21 | return; 22 | } 23 | 24 | this.testLoaded(this.props.loaded); 25 | } 26 | 27 | testLoaded(obj) { 28 | for (const key of Object.keys(obj)) { 29 | var loaded = true; 30 | 31 | if (obj[key] === false) { 32 | loaded = false; 33 | } 34 | } 35 | 36 | this.setState({ 37 | loaded: loaded, 38 | }); 39 | } 40 | 41 | render() { 42 | return( 43 |
44 | {this.state.loaded && this.props.children} 45 |
46 | ); 47 | } 48 | } 49 | 50 | export default Loaded; 51 | -------------------------------------------------------------------------------- /ui/src/components/MapTileLayer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { TileLayer } from 'react-leaflet'; 4 | 5 | 6 | class MapTileLayer extends Component { 7 | render() { 8 | return( 9 | 13 | ) 14 | } 15 | } 16 | 17 | export default MapTileLayer; 18 | -------------------------------------------------------------------------------- /ui/src/components/Notifications.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | 4 | import Snackbar from '@material-ui/core/Snackbar'; 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import Close from "mdi-material-ui/Close"; 7 | 8 | import NotificationStore from "../stores/NotificationStore"; 9 | import dispatcher from "../dispatcher"; 10 | 11 | 12 | class Item extends Component { 13 | constructor() { 14 | super(); 15 | this.onClose = this.onClose.bind(this); 16 | } 17 | 18 | onClose(event, reason) { 19 | dispatcher.dispatch({ 20 | type: "DELETE_NOTIFICATION", 21 | id: this.props.id, 22 | }); 23 | } 24 | 25 | render() { 26 | return( 27 | {this.props.notification.message}} 34 | autoHideDuration={3000} 35 | onClose={this.onClose} 36 | action={[ 37 | 43 | 44 | 45 | ]} 46 | /> 47 | ); 48 | } 49 | } 50 | 51 | 52 | class Notifications extends Component { 53 | constructor() { 54 | super(); 55 | 56 | this.state = { 57 | notifications: NotificationStore.getAll(), 58 | }; 59 | } 60 | 61 | componentDidMount() { 62 | NotificationStore.on("change", () => { 63 | this.setState({ 64 | notifications: NotificationStore.getAll(), 65 | }); 66 | }); 67 | } 68 | 69 | render() { 70 | const items = this.state.notifications.map((n, i) => ); 71 | 72 | return (items); 73 | } 74 | } 75 | 76 | export default withRouter(Notifications); 77 | -------------------------------------------------------------------------------- /ui/src/components/Paper.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Paper from '@material-ui/core/Paper'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | 6 | import theme from "../theme"; 7 | 8 | 9 | const styles = { 10 | root: { 11 | padding: theme.spacing(2), 12 | }, 13 | }; 14 | 15 | 16 | class PaperComponent extends Component { 17 | render() { 18 | return( 19 | 20 | {this.props.children} 21 | 22 | ); 23 | } 24 | } 25 | 26 | export default withStyles(styles)(PaperComponent); -------------------------------------------------------------------------------- /ui/src/components/TableCellLink.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import TableCell from '@material-ui/core/TableCell'; 5 | import { withStyles } from '@material-ui/core/styles'; 6 | 7 | import theme from "../theme"; 8 | 9 | 10 | const styles = { 11 | link: { 12 | textDecoration: "none", 13 | color: theme.palette.primary.main, 14 | cursor: "pointer", 15 | }, 16 | }; 17 | 18 | 19 | class TableCellLink extends Component { 20 | render() { 21 | return( 22 | 23 | {this.props.to && {this.props.children}} 24 | {this.props.onClick && {this.props.children}} 25 | 26 | ); 27 | } 28 | } 29 | 30 | export default withStyles(styles)(TableCellLink); 31 | -------------------------------------------------------------------------------- /ui/src/components/TitleBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | 6 | 7 | const styles = { 8 | clear: { 9 | clear: "both", 10 | }, 11 | 12 | left: { 13 | float: "left", 14 | }, 15 | 16 | right: { 17 | float: "right", 18 | }, 19 | }; 20 | 21 | 22 | class TitleBar extends Component { 23 | render() { 24 | return( 25 | 26 |
27 | {this.props.children} 28 |
29 |
30 | {this.props.buttons} 31 |
32 |
33 | ); 34 | } 35 | } 36 | 37 | export default withStyles(styles)(TitleBar); 38 | -------------------------------------------------------------------------------- /ui/src/components/TitleBarButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import Button from '@material-ui/core/Button'; 6 | 7 | import theme from "../theme"; 8 | 9 | 10 | const styles = { 11 | button: { 12 | marginLeft: theme.spacing(1), 13 | }, 14 | icon: { 15 | marginRight: theme.spacing(1), 16 | }, 17 | }; 18 | 19 | 20 | class TitleBarButton extends Component { 21 | render() { 22 | let component = "button"; 23 | let icon = null; 24 | 25 | if (this.props.to !== undefined) { 26 | component = Link 27 | } 28 | 29 | if (this.props.icon !== undefined) { 30 | icon = React.cloneElement(this.props.icon, { 31 | className: this.props.classes.icon, 32 | }) 33 | } 34 | 35 | return( 36 | 47 | ); 48 | } 49 | } 50 | 51 | export default withStyles(styles)(TitleBarButton); 52 | -------------------------------------------------------------------------------- /ui/src/components/TitleBarTitle.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import classNames from "classnames"; 5 | 6 | import Typography from '@material-ui/core/Typography'; 7 | import { withStyles } from '@material-ui/core/styles'; 8 | 9 | import theme from "../theme"; 10 | 11 | const styles = { 12 | title: { 13 | marginTop: theme.spacing(1), 14 | marginBottom: theme.spacing(1), 15 | marginRight: theme.spacing(1), 16 | float: "left", 17 | }, 18 | 19 | link: { 20 | textDecoration: "none", 21 | color: theme.palette.primary.main, 22 | }, 23 | }; 24 | 25 | 26 | class TitleBarTitle extends Component { 27 | render() { 28 | let component = null; 29 | let combinedStyles = null; 30 | 31 | if (this.props.to !== undefined) { 32 | component = Link; 33 | combinedStyles = classNames(this.props.classes.title, this.props.classes.link); 34 | } else { 35 | combinedStyles = this.props.classes.title; 36 | } 37 | 38 | 39 | return( 40 | 41 | {this.props.title} 42 | 43 | ); 44 | } 45 | } 46 | 47 | export default withStyles(styles)(TitleBarTitle); 48 | -------------------------------------------------------------------------------- /ui/src/dispatcher.js: -------------------------------------------------------------------------------- 1 | import { Dispatcher } from "flux"; 2 | 3 | export default new Dispatcher(); 4 | -------------------------------------------------------------------------------- /ui/src/history.js: -------------------------------------------------------------------------------- 1 | import { createHashHistory } from 'history'; 2 | export default createHashHistory(); 3 | -------------------------------------------------------------------------------- /ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import "typeface-roboto"; 5 | import Leaflet from "leaflet"; 6 | import { Chart } from 'chart.js'; 7 | import { MatrixElement, MatrixController } from 'chartjs-chart-matrix'; 8 | import 'chartjs-adapter-moment'; 9 | 10 | import App from "./App"; 11 | 12 | import "leaflet/dist/leaflet.css"; 13 | import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css"; 14 | import "codemirror/lib/codemirror.css"; 15 | import "codemirror/theme/base16-light.css"; 16 | import "react-leaflet-markercluster/dist/styles.min.css"; 17 | import "@fortawesome/fontawesome-free/css/all.min.css"; 18 | import "./index.css"; 19 | 20 | Leaflet.Icon.Default.imagePath = "//cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0/images/" 21 | Chart.register(MatrixController, MatrixElement); 22 | 23 | ReactDOM.render(, document.getElementById("root")); 24 | -------------------------------------------------------------------------------- /ui/src/stores/DeviceQueueStore.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | 3 | import Swagger from "swagger-client"; 4 | 5 | import sessionStore from "./SessionStore"; 6 | import {checkStatus, errorHandler } from "./helpers"; 7 | import dispatcher from "../dispatcher"; 8 | 9 | 10 | class DeviceQueueStore extends EventEmitter { 11 | constructor() { 12 | super(); 13 | this.swagger = new Swagger("/swagger/deviceQueue.swagger.json", sessionStore.getClientOpts()); 14 | } 15 | 16 | flush(devEUI, callbackFunc) { 17 | this.swagger.then(client => { 18 | client.apis.DeviceQueueService.Flush({ 19 | dev_eui: devEUI, 20 | }) 21 | .then(checkStatus) 22 | .then(resp => { 23 | this.notify("device-queue has been flushed"); 24 | callbackFunc(resp.obj); 25 | }) 26 | .catch(errorHandler); 27 | }); 28 | } 29 | 30 | list(devEUI, callbackFunc) { 31 | this.swagger.then(client => { 32 | client.apis.DeviceQueueService.List({ 33 | dev_eui: devEUI, 34 | }) 35 | .then(checkStatus) 36 | .then(resp => { 37 | callbackFunc(resp.obj); 38 | }) 39 | .catch(errorHandler); 40 | }); 41 | } 42 | 43 | enqueue(item, callbackFunc) { 44 | this.swagger.then(client => { 45 | client.apis.DeviceQueueService.Enqueue({ 46 | "device_queue_item.dev_eui": item.devEUI, 47 | body: { 48 | deviceQueueItem: item, 49 | }, 50 | }) 51 | .then(checkStatus) 52 | .then(resp => { 53 | this.notify("device-queue item has been created"); 54 | this.emit("enqueue"); 55 | callbackFunc(resp.obj); 56 | }) 57 | .catch(errorHandler); 58 | }); 59 | } 60 | 61 | notify(msg) { 62 | dispatcher.dispatch({ 63 | type: "CREATE_NOTIFICATION", 64 | notification: { 65 | type: "success", 66 | message: msg, 67 | }, 68 | }); 69 | } 70 | } 71 | 72 | const deviceQueueStore = new DeviceQueueStore(); 73 | export default deviceQueueStore; 74 | -------------------------------------------------------------------------------- /ui/src/stores/LocationStore.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import "whatwg-fetch"; 3 | 4 | 5 | class LocationStore extends EventEmitter { 6 | getLocation(callbackFunc) { 7 | if (navigator.geolocation) { 8 | navigator.geolocation.getCurrentPosition((position) => { 9 | callbackFunc(position); 10 | }); 11 | } 12 | } 13 | } 14 | 15 | const locationStore = new LocationStore(); 16 | 17 | export default locationStore; 18 | -------------------------------------------------------------------------------- /ui/src/stores/NotificationStore.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import dispatcher from "../dispatcher"; 3 | 4 | 5 | class NotificationStore extends EventEmitter { 6 | constructor() { 7 | super(); 8 | this.notifications = []; 9 | } 10 | 11 | getAll() { 12 | return this.notifications; 13 | } 14 | 15 | createNotification(type, message) { 16 | const id = Date.now(); 17 | 18 | this.notifications.push({ 19 | id: id, 20 | type: type, 21 | message: message, 22 | }); 23 | 24 | this.emit("change"); 25 | } 26 | 27 | deleteNotification(id) { 28 | let notification = null; 29 | 30 | for(var n of this.notifications) { 31 | if(n.id === id) { 32 | notification = n; 33 | } 34 | } 35 | 36 | this.notifications.splice(this.notifications.indexOf(notification), 1); 37 | this.emit("change"); 38 | } 39 | 40 | handleActions(action) { 41 | switch(action.type) { 42 | case "CREATE_NOTIFICATION": { 43 | this.createNotification(action.notification.type, action.notification.message); 44 | break; 45 | } 46 | case "DELETE_NOTIFICATION": { 47 | this.deleteNotification(action.id); 48 | break; 49 | } 50 | default: 51 | break; 52 | } 53 | } 54 | } 55 | 56 | 57 | const notificationStore = new NotificationStore(); 58 | dispatcher.register(notificationStore.handleActions.bind(notificationStore)); 59 | 60 | export default notificationStore; 61 | -------------------------------------------------------------------------------- /ui/src/theme.js: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core/styles"; 2 | import blue from "@material-ui/core/colors/blue"; 3 | 4 | 5 | const theme = createMuiTheme({ 6 | palette: { 7 | primary: blue, 8 | }, 9 | }); 10 | 11 | export default theme; 12 | -------------------------------------------------------------------------------- /ui/src/views/api-keys/CreateAdminAPIKey.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import Card from '@material-ui/core/Card'; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import TitleBar from "../../components/TitleBar"; 8 | import TitleBarTitle from "../../components/TitleBarTitle"; 9 | import APIKeyForm from "./APIKeyForm"; 10 | 11 | 12 | class CreateAdminAPIKey extends Component { 13 | render() { 14 | return( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default CreateAdminAPIKey; 39 | -------------------------------------------------------------------------------- /ui/src/views/api-keys/CreateOrganizationAPIKey.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import Card from '@material-ui/core/Card'; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import TitleBar from "../../components/TitleBar"; 8 | import TitleBarTitle from "../../components/TitleBarTitle"; 9 | import APIKeyForm from "./APIKeyForm"; 10 | 11 | 12 | class CreateOrganizationAPIKey extends Component { 13 | render() { 14 | return( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | } 37 | 38 | export default CreateOrganizationAPIKey; 39 | -------------------------------------------------------------------------------- /ui/src/views/applications/UpdateApplication.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Card from '@material-ui/core/Card'; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | 9 | import ApplicationStore from "../../stores/ApplicationStore"; 10 | import ApplicationForm from "./ApplicationForm"; 11 | 12 | 13 | const styles = { 14 | card: { 15 | overflow: "visible", 16 | }, 17 | }; 18 | 19 | 20 | class UpdateApplication extends Component { 21 | constructor() { 22 | super(); 23 | this.onSubmit = this.onSubmit.bind(this); 24 | } 25 | 26 | onSubmit(application) { 27 | ApplicationStore.update(application, resp => { 28 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${application.id}`); 29 | }); 30 | } 31 | 32 | render() { 33 | return( 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default withStyles(styles)(withRouter(UpdateApplication)); 53 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateAWSSNSIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import AWSSNSIntegrationForm from "./AWSSNSIntegrationForm"; 9 | 10 | 11 | class CreateAWSSNSIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createAWSSNSIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateAWSSNSIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateAzureServiceBusIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm"; 9 | 10 | 11 | class CreateAzureServiceBusIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createAzureServiceBusIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateAzureServiceBusIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateGCPPubSubIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import GCPPubSubIntegrationForm from "./GCPPubSubIntegrationForm"; 9 | 10 | 11 | class CreateGCPPubSubIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createGCPPubSubIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateGCPPubSubIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateHTTPIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import HTTPIntegrationForm from "./HTTPIntegrationForm"; 9 | 10 | 11 | class CreateHTTPIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createHTTPIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateHTTPIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateInfluxDBIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import InfluxDBIntegrationForm from "./InfluxDBIntegrationForm"; 9 | 10 | 11 | class CreateInfluxDBIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createInfluxDBIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateInfluxDBIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateLoRaCloudIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm"; 9 | 10 | 11 | class CreateLoRaCloudIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createLoRaCloudIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | let obj = { 23 | das: true, 24 | dasGNSSPort: 198, 25 | dasModemPort: 199, 26 | }; 27 | 28 | return( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | export default CreateLoRaCloudIntegration; 44 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateMyDevicesIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm"; 9 | 10 | 11 | class CreateMyDevicesIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createMyDevicesIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateMyDevicesIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreatePilotThingsIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm"; 9 | 10 | 11 | class CreatePilotThingsIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createPilotThingsIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreatePilotThingsIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/CreateThingsBoardIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm"; 9 | 10 | 11 | class CreateThingsBoardIntegration extends Component { 12 | onSubmit = (integration) => { 13 | let integr = integration; 14 | integr.applicationID = this.props.match.params.applicationID; 15 | 16 | ApplicationStore.createThingsBoardIntegration(integr, resp => { 17 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 18 | }); 19 | } 20 | 21 | render() { 22 | return( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default CreateThingsBoardIntegration; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/MQTTCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardActions from '@material-ui/core/CardActions'; 7 | import CardContent from '@material-ui/core/CardContent'; 8 | import CardMedia from '@material-ui/core/CardMedia'; 9 | import Button from '@material-ui/core/Button'; 10 | import Typography from '@material-ui/core/Typography'; 11 | 12 | 13 | const styles = { 14 | media: { 15 | paddingTop: '35%', 16 | backgroundSize: 'contain', 17 | }, 18 | }; 19 | 20 | 21 | class MQTTCard extends Component { 22 | render() { 23 | return ( 24 | 25 | 30 | 31 | 32 | MQTT 33 | 34 | 35 | The MQTT integration forwards events to a MQTT broker. 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | export default withStyles(styles)(MQTTCard); 51 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/PilotThingsIntegrationForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TextField from '@material-ui/core/TextField'; 4 | 5 | import FormComponent from "../../../classes/FormComponent"; 6 | import Form from "../../../components/Form"; 7 | 8 | 9 | class PilotThingsIntegrationForm extends FormComponent { 10 | render() { 11 | if (this.state.object === undefined) { 12 | return null; 13 | } 14 | 15 | return( 16 |
17 | 27 | 28 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | 43 | export default PilotThingsIntegrationForm; 44 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/ThingsBoardIntegrationForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TextField from '@material-ui/core/TextField'; 4 | import FormHelperText from "@material-ui/core/FormHelperText"; 5 | 6 | import FormComponent from "../../../classes/FormComponent"; 7 | import Form from "../../../components/Form"; 8 | 9 | 10 | class ThingsBoardIntegrationForm extends FormComponent { 11 | render() { 12 | if (this.state.object === undefined) { 13 | return null; 14 | } 15 | 16 | return( 17 |
18 | 28 | 29 | Each device must have a 'ThingsBoardAccessToken' variable assigned. This access-token is generated by ThingsBoard. 30 | 31 | 32 | ); 33 | } 34 | } 35 | 36 | 37 | export default ThingsBoardIntegrationForm; 38 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateAWSSNSIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import AWSSNSIntegrationForm from "./AWSSNSIntegrationForm"; 9 | 10 | 11 | class UpdateAWSSNSIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateAWSSNSIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getAWSSNSIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateAWSSNSIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateAzureServiceBusIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import AzureServiceBusIntegrationForm from "./AzureServiceBusIntegrationForm"; 9 | 10 | 11 | class UpdateAzureServiceBusIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateAzureServiceBusIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getAzureServiceBusIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateAzureServiceBusIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateGCPPubSubIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import GCPPubSubIntegrationForm from "./GCPPubSubIntegrationForm"; 9 | 10 | 11 | class UpdateGCPPubSubIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateGCPPubSubIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getGCPPubSubIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateGCPPubSubIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateHTTPIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import HTTPIntegrationForm from "./HTTPIntegrationForm"; 9 | 10 | 11 | class UpdateHTTPIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateHTTPIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getHTTPIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateHTTPIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateInfluxDBIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import InfluxDBIntegrationForm from "./InfluxDBIntegrationForm"; 9 | 10 | 11 | class UpdateInfluxDBIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateInfluxDBIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getInfluxDBIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateInfluxDBIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateLoRaCloudIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import LoRaCloudIntegrationForm from "./LoRaCloudIntegrationForm"; 9 | 10 | 11 | class UpdateLoRaCloudIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateLoRaCloudIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getLoRaCloudIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateLoRaCloudIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateMyDevicesIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import MyDevicesIntegrationForm from "./MyDevicesIntegrationForm"; 9 | 10 | 11 | class UpdateMyDevicesIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateMyDevicesIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getMyDevicesIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateMyDevicesIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdatePilotThingsIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import PilotThingsIntegrationForm from "./PilotThingsIntegrationForm"; 9 | 10 | 11 | class UpdatePilotThingsIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updatePilotThingsIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getPilotThingsIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdatePilotThingsIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/applications/integrations/UpdateThingsBoardIntegration.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Grid from '@material-ui/core/Grid'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardHeader from "@material-ui/core/CardHeader"; 5 | import CardContent from "@material-ui/core/CardContent"; 6 | 7 | import ApplicationStore from "../../../stores/ApplicationStore"; 8 | import ThingsBoardIntegrationForm from "./ThingsBoardIntegrationForm"; 9 | 10 | 11 | class UpdateThingsBoardIntegration extends Component { 12 | constructor() { 13 | super(); 14 | 15 | this.state = {}; 16 | } 17 | 18 | onSubmit = (integration) => { 19 | let integr = integration; 20 | integr.applicationID = this.props.match.params.applicationID; 21 | 22 | ApplicationStore.updateThingsBoardIntegration(integr, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/integrations`); 24 | }); 25 | } 26 | 27 | componentDidMount() { 28 | ApplicationStore.getThingsBoardIntegration(this.props.match.params.applicationID, (resp) => { 29 | this.setState({ 30 | object: resp.integration, 31 | }); 32 | }); 33 | } 34 | 35 | render() { 36 | if (this.state.object === undefined) { 37 | return null; 38 | } 39 | 40 | return( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default UpdateThingsBoardIntegration; 56 | -------------------------------------------------------------------------------- /ui/src/views/device-profiles/UpdateDeviceProfile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Card from '@material-ui/core/Card'; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | 9 | import DeviceProfileStore from "../../stores/DeviceProfileStore"; 10 | import DeviceProfileForm from "./DeviceProfileForm"; 11 | 12 | 13 | const styles = { 14 | card: { 15 | overflow: "visible", 16 | }, 17 | }; 18 | 19 | 20 | class UpdateDeviceProfile extends Component { 21 | constructor() { 22 | super(); 23 | this.onSubmit = this.onSubmit.bind(this); 24 | } 25 | 26 | onSubmit(deviceProfile) { 27 | DeviceProfileStore.update(deviceProfile, resp => { 28 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/device-profiles`); 29 | }); 30 | } 31 | 32 | render() { 33 | return( 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | 54 | export default withStyles(styles)(withRouter(UpdateDeviceProfile)); 55 | -------------------------------------------------------------------------------- /ui/src/views/devices/UpdateDevice.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Card from '@material-ui/core/Card'; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | 9 | import DeviceStore from "../../stores/DeviceStore"; 10 | import DeviceForm from "./DeviceForm"; 11 | 12 | 13 | const styles = { 14 | card: { 15 | overflow: "visible", 16 | }, 17 | }; 18 | 19 | 20 | class UpdateDevice extends Component { 21 | constructor() { 22 | super(); 23 | this.onSubmit = this.onSubmit.bind(this); 24 | } 25 | 26 | onSubmit(device) { 27 | DeviceStore.update(device, resp => { 28 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/devices/${this.props.match.params.devEUI}`); 29 | }); 30 | } 31 | 32 | render() { 33 | return( 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 | 49 | 50 | ); 51 | } 52 | } 53 | 54 | export default withStyles(styles)(withRouter(UpdateDevice)); 55 | -------------------------------------------------------------------------------- /ui/src/views/gateway-profiles/GatewayProfileLayout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from "react-router-dom"; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | 6 | import Delete from "mdi-material-ui/Delete"; 7 | 8 | import TitleBar from "../../components/TitleBar"; 9 | import TitleBarTitle from "../../components/TitleBarTitle"; 10 | import TitleBarButton from "../../components/TitleBarButton"; 11 | import GatewayProfileStore from "../../stores/GatewayProfileStore"; 12 | import UpdateGatewayProfile from "./UpdateGatewayProfile"; 13 | 14 | 15 | class GatewayProfileLayout extends Component { 16 | constructor() { 17 | super(); 18 | 19 | this.state = {}; 20 | 21 | this.deleteGatewayProfile = this.deleteGatewayProfile.bind(this); 22 | } 23 | 24 | componentDidMount() { 25 | GatewayProfileStore.get(this.props.match.params.gatewayProfileID, resp => { 26 | this.setState({ 27 | gatewayProfile: resp, 28 | }); 29 | }); 30 | } 31 | 32 | deleteGatewayProfile() { 33 | if (window.confirm("Are you sure you want to delete this gateway-profile?")) { 34 | GatewayProfileStore.delete(this.props.match.params.gatewayProfileID, () => { 35 | this.props.history.push("/gateway-profiles"); 36 | }); 37 | } 38 | } 39 | 40 | render() { 41 | if (this.state.gatewayProfile === undefined) { 42 | return(
); 43 | } 44 | 45 | return( 46 | 47 | } 53 | color="secondary" 54 | onClick={this.deleteGatewayProfile} 55 | />, 56 | ]} 57 | > 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | } 69 | } 70 | 71 | export default withRouter(GatewayProfileLayout); 72 | -------------------------------------------------------------------------------- /ui/src/views/gateway-profiles/UpdateGatewayProfile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import { CardContent } from "@material-ui/core"; 7 | 8 | import GatewayProfileStore from "../../stores/GatewayProfileStore"; 9 | import GatewayProfileForm from "./GatewayProfileForm"; 10 | 11 | 12 | class UpdateGatewayProfile extends Component { 13 | constructor() { 14 | super(); 15 | 16 | this.onSubmit = this.onSubmit.bind(this); 17 | } 18 | 19 | onSubmit(gatewayProfile) { 20 | GatewayProfileStore.update(gatewayProfile, resp => { 21 | this.props.history.push("/gateway-profiles"); 22 | }); 23 | } 24 | 25 | render() { 26 | return( 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | 45 | export default withRouter(UpdateGatewayProfile); 46 | -------------------------------------------------------------------------------- /ui/src/views/gateways/UpdateGateway.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardContent from "@material-ui/core/CardContent"; 7 | 8 | import GatewayStore from "../../stores/GatewayStore"; 9 | import GatewayForm from "./GatewayForm"; 10 | 11 | 12 | class UpdateGateway extends Component { 13 | constructor() { 14 | super(); 15 | this.onSubmit = this.onSubmit = this.onSubmit.bind(this); 16 | } 17 | 18 | onSubmit(gateway) { 19 | GatewayStore.update(gateway, resp => { 20 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/gateways`); 21 | }); 22 | } 23 | 24 | render() { 25 | return( 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | } 44 | 45 | export default withRouter(UpdateGateway); 46 | -------------------------------------------------------------------------------- /ui/src/views/multicast-groups/UpdateMulticastGroup.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Card from '@material-ui/core/Card'; 7 | import CardContent from "@material-ui/core/CardContent"; 8 | 9 | import MulticastGroupStore from "../../stores/MulticastGroupStore"; 10 | import MulticastGroupForm from "./MulticastGroupForm"; 11 | 12 | 13 | const styles = { 14 | card: { 15 | overflow: "visible", 16 | }, 17 | }; 18 | 19 | 20 | class UpdateMulticastGroup extends Component { 21 | constructor() { 22 | super(); 23 | this.onSubmit = this.onSubmit.bind(this); 24 | } 25 | 26 | onSubmit(multicastGroup) { 27 | MulticastGroupStore.update(multicastGroup, resp => { 28 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/applications/${this.props.match.params.applicationID}/multicast-groups/${this.props.match.params.multicastGroupID}`); 29 | }); 30 | } 31 | 32 | render() { 33 | return( 34 | 35 | 36 | 37 | 38 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default withStyles(styles)(withRouter(UpdateMulticastGroup)); 53 | -------------------------------------------------------------------------------- /ui/src/views/network-servers/CreateNetworkServer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | 7 | import { CardContent } from "@material-ui/core"; 8 | 9 | import TitleBar from "../../components/TitleBar"; 10 | import TitleBarTitle from "../../components/TitleBarTitle"; 11 | import NetworkServerForm from "./NetworkServerForm"; 12 | import NetworkServerStore from "../../stores/NetworkServerStore"; 13 | 14 | 15 | class CreateNetworkServer extends Component { 16 | constructor() { 17 | super(); 18 | this.onSubmit = this.onSubmit.bind(this); 19 | } 20 | 21 | onSubmit(networkServer) { 22 | NetworkServerStore.create(networkServer, resp => { 23 | this.props.history.push("/network-servers"); 24 | }); 25 | } 26 | 27 | render() { 28 | return( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | export default withRouter(CreateNetworkServer); 51 | -------------------------------------------------------------------------------- /ui/src/views/network-servers/ListNetworkServers.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Grid from '@material-ui/core/Grid'; 4 | import TableCell from '@material-ui/core/TableCell'; 5 | import TableRow from '@material-ui/core/TableRow'; 6 | 7 | import Plus from "mdi-material-ui/Plus"; 8 | 9 | import TitleBar from "../../components/TitleBar"; 10 | import TitleBarTitle from "../../components/TitleBarTitle"; 11 | import TableCellLink from "../../components/TableCellLink"; 12 | import TitleBarButton from "../../components/TitleBarButton"; 13 | import DataTable from "../../components/DataTable"; 14 | 15 | import NetworkServerStore from "../../stores/NetworkServerStore"; 16 | 17 | 18 | class ListNetworkServers extends Component { 19 | getPage(limit, offset, callbackFunc) { 20 | NetworkServerStore.list(0, limit, offset, callbackFunc); 21 | } 22 | 23 | getRow(obj) { 24 | return( 25 | 29 | {obj.name} 30 | {obj.server} 31 | 32 | ); 33 | } 34 | 35 | render() { 36 | return( 37 | 38 | } 43 | label="Add" 44 | to={`/network-servers/create`} 45 | />, 46 | ]} 47 | > 48 | 49 | 50 | 51 | 54 | Name 55 | Server 56 | 57 | } 58 | getPage={this.getPage} 59 | getRow={this.getRow} 60 | /> 61 | 62 | 63 | ); 64 | } 65 | } 66 | 67 | export default ListNetworkServers; 68 | -------------------------------------------------------------------------------- /ui/src/views/network-servers/UpdateNetworkServer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import { CardContent } from "@material-ui/core"; 7 | 8 | import NetworkServerStore from "../../stores/NetworkServerStore"; 9 | import NetworkServerForm from "./NetworkServerForm"; 10 | 11 | 12 | class UpdateNetworkServer extends Component { 13 | constructor() { 14 | super(); 15 | 16 | this.onSubmit = this.onSubmit.bind(this); 17 | } 18 | 19 | onSubmit(networkServer) { 20 | NetworkServerStore.update(networkServer, resp => { 21 | this.props.history.push("/network-servers"); 22 | }); 23 | } 24 | 25 | render() { 26 | return( 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default withRouter(UpdateNetworkServer); 45 | -------------------------------------------------------------------------------- /ui/src/views/organizations/CreateOrganization.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | 7 | import { CardContent } from "@material-ui/core"; 8 | 9 | import TitleBar from "../../components/TitleBar"; 10 | import TitleBarTitle from "../../components/TitleBarTitle"; 11 | import OrganizationForm from "./OrganizationForm"; 12 | import OrganizationStore from "../../stores/OrganizationStore"; 13 | 14 | 15 | class CreateOrganization extends Component { 16 | constructor() { 17 | super(); 18 | this.onSubmit = this.onSubmit.bind(this); 19 | } 20 | 21 | onSubmit(organization) { 22 | OrganizationStore.create(organization, resp => { 23 | this.props.history.push("/organizations"); 24 | }); 25 | } 26 | 27 | render() { 28 | return( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | export default withRouter(CreateOrganization); -------------------------------------------------------------------------------- /ui/src/views/organizations/CreateOrganizationUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardContent from "@material-ui/core/CardContent"; 7 | 8 | import TitleBar from "../../components/TitleBar"; 9 | import TitleBarTitle from "../../components/TitleBarTitle"; 10 | import OrganizationStore from "../../stores/OrganizationStore"; 11 | import OrganizationUserForm from "./OrganizationUserForm"; 12 | 13 | 14 | class CreateOrganizationUser extends Component { 15 | constructor() { 16 | super(); 17 | 18 | this.onAssignUser = this.onAssignUser.bind(this); 19 | } 20 | 21 | onAssignUser(user) { 22 | OrganizationStore.addUser(this.props.match.params.organizationID, user, resp => { 23 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/users`); 24 | }); 25 | }; 26 | 27 | render() { 28 | return( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | } 52 | 53 | export default withRouter(CreateOrganizationUser); 54 | -------------------------------------------------------------------------------- /ui/src/views/organizations/OrganizationRedirect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import OrganizationStore from "../../stores/OrganizationStore"; 5 | import SessionStore from "../../stores/SessionStore"; 6 | 7 | 8 | class OrganizationRedirect extends Component { 9 | componentDidMount() { 10 | const organizationID = SessionStore.getOrganizationID(); 11 | if (organizationID !== undefined && organizationID !== null && organizationID !== "") { 12 | this.props.history.push(`/organizations/${organizationID}`); 13 | } else { 14 | OrganizationStore.list("", 1, 0, resp => { 15 | if (resp.result.length > 0) { 16 | this.props.history.push(`/organizations/${resp.result[0].id}`); 17 | } 18 | }); 19 | } 20 | } 21 | 22 | render() { 23 | return(
); 24 | } 25 | } 26 | 27 | export default withRouter(OrganizationRedirect); 28 | -------------------------------------------------------------------------------- /ui/src/views/organizations/UpdateOrganization.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardContent from "@material-ui/core/CardContent"; 7 | 8 | import OrganzationStore from "../../stores/OrganizationStore"; 9 | import OrganizationForm from "./OrganizationForm"; 10 | 11 | 12 | class UpdateOrganization extends Component { 13 | constructor() { 14 | super(); 15 | this.onSubmit = this.onSubmit.bind(this); 16 | } 17 | 18 | onSubmit(organization) { 19 | OrganzationStore.update(organization, resp => { 20 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}`); 21 | }); 22 | } 23 | 24 | render() { 25 | return( 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default withRouter(UpdateOrganization); 45 | -------------------------------------------------------------------------------- /ui/src/views/organizations/UpdateOrganizationUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardContent from "@material-ui/core/CardContent"; 7 | 8 | import OrganizationStore from "../../stores/OrganizationStore"; 9 | import OrganizationUserForm from "./OrganizationUserForm"; 10 | 11 | 12 | class UpdateOrganizationUser extends Component { 13 | constructor() { 14 | super(); 15 | this.onSubmit = this.onSubmit.bind(this); 16 | } 17 | 18 | onSubmit(organizationUser) { 19 | OrganizationStore.updateUser(organizationUser, resp => { 20 | this.props.history.push(`/organizations/${organizationUser.organizationID}/users`); 21 | }); 22 | } 23 | 24 | render() { 25 | return( 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | export default withRouter(UpdateOrganizationUser); 45 | -------------------------------------------------------------------------------- /ui/src/views/service-profiles/UpdateServiceProfile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import CardContent from "@material-ui/core/CardContent"; 7 | 8 | import ServiceProfileStore from "../../stores/ServiceProfileStore"; 9 | import ServiceProfileForm from "./ServiceProfileForm"; 10 | 11 | 12 | class UpdateServiceProfile extends Component { 13 | constructor() { 14 | super(); 15 | this.onSubmit = this.onSubmit.bind(this); 16 | } 17 | 18 | onSubmit(serviceProfile) { 19 | ServiceProfileStore.update(serviceProfile, resp => { 20 | this.props.history.push(`/organizations/${this.props.match.params.organizationID}/service-profiles`); 21 | }); 22 | } 23 | 24 | render() { 25 | return( 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | export default withRouter(UpdateServiceProfile); 47 | -------------------------------------------------------------------------------- /ui/src/views/users/CreateUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | 7 | import { CardContent } from "@material-ui/core"; 8 | 9 | import TitleBar from "../../components/TitleBar"; 10 | import TitleBarTitle from "../../components/TitleBarTitle"; 11 | import UserForm from "./UserForm"; 12 | import UserStore from "../../stores/UserStore"; 13 | 14 | 15 | class CreateUser extends Component { 16 | constructor() { 17 | super(); 18 | this.onSubmit = this.onSubmit.bind(this); 19 | } 20 | 21 | onSubmit(user) { 22 | UserStore.create(user, user.password, [], resp => { 23 | this.props.history.push("/users"); 24 | }); 25 | } 26 | 27 | render() { 28 | return( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | export default withRouter(CreateUser); 51 | -------------------------------------------------------------------------------- /ui/src/views/users/UpdateUser.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { withRouter } from 'react-router-dom'; 3 | 4 | import Grid from '@material-ui/core/Grid'; 5 | import Card from '@material-ui/core/Card'; 6 | import { CardContent } from "@material-ui/core"; 7 | 8 | import UserStore from "../../stores/UserStore"; 9 | import UserForm from "./UserForm"; 10 | 11 | class UpdateUser extends Component { 12 | constructor() { 13 | super(); 14 | this.onSubmit = this.onSubmit.bind(this); 15 | } 16 | 17 | onSubmit(user) { 18 | UserStore.update(user, resp => { 19 | this.props.history.push("/users"); 20 | }); 21 | } 22 | 23 | render() { 24 | return( 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | export default withRouter(UpdateUser); 43 | --------------------------------------------------------------------------------