1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3197],{43197:(e,t,s)=>{s.d(t,{j:()=>I});var a=s(95155),o=s(12115),i=s(51834),r=s(13406),n=s(87160),l=s(65142),d=s(76444),c=s(5917),u=s(42529),m=s(75426),h=s(57828),x=s(70532),p=s(23327),g=s(35299),b=s(89559),y=s(34172),k=s(45038),j=s(65820),v=s(36969),f=s(16794),w=s(86948),N=s(18332);let T="https://bldojxpockljyivldxwf.supabase.co",$=T.includes("bldojxpockljyivldxwf")?"https://link.netneural.ai/v1/mqtt-external-ingest":`${T}/functions/v1/mqtt-external-ingest`;function _(){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e).map(e=>e.toString(16).padStart(2,"0")).join("")}function I(e){let{open:t,onOpenChange:s,integrationId:T,organizationId:I,onSaved:E,mode:C="dialog",initialName:A}=e,[R,O]=(0,o.useState)(!1),[S,P]=(0,o.useState)(!1),[F,M]=(0,o.useState)(null),[q,X]=(0,o.useState)(T),[H,D]=(0,o.useState)(!1),[Q,K]=(0,o.useState)("emqx"),[W,L]=(0,o.useState)({name:A||"External MQTT",topics:"devices/+/telemetry",webhook_key:""}),z=(0,o.useCallback)(async()=>{if(T){O(!0);try{let e=await k.d.integrations.list(I);if(!e.success)throw Error("Failed to load integrations");let t=e.data,s=t.integrations?.find(e=>e.id===T);s?.settings&&L({name:s.name,topics:s.settings.topics||"devices/+/telemetry",webhook_key:s.settings.webhook_key||""})}catch{j.oR.error("Failed to load configuration")}finally{O(!1)}}},[T,I]);(0,o.useEffect)(()=>{t&&T?z():t&&!T&&(L({name:A||"External MQTT",topics:"devices/+/telemetry",webhook_key:_()}),P(!1))},[t,T,A,z]);let B=(e,t)=>{navigator.clipboard.writeText(e),M(t),setTimeout(()=>M(null),2e3)},U=e=>{let{value:t,field:s}=e;return(0,a.jsx)(n.$,{type:"button",variant:"ghost",size:"sm",className:"h-8 w-8 p-0",onClick:()=>B(t,s),children:F===s?(0,a.jsx)(c.A,{className:"h-3.5 w-3.5 text-green-600"}):(0,a.jsx)(m.A,{className:"h-3.5 w-3.5"})})},J=async()=>{if(!W.name)return void j.oR.error("Please provide an integration name");O(!0);try{let e,t=T||q;if(t){if(!(e=await k.d.integrations.update(t,{name:W.name,settings:{topics:W.topics,webhook_key:W.webhook_key},status:"active"})).success)throw Error("string"==typeof e.error?e.error:"Failed to update")}else{let t=W.webhook_key||_();if(L(e=>({...e,webhook_key:t})),!(e=await k.d.integrations.create({organization_id:I,integration_type:"mqtt_external",name:W.name,settings:{topics:W.topics,webhook_key:t}})).success){let t="string"==typeof e.message?e.message:"",s="string"==typeof e.error?e.error:"",a=`${s} ${t}`.trim();throw Error(a.includes("duplicate")?`An integration named "${W.name}" already exists.`:a||"Failed to create")}}j.oR.success("Configuration saved");let a=e.data,o=t||a?.id||a?.integration?.id;if(o&&X(o),E&&E(o),!t)return;s(!1)}catch(t){let e=t instanceof Error?t.message:"Unknown error";j.oR.error(`Failed to save: ${e}`)}finally{O(!1)}},Y=async()=>{let e=T||q;if(e){D(!0);try{let t=_();if(!(await k.d.integrations.update(e,{settings:{topics:W.topics,webhook_key:t}})).success)throw Error("Failed to rotate key");L(e=>({...e,webhook_key:t})),P(!0),j.oR.success("Webhook key rotated. Update your broker configuration.")}catch{j.oR.error("Failed to rotate webhook key")}finally{D(!1)}}},G=T||q,V={emqx:{label:"EMQX",notes:"In EMQX Dashboard: Rules -> Create Rule -> add HTTP Action pointing to the endpoint below.",code:`-- EMQX Rule SQL (Routes all device telemetry) 2SELECT 3 topic, 4 payload, 5 clientid 6FROM 7 "devices/#" 8 9-- HTTP Action configuration: 10-- Method: POST 11-- URL: ${$} 12-- Headers: 13-- X-Integration-ID: ${G||"<integration-id>"} 14-- X-Webhook-Key: <your-webhook-key> 15-- Content-Type: application/json 16-- Body template: 17{ 18 "topic": "\${topic}", 19 "payload": \${payload} 20}`},hivemq:{label:"HiveMQ",notes:"Install the HiveMQ HTTP Webhook Extension, then add this configuration to webhooks.xml.",code:`<!-- HiveMQ webhooks.xml --> 21<webhooks> 22 <webhook> 23 <id>sentinel-ingest</id> 24 <url>${$}</url> 25 <topic-filter>devices/#</topic-filter> 26 <headers> 27 <header> 28 <name>X-Integration-ID</name> 29 <value>${G||"<integration-id>"}</value> 30 </header> 31 <header> 32 <name>X-Webhook-Key</name> 33 <value><your-webhook-key></value> 34 </header> 35 </headers> 36 </webhook> 37</webhooks>`},mosquitto:{label:"Mosquitto",notes:"Mosquitto has no built-in webhook supp
37ort. Run this bridge script as a service alongside your broker.",code:`#!/bin/bash 38# mosquitto-sentinel-bridge.sh 39# Run as: ./mosquitto-sentinel-bridge.sh 40# Or with systemd/PM2 for production 41 42BROKER="localhost" 43TOPIC="devices/#" 44ENDPOINT="${$}" 45INTEGRATION_ID="${G||"<integration-id>"}" 46WEBHOOK_KEY="<your-webhook-key>" 47 48mosquitto_sub -h "$BROKER" -t "$TOPIC" -v | while read -r line; do 49 TOPIC_NAME=$(echo "$line" | awk '{print $1}') 50 PAYLOAD=$(echo "$line" | cut -d' ' -f2-) 51 curl -s -X POST "$ENDPOINT" \\ 52 -H "Content-Type: application/json" \\ 53 -H "X-Integration-ID: $INTEGRATION_ID" \\ 54 -H "X-Webhook-Key: $WEBHOOK_KEY" \\ 55 -d "{"topic": "$TOPIC_NAME", "payload": $PAYLOAD}" 56done`},generic:{label:"Generic",notes:"Any system that can make HTTP requests can forward messages to Sentinel.",code:`POST ${$} 57Content-Type: application/json 58X-Integration-ID: ${G||"<integration-id>"} 59X-Webhook-Key: <your-webhook-key> 60 61// Single message 62{ 63 "topic": "devices/${G||"<integration-id>"}/telemetry", 64 "payload": { "device_id": "<device-uuid>", "temperature": 22.5, "humidity": 65 } 65} 66 67// Batch (array of messages) 68[ 69 { "topic": "devices/${G||"<integration-id>"}/telemetry", "payload": { "device_id": "<device-uuid>", "temperature": 22.5 } }, 70 { "topic": "devices/${G||"<integration-id>"}/status", "payload": { "device_id": "<device-uuid>", "online": true } } 71] 72 73// Response 200: { "success": true, "stored": 1, "skipped": 0 } 74// Response 400: { "success": false, "errors": ["Device not found..."] }`}},Z=()=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(r.tU,{defaultValue:"general",className:"w-full",children:[(0,a.jsxs)(r.j7,{className:"w-full justify-start bg-gray-100 dark:bg-gray-800",children:[(0,a.jsx)(r.Xi,{value:"general",children:"General"}),G&&(0,a.jsx)(r.Xi,{value:"broker-guide",children:"Broker Setup"}),G&&(0,a.jsx)(r.Xi,{value:"data-format",children:"Data Format"}),G&&(0,a.jsx)(r.Xi,{value:"activity",children:"Activity Log"})]}),(0,a.jsx)(r.av,{value:"general",className:"space-y-6 pt-4",children:R?(0,a.jsx)("div",{className:"flex justify-center py-8",children:(0,a.jsx)(g.A,{className:"h-6 w-6 animate-spin text-muted-foreground"})}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)(d.J,{htmlFor:"name",children:"Integration Name"}),(0,a.jsx)(l.p,{id:"name",value:W.name,onChange:e=>L({...W,name:e.target.value}),placeholder:"e.g., Factory Floor MQTT"})]}),(0,a.jsxs)(N.Fc,{className:"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/20",children:[(0,a.jsx)(p.A,{className:"h-4 w-4 text-blue-600"}),(0,a.jsx)(N.XL,{className:"text-sm text-blue-900 dark:text-blue-100",children:"How it works"}),(0,a.jsxs)(N.TN,{className:"text-xs text-blue-800 dark:text-blue-200 space-y-1",children:[(0,a.jsx)("p",{children:"Your devices publish to your own MQTT broker. You configure your broker to forward those messages to Sentinel via HTTP POST."}),(0,a.jsx)("p",{children:"Sentinel never connects to your broker - data flows inbound only."})]})]}),G&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)("h4",{className:"text-sm font-semibold",children:"Webhook Endpoint"}),(0,a.jsxs)("div",{className:"space-y-1 rounded-lg border bg-muted/50 p-4",children:[(0,a.jsx)(d.J,{className:"text-xs text-muted-foreground",children:"POST URL"}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("code",{className:"flex-1 rounded border bg-white dark:bg-gray-800 px-3 py-1.5 text-xs font-mono break-all",children:$}),(0,a.jsx)(U,{value:$,field:"endpoint"})]})]})]}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)("h4",{className:"text-sm font-semibold",children:"Authentication Headers"}),(0,a.jsxs)("div",{className:"space-y-3 rounded-lg border bg-muted/50 p-4",children:[(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(d.J,{className:"text-xs text-muted-foreground",children:"X-Integration-ID"}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("code",{className:"flex-1 rounded border bg-white dark:bg-gray-800 px-3 py-1.5 text-xs font-mono break-all",children:G}),(0,a.jsx)(U,{value:G,field:"integration-id"})]})]}),(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(d.J,{className:"text-xs text-muted-foreground",children:"X-Webhook-Key"}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("code",{className:`flex-1 rounded border bg-white dark:bg-gray-800 px-3 py-1.5 text-xs font-mono break-all ${!S?"blur-sm select-none":""}`,children:W.webhook_key||"(not yet generated)"}),(0,a.jsx)(n.$,{type:"button",variant:"ghost",size:"sm",className:"h-8 w-8 p-0",onClick:()=>P(!S),children:S?(0,a.jsx)(x.A,{className:"h-3.5 w-3.5"}):(0,a.jsx)(h.A,{className:"h-3.5 w-3.5"})}),W.webhook_key&&(0,a.jsx)(U,{value:W.webhook_key,field:"webhook-key"})]}),(0,a.jsxs)(n.$,{type:"button",variant:"outline",size:"sm",onClick:Y,disabled:H,className:"mt-1 text-amber-700 border-amber-300 hover:bg-amber-50",children:[H?(0,a.jsx)(g.A,{className:"mr-2 h-3.5 w-3.5 animate-spin"}):(0,a.jsx)(b.A,{className:"mr-2 h-3.5 w-3.5"}),"Rotate Key"]}),(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:"Rotating invalidates the old key immediately. Update your broker configuration before rotating."})]})]})]})]}),!T&&G&&W.webhook_key&&(0,a.jsxs)(N.Fc,{className:"border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/20",children:[(0,a.jsx)(u.A,{className:"h-4 w-4 text-green-600"}),(0,a.jsx)(N.TN,{className:"text-sm text-green-800 dark:text-green-200",children:"Integration created. Copy your webhook key above before leaving - it can be revealed again from this dialog."})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)(d.J,{htmlFor:"topics",children:["Topics you will forward ",(0,a.jsx)("span",{className:"text-muted-foreground font-normal",children:"(for your reference)"})]}),(0,a.jsx)(l.p,{id:"topics",value:W.topics,onChange:e=>L({...W,topics:e.target.value}),placeholder:"devices/<integration-id>/telemetry,devices/<integration-id>/status"}),(0,a.jsxs)("p",{className:"text-xs text-muted-foreground",children:["Topic format: ",(0,a.jsx)("code",{className:"rounded bg-muted px-1",children:"devices/<integration_id>/telemetry"}),". Include ",(0,a.jsx)("code",{className:"rounded bg-muted px-1",children:"device_id"})," or ",(0,a.jsx)("code",{className:"rounded bg-muted px-1",children:"device_name"})," in the JSON payload to identify the device."]})]})]})}),G&&(0,a.jsxs)(r.av,{value:"broker-guide",className:"space-y-4 pt-4",children:[(0,a.jsx)("div",{className:"flex gap-2",children:["emqx","hivemq","mosquitto","generic"].map(e=>(0,a.jsx)(n.$,{type:"button",variant:Q===e?"default":"outline",size:"sm",onClick:()=>K(e),children:V[e].label},e))}),(0,a.jsxs)(N.Fc,{children:[(0,a.jsx)(y.A,{className:"h-4 w-4"}),(0,a.jsx)(N.TN,{className:"text-sm",children:V[Q].notes})]}),(0,a.jsxs)("div",{className:"relative",children:[(0,a.jsx)("pre",{className:"rounded-lg border bg-muted/50 p-4 text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all",children:V[Q].code}),(0,a.jsxs)(n.$,{type:"button",variant:"ghost",size:"sm",className:"absolute top-2 right-2 h-7 text-xs",onClick:()=>B(V[Q].code,`guide-${Q}`),children:[F===`guide-${Q}`?(0,a.jsx)(c.A,{className:"h-3 w-3 mr-1 text-green-600"}):(0,a.jsx)(m.A,{className:"h-3 w-3 mr-1"}),"Copy"]})]}),"mosquitto"===Q&&(0,a.jsxs)("p",{className:"text-xs text-muted-foreground",children:["For production, run the bridge script with PM2 or systemd. Add ",(0,a.jsx)("code",{className:"rounded bg-muted px-1",children:"-u username -P password"})," to the mosquitto_sub command if your broker requires authentication."]})]}),G&&(0,a.jsx)(r.av,{value:"data-format",className:"space-y-4",children:(0,a.jsx)(f.vT,{integrationId:G})}),G&&(0,a.jsx)(r.av,{value:"activity",className:"space-y-4",children:(0,a.jsx)(v.E,{integrationId:G,organizationId:I,limit:50,autoRefresh:!0})})]}),(0,a.jsxs)("div",{className:"flex justify-end gap-2 border-t pt-4",children:[(0,a.jsx)(n.$,{variant:"outline",onClick:()=>
74s(!1),disabled:R,children:G?"Close":"Cancel"}),(0,a.jsxs)(n.$,{onClick:J,disabled:R,children:[R&&(0,a.jsx)(g.A,{className:"mr-2 h-4 w-4 animate-spin"}),G?"Save Changes":"Create Integration"]})]})]});return"page"===C?(0,a.jsxs)("div",{className:"flex-1 space-y-4 p-4 pt-6 md:p-8",children:[(0,a.jsx)("div",{className:"mb-6 flex items-center justify-between",children:(0,a.jsxs)("div",{children:[(0,a.jsx)("h2",{className:"text-3xl font-bold tracking-tight",children:"External MQTT Integration"}),(0,a.jsx)("p",{className:"text-muted-foreground",children:"Configure your broker to forward device messages to Sentinel"})]})}),(0,a.jsx)(w.Zp,{children:(0,a.jsx)(w.Wu,{className:"pt-6",children:Z()})})]}):(0,a.jsx)(i.lG,{open:t,onOpenChange:s,children:(0,a.jsxs)(i.Cf,{className:"max-h-[90vh] w-full max-w-3xl overflow-y-auto",children:[(0,a.jsx)(i.c7,{children:(0,a.jsx)(i.L3,{children:"External MQTT Integration"})}),Z()]})})}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.