diff --git a/assets/img/channels/community/flows/qubitro-dark.png b/assets/img/channels/community/flows/qubitro-dark.png
new file mode 100644
index 000000000..ab4df2259
Binary files /dev/null and b/assets/img/channels/community/flows/qubitro-dark.png differ
diff --git a/assets/img/channels/community/intro_screens/qubitro.png b/assets/img/channels/community/intro_screens/qubitro.png
new file mode 100644
index 000000000..7eebc3ae1
Binary files /dev/null and b/assets/img/channels/community/intro_screens/qubitro.png differ
diff --git a/assets/img/channels/community/qubitro.png b/assets/img/channels/community/qubitro.png
new file mode 100644
index 000000000..b551efc79
Binary files /dev/null and b/assets/img/channels/community/qubitro.png differ
diff --git a/assets/js/components/channels/ChannelConnectionDetails.jsx b/assets/js/components/channels/ChannelConnectionDetails.jsx
index c7a2fd5ed..79d13bafa 100644
--- a/assets/js/components/channels/ChannelConnectionDetails.jsx
+++ b/assets/js/components/channels/ChannelConnectionDetails.jsx
@@ -15,6 +15,7 @@ import GoogleSheetForm from "./community/google_sheets/GoogleSheetUpdateForm.jsx
import MicroshareForm from "./community/microshare/MicroshareUpdateForm.jsx";
import TagoForm from "./community/tago/TagoUpdateForm.jsx";
import UbidotsForm from "./community/ubidots/UbidotsUpdateForm.jsx";
+import QubitroForm from "./community/qubitro/QubitroForm";
const { Panel } = Collapse
function DetailsUpdateWrapper({ handleUpdateDetailsChange, validInput, children, mobile }) {
@@ -60,7 +61,7 @@ function DetailsUpdateWrapper({ handleUpdateDetailsChange, validInput, children,
)
}
-export default ({ channel, handleUpdateDetailsInput, handleUpdateDetailsChange, validInput, mobile}) => {
+export default ({ channel, handleUpdateDetailsInput, handleUpdateDetailsChange, validInput, mobile }) => {
switch (channel.type) {
case "aws":
return (
@@ -117,13 +118,24 @@ export default ({ channel, handleUpdateDetailsInput, handleUpdateDetailsChange,
/>
);
+ case "qubitro":
+ return (
+
+
+
+ );
case "cargo":
return (
-
+
);
case "my_devices":
return (
-
+
);
case "adafruit":
return (
diff --git a/assets/js/components/channels/ChannelNew.jsx b/assets/js/components/channels/ChannelNew.jsx
index 709e39c30..75dd6b8c8 100644
--- a/assets/js/components/channels/ChannelNew.jsx
+++ b/assets/js/components/channels/ChannelNew.jsx
@@ -27,6 +27,7 @@ import MobileLayout from "../mobile/MobileLayout";
import ArrowLeftOutlined from "@ant-design/icons/ArrowLeftOutlined";
import { CORE_INTEGRATION_TYPES, COMMUNITY_INTEGRATION_TYPES, getAllowedIntegrations } from "../../util/integrationInfo";
import { isMobile } from "../../util/constants";
+import QubitroForm from "./community/qubitro/QubitroForm";
@connect(null, mapDispatchToProps)
class ChannelNew extends Component {
@@ -42,7 +43,7 @@ class ChannelNew extends Component {
const allowedIntegrations = getAllowedIntegrations()
const { search } = this.props.history.location
const searchParams = search.split("?type=")
- if ( searchParams[1] && find(COMMUNITY_INTEGRATION_TYPES.filter(i => allowedIntegrations[i.type]), {type: searchParams[1]}) ) {
+ if (searchParams[1] && find(COMMUNITY_INTEGRATION_TYPES.filter(i => allowedIntegrations[i.type]), { type: searchParams[1] })) {
this.setState({ type: searchParams[1] })
}
}
@@ -82,6 +83,8 @@ class ChannelNew extends Component {
return
case "microshare":
return
+ case "qubitro":
+ return
default:
return
}
diff --git a/assets/js/components/channels/community/qubitro/QubitroForm.jsx b/assets/js/components/channels/community/qubitro/QubitroForm.jsx
new file mode 100644
index 000000000..a4e96e0ba
--- /dev/null
+++ b/assets/js/components/channels/community/qubitro/QubitroForm.jsx
@@ -0,0 +1,82 @@
+import React, { Component } from "react";
+import { Typography, Input } from "antd";
+const { Text } = Typography;
+
+class QubitroForm extends Component {
+ state = {
+ webhookKey: "",
+ projectId: "",
+ appName: "",
+ };
+
+ componentDidMount() {
+ const { channel } = this.props;
+
+ if (channel) {
+ this.setState({
+ appName: channel.qubitro_app_name,
+ webhookKey: channel.qubitro_webhook_key,
+ projectId: channel.qubitro_projectId,
+ });
+ }
+ }
+
+ handleInputUpdate = (e) => {
+ this.setState({ [e.target.name]: e.target.value }, () => {
+ const { webhookKey, projectId, appName } = this.state;
+
+ this.props.onValidInput({
+ qubitro_webhook_key: webhookKey,
+ qubitro_projectId: projectId,
+ qubitro_app_name: appName,
+ }, webhookKey.length > 0 && projectId.length > 0 && appName.length > 0);
+ });
+ };
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default QubitroForm;
diff --git a/assets/js/util/integrationInfo.js b/assets/js/util/integrationInfo.js
index 0e7009237..5cfd40e73 100644
--- a/assets/js/util/integrationInfo.js
+++ b/assets/js/util/integrationInfo.js
@@ -31,12 +31,16 @@ import TagoDark from "../../img/channels/community/flows/tago-dark.png";
import Ubidots from "../../img/channels/community/ubidots.png";
import UbidotsDark from "../../img/channels/community/flows/ubidots-dark.png";
import UbidotsIntro from "../../img/channels/community/intro_screens/ubidots.png";
+import Qubitro from "../../img/channels/community/qubitro.png";
+import QubitroDark from "../../img/channels/community/flows/qubitro-dark.png"
+import QubitroIntro from "../../img/channels/community/intro_screens/qubitro.png";
export const integrationImgMap = {
adafruit: AdafruitDark,
aws: AwsDark,
azure: AzureDark,
iot_central: IotCentralDark,
+ qubitro: QubitroDark,
cargo: CargoDark,
my_devices: MyDevicesDark,
datacake: DatacakeDark,
@@ -49,7 +53,7 @@ export const integrationImgMap = {
akenza: AkenzaDark,
};
-export const http_integrations = ["http", "cargo", "my_devices", "akenza", "datacake", "microshare", "tago", "ubidots", "google_sheets"]
+export const http_integrations = ["http", "cargo", "my_devices", "akenza", "datacake", "microshare", "tago", "ubidots", "google_sheets", "qubitro"]
export const mqtt_integrations = ["mqtt", "adafruit"]
export const getAllowedIntegrations = () => {
@@ -231,4 +235,16 @@ export const COMMUNITY_INTEGRATION_TYPES = [
},
introImg: `${UbidotsIntro}`
},
+ {
+ name: "Qubitro",
+ type: "qubitro",
+ img: `${Qubitro}`,
+ info: {
+ title: "The fastest way to ingest data from devices and transform them into actionable information.",
+ desc: "Qubitro is a device data platform that gives superpowers to innovators to build solutions powered by device data.",
+ docLink: "https://docs.qubitro.com/data-sources/lorawan/helium-console",
+ externalLink: "https://www.qubitro.com"
+ },
+ introImg: `${QubitroIntro}`
+ },
];
diff --git a/lib/console/channels/channel.ex b/lib/console/channels/channel.ex
index 827c0aa07..736ba1c74 100644
--- a/lib/console/channels/channel.ex
+++ b/lib/console/channels/channel.ex
@@ -6,7 +6,7 @@ defmodule Console.Channels.Channel do
alias Console.Organizations.Organization
alias Console.Channels.Channel
- @http_types ~w(http cargo my_devices akenza datacake microshare tago ubidots google_sheets)
+ @http_types ~w(http cargo my_devices akenza datacake microshare tago ubidots google_sheets qubitro)
@long_type_names %{
"aws" => "AWS IoT",
"azure" => "Azure IoT Hub",
@@ -23,6 +23,7 @@ defmodule Console.Channels.Channel do
"ubidots" => "Ubidots",
"google_sheets" => "Google Sheets",
"adafruit" => "Adafruit IO",
+ "qubitro" => "Qubitro",
}
@primary_key {:id, :binary_id, autogenerate: true}
diff --git a/lib/console/community/community_channels.ex b/lib/console/community/community_channels.ex
index 90bc6ded8..ac1c047d4 100644
--- a/lib/console/community/community_channels.ex
+++ b/lib/console/community/community_channels.ex
@@ -53,6 +53,11 @@ defmodule Console.CommunityChannels do
|> Map.put(:endpoint, "https://dataplugin.ubidots.com/api/web-hook/#{channel.credentials["webhook_token"]}")
|> Map.put(:method, "post")
|> Map.put(:headers, Jason.encode!(%{}))
+ "qubitro" ->
+ channel
+ |> Map.put(:endpoint, "https://webhook.qubitro.com/integrations/helium")
+ |> Map.put(:method, "post")
+ |> Map.put(:headers, Jason.encode!(%{ "webhookSigningKey" => channel.credentials["qubitro_webhook_key"], "projectId" => channel.credentails["qubitro_projectId"] }))
_ ->
channel
end
@@ -141,6 +146,15 @@ defmodule Console.CommunityChannels do
"url_params" => %{}
})
|> Map.put(:type, (if show_underlying_type, do: "http", else: channel.type))
+ "qubitro" ->
+ channel
+ |> Map.put(:credentials, %{
+ "endpoint" => "https://webhook.qubitro.com/integrations/helium",
+ "headers" => %{ "webhookSigningKey" => channel.credentials["qubitro_webhook_key"], "projectId" => channel.credentails["qubitro_projectId"] },
+ "method" => "post",
+ "url_params" => %{}
+ })
+ |> Map.put(:type, (if show_underlying_type, do: "http", else: channel.type))
_ ->
channel
end
diff --git a/lib/console_web/controllers/v1/channel_controller.ex b/lib/console_web/controllers/v1/channel_controller.ex
index cf8a3fed1..9696f0a36 100644
--- a/lib/console_web/controllers/v1/channel_controller.ex
+++ b/lib/console_web/controllers/v1/channel_controller.ex
@@ -285,6 +285,46 @@ defmodule ConsoleWeb.V1.ChannelController do
end
end
+ def create(conn, %{ "name" => name, "type" => "qubitro", "qubitro_webhook_key" => qubitro_webhook_key, "qubitro_projectId" => qubitro_projectId, "qubitro_app_name" => qubitro_app_name } = attrs) do
+ current_organization = conn.assigns.current_organization
+ allowed_types = Channel.get_allowed_integration_types()
+
+ if "qubitro" in allowed_types do
+ channel_params =
+ %{
+ "credentials" => %{
+ "qubitro_webhook_key" => qubitro_webhook_key,
+ "qubitro_projectId" => qubitro_projectId,
+ "qubitro_app_name" => qubitro_app_name
+ },
+ "name" => name,
+ "type" => "qubitro",
+ "organization_id" => current_organization.id
+ }
+
+ with {:ok, %Channel{} = channel} <- Channels.create_channel(current_organization, channel_params) do
+ channel =
+ channel
+ |> Map.put(:devices, [])
+ |> Map.put(:labels, [])
+
+ AuditActions.create_audit_action(
+ current_organization.id,
+ "v1_api",
+ "channel_controller_create",
+ channel.id,
+ attrs
+ )
+
+ conn
+ |> put_status(:created)
+ |> render("show.json", channel: channel)
+ end
+ else
+ {:error, :bad_request, "This integration type is not allowed on this Console" }
+ end
+ end
+
def create(conn, %{ "name" => name, "type" => "mqtt", "endpoint" => endpoint, "uplink_topic" => uplink_topic, "downlink_topic" => downlink_topic } = attrs) do
current_organization = conn.assigns.current_organization
allowed_types = Channel.get_allowed_integration_types()