diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index eb64760..1b9977f 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -946,13 +946,10 @@ function MqttClientsTab() { const [loading, setLoading] = useState(true) const [showForm, setShowForm] = useState(false) const [name, setName] = useState('') - const [topicScope, setTopicScope] = useState('') - const [canPublish, setCanPublish] = useState(false) - const [canSubscribe, setCanSubscribe] = useState(true) const [isService, setIsService] = useState(false) const [creating, setCreating] = useState(false) const [error, setError] = useState('') - const [created, setCreated] = useState<{ username: string; password: string } | null>(null) + const [revealed, setRevealed] = useState<{ id: number; username: string; password: string } | null>(null) const [copied, setCopied] = useState(false) useEffect(() => { load() }, []) @@ -968,23 +965,18 @@ function MqttClientsTab() { e.preventDefault() setError('') if (!name.trim()) { setError('Name is required'); return } - if (!topicScope.trim()) { setError('Topic scope is required'); return } - if (!canPublish && !canSubscribe) { setError('Enable at least one of publish or subscribe'); return } setCreating(true) try { const res = await fetch('/settings/api/mqtt-clients', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - name: name.trim(), topic_scope: topicScope.trim(), - can_publish: canPublish, can_subscribe: canSubscribe, is_service: isService, - }), + body: JSON.stringify({ name: name.trim(), is_service: isService }), }) const data = await res.json() if (!res.ok) { setError(data.error || 'Failed to create client'); return } - setCreated({ username: data.username, password: data.password }) + setRevealed({ id: data.id, username: data.username, password: data.password }) setShowForm(false) - setName(''); setTopicScope(''); setCanPublish(false); setCanSubscribe(true); setIsService(false) + setName(''); setIsService(false) load() } finally { setCreating(false) @@ -1016,9 +1008,32 @@ function MqttClientsTab() { load() } + async function reveal(c: MqttClient) { + const res = await fetch(`/settings/api/mqtt-clients/${c.id}/password`, { credentials: 'include' }) + const data = await res.json() + if (!res.ok) { alert(data.error || 'Failed to load password'); return } + setRevealed({ id: c.id, username: c.username, password: data.password }) + } + + async function resetPassword(c: MqttClient) { + if (!confirm(`Generate a new password for "${c.username}"? Anything still using the old one will be refused until updated.`)) return + const res = await fetch(`/settings/api/mqtt-clients/${c.id}/password`, { method: 'POST', credentials: 'include' }) + const data = await res.json() + if (!res.ok) { alert(data.error || 'Failed to reset password'); return } + setRevealed({ id: c.id, username: c.username, password: data.password }) + } + + async function openAccess(c: MqttClient) { + if (!confirm(`Give "${c.username}" access to all topics? Its current topic restrictions are removed.`)) return + const res = await fetch(`/settings/api/mqtt-clients/${c.id}/open-access`, { method: 'POST', credentials: 'include' }) + const data = await res.json() + if (!res.ok) { alert(data.error || 'Failed'); return } + load() + } + function copyPassword() { - if (!created) return - navigator.clipboard.writeText(created.password) + if (!revealed) return + navigator.clipboard.writeText(revealed.password) setCopied(true) setTimeout(() => setCopied(false), 2000) } @@ -1031,7 +1046,7 @@ function MqttClientsTab() { MQTT Broker Clients
- Per-device/app broker logins, scoped to a topic filter — no more manually running mosquitto_ctrl. + Broker logins with access to all topics. Reuse one login across a batch of similar devices — passwords can be revealed any time.