Add MQTT Broker integration (config UI + connection test)

schema.js already had an uncommitted mqtt entry (host/port config,
username/password secrets) from a prior session — commits it now, adds
the missing test-connection case (routes/integrations.js), and the mqtt
package dependency it needs. The existing generic integration card UI in
portal's AdminSettings.tsx needs no changes — it renders any schema
entry automatically. initDb() auto-seeds the 'mqtt' row on next restart,
so the "MQTT Broker" card will appear in Settings -> Integrations.

Also adds a .gitignore — node_modules was untracked in this repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-28 14:58:53 +00:00
parent aaf857a5fd
commit 221be89667
5 changed files with 1179 additions and 3 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

1158
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,9 @@
"dependencies": {
"@fastify/cors": "^9.0.1",
"fastify": "^4.28.1",
"nodemailer": "^6.10.1",
"mqtt": "^5.15.2",
"mssql": "^11.0.1",
"nodemailer": "^6.10.1",
"pg": "^8.12.0"
}
}

View file

@ -47,6 +47,11 @@ export const INTEGRATIONS = {
configFields: [],
secretFields: ['api_key'],
},
mqtt: {
name: 'MQTT Broker',
configFields: ['host', 'port'],
secretFields: ['username', 'password'],
},
}
export const MASKED = '••••••••'

View file

@ -141,6 +141,21 @@ export async function integrationRoutes(app) {
if (res.status === 401) throw new Error('Invalid Nextcloud credentials')
if (!res.ok) throw new Error(`Nextcloud returned ${res.status}`)
}
else if (req.params.slug === 'mqtt') {
if (!creds.host || !creds.username || !creds.password) throw new Error('MQTT host, username and password are required')
const mqtt = await import('mqtt')
await new Promise((resolve, reject) => {
const c = mqtt.default.connect(`mqtt://${creds.host}:${creds.port || 1883}`, {
username: creds.username,
password: creds.password,
connectTimeout: 8000,
reconnectPeriod: 0,
})
const timer = setTimeout(() => { c.end(true); reject(new Error('Connection timed out')) }, 9000)
c.on('connect', () => { clearTimeout(timer); c.end(true); resolve() })
c.on('error', (err) => { clearTimeout(timer); c.end(true); reject(err) })
})
}
else if (req.params.slug === 'azure') {
if (!creds.endpoint || !creds.api_key) throw new Error('Azure endpoint and API key are required')
const base = creds.endpoint.replace(/\/$/, '')