@attraccess/plugin-rabbitmq 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1001 @@
1
+ import { r as importShared } from "./_virtual___federation_fn_import-BXODRNto.js";
2
+ import { t as require_jsx_runtime } from "./jsx-runtime-B9Qmn8k7.js";
3
+ //#region ../../../libs/plugins-frontend-sdk/src/lib/frontend.api-client.ts
4
+ var BASE_URL_GLOBAL = "__ATTRACCESS_API_BASE_URL__";
5
+ /** The API origin the host is talking to. Falls back to the current origin. */
6
+ function getApiBaseUrl() {
7
+ return window[BASE_URL_GLOBAL] ?? window.location.origin;
8
+ }
9
+ /** Thrown for any non-2xx response, carrying the status for callers that branch on it. */
10
+ var PluginApiError = class extends Error {
11
+ constructor(message, status) {
12
+ super(message);
13
+ this.status = status;
14
+ this.name = "PluginApiError";
15
+ }
16
+ };
17
+ async function toError(res) {
18
+ try {
19
+ const body = await res.json();
20
+ const message = Array.isArray(body.message) ? body.message.join(", ") : body.message;
21
+ if (message) return new PluginApiError(message, res.status);
22
+ } catch {}
23
+ return new PluginApiError(`Request failed (HTTP ${res.status}).`, res.status);
24
+ }
25
+ /**
26
+ * Creates a client bound to `basePath`, which is resolved against the host's
27
+ * API origin. Plugin routes are mounted under `/api/<plugin-name>`, so that is
28
+ * the usual argument; pass nothing to address the host API directly.
29
+ */
30
+ function createPluginApiClient(basePath = "") {
31
+ const url = (path, query) => {
32
+ const target = new URL(`${basePath}${path}`, getApiBaseUrl());
33
+ Object.entries(query ?? {}).forEach(([key, value]) => {
34
+ if (value !== void 0 && value !== null) target.searchParams.set(key, String(value));
35
+ });
36
+ return target.toString();
37
+ };
38
+ const doFetch = (path, init) => fetch(url(path), {
39
+ credentials: "include",
40
+ ...init
41
+ });
42
+ return {
43
+ url,
44
+ fetch: doFetch,
45
+ async request(path, options = {}) {
46
+ const { body, query, headers, ...init } = options;
47
+ const res = await fetch(url(path, query), {
48
+ credentials: "include",
49
+ ...init,
50
+ headers: body === void 0 ? headers : {
51
+ "Content-Type": "application/json",
52
+ ...headers
53
+ },
54
+ body: body === void 0 ? void 0 : JSON.stringify(body)
55
+ });
56
+ if (!res.ok) throw await toError(res);
57
+ const text = await res.text();
58
+ return text.length > 0 ? JSON.parse(text) : null;
59
+ }
60
+ };
61
+ }
62
+ //#endregion
63
+ //#region frontend/src/detection.ts
64
+ var { useCallback: useCallback$1, useEffect: useEffect$3, useState: useState$3 } = await importShared("react");
65
+ var api$1 = createPluginApiClient("/api/rabbitmq/detection");
66
+ function fetchDetection(mqttServerId, refresh) {
67
+ return api$1.request(`/${mqttServerId}`, { query: { refresh: refresh ? "true" : void 0 } });
68
+ }
69
+ var CACHE_TTL_MS = 6e4;
70
+ var EMPTY_ENTRY = {
71
+ result: null,
72
+ loading: true,
73
+ error: null,
74
+ inFlight: null,
75
+ expiresAt: 0
76
+ };
77
+ var store = /* @__PURE__ */ new Map();
78
+ var subscribers = /* @__PURE__ */ new Map();
79
+ function now() {
80
+ return Date.now();
81
+ }
82
+ function notify(mqttServerId) {
83
+ subscribers.get(mqttServerId)?.forEach((fn) => fn());
84
+ }
85
+ function ensureLoaded(mqttServerId, refresh) {
86
+ const current = store.get(mqttServerId);
87
+ if (current?.inFlight) return;
88
+ if (!refresh && current?.result && current.expiresAt > now()) return;
89
+ const entry = {
90
+ result: current?.result ?? null,
91
+ loading: true,
92
+ error: null,
93
+ inFlight: null,
94
+ expiresAt: current?.expiresAt ?? 0
95
+ };
96
+ entry.inFlight = fetchDetection(mqttServerId, refresh).then((data) => {
97
+ entry.result = data;
98
+ entry.error = null;
99
+ entry.expiresAt = now() + CACHE_TTL_MS;
100
+ }).catch((err) => {
101
+ entry.error = err.message;
102
+ }).finally(() => {
103
+ entry.loading = false;
104
+ entry.inFlight = null;
105
+ notify(mqttServerId);
106
+ });
107
+ store.set(mqttServerId, entry);
108
+ notify(mqttServerId);
109
+ }
110
+ function useDetection(mqttServerId) {
111
+ const [, forceRender] = useState$3(0);
112
+ useEffect$3(() => {
113
+ const rerender = () => forceRender((n) => n + 1);
114
+ let set = subscribers.get(mqttServerId);
115
+ if (!set) {
116
+ set = /* @__PURE__ */ new Set();
117
+ subscribers.set(mqttServerId, set);
118
+ }
119
+ set.add(rerender);
120
+ ensureLoaded(mqttServerId, false);
121
+ return () => {
122
+ set?.delete(rerender);
123
+ if (set && set.size === 0) subscribers.delete(mqttServerId);
124
+ };
125
+ }, [mqttServerId]);
126
+ const refresh = useCallback$1(() => ensureLoaded(mqttServerId, true), [mqttServerId]);
127
+ const entry = store.get(mqttServerId) ?? EMPTY_ENTRY;
128
+ return {
129
+ result: entry.result,
130
+ loading: entry.loading,
131
+ error: entry.error,
132
+ refresh
133
+ };
134
+ }
135
+ //#endregion
136
+ //#region frontend/src/RabbitmqListBadge.tsx
137
+ var import_jsx_runtime = require_jsx_runtime();
138
+ var { Chip: Chip$2 } = await importShared("@heroui/react");
139
+ var { RabbitIcon: RabbitIcon$2 } = await importShared("lucide-react");
140
+ function RabbitmqListBadge({ mqttServerId }) {
141
+ const { result } = useDetection(mqttServerId);
142
+ if (!result?.isRabbitMQ) return null;
143
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Chip$2, {
144
+ "data-cy": `rabbitmq-list-badge-${mqttServerId}`,
145
+ color: "accent",
146
+ variant: "soft",
147
+ size: "sm",
148
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitIcon$2, { className: "rmq:w-3.5 rmq:h-3.5" }), "RabbitMQ"]
149
+ });
150
+ }
151
+ //#endregion
152
+ //#region frontend/src/RabbitmqStatusPanel.tsx
153
+ var { Alert: Alert$3, AlertContent: AlertContent$3, AlertDescription: AlertDescription$3, Button: Button$3, Card: Card$2, Chip: Chip$1, Spinner: Spinner$1 } = await importShared("@heroui/react");
154
+ var { CheckCircle2Icon, RabbitIcon: RabbitIcon$1, RefreshCwIcon: RefreshCwIcon$1, XCircleIcon } = await importShared("lucide-react");
155
+ function StatusRow({ ok, label, value }) {
156
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
157
+ className: "rmq:flex rmq:items-center rmq:justify-between rmq:gap-3 rmq:py-1.5",
158
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
159
+ className: "rmq:flex rmq:items-center rmq:gap-2",
160
+ children: [ok ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckCircle2Icon, { className: "rmq:w-4 rmq:h-4 rmq:text-success" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XCircleIcon, { className: "rmq:w-4 rmq:h-4 rmq:text-danger" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
161
+ className: "rmq:text-sm rmq:text-default-600",
162
+ children: label
163
+ })]
164
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
165
+ className: "rmq:text-sm rmq:font-medium rmq:text-default-700",
166
+ children: value
167
+ })]
168
+ });
169
+ }
170
+ function RabbitmqStatusPanel({ mqttServerId }) {
171
+ const { result, loading, refresh } = useDetection(mqttServerId);
172
+ if (!result?.isRabbitMQ) return null;
173
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2, {
174
+ "data-cy": `rabbitmq-status-panel-${mqttServerId}`,
175
+ className: "rmq:w-full rmq:border rmq:border-default-200 rmq:dark:border-default-100",
176
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2.Header, {
177
+ className: "rmq:flex rmq:flex-row rmq:items-center rmq:justify-between rmq:gap-2",
178
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
179
+ className: "rmq:flex rmq:items-center rmq:gap-2",
180
+ children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitIcon$1, { className: "rmq:w-5 rmq:h-5 rmq:text-accent-soft-foreground" }),
182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
183
+ className: "rmq:text-base rmq:font-semibold rmq:text-default-700",
184
+ children: "RabbitMQ"
185
+ }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip$1, {
187
+ color: "accent",
188
+ variant: "soft",
189
+ size: "sm",
190
+ children: "detected"
191
+ })
192
+ ]
193
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$3, {
194
+ isIconOnly: true,
195
+ size: "sm",
196
+ variant: "ghost",
197
+ "aria-label": "Refresh RabbitMQ status",
198
+ "data-cy": `rabbitmq-status-refresh-${mqttServerId}`,
199
+ onPress: refresh,
200
+ isDisabled: loading,
201
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner$1, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCwIcon$1, { className: "rmq:w-4 rmq:h-4" })
202
+ })]
203
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$2.Content, {
204
+ className: "rmq:flex rmq:flex-col rmq:gap-1",
205
+ children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusRow, {
207
+ ok: result.reachable,
208
+ label: "Management API reachable",
209
+ value: result.reachable ? "Yes" : "No"
210
+ }),
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusRow, {
212
+ ok: result.authOk,
213
+ label: "Authentication",
214
+ value: result.authOk ? "OK" : "Failed"
215
+ }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
217
+ className: "rmq:flex rmq:items-center rmq:justify-between rmq:gap-3 rmq:py-1.5",
218
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
219
+ className: "rmq:text-sm rmq:text-default-600",
220
+ children: "Management API version"
221
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
222
+ className: "rmq:text-sm rmq:font-medium rmq:text-default-700",
223
+ children: result.managementVersion ?? "—"
224
+ })]
225
+ }),
226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
227
+ className: "rmq:flex rmq:items-center rmq:justify-between rmq:gap-3 rmq:py-1.5",
228
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
229
+ className: "rmq:text-sm rmq:text-default-600",
230
+ children: "RabbitMQ version"
231
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
232
+ className: "rmq:text-sm rmq:font-medium rmq:text-default-700",
233
+ children: result.rabbitmqVersion ?? "—"
234
+ })]
235
+ }),
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
237
+ className: "rmq:flex rmq:items-center rmq:justify-between rmq:gap-3 rmq:py-1.5",
238
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
239
+ className: "rmq:text-sm rmq:text-default-600",
240
+ children: "Management API"
241
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
242
+ className: "rmq:text-xs rmq:text-default-500",
243
+ children: result.managementApi || "—"
244
+ })]
245
+ }),
246
+ result.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$3, {
247
+ status: result.reachable ? "warning" : "danger",
248
+ className: "rmq:mt-2",
249
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$3, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$3, { children: result.error }) })
250
+ })
251
+ ]
252
+ })]
253
+ });
254
+ }
255
+ //#endregion
256
+ //#region frontend/src/users-api.ts
257
+ var DEFAULT_MQTT_PERMISSIONS = {
258
+ configure: "^mqtt-subscription-.*$",
259
+ write: "^(amq\\.topic|mqtt-subscription-.*)$",
260
+ read: "^(amq\\.topic|mqtt-subscription-.*)$"
261
+ };
262
+ var api = createPluginApiClient("/api/rabbitmq/users");
263
+ function fetchUsers(mqttServerId) {
264
+ return api.request(`/${mqttServerId}`);
265
+ }
266
+ function upsertUser(mqttServerId, username, body) {
267
+ return api.request(`/${mqttServerId}/${encodeURIComponent(username)}`, {
268
+ method: "PUT",
269
+ body
270
+ });
271
+ }
272
+ function deleteUser(mqttServerId, username) {
273
+ return api.request(`/${mqttServerId}/${encodeURIComponent(username)}`, { method: "DELETE" });
274
+ }
275
+ function setPermissions(mqttServerId, username, permission) {
276
+ return api.request(`/${mqttServerId}/${encodeURIComponent(username)}/permissions`, {
277
+ method: "PUT",
278
+ body: permission
279
+ });
280
+ }
281
+ function clearPermissions(mqttServerId, username, vhost) {
282
+ return api.request(`/${mqttServerId}/${encodeURIComponent(username)}/permissions`, {
283
+ method: "DELETE",
284
+ query: { vhost }
285
+ });
286
+ }
287
+ //#endregion
288
+ //#region frontend/src/RabbitmqPermissionsModal.tsx
289
+ var { Alert: Alert$2, AlertContent: AlertContent$2, AlertDescription: AlertDescription$2, Button: Button$2, Input: Input$1, Label: Label$1, Modal: Modal$2, ModalBackdrop: ModalBackdrop$2, ModalBody: ModalBody$2, ModalContainer: ModalContainer$2, ModalDialog: ModalDialog$2, ModalFooter: ModalFooter$2, ModalHeader: ModalHeader$2, ModalHeading: ModalHeading$2, TextField: TextField$1 } = await importShared("@heroui/react");
290
+ var { PlusIcon: PlusIcon$1, Trash2Icon: Trash2Icon$1 } = await importShared("lucide-react");
291
+ var { useEffect: useEffect$2, useState: useState$2 } = await importShared("react");
292
+ function PermissionEditor({ row, busy, onChange, onSave, onRemove }) {
293
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
294
+ className: "rmq:flex rmq:flex-col rmq:gap-3 rmq:rounded-lg rmq:border rmq:border-default-200 rmq:dark:border-default-100 rmq:p-3",
295
+ "data-cy": `rabbitmq-permissions-row-${row.vhost}`,
296
+ children: [
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
298
+ className: "rmq:flex rmq:items-center rmq:justify-between rmq:gap-2",
299
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
300
+ className: "rmq:text-sm rmq:font-semibold rmq:text-default-700",
301
+ children: ["Vhost ", /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: row.vhost })]
302
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
303
+ isIconOnly: true,
304
+ size: "sm",
305
+ variant: "ghost",
306
+ "aria-label": `Remove permissions on ${row.vhost}`,
307
+ onPress: onRemove,
308
+ isDisabled: busy,
309
+ "data-cy": `rabbitmq-permissions-remove-button-${row.vhost}`,
310
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2Icon$1, { className: "rmq:w-4 rmq:h-4 rmq:text-danger" })
311
+ })]
312
+ }),
313
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
314
+ className: "rmq:grid rmq:grid-cols-1 rmq:md:grid-cols-3 rmq:gap-3",
315
+ children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$1, {
317
+ value: row.configure,
318
+ onChange: (v) => onChange({
319
+ ...row,
320
+ configure: v
321
+ }),
322
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Configure" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
323
+ autoComplete: "off",
324
+ "data-cy": `rabbitmq-permissions-configure-input-${row.vhost}`
325
+ })]
326
+ }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$1, {
328
+ value: row.write,
329
+ onChange: (v) => onChange({
330
+ ...row,
331
+ write: v
332
+ }),
333
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Write" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
334
+ autoComplete: "off",
335
+ "data-cy": `rabbitmq-permissions-write-input-${row.vhost}`
336
+ })]
337
+ }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$1, {
339
+ value: row.read,
340
+ onChange: (v) => onChange({
341
+ ...row,
342
+ read: v
343
+ }),
344
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Read" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
345
+ autoComplete: "off",
346
+ "data-cy": `rabbitmq-permissions-read-input-${row.vhost}`
347
+ })]
348
+ })
349
+ ]
350
+ }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
352
+ className: "rmq:flex rmq:justify-end rmq:gap-2",
353
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
354
+ size: "sm",
355
+ variant: "ghost",
356
+ onPress: () => onChange({
357
+ ...row,
358
+ ...DEFAULT_MQTT_PERMISSIONS
359
+ }),
360
+ isDisabled: busy,
361
+ "data-cy": `rabbitmq-permissions-mqtt-defaults-button-${row.vhost}`,
362
+ children: "Use MQTT defaults"
363
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
364
+ size: "sm",
365
+ variant: "primary",
366
+ onPress: onSave,
367
+ isPending: busy,
368
+ "data-cy": `rabbitmq-permissions-save-button-${row.vhost}`,
369
+ children: "Save"
370
+ })]
371
+ })
372
+ ]
373
+ });
374
+ }
375
+ function RabbitmqPermissionsModal({ mqttServerId, isOpen, user, vhosts, onClose, onSaved }) {
376
+ const [rows, setRows] = useState$2([]);
377
+ const [newVhost, setNewVhost] = useState$2("");
378
+ const [busyVhost, setBusyVhost] = useState$2(null);
379
+ const [error, setError] = useState$2(null);
380
+ useEffect$2(() => {
381
+ if (!isOpen) return;
382
+ setRows((user?.permissions ?? []).map((permission) => ({
383
+ ...permission,
384
+ persisted: true
385
+ })));
386
+ setNewVhost("");
387
+ setBusyVhost(null);
388
+ setError(null);
389
+ }, [isOpen, user]);
390
+ if (!user) return null;
391
+ const updateRow = (index, next) => {
392
+ setRows((current) => current.map((row, i) => i === index ? next : row));
393
+ };
394
+ const saveRow = async (index) => {
395
+ const row = rows[index];
396
+ setBusyVhost(row.vhost);
397
+ setError(null);
398
+ try {
399
+ await setPermissions(mqttServerId, user.name, {
400
+ vhost: row.vhost,
401
+ configure: row.configure,
402
+ write: row.write,
403
+ read: row.read
404
+ });
405
+ updateRow(index, {
406
+ ...row,
407
+ persisted: true
408
+ });
409
+ onSaved();
410
+ } catch (err) {
411
+ setError(err instanceof Error ? err.message : "Saving permissions failed.");
412
+ } finally {
413
+ setBusyVhost(null);
414
+ }
415
+ };
416
+ const removeRow = async (index) => {
417
+ const row = rows[index];
418
+ if (!row.persisted) {
419
+ setRows((current) => current.filter((_, i) => i !== index));
420
+ return;
421
+ }
422
+ setBusyVhost(row.vhost);
423
+ setError(null);
424
+ try {
425
+ await clearPermissions(mqttServerId, user.name, row.vhost);
426
+ setRows((current) => current.filter((_, i) => i !== index));
427
+ onSaved();
428
+ } catch (err) {
429
+ setError(err instanceof Error ? err.message : "Removing permissions failed.");
430
+ } finally {
431
+ setBusyVhost(null);
432
+ }
433
+ };
434
+ const addVhost = () => {
435
+ const vhost = newVhost.trim();
436
+ if (vhost.length === 0) return;
437
+ if (rows.some((row) => row.vhost === vhost)) {
438
+ setError(`Permissions for vhost "${vhost}" are already listed.`);
439
+ return;
440
+ }
441
+ setRows((current) => [...current, {
442
+ vhost,
443
+ persisted: false,
444
+ ...DEFAULT_MQTT_PERMISSIONS
445
+ }]);
446
+ setNewVhost("");
447
+ setError(null);
448
+ };
449
+ const unusedVhosts = vhosts.filter((vhost) => !rows.some((row) => row.vhost === vhost));
450
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal$2, {
451
+ isOpen,
452
+ onOpenChange: (open) => {
453
+ if (!open) onClose();
454
+ },
455
+ "data-cy": `rabbitmq-permissions-modal-${mqttServerId}`,
456
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalBackdrop$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContainer$2, {
457
+ size: "lg",
458
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalDialog$2, { children: [
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeader$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalHeading$2, { children: [
460
+ "Permissions of \"",
461
+ user.name,
462
+ "\""
463
+ ] }) }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalBody$2, {
465
+ className: "rmq:flex rmq:flex-col rmq:gap-4 rmq:w-full",
466
+ children: [
467
+ rows.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
468
+ className: "rmq:text-sm rmq:text-default-500",
469
+ "data-cy": "rabbitmq-permissions-empty",
470
+ children: "This user has no permissions on any vhost — it cannot connect yet."
471
+ }),
472
+ rows.map((row, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PermissionEditor, {
473
+ row,
474
+ busy: busyVhost === row.vhost,
475
+ onChange: (next) => updateRow(index, next),
476
+ onSave: () => saveRow(index),
477
+ onRemove: () => removeRow(index)
478
+ }, row.vhost)),
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
480
+ className: "rmq:flex rmq:items-end rmq:gap-2",
481
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField$1, {
482
+ value: newVhost,
483
+ onChange: setNewVhost,
484
+ className: "rmq:flex-1",
485
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Label$1, { children: ["Add vhost", unusedVhosts.length > 0 ? ` (available: ${unusedVhosts.join(", ")})` : ""] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input$1, {
486
+ placeholder: "/",
487
+ autoComplete: "off",
488
+ "data-cy": "rabbitmq-permissions-add-vhost-input"
489
+ })]
490
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button$2, {
491
+ variant: "secondary",
492
+ onPress: addVhost,
493
+ isDisabled: newVhost.trim().length === 0,
494
+ "data-cy": "rabbitmq-permissions-add-vhost-button",
495
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon$1, { className: "rmq:w-4 rmq:h-4" }), "Add"]
496
+ })]
497
+ }),
498
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$2, {
499
+ status: "danger",
500
+ "data-cy": "rabbitmq-permissions-error-alert",
501
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$2, { children: error }) })
502
+ })
503
+ ]
504
+ }),
505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalFooter$2, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$2, {
506
+ variant: "secondary",
507
+ onPress: onClose,
508
+ "data-cy": "rabbitmq-permissions-close-button",
509
+ children: "Close"
510
+ }) })
511
+ ] })
512
+ }) })
513
+ });
514
+ }
515
+ //#endregion
516
+ //#region frontend/src/RabbitmqUserFormModal.tsx
517
+ var { Alert: Alert$1, AlertContent: AlertContent$1, AlertDescription: AlertDescription$1, Button: Button$1, Checkbox, Form, Input, Label, Modal: Modal$1, ModalBackdrop: ModalBackdrop$1, ModalBody: ModalBody$1, ModalContainer: ModalContainer$1, ModalDialog: ModalDialog$1, ModalFooter: ModalFooter$1, ModalHeader: ModalHeader$1, ModalHeading: ModalHeading$1, TextField } = await importShared("@heroui/react");
518
+ var { useEffect: useEffect$1, useState: useState$1 } = await importShared("react");
519
+ function parseTags(value) {
520
+ return value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
521
+ }
522
+ function RabbitmqUserFormModal({ mqttServerId, isOpen, user, vhosts, onClose, onSaved }) {
523
+ const isEdit = user !== null;
524
+ const [username, setUsername] = useState$1("");
525
+ const [password, setPassword] = useState$1("");
526
+ const [tags, setTags] = useState$1("");
527
+ const [grantMqttDefaults, setGrantMqttDefaults] = useState$1(true);
528
+ const [vhost, setVhost] = useState$1("/");
529
+ const [saving, setSaving] = useState$1(false);
530
+ const [error, setError] = useState$1(null);
531
+ useEffect$1(() => {
532
+ if (!isOpen) return;
533
+ setUsername(user?.name ?? "");
534
+ setPassword("");
535
+ setTags(user?.tags.join(", ") ?? "");
536
+ setGrantMqttDefaults(true);
537
+ setVhost(vhosts.includes("/") || vhosts.length === 0 ? "/" : vhosts[0]);
538
+ setError(null);
539
+ }, [
540
+ isOpen,
541
+ user,
542
+ vhosts
543
+ ]);
544
+ const handleSubmit = async (e) => {
545
+ e.preventDefault();
546
+ const name = username.trim();
547
+ if (name.length === 0) {
548
+ setError("Username is required.");
549
+ return;
550
+ }
551
+ if (!isEdit && password.length === 0) {
552
+ setError("Password is required when creating a user.");
553
+ return;
554
+ }
555
+ const body = { tags: parseTags(tags) };
556
+ if (password.length > 0) body.password = password;
557
+ if (!isEdit && grantMqttDefaults && vhost.trim().length > 0) body.permissions = [{
558
+ vhost: vhost.trim(),
559
+ ...DEFAULT_MQTT_PERMISSIONS
560
+ }];
561
+ setSaving(true);
562
+ setError(null);
563
+ try {
564
+ await upsertUser(mqttServerId, name, body);
565
+ onSaved();
566
+ onClose();
567
+ } catch (err) {
568
+ setError(err instanceof Error ? err.message : "Saving the user failed.");
569
+ } finally {
570
+ setSaving(false);
571
+ }
572
+ };
573
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal$1, {
574
+ isOpen,
575
+ onOpenChange: (open) => {
576
+ if (!open) onClose();
577
+ },
578
+ "data-cy": `rabbitmq-user-form-modal-${mqttServerId}`,
579
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalBackdrop$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContainer$1, {
580
+ size: "md",
581
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalDialog$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeader$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeading$1, { children: isEdit ? `Edit user "${user.name}"` : "Create RabbitMQ user" }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Form, {
582
+ onSubmit: handleSubmit,
583
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalBody$1, {
584
+ className: "rmq:flex rmq:flex-col rmq:gap-4 rmq:w-full",
585
+ children: [
586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField, {
587
+ value: username,
588
+ onChange: setUsername,
589
+ className: "rmq:w-full",
590
+ isDisabled: isEdit,
591
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Username" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
592
+ name: "rabbitmq-username",
593
+ placeholder: "e.g. shop-floor-sensor",
594
+ autoComplete: "off",
595
+ "data-cy": "rabbitmq-user-form-username-input"
596
+ })]
597
+ }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField, {
599
+ value: password,
600
+ onChange: setPassword,
601
+ className: "rmq:w-full",
602
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: isEdit ? "New password (leave blank to keep current)" : "Password" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
603
+ name: "rabbitmq-password",
604
+ type: "password",
605
+ autoComplete: "new-password",
606
+ "data-cy": "rabbitmq-user-form-password-input"
607
+ })]
608
+ }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField, {
610
+ value: tags,
611
+ onChange: setTags,
612
+ className: "rmq:w-full",
613
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Tags (comma-separated, e.g. management, administrator)" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
614
+ name: "rabbitmq-tags",
615
+ placeholder: "none",
616
+ autoComplete: "off",
617
+ "data-cy": "rabbitmq-user-form-tags-input"
618
+ })]
619
+ }),
620
+ !isEdit && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
621
+ className: "rmq:flex rmq:flex-col rmq:gap-3 rmq:rounded-lg rmq:border rmq:border-default-200 rmq:dark:border-default-100 rmq:p-3",
622
+ children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox, {
624
+ isSelected: grantMqttDefaults,
625
+ onChange: setGrantMqttDefaults,
626
+ "data-cy": "rabbitmq-user-form-mqtt-defaults-checkbox",
627
+ children: "Grant default MQTT permissions"
628
+ }),
629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
630
+ className: "rmq:text-xs rmq:text-default-500",
631
+ children: [
632
+ "Allows the user to publish and subscribe over MQTT: configure",
633
+ " ",
634
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: DEFAULT_MQTT_PERMISSIONS.configure }),
635
+ ", write/read",
636
+ " ",
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: DEFAULT_MQTT_PERMISSIONS.write }),
638
+ "."
639
+ ]
640
+ }),
641
+ grantMqttDefaults && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TextField, {
642
+ value: vhost,
643
+ onChange: setVhost,
644
+ className: "rmq:w-full",
645
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Label, { children: ["Vhost", vhosts.length > 0 ? ` (available: ${vhosts.join(", ")})` : ""] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
646
+ name: "rabbitmq-vhost",
647
+ placeholder: "/",
648
+ autoComplete: "off",
649
+ "data-cy": "rabbitmq-user-form-vhost-input"
650
+ })]
651
+ })
652
+ ]
653
+ }),
654
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert$1, {
655
+ status: "danger",
656
+ "data-cy": "rabbitmq-user-form-error-alert",
657
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription$1, { children: error }) })
658
+ })
659
+ ]
660
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
661
+ variant: "secondary",
662
+ onPress: onClose,
663
+ "data-cy": "rabbitmq-user-form-cancel-button",
664
+ children: "Cancel"
665
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
666
+ variant: "primary",
667
+ type: "submit",
668
+ isPending: saving,
669
+ "data-cy": "rabbitmq-user-form-save-button",
670
+ children: isEdit ? "Save changes" : "Create user"
671
+ })] })]
672
+ })] })
673
+ }) })
674
+ });
675
+ }
676
+ //#endregion
677
+ //#region frontend/src/RabbitmqUserPanel.tsx
678
+ var { Alert, AlertContent, AlertDescription, Button, Card: Card$1, Chip, Modal, ModalBackdrop, ModalBody, ModalContainer, ModalDialog, ModalFooter, ModalHeader, ModalHeading, Spinner, Table, TableBody, TableCell, TableColumn, TableContent, TableHeader, TableRow, TableScrollContainer } = await importShared("@heroui/react");
679
+ var { KeyRoundIcon, PencilIcon, PlusIcon, RefreshCwIcon, Trash2Icon, UsersIcon } = await importShared("lucide-react");
680
+ var { useCallback, useEffect, useRef, useState } = await importShared("react");
681
+ function PermissionChips({ user }) {
682
+ if (user.permissions.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
683
+ className: "rmq:text-xs rmq:text-default-400",
684
+ children: "none"
685
+ });
686
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
687
+ className: "rmq:flex rmq:flex-wrap rmq:gap-1",
688
+ children: user.permissions.map((permission) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
689
+ size: "sm",
690
+ variant: "soft",
691
+ title: `configure: ${permission.configure}\nwrite: ${permission.write}\nread: ${permission.read}`,
692
+ children: permission.vhost
693
+ }, permission.vhost))
694
+ });
695
+ }
696
+ function RabbitmqUserPanel({ mqttServerId }) {
697
+ const { result } = useDetection(mqttServerId);
698
+ const [data, setData] = useState(null);
699
+ const [loading, setLoading] = useState(false);
700
+ const [loadError, setLoadError] = useState(null);
701
+ const [formOpen, setFormOpen] = useState(false);
702
+ const [formUser, setFormUser] = useState(null);
703
+ const [permissionsUser, setPermissionsUser] = useState(null);
704
+ const [userToDelete, setUserToDelete] = useState(null);
705
+ const [deleting, setDeleting] = useState(false);
706
+ const [deleteError, setDeleteError] = useState(null);
707
+ const mounted = useRef(true);
708
+ useEffect(() => {
709
+ mounted.current = true;
710
+ return () => {
711
+ mounted.current = false;
712
+ };
713
+ }, []);
714
+ const reload = useCallback(async () => {
715
+ setLoading(true);
716
+ setLoadError(null);
717
+ try {
718
+ const list = await fetchUsers(mqttServerId);
719
+ if (mounted.current) {
720
+ setData(list);
721
+ setPermissionsUser((current) => current ? list.users.find((user) => user.name === current.name) ?? current : null);
722
+ }
723
+ } catch (err) {
724
+ if (mounted.current) setLoadError(err instanceof Error ? err.message : "Loading users failed.");
725
+ } finally {
726
+ if (mounted.current) setLoading(false);
727
+ }
728
+ }, [mqttServerId]);
729
+ const manageable = result?.isRabbitMQ === true && result.authOk;
730
+ useEffect(() => {
731
+ if (manageable) reload();
732
+ }, [manageable, reload]);
733
+ if (!manageable) return null;
734
+ const confirmDelete = async () => {
735
+ if (!userToDelete) return;
736
+ setDeleting(true);
737
+ setDeleteError(null);
738
+ try {
739
+ await deleteUser(mqttServerId, userToDelete.name);
740
+ setUserToDelete(null);
741
+ reload();
742
+ } catch (err) {
743
+ setDeleteError(err instanceof Error ? err.message : "Deleting the user failed.");
744
+ } finally {
745
+ setDeleting(false);
746
+ }
747
+ };
748
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1, {
749
+ "data-cy": `rabbitmq-user-panel-${mqttServerId}`,
750
+ className: "rmq:w-full rmq:border rmq:border-default-200 rmq:dark:border-default-100",
751
+ children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1.Header, {
753
+ className: "rmq:flex rmq:flex-row rmq:items-center rmq:justify-between rmq:gap-2",
754
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
755
+ className: "rmq:flex rmq:items-center rmq:gap-2",
756
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(UsersIcon, { className: "rmq:w-5 rmq:h-5 rmq:text-accent-soft-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
757
+ className: "rmq:text-base rmq:font-semibold rmq:text-default-700",
758
+ children: "RabbitMQ users"
759
+ })]
760
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
761
+ className: "rmq:flex rmq:items-center rmq:gap-2",
762
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
763
+ isIconOnly: true,
764
+ size: "sm",
765
+ variant: "ghost",
766
+ "aria-label": "Reload RabbitMQ users",
767
+ onPress: () => void reload(),
768
+ isDisabled: loading,
769
+ "data-cy": `rabbitmq-user-panel-reload-${mqttServerId}`,
770
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { size: "sm" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RefreshCwIcon, { className: "rmq:w-4 rmq:h-4" })
771
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
772
+ size: "sm",
773
+ variant: "primary",
774
+ onPress: () => {
775
+ setFormUser(null);
776
+ setFormOpen(true);
777
+ },
778
+ "data-cy": `rabbitmq-user-panel-create-button-${mqttServerId}`,
779
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlusIcon, { className: "rmq:w-4 rmq:h-4" }), "Add user"]
780
+ })]
781
+ })]
782
+ }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card$1.Content, {
784
+ className: "rmq:flex rmq:flex-col rmq:gap-3",
785
+ children: [
786
+ loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, {
787
+ status: "danger",
788
+ "data-cy": "rabbitmq-user-panel-error-alert",
789
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: loadError }) })
790
+ }),
791
+ !data && !loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
792
+ className: "rmq:flex rmq:items-center rmq:justify-center rmq:p-4",
793
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { "data-cy": "rabbitmq-user-panel-loading-spinner" })
794
+ }),
795
+ data && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Table, {
796
+ "data-cy": "rabbitmq-user-panel-table",
797
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableScrollContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableContent, {
798
+ "aria-label": "RabbitMQ users",
799
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableHeader, { children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, {
801
+ isRowHeader: true,
802
+ children: "Username"
803
+ }),
804
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: "Tags" }),
805
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: "Vhost access" }),
806
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableColumn, { children: "Actions" })
807
+ ] }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, {
808
+ items: data.users,
809
+ children: (user) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
810
+ id: user.name,
811
+ children: [
812
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
813
+ className: "rmq:whitespace-nowrap rmq:font-medium",
814
+ children: user.name
815
+ }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: user.tags.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
817
+ className: "rmq:text-xs rmq:text-default-400",
818
+ children: "none"
819
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
820
+ className: "rmq:flex rmq:flex-wrap rmq:gap-1",
821
+ children: user.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Chip, {
822
+ size: "sm",
823
+ color: "accent",
824
+ variant: "soft",
825
+ children: tag
826
+ }, tag))
827
+ }) }),
828
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PermissionChips, { user }) }),
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
830
+ className: "rmq:flex rmq:flex-row rmq:gap-1",
831
+ children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
833
+ isIconOnly: true,
834
+ size: "sm",
835
+ variant: "ghost",
836
+ "aria-label": `Edit user ${user.name}`,
837
+ onPress: () => {
838
+ setFormUser(user);
839
+ setFormOpen(true);
840
+ },
841
+ "data-cy": `rabbitmq-user-edit-button-${user.name}`,
842
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PencilIcon, { className: "rmq:w-4 rmq:h-4" })
843
+ }),
844
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
845
+ isIconOnly: true,
846
+ size: "sm",
847
+ variant: "ghost",
848
+ "aria-label": `Edit permissions of ${user.name}`,
849
+ onPress: () => setPermissionsUser(user),
850
+ "data-cy": `rabbitmq-user-permissions-button-${user.name}`,
851
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KeyRoundIcon, { className: "rmq:w-4 rmq:h-4" })
852
+ }),
853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
854
+ isIconOnly: true,
855
+ size: "sm",
856
+ variant: "ghost",
857
+ "aria-label": `Delete user ${user.name}`,
858
+ onPress: () => {
859
+ setDeleteError(null);
860
+ setUserToDelete(user);
861
+ },
862
+ "data-cy": `rabbitmq-user-delete-button-${user.name}`,
863
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2Icon, { className: "rmq:w-4 rmq:h-4 rmq:text-danger" })
864
+ })
865
+ ]
866
+ }) })
867
+ ]
868
+ }, user.name)
869
+ })]
870
+ }) })
871
+ })
872
+ ]
873
+ }),
874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqUserFormModal, {
875
+ mqttServerId,
876
+ isOpen: formOpen,
877
+ user: formUser,
878
+ vhosts: data?.vhosts ?? [],
879
+ onClose: () => setFormOpen(false),
880
+ onSaved: () => void reload()
881
+ }),
882
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqPermissionsModal, {
883
+ mqttServerId,
884
+ isOpen: permissionsUser !== null,
885
+ user: permissionsUser,
886
+ vhosts: data?.vhosts ?? [],
887
+ onClose: () => setPermissionsUser(null),
888
+ onSaved: () => void reload()
889
+ }),
890
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Modal, {
891
+ isOpen: userToDelete !== null,
892
+ onOpenChange: (open) => {
893
+ if (!open) setUserToDelete(null);
894
+ },
895
+ "data-cy": `rabbitmq-user-delete-modal-${mqttServerId}`,
896
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalBackdrop, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalContainer, {
897
+ size: "sm",
898
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalDialog, { children: ({ close }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
899
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModalHeading, { children: "Delete user" }) }),
900
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalBody, {
901
+ className: "rmq:flex rmq:flex-col rmq:gap-3",
902
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
903
+ "Delete the RabbitMQ user ",
904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: userToDelete?.name }),
905
+ "? Connected clients using this account will be disconnected and its permissions removed."
906
+ ] }), deleteError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Alert, {
907
+ status: "danger",
908
+ "data-cy": "rabbitmq-user-delete-error-alert",
909
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDescription, { children: deleteError }) })
910
+ })]
911
+ }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ModalFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
913
+ variant: "secondary",
914
+ onPress: close,
915
+ "data-cy": "rabbitmq-user-delete-cancel-button",
916
+ children: "Cancel"
917
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
918
+ variant: "danger",
919
+ onPress: () => void confirmDelete(),
920
+ isPending: deleting,
921
+ "data-cy": "rabbitmq-user-delete-confirm-button",
922
+ children: "Delete"
923
+ })] })
924
+ ] }) })
925
+ }) })
926
+ })
927
+ ]
928
+ });
929
+ }
930
+ //#endregion
931
+ //#region frontend/src/plugin.tsx
932
+ var { Card } = await importShared("@heroui/react");
933
+ var { RabbitIcon } = await importShared("lucide-react");
934
+ var MQTT_SERVER_DETAIL_SLOT = "mqtt.server.detail";
935
+ var MQTT_SERVER_LIST_ROW_SLOT = "mqtt.server.list.row";
936
+ function RabbitmqDetailSlot({ mqttServerId }) {
937
+ const { result } = useDetection(mqttServerId);
938
+ if (!result?.isRabbitMQ) return null;
939
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
940
+ className: "rmq:flex rmq:flex-col rmq:gap-4 rmq:w-full",
941
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqStatusPanel, { mqttServerId }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqUserPanel, { mqttServerId })]
942
+ });
943
+ }
944
+ function RabbitmqPage() {
945
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
946
+ className: "rmq:flex rmq:flex-col rmq:gap-6 rmq:p-6 rmq:max-w-4xl rmq:mx-auto",
947
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
948
+ className: "rmq:flex rmq:items-center rmq:gap-3",
949
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitIcon, { className: "rmq:w-6 rmq:h-6 rmq:text-accent-soft-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
950
+ className: "rmq:text-2xl rmq:font-semibold rmq:text-default-800",
951
+ children: "RabbitMQ"
952
+ })]
953
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
954
+ className: "rmq:border rmq:border-default-200 rmq:dark:border-default-100",
955
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card.Content, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
956
+ className: "rmq:text-sm rmq:text-default-500",
957
+ children: "RabbitMQ management plugin. RabbitMQ MQTT servers show a detection badge and connection-status panel in the MQTT settings, and broker users can be managed (create, edit, permissions, delete) from the MQTT server detail view."
958
+ }) })
959
+ })]
960
+ });
961
+ }
962
+ var RabbitmqPlugin = class {
963
+ getPluginName() {
964
+ return "rabbitmq-plugin@0.1.0";
965
+ }
966
+ getDependencies() {
967
+ return [];
968
+ }
969
+ init(_store) {}
970
+ activate() {}
971
+ deactivate() {}
972
+ onApiAuthStateChange(_authData) {}
973
+ onApiEndpointChange(_endpoint) {}
974
+ getRoutes() {
975
+ return [{
976
+ path: "/rabbitmq",
977
+ authRequired: "resources.update",
978
+ element: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqPage, {})
979
+ }];
980
+ }
981
+ getSidebarItems() {
982
+ return [{
983
+ label: "RabbitMQ",
984
+ path: "/rabbitmq",
985
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitIcon, { className: "rmq:w-5 rmq:h-5" })
986
+ }];
987
+ }
988
+ getSlotContributions() {
989
+ return [{
990
+ slotId: MQTT_SERVER_DETAIL_SLOT,
991
+ key: "rabbitmq-mqtt-detail",
992
+ render: (context) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqDetailSlot, { mqttServerId: context.mqttServerId })
993
+ }, {
994
+ slotId: MQTT_SERVER_LIST_ROW_SLOT,
995
+ key: "rabbitmq-mqtt-list-row",
996
+ render: (context) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RabbitmqListBadge, { mqttServerId: context.mqttServerId })
997
+ }];
998
+ }
999
+ };
1000
+ //#endregion
1001
+ export { RabbitmqPlugin as default };