portal: 'service consumer' checkbox for MQTT broker clients
Surfaces settings' new service-client flag in the MQTT Broker Clients tab — a checkbox that stores the password encrypted so a stack app (e.g. hvac-backend) can fetch its own login at runtime, plus a 'service' badge in the client list. Device logins are unchanged (leave the box off). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d1b253a00c
commit
411911c4c8
1 changed files with 24 additions and 2 deletions
|
|
@ -929,6 +929,7 @@ interface MqttClient {
|
||||||
topic_scope: string
|
topic_scope: string
|
||||||
can_publish: boolean
|
can_publish: boolean
|
||||||
can_subscribe: boolean
|
can_subscribe: boolean
|
||||||
|
is_service: boolean
|
||||||
active: boolean
|
active: boolean
|
||||||
created_at: string
|
created_at: string
|
||||||
revoked_at: string | null
|
revoked_at: string | null
|
||||||
|
|
@ -942,6 +943,7 @@ function MqttClientsTab() {
|
||||||
const [topicScope, setTopicScope] = useState('')
|
const [topicScope, setTopicScope] = useState('')
|
||||||
const [canPublish, setCanPublish] = useState(false)
|
const [canPublish, setCanPublish] = useState(false)
|
||||||
const [canSubscribe, setCanSubscribe] = useState(true)
|
const [canSubscribe, setCanSubscribe] = useState(true)
|
||||||
|
const [isService, setIsService] = useState(false)
|
||||||
const [creating, setCreating] = useState(false)
|
const [creating, setCreating] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [created, setCreated] = useState<{ username: string; password: string } | null>(null)
|
const [created, setCreated] = useState<{ username: string; password: string } | null>(null)
|
||||||
|
|
@ -969,14 +971,14 @@ function MqttClientsTab() {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: name.trim(), topic_scope: topicScope.trim(),
|
name: name.trim(), topic_scope: topicScope.trim(),
|
||||||
can_publish: canPublish, can_subscribe: canSubscribe,
|
can_publish: canPublish, can_subscribe: canSubscribe, is_service: isService,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (!res.ok) { setError(data.error || 'Failed to create client'); return }
|
if (!res.ok) { setError(data.error || 'Failed to create client'); return }
|
||||||
setCreated({ username: data.username, password: data.password })
|
setCreated({ username: data.username, password: data.password })
|
||||||
setShowForm(false)
|
setShowForm(false)
|
||||||
setName(''); setTopicScope(''); setCanPublish(false); setCanSubscribe(true)
|
setName(''); setTopicScope(''); setCanPublish(false); setCanSubscribe(true); setIsService(false)
|
||||||
load()
|
load()
|
||||||
} finally {
|
} finally {
|
||||||
setCreating(false)
|
setCreating(false)
|
||||||
|
|
@ -1050,6 +1052,17 @@ function MqttClientsTab() {
|
||||||
<input type="checkbox" checked={canSubscribe} onChange={e => setCanSubscribe(e.target.checked)} /> Can subscribe
|
<input type="checkbox" checked={canSubscribe} onChange={e => setCanSubscribe(e.target.checked)} /> Can subscribe
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '0.4rem', fontSize: '0.85rem', color: 'var(--text-dark)' }}>
|
||||||
|
<input type="checkbox" checked={isService} onChange={e => setIsService(e.target.checked)} style={{ marginTop: '0.2rem' }} />
|
||||||
|
<span>
|
||||||
|
Service consumer (stack app)
|
||||||
|
<span style={{ display: 'block', fontSize: '0.76rem', color: 'var(--text-mid)', fontWeight: 400 }}>
|
||||||
|
Stores the password encrypted so a stack app can fetch its own login at runtime — needed for apps like
|
||||||
|
<code style={{ margin: '0 0.25em' }}>hvac-backend</code> that reconnect on restart. Name it to match the app's
|
||||||
|
client id; its username becomes the name's slug (no random suffix). Leave off for device logins (Shelly, gateways).
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" disabled={creating} style={{
|
<button type="submit" disabled={creating} style={{
|
||||||
background: 'var(--gold)', color: 'var(--navy)', border: 'none',
|
background: 'var(--gold)', color: 'var(--navy)', border: 'none',
|
||||||
|
|
@ -1119,6 +1132,15 @@ function MqttClientsTab() {
|
||||||
<span style={{ marginLeft: '0.5rem', fontFamily: 'monospace', fontSize: '0.78rem', color: 'var(--text-mid)', fontWeight: 400 }}>
|
<span style={{ marginLeft: '0.5rem', fontFamily: 'monospace', fontSize: '0.78rem', color: 'var(--text-mid)', fontWeight: 400 }}>
|
||||||
{c.username}
|
{c.username}
|
||||||
</span>
|
</span>
|
||||||
|
{c.is_service && (
|
||||||
|
<span style={{
|
||||||
|
marginLeft: '0.5rem', fontSize: '0.68rem', fontWeight: 600, color: 'var(--navy)',
|
||||||
|
background: 'var(--surface-2)', border: '1px solid var(--card-border)',
|
||||||
|
borderRadius: '4px', padding: '0.05rem 0.4rem', verticalAlign: 'middle',
|
||||||
|
}}>
|
||||||
|
service
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '0.72rem', color: 'var(--text-mid)', fontFamily: 'monospace' }}>
|
<div style={{ fontSize: '0.72rem', color: 'var(--text-mid)', fontFamily: 'monospace' }}>
|
||||||
{c.topic_scope}
|
{c.topic_scope}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue