Add MQTT brokers section
This commit is contained in:
parent
9232a9a0d6
commit
0a54980cf1
|
|
@ -1,21 +0,0 @@
|
|||
import { request } from './request'
|
||||
|
||||
export const publishMqttMessage = (body: {
|
||||
server: string
|
||||
clientId?: string
|
||||
username?: string
|
||||
password?: string
|
||||
qos?: number
|
||||
retain?: boolean
|
||||
message: string
|
||||
topic: string
|
||||
}) =>
|
||||
request(
|
||||
`/api/mqtt/publish`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
'void'
|
||||
)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { request } from './request'
|
||||
|
||||
export type MQTTBrokerInfo = {
|
||||
id: number
|
||||
name: string
|
||||
address: string
|
||||
username?: string
|
||||
password?: string
|
||||
clientId?: string
|
||||
}
|
||||
|
||||
export const getMQTTBrokers = () =>
|
||||
request<MQTTBrokerInfo[]>('/api/mqtt/brokers')
|
||||
|
||||
export const createMQTTBroker = (data: Omit<MQTTBrokerInfo, 'id'>) =>
|
||||
request<MQTTBrokerInfo>('/api/mqtt/brokers', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
|
||||
export const updateMQTTBroker = ({
|
||||
id,
|
||||
...body
|
||||
}: Omit<MQTTBrokerInfo, 'lastStatus' | 'lastStatusAt'>) =>
|
||||
request<MQTTBrokerInfo>(`/api/mqtt/brokers/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
export const deleteMQTTBroker = (id: number) =>
|
||||
request<MQTTBrokerInfo, 'void'>(
|
||||
`/api/mqtt/brokers/${id}`,
|
||||
{ method: 'DELETE' },
|
||||
'void'
|
||||
)
|
||||
|
||||
export const publishMqttBroker = ({
|
||||
mqttBrokerId,
|
||||
...body
|
||||
}: {
|
||||
mqttBrokerId: number
|
||||
qos?: number
|
||||
retain?: boolean
|
||||
message: string
|
||||
topic: string
|
||||
}) =>
|
||||
request(
|
||||
`/api/mqtt/brokers/${mqttBrokerId}/publish`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
'void'
|
||||
)
|
||||
|
|
@ -54,17 +54,16 @@ textarea {
|
|||
|
||||
.color-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
|
||||
.current-color {
|
||||
width: 2rem;
|
||||
height: 1.5rem;
|
||||
border: 1px solid var(--input-border-color);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
margin-top: -280px;
|
||||
margin-top: -240px;
|
||||
|
||||
> .title {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export const UserMenu = ({ shown, popup, onHide }: Props) => {
|
|||
<NavLink href="/">📈 Dashboards</NavLink>
|
||||
<NavLink href="/sensors">⌚ Sensors</NavLink>
|
||||
<NavLink href="/alerts">🚨 Alerts</NavLink>
|
||||
<NavLink href="/mqtt/brokers">⚙️ MQTT Brokers</NavLink>
|
||||
{/*<a href="#">⚙️ Settings</a>*/}
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { AlertsPage } from './alerts/AlertsPage'
|
|||
import { NewDashboardPage } from './dashboard/NewDashboardPage'
|
||||
import { LoginPage } from './login/LoginPage'
|
||||
import { SensorsPage } from './sensors/SensorsPage'
|
||||
import { MqttBrokersPage } from './mqttBrokers/MqttBrokersPage'
|
||||
|
||||
export const Router = () => {
|
||||
const { loggedIn } = useAppContext()
|
||||
|
|
@ -22,6 +23,9 @@ export const Router = () => {
|
|||
<Route path="/alerts">
|
||||
<AlertsPage />
|
||||
</Route>
|
||||
<Route path="/mqtt/brokers">
|
||||
<MqttBrokersPage />
|
||||
</Route>
|
||||
</Wouter>
|
||||
)}
|
||||
{!loggedIn && <LoginPage />}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { getMQTTBrokers } from '@/api/mqttBrokers'
|
||||
import { CssColorInput } from '@/components/CssColorInput'
|
||||
import { FormCheckboxField } from '@/components/FormCheckboxField'
|
||||
import { FormField } from '@/components/FormField'
|
||||
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
|
||||
import { useForm } from '@/utils/hooks/useForm'
|
||||
import { omit } from '@/utils/omit'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
type Props = {
|
||||
value?: DashboardMQTTButtonData
|
||||
|
|
@ -11,9 +13,10 @@ type Props = {
|
|||
}
|
||||
|
||||
export const MQTTButtonSettings = ({ value, onChange }: Props) => {
|
||||
const brokers = useQuery('/mqtt/brokers', getMQTTBrokers)
|
||||
|
||||
const { register } = useForm({
|
||||
defaultValue: () => ({
|
||||
server: '',
|
||||
topic: '',
|
||||
message: '',
|
||||
retain: false,
|
||||
|
|
@ -24,21 +27,14 @@ export const MQTTButtonSettings = ({ value, onChange }: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
name="server"
|
||||
label="MQTT Broker Address"
|
||||
hint="Example: tcp://10.10.1.1:1883"
|
||||
>
|
||||
<input required {...register('server')} />
|
||||
</FormField>
|
||||
<FormField name="clientId" label="Client ID">
|
||||
<input {...register('clientId')} />
|
||||
</FormField>
|
||||
<FormField name="username" label="Username">
|
||||
<input {...register('username')} />
|
||||
</FormField>
|
||||
<FormField name="password" label="Password">
|
||||
<input type="password" {...register('password')} />
|
||||
<FormField name="mqttBrokerId" label="MQTT Broker">
|
||||
<select {...register('mqttBrokerId', { type: 'integer' })}>
|
||||
{brokers.data?.map((b) => (
|
||||
<option key={b.id} value={b.id}>
|
||||
{b.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField name="qoc" label="QoS">
|
||||
<select {...register('qos', { type: 'integer' })}>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
import { DashboardMQTTButtonData } from '@/utils/dashboard/parseDashboard'
|
||||
import { EditableBox, EditableBoxProps } from './EditableBox'
|
||||
import { useMutation } from 'react-query'
|
||||
import { publishMqttMessage } from '@/api/mqtt'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { publishMqttBroker } from '@/api/mqttBrokers'
|
||||
|
||||
type Props = EditableBoxProps & {
|
||||
data: DashboardMQTTButtonData
|
||||
}
|
||||
|
||||
export const BoxMQTTButtonContent = ({ data, ...boxProps }: Props) => {
|
||||
const pushMutation = useMutation(publishMqttMessage)
|
||||
const pushMutation = useMutation(publishMqttBroker)
|
||||
|
||||
const onClick = () => {
|
||||
pushMutation.mutate({
|
||||
...data,
|
||||
})
|
||||
const id = data.mqttBrokerId
|
||||
|
||||
if (id) {
|
||||
pushMutation.mutate({
|
||||
mqttBrokerId: id,
|
||||
message: data.message,
|
||||
topic: data.topic,
|
||||
qos: data.qos,
|
||||
retain: data.retain,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import {
|
||||
MQTTBrokerInfo,
|
||||
deleteMQTTBroker,
|
||||
getMQTTBrokers,
|
||||
} from '@/api/mqttBrokers'
|
||||
import { DataTable } from '@/components/DataTable'
|
||||
import { useConfirmModal } from '@/contexts/ConfirmModalsContext'
|
||||
import { EditIcon, PlusIcon, TrashIcon } from '@/icons'
|
||||
import { UserLayout } from '@/layouts/UserLayout/UserLayout'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query'
|
||||
import { MQTTBrokerFormModal } from './components/MQTTBrokerFormModal'
|
||||
import { NoMQTTBrokers } from './components/NoMQTTBrokers'
|
||||
|
||||
export const MqttBrokersPage = () => {
|
||||
const brokers = useQuery(['/mqtt/brokers'], getMQTTBrokers)
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const deleteMutation = useMutation(deleteMQTTBroker, {
|
||||
onSuccess: () => queryClient.invalidateQueries(['/mqtt/brokers']),
|
||||
})
|
||||
|
||||
const [showNew, setShowNew] = useState(false)
|
||||
const [edited, setEdited] = useState<MQTTBrokerInfo>()
|
||||
|
||||
const deleteConfirm = useConfirmModal({
|
||||
content: (deleted: MQTTBrokerInfo) =>
|
||||
`Are you sure you want to delete sensor ${deleted.name}?`,
|
||||
onConfirm: (deleted) => deleteMutation.mutate(deleted.id),
|
||||
})
|
||||
|
||||
return (
|
||||
<UserLayout className="sensors-page">
|
||||
<div className="section-title">
|
||||
<h2>Sensors</h2>
|
||||
<button onClick={() => setShowNew(true)}>
|
||||
<PlusIcon /> Add MQTT Broker
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="box-shadow">
|
||||
<DataTable
|
||||
query={brokers}
|
||||
emptyMessage={<NoMQTTBrokers />}
|
||||
columns={[
|
||||
{ key: 'name', title: 'Name', render: (c) => c.name, scale: 1 },
|
||||
{
|
||||
key: 'address',
|
||||
title: 'Address',
|
||||
render: (c) => c.address,
|
||||
scale: 1,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: 'Actions',
|
||||
width: '15rem',
|
||||
className: 'actions',
|
||||
render: (c) => (
|
||||
<div>
|
||||
<button
|
||||
className="danger-variant"
|
||||
onClick={() => deleteConfirm.show(c)}
|
||||
>
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
<button onClick={() => setEdited(c)}>
|
||||
<EditIcon /> Edit
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(showNew || edited) && (
|
||||
<MQTTBrokerFormModal
|
||||
open
|
||||
mqttBroker={edited}
|
||||
onClose={() => {
|
||||
setShowNew(false)
|
||||
setEdited(undefined)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</UserLayout>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
MQTTBrokerInfo,
|
||||
createMQTTBroker,
|
||||
updateMQTTBroker,
|
||||
} from '@/api/mqttBrokers'
|
||||
import { FormField } from '@/components/FormField'
|
||||
import { Modal } from '@/components/Modal'
|
||||
import { useForm } from '@/utils/hooks/useForm'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
mqttBroker?: MQTTBrokerInfo
|
||||
}
|
||||
|
||||
export const MQTTBrokerFormModal = ({ open, onClose, mqttBroker }: Props) => {
|
||||
const queryClient = useQueryClient()
|
||||
const createMutation = useMutation(createMQTTBroker)
|
||||
const updateMutation = useMutation(updateMQTTBroker)
|
||||
|
||||
const { handleSubmit, register } = useForm({
|
||||
defaultValue: () => ({
|
||||
name: mqttBroker?.name ?? '',
|
||||
address: mqttBroker?.address ?? '',
|
||||
clientId: mqttBroker?.clientId ?? '',
|
||||
username: mqttBroker?.username ?? '',
|
||||
password: mqttBroker?.password ?? '',
|
||||
}),
|
||||
onSubmit: async (v) => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: v.name,
|
||||
address: v.address,
|
||||
clientId: v.clientId,
|
||||
username: v.username,
|
||||
password: v.password,
|
||||
}
|
||||
|
||||
if (mqttBroker) {
|
||||
await updateMutation.mutateAsync({
|
||||
id: mqttBroker.id,
|
||||
...data,
|
||||
})
|
||||
} else {
|
||||
await createMutation.mutateAsync(data)
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries(['/mqtt/brokers'])
|
||||
|
||||
onClose()
|
||||
},
|
||||
})
|
||||
|
||||
const isLoading = createMutation.isLoading || updateMutation.isLoading
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} open={open}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="input">
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
minLength={1}
|
||||
required
|
||||
autoFocus
|
||||
{...register('name')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
name="address"
|
||||
label="MQTT Broker Address"
|
||||
hint="Example: tcp://10.10.1.1:1883"
|
||||
>
|
||||
<input required {...register('address')} />
|
||||
</FormField>
|
||||
<FormField name="clientId" label="Client ID">
|
||||
<input {...register('clientId')} />
|
||||
</FormField>
|
||||
<FormField name="username" label="Username">
|
||||
<input {...register('username')} />
|
||||
</FormField>
|
||||
<FormField name="password" label="Password">
|
||||
<input type="password" {...register('password')} />
|
||||
</FormField>
|
||||
|
||||
<div className="actions">
|
||||
<button className="cancel" type="button" onClick={onClose}>
|
||||
Cancel
|
||||
</button>
|
||||
<button disabled={isLoading}>Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { PlusIcon } from '@/icons'
|
||||
import { useState } from 'preact/hooks'
|
||||
import { MQTTBrokerFormModal } from './MQTTBrokerFormModal'
|
||||
|
||||
export const NoMQTTBrokers = () => {
|
||||
const [showNew, setShowNew] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="empty">
|
||||
<div>No MQTT brokers defined.</div>
|
||||
<div>
|
||||
<button onClick={() => setShowNew(true)}>
|
||||
<PlusIcon /> Add a new MQTT broker
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{showNew && (
|
||||
<MQTTBrokerFormModal open onClose={() => setShowNew(false)} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import { deleteSensor, SensorInfo } from '@/api/sensors'
|
||||
import { InputWithCopy } from '@/components/InputWithCopy'
|
||||
import { useConfirmModal } from '@/contexts/ConfirmModalsContext'
|
||||
import { CancelIcon, EditIcon } from '@/icons'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
|
||||
type Props = {
|
||||
sensor: SensorInfo
|
||||
onEdit: (sensor: SensorInfo) => void
|
||||
}
|
||||
|
||||
export const SensorItem = ({ sensor, onEdit }: Props) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const deleteMutation = useMutation(deleteSensor, {
|
||||
onSuccess: () => queryClient.invalidateQueries(['/sensors']),
|
||||
})
|
||||
|
||||
const deleteConfirm = useConfirmModal({
|
||||
content: `Are you sure you want to delete sensor ${sensor.name}?`,
|
||||
onConfirm: () => deleteMutation.mutate(sensor.id),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="box sensor-item">
|
||||
<div className="name">{sensor.name}</div>
|
||||
|
||||
<div className="auth">
|
||||
<div className="auth-value">
|
||||
<div className="label">ID</div>
|
||||
<div className="value">
|
||||
<InputWithCopy value={sensor.id.toString()} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="auth-value">
|
||||
<div className="label">KEY</div>
|
||||
<div className="value">
|
||||
<InputWithCopy value={sensor.authKey} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<button onClick={() => onEdit(sensor)}>
|
||||
<EditIcon /> Edit
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={deleteConfirm.show}
|
||||
disabled={deleteMutation.isLoading}
|
||||
>
|
||||
<CancelIcon /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -46,10 +46,7 @@ export type DashboardDialData = {
|
|||
|
||||
export type DashboardMQTTButtonData = {
|
||||
type: 'mqttButton'
|
||||
server: string
|
||||
clientId?: string
|
||||
username?: string
|
||||
password?: string
|
||||
mqttBrokerId?: number
|
||||
qos?: number
|
||||
retain?: boolean
|
||||
message: string
|
||||
|
|
|
|||
|
|
@ -29,5 +29,8 @@ export const useHashRouterLocation = () => {
|
|||
|
||||
const locationWithoutQueryString = location.split('?')[0]
|
||||
|
||||
return [locationWithoutQueryString, setLocation] as const
|
||||
return [locationWithoutQueryString, setLocation] as [
|
||||
string,
|
||||
(to: string) => void
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
CREATE TABLE IF NOT EXISTS mqtt_brokers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
username TEXT,
|
||||
password TEXT,
|
||||
client_id TEXT
|
||||
);
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
package services
|
||||
package integrations
|
||||
|
||||
import (
|
||||
MQTT "github.com/eclipse/paho.mqtt.golang"
|
||||
)
|
||||
|
||||
type MQTTService struct {
|
||||
// TODO:
|
||||
// ctx *Context
|
||||
}
|
||||
type MQTTIntegration struct{}
|
||||
|
||||
func (s *MQTTService) Publish(server string, username string, password string, clientId string, retain bool, qos byte, topic string, message string) error {
|
||||
func (s *MQTTIntegration) Publish(server string, username string, password string, clientId string, retain bool, qos byte, topic string, message string) error {
|
||||
opts := MQTT.NewClientOptions()
|
||||
opts.AddBroker(server)
|
||||
opts.SetUsername(username)
|
||||
|
|
@ -69,7 +69,12 @@ func main() {
|
|||
loginProtected.PUT("/api/contact-points/:contactPointId", routes.PutContactPoint(server))
|
||||
loginProtected.DELETE("/api/contact-points/:contactPointId", routes.DeleteContactPoint(server))
|
||||
loginProtected.POST("/api/contact-points/test", routes.TestContactPoint(server))
|
||||
loginProtected.POST("/api/mqtt/publish", routes.PutMQTTPublish(server))
|
||||
loginProtected.GET("/api/mqtt/brokers", routes.GetMQTTBrokers(server))
|
||||
loginProtected.POST("/api/mqtt/brokers", routes.PostMQTTBroker(server))
|
||||
loginProtected.GET("/api/mqtt/brokers/:brokerId", routes.GetMQTTBroker(server))
|
||||
loginProtected.PUT("/api/mqtt/brokers/:brokerId", routes.PutMQTTBroker(server))
|
||||
loginProtected.DELETE("/api/mqtt/brokers/:brokerId", routes.DeleteMQTTBroker(server))
|
||||
loginProtected.POST("/api/mqtt/brokers/:brokerId/publish", routes.PostMQTTBrokerPublish(server))
|
||||
|
||||
if server.Config.AuthEnabled {
|
||||
loginProtected.POST("/api/logout", routes.Logout(server))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package models
|
||||
|
||||
type MQTTBrokerItem struct {
|
||||
Id int `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Address string `json:"address" db:"address"`
|
||||
Username *string `json:"username" db:"username"`
|
||||
Password *string `json:"password" db:"password"`
|
||||
ClientId *string `json:"client_id" db:"client_id"`
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/app"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type putMQTTPublishBody struct {
|
||||
Server string `json:"server" binding:"required"`
|
||||
ClientId string `json:"clientId"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Retain *bool `json:"retain"`
|
||||
Qos *float64 `json:"qos"`
|
||||
Topic string `json:"topic" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
}
|
||||
|
||||
func PutMQTTPublish(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
body := putMQTTPublishBody{}
|
||||
|
||||
if err := bindJSONBodyOrAbort(c, &body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
qos := byte(0)
|
||||
retain := false
|
||||
|
||||
if body.Retain != nil {
|
||||
retain = *body.Retain
|
||||
}
|
||||
|
||||
if body.Qos != nil {
|
||||
qos = byte(*body.Qos)
|
||||
}
|
||||
|
||||
if err := s.Services.MQTT.Publish(body.Server, body.Username, body.Password, body.ClientId, retain, qos, body.Topic, body.Message); err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"basic-sensor-receiver/app"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type postOrPutMQTTBrokerRequest struct {
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
Username *string `json:"username"`
|
||||
Password *string `json:"password"`
|
||||
ClientId *string `json:"client_id"`
|
||||
}
|
||||
|
||||
type postMQTTPublishRequest struct {
|
||||
Retain *bool `json:"retain"`
|
||||
Qos *float64 `json:"qos"`
|
||||
Topic string `json:"topic" binding:"required"`
|
||||
Message string `json:"message" binding:"required"`
|
||||
}
|
||||
|
||||
func GetMQTTBrokers(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
brokers, err := s.Services.MQTTBrokers.GetList()
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, brokers)
|
||||
}
|
||||
}
|
||||
|
||||
func PostMQTTBroker(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
body := postOrPutMQTTBrokerRequest{}
|
||||
if err := bindJSONBodyOrAbort(c, &body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
broker, err := s.Services.MQTTBrokers.Create(body.Name, body.Address, body.Username, body.Password, body.ClientId)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, broker)
|
||||
}
|
||||
}
|
||||
|
||||
func PutMQTTBroker(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
brokerId, err := getIntParamOrAbort(c, "brokerId")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body := postOrPutMQTTBrokerRequest{}
|
||||
if err := bindJSONBodyOrAbort(c, &body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
broker, err := s.Services.MQTTBrokers.Update(brokerId, body.Name, body.Address, body.Username, body.Password, body.ClientId)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, broker)
|
||||
}
|
||||
}
|
||||
|
||||
func GetMQTTBroker(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
brokerId, err := getIntParamOrAbort(c, "brokerId")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
broker, err := s.Services.MQTTBrokers.GetById(brokerId)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, broker)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteMQTTBroker(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
brokerId, err := getIntParamOrAbort(c, "brokerId")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Services.MQTTBrokers.Delete(brokerId)
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
}
|
||||
|
||||
func PostMQTTBrokerPublish(s *app.Server) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
brokerId, err := getIntParamOrAbort(c, "brokerId")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
body := postMQTTPublishRequest{}
|
||||
|
||||
if err := bindJSONBodyOrAbort(c, &body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
qos := byte(0)
|
||||
retain := false
|
||||
|
||||
if body.Retain != nil {
|
||||
retain = *body.Retain
|
||||
}
|
||||
|
||||
if body.Qos != nil {
|
||||
qos = byte(*body.Qos)
|
||||
}
|
||||
|
||||
if err := s.Services.MQTTBrokers.PublishTopic(brokerId, body.Topic, body.Message, qos, retain); err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package services
|
||||
|
||||
import "basic-sensor-receiver/models"
|
||||
|
||||
type MQTTBrokersService struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) GetList() ([]models.MQTTBrokerItem, error) {
|
||||
brokers := []models.MQTTBrokerItem{}
|
||||
|
||||
err := s.ctx.DB.Select(&brokers, `
|
||||
SELECT id, name, address, username, password, client_id
|
||||
FROM mqtt_brokers
|
||||
ORDER BY name ASC
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return brokers, nil
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) GetById(id int64) (*models.MQTTBrokerItem, error) {
|
||||
broker := models.MQTTBrokerItem{}
|
||||
|
||||
err := s.ctx.DB.Get(&broker,
|
||||
`
|
||||
SELECT id, name, address, username, password, client_id
|
||||
FROM mqtt_brokers
|
||||
WHERE id = $1
|
||||
`,
|
||||
id,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &broker, nil
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) Create(name string, address string, username *string, password *string, clientId *string) (*models.MQTTBrokerItem, error) {
|
||||
broker := models.MQTTBrokerItem{
|
||||
Name: name,
|
||||
Address: address,
|
||||
Username: username,
|
||||
Password: password,
|
||||
ClientId: clientId,
|
||||
}
|
||||
|
||||
res, err := s.ctx.DB.NamedExec(
|
||||
`
|
||||
INSERT INTO mqtt_brokers (name, address, username, password, client_id)
|
||||
VALUES (:name, :address, :username, :password, :client_id)
|
||||
`,
|
||||
broker,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
broker.Id = int(id)
|
||||
|
||||
return &broker, nil
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) Update(id int64, name string, address string, username *string, password *string, clientId *string) (*models.MQTTBrokerItem, error) {
|
||||
broker := models.MQTTBrokerItem{
|
||||
Id: int(id),
|
||||
Name: name,
|
||||
Address: address,
|
||||
Username: username,
|
||||
Password: password,
|
||||
ClientId: clientId,
|
||||
}
|
||||
|
||||
_, err := s.ctx.DB.NamedExec(
|
||||
`
|
||||
UPDATE mqtt_brokers
|
||||
SET name = :name, address = :address, username = :username, password = :password, client_id = :client_id
|
||||
WHERE id = :id
|
||||
`,
|
||||
broker,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &broker, nil
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) Delete(id int64) error {
|
||||
_, err := s.ctx.DB.Exec(
|
||||
`
|
||||
DELETE FROM mqtt_brokers
|
||||
WHERE id = $1
|
||||
`,
|
||||
id,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MQTTBrokersService) PublishTopic(brokerId int64, topic string, message string, qos byte, retain bool) error {
|
||||
broker, err := s.GetById(brokerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
username := ""
|
||||
if broker.Username != nil {
|
||||
username = *broker.Username
|
||||
}
|
||||
|
||||
password := ""
|
||||
if broker.Password != nil {
|
||||
password = *broker.Password
|
||||
}
|
||||
|
||||
clientId := ""
|
||||
if broker.ClientId != nil {
|
||||
clientId = *broker.ClientId
|
||||
}
|
||||
|
||||
return s.ctx.Integrations.MQTT.Publish(
|
||||
broker.Address,
|
||||
username,
|
||||
password,
|
||||
clientId,
|
||||
retain,
|
||||
qos,
|
||||
topic,
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
|
@ -17,11 +17,12 @@ type Services struct {
|
|||
Alerts *AlertsService
|
||||
AlertsEvaluator *AlertsEvaluatorService
|
||||
ContactPoints *ContactPointsService
|
||||
MQTT *MQTTService
|
||||
MQTTBrokers *MQTTBrokersService
|
||||
}
|
||||
|
||||
type Integrations struct {
|
||||
Telegram *integrations.TelegramIntegration
|
||||
MQTT *integrations.MQTTIntegration
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
|
|
@ -45,10 +46,11 @@ func InitializeServices(ctx *Context) *Services {
|
|||
services.Alerts = &AlertsService{ctx: ctx}
|
||||
services.AlertsEvaluator = &AlertsEvaluatorService{ctx: ctx}
|
||||
services.ContactPoints = &ContactPointsService{ctx: ctx}
|
||||
services.MQTT = &MQTTService{}
|
||||
services.MQTTBrokers = &MQTTBrokersService{ctx: ctx}
|
||||
|
||||
ctx.Integrations = &Integrations{}
|
||||
ctx.Integrations.Telegram = &integrations.TelegramIntegration{}
|
||||
ctx.Integrations.MQTT = &integrations.MQTTIntegration{}
|
||||
|
||||
return &services
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue