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 ( +
+
+ Webhook Signing Key + +
+ Project ID + +
+ App Name + +
+
+
+ ); + } +} + +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()